From 4f59c48c1c67d5e8ba1af17e90920cddeb531b43 Mon Sep 17 00:00:00 2001 From: sunflower2014 Date: Tue, 22 Jul 2025 15:47:33 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B5=84=E8=AE=AF=E7=B2=BE=E9=80=89=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E6=93=8D=E4=BD=9C=E6=97=A5=E5=BF=97=E8=AE=B0=E5=BD=95?= =?UTF-8?q?=E3=80=81=E9=80=81=E5=AE=A1=E3=80=81=E5=8F=91=E5=B8=83=E7=AD=89?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- admin/pom.xml | 22 +- .../com/jinrui/reference/admin/.gitignore | 2 + .../reference/admin/AdminApplication.java | 22 +- .../com/jinrui/reference/admin/ApiClient.java | 68 ++++ .../admin/annotation/OperationInfo.java | 18 + .../admin/aspect/UserOperationLogAspect.java | 66 ++++ .../admin/controller/ApiController.java | 80 ++++ .../admin/controller/CommonController.java | 18 +- .../admin/controller/NewsController.java | 250 +++++++++++- .../admin/controller/NewsInfoController.java | 40 +- .../admin/job/NewsDeduplicationJob.java | 39 ++ .../job/NewsTagsIndustryLabelRepairJob.java | 271 +++++++++++++ .../admin/mapper/AdminUserMapper.java | 2 + .../admin/model/entity/AdminUser.java | 22 ++ .../admin/model/vo/login/LoginVO.java | 14 + .../admin/service/AdminJwtService.java | 3 + .../admin/service/AdminUserService.java | 21 +- .../service/UserOperationLogService.java | 21 + admin/src/main/resources/.gitignore | 1 + admin/src/main/resources/application-dev.yml | 43 +- admin/src/main/resources/application.yml | 5 +- admin/src/main/resources/sql/20250716.sql | 5 + core/pom.xml | 9 + .../jinrui/reference/core/RedisConfig.java | 34 ++ .../com/jinrui/reference/core/WebConfig.java | 17 + .../reference/core/api/ApiKeyGenerator.java | 18 + .../reference/core/api/ApiReturnCode.java | 27 ++ .../core/interceptor/ApiAuthInterceptor.java | 123 ++++++ .../reference/core/mapper/ApiUserMapper.java | 19 + .../core/mapper/NewsDeletedMapper.java | 12 + .../reference/core/mapper/NewsMapper.java | 173 +++++--- .../reference/core/mapper/NewsTagsMapper.java | 6 + .../core/mapper/UserOperationLogMapper.java | 35 ++ .../core/model/dto/news/SaveNewsDTO.java | 20 + .../reference/core/model/entity/News.java | 59 ++- .../core/model/entity/NewsDeleted.java | 66 ++++ .../reference/core/model/entity/NewsInfo.java | 16 +- .../reference/core/model/entity/NewsTags.java | 30 +- .../core/model/entity/UserOperationLog.java | 63 +++ .../core/model/vo/news/NewsApiVO.java | 221 +++++++++++ .../core/model/vo/news/NewsDetailVO.java | 91 ++++- .../core/model/vo/news/NewsLogVO.java | 44 +++ .../core/model/vo/news/NewsTranslatorVO.java | 50 +++ .../reference/core/model/vo/news/NewsVO.java | 22 ++ .../core/model/vo/newsinfo/NewsInfoVO.java | 18 +- .../model/vo/newsinfo/SaveNewsInfoDTO.java | 8 + .../reference/core/service/ApiKeyService.java | 49 +++ .../core/service/NewsInfoService.java | 149 +++++-- .../reference/core/service/NewsService.java | 370 +++++++++++++----- mini/pom.xml | 4 +- .../reference/mini/MiniApplication.java | 26 +- 51 files changed, 2541 insertions(+), 271 deletions(-) create mode 100644 admin/src/main/java/com/jinrui/reference/admin/.gitignore create mode 100644 admin/src/main/java/com/jinrui/reference/admin/ApiClient.java create mode 100644 admin/src/main/java/com/jinrui/reference/admin/annotation/OperationInfo.java create mode 100644 admin/src/main/java/com/jinrui/reference/admin/aspect/UserOperationLogAspect.java create mode 100644 admin/src/main/java/com/jinrui/reference/admin/controller/ApiController.java create mode 100644 admin/src/main/java/com/jinrui/reference/admin/job/NewsDeduplicationJob.java create mode 100644 admin/src/main/java/com/jinrui/reference/admin/job/NewsTagsIndustryLabelRepairJob.java create mode 100644 admin/src/main/java/com/jinrui/reference/admin/service/UserOperationLogService.java create mode 100644 admin/src/main/resources/.gitignore create mode 100644 admin/src/main/resources/sql/20250716.sql create mode 100644 core/src/main/java/com/jinrui/reference/core/RedisConfig.java create mode 100644 core/src/main/java/com/jinrui/reference/core/api/ApiKeyGenerator.java create mode 100644 core/src/main/java/com/jinrui/reference/core/api/ApiReturnCode.java create mode 100644 core/src/main/java/com/jinrui/reference/core/interceptor/ApiAuthInterceptor.java create mode 100644 core/src/main/java/com/jinrui/reference/core/mapper/ApiUserMapper.java create mode 100644 core/src/main/java/com/jinrui/reference/core/mapper/NewsDeletedMapper.java create mode 100644 core/src/main/java/com/jinrui/reference/core/mapper/UserOperationLogMapper.java create mode 100644 core/src/main/java/com/jinrui/reference/core/model/entity/NewsDeleted.java create mode 100644 core/src/main/java/com/jinrui/reference/core/model/entity/UserOperationLog.java create mode 100644 core/src/main/java/com/jinrui/reference/core/model/vo/news/NewsApiVO.java create mode 100644 core/src/main/java/com/jinrui/reference/core/model/vo/news/NewsLogVO.java create mode 100644 core/src/main/java/com/jinrui/reference/core/model/vo/news/NewsTranslatorVO.java create mode 100644 core/src/main/java/com/jinrui/reference/core/service/ApiKeyService.java diff --git a/admin/pom.xml b/admin/pom.xml index fdb215c..c77f4c9 100644 --- a/admin/pom.xml +++ b/admin/pom.xml @@ -35,11 +35,6 @@ - - redis.clients - jedis - ${jedis.version} - org.elasticsearch.client elasticsearch-rest-client @@ -50,6 +45,23 @@ jakarta.json-api ${jakartajson.version} + + commons-io + commons-io + 2.19.0 + + + org.apache.pdfbox + pdfbox + 3.0.5 + + + + com.itextpdf + itext7-core + 9.2.0 + pom + diff --git a/admin/src/main/java/com/jinrui/reference/admin/.gitignore b/admin/src/main/java/com/jinrui/reference/admin/.gitignore new file mode 100644 index 0000000..d69442e --- /dev/null +++ b/admin/src/main/java/com/jinrui/reference/admin/.gitignore @@ -0,0 +1,2 @@ +/PDFTextSearcher.java +/PDFTextSearcherItext.java diff --git a/admin/src/main/java/com/jinrui/reference/admin/AdminApplication.java b/admin/src/main/java/com/jinrui/reference/admin/AdminApplication.java index 5ae9552..4a1b77c 100644 --- a/admin/src/main/java/com/jinrui/reference/admin/AdminApplication.java +++ b/admin/src/main/java/com/jinrui/reference/admin/AdminApplication.java @@ -1,14 +1,10 @@ package com.jinrui.reference.admin; import org.mybatis.spring.annotation.MapperScan; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.Bean; -import org.springframework.util.StringUtils; - -import redis.clients.jedis.JedisPool; -import redis.clients.jedis.JedisPoolConfig; +import org.springframework.context.annotation.EnableAspectJAutoProxy; +import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication(scanBasePackages = { "com.jinrui.reference.admin", @@ -17,21 +13,11 @@ import redis.clients.jedis.JedisPoolConfig; @MapperScan({ "com.jinrui.reference.admin.mapper", "com.jinrui.reference.core.mapper"}) +@EnableScheduling +@EnableAspectJAutoProxy public class AdminApplication { public static void main(String[] args) { SpringApplication.run(AdminApplication.class, args); } - - @Bean - public JedisPool jedisPool(@Value("${redis.host}") String host, - @Value("${redis.port}") int port, - @Value("${redis.timeout:1000}") int timeout, - @Value("${redis.password:}") String password) { - if (StringUtils.hasText(password)) { - JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); - return new JedisPool(jedisPoolConfig, host, port, timeout, password); - } - return new JedisPool(host, port); - } } diff --git a/admin/src/main/java/com/jinrui/reference/admin/ApiClient.java b/admin/src/main/java/com/jinrui/reference/admin/ApiClient.java new file mode 100644 index 0000000..6c5219c --- /dev/null +++ b/admin/src/main/java/com/jinrui/reference/admin/ApiClient.java @@ -0,0 +1,68 @@ +package com.jinrui.reference.admin; + +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +import org.apache.commons.codec.binary.Hex; +import org.apache.commons.io.IOUtils; + +public class ApiClient { + public final static String ak = "6a91a5070b044eb0b798f9420ccae15d"; + public final static String sk = "clkky/Ad6d4DXDZ0UzemOKUKroLwSmc5vlIb2Sjh6YM="; + public final static String baseUrl = "http://127.0.0.1:13579"; + + public ApiClient() { + } + + public static void main(String[] args) throws Exception { + String path = "/admin/api/news"; + Map params = new HashMap<>(); + params.put("num", String.valueOf(20)); + String result = callApi(path, params); + System.out.println("+++++++++++++++++++++++++++++++++++++: " + result); + } + + public static String callApi(String path, Map params) throws Exception { + String timestamp = String.valueOf(System.currentTimeMillis()); + String nonce = UUID.randomUUID().toString(); + String sortedParams = sortParams(params); + String url = baseUrl + path + "?" + sortedParams; + + // 1. 计算签名 + String data = "GET" + path + sortedParams + timestamp + nonce; + String signature = calculateSignature(data, sk); + + // 2. 设置请求头 + HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); + conn.setRequestMethod("GET"); + conn.setRequestProperty("X-Api-Key", ak); + conn.setRequestProperty("X-Timestamp", timestamp); + conn.setRequestProperty("X-Nonce", nonce); + conn.setRequestProperty("X-Signature", signature); + + // 3. 发送请求 + return IOUtils.toString(conn.getInputStream(), StandardCharsets.UTF_8); + } + + public static String calculateSignature(String data, String sk) throws Exception { + Mac sha256 = Mac.getInstance("HmacSHA256"); + sha256.init(new SecretKeySpec(sk.getBytes(), "HmacSHA256")); + byte[] signBytes = sha256.doFinal(data.getBytes(StandardCharsets.UTF_8)); + return Hex.encodeHexString(signBytes); + } + + public static String sortParams(Map params) { + return params.entrySet().stream() + .sorted(Map.Entry.comparingByKey()) + .map(e -> e.getKey() + "=" + e.getValue()) + .collect(Collectors.joining("&")); + } +} diff --git a/admin/src/main/java/com/jinrui/reference/admin/annotation/OperationInfo.java b/admin/src/main/java/com/jinrui/reference/admin/annotation/OperationInfo.java new file mode 100644 index 0000000..2e479c3 --- /dev/null +++ b/admin/src/main/java/com/jinrui/reference/admin/annotation/OperationInfo.java @@ -0,0 +1,18 @@ +package com.jinrui.reference.admin.annotation; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Documented +@Retention(RUNTIME) +@Target({ METHOD, ANNOTATION_TYPE }) +public @interface OperationInfo { + String type() default ""; + + String behavior(); +} diff --git a/admin/src/main/java/com/jinrui/reference/admin/aspect/UserOperationLogAspect.java b/admin/src/main/java/com/jinrui/reference/admin/aspect/UserOperationLogAspect.java new file mode 100644 index 0000000..f33fd90 --- /dev/null +++ b/admin/src/main/java/com/jinrui/reference/admin/aspect/UserOperationLogAspect.java @@ -0,0 +1,66 @@ +package com.jinrui.reference.admin.aspect; + +import java.lang.reflect.Method; + +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.AfterThrowing; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.MethodSignature; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.jinrui.reference.admin.annotation.OperationInfo; +import com.jinrui.reference.admin.model.dto.news.PublishNewsDTO; +import com.jinrui.reference.admin.service.UserOperationLogService; +import com.jinrui.reference.core.model.vo.ResultObject; + +@Component +@Aspect +public class UserOperationLogAspect { + private static final Logger LOGGER = LoggerFactory.getLogger(UserOperationLogAspect.class); + + @Autowired + private UserOperationLogService userOperationLogService; + + @Pointcut("@annotation(com.jinrui.reference.admin.annotation.OperationInfo)") + public void userOperation() {} + + @AfterThrowing(pointcut = "userOperation()", throwing = "e") + public void handleThrowing(JoinPoint point, Exception e) { + + } + + @Around("userOperation()") + public Object handleSuccess(ProceedingJoinPoint point) throws Throwable { + Object result = point.proceed(); + OperationInfo operationInfo = getOperationInfo(point); + String behavior = operationInfo.behavior(); + String type = operationInfo.type(); + Object[] args = point.getArgs(); + + if (result instanceof ResultObject) { + int code = ((ResultObject)result).getCode(); + if (code != 200) { + return result; + } + } + if ("news".equals(type)) { + userOperationLogService.logUserOperation(type, behavior, (String)args[0], ((PublishNewsDTO)args[1]).getId()); + } + + + return result; + } + + private OperationInfo getOperationInfo(JoinPoint point) { + MethodSignature ms = (MethodSignature)point.getSignature(); + Method method = ms.getMethod(); + return method.getAnnotation(OperationInfo.class); + } + +} diff --git a/admin/src/main/java/com/jinrui/reference/admin/controller/ApiController.java b/admin/src/main/java/com/jinrui/reference/admin/controller/ApiController.java new file mode 100644 index 0000000..8f44510 --- /dev/null +++ b/admin/src/main/java/com/jinrui/reference/admin/controller/ApiController.java @@ -0,0 +1,80 @@ +package com.jinrui.reference.admin.controller; + +import java.util.List; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +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.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import com.jinrui.reference.admin.model.entity.AdminUser; +import com.jinrui.reference.admin.service.AdminJwtService; +import com.jinrui.reference.core.model.vo.ResultObject; +import com.jinrui.reference.core.model.vo.news.NewsApiVO; +import com.jinrui.reference.core.service.ApiKeyService; +import com.jinrui.reference.core.service.NewsService; + +@RestController +@RequestMapping("/api") +public class ApiController { + private static final Logger log = LoggerFactory.getLogger(ApiController.class); + + private final NewsService newsService; + private final ApiKeyService apiKeyService; + + public ApiController(NewsService newsService, ApiKeyService apiKeyService) { + this.newsService = newsService; + this.apiKeyService = apiKeyService; + } + + @GetMapping("/news") + public ResultObject> getNews(@RequestParam(name = "num", required = true, defaultValue = "10") Integer num, @RequestParam(name = "last", required = false) Long last) { + return newsService.requestNewsByApi(num, last); + } + + @PostMapping("/news") + public ResultObject> queryNews(@RequestParam(name = "num", required = true, defaultValue = "10") Integer num, @RequestParam(name = "last", required = false) Long last) { + return newsService.requestNewsByApi(num, last); + } + + @PostMapping("/key") + public ResultObject> generateApiKey(@RequestHeader("auth-token") String token, + @RequestParam(name = "clientName", required = true) String clientName) { + 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: /api/key, method: POST, request user id: {}, clientName: {}", adminUserId, clientName); + return apiKeyService.generateApiKey(clientName); + } catch (Exception e) { + log.error("解析登陆Token出错!", e); + return ResultObject.failed(500, "服务端错误,请联系系统管理员!"); + } + } + + + @GetMapping("/key") + public ResultObject> getApiKey(@RequestHeader("auth-token") String token, + @RequestParam(name = "clientName", required = true) String clientName) { + return generateApiKey(token, clientName); + } +} diff --git a/admin/src/main/java/com/jinrui/reference/admin/controller/CommonController.java b/admin/src/main/java/com/jinrui/reference/admin/controller/CommonController.java index ffbf096..1141432 100644 --- a/admin/src/main/java/com/jinrui/reference/admin/controller/CommonController.java +++ b/admin/src/main/java/com/jinrui/reference/admin/controller/CommonController.java @@ -1,5 +1,16 @@ package com.jinrui.reference.admin.controller; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + import com.aliyuncs.auth.sts.AssumeRoleRequest; import com.aliyuncs.auth.sts.AssumeRoleResponse; import com.aliyuncs.http.MethodType; @@ -7,19 +18,12 @@ import com.aliyuncs.profile.DefaultProfile; import com.aliyuncs.profile.IClientProfile; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import com.jinrui.reference.admin.model.dto.newsinfo.PublishNewsInfoDTO; import com.jinrui.reference.admin.model.dto.oss.CloseableAcsClient; import com.jinrui.reference.admin.model.entity.AdminUser; import com.jinrui.reference.admin.model.vo.oss.OssVO; import com.jinrui.reference.admin.service.AdminJwtService; import com.jinrui.reference.core.model.vo.ResultObject; import com.jinrui.reference.core.service.NewsInfoService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.util.ObjectUtils; -import org.springframework.util.StringUtils; -import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/common") diff --git a/admin/src/main/java/com/jinrui/reference/admin/controller/NewsController.java b/admin/src/main/java/com/jinrui/reference/admin/controller/NewsController.java index c4d7d17..2028e10 100644 --- a/admin/src/main/java/com/jinrui/reference/admin/controller/NewsController.java +++ b/admin/src/main/java/com/jinrui/reference/admin/controller/NewsController.java @@ -1,6 +1,7 @@ package com.jinrui.reference.admin.controller; import java.util.Date; +import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -8,6 +9,7 @@ import org.springframework.format.annotation.DateTimeFormat; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; @@ -16,6 +18,7 @@ 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.annotation.OperationInfo; import com.jinrui.reference.admin.model.dto.news.PublishNewsDTO; import com.jinrui.reference.admin.model.entity.AdminUser; import com.jinrui.reference.admin.service.AdminJwtService; @@ -23,10 +26,15 @@ import com.jinrui.reference.core.model.dto.news.SaveNewsDTO; import com.jinrui.reference.core.model.vo.PageObject; import com.jinrui.reference.core.model.vo.ResultObject; import com.jinrui.reference.core.model.vo.news.NewsDetailVO; +import com.jinrui.reference.core.model.vo.news.NewsLogVO; import com.jinrui.reference.core.model.vo.news.NewsScoreVO; +import com.jinrui.reference.core.model.vo.news.NewsTranslatorVO; import com.jinrui.reference.core.model.vo.news.NewsVO; import com.jinrui.reference.core.service.NewsService; +import redis.clients.jedis.Jedis; +import redis.clients.jedis.JedisPool; + @RestController @RequestMapping("/news") public class NewsController { @@ -35,15 +43,96 @@ public class NewsController { private final NewsService newsService; private final ObjectMapper objectMapper; + private final JedisPool jedisPool; public NewsController(NewsService newsService, - ObjectMapper objectMapper) { + ObjectMapper objectMapper, + JedisPool jedisPool) { this.newsService = newsService; - this.objectMapper = objectMapper;; + this.objectMapper = objectMapper; + this.jedisPool = jedisPool; } + + @PostMapping("/submit") + @OperationInfo(behavior = "送审", type = "news") + public ResultObject submit(@RequestHeader("auth-token") String token, + @RequestBody PublishNewsDTO 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("当前用户已被封禁!请联系系统管理员!"); + } + + Long id = publishNewsDTO.getId(); + if (id == null) { + return ResultObject.failed("要送审的新闻ID不可为空!"); + } + boolean isSuccessed = setEditingFlag(id); + if (!isSuccessed) { + return ResultObject.failed("该资讯正在审核中,请勿重复操作!"); + } + log.info("path: /news/publish, method: POST, request user id: {}, news id: {}", adminUserId, id); + return newsService.submit(id, adminUserId); + } catch (Exception e) { + log.error("解析登陆Token出错!", e); + return ResultObject.failed(500, "服务端错误,请联系系统管理员!"); + } finally { + unsetEditingFlag(publishNewsDTO.getId()); + } + } + + @PostMapping("/revoke") + public ResultObject revoke(@RequestHeader("auth-token") String token, + @RequestBody PublishNewsDTO 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("当前用户已被封禁!请联系系统管理员!"); + } + + Long id = publishNewsDTO.getId(); + if (id == null) { + return ResultObject.failed("要送审的新闻ID不可为空!"); + } + boolean isSuccessed = setEditingFlag(id); + if (!isSuccessed) { + return ResultObject.failed("该资讯正在审核中,请勿重复操作!"); + } + log.info("path: /news/publish, method: POST, request user id: {}, news id: {}", adminUserId, id); + return newsService.revoke(id, adminUserId, adminUser.isReviewer()); + } catch (Exception e) { + log.error("解析登陆Token出错!", e); + return ResultObject.failed(500, "服务端错误,请联系系统管理员!"); + } finally { + unsetEditingFlag(publishNewsDTO.getId()); + } + } @PostMapping("/publish") + @OperationInfo(behavior = "发布", type = "news") public ResultObject publish(@RequestHeader("auth-token") String token, @RequestBody PublishNewsDTO publishNewsDTO) { if (!StringUtils.hasText(token)) { @@ -67,7 +156,10 @@ public class NewsController { if (id == null) { return ResultObject.failed("要发布/下架的新闻ID不可为空!"); } - + if (!adminUser.isReviewer()) { + return ResultObject.failed("当前操作非法,没有审核员权限!"); + } + log.info("path: /news/publish, method: POST, request user id: {}, news id: {}", adminUserId, id); return newsService.publish(id, adminUserId); } catch (Exception e) { @@ -111,6 +203,39 @@ public class NewsController { 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("当前用户已被封禁!请联系系统管理员!"); + } + boolean isSuccessed = setEditingFlag(id); + if (!isSuccessed) { + return ResultObject.failed("该资讯正在审核中,请勿重复操作!"); + } + log.info("path: /news, method: DELETE, request user id: {}, news id: {}", adminUserId, id); + return newsService.deleteNews(id, adminUserId); + } catch (Exception e) { + log.error("解析登陆Token出错!", e); + return ResultObject.failed(500, "服务端错误,请联系系统管理员!"); + } finally { + unsetEditingFlag(id); + } + } + + @PostMapping("/{id}/recover") + public ResultObject recoverNews(@RequestHeader("auth-token") String token, + @PathVariable( name = "id", required = true) Long id) { + if (!StringUtils.hasText(token)) { + return ResultObject.failed("登陆Token为空!"); + } + try { AdminUser adminUser = AdminJwtService.parseToken(token); if (adminUser == null) { @@ -125,7 +250,7 @@ public class NewsController { } log.info("path: /news, method: DELETE, request user id: {}, news id: {}", adminUserId, id); - return newsService.deleteNews(id); + return newsService.recoverNews(id, adminUserId); } catch (Exception e) { log.error("解析登陆Token出错!", e); return ResultObject.failed(500, "服务端错误,请联系系统管理员!"); @@ -151,14 +276,18 @@ public class NewsController { log.warn("当前用户已被封禁! id = {}", adminUserId); return ResultObject.failed("当前用户已被封禁!请联系系统管理员!"); } - log.info("path: /news/create/publish, method: POST, request user id: {}, param: {}", adminUserId, objectMapper.writeValueAsString(saveNewsDTO)); - - return newsService.createPublish(adminUserId, saveNewsDTO); + boolean isSuccessed = setEditingFlag(saveNewsDTO.getId()); + if (!isSuccessed) { + return ResultObject.failed("该资讯正在审核中,请勿重复操作!"); + } + return newsService.createPublish(adminUserId, saveNewsDTO, adminUser.isReviewer()); } catch (Exception e) { log.error("解析登陆Token出错!", e); return ResultObject.failed(500, "服务端错误,请联系系统管理员!"); + } finally { + unsetEditingFlag(saveNewsDTO.getId()); } } @@ -183,14 +312,12 @@ public class NewsController { log.info("path: /news/save, method: POST, request user id: {}, param: {}", adminUser.getId(), objectMapper.writeValueAsString(saveNewsDTO)); + + return newsService.saveDraft(saveNewsDTO, adminUser.isReviewer()); } catch (Exception e) { log.error("解析登陆Token出错!", e); return ResultObject.failed(500, "服务端错误,请联系系统管理员!"); } - - // 这个接口是保存,那status应该是1,未发布 - saveNewsDTO.setStatus(1); - return newsService.saveDraft(saveNewsDTO); } @GetMapping @@ -206,10 +333,12 @@ public class NewsController { @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 = "id$asc") String orderBy, + @RequestParam(value = "orderBy", required = false) String orderBy, @RequestParam(value = "mediaId", required = false) Long mediaId, @RequestParam(value = "dateline_from", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date datelineFrom, - @RequestParam(value = "dateline_to", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date datelineTo + @RequestParam(value = "dateline_to", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date datelineTo, + @RequestParam(value = "deleted", required = false) Integer deleted, + @RequestParam(value = "rating", required = false) Byte rating ) { if (!StringUtils.hasText(token)) { return PageObject.failedPage("登陆Token为空!"); @@ -230,11 +359,11 @@ public class NewsController { log.info("path: /news, method: GET, request user id: {}, keyword: {}, column: {}, status: {}, " + "page: {}, size: {}, last: {}, current: {}, orderBy: {}, tag: {}, industry: {}", adminUser.getId(), keyword, columnList, status, page, size, last, current, orderBy, tag, industry); + return newsService.queryNews(keyword, columnList, status, page, size, last, current, orderBy, minScore, maxScore, tag, industry, mediaId, datelineFrom, datelineTo, deleted, rating, adminUser.isReviewer()); } catch (Exception e) { log.error("解析登陆Token出错!", e); return PageObject.failedPage(500, "服务端错误,请联系系统管理员!"); } - return newsService.queryNews(keyword, columnList, status, page, size, last, current, orderBy, minScore, maxScore, tag, industry, mediaId, datelineFrom, datelineTo); } @GetMapping("/score") @@ -264,4 +393,97 @@ public class NewsController { return ResultObject.failed(500, "服务端错误,请联系系统管理员!"); } } + + @GetMapping("/{id}/translator") + public ResultObject getTranslator(@RequestHeader("auth-token") String token, + @PathVariable(name = "id", required = true) Long 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: /news/detail, method: GET, request user id: {}, news id: {}", adminUserId, id); + return newsService.getTranslator(id); + } catch (Exception e) { + log.error("解析登陆Tokn出错!", e); + return ResultObject.failed(500, "服务端错误,请联系系统管理员!"); + } + } + + @GetMapping("/log") + public PageObject getLog(@RequestHeader("auth-token") String token, + @RequestParam("id") Long id, + @RequestParam(value = "page", required = false, defaultValue = "1") int page, + @RequestParam(value = "size", required = false, defaultValue = "10") int size, + @RequestParam(value = "current", required = false) Integer current) { + 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有误,请联系系统管理员!"); + } + + Long adminUserId = adminUser.getId(); + if (!adminUser.isActive()) { + log.warn("当前用户已被封禁! id = {}", adminUserId); + return PageObject.failedPage("当前用户已被封禁!请联系系统管理员!"); + } + + log.info("path: /news/log, method: GET, request user id: {}, news id: {}, page: {}, size: {}, current: {}", adminUserId, id, page, size, current); + return newsService.getLog(id, page, size, current); + } catch (Exception e) { + log.error("解析登陆Tokn出错!", e); + return PageObject.failedPage(500, "服务端错误,请联系系统管理员!"); + } + } + + /** + * + * @param newsId + * @return true:表示不存在其他人在编辑; false表示已经有人在编辑或者操作 + */ + private boolean setEditingFlag(Long newsId) { + if (newsId == null) { + return true; + } + String newsIdStr = String.valueOf(newsId); + try(Jedis jedis = jedisPool.getResource()) { + Long result = jedis.setnx(newsIdStr, ""); + if (result == null ||result == 0L) { + return false; + } + jedis.expire(newsIdStr, 10*60L); + return true; + } catch(Exception e) { + return false; + } + } + + private void unsetEditingFlag(Long newsId) { + if (newsId == null) { + return; + } + String newsIdStr = String.valueOf(newsId); + try(Jedis jedis = jedisPool.getResource()) { + jedis.del(newsIdStr); + } catch(Exception e) { + } + } + } 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 index 585e0d0..5da8d54 100644 --- a/admin/src/main/java/com/jinrui/reference/admin/controller/NewsInfoController.java +++ b/admin/src/main/java/com/jinrui/reference/admin/controller/NewsInfoController.java @@ -1,7 +1,10 @@ package com.jinrui.reference.admin.controller; +import java.util.Date; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.format.annotation.DateTimeFormat; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.GetMapping; @@ -18,6 +21,7 @@ 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.news.NewsScoreVO; 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; @@ -119,7 +123,7 @@ public class NewsInfoController { } log.info("path: /newsinfo, method: DELETE, request user id: {}, news id: {}", adminUserId, id); - return newsInfoService.deleteNewsInfo(id); + return newsInfoService.deleteNewsInfo(id, adminUserId); } catch (Exception e) { log.error("解析登陆Token出错!", e); return ResultObject.failed(500, "服务端错误,请联系系统管理员!"); @@ -168,7 +172,11 @@ public class NewsInfoController { @RequestParam(value = "size", required = false, defaultValue = "10") int size, @RequestParam(value = "current", required = false) Integer current, @RequestParam(value = "orderBy", required = false, defaultValue = "inputDate") String orderBy, - @RequestParam(value = "direction", required = false, defaultValue = "asc") String direction + @RequestParam(value = "direction", required = false, defaultValue = "desc") String direction, + @RequestParam(value = "minScore", required = false) Double minScore, + @RequestParam(value = "maxScore", required = false) Double maxScore, + @RequestParam(value = "inputDateFrom", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date inputDateFrom, + @RequestParam(value = "inputDateTo", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date inputDateTo ) { if (!StringUtils.hasText(token)) { return PageObject.failedPage("登陆Token为空!"); @@ -193,8 +201,34 @@ public class NewsInfoController { log.error("解析登陆Token出错!", e); return PageObject.failedPage(500, "服务端错误,请联系系统管理员!"); } - return newsInfoService.queryNewsInfo(title, content, stockcode, sourcename, page, size, last, current, orderBy, direction); + return newsInfoService.queryNewsInfo(title, content, stockcode, sourcename, page, size, last, current, orderBy, direction, minScore, maxScore, inputDateFrom, inputDateTo); } + @GetMapping("/score") + public ResultObject getScore(@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: /news/score, method: GET, request user id: {}, news id: {}", adminUserId, id); + return newsInfoService.getScore(id); + } catch (Exception e) { + log.error("解析登陆Tokn出错!", e); + return ResultObject.failed(500, "服务端错误,请联系系统管理员!"); + } + } } diff --git a/admin/src/main/java/com/jinrui/reference/admin/job/NewsDeduplicationJob.java b/admin/src/main/java/com/jinrui/reference/admin/job/NewsDeduplicationJob.java new file mode 100644 index 0000000..a86fe71 --- /dev/null +++ b/admin/src/main/java/com/jinrui/reference/admin/job/NewsDeduplicationJob.java @@ -0,0 +1,39 @@ +package com.jinrui.reference.admin.job; + +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import com.jinrui.reference.core.mapper.NewsMapper; +import com.jinrui.reference.core.model.entity.News; +import com.jinrui.reference.core.service.NewsService; + + +@Component +public class NewsDeduplicationJob { + + @Autowired + private NewsService newsService; + @Autowired + private NewsMapper newsMapper; + + +// @Scheduled(fixedDelay=15, initialDelay=0, timeUnit = TimeUnit.MINUTES) + public void cleanDuplicatedData() { + List clusterIds = newsMapper.getDuplicatedCluster(); + for (String clusterId: clusterIds) { + List duplicatedNews = newsMapper.getDuplicatedNews(clusterId); + List toBeDeletedNews = duplicatedNews.stream().filter(e -> e.getDeleted() && e.getEditorId() == null).collect(Collectors.toList()); + if (duplicatedNews.size() > toBeDeletedNews.size()) { + newsService.backupDuplicatedNews(toBeDeletedNews, clusterId); + newsService.deletDuplicatedNews(toBeDeletedNews); + } + } + } + + +} diff --git a/admin/src/main/java/com/jinrui/reference/admin/job/NewsTagsIndustryLabelRepairJob.java b/admin/src/main/java/com/jinrui/reference/admin/job/NewsTagsIndustryLabelRepairJob.java new file mode 100644 index 0000000..f1ae941 --- /dev/null +++ b/admin/src/main/java/com/jinrui/reference/admin/job/NewsTagsIndustryLabelRepairJob.java @@ -0,0 +1,271 @@ +package com.jinrui.reference.admin.job; + +import java.io.IOException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.jinrui.reference.core.mapper.NewsTagsMapper; +import com.jinrui.reference.core.model.entity.NewsInfo; +import com.jinrui.reference.core.model.entity.NewsTags; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.elasticsearch._types.ElasticsearchException; +import co.elastic.clients.elasticsearch._types.InlineScript; +import co.elastic.clients.elasticsearch._types.Refresh; +import co.elastic.clients.elasticsearch._types.SortOptions; +import co.elastic.clients.elasticsearch._types.SortOrder; +import co.elastic.clients.elasticsearch._types.query_dsl.ExistsQuery; +import co.elastic.clients.elasticsearch._types.query_dsl.Query; +import co.elastic.clients.elasticsearch._types.query_dsl.ScriptQuery; +import co.elastic.clients.elasticsearch._types.query_dsl.WildcardQuery; +import co.elastic.clients.elasticsearch.core.SearchRequest; +import co.elastic.clients.elasticsearch.core.SearchResponse; +import co.elastic.clients.elasticsearch.core.search.Hit; +import co.elastic.clients.elasticsearch.core.search.HitsMetadata; + +@Component +public class NewsTagsIndustryLabelRepairJob { + private static final Logger log = LoggerFactory.getLogger(NewsTagsIndustryLabelRepairJob.class); + + @Autowired + private ElasticsearchClient elasticsearchClient; + @Autowired + private NewsTagsMapper newsTagsMapper; + +// @Scheduled(fixedDelay=15, initialDelay=0, timeUnit = TimeUnit.MINUTES) + public void startToRepair() { + List filters = new ArrayList<>(); + filters.add(ExistsQuery.of(e -> e.field("news_tags.industry_label"))._toQuery()); + filters.add(WildcardQuery.of(e -> e.field("news_tags.industry_label").value("*"))._toQuery()); + Query scriptQuery = ScriptQuery.of(e -> e.script(s -> s.inline(InlineScript.of(i -> i.lang("painless").source("doc['news_tags.industry_label.keyword'][0].indexOf('[') == 0")))))._toQuery(); + filters.add(scriptQuery); + + SortOptions sortOptions = SortOptions.of(e -> e.field(s -> s.field("create_time").order(SortOrder.Desc))); + + Query resultQuery = new Query.Builder().bool(b -> b.filter(filters)).build(); + + int pageSize = 1000; + SearchRequest request = SearchRequest.of(s -> + s.index(NewsInfo.INDEX_NAME).from(0) + .size(pageSize) // 分页参数 + .sort(sortOptions) // 排序字段 + .query(resultQuery)); + + SearchResponse searchResp; + try { + searchResp = elasticsearchClient.search(request, JsonNode.class); + HitsMetadata hits = searchResp.hits(); + if (!ObjectUtils.isEmpty(hits)) { + for (Hit hit: hits.hits()) { + String id = hit.id(); + NewsTags newsTags = newsTagsMapper.getNewsTagsByNewsId(id); + if (newsTags == null) { + JsonNode newsInfo = hit.source(); + JsonNode newsTagsNode = newsInfo.get("news_tags"); + newsTags = new NewsTags(); + String summary = newsTagsNode.get("abstract").asText(); + newsTags.setSummary(summary); + String title = newsTagsNode.get("title").asText(); + newsTags.setTitle(title); + String rewriteContent = newsTagsNode.get("rewrite_content").asText(); + newsTags.setRewriteContent(rewriteContent); + List industryLabel = getIndustryLabel(newsTagsNode.get("industry_label").asText()); + newsTags.setIndustryLabel(industryLabel); + List industryConfidence = doubleArrayNodeToList(newsTagsNode.get("industry_confidence")); + newsTags.setIndustryConfidence(industryConfidence); + List industryScore = doubleArrayNodeToList(newsTagsNode.get("industry_score")); + newsTags.setIndustryScore(industryScore); + List conceptLabel = textArrayNodeToList(newsTagsNode.get("concept_label")); + newsTags.setConceptLabel(conceptLabel); + List conceptConfidence = doubleArrayNodeToList(newsTagsNode.get("concept_confidence")); + newsTags.setConceptConfidence(conceptConfidence); + List conceptScore = doubleArrayNodeToList(newsTagsNode.get("concept_score")); + newsTags.setConceptScore(conceptScore); + Integer publicOpinionScore = null; + if (newsTagsNode.hasNonNull("public_opinion_score")) { + publicOpinionScore = newsTagsNode.get("public_opinion_score").asInt(); + } + newsTags.setPublicOpinionScore(publicOpinionScore); + Double chinaFactor = null; + if (newsTagsNode.hasNonNull("China_factor")) { + chinaFactor = newsTagsNode.get("China_factor").asDouble(); + } + newsTags.setChinaFactor(chinaFactor); + String source = newsTagsNode.get("source").asText(); + newsTags.setSource(source); + Integer sourceImpact = null; + if (newsTagsNode.hasNonNull("source_impact")) { + sourceImpact = newsTagsNode.get("source_impact").asInt(); + } + newsTags.setSourceImpact(sourceImpact); + Double newsScore = null; + if (newsTagsNode.hasNonNull("news_score")) { + newsScore = newsTagsNode.get("news_score").asDouble(); + } + newsTags.setNewsScore(newsScore); + Long newsId = null; + if (newsTagsNode.hasNonNull("news_id")) { + newsId = newsTagsNode.get("news_id").asLong(); + } + newsTags.setNewsId(newsId); + Integer deleted = newsTagsNode.get("deleted").asInt(); + newsTags.setDeleted(deleted); + Date createTime = getDateNodeValue(newsTagsNode.get("create_time").asText()); + newsTags.setCreateTime(createTime); + Date updateTime = getDateNodeValue(newsTagsNode.get("update_time").asText()); + newsTags.setUpdateTime(updateTime); + newsTagsMapper.save(newsTags); + + NewsInfo newsInfo2 = new NewsInfo(); + newsInfo2.setNewsTags(new NewsTags()); + newsInfo2.getNewsTags().setIndustryLabel(industryLabel); + + elasticsearchClient.update(e -> e.index(NewsInfo.INDEX_NAME).refresh(Refresh.True).id(id).doc(newsInfo2), NewsInfo.class); + } + } + } + } catch (ElasticsearchException|IOException e1) { + log.error("修复行业分类编码问题失败: ", e1); + } + } + + private Date getDateNodeValue(String dateStr) { + if (ObjectUtils.isEmpty(dateStr)) { + return null; + } + String pattern = "yyyy-MM-dd'T'HH:mm:ss"; + SimpleDateFormat sdf = new SimpleDateFormat(pattern); + try { + return sdf.parse(dateStr); + } catch (ParseException e) { + e.printStackTrace(); + } + return new Date(); + } + + private List doubleArrayNodeToList(JsonNode node) { + if (node.isArray()) { + int size = ((ArrayNode)node).size(); + if (size == 0) { + return Collections.emptyList(); + } + List nodeValues = new ArrayList<>(); + Iterator iterator = ((ArrayNode)node).elements(); + while (iterator.hasNext()) { + nodeValues.add(iterator.next().asDouble()); + } + return nodeValues; + } + double nodeValue = node.asDouble(); + if (nodeValue > 0.0d) { + return Arrays.asList(nodeValue); + } + return Collections.emptyList(); + } + + private List textArrayNodeToList(JsonNode node) { + if (node.isArray()) { + int size = ((ArrayNode)node).size(); + if (size == 0) { + return Collections.emptyList(); + } + List nodeValues = new ArrayList<>(); + Iterator iterator = ((ArrayNode)node).elements(); + while (iterator.hasNext()) { + nodeValues.add(iterator.next().asText()); + } + return nodeValues; + } + String nodeValue = node.asText(); + if (!ObjectUtils.isEmpty(nodeValue)) { + return Arrays.asList(nodeValue); + } + return Collections.emptyList(); + } + + private List getIndustryLabel(String unicodeIndustryLabel) { + if (ObjectUtils.isEmpty(unicodeIndustryLabel)) { + return Collections.emptyList(); + } + unicodeIndustryLabel = unicodeIndustryLabel.replace("[", "").replace("]", "").replace("\"", ""); + String industryLabel = unicodeDecode(unicodeIndustryLabel); + if (ObjectUtils.isEmpty(industryLabel)) { + return Collections.emptyList(); + } + return Stream.of(industryLabel.split(",")).map(e -> e.trim()).collect(Collectors.toList()); + } + + private String unicodeDecode(String unicodeIndustryLabel) { + if (!StringUtils.hasLength(unicodeIndustryLabel)) { + return null; + } + Pattern pattern = Pattern.compile("(\\\\u(\\p{XDigit}{4}))"); + Matcher matcher = pattern.matcher(unicodeIndustryLabel); + char ch; + while (matcher.find()) { + ch = (char) Integer.parseInt(matcher.group(2), 16); + unicodeIndustryLabel = unicodeIndustryLabel.replace(matcher.group(1), ch + ""); + } + return unicodeIndustryLabel; + } + + +// GET /news_info/_search +// { +// "size": 100, +// "query": { +// "bool": { +// "filter": [ +// { +// "exists": { +// "field": "news_tags.industry_label" +// } +// }, +// { +// "script": { +// "script": { +// "lang": "painless", +// "source": "doc['news_tags.industry_label.keyword'][0].indexOf('[') == 0" +// } +// } +// }, +// { +// "wildcard": { +// "news_tags.industry_label": "*" +// } +// } +// ] +// } +// }, +// "sort": [ +// { +// "create_time": { +// "order": "desc" +// } +// } +// ], +// "track_total_hits": true +// } + +} diff --git a/admin/src/main/java/com/jinrui/reference/admin/mapper/AdminUserMapper.java b/admin/src/main/java/com/jinrui/reference/admin/mapper/AdminUserMapper.java index 6c6ffca..b129625 100644 --- a/admin/src/main/java/com/jinrui/reference/admin/mapper/AdminUserMapper.java +++ b/admin/src/main/java/com/jinrui/reference/admin/mapper/AdminUserMapper.java @@ -20,6 +20,7 @@ public interface AdminUserMapper { @Result(column = "phone", property = "phone"), @Result(column = "name", property = "name"), @Result(column = "active", property = "active"), + @Result(column = "user_type", property = "userType"), @Result(column = "create_time", property = "createTime", javaType = Date.class, jdbcType = JdbcType.TIMESTAMP), @Result(column = "update_time", property = "updateTime", javaType = Date.class, jdbcType = JdbcType.TIMESTAMP) }) @@ -31,6 +32,7 @@ public interface AdminUserMapper { @Result(column = "phone", property = "phone"), @Result(column = "name", property = "name"), @Result(column = "active", property = "active"), + @Result(column = "user_type", property = "userType"), @Result(column = "create_time", property = "createTime", javaType = Date.class, jdbcType = JdbcType.TIMESTAMP), @Result(column = "update_time", property = "updateTime", javaType = Date.class, jdbcType = JdbcType.TIMESTAMP) }) diff --git a/admin/src/main/java/com/jinrui/reference/admin/model/entity/AdminUser.java b/admin/src/main/java/com/jinrui/reference/admin/model/entity/AdminUser.java index f42eeb0..e9bb4bc 100644 --- a/admin/src/main/java/com/jinrui/reference/admin/model/entity/AdminUser.java +++ b/admin/src/main/java/com/jinrui/reference/admin/model/entity/AdminUser.java @@ -5,6 +5,7 @@ import com.auth0.jwt.interfaces.DecodedJWT; import java.util.Date; import java.util.Map; +import java.util.Objects; /** * 管理后台用户 @@ -32,6 +33,11 @@ public class AdminUser { * true - 启动 | false - 禁用 */ private boolean active; + + /** + * 用户类型 + */ + private String userType; /** * 用户创建时间 @@ -62,6 +68,10 @@ public class AdminUser { this.name = value.asString(); break; } + case "userType": { + this.userType = value.asString(); + break; + } case "createTime": { this.createTime = new Date(value.asLong()); break; @@ -122,4 +132,16 @@ public class AdminUser { public void setUpdateTime(Date updateTime) { this.updateTime = updateTime; } + + public void setUserType(String userType) { + this.userType = userType; + } + + public String getUserType() { + return userType; + } + + public boolean isReviewer() { + return Objects.equals(userType, "01"); + } } diff --git a/admin/src/main/java/com/jinrui/reference/admin/model/vo/login/LoginVO.java b/admin/src/main/java/com/jinrui/reference/admin/model/vo/login/LoginVO.java index 2d30497..4cc96a1 100644 --- a/admin/src/main/java/com/jinrui/reference/admin/model/vo/login/LoginVO.java +++ b/admin/src/main/java/com/jinrui/reference/admin/model/vo/login/LoginVO.java @@ -31,6 +31,11 @@ public class LoginVO { * 用户登陆Token */ private String token; + + /** + * 用户类型 + */ + private String userType; /** * 用户创建时间 @@ -49,6 +54,7 @@ public class LoginVO { this.phone = adminUser.getPhone(); this.name = adminUser.getName(); this.token = AdminJwtService.generateToken(adminUser); + this.userType = adminUser.getUserType(); this.createTime = adminUser.getCreateTime(); this.updateTime = adminUser.getUpdateTime(); } @@ -100,4 +106,12 @@ public class LoginVO { public void setUpdateTime(Date updateTime) { this.updateTime = updateTime; } + + public String getUserType() { + return userType; + } + + public void setUserType(String userType) { + this.userType = userType; + } } diff --git a/admin/src/main/java/com/jinrui/reference/admin/service/AdminJwtService.java b/admin/src/main/java/com/jinrui/reference/admin/service/AdminJwtService.java index 191cea3..f119122 100644 --- a/admin/src/main/java/com/jinrui/reference/admin/service/AdminJwtService.java +++ b/admin/src/main/java/com/jinrui/reference/admin/service/AdminJwtService.java @@ -27,12 +27,14 @@ public final class AdminJwtService { Long id = adminUser.getId(); String phone = adminUser.getPhone(); String name = adminUser.getName(); + String userType = adminUser.getUserType(); long createTime = adminUser.getCreateTime().getTime(); long updateTime = adminUser.getUpdateTime().getTime(); JWTCreator.Builder jwtBuilder = JWT.create(); jwtBuilder.withClaim("id", id); jwtBuilder.withClaim("phone", phone); jwtBuilder.withClaim("name", name); + jwtBuilder.withClaim("userType", userType); jwtBuilder.withClaim("createTime", createTime); jwtBuilder.withClaim("updateTime", updateTime); jwtBuilder.withClaim("timestamp", System.currentTimeMillis()); @@ -47,6 +49,7 @@ public final class AdminJwtService { .withClaimPresence("id") .withClaimPresence("phone") .withClaimPresence("name") + .withClaimPresence("userType") .withClaimPresence("createTime") .withClaimPresence("updateTime") .withClaimPresence("timestamp") diff --git a/admin/src/main/java/com/jinrui/reference/admin/service/AdminUserService.java b/admin/src/main/java/com/jinrui/reference/admin/service/AdminUserService.java index 06ea165..b4cd58e 100644 --- a/admin/src/main/java/com/jinrui/reference/admin/service/AdminUserService.java +++ b/admin/src/main/java/com/jinrui/reference/admin/service/AdminUserService.java @@ -1,5 +1,15 @@ package com.jinrui.reference.admin.service; +import java.util.List; +import java.util.Random; +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.StringUtils; + import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.jinrui.reference.admin.mapper.AdminUserMapper; @@ -9,19 +19,10 @@ import com.jinrui.reference.admin.model.vo.admin.user.AdminUserVO; import com.jinrui.reference.admin.model.vo.login.LoginVO; import com.jinrui.reference.core.model.vo.PageObject; import com.jinrui.reference.core.model.vo.ResultObject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Service; -import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; + import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; -import java.util.List; -import java.util.Objects; -import java.util.Random; -import java.util.stream.Collectors; - @Service public class AdminUserService { diff --git a/admin/src/main/java/com/jinrui/reference/admin/service/UserOperationLogService.java b/admin/src/main/java/com/jinrui/reference/admin/service/UserOperationLogService.java new file mode 100644 index 0000000..d313012 --- /dev/null +++ b/admin/src/main/java/com/jinrui/reference/admin/service/UserOperationLogService.java @@ -0,0 +1,21 @@ +package com.jinrui.reference.admin.service; + +import org.springframework.stereotype.Service; + +import com.jinrui.reference.admin.model.entity.AdminUser; +import com.jinrui.reference.core.mapper.UserOperationLogMapper; + +@Service +public class UserOperationLogService { + + private final UserOperationLogMapper userOperationLogMapper; + + public UserOperationLogService(UserOperationLogMapper userOperationLogMapper) { + this.userOperationLogMapper = userOperationLogMapper; + } + + public void logUserOperation(String type, String behavior, String token, Long dataId) { + AdminUser adminUser = AdminJwtService.parseToken(token); + userOperationLogMapper.save(adminUser.getId(), adminUser.getName(), adminUser.getUserType(), dataId, type, behavior); + } +} diff --git a/admin/src/main/resources/.gitignore b/admin/src/main/resources/.gitignore new file mode 100644 index 0000000..ec8bdfe --- /dev/null +++ b/admin/src/main/resources/.gitignore @@ -0,0 +1 @@ +/application-prod.yml diff --git a/admin/src/main/resources/application-dev.yml b/admin/src/main/resources/application-dev.yml index 8b6d10a..abdb533 100644 --- a/admin/src/main/resources/application-dev.yml +++ b/admin/src/main/resources/application-dev.yml @@ -1,13 +1,50 @@ logging: level: root: DEBUG +server: + port: 13579 + compression: + enabled: true + servlet: + context-path: /admin +oss: + ak: LTAI5t8z9QNdCG6b54mDgx8p + sk: F3M41hTgH8g99ZgVWyelj42825YbZM + roleArn: acs:ram::1647420045565932:role/ramoss + endPoint: oss-cn-hangzhou.aliyuncs.com + region: oss-cn-hangzhou + bucketName: lengfeng-test + baseUrl: https://lengfeng-test.oss-cn-hangzhou.aliyuncs.com + sts: + endPoint: sts.cn-hangzhou.aliyuncs.com + spring: datasource: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://127.0.0.1:3306/reference?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2b8&allowMultiQueries=true +# url: jdbc:mysql://192.168.0.142:3306/reference?autoReconnect=true&useUnicode=true&useSSL=false&allowMultiQueries=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai +# username: financial_prod +# password: mmTFncqmDal5HLRGY0BV + url: jdbc:mysql://121.37.185.246:3306/reference?autoReconnect=true&useUnicode=true&useSSL=false&allowMultiQueries=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root - password: Aa123456@ + password: Xgf_8000 redis: - host: 127.0.0.1 +# host: 192.168.0.172 +# port: 6379 +# password: Xgf_redis + host: 123.60.153.169 port: 6379 + password: Xgf_redis +elasticsearch: + scheme: http + # 111.13.176.3 部署地址 + # 10.127.2.194 本地测试地址 + host: 111.13.176.3 + port: 9200 + enable: true + username: elastic + password: ZxE,3VM@Thk0 +mybatis: + type-handlers-package:com.jinrui.reference.core.typehandler +api: + key: WBysu6N1z26AbA12l \ No newline at end of file diff --git a/admin/src/main/resources/application.yml b/admin/src/main/resources/application.yml index c88a076..abdb533 100644 --- a/admin/src/main/resources/application.yml +++ b/admin/src/main/resources/application.yml @@ -1,3 +1,6 @@ +logging: + level: + root: DEBUG server: port: 13579 compression: @@ -36,7 +39,7 @@ elasticsearch: scheme: http # 111.13.176.3 部署地址 # 10.127.2.194 本地测试地址 - host: 10.127.2.194 + host: 111.13.176.3 port: 9200 enable: true username: elastic diff --git a/admin/src/main/resources/sql/20250716.sql b/admin/src/main/resources/sql/20250716.sql new file mode 100644 index 0000000..6dfcc1b --- /dev/null +++ b/admin/src/main/resources/sql/20250716.sql @@ -0,0 +1,5 @@ +ALTER TABLE `reference`.`news` +ADD COLUMN `revision` TEXT NULL AFTER `rating`; + +ALTER TABLE `reference`.`admin_user` +ADD COLUMN `user_type` CHAR(2) NULL DEFAULT '00' AFTER `active`; \ No newline at end of file diff --git a/core/pom.xml b/core/pom.xml index 43e2235..f155b1f 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -39,6 +39,10 @@ org.springframework.boot spring-boot-starter-web + + org.springframework.boot + spring-boot-starter-aop + com.mysql mysql-connector-j @@ -86,6 +90,11 @@ jakarta.json-api ${jakartajson.version} + + redis.clients + jedis + ${jedis.version} + diff --git a/core/src/main/java/com/jinrui/reference/core/RedisConfig.java b/core/src/main/java/com/jinrui/reference/core/RedisConfig.java new file mode 100644 index 0000000..ebd470a --- /dev/null +++ b/core/src/main/java/com/jinrui/reference/core/RedisConfig.java @@ -0,0 +1,34 @@ +package com.jinrui.reference.core; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.StringUtils; + +import redis.clients.jedis.JedisPool; +import redis.clients.jedis.JedisPoolConfig; + + +@Configuration +public class RedisConfig { + @Value("${redis.host}") + private String host; + + @Value("${redis.port}") + private int port; + + @Value("${redis.timeout:1000}") + private int timeout; + + @Value("${redis.password:}") + private String password; + + @Bean + public JedisPool jedisPool() { + if (StringUtils.hasText(password)) { + JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); + return new JedisPool(jedisPoolConfig, host, port, timeout, password); + } + return new JedisPool(host, port); + } +} diff --git a/core/src/main/java/com/jinrui/reference/core/WebConfig.java b/core/src/main/java/com/jinrui/reference/core/WebConfig.java index 711dca8..4179861 100644 --- a/core/src/main/java/com/jinrui/reference/core/WebConfig.java +++ b/core/src/main/java/com/jinrui/reference/core/WebConfig.java @@ -1,9 +1,15 @@ package com.jinrui.reference.core; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import com.jinrui.reference.core.interceptor.ApiAuthInterceptor; + + + @Configuration public class WebConfig implements WebMvcConfigurer { @@ -18,4 +24,15 @@ public class WebConfig implements WebMvcConfigurer { .allowedMethods("GET", "POST", "DELETE", "PUT", "PATCH") .maxAge(3600); } + + @Bean + public ApiAuthInterceptor apiAuthInterceptor() { + return new ApiAuthInterceptor(); + } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(apiAuthInterceptor()) + .addPathPatterns("/api/**").excludePathPatterns("/api/key"); // 拦截API路径 + } } diff --git a/core/src/main/java/com/jinrui/reference/core/api/ApiKeyGenerator.java b/core/src/main/java/com/jinrui/reference/core/api/ApiKeyGenerator.java new file mode 100644 index 0000000..2ea07c0 --- /dev/null +++ b/core/src/main/java/com/jinrui/reference/core/api/ApiKeyGenerator.java @@ -0,0 +1,18 @@ +package com.jinrui.reference.core.api; + +import java.security.SecureRandom; +import java.util.Base64; +import java.util.UUID; + +public class ApiKeyGenerator { + public static String generateAK() { + return UUID.randomUUID().toString().replace("-", ""); + } + + public static String generateSK() { + SecureRandom random = new SecureRandom(); + byte[] bytes = new byte[32]; // 256位密钥 + random.nextBytes(bytes); + return Base64.getEncoder().encodeToString(bytes); + } +} diff --git a/core/src/main/java/com/jinrui/reference/core/api/ApiReturnCode.java b/core/src/main/java/com/jinrui/reference/core/api/ApiReturnCode.java new file mode 100644 index 0000000..5deab93 --- /dev/null +++ b/core/src/main/java/com/jinrui/reference/core/api/ApiReturnCode.java @@ -0,0 +1,27 @@ +package com.jinrui.reference.core.api; + +import com.fasterxml.jackson.annotation.JsonFormat; + +@JsonFormat(shape = JsonFormat.Shape.OBJECT) +public enum ApiReturnCode { + ILLEGAL_HEADER("10001", "缺失API相关的请求头参数!"), + REPLAY_ERROR("10002", "请求超时或重复请求!"), + KEY_ERROR("10003", "请求的KEY非法!"), + ARGUMENT_ERROR("10004", "参数签名错误!"); + + private final String code; + private final String msg; + + private ApiReturnCode(String code, String msg) { + this.code = code; + this.msg = msg; + } + + public String getCode() { + return code; + } + + public String getMsg() { + return msg; + } +} diff --git a/core/src/main/java/com/jinrui/reference/core/interceptor/ApiAuthInterceptor.java b/core/src/main/java/com/jinrui/reference/core/interceptor/ApiAuthInterceptor.java new file mode 100644 index 0000000..92e572b --- /dev/null +++ b/core/src/main/java/com/jinrui/reference/core/interceptor/ApiAuthInterceptor.java @@ -0,0 +1,123 @@ +package com.jinrui.reference.core.interceptor; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.Map; +import java.util.stream.Collectors; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.codec.binary.Hex; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; +import org.springframework.web.servlet.HandlerInterceptor; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.jinrui.reference.core.api.ApiReturnCode; +import com.jinrui.reference.core.service.ApiKeyService; + +import redis.clients.jedis.Jedis; +import redis.clients.jedis.JedisPool; +import redis.clients.jedis.params.SetParams; + +public class ApiAuthInterceptor implements HandlerInterceptor { + public static final Logger LOGGER = LoggerFactory.getLogger(ApiAuthInterceptor.class); + public static final Long REQUEST_TIMEOUT = 300L; + + @Autowired + private ApiKeyService apiKeyService; // 从数据库查询SK + @Autowired + private JedisPool jedisPool; + @Autowired + private ObjectMapper objectMapper; + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws InvalidKeyException, NoSuchAlgorithmException { + String ak = request.getHeader("X-Api-Key"); + String timestamp = request.getHeader("X-Timestamp"); + String nonce = request.getHeader("X-Nonce"); + String clientSign = request.getHeader("X-Signature"); + + // 1. 校验基础参数 + if (!StringUtils.hasLength(ak) || !StringUtils.hasLength(timestamp) + || !StringUtils.hasLength(nonce) || !StringUtils.hasLength(clientSign)) { + responseToFailed(response, ApiReturnCode.ILLEGAL_HEADER); + return false; + } + + // 2. 防重放:时间戳有效期5分钟 + long currentTime = System.currentTimeMillis(); + if (Math.abs(currentTime - Long.parseLong(timestamp)) > REQUEST_TIMEOUT * 1000) { + responseToFailed(response, ApiReturnCode.REPLAY_ERROR); + return false; + } + // 3. 校验nonce唯一性(Redis实现) + try(Jedis jedis = jedisPool.getResource()) { + if (jedis.exists(nonce)) { + responseToFailed(response, ApiReturnCode.REPLAY_ERROR); + return false; + } + jedis.set(nonce, "", SetParams.setParams().ex(REQUEST_TIMEOUT)); + } catch(Exception e) { + LOGGER.error("获取redis对象失败", e); + } + + // 4. 获取SK并计算服务端签名 + String sk = apiKeyService.getSecretKeyByAk(ak); + if (ObjectUtils.isEmpty(sk)) { + responseToFailed(response, ApiReturnCode.KEY_ERROR); + return false; + } + String serverSign = calculateSignature(request, sk, timestamp, nonce); + + // 5. 比对签名 + if (!serverSign.equals(clientSign)) { + responseToFailed(response, ApiReturnCode.ARGUMENT_ERROR); + return false; + } + return true; + } + + private String calculateSignature(HttpServletRequest request, String sk, String timestamp, String nonce) throws NoSuchAlgorithmException, InvalidKeyException { + // 拼接请求数据:方法+路径+排序后的参数 + String method = request.getMethod(); + String path = request.getRequestURI(); + + String params = getSortedParams(request); + String data = method + path + params + timestamp + nonce; + + // HMAC-SHA256签名 + Mac sha256 = Mac.getInstance("HmacSHA256"); + sha256.init(new SecretKeySpec(sk.getBytes(), "HmacSHA256")); + byte[] signBytes = sha256.doFinal(data.getBytes(StandardCharsets.UTF_8)); + return Hex.encodeHexString(signBytes); + } + + private String getSortedParams(HttpServletRequest request) { + // 获取请求参数(目前只考虑query string类型的参数,暂不支持application/json格式参数 + Map parameterMap = request.getParameterMap(); + String sortedQueryString = parameterMap.entrySet().stream().filter(e -> !ObjectUtils.isEmpty(e.getValue())).sorted(Map.Entry.comparingByKey()) + .map(e -> e.getKey() + "=" + String.join(",", e.getValue())).collect(Collectors.joining("&")); + return sortedQueryString; + } + + private void responseToFailed(HttpServletResponse response, ApiReturnCode apiReturnCode) { + try { + response.setStatus(200); + response.setContentType("application/json"); + response.setCharacterEncoding("utf-8"); + response.getWriter().print(objectMapper.writeValueAsString(apiReturnCode)); + } + catch (IOException e) { + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/core/src/main/java/com/jinrui/reference/core/mapper/ApiUserMapper.java b/core/src/main/java/com/jinrui/reference/core/mapper/ApiUserMapper.java new file mode 100644 index 0000000..285ce58 --- /dev/null +++ b/core/src/main/java/com/jinrui/reference/core/mapper/ApiUserMapper.java @@ -0,0 +1,19 @@ +package com.jinrui.reference.core.mapper; + +import java.util.Map; + +import org.apache.ibatis.annotations.Insert; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +public interface ApiUserMapper { + @Select("select secret_key from api_user where access_key = #{ak} and disabled = 0") + String getClientSecretkey(@Param("ak") String ak); + + @Select("select access_key, secret_key from api_user where client_name = #{clientName} and disabled = 0") + Map getClientKey(@Param("clientName") String clientName); + + @Insert("insert into api_user(client_name, access_key, secret_key)" + + "values (#{clientName}, #{accessKey}, #{secretKey})") + void save(@Param("clientName") String clientName, @Param("accessKey") String accessKey, @Param("secretKey") String secretKey); +} diff --git a/core/src/main/java/com/jinrui/reference/core/mapper/NewsDeletedMapper.java b/core/src/main/java/com/jinrui/reference/core/mapper/NewsDeletedMapper.java new file mode 100644 index 0000000..f2d6d49 --- /dev/null +++ b/core/src/main/java/com/jinrui/reference/core/mapper/NewsDeletedMapper.java @@ -0,0 +1,12 @@ +package com.jinrui.reference.core.mapper; + +import org.apache.ibatis.annotations.Insert; + +import com.jinrui.reference.core.model.entity.NewsDeleted; + +public interface NewsDeletedMapper { + @Insert("insert into news_deleted(news_id, newsinfo_id, cluster_id, deleted_time)" + + "values (#{newsId}, #{newsinfoId}, #{clusterId}, now())") + void save(NewsDeleted newsDeleted); + +} 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 f8d31a3..9b22f58 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 @@ -7,16 +7,23 @@ 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.Result; +import org.apache.ibatis.annotations.Results; import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Update; import com.jinrui.reference.core.model.entity.News; import com.jinrui.reference.core.model.entity.NewsDraft; +import com.jinrui.reference.core.model.vo.news.NewsApiVO; +import com.jinrui.reference.core.typehandler.JsonArrayTypeHandler; public interface NewsMapper { - @Delete("delete from news where id = #{newsId}") - void deleteNews(@Param("newsId") Long newsId); + @Delete("update news set is_delete = 1, update_time = now(), editor_id = #{editorId} where id = #{newsId} and status = 1") + int deleteNews(@Param("newsId") Long newsId, @Param("editorId") Long editorId); + + @Delete("update news set is_delete = 0, update_time = now(), editor_id = #{editorId} where id = #{newsId}") + void recoverNews(@Param("newsId") Long newsId, @Param("editorId") Long editorId); @Delete("delete from news_draft where id = #{draftId}") void deleteDraft(@Param("draftId") Long draftId); @@ -27,75 +34,60 @@ public interface NewsMapper { "from news_draft where id = #{id}") NewsDraft getDraftDetail(@Param("id") Long id); - @Select("select id, title, summary, picture, content, " + + @Select("select id, llm_title as title, summary, picture, llm_content as content, " + "create_time as createTime, " + - "update_time as updateTime " + + "update_time as updateTime, " + + "is_delete as deleted, " + + "rating, " + + "revision, " + + "newsinfo_id as newsinfoId " + "from news where id = #{id}") News getNewsDetail(@Param("id") Long id); - @Update("update news set status = 1, update_time = now() where id = #{id}") - void simpleUnpublish(@Param("id") long id); - - @Update("update news set status = 2, update_time = now(), editor_id = #{editorId} where id = #{id}") - void simplePublish(@Param("id") long id, @Param("editorId") long editorId); + @Update("update news set status = #{newStatus}, update_time = now(), editor_id = #{editorId} where id = #{id} and status = #{oldStatus}") + int changeFrom(@Param("id") long id, @Param("oldStatus") int oldStatus, @Param("newStatus") int newStatus, @Param("editorId") long editorId); @Update("update news " + - "set draft_id = #{draftId}," + - "editor_id = #{editorId}," + - "title = #{title}," + - "summary = #{summary}," + - "picture = #{picture}," + - "content = #{content}," + - "content_text = #{contentText}," + - "status = #{status}," + - "newsinfo_id = #{newsinfoId}," + - "update_time = now() " + - "where id = #{id}") - void updateNews(News news); - - @Update("update news " + - "set editor_id = #{editorId}," + - "draft_id = NULL, " + - "title = #{title}," + - "summary = #{summary}," + - "picture = #{picture}," + - "content = #{content}," + - "content_text = #{contentText}," + - "status = #{status}," + + "set editor_id = #{news.editorId}," + + "draft_id = #{news.draftId}, " + + "llm_title = #{news.title}," + + "summary = #{news.summary}," + + "picture = #{news.picture}," + + "llm_content = #{news.content}," + + "content_text = #{news.contentText}," + + "status = #{news.status}," + + "newsinfo_id = #{news.newsinfoId}," + + "rating = #{news.rating}," + + "revision = #{news.revision}," + "update_time = now()" + - "where id = #{id}") - void publishNews(News news); + "where id = #{news.id} and status = #{oldStatus}") + int updateNews(News news, Integer oldStatus); - @Select("select id, draft_id as draftId, status, newsinfo_id as newsinfoId from news where id = #{id}") + @Select("select id, draft_id as draftId, status, newsinfo_id as newsinfoId, is_delete as deleted, rating, revision from news where id = #{id}") News getById(@Param("id") Long id); - @Select("select last_insert_id()") - Long getLastInsertId(); - @Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id") @Insert("insert into news_draft(title, summary, picture, type, content, create_time, update_time)" + "values (#{title}, #{summary}, #{picture}, #{type}, #{content}, now(), now())") void saveDraft(NewsDraft newsDraft); @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})") + @Insert("insert into news(draft_id, title, summary, picture, type, content, create_time, update_time, status, publish_time, content_text, newsinfo_id, llm_title, llm_content, rating)" + + "values (#{draftId}, #{title}, #{summary}, #{picture}, #{type}, #{content}, now(), now(), #{status}, #{publishTime}, #{contentText}, #{newsinfoId}, #{llmTitle}, #{llmContent}, #{rating})") void saveNews(News news); - - @Select("SELECT MAX(id) AS max_id FROM news") - Long getNewsLastInsertId(); - @Select("") int queryTotal(@Param("keywords") List keywords, @@ -223,12 +236,74 @@ public interface NewsMapper { @Param("tags") List tags, @Param("industries") List industries, @Param("datelineFrom") Date datelineFrom, - @Param("datelineTo") Date datelineTo); + @Param("datelineTo") Date datelineTo, + @Param("deleted") Integer deleted, + @Param("rating") Byte rating, + @Param("isReviewer") boolean isReviewer); - @Select("select id, title, summary, picture, content, status, " + + @Select("select id, llm_title as title, summary, picture, llm_content as content, status, " + "create_time as createTime, " + "publish_time as publishTime, " + "update_time as updateTime " + "from news where newsinfo_id = #{newsinfoId}") News getNewsDetailByNewsInfoId(@Param("newsinfoId") String newsinfoId); + + @Select("SELECT cluster_id FROM news WHERE newsinfo_id IS NOT NULL AND cluster_id IS NOT NULL GROUP BY cluster_id HAVING COUNT(*) > 1") + List getDuplicatedCluster(); + + @Select("select id, draft_id as draftId, newsinfo_id as newsinfoId, is_delete as deleted, editor_id as editorId from news where cluster_id = #{clusterId} and newsinfo_id IS NOT NULL") + List getDuplicatedNews(@Param("clusterId") String clusterId); + + @Delete("delete from news where id = #{id}") + void deleteById(@Param("id") Long id); + + + @Results({ + @Result(column = "id", property = "id", id = true), + @Result(column = "llm_title", property = "title"), + @Result(column = "llm_content", property = "content"), + @Result(column = "summary", property = "summary"), + @Result(column = "publish_time", property = "publishTime"), + @Result(column = "industry_label", property = "industryLabel", typeHandler = JsonArrayTypeHandler.class), + @Result(column = "industry_confidence", property = "industryConfidence", typeHandler = JsonArrayTypeHandler.class), + @Result(column = "industry_score", property = "industryScore", typeHandler = JsonArrayTypeHandler.class), + @Result(column = "concept_label", property = "conceptLabel", typeHandler = JsonArrayTypeHandler.class), + @Result(column = "concept_confidence", property = "conceptConfidence", typeHandler = JsonArrayTypeHandler.class), + @Result(column = "concept_score", property = "conceptScore", typeHandler = JsonArrayTypeHandler.class), + @Result(column = "source", property = "source"), + @Result(column = "source_impact", property = "sourceImpact"), + @Result(column = "China_factor", property = "chinaFactor"), + @Result(column = "public_opinion_score", property = "publicOpinionScore"), + @Result(column = "news_score", property = "newsScore"), + }) + @Select("") + List queryNewsByApi(@Param("last") Long last, @Param("limit") int limit); } diff --git a/core/src/main/java/com/jinrui/reference/core/mapper/NewsTagsMapper.java b/core/src/main/java/com/jinrui/reference/core/mapper/NewsTagsMapper.java index c213516..8e09724 100644 --- a/core/src/main/java/com/jinrui/reference/core/mapper/NewsTagsMapper.java +++ b/core/src/main/java/com/jinrui/reference/core/mapper/NewsTagsMapper.java @@ -1,10 +1,12 @@ package com.jinrui.reference.core.mapper; +import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Result; import org.apache.ibatis.annotations.Results; import org.apache.ibatis.annotations.Select; +import com.jinrui.reference.core.model.entity.News; import com.jinrui.reference.core.model.entity.NewsTags; import com.jinrui.reference.core.typehandler.JsonArrayTypeHandler; @@ -32,4 +34,8 @@ public interface NewsTagsMapper { }) @Select("select * from news_tags where newsinfo_id = #{newsId}") NewsTags getNewsTagsByNewsId(@Param("newsId") String newsId); + + @Insert("insert into news_tags(abstract, title, rewrite_content, industry_label, industry_confidence, industry_score, concept_label, concept_confidence, concept_score, source, source_impact, China_factor, public_opinion_score, news_score, news_id, deleted, create_time, update_time)" + + "values (#{summary}, #{title}, #{rewriteContent}, #{industryLabel, jdbcType=VARCHAR,typeHandler=com.jinrui.reference.core.typehandler.JsonArrayTypeHandler}, #{industryConfidence,jdbcType=VARCHAR,typeHandler=com.jinrui.reference.core.typehandler.JsonArrayTypeHandler}, #{industryScore,jdbcType=VARCHAR,typeHandler=com.jinrui.reference.core.typehandler.JsonArrayTypeHandler}, #{conceptLabel,jdbcType=VARCHAR,typeHandler=com.jinrui.reference.core.typehandler.JsonArrayTypeHandler}, #{conceptConfidence,jdbcType=VARCHAR,typeHandler=com.jinrui.reference.core.typehandler.JsonArrayTypeHandler}, #{conceptScore,jdbcType=VARCHAR,typeHandler=com.jinrui.reference.core.typehandler.JsonArrayTypeHandler}, #{source}, #{sourceImpact}, #{chinaFactor}, #{publicOpinionScore}, #{newsScore}, #{newsId}, #{deleted},#{createTime}, #{updateTime})") + void save(NewsTags newsTags); } diff --git a/core/src/main/java/com/jinrui/reference/core/mapper/UserOperationLogMapper.java b/core/src/main/java/com/jinrui/reference/core/mapper/UserOperationLogMapper.java new file mode 100644 index 0000000..ecec0a0 --- /dev/null +++ b/core/src/main/java/com/jinrui/reference/core/mapper/UserOperationLogMapper.java @@ -0,0 +1,35 @@ +package com.jinrui.reference.core.mapper; + +import java.util.Date; +import java.util.List; + +import org.apache.ibatis.annotations.Insert; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Result; +import org.apache.ibatis.annotations.Results; +import org.apache.ibatis.annotations.Select; +import org.apache.ibatis.type.JdbcType; + +import com.jinrui.reference.core.model.entity.UserOperationLog; + +public interface UserOperationLogMapper { + @Insert("insert into user_operation_log(user_id, username, user_type, data_id, type, behavior)" + + "values (#{userId},#{username},#{userType}, #{dataId}, #{type}, #{behavior})") + void save(@Param("userId") Long userId,@Param("username") String username,@Param("userType") String userType,@Param("dataId") Long dataId, @Param("type") String type, @Param("behavior") String behavior); + + @Results({ + @Result(column = "id", property = "id", id = true), + @Result(column = "data_id", property = "dataId"), + @Result(column = "user_id", property = "userId"), + @Result(column = "username", property = "username"), + @Result(column = "user_type", property = "userType"), + @Result(column = "type", property = "type"), + @Result(column = "behavior", property = "behavior"), + @Result(column = "create_time", property = "createTime", javaType = Date.class, jdbcType = JdbcType.TIMESTAMP), + }) + @Select("select * from user_operation_log where data_id = #{dataId} and type = #{type} order by create_time desc limit ${limit} offset ${offset}") + List selectByDataIdAndType(@Param("dataId") Long dataId, @Param("type") String type, @Param("limit") int limit, @Param("offset") int offset); + + @Select("select count(*) from user_operation_log where data_id = #{dataId} and type = #{type}") + int queryTotal(@Param("dataId") Long dataId, @Param("type") String type); +} 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 60b4ca7..e999471 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 @@ -32,6 +32,10 @@ public class SaveNewsDTO { private String contentText; private Integer status; private Date publishTime; + + private Byte rating; + + private String revision; public SaveNewsDTO() {} @@ -144,4 +148,20 @@ public class SaveNewsDTO { newsInfo.setInputDate(newsInfo.getCreateTime()); return newsInfo; } + + public Byte getRating() { + return rating; + } + + public void setRating(Byte rating) { + this.rating = rating; + } + + public String getRevision() { + return revision; + } + + public void setRevision(String revision) { + this.revision = revision; + } } 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 db424f8..344d470 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 @@ -52,7 +52,7 @@ public class News { private String contentText; /** - * 新闻状态 0-草稿 | 1-未发布 | 2-已发布 + * 新闻状态 0-草稿 | 1-未发布 | 2-已发布 | 3-送审 */ private Integer status; @@ -77,7 +77,17 @@ public class News { private String newsinfoId; private Double score; - + + private String llmTitle; + + private String llmContent; + + private Boolean deleted; + + private Byte rating; + + private String revision; + public News() {} public News(SaveNewsDTO saveNewsDTO) { @@ -90,7 +100,10 @@ public class News { this.content = saveNewsDTO.getContent(); this.contentText = saveNewsDTO.getContentText(); this.publishTime = saveNewsDTO.getPublishTime(); -// this.status = 0; + this.llmTitle = saveNewsDTO.getTitle(); + this.llmContent = saveNewsDTO.getContent(); + this.rating = saveNewsDTO.getRating(); + this.revision = saveNewsDTO.getRevision(); this.createTime = new Date(); this.updateTime = new Date(); } @@ -214,4 +227,44 @@ public class News { public void setScore(Double score) { this.score = score; } + + public String getLlmTitle() { + return llmTitle; + } + + public void setLlmTitle(String llmTitle) { + this.llmTitle = llmTitle; + } + + public String getLlmContent() { + return llmContent; + } + + public void setLlmContent(String llmContent) { + this.llmContent = llmContent; + } + + public Boolean getDeleted() { + return deleted; + } + + public void setDeleted(Boolean deleted) { + this.deleted = deleted; + } + + public Byte getRating() { + return rating; + } + + public void setRating(Byte rating) { + this.rating = rating; + } + + public void setRevision(String revision) { + this.revision = revision; + } + + public String getRevision() { + return revision; + } } diff --git a/core/src/main/java/com/jinrui/reference/core/model/entity/NewsDeleted.java b/core/src/main/java/com/jinrui/reference/core/model/entity/NewsDeleted.java new file mode 100644 index 0000000..65bcb79 --- /dev/null +++ b/core/src/main/java/com/jinrui/reference/core/model/entity/NewsDeleted.java @@ -0,0 +1,66 @@ +package com.jinrui.reference.core.model.entity; + +import java.util.Date; + +public class NewsDeleted { + + private Long id; + + private Long newsId; + + private String newsinfoId; + + private String clusterId; + + private Date deletedTime; + + public NewsDeleted() { + + } + + public NewsDeleted(News news, String clusterId) { + this.newsId = news.getId(); + this.newsinfoId = news.getNewsinfoId(); + this.clusterId = clusterId; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getNewsId() { + return newsId; + } + + public void setNewsId(Long newsId) { + this.newsId = newsId; + } + + public String getNewsinfoId() { + return newsinfoId; + } + + public void setNewsinfoId(String newsinfoId) { + this.newsinfoId = newsinfoId; + } + + public String getClusterId() { + return clusterId; + } + + public void setClusterId(String clusterId) { + this.clusterId = clusterId; + } + + public Date getDeletedTime() { + return deletedTime; + } + + public void setDeletedTime(Date deletedTime) { + this.deletedTime = deletedTime; + } +} 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 index 19a9dc3..eceebdb 100644 --- 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 @@ -18,6 +18,8 @@ public class NewsInfo { 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"; + public static final String SCORE_ES_NAME = "news_tags.news_score"; + public static final String INPUT_DATE_ES_NAME = "input_date"; /** * 全量资讯ID @@ -33,7 +35,7 @@ public class NewsInfo { /** * 数据输入时间 */ - @JsonProperty("input_date") + @JsonProperty(INPUT_DATE_ES_NAME) @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss", timezone = "Asia/Shanghai") private Date inputDate; @@ -137,6 +139,8 @@ public class NewsInfo { */ private Long editorId; + @JsonProperty("news_tags") + private NewsTags newsTags; public NewsInfo() {} @@ -315,12 +319,22 @@ public class NewsInfo { public void setWords(Integer words) { this.words = words; } + + public NewsTags getNewsTags() { + return newsTags; + } + + public void setNewsTags(NewsTags newsTags) { + this.newsTags = newsTags; + } public News toNews() { News news = new News(); news.setTitle(this.getTitle()); news.setContent(this.getContent()); + news.setLlmTitle(this.getTitle()); + news.setLlmContent(this.getContent()); news.setStatus(this.getStatus()); news.setSummary(this.getSummary()); news.setPublishTime(this.getInputDate()); diff --git a/core/src/main/java/com/jinrui/reference/core/model/entity/NewsTags.java b/core/src/main/java/com/jinrui/reference/core/model/entity/NewsTags.java index 631b65c..c89cc7f 100644 --- a/core/src/main/java/com/jinrui/reference/core/model/entity/NewsTags.java +++ b/core/src/main/java/com/jinrui/reference/core/model/entity/NewsTags.java @@ -3,43 +3,67 @@ package com.jinrui.reference.core.model.entity; import java.util.Date; import java.util.List; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonProperty; + public class NewsTags { + @JsonProperty("id") private Long id; + @JsonProperty("abstract") private String summary; + @JsonProperty("title") private String title; + @JsonProperty("rewrite_content") private String rewriteContent; + @JsonProperty("industry_label") private List industryLabel; + @JsonProperty("industry_confidence") private List industryConfidence; + @JsonProperty("industry_score") private List industryScore; + @JsonProperty("concept_label") private List conceptLabel; + @JsonProperty("concept_confidence") private List conceptConfidence; + @JsonProperty("concept_score") private List conceptScore; + @JsonProperty("source") private String source; + @JsonProperty("source_impact") private Integer sourceImpact; + @JsonProperty("China_factor") private Double chinaFactor; + @JsonProperty("public_opinion_score") private Integer publicOpinionScore; + @JsonProperty("news_score") private Double newsScore; + @JsonProperty("news_id") private Long newsId; - private Boolean deleted; + @JsonProperty("deleted") + private Integer deleted; + @JsonProperty("create_time") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss", timezone = "Asia/Shanghai") private Date createTime; + @JsonProperty("update_time") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss", timezone = "Asia/Shanghai") private Date updateTime; public List getIndustryScore() { @@ -170,11 +194,11 @@ public class NewsTags { this.newsId = newsId; } - public Boolean getDeleted() { + public Integer getDeleted() { return deleted; } - public void setDeleted(Boolean deleted) { + public void setDeleted(Integer deleted) { this.deleted = deleted; } diff --git a/core/src/main/java/com/jinrui/reference/core/model/entity/UserOperationLog.java b/core/src/main/java/com/jinrui/reference/core/model/entity/UserOperationLog.java new file mode 100644 index 0000000..27f8658 --- /dev/null +++ b/core/src/main/java/com/jinrui/reference/core/model/entity/UserOperationLog.java @@ -0,0 +1,63 @@ +package com.jinrui.reference.core.model.entity; + +import java.util.Date; + +public class UserOperationLog { + private Long id; + private Long dataId; + private String type; + private String behavior; + private Long userId; + private String username; + private String userType; + private Date createTime; + + public Long getId() { + return id; + } + public void setId(Long id) { + this.id = id; + } + public Long getDataId() { + return dataId; + } + public void setDataId(Long dataId) { + this.dataId = dataId; + } + public String getType() { + return type; + } + public void setType(String type) { + this.type = type; + } + public String getBehavior() { + return behavior; + } + public void setBehavior(String behavior) { + this.behavior = behavior; + } + public Long getUserId() { + return userId; + } + public void setUserId(Long userId) { + this.userId = userId; + } + public Date getCreateTime() { + return createTime; + } + public void setCreateTime(Date createTime) { + this.createTime = createTime; + } + public String getUserType() { + return userType; + } + public void setUserType(String userType) { + this.userType = userType; + } + public String getUsername() { + return username; + } + public void setUsername(String username) { + this.username = username; + } +} diff --git a/core/src/main/java/com/jinrui/reference/core/model/vo/news/NewsApiVO.java b/core/src/main/java/com/jinrui/reference/core/model/vo/news/NewsApiVO.java new file mode 100644 index 0000000..a28d31d --- /dev/null +++ b/core/src/main/java/com/jinrui/reference/core/model/vo/news/NewsApiVO.java @@ -0,0 +1,221 @@ +package com.jinrui.reference.core.model.vo.news; + +import java.util.Date; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonFormat; + +public class NewsApiVO { + /** + * 资讯ID + */ + private Long id; + + /** + * 资讯标题 + */ + private String title; + + /** + * 资讯内容 + */ + private String content; + + /** + * 报道时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai") + private Date publishTime; + + /** + * 资讯摘要 + */ + private String summary; + + /** + * 资讯评分 + */ + private Double newsScore; + + /** + * 资讯来源 + */ + private String source; + + /** + * 行业分类标签 + */ + private List industryLabel; + + /** + * 行业分类置信度 + */ + private List industryConfidence; + + /** + * 行业分类评分 + */ + private List industryScore; + + + /** + * 概念标签 + * @return + */ + private List conceptLabel; + + /** + * 概念标签置信度 + */ + private List conceptConfidence; + + /** + * 概念标签评分 + */ + private List conceptScore; + + /** + * 媒体影响力 + */ + private Integer sourceImpact; + + /** + * 中国股市相关性 + */ + private Double chinaFactor; + + /** + * 资讯质量 + */ + private Integer publicOpinionScore; + + + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public Date getPublishTime() { + return publishTime; + } + + public void setPublishTime(Date publishTime) { + this.publishTime = publishTime; + } + + public String getSummary() { + return summary; + } + + public void setSummary(String summary) { + this.summary = summary; + } + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } + + public List getIndustryLabel() { + return industryLabel; + } + + public void setIndustryLabel(List industryLabel) { + this.industryLabel = industryLabel; + } + + public List getConceptLabel() { + return conceptLabel; + } + + public void setConceptLabel(List conceptLabel) { + this.conceptLabel = conceptLabel; + } + + public Double getNewsScore() { + return newsScore; + } + + public void setNewsScore(Double newsScore) { + this.newsScore = newsScore; + } + + public List getIndustryConfidence() { + return industryConfidence; + } + + public void setIndustryConfidence(List industryConfidence) { + this.industryConfidence = industryConfidence; + } + + public List getIndustryScore() { + return industryScore; + } + + public void setIndustryScore(List industryScore) { + this.industryScore = industryScore; + } + + public List getConceptConfidence() { + return conceptConfidence; + } + + public void setConceptConfidence(List conceptConfidence) { + this.conceptConfidence = conceptConfidence; + } + + public List getConceptScore() { + return conceptScore; + } + + public void setConceptScore(List conceptScore) { + this.conceptScore = conceptScore; + } + + public Integer getSourceImpact() { + return sourceImpact; + } + + public void setSourceImpact(Integer sourceImpact) { + this.sourceImpact = sourceImpact; + } + + public Double getChinaFactor() { + return chinaFactor; + } + + public void setChinaFactor(Double chinaFactor) { + this.chinaFactor = chinaFactor; + } + + public Integer getPublicOpinionScore() { + return publicOpinionScore; + } + + public void setPublicOpinionScore(Integer publicOpinionScore) { + this.publicOpinionScore = publicOpinionScore; + } +} diff --git a/core/src/main/java/com/jinrui/reference/core/model/vo/news/NewsDetailVO.java b/core/src/main/java/com/jinrui/reference/core/model/vo/news/NewsDetailVO.java index ebcc6b8..b31dac2 100644 --- a/core/src/main/java/com/jinrui/reference/core/model/vo/news/NewsDetailVO.java +++ b/core/src/main/java/com/jinrui/reference/core/model/vo/news/NewsDetailVO.java @@ -8,12 +8,16 @@ import com.jinrui.reference.core.model.entity.NewsDraft; @SuppressWarnings("unused") public class NewsDetailVO { + + public static final String AI_TITLE_FLAG = "title"; + public static final String AI_CONENT_FLAG = "content"; + public static final String AI_TITLE_CONTENT_FLAG = "title&content"; private Long id; - private String title; + private String title; // 标题 - private String summary; + private String summary; private String picture; @@ -23,10 +27,22 @@ public class NewsDetailVO { private List industry; - private String content; + private String content; // 正文 private Date publishTime; - + + private Boolean titleTranslated; // 大模型翻译标识 + + private Boolean contentTranslated; // 大模型翻译标识 + + private Boolean deleted; // 删除标识 + + private String newsInfoId; // 获取原文和参考译文的字段 + + private Byte rating; // 打分 + + private String revision; // 修订说明 + public NewsDetailVO() { } @@ -37,6 +53,10 @@ public class NewsDetailVO { this.picture = news.getPicture(); this.content = news.getContent(); this.publishTime = news.getPublishTime(); + this.deleted = news.getDeleted(); + this.newsInfoId = news.getNewsinfoId(); + this.rating = news.getRating(); + this.revision = news.getRevision(); } public NewsDetailVO(NewsDraft newsDraft) { @@ -117,5 +137,68 @@ public class NewsDetailVO { public void setPublishTime(Date publishTime) { this.publishTime = publishTime; } + + public Boolean getTitleTranslated() { + return titleTranslated; + } + public void setTitleTranslated(Boolean titleTranslated) { + this.titleTranslated = titleTranslated; + } + + public Boolean getContentTranslated() { + return contentTranslated; + } + + public void setContentTranslated(Boolean contentTranslated) { + this.contentTranslated = contentTranslated; + } + + public Boolean getDeleted() { + return deleted; + } + + public void setDeleted(Boolean deleted) { + this.deleted = deleted; + } + + public String getNewsInfoId() { + return newsInfoId; + } + + public void setNewsInfoId(String newsInfoId) { + this.newsInfoId = newsInfoId; + } + + public void markTranslatedFlag(String aiFlag) { + if (AI_TITLE_FLAG.equals(aiFlag)) { + this.setTitleTranslated(true); + this.setContentTranslated(false); + } else if (AI_CONENT_FLAG.equals(aiFlag)) { + this.setTitleTranslated(false); + this.setContentTranslated(true); + } else if (AI_TITLE_CONTENT_FLAG.equals(aiFlag)) { + this.setTitleTranslated(true); + this.setContentTranslated(true); + } else { + this.setTitleTranslated(false); + this.setContentTranslated(false); + } + } + + public Byte getRating() { + return rating; + } + + public void setRating(Byte rating) { + this.rating = rating; + } + + public String getRevision() { + return revision; + } + + public void setRevision(String revision) { + this.revision = revision; + } } diff --git a/core/src/main/java/com/jinrui/reference/core/model/vo/news/NewsLogVO.java b/core/src/main/java/com/jinrui/reference/core/model/vo/news/NewsLogVO.java new file mode 100644 index 0000000..323cb26 --- /dev/null +++ b/core/src/main/java/com/jinrui/reference/core/model/vo/news/NewsLogVO.java @@ -0,0 +1,44 @@ +package com.jinrui.reference.core.model.vo.news; + +import java.util.Date; + +import com.jinrui.reference.core.model.entity.UserOperationLog; + +public class NewsLogVO { + private String username; + private String userType; + private String behavior; + private Date createTime; + + public NewsLogVO(UserOperationLog userOperationLog) { + this.username = userOperationLog.getUsername(); + this.userType = userOperationLog.getUserType(); + this.behavior = userOperationLog.getBehavior(); + this.createTime = userOperationLog.getCreateTime(); + } + + public String getUsername() { + return username; + } + public void setUsername(String username) { + this.username = username; + } + public String getUserType() { + return userType; + } + public void setUserType(String userType) { + this.userType = userType; + } + public String getBehavior() { + return behavior; + } + public void setBehavior(String behavior) { + this.behavior = behavior; + } + public Date getCreateTime() { + return createTime; + } + public void setCreateTime(Date createTime) { + this.createTime = createTime; + } +} diff --git a/core/src/main/java/com/jinrui/reference/core/model/vo/news/NewsTranslatorVO.java b/core/src/main/java/com/jinrui/reference/core/model/vo/news/NewsTranslatorVO.java new file mode 100644 index 0000000..a8c7f16 --- /dev/null +++ b/core/src/main/java/com/jinrui/reference/core/model/vo/news/NewsTranslatorVO.java @@ -0,0 +1,50 @@ +package com.jinrui.reference.core.model.vo.news; + +import java.util.Date; +import java.util.List; + +import com.jinrui.reference.core.model.entity.News; +import com.jinrui.reference.core.model.entity.NewsDraft; + +@SuppressWarnings("unused") +public class NewsTranslatorVO { + private String titleEN; // 原文标题 + + private String contentEN; // 原文正文 + + private String titleCN; // 参考翻译标题 + + private String contentCN; // 参考翻译正文 + + public String getTitleEN() { + return titleEN; + } + + public void setTitleEN(String titleEN) { + this.titleEN = titleEN; + } + + public String getContentEN() { + return contentEN; + } + + public void setContentEN(String contentEN) { + this.contentEN = contentEN; + } + + public String getTitleCN() { + return titleCN; + } + + public void setTitleCN(String titleCN) { + this.titleCN = titleCN; + } + + public String getContentCN() { + return contentCN; + } + + public void setContentCN(String contentCN) { + this.contentCN = contentCN; + } +} diff --git a/core/src/main/java/com/jinrui/reference/core/model/vo/news/NewsVO.java b/core/src/main/java/com/jinrui/reference/core/model/vo/news/NewsVO.java index c037dd7..02a0cb6 100644 --- a/core/src/main/java/com/jinrui/reference/core/model/vo/news/NewsVO.java +++ b/core/src/main/java/com/jinrui/reference/core/model/vo/news/NewsVO.java @@ -53,6 +53,18 @@ public class NewsVO { private Date updateTime; private Double score; + + public Boolean getDeleted() { + return deleted; + } + + public void setDeleted(Boolean deleted) { + this.deleted = deleted; + } + + private Boolean deleted; // 删除标识 + + private Byte rating; // 打分 public NewsVO(News news) { this.id = news.getId(); @@ -62,6 +74,8 @@ public class NewsVO { this.publishTime = news.getPublishTime(); this.updateTime = news.getUpdateTime(); this.score = news.getScore(); + this.deleted = news.getDeleted(); + this.rating = news.getRating(); } public Long getId() { @@ -123,4 +137,12 @@ public class NewsVO { public void setScore(Double score) { this.score = score; } + + public Byte getRating() { + return rating; + } + + public void setRating(Byte rating) { + this.rating = rating; + } } 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 index aef422f..26e95a9 100644 --- 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 @@ -75,7 +75,12 @@ public class NewsInfoVO { */ private Long editorId; - public NewsInfoVO(NewsInfo newsInfo) { + /** + * 资讯评分 + */ + private Double score; + + public NewsInfoVO(NewsInfo newsInfo) { this.id = newsInfo.getId(); this.title = newsInfo.getTitle(); this.summary = newsInfo.getSummary(); @@ -86,6 +91,9 @@ public class NewsInfoVO { this.createTime = newsInfo.getCreateTime(); this.updateTime = newsInfo.getUpdateTime(); this.inputDate = newsInfo.getInputDate(); + if (newsInfo.getNewsTags() != null) { + this.score = newsInfo.getNewsTags().getNewsScore(); + } } public String getId() { @@ -191,4 +199,12 @@ public class NewsInfoVO { public void setContent(String content) { this.content = content; } + + public Double getScore() { + return score; + } + + public void setScore(Double score) { + this.score = score; + } } 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 index d0f3da0..f585bba 100644 --- 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 @@ -140,12 +140,20 @@ public class SaveNewsInfoDTO { News news = new News(); news.setTitle(this.getTitle()); news.setSummary(this.getSummary()); + news.setLlmTitle(this.getTitle()); + news.setLlmContent(this.getContent()); news.setStatus(this.getStatus()); news.setContent(this.getContent()); news.setCreateTime(new Date()); news.setUpdateTime(news.getCreateTime()); news.setType(1); news.setNewsinfoId(this.getId()); + // 报道时间 + if (this.getPublishTime() != null) { + news.setPublishTime(this.getPublishTime()); + } else { + news.setPublishTime(news.getCreateTime()); + } return news; } } diff --git a/core/src/main/java/com/jinrui/reference/core/service/ApiKeyService.java b/core/src/main/java/com/jinrui/reference/core/service/ApiKeyService.java new file mode 100644 index 0000000..1207453 --- /dev/null +++ b/core/src/main/java/com/jinrui/reference/core/service/ApiKeyService.java @@ -0,0 +1,49 @@ +package com.jinrui.reference.core.service; + +import java.util.HashMap; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; +import org.springframework.util.ObjectUtils; + +import com.jinrui.reference.core.api.ApiKeyGenerator; +import com.jinrui.reference.core.mapper.ApiUserMapper; +import com.jinrui.reference.core.model.vo.ResultObject; + +@Service +public class ApiKeyService { + private static final Logger LOGGER = LoggerFactory.getLogger(ApiKeyService.class); + + private final ApiUserMapper apiUserMapper; + + public ApiKeyService(ApiUserMapper apiUserMapper) { + this.apiUserMapper = apiUserMapper; + } + + public String getSecretKeyByAk(String ak) { + String secretKey = apiUserMapper.getClientSecretkey(ak); + return secretKey; + } + + public ResultObject> generateApiKey(String clientName) { + Map clientKeyMap = apiUserMapper.getClientKey(clientName); + if (ObjectUtils.isEmpty(clientKeyMap)) { + String accessKey = ApiKeyGenerator.generateAK(); + String secretKey = ApiKeyGenerator.generateSK(); + try { + apiUserMapper.save(clientName, accessKey, secretKey); + } catch (Exception e) { + LOGGER.error("产生客户API密钥对失败", e); + ResultObject.failed("客户已存在!"); + } + clientKeyMap = new HashMap<>(); + clientKeyMap.put("access_key", accessKey); + clientKeyMap.put("secret_key", secretKey); + } + + return ResultObject.success(clientKeyMap); + } + +} 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 index b3ee813..df58a14 100644 --- a/core/src/main/java/com/jinrui/reference/core/service/NewsInfoService.java +++ b/core/src/main/java/com/jinrui/reference/core/service/NewsInfoService.java @@ -1,10 +1,14 @@ package com.jinrui.reference.core.service; import java.io.IOException; +import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.function.Function; import java.util.stream.Collectors; @@ -13,7 +17,6 @@ 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; @@ -33,6 +36,7 @@ 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.news.NewsScoreVO; 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; @@ -40,12 +44,14 @@ 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.SortOptions; 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.RangeQuery; import co.elastic.clients.elasticsearch._types.query_dsl.TermQuery; import co.elastic.clients.elasticsearch.core.GetResponse; import co.elastic.clients.elasticsearch.core.IndexResponse; @@ -53,8 +59,7 @@ import co.elastic.clients.elasticsearch.core.SearchRequest; import co.elastic.clients.elasticsearch.core.SearchResponse; import co.elastic.clients.elasticsearch.core.search.Hit; import co.elastic.clients.elasticsearch.core.search.HitsMetadata; -import co.elastic.clients.json.JsonpUtils; -import co.elastic.clients.json.jackson.JacksonJsonpMapper; +import co.elastic.clients.json.JsonData; @Service public class NewsInfoService { @@ -104,6 +109,14 @@ public class NewsInfoService { News relNews = newsInfo.toNews(); relNews.setStatus(1); relNews.setEditorId(editorId); + + NewsTags newsTags = newsTagsMapper.getNewsTagsByNewsId(id); + if (newsTags != null) { + String llmTitle = Optional.ofNullable(newsTags.getTitle()).filter(s -> !s.isEmpty()).orElse(relNews.getTitle()); + relNews.setLlmTitle(llmTitle); + String llmContent = Optional.ofNullable(newsTags.getRewriteContent()).filter(s -> !s.isEmpty()).orElse(relNews.getContent()); + relNews.setLlmContent(llmContent); + } newsMapper.saveNews(relNews); saveNewsRel(id, relNews.getId()); } @@ -336,6 +349,7 @@ public class NewsInfoService { News relateNews = newsMapper.getNewsDetailByNewsInfoId(id); if (relateNews == null) { + saveNewsInfoDTO.setPublishTime(newsInfo.getInputDate()); // 报道时间 relateNews = createRelateNews(saveNewsInfoDTO); NewsInfo updatedNewsInfo = toNewsInfo(relateNews.getId(), saveNewsInfoDTO); elasticsearchClient.update(e -> e.index(NewsInfo.INDEX_NAME).id(updatedNewsInfo.getId()).doc(updatedNewsInfo).refresh(Refresh.True), NewsInfo.class); @@ -351,7 +365,7 @@ public class NewsInfoService { return ResultObject.success(); } - public ResultObject deleteNewsInfo(String newsInfoId) { + public ResultObject deleteNewsInfo(String newsInfoId, Long editorId) { try { GetResponse getResp = elasticsearchClient.get(e -> e.index(NewsInfo.INDEX_NAME).id(String.valueOf(newsInfoId)), NewsInfo.class); if (getResp.found()) { @@ -359,7 +373,7 @@ public class NewsInfoService { Long newsId = newsInfo.getNewsId(); // 删除资讯精选中关联的数据 deleteNewsRel(newsId); - newsMapper.deleteNews(newsId); + newsMapper.deleteNews(newsId, editorId); NewsInfo deletedNewsInfo = new NewsInfo(); deletedNewsInfo.setDeleted(1); @@ -385,7 +399,7 @@ public class NewsInfoService { try { IndexResponse resp = elasticsearchClient.index(c -> c.index(NewsInfo.INDEX_NAME).document(newsInfo).refresh(Refresh.True)); news.setNewsinfoId(resp.id()); - newsMapper.updateNews(news); + newsMapper.updateNews(news, news.getStatus()); } catch(IOException|ElasticsearchException e) { log.error("新建全量资讯出错!", e); return ResultObject.failed(500, "服务器错误,请联系系统管理员!"); @@ -419,7 +433,7 @@ public class NewsInfoService { private void updateRelateNews(Long newsId, SaveNewsInfoDTO saveNewsDTO) { News news = saveNewsDTO.toNews(); news.setId(newsId); - newsMapper.updateNews(news); + newsMapper.updateNews(news, news.getStatus()); tagMapper.deleteNews(newsId); industryMapper.deleteNews(newsId); @@ -489,30 +503,10 @@ public class NewsInfoService { } public PageObject queryNewsInfo(String title, String content, String stockcode, Long sourcename, int page, int size, - String last, Integer current, String orderBy, String direction) { + String last, Integer current, String orderBy, String direction, Double minScore, Double maxScore, + Date inputDateFrom, Date inputDateTo) { - if (StringUtils.hasText(orderBy)) { - switch (orderBy) { - case "publishTime": { - orderBy = "publish_time"; - break; - } - case "updateTime": { - orderBy = "update_time"; - break; - } - case "createTime": { - orderBy = "create_time"; - break; - } - case "inputDate": { - orderBy = "input_date"; - break; - } - } - } - - final String orderByField = orderBy; + final String orderByField = mappingToEsFieldName(orderBy); int offset = 0; if (current != null) { offset = (Math.max(0, current - 1)) * size; @@ -542,18 +536,34 @@ public class NewsInfoService { filters.add(sourcenameQuery); } } - - if (conditions.size() == 0) { - conditions.add(QueryBuilders.matchAll().build()._toQuery()); + // 评分范围查询 + Query scoreRangeQuery = buildRangeQuery(NewsInfo.SCORE_ES_NAME, minScore, maxScore); + if (scoreRangeQuery != null) { + filters.add(scoreRangeQuery); } + // 报道时间范围查询 + Query inputDateRangeQuery = buildRangeQuery(NewsInfo.INPUT_DATE_ES_NAME, inputDateFrom, inputDateTo); + if (inputDateRangeQuery != null) { + filters.add(inputDateRangeQuery); + } + + List sortOptions = new ArrayList<>(); + if (conditions.size() > 0) { + sortOptions.add(SortOptions.of(e -> e.score(x -> x.order(SortOrder.Desc)))); + } + +// 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); + sortOptions.add(SortOptions.of(e -> e.field(f -> f.field(orderByField).order(sortOrder)))); SearchRequest request = SearchRequest.of(s -> - s.from(from) + s.index(NewsInfo.INDEX_NAME).from(from) .size(size) // 分页参数 .trackTotalHits(e -> e.enabled(true)) - .sort(so -> so.field(f -> f.field(orderByField).order(sortOrder))) // 排序字段 + .sort(sortOptions) // 排序字段 .query(resultQuery).trackScores(true) .highlight(h -> h.preTags("") .postTags("") @@ -592,4 +602,73 @@ public class NewsInfoService { pageObject.setData(newsInfoList); return pageObject; } + + private Query buildRangeQuery(String fieldName, Object fromValue, Object toValue) { + if (fromValue == null && toValue == null) { + return null; + } + + RangeQuery.Builder rangeBuilder = QueryBuilders.range().field(fieldName); + if (fromValue != null) { + if (fromValue instanceof Date) { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + rangeBuilder = rangeBuilder.gte(JsonData.of(sdf.format(fromValue))); + } else { + rangeBuilder = rangeBuilder.gte(JsonData.of(fromValue)); + } + + } + if (toValue != null) { + if (toValue instanceof Date) { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + Calendar calendar = Calendar.getInstance(); + calendar.setTime((Date)toValue); + calendar.add(Calendar.DATE, 1); + Date expireDate = calendar.getTime(); + rangeBuilder = rangeBuilder.lt(JsonData.of(sdf.format(expireDate))); + } else { + rangeBuilder = rangeBuilder.lte(JsonData.of(toValue)); + } + + } + return rangeBuilder.build()._toQuery(); + } + + private String mappingToEsFieldName(String orderBy) { + String esFieldName = ""; + switch (orderBy) { + case "publishTime": { + esFieldName = "publish_time"; + break; + } + case "updateTime": { + esFieldName = "update_time"; + break; + } + case "createTime": { + esFieldName = "create_time"; + break; + } + case "inputDate": { + esFieldName = "input_date"; + break; + } + case "score": { + esFieldName = "news_tags.news_score"; + break; + } + default: + esFieldName = "input_date"; + } + return esFieldName; + } + + public ResultObject getScore(String id) { + NewsTags newsTags = newsTagsMapper.getNewsTagsByNewsId(id); + if (newsTags == null) { + return ResultObject.success(); + } + NewsScoreVO newsScoreVO = new NewsScoreVO(newsTags); + return ResultObject.success(newsScoreVO); + } } 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 9335772..61ab262 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 @@ -10,6 +10,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; @@ -22,15 +23,18 @@ import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; -import com.aliyun.oss.common.utils.DateUtil; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.jinrui.reference.core.mapper.ColumnMapper; import com.jinrui.reference.core.mapper.IndustryMapper; +import com.jinrui.reference.core.mapper.NewsDeletedMapper; import com.jinrui.reference.core.mapper.NewsMapper; import com.jinrui.reference.core.mapper.NewsTagsMapper; import com.jinrui.reference.core.mapper.TagMapper; +import com.jinrui.reference.core.mapper.UserOperationLogMapper; import com.jinrui.reference.core.model.dto.news.SaveDraftColumn; import com.jinrui.reference.core.model.dto.news.SaveDraftColumnItem; import com.jinrui.reference.core.model.dto.news.SaveDraftColumnVip; @@ -43,26 +47,32 @@ import com.jinrui.reference.core.model.entity.DraftTagRel; import com.jinrui.reference.core.model.entity.Industry; import com.jinrui.reference.core.model.entity.News; import com.jinrui.reference.core.model.entity.NewsColumnRel; +import com.jinrui.reference.core.model.entity.NewsDeleted; 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.NewsTags; import com.jinrui.reference.core.model.entity.Tag; +import com.jinrui.reference.core.model.entity.UserOperationLog; import com.jinrui.reference.core.model.vo.PageObject; import com.jinrui.reference.core.model.vo.ResultObject; import com.jinrui.reference.core.model.vo.column.ColumnVO; +import com.jinrui.reference.core.model.vo.news.NewsApiVO; import com.jinrui.reference.core.model.vo.news.NewsDetailColumn; import com.jinrui.reference.core.model.vo.news.NewsDetailColumnVip; 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.news.NewsDetailVO; +import com.jinrui.reference.core.model.vo.news.NewsLogVO; import com.jinrui.reference.core.model.vo.news.NewsScoreVO; +import com.jinrui.reference.core.model.vo.news.NewsTranslatorVO; import com.jinrui.reference.core.model.vo.news.NewsVO; import co.elastic.clients.elasticsearch.ElasticsearchClient; import co.elastic.clients.elasticsearch._types.Refresh; +import co.elastic.clients.elasticsearch.core.GetResponse; @Service public class NewsService { @@ -76,6 +86,8 @@ public class NewsService { private final IndustryMapper industryMapper; private final ElasticsearchClient elasticsearchClient; private final NewsTagsMapper newsTagsMapper; + private final NewsDeletedMapper newsDeletedMapper; + private final UserOperationLogMapper userOperationLogMapper; public NewsService(NewsMapper newsMapper, ColumnMapper columnMapper, @@ -83,7 +95,9 @@ public class NewsService { TagMapper tagMapper, IndustryMapper industryMapper, ElasticsearchClient elasticsearchClient, - NewsTagsMapper newsTagsMapper) { + NewsTagsMapper newsTagsMapper, + NewsDeletedMapper newsDeletedMapper, + UserOperationLogMapper userOperationLogMapper) { this.newsMapper = newsMapper; this.columnMapper = columnMapper; this.objectMapper = objectMapper; @@ -91,26 +105,30 @@ public class NewsService { this.industryMapper = industryMapper; this.elasticsearchClient = elasticsearchClient; this.newsTagsMapper = newsTagsMapper; + this.newsDeletedMapper = newsDeletedMapper; + this.userOperationLogMapper = userOperationLogMapper; } - + public ResultObject publish(long id, long editorId) { News news = newsMapper.getById(id); if (news == null) { log.warn("找不到ID为{}的新闻!", id); return ResultObject.failed("找不到ID为" + id + "的新闻!"); } - Integer status = news.getStatus(); - if (status == 2) { - newsMapper.simpleUnpublish(id); + Integer oldStatus = news.getStatus(); + if (oldStatus == 1) { + return ResultObject.failed("资讯已被撤稿,请刷新列表页面!"); + } + if (oldStatus == 2) { + newsMapper.changeFrom(id, oldStatus, 3, editorId); return ResultObject.success(); } Long draftId = news.getDraftId(); if (draftId == null) { - newsMapper.simplePublish(id, editorId); + newsMapper.changeFrom(id, oldStatus, 2, editorId); return ResultObject.success(); } - ResultObject resultObject = detail(id); int code = resultObject.getCode(); NewsDetailVO newsDetailVO = resultObject.getData(); @@ -118,7 +136,7 @@ public class NewsService { return ResultObject.failed(resultObject.getCode(), resultObject.getMsg()); } SaveNewsDTO saveNewsDTO = new SaveNewsDTO(newsDetailVO); - return createPublish(editorId, saveNewsDTO); + return createPublish(editorId, saveNewsDTO, true); } public ResultObject detail(Long id) { @@ -139,6 +157,10 @@ public class NewsService { NewsDraft newsDraft = newsMapper.getDraftDetail(draftId); NewsDetailVO newsDetailVO = new NewsDetailVO(newsDraft); newsDetailVO.setId(id); + newsDetailVO.setDeleted(news.getDeleted()); + newsDetailVO.setNewsInfoId(news.getNewsinfoId()); + newsDetailVO.setRating(news.getRating()); + newsDetailVO.setRevision(news.getRevision()); Set set = new HashSet<>(); set.add(draftId); @@ -221,33 +243,13 @@ public class NewsService { } } - -// List tagRelListNews = tagMapper.getNewsTagRelList(id); -// NewsDetailTag newsDetailTag2 = new NewsDetailTag(); -// newsDetailVO.setTag(newsDetailTag2); -// List arr = new ArrayList<>(); -// if (!CollectionUtils.isEmpty(tagRelListNews)) { -// for (NewsTagRel rel : tagRelListNews) { -// 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) { -// newsDetailTag2.setSource(tagItem); -// } else { -//// newsDetailTag2.setField(tagItem); -// arr.add(tagItem); -// } -// } -// newsDetailTag2.setFieldArr(arr); -// } + processLLMFlag(newsDetailVO); return ResultObject.success(newsDetailVO); } news = newsMapper.getNewsDetail(id); NewsDetailVO newsDetailVO = new NewsDetailVO(news); + processLLMFlag(newsDetailVO); Set set = new HashSet<>(); set.add(id); @@ -338,15 +340,49 @@ public class NewsService { } return ResultObject.success(newsDetailVO); } + + private void processLLMFlag(NewsDetailVO newsDetailVO) { + String newsInfoId = newsDetailVO.getNewsInfoId(); + String llmFlag = null; + if (ObjectUtils.isEmpty(newsInfoId)) { + newsDetailVO.markTranslatedFlag(llmFlag); + return; + } + + NewsTags newsTags = newsTagsMapper.getNewsTagsByNewsId(newsInfoId); + if (newsTags == null) { + newsDetailVO.markTranslatedFlag(llmFlag); + return; + } + llmFlag = checkNewsLLMFlag(newsTags); + newsDetailVO.markTranslatedFlag(llmFlag); + } + + private String checkNewsLLMFlag(NewsTags newsTags) { + String llmFlag = null; + if (!ObjectUtils.isEmpty(newsTags.getTitle()) && !ObjectUtils.isEmpty(newsTags.getRewriteContent())) { + llmFlag = NewsDetailVO.AI_TITLE_CONTENT_FLAG; + } else if (!ObjectUtils.isEmpty(newsTags.getTitle())) { + llmFlag = NewsDetailVO.AI_TITLE_FLAG; + } else if (!ObjectUtils.isEmpty(newsTags.getRewriteContent())) { + llmFlag = NewsDetailVO.AI_CONENT_FLAG; + } + return llmFlag; + } - public ResultObject createPublish(Long editorId, SaveNewsDTO saveNewsDTO) { + public ResultObject createPublish(Long editorId, SaveNewsDTO saveNewsDTO, boolean isReviewer) { Long id = saveNewsDTO.getId(); Long newsId = saveNewsDTO.getId(); + Integer newStatus = (isReviewer ? 2: 3); + Integer oldStatus = (isReviewer ? 3: 1); News news; + Long draftId = null; + String newsInfoId = null; if (id != null) { news = newsMapper.getById(id); - Long draftId = news.getDraftId(); - if (draftId != null) { + draftId = news.getDraftId(); + newsInfoId = news.getNewsinfoId(); + if (draftId != null && newStatus.intValue() == 2) { deleteDraft(draftId); } } @@ -356,14 +392,20 @@ public class NewsService { // 已发布 saveNewsDTO.setStatus(2); saveNewsDTO.setPublishTime(new Date()); - saveNewDraft(saveNewsDTO, null); + saveNewDraft(saveNewsDTO, null, isReviewer); } Long newIdRl = saveNewsDTO.getId(); news = new News(saveNewsDTO); - news.setStatus(2); + news.setStatus(newStatus); news.setEditorId(editorId); - newsMapper.publishNews(news); + news.setNewsinfoId(newsInfoId); + news.setDraftId(isReviewer ? null: draftId); + + int count = newsMapper.updateNews(news, oldStatus); + if (count == 0) { + return ResultObject.failed("该资讯正在审核中,请勿重复操作"); + } // zzp deleteNewsRel(newsId); try { @@ -477,57 +519,37 @@ public class NewsService { elasticsearchClient.update(e -> e.index(NewsInfo.INDEX_NAME).refresh(Refresh.True).id(newsInfoId).doc(newsInfo), NewsInfo.class); } - public ResultObject saveDraft(SaveNewsDTO saveNewsDTO) { + public ResultObject saveDraft(SaveNewsDTO saveNewsDTO, boolean isReviewer) { Long id = saveNewsDTO.getId(); if (id == null) { - return saveNewDraft(saveNewsDTO, null); + saveNewsDTO.setStatus(1); + return saveNewDraft(saveNewsDTO, null, isReviewer); } - return updateDraft(saveNewsDTO); + return updateDraft(saveNewsDTO, isReviewer); } - private ResultObject updateDraft(SaveNewsDTO saveNewsDTO) { + private ResultObject updateDraft(SaveNewsDTO saveNewsDTO, boolean isReviewer) { Long id = saveNewsDTO.getId(); News news = newsMapper.getById(id); Long draftId = news.getDraftId(); if (draftId != null) { deleteDraft(draftId); } - return saveNewDraft(saveNewsDTO, news); + return saveNewDraft(saveNewsDTO, news, isReviewer); } - public ResultObject deleteNews(Long newsId) { + public ResultObject deleteNews(Long newsId, Long editorId) { News news = newsMapper.getById(newsId); Integer status = news.getStatus(); if (status == 2) { return ResultObject.failed("请先手动下架新闻然后进行删除!"); } - Long draftId = news.getDraftId(); - if (draftId != null) { - try { - deleteDraft(draftId); - } catch (Exception e) { - log.error("删除新闻草稿异常!", e); - return ResultObject.failed(500, "服务器错误,请联系系统管理员!"); - } - } try { - deleteNewsRel(newsId); - } catch (Exception e) { - log.error("删除新闻栏目标签异常!", e); - return ResultObject.failed(500, "服务器错误,请联系系统管理员!"); - } - 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); + int count = newsMapper.deleteNews(newsId, editorId); + if (count == 0) { + return ResultObject.failed("该资讯正在审核中,请勿重复操作"); } } catch (Exception e) { log.error("删除新闻异常!", e); @@ -549,7 +571,7 @@ public class NewsService { industryMapper.deleteDraft(draftId); } - private ResultObject saveNewDraft(SaveNewsDTO saveNewsDTO, News news) { + private ResultObject saveNewDraft(SaveNewsDTO saveNewsDTO, News news, boolean isReviewer) { NewsDraft newsDraft = new NewsDraft(saveNewsDTO); try { newsMapper.saveDraft(newsDraft); @@ -558,6 +580,7 @@ public class NewsService { return ResultObject.failed(500, "服务器错误,请联系系统管理员!"); } + Integer oldStatus = (isReviewer ? 3: 1); Long draftId = newsDraft.getId(); if (news == null) { news = new News(saveNewsDTO); @@ -570,29 +593,19 @@ public class NewsService { return ResultObject.failed(500, "服务器错误,请联系系统管理员!"); } } else { - Integer status = news.getStatus(); String newsInfoId = news.getNewsinfoId(); - if (status < 2) { - news = new News(saveNewsDTO); - news.setStatus(status); - news.setDraftId(draftId); - news.setNewsinfoId(newsInfoId); - try { - newsMapper.updateNews(news); - } catch (Exception e) { - log.error("更新新闻报错!", e); - return ResultObject.failed(500, "服务器错误,请联系系统管理员!"); - } - } else { - news = new News(saveNewsDTO); - news.setDraftId(draftId); - news.setNewsinfoId(newsInfoId); - try { - newsMapper.updateNews(news); - } catch (Exception e) { - log.error("更新新闻报错!", e); - return ResultObject.failed(500, "服务器错误,请联系系统管理员!"); + news = new News(saveNewsDTO); + news.setStatus(oldStatus); + news.setDraftId(draftId); + news.setNewsinfoId(newsInfoId); + try { + int count = newsMapper.updateNews(news, oldStatus); + if (count == 0) { + return ResultObject.failed("该资讯正在审核中,请勿重复操作"); } + } catch (Exception e) { + log.error("更新新闻报错!", e); + return ResultObject.failed(500, "服务器错误,请联系系统管理员!"); } } Long newsId = news.getId(); @@ -781,7 +794,7 @@ public class NewsService { public PageObject queryNews(String keyword, String columnParam, Integer status, int page, int size, Integer last, Integer current, String orderBy, Double minScore, Double maxScore, String tag, String industry, Long mediaId, - Date datelineFrom, Date datelineTo) { + Date datelineFrom, Date datelineTo, Integer deleted, Byte rating,boolean isReviewer) { String orderByClause = null; if (StringUtils.hasText(orderBy)) { String orderByStr = orderBy; @@ -800,6 +813,8 @@ public class NewsService { orderByClause = orderByClause.replace("news.score", "news_tags.news_score"); orderByClause = " IF(ISNULL(news_tags.news_score), 1, 0) asc, " + orderByClause; } + } else { + orderByClause = (deleted != null && (deleted.intValue() == 1))? "news.update_time desc":"news.publish_time desc"; } int offset = 0; if (current != null) { @@ -844,7 +859,7 @@ public class NewsService { List newsList; try { - newsList = newsMapper.queryNews(keywords, minScore, maxScore, columnParam, status, last, orderByClause, size, offset, tags, industries, datelineFrom, datelineTo); + newsList = newsMapper.queryNews(keywords, minScore, maxScore, columnParam, status, last, orderByClause, size, offset, tags, industries, datelineFrom, datelineTo, deleted, rating, isReviewer); } catch (Exception e) { log.error("搜索新闻异常!", e); return PageObject.failedPage(500, "服务器错误,请联系系统管理员!"); @@ -853,7 +868,7 @@ public class NewsService { PageObject pageObject = new PageObject<>(); if (page == 1) { try { - int total = newsMapper.queryTotal(keywords,minScore, maxScore, columnParam, status, tags, industries, datelineFrom, datelineTo); + int total = newsMapper.queryTotal(keywords,minScore, maxScore, columnParam, status, tags, industries, datelineFrom, datelineTo, deleted, rating, isReviewer); pageObject.setTotal(total); } catch (Exception e) { log.error("获取新闻总数异常!", e); @@ -958,11 +973,172 @@ public class NewsService { } return result; } - - public static void main(String[] args) { - String keyword = "中国 美国 俄罗斯"; - keyword = keyword.replaceAll("\\s+", "%"); - System.out.println(keyword); + + public ResultObject getTranslator(Long id) { + News news = newsMapper.getNewsDetail(id); + String newsInfoId = news.getNewsinfoId(); + if (ObjectUtils.isEmpty(newsInfoId)) { + return ResultObject.success(); + } + NewsTranslatorVO newsTranslatorVO = new NewsTranslatorVO(); + try { + GetResponse newsInfoResp = elasticsearchClient.get(g -> g.index(NewsInfo.INDEX_NAME).id(newsInfoId), ObjectNode.class); + if (!newsInfoResp.found()) { + return ResultObject.success(); + } + ObjectNode newsInfo = newsInfoResp.source(); + String titleEN = Optional.ofNullable(newsInfo.get("title_EN")).filter(JsonNode::isTextual).map(JsonNode::asText).orElse(null); + String contentEN = Optional.ofNullable(newsInfo.get("EN_content")).filter(JsonNode::isTextual).map(JsonNode::asText).orElse(null); + + //大模型翻译 + String title = Optional.ofNullable(newsInfo.at("/news_tags/title")).filter(JsonNode::isTextual).map(JsonNode::asText).orElse(null); + String rewriteContent = Optional.ofNullable(newsInfo.at("/news_tags/rewrite_content")).filter(JsonNode::isTextual).map(JsonNode::asText).orElse(null); + + newsTranslatorVO.setTitleEN(titleEN); + newsTranslatorVO.setContentEN(contentEN); + if (!ObjectUtils.isEmpty(title)) { + newsTranslatorVO.setTitleCN(news.getTitle()); + } + if (!ObjectUtils.isEmpty(rewriteContent)) { + newsTranslatorVO.setContentCN(news.getContent()); + } + return ResultObject.success(newsTranslatorVO); + } catch(IOException e) { + log.error("获取全量资讯详情异常!", e); + return ResultObject.failed(500, "服务器错误,请联系系统管理员!"); + } } + + public void backupDuplicatedNews(List newsList, String clusterId) { + for (News news: newsList) { + NewsDeleted newsDeleted = new NewsDeleted(news, clusterId); + newsDeletedMapper.save(newsDeleted); + } + } + /** + * 挪到service层 + * @param newsList + */ + public void deletDuplicatedNews(List newsList) { + for (News news: newsList) { + Long draftId = news.getDraftId(); + if (draftId != null) { + newsMapper.deleteDraft(draftId); + columnMapper.deleteDraft(draftId); + tagMapper.deleteDraft(draftId); + industryMapper.deleteDraft(draftId); + } + Long newsId = news.getId(); + columnMapper.deleteNews(newsId); + tagMapper.deleteNews(newsId); + industryMapper.deleteNews(newsId); + newsMapper.deleteById(newsId); + } + } + + public ResultObject recoverNews(Long id, Long adminUserId) { + News news = newsMapper.getById(id); + if (news == null) { + return ResultObject.failed("要恢复的数据不存在,请联系系统管理员!"); + } + + try { + newsMapper.recoverNews(id, adminUserId); + } catch (Exception e) { + log.error("恢复新闻异常!", e); + return ResultObject.failed(500, "服务器错误,请联系系统管理员!"); + } + return ResultObject.success(); + } + + /** + * 返回最近两天的数据 + * @param num + * @param last + * @return + */ + public ResultObject> requestNewsByApi(Integer num, Long last) { + List result = newsMapper.queryNewsByApi(last, Integer.min(num, 1000)); + + Map industryMap = industryMapper.queryAll().stream().collect(Collectors.toMap(Industry::getId, Function.identity())); + result.stream().forEach( e -> { + List newsTagRels = tagMapper.getNewsTagRelList(e.getId()).stream().map(x -> tagMapper.queryById(x.getTagId())).collect(Collectors.toList()); + + if (ObjectUtils.isEmpty(e.getSource())) { + Optional sourceTag = newsTagRels.stream().filter(x -> x != null && (x.getParentId() == 1L)).findFirst(); + if (sourceTag.isPresent()) { + e.setSource(sourceTag.get().getName()); + } + } + List conceptLabel = newsTagRels.stream().filter(t -> t != null).map(Tag::getDisplayName).collect(Collectors.toList()); + e.setConceptLabel(conceptLabel); + + List industryLabel = industryMapper.getNewsIndustryRelList(e.getId()).stream().map(x -> industryMap.get(x.getIndustryId())).filter(i -> i != null).map(n -> n.getDisplayName()).collect(Collectors.toList()); + e.setIndustryLabel(industryLabel); + }); + return ResultObject.success(result); + } + + public ResultObject submit(long id, long editorId) { + News news = newsMapper.getById(id); + if (news == null) { + log.warn("找不到ID为{}的新闻!", id); + return ResultObject.failed("找不到ID为" + id + "的新闻!"); + } + Integer oldStatus = news.getStatus(); + if (oldStatus != 1) { + return ResultObject.failed("该资讯正在审核中,请勿重复操作"); + } + + newsMapper.changeFrom(id, oldStatus, 3, editorId); + return ResultObject.success(); + } + + public ResultObject revoke(Long id, Long editorId, boolean isReviewer) { + News news = newsMapper.getById(id); + if (news == null) { + log.warn("找不到ID为{}的新闻!", id); + return ResultObject.failed("找不到ID为" + id + "的新闻!"); + } + Integer oldStatus = news.getStatus(); + if (isReviewer) { + if (oldStatus != 2) { + return ResultObject.failed("该资讯正在审核中,请勿重复操作"); + } + newsMapper.changeFrom(id, oldStatus, 3, editorId); + return ResultObject.success(); + } + + if (oldStatus != 3) { + return ResultObject.failed("该资讯正在审核中,请勿重复操作"); + } + + newsMapper.changeFrom(id, oldStatus, 1, editorId); + return ResultObject.success(); + } + + public PageObject getLog(Long id, int page, int size, Integer current) { + int offset = 0; + if (current != null) { + offset = (Math.max(0, current - 1)) * size; + } + List dataOperationLogs = userOperationLogMapper.selectByDataIdAndType(id, "news", size, offset); + List newsLogList = dataOperationLogs.stream().map(e -> new NewsLogVO(e)).collect(Collectors.toList()); + + PageObject pageObject = new PageObject<>(); + pageObject.setData(newsLogList); + pageObject.setCode(200); + pageObject.setCurrent(page); + size = Math.min(newsLogList.size(), size); + pageObject.setSize(size); + try { + int total = userOperationLogMapper.queryTotal(id, "news"); + pageObject.setTotal(total); + } catch (Exception e) { + log.error("获取新闻总数异常!", e); + return PageObject.failedPage(500, "服务器错误,请联系系统管理员!"); + } + return pageObject; + } } diff --git a/mini/pom.xml b/mini/pom.xml index ae18a82..72c4af5 100644 --- a/mini/pom.xml +++ b/mini/pom.xml @@ -35,11 +35,11 @@ - + org.elasticsearch.client elasticsearch-rest-client diff --git a/mini/src/main/java/com/jinrui/reference/mini/MiniApplication.java b/mini/src/main/java/com/jinrui/reference/mini/MiniApplication.java index d7ad2c7..5bbf433 100644 --- a/mini/src/main/java/com/jinrui/reference/mini/MiniApplication.java +++ b/mini/src/main/java/com/jinrui/reference/mini/MiniApplication.java @@ -1,14 +1,10 @@ package com.jinrui.reference.mini; import org.mybatis.spring.annotation.MapperScan; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; -import org.springframework.util.StringUtils; import org.springframework.web.client.RestTemplate; -import redis.clients.jedis.JedisPool; -import redis.clients.jedis.JedisPoolConfig; @SpringBootApplication(scanBasePackages = { "com.jinrui.reference.mini", @@ -23,17 +19,17 @@ public class MiniApplication { SpringApplication.run(MiniApplication.class, args); } - @Bean - public JedisPool jedisPool(@Value("${redis.host}") String host, - @Value("${redis.port}") int port, - @Value("${redis.timeout:1000}") int timeout, - @Value("${redis.password:}") String password) { - if (StringUtils.hasText(password)) { - JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); - return new JedisPool(jedisPoolConfig, host, port, timeout, password); - } - return new JedisPool(host, port); - } +// @Bean +// public JedisPool jedisPool(@Value("${redis.host}") String host, +// @Value("${redis.port}") int port, +// @Value("${redis.timeout:1000}") int timeout, +// @Value("${redis.password:}") String password) { +// if (StringUtils.hasText(password)) { +// JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); +// return new JedisPool(jedisPoolConfig, host, port, timeout, password); +// } +// return new JedisPool(host, port); +// } @Bean public RestTemplate restTemplate() {