《黑马头条》 ElectricSearch 分词器 联想词 MangoDB day08-平台管理[实战]作业

07 app端文章搜索

1) 今日内容介绍

1.1)App端搜索-效果图

1.2)今日内容

2) 搭建ElasticSearch环境

2.1) 拉取镜像

docker pull elasticsearch:7.4.0

2.2) 创建容器

docker run -id --name elasticsearch -d --restart=always -p 9200:9200 -p 9300:9300 -v /usr/share/elasticsearch/plugins:/usr/share/elasticsearch/plugins -e "discovery.type=single-node" elasticsearch:7.4.0

2.3) 配置中文分词器 ik

因为在创建elasticsearch容器的时候,映射了目录,所以可以在宿主机上进行配置ik中文分词器

在去选择ik分词器的时候,需要与elasticsearch的版本好对应上

把资料中的elasticsearch-analysis-ik-7.4.0.zip上传到服务器上,放到对应目录(plugins)解压

#切换目录
cd /usr/share/elasticsearch/plugins
#新建目录
mkdir analysis-ik
cd analysis-ik
#root根目录中拷贝文件
mv elasticsearch-analysis-ik-7.4.0.zip /usr/share/elasticsearch/plugins/analysis-ik
#解压文件
cd /usr/share/elasticsearch/plugins/analysis-ik
unzip elasticsearch-analysis-ik-7.4.0.zip

2.4) 使用postman测试

192.168.200.130:9200/_analyze
{"analyzer":"ik_max_word","text":"欢迎来到黑马程序员学习"
}

3) app端文章搜索

3.1) 需求分析

  • 用户输入关键可搜索文章列表

  • 关键词高亮显示

  • 文章列表展示与home展示一样,当用户点击某一篇文章,可查看文章详情

3.2) 思路分析

为了加快检索的效率,在查询的时候不会直接从数据库中查询文章,需要在elasticsearch中进行高速检索。

3.3) 创建索引和映射

使用postman添加映射

put请求 : http://192.168.200.130:9200/app_info_article

{"mappings":{"properties":{"id":{"type":"long"},"publishTime":{"type":"date"},"layout":{"type":"integer"},"images":{"type":"keyword","index": false},"staticUrl":{"type":"keyword","index": false},"authorId": {"type": "long"},"authorName": {"type": "text"},"title":{"type":"text","analyzer":"ik_smart"},"content":{"type":"text","analyzer":"ik_smart"}}}
}

GET请求查询映射:http://192.168.200.130:9200/app_info_article

DELETE请求,删除索引及映射:http://192.168.200.130:9200/app_info_article

GET请求,查询所有文档:http://192.168.200.130:9200/app_info_article/_search

3.4) 数据初始化到索引库

3.4.1)导入es-init到heima-leadnews-test工程下

3.4.1)查询所有的文章信息,批量导入到es索引库中

package com.heima.es;
import java.util.List;@SpringBootTest
@RunWith(SpringRunner.class)
public class ApArticleTest {@Autowiredprivate ApArticleMapper apArticleMapper;@Autowiredprivate RestHighLevelClient restHighLevelClient;/*** 注意:数据量的导入,如果数据量过大,需要分页导入* @throws Exception*/@Testpublic void init() throws Exception {//1.查询所有符合条件的文章数据List<SearchArticleVo> searchArticleVos = apArticleMapper.loadArticleList();//2.批量导入到es索引库BulkRequest bulkRequest = new BulkRequest("app_info_article");for (SearchArticleVo searchArticleVo : searchArticleVos) {IndexRequest indexRequest = new IndexRequest().id(searchArticleVo.getId().toString()).source(JSON.toJSONString(searchArticleVo), XContentType.JSON);//批量添加数据bulkRequest.add(indexRequest);}restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);}}

3.4.3)测试

postman查询所有的es中数据 GET请求: http://192.168.200.130:9200/app_info_article/_search

3.5) 文章搜索功能实现

3.5.1)搭建搜索微服务

(1)导入 heima-leadnews-search

(2)在heima-leadnews-service的pom中添加依赖

<!--elasticsearch-->
<dependency><groupId>org.elasticsearch.client</groupId><artifactId>elasticsearch-rest-high-level-client</artifactId><version>7.4.0</version>
</dependency>
<dependency><groupId>org.elasticsearch.client</groupId><artifactId>elasticsearch-rest-client</artifactId><version>7.4.0</version>
</dependency>
<dependency><groupId>org.elasticsearch</groupId><artifactId>elasticsearch</artifactId><version>7.4.0</version>
</dependency>

(3)nacos配置中心leadnews-search

spring:autoconfigure:exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
elasticsearch:host: 192.168.200.130port: 9200

3.5.2) 搜索接口定义

package com.heima.search.controller.v1;
import java.io.IOException;@RestController
@RequestMapping("/api/v1/article/search")
public class ArticleSearchController {@PostMapping("/search")public ResponseResult search(@RequestBody UserSearchDto dto) throws IOException {return null;}
}

UserSearchDto

package com.heima.model.search.dtos;
import lombok.Data;
import java.util.Date;@Data
public class UserSearchDto {/*** 搜索关键字*/String searchWords;/*** 当前页*/int pageNum;/*** 分页条数*/int pageSize;/*** 最小时间*/Date minBehotTime;public int getFromIndex(){if(this.pageNum<1)return 0;if(this.pageSize<1) this.pageSize = 10;return this.pageSize * (pageNum-1);}
}

3.5.3) 业务层实现

创建业务层接口:ApArticleSearchService

