资讯精选增加操作日志记录、送审、发布等功能

This commit is contained in:
sunflower2014 2025-07-22 15:47:33 +08:00
parent d291e3be72
commit 4f59c48c1c
51 changed files with 2541 additions and 271 deletions

View File

@ -35,11 +35,6 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>${jedis.version}</version>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId>
@ -50,6 +45,23 @@
<artifactId>jakarta.json-api</artifactId>
<version>${jakartajson.version}</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.19.0</version>
</dependency>
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>3.0.5</version> <!-- 或使用最新版本 -->
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext7-core</artifactId>
<version>9.2.0</version> <!-- 或使用最新版本 -->
<type>pom</type>
</dependency>
</dependencies>
<build>

View File

@ -0,0 +1,2 @@
/PDFTextSearcher.java
/PDFTextSearcherItext.java

View File

@ -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);
}
}

View File

@ -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<String, String> 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<String, String> 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<String, String> params) {
return params.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.map(e -> e.getKey() + "=" + e.getValue())
.collect(Collectors.joining("&"));
}
}

View File

@ -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();
}

View File

@ -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.<OperationInfo>getAnnotation(OperationInfo.class);
}
}

View File

@ -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<List<NewsApiVO>> 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<List<NewsApiVO>> 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<Map<String, Object>> 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<Map<String, Object>> getApiKey(@RequestHeader("auth-token") String token,
@RequestParam(name = "clientName", required = true) String clientName) {
return generateApiKey(token, clientName);
}
}

View File

@ -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")

View File

@ -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<Void> 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<Void> 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<Void> 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<Void> 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<NewsTranslatorVO> 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<NewsLogVO> 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) {
}
}
}

View File

@ -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<NewsScoreVO> 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, "服务端错误,请联系系统管理员!");
}
}
}

View File

@ -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<String> clusterIds = newsMapper.getDuplicatedCluster();
for (String clusterId: clusterIds) {
List<News> duplicatedNews = newsMapper.getDuplicatedNews(clusterId);
List<News> 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);
}
}
}
}

View File

@ -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<Query> 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<JsonNode> searchResp;
try {
searchResp = elasticsearchClient.search(request, JsonNode.class);
HitsMetadata<JsonNode> hits = searchResp.hits();
if (!ObjectUtils.isEmpty(hits)) {
for (Hit<JsonNode> 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<String> industryLabel = getIndustryLabel(newsTagsNode.get("industry_label").asText());
newsTags.setIndustryLabel(industryLabel);
List<Double> industryConfidence = doubleArrayNodeToList(newsTagsNode.get("industry_confidence"));
newsTags.setIndustryConfidence(industryConfidence);
List<Double> industryScore = doubleArrayNodeToList(newsTagsNode.get("industry_score"));
newsTags.setIndustryScore(industryScore);
List<String> conceptLabel = textArrayNodeToList(newsTagsNode.get("concept_label"));
newsTags.setConceptLabel(conceptLabel);
List<Double> conceptConfidence = doubleArrayNodeToList(newsTagsNode.get("concept_confidence"));
newsTags.setConceptConfidence(conceptConfidence);
List<Double> 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<Double> doubleArrayNodeToList(JsonNode node) {
if (node.isArray()) {
int size = ((ArrayNode)node).size();
if (size == 0) {
return Collections.emptyList();
}
List<Double> nodeValues = new ArrayList<>();
Iterator<JsonNode> 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<String> textArrayNodeToList(JsonNode node) {
if (node.isArray()) {
int size = ((ArrayNode)node).size();
if (size == 0) {
return Collections.emptyList();
}
List<String> nodeValues = new ArrayList<>();
Iterator<JsonNode> 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<String> 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
// }
}

View File

@ -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)
})

View File

@ -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");
}
}

View File

@ -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;
}
}

View File

@ -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")

View File

@ -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 {

View File

@ -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);
}
}

1
admin/src/main/resources/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/application-prod.yml

View File

@ -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

View File

@ -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

View File

