diff --git a/admin/pom.xml b/admin/pom.xml index 28d264e..fdb215c 100644 --- a/admin/pom.xml +++ b/admin/pom.xml @@ -15,6 +15,8 @@ 8 8 UTF-8 + 8.12.2 + 2.0.1 @@ -22,12 +24,32 @@ com.jinrui core 0.0.1-SNAPSHOT + + + org.elasticsearch.client + elasticsearch-rest-client + + + jakarta.json-api + jakarta.json + + redis.clients jedis ${jedis.version} + + org.elasticsearch.client + elasticsearch-rest-client + ${elasticsearch.version} + + + jakarta.json + jakarta.json-api + ${jakartajson.version} + diff --git a/admin/src/main/java/com/jinrui/reference/admin/controller/NewsInfoController.java b/admin/src/main/java/com/jinrui/reference/admin/controller/NewsInfoController.java new file mode 100644 index 0000000..e53b8b3 --- /dev/null +++ b/admin/src/main/java/com/jinrui/reference/admin/controller/NewsInfoController.java @@ -0,0 +1,201 @@ +package com.jinrui.reference.admin.controller; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.jinrui.reference.admin.model.dto.newsinfo.PublishNewsInfoDTO; +import com.jinrui.reference.admin.model.entity.AdminUser; +import com.jinrui.reference.admin.service.AdminJwtService; +import com.jinrui.reference.core.model.vo.PageObject; +import com.jinrui.reference.core.model.vo.ResultObject; +import com.jinrui.reference.core.model.vo.newsinfo.NewsInfoDetailVO; +import com.jinrui.reference.core.model.vo.newsinfo.NewsInfoVO; +import com.jinrui.reference.core.model.vo.newsinfo.SaveNewsInfoDTO; +import com.jinrui.reference.core.service.NewsInfoService; + +@RestController +@RequestMapping("/newsinfo") +public class NewsInfoController { + private static final Logger log = LoggerFactory.getLogger(NewsInfoController.class); + + private final NewsInfoService newsInfoService; + private final ObjectMapper objectMapper; + + public NewsInfoController(NewsInfoService newsInfoService, + ObjectMapper objectMapper) { + this.newsInfoService = newsInfoService; + this.objectMapper = objectMapper;; + } + + @PostMapping("/publish") + public ResultObject publish(@RequestHeader("auth-token") String token, + @RequestBody PublishNewsInfoDTO publishNewsDTO) { + if (!StringUtils.hasText(token)) { + return ResultObject.failed("登陆Token为空!"); + } + + try { + AdminUser adminUser = AdminJwtService.parseToken(token); + if (adminUser == null) { + log.warn("解析token {}拿不到AdminUser对象!", token); + return ResultObject.failed("登陆Token有误,请联系系统管理员!"); + } + + Long adminUserId = adminUser.getId(); + if (!adminUser.isActive()) { + log.warn("当前用户已被封禁! id = {}", adminUserId); + return ResultObject.failed("当前用户已被封禁!请联系系统管理员!"); + } + + String id = publishNewsDTO.getId(); + if (ObjectUtils.isEmpty(id)) { + return ResultObject.failed("要发布/下架的新闻ID不可为空!"); + } + + log.info("path: /newsinfo/publish, method: POST, request user id: {}, news id: {}", adminUserId, id); + return newsInfoService.publish(id, adminUserId); + } catch (Exception e) { + log.error("解析登陆Token出错!", e); + return ResultObject.failed(500, "服务端错误,请联系系统管理员!"); + } + } + + @GetMapping("/detail") + public ResultObject detail(@RequestHeader("auth-token") String token, + @RequestParam("id") String id) { + if (!StringUtils.hasText(token)) { + return ResultObject.failed("登陆Token为空!"); + } + + try { + AdminUser adminUser = AdminJwtService.parseToken(token); + if (adminUser == null) { + log.warn("解析token {}拿不到AdminUser对象!", token); + return ResultObject.failed("登陆Token有误,请联系系统管理员!"); + } + + Long adminUserId = adminUser.getId(); + if (!adminUser.isActive()) { + log.warn("当前用户已被封禁! id = {}", adminUserId); + return ResultObject.failed("当前用户已被封禁!请联系系统管理员!"); + } + + log.info("path: /newsinfo/detail, method: GET, request user id: {}, news id: {}", adminUserId, id); + return newsInfoService.detail(id); + } catch (Exception e) { + log.error("解析登陆Tokn出错!", e); + return ResultObject.failed(500, "服务端错误,请联系系统管理员!"); + } + } + + @PostMapping("/delete") + public ResultObject deleteNews(@RequestHeader("auth-token") String token, + @RequestParam("id") String id) { + if (!StringUtils.hasText(token)) { + return ResultObject.failed("登陆Token为空!"); + } + + try { + AdminUser adminUser = AdminJwtService.parseToken(token); + if (adminUser == null) { + log.warn("解析token {}拿不到AdminUser对象!", token); + return ResultObject.failed("登陆Token有误,请联系系统管理员!"); + } + + Long adminUserId = adminUser.getId(); + if (!adminUser.isActive()) { + log.warn("当前用户已被封禁! id = {}", adminUserId); + return ResultObject.failed("当前用户已被封禁!请联系系统管理员!"); + } + + log.info("path: /newsinfo, method: DELETE, request user id: {}, news id: {}", adminUserId, id); + return newsInfoService.deleteNewsInfo(id); + } catch (Exception e) { + log.error("解析登陆Token出错!", e); + return ResultObject.failed(500, "服务端错误,请联系系统管理员!"); + } + } + + @PostMapping("/save") + public ResultObject saveDraft(@RequestHeader("auth-token") String token, + @RequestBody SaveNewsInfoDTO saveNewsDTO) { + if (!StringUtils.hasText(token)) { + return ResultObject.failed("登陆Token为空!"); + } + + try { + AdminUser adminUser = AdminJwtService.parseToken(token); + if (adminUser == null) { + log.warn("解析token {}拿不到AdminUser对象!", token); + return ResultObject.failed("登陆Token有误,请联系系统管理员!"); + } + + if (!adminUser.isActive()) { + log.warn("当前用户已被封禁! id = {}", adminUser.getId()); + return ResultObject.failed("当前用户已被封禁!请联系系统管理员!"); + } + + log.info("path: /news/save, method: POST, request user id: {}, param: {}", + adminUser.getId(), objectMapper.writeValueAsString(saveNewsDTO)); + } catch (Exception e) { + log.error("解析登陆Token出错!", e); + return ResultObject.failed(500, "服务端错误,请联系系统管理员!"); + } + + // 这个接口是保存和编辑,那status应该是2 + saveNewsDTO.setStatus(2); + return newsInfoService.save(saveNewsDTO); + } + + @GetMapping + public PageObject queryNewsInfo(@RequestHeader("auth-token") String token, + @RequestParam(value = "title", required = false) String title, + @RequestParam(value = "content", required = false) String content, + @RequestParam(value = "stockcode", required = false) String stockcode, + @RequestParam(value = "sourcename", required = false) Long sourcename, + @RequestParam(value = "page", required = false, defaultValue = "1") int page, + @RequestParam(value = "size", required = false, defaultValue = "10") int size, + @RequestParam(value = "last", required = false) Integer last, + @RequestParam(value = "current", required = false) Integer current, + @RequestParam(value = "orderBy", required = false, defaultValue = "createTime") String orderBy, + @RequestParam(value = "direction", required = false, defaultValue = "asc") String direction + ) { + if (!StringUtils.hasText(token)) { + return PageObject.failedPage("登陆Token为空!"); + } + + try { + AdminUser adminUser = AdminJwtService.parseToken(token); + if (adminUser == null) { + log.warn("解析token {}拿不到AdminUser对象!", token); + return PageObject.failedPage("登陆Token有误,请联系系统管理员!"); + } + + if (!adminUser.isActive()) { + log.warn("当前用户已被封禁! id = {}", adminUser.getId()); + return PageObject.failedPage("当前用户已被封禁!请联系系统管理员!"); + } + + log.info("path: /newsinfo, method: GET, request user id: {}, title: {}, content: {}, stockcode: {}, sourcename: {}, " + + "page: {}, size: {}, last: {}, current: {}, orderBy: {}, direction: {}", + adminUser.getId(), title, content, stockcode,sourcename, page, size, last, current, orderBy, direction); + } catch (Exception e) { + log.error("解析登陆Token出错!", e); + return PageObject.failedPage(500, "服务端错误,请联系系统管理员!"); + } + + return newsInfoService.queryNewsInfo(title, content, stockcode, sourcename, page, size, last, current, orderBy, direction); + } + + +} diff --git a/admin/src/main/java/com/jinrui/reference/admin/model/dto/newsinfo/PublishNewsInfoDTO.java b/admin/src/main/java/com/jinrui/reference/admin/model/dto/newsinfo/PublishNewsInfoDTO.java new file mode 100644 index 0000000..c93820d --- /dev/null +++ b/admin/src/main/java/com/jinrui/reference/admin/model/dto/newsinfo/PublishNewsInfoDTO.java @@ -0,0 +1,13 @@ +package com.jinrui.reference.admin.model.dto.newsinfo; + +public class PublishNewsInfoDTO { + private String id; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } +} diff --git a/admin/src/main/resources/application.yml b/admin/src/main/resources/application.yml index c2ab94d..f08fca3 100644 --- a/admin/src/main/resources/application.yml +++ b/admin/src/main/resources/application.yml @@ -32,3 +32,10 @@ redis: host: 123.60.153.169 port: 6379 password: Xgf_redis +elasticsearch: + scheme: http + host: 10.127.2.194 + port: 9200 + enable: true + username: elastic + password: Aa123456 \ No newline at end of file diff --git a/core/pom.xml b/core/pom.xml index 64ae0ba..43e2235 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -17,6 +17,8 @@ UTF-8 1.70 4.4.0 + 8.12.2 + 2.0.1 @@ -59,6 +61,31 @@ bcutil-jdk15on ${bouncycastle.version} + + co.elastic.clients + elasticsearch-java + ${elasticsearch.version} + + + org.elasticsearch.client + elasticsearch-rest-client + + + jakarta.json-api + jakarta.json + + + + + org.elasticsearch.client + elasticsearch-rest-client + ${elasticsearch.version} + + + jakarta.json + jakarta.json-api + ${jakartajson.version} + diff --git a/core/src/main/java/com/jinrui/reference/core/ElasticSearchConfig.java b/core/src/main/java/com/jinrui/reference/core/ElasticSearchConfig.java new file mode 100644 index 0000000..746795c --- /dev/null +++ b/core/src/main/java/com/jinrui/reference/core/ElasticSearchConfig.java @@ -0,0 +1,90 @@ +package com.jinrui.reference.core; + +import org.apache.http.HttpHost; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.impl.client.BasicCredentialsProvider; +import org.apache.http.message.BasicHeader; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.Header; +import org.elasticsearch.client.RestClient; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import co.elastic.clients.elasticsearch.ElasticsearchAsyncClient; +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.json.jackson.JacksonJsonpMapper; +import co.elastic.clients.transport.ElasticsearchTransport; +import co.elastic.clients.transport.rest_client.RestClientTransport; + +@Configuration +public class ElasticSearchConfig { + + @Value("${elasticsearch.scheme}") + private String scheme; + + @Value("${elasticsearch.host}") + private String host; + + @Value("${elasticsearch.port}") + private int port; + + @Value("${elasticsearch.enable}") + private boolean enable; + + @Value("${elasticsearch.username}") + private String username; + + @Value("${elasticsearch.password}") + private String password; + + + @Bean + public ElasticsearchClient elasticsearchClient(){ + ElasticsearchClient client = new ElasticsearchClient(null); + if (enable){ + final CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); + if (username != null && !username.isEmpty() && password != null && !password.isEmpty()) { + + //设置账号密码 + credentialsProvider.setCredentials( + AuthScope.ANY, new UsernamePasswordCredentials(username, password)); + } + + Header[] defaultHeaders = new Header[]{new BasicHeader("header", "value")}; + RestClient restClient = RestClient.builder(new HttpHost(host, port, scheme)) + .setHttpClientConfigCallback(httpClientBuilder -> httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider)) + .setDefaultHeaders(defaultHeaders) + .build(); + ElasticsearchTransport transport = new RestClientTransport(restClient,new JacksonJsonpMapper()); + client = new ElasticsearchClient(transport); + } + return client; + + } + + @Bean + public ElasticsearchAsyncClient elasticsearchAsyncClient(){ + ElasticsearchAsyncClient syncClient = new ElasticsearchAsyncClient(null); + if (enable){ + final CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); + if (username != null && !username.isEmpty() && password != null && !password.isEmpty()) { + + //设置账号密码 + credentialsProvider.setCredentials( + AuthScope.ANY, new UsernamePasswordCredentials(username, password)); + } + + Header[] defaultHeaders = new Header[]{new BasicHeader("header", "value")}; + RestClient restClient = RestClient.builder(new HttpHost(host, port, scheme)) + .setHttpClientConfigCallback(httpClientBuilder -> httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider)) + .setDefaultHeaders(defaultHeaders) + .build(); + ElasticsearchTransport transport = new RestClientTransport(restClient,new JacksonJsonpMapper()); + syncClient = new ElasticsearchAsyncClient(transport); + } + return syncClient; + + } +} diff --git a/core/src/main/java/com/jinrui/reference/core/mapper/NewsMapper.java b/core/src/main/java/com/jinrui/reference/core/mapper/NewsMapper.java index e9a7d32..87f60e2 100644 --- a/core/src/main/java/com/jinrui/reference/core/mapper/NewsMapper.java +++ b/core/src/main/java/com/jinrui/reference/core/mapper/NewsMapper.java @@ -4,6 +4,7 @@ import com.jinrui.reference.core.model.entity.News; import com.jinrui.reference.core.model.entity.NewsDraft; import org.apache.ibatis.annotations.Delete; import org.apache.ibatis.annotations.Insert; +import org.apache.ibatis.annotations.Options; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Update; @@ -35,7 +36,7 @@ public interface NewsMapper { @Update("update news set status = 2, publish_time = now(), editor_id = #{editorId} where id = #{id}") void simplePublish(@Param("id") long id, @Param("editorId") long editorId); - + @Update("update news " + "set draft_id = #{draftId}," + "editor_id = #{editorId}," + @@ -45,6 +46,7 @@ public interface NewsMapper { "content = #{content}," + "content_text = #{contentText}," + "status = #{status}," + + "newsinfo_id = #{newsinfoId}," + "update_time = now() " + "where id = #{id}") void updateNews(News news); @@ -63,7 +65,7 @@ public interface NewsMapper { "where id = #{id}") void publishNews(News news); - @Select("select id, draft_id as draftId, status from news where id = #{id}") + @Select("select id, draft_id as draftId, status, newsinfo_id as newsinfoId from news where id = #{id}") News getById(@Param("id") Long id); @Select("select last_insert_id()") @@ -73,8 +75,9 @@ public interface NewsMapper { "values (#{title}, #{summary}, #{picture}, #{type}, #{content}, now(), now())") void saveDraft(NewsDraft newsDraft); - @Insert("insert into news(draft_id, title, summary, picture, type, content, create_time, update_time, status, publish_time, content_text)" + - "values (#{draftId}, #{title}, #{summary}, #{picture}, #{type}, #{content}, now(), now(), #{status}, #{publishTime}, #{contentText})") + @Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id") + @Insert("insert into news(draft_id, title, summary, picture, type, content, create_time, update_time, status, publish_time, content_text, newsinfo_id)" + + "values (#{draftId}, #{title}, #{summary}, #{picture}, #{type}, #{content}, now(), now(), #{status}, #{publishTime}, #{contentText}, #{newsinfoId})") void saveNews(News news); diff --git a/core/src/main/java/com/jinrui/reference/core/mapper/TagMapper.java b/core/src/main/java/com/jinrui/reference/core/mapper/TagMapper.java index 4ee0b2d..753c270 100644 --- a/core/src/main/java/com/jinrui/reference/core/mapper/TagMapper.java +++ b/core/src/main/java/com/jinrui/reference/core/mapper/TagMapper.java @@ -25,6 +25,10 @@ public interface TagMapper { }) @Select("select * from tag") List queryAll(); + + + @Select("select id, parent_id as parentId, name from tag where id = #{id}") + Tag queryById(@Param("id") Long id); @Select("select id, draft_id as draftId, tag_id as tagId " + "from draft_tag_rel where draft_id = #{draftId}") diff --git a/core/src/main/java/com/jinrui/reference/core/model/dto/news/SaveNewsDTO.java b/core/src/main/java/com/jinrui/reference/core/model/dto/news/SaveNewsDTO.java index 17e69b9..60b4ca7 100644 --- a/core/src/main/java/com/jinrui/reference/core/model/dto/news/SaveNewsDTO.java +++ b/core/src/main/java/com/jinrui/reference/core/model/dto/news/SaveNewsDTO.java @@ -3,6 +3,10 @@ package com.jinrui.reference.core.model.dto.news; import java.util.Date; import java.util.List; +import org.springframework.util.CollectionUtils; +import org.springframework.util.ObjectUtils; + +import com.jinrui.reference.core.model.entity.NewsInfo; import com.jinrui.reference.core.model.vo.news.NewsDetailVO; @SuppressWarnings("unused") @@ -128,4 +132,16 @@ public class SaveNewsDTO { public void setIndustries(List industries) { this.industries = industries; } + + public NewsInfo toNewsInfo() { + NewsInfo newsInfo = new NewsInfo(); + newsInfo.setTitle(this.getTitle()); + newsInfo.setSummary(this.getSummary()); + newsInfo.setStatus(this.getStatus()); + newsInfo.setContent(this.getContent()); + newsInfo.setCreateTime(new Date()); + newsInfo.setUpdateTime(newsInfo.getCreateTime()); + newsInfo.setInputDate(newsInfo.getCreateTime()); + return newsInfo; + } } diff --git a/core/src/main/java/com/jinrui/reference/core/model/entity/Industry.java b/core/src/main/java/com/jinrui/reference/core/model/entity/Industry.java index d5fde6a..1f43db0 100644 --- a/core/src/main/java/com/jinrui/reference/core/model/entity/Industry.java +++ b/core/src/main/java/com/jinrui/reference/core/model/entity/Industry.java @@ -3,6 +3,7 @@ package com.jinrui.reference.core.model.entity; import java.util.Date; import org.apache.ibatis.annotations.Param; +import org.springframework.util.ObjectUtils; /** * 行业分类 @@ -86,4 +87,11 @@ public class Industry { public void setUpdateTime(Date updateTime) { this.updateTime = updateTime; } + + public String getDisplayName() { + if (ObjectUtils.isEmpty(this.getSecondaryName())) { + return this.getPrimaryName(); + } + return this.getSecondaryName(); + } } diff --git a/core/src/main/java/com/jinrui/reference/core/model/entity/News.java b/core/src/main/java/com/jinrui/reference/core/model/entity/News.java index dfdcf37..0c3a291 100644 --- a/core/src/main/java/com/jinrui/reference/core/model/entity/News.java +++ b/core/src/main/java/com/jinrui/reference/core/model/entity/News.java @@ -70,8 +70,13 @@ public class News { * 修改时间 */ private Date updateTime; + + /** + * 关联的全量资讯es文档ID + */ + private String newsinfoId; - public News() {} + public News() {} public News(SaveNewsDTO saveNewsDTO) { this.id = saveNewsDTO.getId(); @@ -191,4 +196,12 @@ public class News { public void setContentText(String contentText) { this.contentText = contentText; } + + public String getNewsinfoId() { + return newsinfoId; + } + + public void setNewsinfoId(String newsinfoId) { + this.newsinfoId = newsinfoId; + } } diff --git a/core/src/main/java/com/jinrui/reference/core/model/entity/NewsInfo.java b/core/src/main/java/com/jinrui/reference/core/model/entity/NewsInfo.java new file mode 100644 index 0000000..acc5036 --- /dev/null +++ b/core/src/main/java/com/jinrui/reference/core/model/entity/NewsInfo.java @@ -0,0 +1,323 @@ +package com.jinrui.reference.core.model.entity; + +import java.util.Date; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * 全量资讯 + */ +public class NewsInfo { + + public static final String INDEX_NAME = "news_info"; + public static final String TITLE_ES_NAME = "title_txt"; + public static final String SUMMARY_ES_NAME = "abstract"; + public static final String CONTENT_ES_NAME = "CN_content"; + public static final String SOURCE_ES_NAME = "sourcename.keyword"; + public static final String DELETED_ES_NAME = "deleted.keyword"; + + /** + * 全量资讯ID + */ + private String id; + + /** + * 资讯精选ID + */ + @JsonProperty("news_id") + private Long newsId; + + /** + * 数据输入时间 + */ + @JsonProperty("input_date") + private Date inputDate; + + private Integer words; + + /** + * 关键词列表(分号分割) + */ + @JsonProperty("key_word") + private String keyword; + + /** + * 标题 + */ + @JsonProperty(TITLE_ES_NAME) + private String title; + + @JsonProperty("title_EN") + private String englishTitle; + + /** + * 摘要 + */ + @JsonProperty(SUMMARY_ES_NAME) + private String summary; + + /** + * 原文链接 + */ + @JsonProperty("URL") + private String url; + + /** + * 数据来源名称或标识 + */ + private String sourcename; + + /** + * 语言 + */ + private String lang; + + /** + * H5富文本 + */ + @JsonProperty(CONTENT_ES_NAME) + private String content; + + @JsonProperty("EN_content") + private String englishContent; + + /** + * 分类信息json格式 + */ + @JsonProperty("category") + private String industry; + + /** + * 是否删除 0 正常, 1 已删除 + */ + private Integer deleted = 0; + + /** + * 文件日期(取自目录结构) + */ + @JsonProperty("file_date") + private Date fileDate; + + /** + * 完整文件名(含扩展名) + */ + @JsonProperty("file_name") + private String fileName; + + /** + * 新闻状态 0-草稿 | 1-未发布 | 2-已发布 + */ + private Integer status; + + /** + * 创建时间 + */ + @JsonProperty("create_time") + private Date createTime; + + /** + * 发布时间 + */ + private Date publishTime; + + /** + * 修改时间 + */ + @JsonProperty("update_time") + private Date updateTime; + + /** + * 编辑ID - 最后一个点击发布的人的ID + */ + private Long editorId; + + public NewsInfo() {} + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public Long getEditorId() { + return editorId; + } + + public void setEditorId(Long editorId) { + this.editorId = editorId; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getSummary() { + return summary; + } + + public void setSummary(String summary) { + this.summary = summary; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public Date getCreateTime() { + return createTime; + } + + public void setCreateTime(Date createTime) { + this.createTime = createTime; + } + + public Date getPublishTime() { + return publishTime; + } + + public void setPublishTime(Date publishTime) { + this.publishTime = publishTime; + } + + public Date getUpdateTime() { + return updateTime; + } + + public void setUpdateTime(Date updateTime) { + this.updateTime = updateTime; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getSourcename() { + return sourcename; + } + + public void setSourcename(String sourcename) { + this.sourcename = sourcename; + } + + public String getLang() { + return lang; + } + + public void setLang(String lang) { + this.lang = lang; + } + + public Integer getDeleted() { + return deleted; + } + + public void setDeleted(Integer deleted) { + this.deleted = deleted; + } + + public Long getNewsId() { + return newsId; + } + + public void setNewsId(Long newsId) { + this.newsId = newsId; + } + + public Date getInputDate() { + return inputDate; + } + + public void setInputDate(Date inputDate) { + this.inputDate = inputDate; + } + + public String getKeyword() { + return keyword; + } + + public void setKeyword(String keyword) { + this.keyword = keyword; + } + + public String getEnglishTitle() { + return englishTitle; + } + + public void setEnglishTitle(String englishTitle) { + this.englishTitle = englishTitle; + } + + public String getEnglishContent() { + return englishContent; + } + + public void setEnglishContent(String englishContent) { + this.englishContent = englishContent; + } + + public String getIndustry() { + return industry; + } + + public void setIndustry(String industry) { + this.industry = industry; + } + + public Date getFileDate() { + return fileDate; + } + + public void setFileDate(Date fileDate) { + this.fileDate = fileDate; + } + + public String getFileName() { + return fileName; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + + public Integer getWords() { + return words; + } + + public void setWords(Integer words) { + this.words = words; + } + + public News toNews() { + News news = new News(); + news.setTitle(this.getTitle()); + news.setContent(this.getContent()); + news.setStatus(this.getStatus()); + news.setSummary(this.getSummary()); + news.setPublishTime(new Date()); + news.setType(1); + news.setNewsinfoId(this.getId()); + return news; + } +} diff --git a/core/src/main/java/com/jinrui/reference/core/model/vo/newsinfo/NewsInfoDetailVO.java b/core/src/main/java/com/jinrui/reference/core/model/vo/newsinfo/NewsInfoDetailVO.java new file mode 100644 index 0000000..1e4f559 --- /dev/null +++ b/core/src/main/java/com/jinrui/reference/core/model/vo/newsinfo/NewsInfoDetailVO.java @@ -0,0 +1,222 @@ +package com.jinrui.reference.core.model.vo.newsinfo; + +import java.util.Date; +import java.util.List; + +import com.jinrui.reference.core.model.entity.NewsInfo; +import com.jinrui.reference.core.model.vo.news.NewsDetailIndustry; +import com.jinrui.reference.core.model.vo.news.NewsDetailTag; + +public class NewsInfoDetailVO { + /** + * 全量资讯ID + */ + private String id; + + /** + * 资讯精选ID + */ + private Long newsId; + + /** + * 数据输入时间 + */ + private Date inputDate; + + /** + * 关键词列表(分号分割) + */ + private String keyword; + + /** + * 标题 + */ + private String title; + + + /** + * 摘要 + */ + private String summary; + + /** + * 数据来源名称或标识 + */ + private NewsDetailTag sourcename; + + /** + * H5富文本 + */ + private String content; + + /** + * 分类信息json格式 + */ + private List industry; + + /** + * 文件日期(取自目录结构) + */ + private Date fileDate; + + /** + * 新闻状态 0-草稿 | 1-未发布 | 2-已发布 + */ + private Integer status; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 发布时间 + */ + private Date publishTime; + + /** + * 修改时间 + */ + private Date updateTime; + + /** + * 编辑ID - 最后一个点击发布的人的ID + */ + private Long editorId; + + + + public NewsInfoDetailVO() {} + + public NewsInfoDetailVO(NewsInfo newsInfo) { + this.id = newsInfo.getId(); + this.newsId = newsInfo.getNewsId(); + this.title = newsInfo.getTitle(); + this.summary = newsInfo.getSummary(); + this.status = (newsInfo.getStatus() == null ? 1: newsInfo.getStatus()); + this.content = newsInfo.getContent(); + this.publishTime = newsInfo.getPublishTime(); + this.createTime = newsInfo.getCreateTime(); + this.updateTime = newsInfo.getUpdateTime(); + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public Long getEditorId() { + return editorId; + } + + public void setEditorId(Long editorId) { + this.editorId = editorId; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getSummary() { + return summary; + } + + public void setSummary(String summary) { + this.summary = summary; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public Date getCreateTime() { + return createTime; + } + + public void setCreateTime(Date createTime) { + this.createTime = createTime; + } + + public Date getPublishTime() { + return publishTime; + } + + public void setPublishTime(Date publishTime) { + this.publishTime = publishTime; + } + + public Date getUpdateTime() { + return updateTime; + } + + public void setUpdateTime(Date updateTime) { + this.updateTime = updateTime; + } + + public NewsDetailTag getSourcename() { + return sourcename; + } + + public void setSourcename(NewsDetailTag sourcename) { + this.sourcename = sourcename; + } + + public Long getNewsId() { + return newsId; + } + + public void setNewsId(Long newsId) { + this.newsId = newsId; + } + + public Date getInputDate() { + return inputDate; + } + + public void setInputDate(Date inputDate) { + this.inputDate = inputDate; + } + + public String getKeyword() { + return keyword; + } + + public void setKeyword(String keyword) { + this.keyword = keyword; + } + + public List getIndustry() { + return industry; + } + + public void setIndustry(List industry) { + this.industry = industry; + } + + public Date getFileDate() { + return fileDate; + } + + public void setFileDate(Date fileDate) { + this.fileDate = fileDate; + } +} diff --git a/core/src/main/java/com/jinrui/reference/core/model/vo/newsinfo/NewsInfoVO.java b/core/src/main/java/com/jinrui/reference/core/model/vo/newsinfo/NewsInfoVO.java new file mode 100644 index 0000000..d4902ce --- /dev/null +++ b/core/src/main/java/com/jinrui/reference/core/model/vo/newsinfo/NewsInfoVO.java @@ -0,0 +1,194 @@ +package com.jinrui.reference.core.model.vo.newsinfo; + +import java.util.Date; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.jinrui.reference.core.model.entity.NewsInfo; + +public class NewsInfoVO { + /** + * 全量资讯ID + */ + private String id; + + /** + * 资讯精选ID + */ + private Long newsId; + + /** + * 数据输入时间 + */ + private Date inputDate; + + /** + * 标题 + */ + private String title; + + /** + * 摘要 + */ + private String summary; + + /** + * 正文 + */ + private String content; + + /** + * 数据来源名称或标识 + */ + private String sourcename; + + /** + * 文件日期(取自目录结构) + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai") + private Date fileDate; + + /** + * 新闻状态 0-草稿 | 1-未发布 | 2-已发布 + */ + private Integer status; + + /** + * 创建时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai") + private Date createTime; + + /** + * 发布时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai") + private Date publishTime; + + /** + * 修改时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai") + private Date updateTime; + + /** + * 编辑ID - 最后一个点击发布的人的ID + */ + private Long editorId; + + + public NewsInfoVO(NewsInfo newsInfo) { + this.id = newsInfo.getId(); + this.title = newsInfo.getTitle(); + this.summary = newsInfo.getSummary(); + this.status = newsInfo.getStatus(); + this.sourcename = newsInfo.getSourcename(); + this.content = newsInfo.getContent(); + this.publishTime = newsInfo.getPublishTime(); + this.createTime = newsInfo.getCreateTime(); + this.updateTime = newsInfo.getUpdateTime(); + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public Long getEditorId() { + return editorId; + } + + public void setEditorId(Long editorId) { + this.editorId = editorId; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getSummary() { + return summary; + } + + public void setSummary(String summary) { + this.summary = summary; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public Date getCreateTime() { + return createTime; + } + + public void setCreateTime(Date createTime) { + this.createTime = createTime; + } + + public Date getPublishTime() { + return publishTime; + } + + public void setPublishTime(Date publishTime) { + this.publishTime = publishTime; + } + + public Date getUpdateTime() { + return updateTime; + } + + public void setUpdateTime(Date updateTime) { + this.updateTime = updateTime; + } + + public String getSourcename() { + return sourcename; + } + + public void setSourcename(String sourcename) { + this.sourcename = sourcename; + } + + public Long getNewsId() { + return newsId; + } + + public void setNewsId(Long newsId) { + this.newsId = newsId; + } + + public Date getInputDate() { + return inputDate; + } + + public void setInputDate(Date inputDate) { + this.inputDate = inputDate; + } + + public Date getFileDate() { + return fileDate; + } + + public void setFileDate(Date fileDate) { + this.fileDate = fileDate; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } +} diff --git a/core/src/main/java/com/jinrui/reference/core/model/vo/newsinfo/SaveNewsInfoDTO.java b/core/src/main/java/com/jinrui/reference/core/model/vo/newsinfo/SaveNewsInfoDTO.java new file mode 100644 index 0000000..d0f3da0 --- /dev/null +++ b/core/src/main/java/com/jinrui/reference/core/model/vo/newsinfo/SaveNewsInfoDTO.java @@ -0,0 +1,151 @@ +package com.jinrui.reference.core.model.vo.newsinfo; + +import java.util.Date; +import java.util.List; + +import com.jinrui.reference.core.model.dto.news.SaveDraftColumn; +import com.jinrui.reference.core.model.dto.news.SaveDraftTag; +import com.jinrui.reference.core.model.entity.News; +import com.jinrui.reference.core.model.entity.NewsInfo; +import com.jinrui.reference.core.model.vo.news.NewsDetailVO; + +@SuppressWarnings("unused") +public class SaveNewsInfoDTO { + + private String id; + + private String title; + + private String summary; + + private String picture; + + private SaveDraftTag tag; + + private SaveDraftColumn column; + + private List industries; + + + private String content; + // 不含html标签的纯文本 + private String contentText; + private Integer status; + private Date publishTime; + + public SaveNewsInfoDTO() {} + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getSummary() { + return summary; + } + + public void setSummary(String summary) { + this.summary = summary; + } + + public String getPicture() { + return picture; + } + + public void setPicture(String picture) { + this.picture = picture; + } + + public SaveDraftTag getTag() { + return tag; + } + + public void setTag(SaveDraftTag tag) { + this.tag = tag; + } + + public SaveDraftColumn getColumn() { + return column; + } + + public void setColumn(SaveDraftColumn column) { + this.column = column; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public Date getPublishTime() { + return publishTime; + } + + public void setPublishTime(Date publishTime) { + this.publishTime = publishTime; + } + + public String getContentText() { + return contentText; + } + + public void setContentText(String contentText) { + this.contentText = contentText; + } + + public List getIndustries() { + return industries; + } + + public void setIndustries(List industries) { + this.industries = industries; + } + + public NewsInfo toNewsInfo() { + NewsInfo newsInfo = new NewsInfo(); + newsInfo.setId(this.getId()); + newsInfo.setTitle(this.getTitle()); + newsInfo.setSummary(this.getSummary()); + newsInfo.setStatus(this.getStatus()); + newsInfo.setContent(this.getContent()); + newsInfo.setCreateTime(new Date()); + newsInfo.setUpdateTime(newsInfo.getCreateTime()); + newsInfo.setInputDate(newsInfo.getCreateTime()); + return newsInfo; + } + + public News toNews() { + News news = new News(); + news.setTitle(this.getTitle()); + news.setSummary(this.getSummary()); + news.setStatus(this.getStatus()); + news.setContent(this.getContent()); + news.setCreateTime(new Date()); + news.setUpdateTime(news.getCreateTime()); + news.setType(1); + news.setNewsinfoId(this.getId()); + return news; + } +} diff --git a/core/src/main/java/com/jinrui/reference/core/service/NewsInfoService.java b/core/src/main/java/com/jinrui/reference/core/service/NewsInfoService.java new file mode 100644 index 0000000..0020490 --- /dev/null +++ b/core/src/main/java/com/jinrui/reference/core/service/NewsInfoService.java @@ -0,0 +1,513 @@ +package com.jinrui.reference.core.service; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.jinrui.reference.core.mapper.IndustryMapper; +import com.jinrui.reference.core.mapper.NewsMapper; +import com.jinrui.reference.core.mapper.TagMapper; +import com.jinrui.reference.core.model.dto.news.SaveDraftTag; +import com.jinrui.reference.core.model.entity.Industry; +import com.jinrui.reference.core.model.entity.News; +import com.jinrui.reference.core.model.entity.NewsIndustryRel; +import com.jinrui.reference.core.model.entity.NewsInfo; +import com.jinrui.reference.core.model.entity.NewsTagRel; +import com.jinrui.reference.core.model.entity.Tag; +import com.jinrui.reference.core.model.vo.PageObject; +import com.jinrui.reference.core.model.vo.ResultObject; +import com.jinrui.reference.core.model.vo.news.NewsDetailIndustry; +import com.jinrui.reference.core.model.vo.news.NewsDetailTag; +import com.jinrui.reference.core.model.vo.news.NewsDetailTagItem; +import com.jinrui.reference.core.model.vo.newsinfo.NewsInfoDetailVO; +import com.jinrui.reference.core.model.vo.newsinfo.NewsInfoVO; +import com.jinrui.reference.core.model.vo.newsinfo.SaveNewsInfoDTO; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.elasticsearch._types.ElasticsearchException; +import co.elastic.clients.elasticsearch._types.Refresh; +import co.elastic.clients.elasticsearch._types.SortOrder; +import co.elastic.clients.elasticsearch._types.query_dsl.MatchQuery; +import co.elastic.clients.elasticsearch._types.query_dsl.MultiMatchQuery; +import co.elastic.clients.elasticsearch._types.query_dsl.Operator; +import co.elastic.clients.elasticsearch._types.query_dsl.Query; +import co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders; +import co.elastic.clients.elasticsearch._types.query_dsl.TermQuery; +import co.elastic.clients.elasticsearch.core.GetResponse; +import co.elastic.clients.elasticsearch.core.IndexResponse; +import co.elastic.clients.elasticsearch.core.SearchResponse; +import co.elastic.clients.elasticsearch.core.search.Hit; +import co.elastic.clients.elasticsearch.core.search.HitsMetadata; + +@Service +public class NewsInfoService { + + private static final Logger log = LoggerFactory.getLogger(NewsInfoService.class); + + private final ElasticsearchClient elasticsearchClient; + private final ObjectMapper objectMapper; + private final TagMapper tagMapper; + private final IndustryMapper industryMapper; + private final NewsMapper newsMapper; + + public NewsInfoService(ElasticsearchClient elasticsearchClient, + ObjectMapper objectMapper, + TagMapper tagMapper, + IndustryMapper industryMapper, + NewsMapper newsMapper) { + this.elasticsearchClient = elasticsearchClient; + this.objectMapper = objectMapper; + this.tagMapper = tagMapper; + this.industryMapper = industryMapper; + this.newsMapper = newsMapper; + } + + public ResultObject publish(String id, long editorId) { + try { + GetResponse newsInfoResp = elasticsearchClient.get(g -> g.index(NewsInfo.INDEX_NAME).id(String.valueOf(id)), NewsInfo.class); + if (!newsInfoResp.found()) { + return ResultObject.success(); + } + + NewsInfo newsInfo = newsInfoResp.source(); + newsInfo.setId(id); + if (newsInfo.getStatus() == null ||newsInfo.getStatus().intValue() == 1) { + newsInfo.setStatus(2); + } else { + newsInfo.setStatus(1); + } + + News relateNews = newsMapper.getNewsDetail(newsInfo.getNewsId()); + + if (relateNews == null||!Objects.equals(relateNews.getTitle(), newsInfo.getTitle())) { + // 插入数据到News表中 + News relNews = newsInfo.toNews(); + relNews.setEditorId(editorId); + newsMapper.saveNews(relNews); + newsInfo.setNewsId(relNews.getId()); + + + String industry = newsInfo.getIndustry(); + if (!ObjectUtils.isEmpty(industry)) { + List industries = industryMapper.queryAll(); + for (Industry industryItem: industries) { + if (industry.equals(industryItem.getPrimaryName()) || industryItem.equals(industryItem.getSecondaryName())) { + industryMapper.saveNewsIndustryRel(newsInfo.getNewsId(), industryItem.getId()); + } + } + } + + String sourcename = newsInfo.getSourcename(); + if (!ObjectUtils.isEmpty(sourcename)) { + List matchedTags = tagMapper.queryTag(1L, sourcename, null, "create_time", null); + for (Tag tag: matchedTags) { + if (tag.getName().equals(sourcename)) { + tagMapper.saveNewsTagRel(newsInfo.getNewsId(), tag.getId()); + } + } + } + } else { + if (newsInfo.getStatus().intValue() == 2) { + newsMapper.simplePublish(newsInfo.getNewsId(), editorId); + } else { + newsMapper.simpleUnpublish(newsInfo.getNewsId()); + } + } + + NewsInfo publishedNewsInfo = new NewsInfo(); + publishedNewsInfo.setStatus(newsInfo.getStatus()); + publishedNewsInfo.setNewsId(newsInfo.getNewsId()); + + elasticsearchClient.update(e -> e.index(NewsInfo.INDEX_NAME).refresh(Refresh.True).id(String.valueOf(id)).doc(publishedNewsInfo), NewsInfo.class); + + } catch(IOException e) { + log.error("全量资讯发布失败!", e); + return ResultObject.failed(500, "服务器错误,请联系系统管理员!"); + } + + return ResultObject.success(); + } + + public ResultObject detail(String id) { + + try { + GetResponse newsInfoResp = elasticsearchClient.get(g -> g.index(NewsInfo.INDEX_NAME).id(String.valueOf(id)), NewsInfo.class); + if (!newsInfoResp.found()) { + return ResultObject.success(); + } + NewsInfo newsInfo = newsInfoResp.source(); + newsInfo.setId(id); + + NewsInfoDetailVO newsInfoDetailVO = new NewsInfoDetailVO(newsInfo); + + List allTags = tagMapper.queryAll(); + Map tagMap = allTags.stream().collect(Collectors.toMap(Tag::getId, Function.identity())); + List allIndustrys = industryMapper.queryAll(); + + + if (newsInfo.getStatus() == null ||newsInfo.getStatus() == 1) { + for (Tag tag: allTags) { + if (tag.getName().equals(newsInfo.getSourcename())) { + NewsDetailTag newsDetailTag = new NewsDetailTag(); + + NewsDetailTagItem sourceTagItem = new NewsDetailTagItem(); + newsDetailTag.setSource(sourceTagItem); + sourceTagItem.setId(tag.getId()); + sourceTagItem.setName(tag.getName()); + newsInfoDetailVO.setSourcename(newsDetailTag); + } + } + String industryStr = newsInfo.getIndustry(); + for (Industry industry: allIndustrys) { + if (Objects.equals(industry.getDisplayName(), industryStr)) { + NewsDetailIndustry newsDetailIndustry = new NewsDetailIndustry(); + newsDetailIndustry.setId(newsDetailIndustry.getId()); + newsDetailIndustry.setPrimaryName(newsDetailIndustry.getPrimaryName()); + newsDetailIndustry.setSecondaryName(newsDetailIndustry.getSecondaryName()); + newsInfoDetailVO.setIndustry(Arrays.asList(newsDetailIndustry)); + } + } + + } else { + Map industryMap = allIndustrys.stream().collect(Collectors.toMap(Industry::getId, Function.identity())); + + List tagRelList = tagMapper.getNewsTagRelList(newsInfo.getNewsId()); + NewsDetailTag newsDetailTag = new NewsDetailTag(); + newsInfoDetailVO.setSourcename(newsDetailTag); + + if (!CollectionUtils.isEmpty(tagRelList)) { + List arr = new ArrayList<>(); + for (NewsTagRel rel : tagRelList) { + Long tagId = rel.getTagId(); + Tag tag = tagMap.get(tagId); + Long parentId = tag.getParentId(); + NewsDetailTagItem tagItem = new NewsDetailTagItem(); + tagItem.setId(tagId); + tagItem.setName(tag.getName()); + if (parentId != null && parentId == 1) { + newsDetailTag.setSource(tagItem); + } else { + newsDetailTag.setField(tagItem); + arr.add(tagItem); + } + } + newsDetailTag.setFieldArr(arr); + } + + List industryRelList = industryMapper.getNewsIndustryRelList(newsInfo.getNewsId()); + List newsIndustryList = new ArrayList<>(); + newsInfoDetailVO.setIndustry(newsIndustryList); + if (!CollectionUtils.isEmpty(industryRelList)) { + for (NewsIndustryRel rel: industryRelList) { + Long industryId = rel.getIndustryId(); + Industry industry = industryMap.get(industryId); + NewsDetailIndustry newsDetailIndustry = new NewsDetailIndustry(); + newsDetailIndustry.setId(industryId); + newsDetailIndustry.setPrimaryName(industry.getPrimaryName()); + newsDetailIndustry.setSecondaryName(industry.getSecondaryName()); + newsIndustryList.add(newsDetailIndustry); + } + } + + } + return ResultObject.success(newsInfoDetailVO); + } catch(IOException e) { + log.error("获取全量资讯详情异常!", e); + return ResultObject.failed(500, "服务器错误,请联系系统管理员!"); + } + } + + public ResultObject save(SaveNewsInfoDTO saveNewsDTO) { + String id = saveNewsDTO.getId(); + if (ObjectUtils.isEmpty(id)) { + return saveNewsInfo(saveNewsDTO); + } + return updateNewsInfo(saveNewsDTO); + } + + + private ResultObject updateNewsInfo(SaveNewsInfoDTO saveNewsInfoDTO) { + String id = saveNewsInfoDTO.getId(); + + try { + GetResponse getResp = elasticsearchClient.get(e -> e.index(NewsInfo.INDEX_NAME).id(String.valueOf(id)), NewsInfo.class); + if (getResp.found()) { + NewsInfo newsInfo = getResp.source(); + newsInfo.setId(id); + Long newsId = newsInfo.getNewsId(); + News relateNews = newsMapper.getNewsDetail(newsId); + + if (relateNews == null ||!Objects.equals(newsInfo.getTitle(), relateNews.getTitle())) { + News news = createRelateNews(saveNewsInfoDTO); + newsId = news.getId(); + } else { + updateRelateNews(newsId, saveNewsInfoDTO); + } + NewsInfo updatedNewsInfo = toNewsInfo(newsId, saveNewsInfoDTO); + elasticsearchClient.update(e -> e.index(NewsInfo.INDEX_NAME).id(updatedNewsInfo.getId()).doc(updatedNewsInfo).refresh(Refresh.True), NewsInfo.class); + } + } catch(IOException e) { + log.error("全量资讯更新异常!", e); + return ResultObject.failed(500, "服务器错误,请联系系统管理员!"); + } + return ResultObject.success(); + } + + public ResultObject deleteNewsInfo(String newsInfoId) { + try { + GetResponse getResp = elasticsearchClient.get(e -> e.index(NewsInfo.INDEX_NAME).id(String.valueOf(newsInfoId)), NewsInfo.class); + if (getResp.found()) { + NewsInfo newsInfo = getResp.source(); + Long newsId = newsInfo.getNewsId(); + // 删除资讯精选中关联的数据 + deleteNewsRel(newsId); + newsMapper.deleteNews(newsId); + + NewsInfo deletedNewsInfo = new NewsInfo(); + deletedNewsInfo.setDeleted(1); + elasticsearchClient.update(e -> e.index(NewsInfo.INDEX_NAME).refresh(Refresh.True).id(newsInfoId).doc(deletedNewsInfo), NewsInfo.class); + } + + } catch(IOException e) { + log.error("删除全量资讯异常!", e); + return ResultObject.failed(500, "服务器错误,请联系系统管理员!"); + } + return ResultObject.success(); + } + + private void deleteNewsRel(Long newsId) { + tagMapper.deleteNews(newsId); + industryMapper.deleteNews(newsId); + } + + private ResultObject saveNewsInfo(SaveNewsInfoDTO saveNewsDTO) { + // 创建关联的资讯精选 + News news = createRelateNews(saveNewsDTO); + NewsInfo newsInfo = toNewsInfo(news.getId(), saveNewsDTO); + try { + IndexResponse resp = elasticsearchClient.index(c -> c.index(NewsInfo.INDEX_NAME).document(newsInfo).refresh(Refresh.True)); + news.setNewsinfoId(resp.id()); + newsMapper.updateNews(news); + } catch(IOException|ElasticsearchException e) { + log.error("新建全量资讯出错!", e); + return ResultObject.failed(500, "服务器错误,请联系系统管理员!"); + } + return ResultObject.success(); + } + + private NewsInfo toNewsInfo(Long newsId, SaveNewsInfoDTO saveNewsDTO) { + NewsInfo newsInfo = saveNewsDTO.toNewsInfo(); + newsInfo.setNewsId(newsId); + List industries = saveNewsDTO.getIndustries(); + if (!ObjectUtils.isEmpty(industries)) { + List allIndustry = industryMapper.queryAll(); + Map industryMap = allIndustry.stream().collect(Collectors.toMap(Industry::getId, Function.identity())); + String industry = industries.stream().map(e -> industryMap.get(e)).map(Industry::getDisplayName).collect(Collectors.joining(";")); + newsInfo.setIndustry(industry); + } + + SaveDraftTag tag = saveNewsDTO.getTag(); + if (tag != null) { + if (tag.getSource() != null) { + Tag sourceTag = tagMapper.queryById(tag.getSource()); + if (sourceTag != null) { + newsInfo.setSourcename(sourceTag.getName()); + } + } + } + return newsInfo; + } + + private void updateRelateNews(Long newsId, SaveNewsInfoDTO saveNewsDTO) { + News news = saveNewsDTO.toNews(); + news.setId(newsId); + newsMapper.updateNews(news); + + tagMapper.deleteNews(newsId); + industryMapper.deleteNews(newsId); + + SaveDraftTag saveDraftTag = saveNewsDTO.getTag(); + if (saveDraftTag != null) { + Long source = saveDraftTag.getSource(); + if (source != null) { + tagMapper.saveNewsTagRel(newsId, source); + } + List fieldArr = saveDraftTag.getFieldArr(); + if (fieldArr != null) { + for (Long field: fieldArr) { + tagMapper.saveNewsTagRel(newsId, field); + } + } + } + + List industries = saveNewsDTO.getIndustries(); + if (industries != null) { + for (Long industry: industries) { + tagMapper.saveNewsTagRel(newsId, industry); + } + } + } + + + private News createRelateNews(SaveNewsInfoDTO saveNewsInfoDTO) { + News news = saveNewsInfoDTO.toNews(); + newsMapper.saveNews(news); + + // 保存标签关系 + try { + SaveDraftTag saveDraftTag = saveNewsInfoDTO.getTag(); + if (saveDraftTag != null) { + Long source = saveDraftTag.getSource(); + if (source != null) { + tagMapper.saveNewsTagRel(news.getId(), source); + } + + // 频道标签多个,批量插 + List fieldArr = saveDraftTag.getFieldArr(); + if (fieldArr != null) { + for (Long item : fieldArr) { + tagMapper.saveNewsTagRel(news.getId(), item); + } + } + } + } catch (Exception e) { + log.error("保存发布全量资讯关联标签出错!", e); + } + + + // 保存新闻行业分类关系 + try { + List saveDraftIndustries = saveNewsInfoDTO.getIndustries(); + if (!CollectionUtils.isEmpty(saveDraftIndustries)) { + for (Long industryId : saveDraftIndustries) { + industryMapper.saveNewsIndustryRel(news.getId(), industryId); + } + } + } catch (Exception e) { + log.error("保存发布新闻行业分类出错!", e); + } + return news; + } + + public PageObject queryNewsInfo(String title, String content, String stockcode, Long sourcename, int page, int size, + Integer last, Integer current, String orderBy, String direction) { + + if (StringUtils.hasText(orderBy)) { + switch (orderBy) { + case "publishTime": { + orderBy = "publish_time"; + break; + } + case "updateTime": { + orderBy = "update_time"; + break; + } + case "createTime": { + orderBy = "create_time"; + break; + } + } + } + + final String orderByField = orderBy; + int offset = 0; + if (current != null) { + offset = (Math.max(0, current - 1)) * size; + } + int from = offset; + long total = 0; + List newsInfoList = new ArrayList<>(); + + try { + Query deletedQuery = TermQuery.of(t -> t.field(NewsInfo.DELETED_ES_NAME).value(0L))._toQuery(); +// BoolQuery boolQuery = BoolQuery.of(e -> e.filter(deletedQuery)); // QueryBuilders.bool().build() + + List conditions = new ArrayList<>(); + List filters = new ArrayList<>(); + filters.add(deletedQuery); + + if (!ObjectUtils.isEmpty(title)) { + Query titleQuery = MatchQuery.of(m -> m.field(NewsInfo.TITLE_ES_NAME).query(title))._toQuery(); // .analyzer("ik_max_word") + conditions.add(titleQuery); + } + if (!ObjectUtils.isEmpty(content)) { + Query summaryContentQuery = MultiMatchQuery.of(m -> m.fields(NewsInfo.CONTENT_ES_NAME, NewsInfo.SUMMARY_ES_NAME).query(content).operator(Operator.Or))._toQuery(); // .analyzer("ik_max_word") + conditions.add(summaryContentQuery); + } + if (sourcename != null) { + Tag sourcenameTag = tagMapper.queryById(sourcename); + if (sourcenameTag != null) { + Query sourcenameQuery = TermQuery.of(t -> t.field(NewsInfo.SOURCE_ES_NAME).value(sourcenameTag.getName()))._toQuery(); + filters.add(sourcenameQuery); + } + } + + if (conditions.size() == 0) { + conditions.add(QueryBuilders.matchAll().build()._toQuery()); + } + Query resultQuery = new Query.Builder().bool(b -> b.must(conditions).filter(filters)).build(); + SortOrder sortOrder = (Objects.equals(direction, SortOrder.Asc.jsonValue()) ?SortOrder.Asc:SortOrder.Desc); + SearchResponse searchResp = elasticsearchClient.search(s -> s.index(NewsInfo.INDEX_NAME) + .from(from) // 分页参数 + .size(size) // 分页参数 + .sort(so -> so.field(f -> f.field("_score").order(SortOrder.Desc).field(orderByField).order(sortOrder))) // 排序字段 + .query(resultQuery) + .highlight(h -> h.preTags("") + .postTags("") + .requireFieldMatch(true) + .fields(NewsInfo.SUMMARY_ES_NAME, hf -> hf.numberOfFragments(0)) + .fields(NewsInfo.CONTENT_ES_NAME, hf -> hf.numberOfFragments(0)) + .fields(NewsInfo.TITLE_ES_NAME, hf -> hf.numberOfFragments(0))), NewsInfo.class); + + HitsMetadata hits = searchResp.hits(); + total = hits.total().value(); + for (Hit hit: hits.hits()) { + NewsInfo newsInfo = hit.source(); + Map> highlight = hit.highlight(); + + newsInfo.setTitle(highlight.get(NewsInfo.TITLE_ES_NAME) == null ? newsInfo.getTitle():highlight.get(NewsInfo.TITLE_ES_NAME).get(0)); + newsInfo.setSummary(highlight.get(NewsInfo.SUMMARY_ES_NAME) == null ? newsInfo.getSummary():highlight.get(NewsInfo.SUMMARY_ES_NAME).get(0)); + newsInfo.setContent(highlight.get(NewsInfo.CONTENT_ES_NAME) == null ? newsInfo.getContent():highlight.get(NewsInfo.CONTENT_ES_NAME).get(0)); + + newsInfo.setId(hit.id()); + newsInfoList.add(newsInfo); + } + } catch (Exception e) { + log.error("搜索新闻异常!", e); + return PageObject.failedPage(500, "服务器错误,请联系系统管理员!"); + } + + PageObject pageObject = new PageObject<>(); + pageObject.setTotal(Long.valueOf(total).intValue()); + pageObject.setCode(200); + pageObject.setCurrent(page); + pageObject.setSize(Math.min(newsInfoList.size(), size)); + if (CollectionUtils.isEmpty(newsInfoList)) { + log.info("搜索结果为空!"); + pageObject.setData(new ArrayList<>()); + return pageObject; + } + + List resultList = new ArrayList<>(); + for (NewsInfo newsInfo : newsInfoList) { + NewsInfoVO newsInfoVO = new NewsInfoVO(newsInfo); + resultList.add(newsInfoVO); + } + + pageObject.setData(resultList); + return pageObject; + } +} diff --git a/core/src/main/java/com/jinrui/reference/core/service/NewsService.java b/core/src/main/java/com/jinrui/reference/core/service/NewsService.java index a21b3aa..3f3bd33 100644 --- a/core/src/main/java/com/jinrui/reference/core/service/NewsService.java +++ b/core/src/main/java/com/jinrui/reference/core/service/NewsService.java @@ -1,5 +1,6 @@ package com.jinrui.reference.core.service; +import java.io.IOException; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; @@ -14,6 +15,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; +import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; import com.fasterxml.jackson.core.JsonProcessingException; @@ -36,6 +38,7 @@ import com.jinrui.reference.core.model.entity.News; import com.jinrui.reference.core.model.entity.NewsColumnRel; import com.jinrui.reference.core.model.entity.NewsDraft; import com.jinrui.reference.core.model.entity.NewsIndustryRel; +import com.jinrui.reference.core.model.entity.NewsInfo; import com.jinrui.reference.core.model.entity.NewsTagRel; import com.jinrui.reference.core.model.entity.Tag; import com.jinrui.reference.core.model.vo.PageObject; @@ -49,6 +52,9 @@ import com.jinrui.reference.core.model.vo.news.NewsDetailTagItem; import com.jinrui.reference.core.model.vo.news.NewsDetailVO; import com.jinrui.reference.core.model.vo.news.NewsVO; +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.elasticsearch._types.Refresh; + @Service public class NewsService { @@ -59,17 +65,20 @@ public class NewsService { private final ObjectMapper objectMapper; private final TagMapper tagMapper; private final IndustryMapper industryMapper; + private final ElasticsearchClient elasticsearchClient; public NewsService(NewsMapper newsMapper, ColumnMapper columnMapper, ObjectMapper objectMapper, TagMapper tagMapper, - IndustryMapper industryMapper) { + IndustryMapper industryMapper, + ElasticsearchClient elasticsearchClient) { this.newsMapper = newsMapper; this.columnMapper = columnMapper; this.objectMapper = objectMapper; this.tagMapper = tagMapper; this.industryMapper = industryMapper; + this.elasticsearchClient = elasticsearchClient; } public ResultObject publish(long id, long editorId) { @@ -81,12 +90,14 @@ public class NewsService { Integer status = news.getStatus(); if (status == 2) { newsMapper.simpleUnpublish(id); + syncStatusToNewsInfo(news.getNewsinfoId(), 1); return ResultObject.success(); } Long draftId = news.getDraftId(); if (draftId == null) { newsMapper.simplePublish(id, editorId); + syncStatusToNewsInfo(news.getNewsinfoId(), 2); return ResultObject.success(); } @@ -99,6 +110,16 @@ public class NewsService { SaveNewsDTO saveNewsDTO = new SaveNewsDTO(newsDetailVO); return createPublish(editorId, saveNewsDTO); } + + private void syncStatusToNewsInfo(String newsInfoId, Integer status) { + NewsInfo publishedNewsInfo = new NewsInfo(); + publishedNewsInfo.setStatus(status); + try { + elasticsearchClient.update(e -> e.index(NewsInfo.INDEX_NAME).refresh(Refresh.True).id(newsInfoId).doc(publishedNewsInfo), NewsInfo.class); + } catch(IOException e) { + log.error("资讯精选状态同步至全量资讯出错!", e); + } + } public ResultObject detail(Long id) { News news = newsMapper.getById(id); @@ -415,8 +436,48 @@ public class NewsService { log.error("保存发布新闻标签出错!", e); return ResultObject.failed(500, "服务器错误,请联系系统管理员!"); } + + // 同步更新对应的全量资讯逻辑 + try { + syncUpdateNewsInfo(saveNewsDTO); + } catch(IOException e) { + log.error("同步更新对应的全量资讯出错!", e); + return ResultObject.failed(500, "服务器错误,请联系系统管理员!"); + } + return ResultObject.success(); } + + private void syncUpdateNewsInfo(SaveNewsDTO saveNewsDTO) throws IOException { + Long newsId = saveNewsDTO.getId(); + if (newsId == null) { + return; + } + News news = newsMapper.getById(newsId); + String newsInfoId = news.getNewsinfoId(); + if (ObjectUtils.isEmpty(newsInfoId)) { + return; + } + + NewsInfo newsInfo = saveNewsDTO.toNewsInfo(); + newsInfo.setStatus(2); + SaveDraftTag saveDraftTag = saveNewsDTO.getTag(); + if (saveDraftTag != null && saveDraftTag.getSource() != null) { + Tag sourceTag = tagMapper.queryById(saveDraftTag.getSource()); + if (sourceTag != null) { + newsInfo.setSourcename(sourceTag.getName()); + } + } + List industries = saveNewsDTO.getIndustries(); + if (!ObjectUtils.isEmpty(industries)) { + List allIndustry = industryMapper.queryAll(); + Map industryMap = allIndustry.stream().collect(Collectors.toMap(Industry::getId, Function.identity())); + String industry = industries.stream().map(e -> industryMap.get(e)).map(Industry::getDisplayName).collect(Collectors.joining(";")); + newsInfo.setIndustry(industry); + } + + elasticsearchClient.update(e -> e.index(NewsInfo.INDEX_NAME).refresh(Refresh.True).id(newsInfoId).doc(newsInfo), NewsInfo.class); + } public ResultObject saveDraft(SaveNewsDTO saveNewsDTO) { Long id = saveNewsDTO.getId(); @@ -461,6 +522,15 @@ public class NewsService { } try { newsMapper.deleteNews(newsId); + String newsInfoId = news.getNewsinfoId(); + if (!ObjectUtils.isEmpty(newsInfoId)) { + NewsInfo deletedNewsInfo = new NewsInfo(); + deletedNewsInfo.setDeleted(1); + elasticsearchClient.update(e -> e.index(NewsInfo.INDEX_NAME) + .refresh(Refresh.True) + .id(newsInfoId) + .doc(deletedNewsInfo), NewsInfo.class); + } } catch (Exception e) { log.error("删除新闻异常!", e); return ResultObject.failed(500, "服务器错误,请联系系统管理员!"); diff --git a/mini/pom.xml b/mini/pom.xml index b1a4945..ae18a82 100644 --- a/mini/pom.xml +++ b/mini/pom.xml @@ -15,6 +15,8 @@ 8 8 UTF-8 + 8.12.2 + 2.0.1 @@ -22,12 +24,32 @@ com.jinrui core 0.0.1-SNAPSHOT + + + org.elasticsearch.client + elasticsearch-rest-client + + + jakarta.json-api + jakarta.json + + redis.clients jedis ${jedis.version} + + org.elasticsearch.client + elasticsearch-rest-client + ${elasticsearch.version} + + + jakarta.json + jakarta.json-api + ${jakartajson.version} +