package com.heima.search.service;
import java.io.IOException;public interface ArticleSearchService {/**ES文章分页搜索@return*/ResponseResult search(UserSearchDto userSearchDto) throws IOException;
}

实现类:

package com.heima.search.service.impl;
import java.util.Map;@Service
@Slf4j
public class ArticleSearchServiceImpl implements ArticleSearchService {@Autowiredprivate RestHighLevelClient restHighLevelClient;/*** es文章分页检索** @param dto* @return*/@Overridepublic ResponseResult search(UserSearchDto dto) throws IOException {//1.检查参数if(dto == null || StringUtils.isBlank(dto.getSearchWords())){return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);}//2.设置查询条件SearchRequest searchRequest = new SearchRequest("app_info_article");SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();//布尔查询BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();//关键字的分词之后查询QueryStringQueryBuilder queryStringQueryBuilder = QueryBuilders.queryStringQuery(dto.getSearchWords()).field("title").field("content").defaultOperator(Operator.OR);boolQueryBuilder.must(queryStringQueryBuilder);//查询小于mindate的数据RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("publishTime").lt(dto.getMinBehotTime().getTime());boolQueryBuilder.filter(rangeQueryBuilder);//分页查询searchSourceBuilder.from(0);searchSourceBuilder.size(dto.getPageSize());//按照发布时间倒序查询searchSourceBuilder.sort("publishTime", SortOrder.DESC);//设置高亮  titleHighlightBuilder highlightBuilder = new HighlightBuilder();highlightBuilder.field("title");highlightBuilder.preTags("<font style='color: red; font-size: inherit;'>");highlightBuilder.postTags("</font>");searchSourceBuilder.highlighter(highlightBuilder);searchSourceBuilder.query(boolQueryBuilder);searchRequest.source(searchSourceBuilder);SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);//3.结果封装返回List<Map> list = new ArrayList<>();SearchHit[] hits = searchResponse.getHits().getHits();for (SearchHit hit : hits) {String json = hit.getSourceAsString();Map map = JSON.parseObject(json, Map.class);//处理高亮if(hit.getHighlightFields() != null && hit.getHighlightFields().size() > 0){Text[] titles = hit.getHighlightFields().get("title").getFragments();String title = StringUtils.join(titles);//高亮标题map.put("h_title",title);}else {//原始标题map.put("h_title",map.get("title"));}list.add(map);}return ResponseResult.okResult(list);}
}

3.5.4) 控制层实现

新建控制器ArticleSearchController

package com.heima.search.controller.v1;
import java.io.IOException;@RestController
@RequestMapping("/api/v1/article/search")
public class ArticleSearchController {@Autowiredprivate ArticleSearchService articleSearchService;@PostMapping("/search")public ResponseResult search(@RequestBody UserSearchDto dto) throws IOException {return articleSearchService.search(dto);}
}

3.5.5) 测试

需要在app的网关中添加搜索微服务的路由配置

#搜索微服务
- id: leadnews-searchuri: lb://leadnews-searchpredicates:- Path=/search/**filters:- StripPrefix= 1

启动项目进行测试,至少要启动文章微服务,用户微服务,搜索微服务,app网关微服务,app前端工程

3.6) 文章自动审核构建索引

3.6.1)思路分析

3.6.2)文章微服务发送消息

1.把SearchArticleVo放到model工程下

package com.heima.model.search.vos;import lombok.Data;import java.util.Date;@Data
public class SearchArticleVo {// 文章idprivate Long id;// 文章标题private String title;// 文章发布时间private Date publishTime;// 文章布局private Integer layout;// 封面private String images;// 作者idprivate Long authorId;// 作者名词private String authorName;//静态urlprivate String staticUrl;//文章内容private String content;}

2.文章微服务的ArticleFreemarkerService中的buildArticleToMinIO方法中收集数据并发送消息

完整代码如下:

package com.heima.article.service.impl;
import java.util.Map;@Service
@Slf4j
@Transactional
public class ArticleFreemarkerServiceImpl implements ArticleFreemarkerService {***前面没变//4.4 修改ap_article表,保存static_url字段apArticleService.update(Wrappers.<ApArticle>lambdaUpdate().eq(ApArticle::getId,apArticle.getId()).set(ApArticle::getStaticUrl,path));//发送消息,创建索引createArticleESIndex(apArticle,content,path);}}@Autowiredprivate KafkaTemplate<String,String> kafkaTemplate;/*** 送消息,创建索引* @param apArticle* @param content* @param path*/private void createArticleESIndex(ApArticle apArticle, String content, String path) {SearchArticleVo vo = new SearchArticleVo();BeanUtils.copyProperties(apArticle,vo);vo.setContent(content);vo.setStaticUrl(path);kafkaTemplate.send(ArticleConstants.ARTICLE_ES_SYNC_TOPIC, JSON.toJSONString(vo));}}

在ArticleConstants类中添加新的常量,完整代码如下

package com.heima.common.constants;public class ArticleConstants {public static final Short LOADTYPE_LOAD_MORE = 1;public static final Short LOADTYPE_LOAD_NEW = 2;public static final String DEFAULT_TAG = "__all__";public static final String ARTICLE_ES_SYNC_TOPIC = "article.es.sync.topic";
}

3.文章微服务集成kafka发送消息

在文章微服务的nacos的配置中心添加如下配置

kafka:bootstrap-servers: 192.168.200.130:9092producer:retries: 10key-serializer: org.apache.kafka.common.serialization.StringSerializervalue-serializer: org.apache.kafka.common.serialization.StringSerializer

3.6.3)搜索微服务接收消息并创建索引

1.搜索微服务中添加kafka的配置,nacos配置如下

spring:kafka:bootstrap-servers: 192.168.200.130:9092consumer:group-id: ${spring.application.name}key-deserializer: org.apache.kafka.common.serialization.StringDeserializervalue-deserializer: org.apache.kafka.common.serialization.StringDeserializer

2.定义监听接收消息,保存索引数据

package com.heima.search.listener;
import java.io.IOException;@Component
@Slf4j
public class SyncArticleListener {@Autowiredprivate RestHighLevelClient restHighLevelClient;@KafkaListener(topics = ArticleConstants.ARTICLE_ES_SYNC_TOPIC)public void onMessage(String message){if(StringUtils.isNotBlank(message)){log.info("SyncArticleListener,message={}",message);SearchArticleVo searchArticleVo = JSON.parseObject(message, SearchArticleVo.class);IndexRequest indexRequest = new IndexRequest("app_info_article");indexRequest.id(searchArticleVo.getId().toString());indexRequest.source(message, XContentType.JSON);try {restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);} catch (IOException e) {e.printStackTrace();log.error("sync es error={}",e);}}}
}