@ -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`;

View File

@ -39,6 +39,10 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
@ -86,6 +90,11 @@
<artifactId>jakarta.json-api</artifactId>
<version>${jakartajson.version}</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>${jedis.version}</version>
</dependency>
</dependencies>
</project>

View File

@ -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);
}
}

View File

@ -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路径
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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<String, String[]> 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();
}
}
}

View File

@ -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<String, Object> 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);
}

View File

@ -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);
}

View File

@ -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("<script>" +
"select distinct " +
"news.id as id," +
"news.draft_id as draftId," +
"news.title as title," +
"news.llm_title as title," +
"news.status as status," +
"news.create_time as createTime," +
"news.publish_time as publishTime," +
"news.update_time as updateTime, " +
"news.newsinfo_id as newsinfoId, " +
"news.is_delete as deleted, " +
"news.rating as rating, " +
"news_tags.news_score as score " +
"from news " +
"<if test=\"column != null and !column.isEmpty()\">" +
@ -121,7 +113,7 @@ public interface NewsMapper {
"<if test=\"keywords != null and !keywords.isEmpty()\">" +
" and " +
"<foreach collection=\"keywords\" item=\"keyword\" open=\"(\" close=\")\" separator=\"or\">\n" +
" (news.title like concat('%', #{keyword}, '%') or news.content like concat('%', #{keyword}, '%')) " +
" (news.llm_title like concat('%', #{keyword}, '%') or news.llm_content like concat('%', #{keyword}, '%')) " +
"</foreach>" +
"</if>" +
"<if test=\"datelineFrom != null\">" +
@ -142,9 +134,18 @@ public interface NewsMapper {
"<if test=\"status != null\">" +
"and news.status = #{status} " +
"</if>" +
"<if test=\"deleted != null\">" +
"and news.is_delete = #{deleted} " +
"</if>" +
"<if test=\"rating != null\">" +
"and news.rating = #{rating} " +
"</if>" +
"<if test=\"last != null\">" +
"and news.id &gt; #{last}" +
"</if>" +
"<if test=\"isReviewer == true\">" +
"and news.status in (2, 3) " +
"</if>" +
"</where>" +
"<if test=\"orderBy != null\">" +
"order by ${orderBy} " +
@ -163,7 +164,10 @@ public interface NewsMapper {
@Param("tags") List<Long> tags,
@Param("industries") List<Long> 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("<script>" +
"select count(*) from (select distinct news.* from news " +
@ -192,7 +196,7 @@ public interface NewsMapper {
"<if test=\"keywords != null and !keywords.isEmpty()\">" +
" and " +
"<foreach collection=\"keywords\" item=\"keyword\" open=\"(\" close=\")\" separator=\"or\">\n" +
" (news.title like concat('%', #{keyword}, '%') or news.content like concat('%', #{keyword}, '%')) " +
" (news.llm_title like concat('%', #{keyword}, '%') or news.llm_content like concat('%', #{keyword}, '%')) " +
"</foreach>" +
"</if>" +
"<if test=\"datelineFrom != null\">" +
@ -213,6 +217,15 @@ public interface NewsMapper {
"<if test=\"status != null\">" +
"and news.status = #{status} " +
"</if>" +
"<if test=\"deleted != null\">" +
"and news.is_delete = #{deleted} " +
"</if>" +
"<if test=\"rating != null\">" +
"and news.rating = #{rating} " +
"</if>" +
"<if test=\"isReviewer == true\">" +
"and news.status in (2, 3) " +
"</if>" +
"</where>) tmp" +
"</script>")
int queryTotal(@Param("keywords") List<String> keywords,
@ -223,12 +236,74 @@ public interface NewsMapper {
@Param("tags") List<Long> tags,
@Param("industries") List<Long> 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<String> 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<News> 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("<script>" +
"select " +
"news.id as id," +
"news.llm_title," +
"news.llm_content," +
"news.summary, " +
"news.publish_time," +
"news_tags.news_score, " +
"news_tags.source, " +
"news_tags.industry_label, " +
"news_tags.industry_confidence," +
"news_tags.industry_score," +
"news_tags.concept_label," +
"news_tags.concept_confidence," +
"news_tags.concept_score," +
"news_tags.source_impact," +
"news_tags.China_factor," +
"news_tags.public_opinion_score" +
"from news " +
" left join news_tags on news.newsinfo_id = news_tags.newsinfo_id " +
"<where>" +
" news.status = 2 and news.is_delete = 0 and news.publish_time &gt;= adddate(date(now()), -2) " +
"<if test=\"last != null\">" +
"and news.id &gt; #{last}" +
"</if>" +
"</where>" +
"order by id desc " +
"limit ${limit}" +
"</script>")
List<NewsApiVO> queryNewsByApi(@Param("last") Long last, @Param("limit") int limit);
}

View File

@ -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);
}

View File

@ -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<UserOperationLog> 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);
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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());

View File

@ -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<String> industryLabel;
@JsonProperty("industry_confidence")
private List<Double> industryConfidence;
@JsonProperty("industry_score")
private List<Double> industryScore;
@JsonProperty("concept_label")
private List<String> conceptLabel;
@JsonProperty("concept_confidence")
private List<Double> conceptConfidence;
@JsonProperty("concept_score")
private List<Double> 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<Double> 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;
}

View File

@ -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;
}
}

View File

@ -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<String> industryLabel;
/**
* 行业分类置信度
*/
private List<Number> industryConfidence;
/**
* 行业分类评分
*/
private List<Number> industryScore;
/**
* 概念标签
* @return
*/
private List<String> conceptLabel;
/**
* 概念标签置信度
*/
private List<Number> conceptConfidence;
/**
* 概念标签评分
*/
private List<Number> 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<String> getIndustryLabel() {
return industryLabel;
}
public void setIndustryLabel(List<String> industryLabel) {
this.industryLabel = industryLabel;
}
public List<String> getConceptLabel() {
return conceptLabel;
}
public void setConceptLabel(List<String> conceptLabel) {
this.conceptLabel = conceptLabel;
}
public Double getNewsScore() {
return newsScore;
}
public void setNewsScore(Double newsScore) {
this.newsScore = newsScore;
}
public List<Number> getIndustryConfidence() {
return industryConfidence;
}
public void setIndustryConfidence(List<Number> industryConfidence) {
this.industryConfidence = industryConfidence;
}
public List<Number> getIndustryScore() {
return industryScore;
}
public void setIndustryScore(List<Number> industryScore) {
this.industryScore = industryScore;
}
public List<Number> getConceptConfidence() {
return conceptConfidence;
}
public void setConceptConfidence(List<Number> conceptConfidence) {
this.conceptConfidence = conceptConfidence;
}
public List<Number> getConceptScore() {
return conceptScore;
}
public void setConceptScore(List<Number> 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;
}
}

View File

@ -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<NewsDetailIndustry> 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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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<Map<String, Object>> generateApiKey(String clientName) {
Map<String, Object> 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);
}
}

View File

@ -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<Void> deleteNewsInfo(String newsInfoId) {
public ResultObject<Void> deleteNewsInfo(String newsInfoId, Long editorId) {
try {
GetResponse<NewsInfo> 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<NewsInfoVO> 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> 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("<font color='red'>")
.postTags("</font>")
@ -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<NewsScoreVO> getScore(String id) {
NewsTags newsTags = newsTagsMapper.getNewsTagsByNewsId(id);
if (newsTags == null) {
return ResultObject.success();
}
NewsScoreVO newsScoreVO = new NewsScoreVO(newsTags);
return ResultObject.success(newsScoreVO);
}
}

View File

@ -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<Void> 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<NewsDetailVO> 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<NewsDetailVO> 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<Long> set = new HashSet<>();
set.add(draftId);
@ -221,33 +243,13 @@ public class NewsService {
}
}
// List<NewsTagRel> tagRelListNews = tagMapper.getNewsTagRelList(id);
// NewsDetailTag newsDetailTag2 = new NewsDetailTag();
// newsDetailVO.setTag(newsDetailTag2);
// List<NewsDetailTagItem> 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<Long> 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<Void> createPublish(Long editorId, SaveNewsDTO saveNewsDTO) {
public ResultObject<Void> 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<Void> saveDraft(SaveNewsDTO saveNewsDTO) {
public ResultObject<Void> 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<Void> updateDraft(SaveNewsDTO saveNewsDTO) {
private ResultObject<Void> 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<Void> deleteNews(Long newsId) {
public ResultObject<Void> 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<Void> saveNewDraft(SaveNewsDTO saveNewsDTO, News news) {
private ResultObject<Void> 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<NewsVO> 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<News> 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<NewsVO> 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<NewsTranslatorVO> 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<ObjectNode> 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<News> newsList, String clusterId) {
for (News news: newsList) {
NewsDeleted newsDeleted = new NewsDeleted(news, clusterId);
newsDeletedMapper.save(newsDeleted);
}
}
/**
* 挪到service层
* @param newsList
*/
public void deletDuplicatedNews(List<News> 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<Void> 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<List<NewsApiVO>> requestNewsByApi(Integer num, Long last) {
List<NewsApiVO> result = newsMapper.queryNewsByApi(last, Integer.min(num, 1000));
Map<Long, Industry> industryMap = industryMapper.queryAll().stream().collect(Collectors.toMap(Industry::getId, Function.identity()));
result.stream().forEach( e -> {
List<Tag> newsTagRels = tagMapper.getNewsTagRelList(e.getId()).stream().map(x -> tagMapper.queryById(x.getTagId())).collect(Collectors.toList());
if (ObjectUtils.isEmpty(e.getSource())) {
Optional<Tag> sourceTag = newsTagRels.stream().filter(x -> x != null && (x.getParentId() == 1L)).findFirst();
if (sourceTag.isPresent()) {
e.setSource(sourceTag.get().getName());
}
}
List<String> conceptLabel = newsTagRels.stream().filter(t -> t != null).map(Tag::getDisplayName).collect(Collectors.toList());
e.setConceptLabel(conceptLabel);
List<String> 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<Void> 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<Void> 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<NewsLogVO> getLog(Long id, int page, int size, Integer current) {
int offset = 0;
if (current != null) {
offset = (Math.max(0, current - 1)) * size;
}
List<UserOperationLog> dataOperationLogs = userOperationLogMapper.selectByDataIdAndType(id, "news", size, offset);
List<NewsLogVO> newsLogList = dataOperationLogs.stream().map(e -> new NewsLogVO(e)).collect(Collectors.toList());
PageObject<NewsLogVO> 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;
}
}

View File

@ -35,11 +35,11 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<!-- <dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>${jedis.version}</version>
</dependency>
</dependency>-->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId>

View File

@ -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() {