4) app端搜索-搜索记录

4.1) 需求分析

  • 展示用户的搜索记录10条,按照搜索关键词的时间倒序

  • 可以删除搜索记录

  • 保存历史记录,保存10条,多余的则删除最久的历史记录

4.2)数据存储说明

用户的搜索记录,需要给每一个用户都保存一份,数据量较大,要求加载速度快,通常这样的数据存储到mongodb更合适,不建议直接存储到关系型数据库中

4.3)MongoDB安装及集成

4.3.1)安装MongoDB

拉取镜像

docker pull mongo

创建容器

docker run -di --name mongo-service --restart=always -p 27017:27017 -v ~/data/mongodata:/data mongo

4.3.2)导入资料中的mongo-demo项目到heima-leadnews-test中

其中有三项配置比较关键:

第一:mongo依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>

第二:mongo配置

server:port: 9998
spring:data:mongodb:host: 192.168.200.130port: 27017database: leadnews-history

第三:映射

package com.itheima.mongo.pojo;
import java.io.Serializable;
import java.util.Date;
/*** <p>* 联想词表* </p>** @author itheima*/
@Data
@Document("ap_associate_words")
public class ApAssociateWords implements Serializable {private static final long serialVersionUID = 1L;private String id;/*** 联想词*/private String associateWords;/*** 创建时间*/private Date createdTime;
}

4.3.3)核心方法

package com.itheima.mongo.test;
import java.util.Date;
import java.util.List;@SpringBootTest(classes = MongoApplication.class)
@RunWith(SpringRunner.class)
public class MongoTest {@Autowiredprivate MongoTemplate mongoTemplate;//保存@Testpublic void saveTest(){/*for (int i = 0; i < 10; i++) {ApAssociateWords apAssociateWords = new ApAssociateWords();apAssociateWords.setAssociateWords("黑马头条");apAssociateWords.setCreatedTime(new Date());mongoTemplate.save(apAssociateWords);}*/ApAssociateWords apAssociateWords = new ApAssociateWords();apAssociateWords.setAssociateWords("黑马直播");apAssociateWords.setCreatedTime(new Date());mongoTemplate.save(apAssociateWords);}//查询一个@Testpublic void saveFindOne(){ApAssociateWords apAssociateWords = mongoTemplate.findById("60bd973eb0c1d430a71a7928", ApAssociateWords.class);System.out.println(apAssociateWords);}//条件查询@Testpublic void testQuery(){Query query = Query.query(Criteria.where("associateWords").is("黑马头条")).with(Sort.by(Sort.Direction.DESC,"createdTime"));List<ApAssociateWords> apAssociateWordsList = mongoTemplate.find(query, ApAssociateWords.class);System.out.println(apAssociateWordsList);}@Testpublic void testDel(){mongoTemplate.remove(Query.query(Criteria.where("associateWords").is("黑马头条")),ApAssociateWords.class);}
}

4.4)保存搜索记录

4.4.1)实现思路

用户输入关键字进行搜索的异步记录关键字

用户搜索记录对应的集合,对应实体类:

package com.heima.search.pojos;import lombok.Data;
import org.springframework.data.mongodb.core.mapping.Document;import java.io.Serializable;
import java.util.Date;/*** <p>* APP用户搜索信息表* </p>* @author itheima*/
@Data
@Document("ap_user_search")
public class ApUserSearch implements Serializable {private static final long serialVersionUID = 1L;/*** 主键*/private String id;/*** 用户ID*/private Integer userId;/*** 搜索词*/private String keyword;/*** 创建时间*/private Date createdTime;}

4.4.2)实现步骤

1.搜索微服务集成mongodb

①:pom依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>

②:nacos配置

spring:data:mongodb:host: 192.168.200.130port: 27017database: leadnews-history

③:在当天资料中找到对应的实体类拷贝到搜索微服务下

2.创建ApUserSearchService新增insert方法

public interface ApUserSearchService {/*** 保存用户搜索历史记录* @param keyword* @param userId*/public void insert(String keyword,Integer userId);
}

实现类:

@Service
@Slf4j
public class ApUserSearchServiceImpl implements ApUserSearchService {@Autowiredprivate MongoTemplate mongoTemplate;/*** 保存用户搜索历史记录* @param keyword* @param userId*/@Override@Asyncpublic void insert(String keyword, Integer userId) {//1.查询当前用户的搜索关键词Query query = Query.query(Criteria.where("userId").is(userId).and("keyword").is(keyword));ApUserSearch apUserSearch = mongoTemplate.findOne(query, ApUserSearch.class);//2.存在 更新创建时间if(apUserSearch != null){apUserSearch.setCreatedTime(new Date());mongoTemplate.save(apUserSearch);return;}//3.不存在,判断当前历史记录总数量是否超过10apUserSearch = new ApUserSearch();apUserSearch.setUserId(userId);apUserSearch.setKeyword(keyword);apUserSearch.setCreatedTime(new Date());Query query1 = Query.query(Criteria.where("userId").is(userId));query1.with(Sort.by(Sort.Direction.DESC,"createdTime"));List<ApUserSearch> apUserSearchList = mongoTemplate.find(query1, ApUserSearch.class);if(apUserSearchList == null || apUserSearchList.size() < 10){mongoTemplate.save(apUserSearch);}else {ApUserSearch lastUserSearch = apUserSearchList.get(apUserSearchList.size() - 1);mongoTemplate.findAndReplace(Query.query(Criteria.where("id").is(lastUserSearch.getId())),apUserSearch);}}
}

3.参考自媒体相关微服务,在搜索微服务中获取当前登录的用户

4.在ArticleSearchService的search方法中调用保存历史记录

完整代码如下:

package com.heima.search.service.impl;
import java.util.Map;@Service
@Slf4j
public class ArticleSearchServiceImpl implements ArticleSearchService {@Autowiredprivate ApUserSearchService apUserSearchService;/*** es文章分页检索** @param dto* @return*/@Overridepublic ResponseResult search(UserSearchDto dto) throws IOException {//1.检查参数if(dto == null || StringUtils.isBlank(dto.getSearchWords())){return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);}ApUser user = AppThreadLocalUtil.getUser();//异步调用 保存搜索记录if(user != null && dto.getFromIndex() == 0){apUserSearchService.insert(dto.getSearchWords(), user.getId());}//2.设置查询条件SearchRequest searchRequest = new SearchRequest("app_info_article");SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();****都没变return ResponseResult.okResult(list);}
}

5.保存历史记录中开启异步调用,添加注解@Async

6.在搜索微服务引导类上开启异步调用

7.测试,搜索后查看结果

4.5) 加载搜索记录列表

4.5.1) 思路分析

按照当前用户,按照时间倒序查询

说明

接口路径

/api/v1/history/load

请求方式

POST

参数

响应结果

ResponseResult

4.5.2) 接口定义

/*** <p>* APP用户搜索信息表 前端控制器* </p>** @author itheima*/
@Slf4j
@RestController
@RequestMapping("/api/v1/history")
public class ApUserSearchController{@PostMapping("/load")@Overridepublic ResponseResult findUserSearch() {return null;}}

4.5.3) mapper

已定义

4.5.4) 业务层

在ApUserSearchService中新增方法

/**查询搜索历史@return*/
ResponseResult findUserSearch();

实现方法

 /*** 查询搜索历史** @return*/
@Override
public ResponseResult findUserSearch() {//获取当前用户ApUser user = AppThreadLocalUtil.getUser();if(user == null){return ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);}//根据用户查询数据,按照时间倒序List<ApUserSearch> apUserSearches = mongoTemplate.find(Query.query(Criteria.where("userId").is(user.getId())).with(Sort.by(Sort.Direction.DESC, "createdTime")), ApUserSearch.class);return ResponseResult.okResult(apUserSearches);
}

4.5.5) 控制器

/*** <p>* APP用户搜索信息表 前端控制器* </p>* @author itheima*/
@Slf4j
@RestController
@RequestMapping("/api/v1/history")
public class ApUserSearchController{@Autowiredprivate ApUserSearchService apUserSearchService;@PostMapping("/load")public ResponseResult findUserSearch() {return apUserSearchService.findUserSearch();}}

4.5.6) 测试

打开app的搜索页面,可以查看搜索记录列表

4.6) 删除搜索记录

4.6.1) 思路分析

按照搜索历史id删除

说明

接口路径

/api/v1/history/del

请求方式

POST

参数

HistorySearchDto

响应结果

ResponseResult

4.6.2) 接口定义

在ApUserSearchController接口新增方法

@PostMapping("/del")
public ResponseResult delUserSearch(@RequestBody HistorySearchDto historySearchDto) {return null;
}

HistorySearchDto

@Data
public class HistorySearchDto {/*** 接收搜索历史记录id*/String id;
}

4.6.3) 业务层

在ApUserSearchService中新增方法

 /**删除搜索历史@param historySearchDto@return*/
ResponseResult delUserSearch(HistorySearchDto historySearchDto);

实现方法

/*** 删除历史记录** @param dto* @return*/
@Override
public ResponseResult delUserSearch(HistorySearchDto dto) {//1.检查参数if(dto.getId() == null){return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);}//2.判断是否登录ApUser user = AppThreadLocalUtil.getUser();if(user == null){return ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);}//3.删除mongoTemplate.remove(Query.query(Criteria.where("userId").is(user.getId()).and("id").is(dto.getId())),ApUserSearch.class);return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
}

4.6.4) 控制器

修改ApUserSearchController,补全方法

@PostMapping("/del")
public ResponseResult delUserSearch(@RequestBody HistorySearchDto historySearchDto) {return apUserSearchService.delUserSearch(historySearchDto);
}

4.6.5) 测试

打开app可以删除搜索记录

5) app端搜索-关键字联想词

5.1 需求分析

  • 根据用户输入的关键字展示联想词

对应实体类

package com.heima.search.pojos;
import lombok.Data;
import org.springframework.data.mongodb.core.mapping.Document;import java.io.Serializable;
import java.util.Date;/*** <p>* 联想词表* </p>** @author itheima*/
@Data
@Document("ap_associate_words")
public class ApAssociateWords implements Serializable {private static final long serialVersionUID = 1L;private String id;/*** 联想词*/private String associateWords;/*** 创建时间*/private Date createdTime;}

5.2)搜索词-数据来源

通常是网上搜索频率比较高的一些词,通常在企业中有两部分来源:

第一:自己维护搜索词

通过分析用户搜索频率较高的词,按照排名作为搜索词

第二:第三方获取

关键词规划师(百度)、5118、爱站网

导入资料中的ap_associate_words.js脚本到mongo中

5.3 功能实现

5.3.1 接口定义

说明

接口路径

/api/v1/associate/search

请求方式

POST

参数

UserSearchDto

响应结果

ResponseResult

新建接口

package com.heima.search.controller.v1;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/api/v1/associate")
public class ApAssociateWordsController {@PostMapping("/search")public ResponseResult search(@RequestBody UserSearchDto userSearchDto) {return null;}
}

5.3.3 业务层

新建联想词业务层接口

package com.heima.search.service;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.search.dtos.UserSearchDto;/*** <p>* 联想词表 服务类* </p>** @author itheima*/
public interface ApAssociateWordsService {/**联想词@param userSearchDto@return*/ResponseResult findAssociate(UserSearchDto userSearchDto);}

实现类

package com.heima.search.service.impl;
import java.util.List;/*** @Description:* @Version: V1.0*/
@Service
public class ApAssociateWordsServiceImpl implements ApAssociateWordsService {@AutowiredMongoTemplate mongoTemplate;/*** 联想词* @param userSearchDto* @return*/@Overridepublic ResponseResult findAssociate(UserSearchDto userSearchDto) {//1 参数检查if(userSearchDto == null || StringUtils.isBlank(userSearchDto.getSearchWords())){return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);}//分页检查if (userSearchDto.getPageSize() > 20) {userSearchDto.setPageSize(20);}//3 执行查询 模糊查询Query query = Query.query(Criteria.where("associateWords").regex(".*?\\" + userSearchDto.getSearchWords() + ".*"));query.limit(userSearchDto.getPageSize());List<ApAssociateWords> wordsList = mongoTemplate.find(query, ApAssociateWords.class);return ResponseResult.okResult(wordsList);}
}

5.3.4 控制器

新建联想词控制器

package com.heima.search.controller.v1;/*** <p>* 联想词表 前端控制器* </p>* @author itheima*/
@Slf4j
@RestController
@RequestMapping("/api/v1/associate")
public class ApAssociateWordsController{@Autowiredprivate ApAssociateWordsService apAssociateWordsService;@PostMapping("/search")public ResponseResult findAssociate(@RequestBody UserSearchDto userSearchDto) {return apAssociateWordsService.findAssociate(userSearchDto);}
}

5.3.5 测试

同样,打开前端联调测试效果


平台管理-需求说明

要求:

  • 每个组长要分配任务,共同完成这些需求

  • 需要创建一个开源项目(每个组创建一个),其他组员共同提交到这个仓库里,最后合并为一个完整的项目

  • 组长创建一个git项目

  • 基础代码是day07完成后的代码

  • 每个组员需要克隆到本地

  • 每个组员创建一个分支去开发

  • 合并为一个完整的项目

28号晚上答辩

  • 展示完成的项目功能

  • 给同学在讲台上,人选:最好是不爱说话或不敢说话的人

0)平台管理前端

查看资料中的admin-web文件夹,使用nginx部署

nginx中的配置为:

upstream  heima-admin-gateway{server localhost:6001;
}server {listen 8803;location / {root D:/workspace/admin-web/;index index.html;}location ~/service_6001/(.*) {proxy_pass http://heima-admin-gateway/$1;proxy_set_header HOST $host;  # 不改变源请求头的值proxy_pass_request_body on;  #开启获取请求体proxy_pass_request_headers on;  #开启获取请求头proxy_set_header X-Real-IP $remote_addr;   # 记录真实发出请求的客户端IPproxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;  #记录代理信息}
}

0 准备工作

0.1数据库和类

数据库导入记得加表名;

给了两个类ApUserRealname、ApUser 按照包名导入就可以啦

0.2 项目结构参考前面的项目

model层、Service层、bootstrap连接nacos

1)登录及网关

1.1)登录

平台管理的表,请查看资料中导入到数据库中 leadnews_admin.sql

  • 用户根据用户名和密码登录

  • 密码需要手动加盐验证

  • 需要返回用户的token和用户信息

1登录接口文档

接口地址:/login/in

请求方式:POST

请求数据类型:application/json

响应数据类型:*/*

接口描述:

请求示例:

{"name": "","password": ""
}

请求参数:

参数名称

参数说明

in

是否必须

数据类型

schema

dto

dto

body

true

AdUserDto

AdUserDto

name

true

string

password

true

string

响应状态:

状态码

说明

schema

200

OK

ResponseResult

201

Created

401

Unauthorized

403

Forbidden

404

Not Found

响应参数:

参数名称

参数说明

类型

schema

code

integer(int32)

integer(int32)

data

object

errorMessage

string

host

string

响应示例:

{"host":null,"code":1002,"errorMessage":"数据不存在","data":null
}
{"host":null,"code":2,"errorMessage":"密码错误","data":null
}{"host":null,"code":200,"errorMessage":"操作成功","data":{"user":{"id":"3","name":"guest","password":"","salt":"","nickname":"gu","image":null,"phone":"13412345676","status":1,"email":"guest@qq.com","loginTime":1596092403000,"createdTime":1596092406000},"token":"eyJhbGciOiJIUzUxMiIsInppcCI6IkdaSVAifQ.H4sIAAAAAAAAADWLQQrEIAwA_5JzBTUaa3-jNGUtFIRYaFn2700Pe5thmC_so8ECawobbnE2jiubQJlMRe8Mxcghc6aIHiZoZcDiyCePwVqaQM6qt9wy-Hi7iOqH21HUyrmqld6V-er_M9F7Nm34ewAJrbr_gAAAAA.TxoXtzsWAdaqCLWeMUdSnMngZSXndTsoYq6Dz5_r_SDZWcMp8ZS2BJhxoRVHG4KxvOn2ZN3MATemX2EZ4KnrLw"}
}

1.1参考User的登录写法

AdUSerDto

package com.heima.model.admin.dtos;import io.swagger.annotations.ApiModelProperty;
import lombok.Data;@Data
public class AdUserDto {/*** 用户名*/@ApiModelProperty(value = "用户名",required = true)private String name;/*** 密码*/@ApiModelProperty(value = "密码",required = true)private String password;
}

AdUserMapper

package com.heima.admin.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.heima.model.admin.pojos.AdUser;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface AdUserMapper extends BaseMapper<AdUser> {}

LoginController

package com.heima.admin.controller.v1;import com.heima.admin.service.UserLoginService;
import com.heima.model.admin.pojos.AdUser;
import com.heima.model.common.dtos.ResponseResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/login")
public class LoginController {@Autowiredprivate UserLoginService userLoginService;@PostMapping("/in")public ResponseResult login(@RequestBody AdUser user) {return userLoginService.login(user);}
}

AdminLoginService

package com.heima.admin.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.heima.model.admin.pojos.AdUser;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.user.pojos.ApUser;public interface AdminLoginService extends IService<AdUser> {/*** 登录** @param dto* @return*/ResponseResult login(AdUser dto);
}

AdminLoginServiceImpl

package com.heima.admin.service.impl;import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.heima.admin.mapper.AdUserMapper;
import com.heima.admin.service.AdminLoginService;
import com.heima.model.admin.pojos.AdUser;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.utils.common.AppJwtUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;import java.util.HashMap;
import java.util.Map;@Service
@SuppressWarnings("all")
public class AdminLoginServiceImpl extends ServiceImpl<AdUserMapper, AdUser> implements AdminLoginService {@Autowiredprivate  AdUserMapper adUserMapper;/*** 登录* @param dto* @return*/@Overridepublic ResponseResult login(AdUser dto) {//1.正常登录 用户名和密码if(StringUtils.isNotBlank(dto.getPhone()) && StringUtils.isNotBlank(dto.getPassword())){//1.1 根据手机号查询用户信息AdUser dbUser = getOne(Wrappers.<AdUser>lambdaQuery().eq(AdUser::getName, dto.getName()));if(dbUser == null){return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST,"用户信息不存在");}//1.2 比对密码String salt = dbUser.getSalt();String password = dto.getPassword();String pswd = DigestUtils.md5DigestAsHex((password + salt).getBytes());if(!pswd.equals(dbUser.getPassword())){return ResponseResult.errorResult(AppHttpCodeEnum.LOGIN_PASSWORD_ERROR);}//1.3 返回数据  jwt  userString token = AppJwtUtil.getToken(dbUser.getId().longValue());Map<String,Object> map = new HashMap<>();map.put("token",token);dbUser.setSalt("");dbUser.setPassword("");map.put("user",dbUser);return ResponseResult.okResult(map);}elsereturn ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST,"用户信息不存在");}
}

AdminApplication

package com.heima.admin;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;/*** 实现后台管理系统*/
@SpringBootApplication
@EnableDiscoveryClient//集成注册中心
@MapperScan("com.heima.admin.mapper")
public class AdminApplication {public static void main(String[] args) {SpringApplication.run(AdminApplication.class, args);}
}

呵呵 真的逆天,网关不给我端口,让我咋猜;

一直报错502 试都试不了;就在我马上要发疯的时候,发现虽然day12的没有但是day11的有;ok我不急了;

。。。真的逆天,nacos没给,不知道网关咋配的一直登录不上

但是postMan测方法成功;

1.2)网关

平台管理端,是作为平台管理员使用的后台服务,所有后台的请求,都应该经过网关访问,需要创建平台管理的网关,并使用nacos配置

  • 需要校验jwt

  • 路由其他微服务

2)频道管理

2.1)新增

  • 前台输入内容进行频道的保存

  • 频道名词不能重复

2.2)查询列表

  • 查询需要按照创建时间倒序查询

  • 按照频道名称模糊查询

  • 可以按照状态进行精确查找(1:启用 true 0:禁用 false)

  • 分页查询

2.3)修改

  • 点击编辑后可以修改频道

  • 如果频道被引用则不能禁用

2.4)删除

只有禁用的频道才能删除

WmchannelController

package com.heima.wemedia.controller.v1;import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.wemedia.dtos.ChannelDto;
import com.heima.model.wemedia.pojos.WmChannel;
import com.heima.wemedia.service.WmChannelService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;@RestController
@RequestMapping("/api/v1/channel")
public class WmchannelController {@Autowiredprivate WmChannelService wmChannelService;@GetMapping("/channels")public ResponseResult findAll(){return wmChannelService.findAll();}@PostMapping("/list")public ResponseResult findByNameAndPage(@RequestBody ChannelDto dto){return wmChannelService.findByNameAndPage(dto);}@PostMapping("/save")public ResponseResult insert(@RequestBody WmChannel adChannel){return wmChannelService.insert(adChannel);}@PostMapping("/update")public ResponseResult update(@RequestBody WmChannel adChannel){return wmChannelService.update(adChannel);}@GetMapping("/del/{id}")public ResponseResult delete(@PathVariable("id") Integer id){return wmChannelService.delete(id);}}

3)敏感词管理

3.1)新增

  • 弹出的输入框,输入敏感词可直接保存

  • 已存在的敏感词则不能保存

3.2)查询列表

  • 查询需要按照创建时间倒序查询

  • 按照敏感词名称模糊查询

  • 分页查询

3.3)修改

3.4)删除

直接删除即可

WmSensitiveController

package com.heima.wemedia.controller.v1;import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.wemedia.dtos.SensitiveDto;
import com.heima.model.wemedia.pojos.WmSensitive;
import com.heima.wemedia.service.WmSensitiveService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;@RestController
@RequestMapping("/api/v1/sensitive")
public class WmSensitiveController {@Autowiredprivate WmSensitiveService wmSensitiveService;@PostMapping("/list")public ResponseResult list(@RequestBody SensitiveDto dto){return wmSensitiveService.list(dto);}@PostMapping("/save")public ResponseResult insert(@RequestBody WmSensitive wmSensitive){return wmSensitiveService.insert(wmSensitive);}@PostMapping("/update")public ResponseResult update(@RequestBody WmSensitive wmSensitive){return wmSensitiveService.update(wmSensitive);}@DeleteMapping("/del/{id}")public ResponseResult delete(@PathVariable("id") Integer id){return wmSensitiveService.delete(id);}
}

4)用户认证审核

  • 在app端的个人中心用户可以实名认证,需要材料为:姓名、身份证号、身份证正面照、身份证反面照、手持照片、活体照片(通过微笑、眨眼、张嘴、摇头、点头等组合动作,确保操作的为真实活体人脸。),当用户提交审核后就到了后端让运营管理人员进行审核

  • 平台运营端查看用户认证信息,进行审核,其中审核包括了用户身份审核,需要对接公安系统校验身份证信息

  • 用户通过审核后需要开通自媒体账号(该账号的用户名和密码与app一致)

4.1)分页查询认证列表

  • 可根据审核状态条件查询

  • 需要分页查询

4.2)审核

人工审核

拒绝

审核成功

ApUserRealnameController

package com.heima.user.controller.v1;import com.heima.common.constants.UserConstants;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.user.dtos.AuthDto;
import com.heima.user.service.ApUserRealnameService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/api/v1/auth")
public class ApUserRealnameController {@Autowiredprivate ApUserRealnameService apUserRealnameService;@PostMapping("/list")public ResponseResult loadListByStatus(@RequestBody AuthDto dto){return apUserRealnameService.loadListByStatus(dto);}@PostMapping("/authPass")public ResponseResult authPass(@RequestBody AuthDto dto ){return apUserRealnameService.updateStatus(dto, UserConstants.PASS_AUTH);}@PostMapping("/authFail")public ResponseResult authFail(@RequestBody AuthDto dto ){return apUserRealnameService.updateStatus(dto, UserConstants.FAIL_AUTH);}
}

5)文章人工审核

自媒体文章如果没有自动审核成功,而是到了人工审核(自媒体文章状态为3),需要在admin端人工处理文章的审核

  • 平台管理员可以查看待人工审核的文章信息,如果存在违规内容则驳回(状态改为2,文章审核失败)

  • 平台管理员可以查看待人工审核的文章信息,如果不存在违规,则需要创建app端的文章信息,并更新自媒体文章的状态

也可以通过点击查看按钮,查看文章详细信息,查看详情后可以根据内容判断是否需要通过审核

5.1)文章列表查询

  • 分页查询自媒体文章

  • 可以按照标题模糊查询

  • 可以按照审核状态进行精确 检索

  • 文章查询按照创建时间倒序查询

  • 注意:需要展示作者名称

5.2)查询文章详情

  • 可以查看文章详细内容

  • 注意:需要展示作者名称

5.3)人工审核

5.3.1) 拒绝

拒绝以后需要给出原因,并修改文章的状态为2

5.3.2)审核成功

需要创建app端的文章信息,并更新自媒体文章的状态

WmNewsController

package com.heima.wemedia.controller.v1;import com.heima.common.constants.WemediaConstants;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.wemedia.dtos.NewsAuthDto;
import com.heima.model.wemedia.dtos.WmNewsDto;
import com.heima.model.wemedia.dtos.WmNewsPageReqDto;
import com.heima.wemedia.service.WmNewsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;@RestController
@RequestMapping("/api/v1/news")
public class WmNewsController {@Autowiredprivate WmNewsService wmNewsService;@PostMapping("/list")public ResponseResult findAll(@RequestBody WmNewsPageReqDto dto){return  wmNewsService.findAll(dto);}@PostMapping("/submit")public ResponseResult submitNews(@RequestBody WmNewsDto dto){return  wmNewsService.submitNews(dto);}@GetMapping("/one/{id}")public ResponseResult findOne(@PathVariable("id") Integer id){return wmNewsService.findOne(id);}@PostMapping("/down_or_up")public ResponseResult downOrUp(@RequestBody WmNewsDto dto){return wmNewsService.downOrUp(dto);}
//------------------上面这四个是之前的@PostMapping("/list_vo")public ResponseResult findList(@RequestBody NewsAuthDto dto){return wmNewsService.findList(dto);}@GetMapping("/one_vo/{id}")public ResponseResult findWmNewsVo(@PathVariable("id") Integer id){return wmNewsService.findWmNewsVo(id);}@PostMapping("/auth_pass")public ResponseResult authPass(@RequestBody NewsAuthDto dto){return wmNewsService.updateStatus(WemediaConstants.WM_NEWS_AUTH_PASS,dto);}@PostMapping("/auth_fail")public ResponseResult authFail(@RequestBody NewsAuthDto dto){return wmNewsService.updateStatus(WemediaConstants.WM_NEWS_AUTH_FAIL,dto);}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/18562.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

关于Vue 、vue2、vue3

vue优点&#xff1f;vue2、vue3响应式比较&#xff1f; &#xff08;1&#xff09; 响应式编程 Vue 会自动对页面中某些数据的变化做出响应。通过 MVVM 思想实现数据的双向绑定&#xff0c;让开发者不用再操作 DOM 对象&#xff0c;有更多的时间去思考业务逻辑。 组件化开发…

springboot服务端接口公网远程调试,并实现HTTP服务监听

文章目录 前言1. 本地环境搭建1.1 环境参数1.2 搭建springboot服务项目 2. 内网穿透2.1 安装配置cpolar内网穿透2.1.1 windows系统2.1.2 linux系统 2.2 创建隧道映射本地端口2.3 测试公网地址 3. 固定公网地址3.1 保留一个二级子域名3.2 配置二级子域名3.2 测试使用固定公网地址…

探索全球市场:初创品牌海外营销策略解析

​随着全球化进程的不断推进&#xff0c;越来越多的初创品牌意识到海外市场的巨大潜力&#xff0c;并希望能够将自己的品牌推广到更广阔的国际舞台上。然而&#xff0c;对于初创品牌来说&#xff0c;进军海外市场并开展品牌营销是一项具有挑战性的任务。本文Nox聚星将介绍一些初…

Vue前后端分离项目 【实战篇】

一、shiro中session的共享问题&#x1f349; 1.演示问题&#x1f95d; (1)启动shiro-springboot的集群项目&#x1f353; (2)修改nginx的配置&#x1f353; (3)测试&#x1f353; 使用swagger测试需要在过滤器中放行 //测试路径 http://localhost:8080/doc.html登录成功后访…

ModaHub魔搭社区:向量数据库Zilliz Cloud集群、Collection 及 Entity教程

目录 集群 Collection 字段 Schema 索引 Entity Zilliz Cloud 集群由全托管 Milvus 实例及相关计算资源构成。您可以在 Zilliz Cloud 集群中创建 Collection,然后在 Collection 中插入 Entity。Zilliz Cloud 集群中的 Collection 类似于关系型数据库中的表。Collection …

请求响应-json参数的接收

JSON参数 JSON参数&#xff1a;JSON数据键名与形参对象属性名&#xff08;即实体类中的成员变量&#xff09;相同&#xff0c;定义POJO实体类即可接收参数&#xff0c;需要使用RequestBody标识前端发送JSON格式的数据&#xff0c;要使用POST方法发送请求&#xff0c;因为JSON格…

FASTADMIN联合查询 搜索

控制器中添加relationSearch开关 控制器里面添加联合查询 MODEL里面添加 js里面添加即可 可以查看数据json 搜索开启searchFileds就可以了

前端基础知识学习——圆角、透明圆角(十四)

文章目录 圆角方法一圆角方法二&#xff08;推荐&#xff09;透明圆角方法一透明圆角方法二&#xff08;推荐&#xff09; 圆角方法一 /* 添加圆角 方法1&#xff1a;border-radius cs3不兼容*/.bo{width: 100px; height: 200px;border: 1px solid #e5e5e5;margin:30px aut…

【MySQL】如何优化SQL查询的总体框架(详细版,关于如何优化数据库服务器从大到小详细说明了步骤)

文章目录 1 数据库服务器的优化步骤2 观察2.1 观察系统总体运行情况2.2 定位执行慢的 SQL&#xff1a;慢查询日志2.3 查看 SQL 执行成本&#xff1a;SHOW PROFILE2.4 分析查询语句&#xff1a;EXPLAIN&#xff08;重点掌握&#xff09;2.4.1 EXPLAIN各列作用2.4.2 EXPLAIN 的 t…

【嵌入式Qt开发入门】Qt如何使用多线程——继承QObject的线程

QObject 在上篇已经说过&#xff0c;继承 QThread 类是创建线程的一种方法&#xff0c;另一种就是继承 QObject 类。继承 QObject 类更加灵活。它通过 QObject::moveToThread()方法&#xff0c;将一个 QObeject 的类转移到一个线程里执行&#xff0c;可以通过下图理解。 通过…

6.2.1 网络基本服务---域名解析系统DNS

6.2.1 网络基本服务—域名解析系统DNS 因特网是需要提供一些最基本的服务的&#xff0c;今天我们就来讨论一下这些基本的服务。 域名系统&#xff08;DNS&#xff09;远程登录&#xff08;Telnet&#xff09;文件传输协议&#xff08;FTP&#xff09;动态主机配置协议&#x…

机器学习之随机森林(Random forest)

1 什么是随机森林 随机森林是一种监督式算法&#xff0c;使用由众多决策树组成的一种集成学习方法&#xff0c;输出是对问题最佳答案的共识。随机森林可用于分类或回归&#xff0c;是一种主流的集成学习算法。 1.1 随机森林算法原理 随机森林中有许多的分类树。我们要将一个输…