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

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> </exclusion>
</exclusions> </exclusions>
</dependency> </dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>${jedis.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.elasticsearch.client</groupId> <groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId> <artifactId>elasticsearch-rest-client</artifactId>
@ -50,6 +45,23 @@
<artifactId>jakarta.json-api</artifactId> <artifactId>jakarta.json-api</artifactId>
<version>${jakartajson.version}</version> <version>${jakartajson.version}</version>
</dependency> </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> </dependencies>
<build> <build>

View File

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

View File

@ -1,14 +1,10 @@
package com.jinrui.reference.admin; package com.jinrui.reference.admin;
import org.mybatis.spring.annotation.MapperScan; import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.util.StringUtils; import org.springframework.scheduling.annotation.EnableScheduling;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
@SpringBootApplication(scanBasePackages = { @SpringBootApplication(scanBasePackages = {
"com.jinrui.reference.admin", "com.jinrui.reference.admin",
@ -17,21 +13,11 @@ import redis.clients.jedis.JedisPoolConfig;
@MapperScan({ @MapperScan({
"com.jinrui.reference.admin.mapper", "com.jinrui.reference.admin.mapper",
"com.jinrui.reference.core.mapper"}) "com.jinrui.reference.core.mapper"})
@EnableScheduling
@EnableAspectJAutoProxy
public class AdminApplication { public class AdminApplication {
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(AdminApplication.class, 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; 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.AssumeRoleRequest;
import com.aliyuncs.auth.sts.AssumeRoleResponse; import com.aliyuncs.auth.sts.AssumeRoleResponse;
import com.aliyuncs.http.MethodType; import com.aliyuncs.http.MethodType;
@ -7,19 +18,12 @@ import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile; import com.aliyuncs.profile.IClientProfile;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; 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.dto.oss.CloseableAcsClient;
import com.jinrui.reference.admin.model.entity.AdminUser; import com.jinrui.reference.admin.model.entity.AdminUser;
import com.jinrui.reference.admin.model.vo.oss.OssVO; import com.jinrui.reference.admin.model.vo.oss.OssVO;
import com.jinrui.reference.admin.service.AdminJwtService; import com.jinrui.reference.admin.service.AdminJwtService;
import com.jinrui.reference.core.model.vo.ResultObject; import com.jinrui.reference.core.model.vo.ResultObject;
import com.jinrui.reference.core.service.NewsInfoService; 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 @RestController
@RequestMapping("/common") @RequestMapping("/common")

View File

@ -1,6 +1,7 @@
package com.jinrui.reference.admin.controller; package com.jinrui.reference.admin.controller;
import java.util.Date; import java.util.Date;
import java.util.List;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -8,6 +9,7 @@ import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping; 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.PostMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader; 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 org.springframework.web.bind.annotation.RestController;
import com.fasterxml.jackson.databind.ObjectMapper; 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.dto.news.PublishNewsDTO;
import com.jinrui.reference.admin.model.entity.AdminUser; import com.jinrui.reference.admin.model.entity.AdminUser;
import com.jinrui.reference.admin.service.AdminJwtService; 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.PageObject;
import com.jinrui.reference.core.model.vo.ResultObject; 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.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.NewsScoreVO;
import com.jinrui.reference.core.model.vo.news.NewsTranslatorVO;
import com.jinrui.reference.core.model.vo.news.NewsVO; import com.jinrui.reference.core.model.vo.news.NewsVO;
import com.jinrui.reference.core.service.NewsService; import com.jinrui.reference.core.service.NewsService;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
@RestController @RestController
@RequestMapping("/news") @RequestMapping("/news")
public class NewsController { public class NewsController {
@ -35,15 +43,96 @@ public class NewsController {
private final NewsService newsService; private final NewsService newsService;
private final ObjectMapper objectMapper; private final ObjectMapper objectMapper;
private final JedisPool jedisPool;
public NewsController(NewsService newsService, public NewsController(NewsService newsService,
ObjectMapper objectMapper) { ObjectMapper objectMapper,
JedisPool jedisPool) {
this.newsService = newsService; 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") @PostMapping("/publish")
@OperationInfo(behavior = "发布", type = "news")
public ResultObject<Void> publish(@RequestHeader("auth-token") String token, public ResultObject<Void> publish(@RequestHeader("auth-token") String token,
@RequestBody PublishNewsDTO publishNewsDTO) { @RequestBody PublishNewsDTO publishNewsDTO) {
if (!StringUtils.hasText(token)) { if (!StringUtils.hasText(token)) {
@ -67,7 +156,10 @@ public class NewsController {
if (id == null) { if (id == null) {
return ResultObject.failed("要发布/下架的新闻ID不可为空"); return ResultObject.failed("要发布/下架的新闻ID不可为空");
} }
if (!adminUser.isReviewer()) {
return ResultObject.failed("当前操作非法,没有审核员权限!");
}
log.info("path: /news/publish, method: POST, request user id: {}, news id: {}", adminUserId, id); log.info("path: /news/publish, method: POST, request user id: {}, news id: {}", adminUserId, id);
return newsService.publish(id, adminUserId); return newsService.publish(id, adminUserId);
} catch (Exception e) { } catch (Exception e) {
@ -111,6 +203,39 @@ public class NewsController {
return ResultObject.failed("登陆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("当前用户已被封禁!请联系系统管理员!");
}
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 { try {
AdminUser adminUser = AdminJwtService.parseToken(token); AdminUser adminUser = AdminJwtService.parseToken(token);
if (adminUser == null) { if (adminUser == null) {
@ -125,7 +250,7 @@ public class NewsController {
} }
log.info("path: /news, method: DELETE, request user id: {}, news id: {}", adminUserId, id); 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) { } catch (Exception e) {
log.error("解析登陆Token出错!", e); log.error("解析登陆Token出错!", e);
return ResultObject.failed(500, "服务端错误,请联系系统管理员!"); return ResultObject.failed(500, "服务端错误,请联系系统管理员!");
@ -151,14 +276,18 @@ public class NewsController {
log.warn("当前用户已被封禁! id = {}", adminUserId); log.warn("当前用户已被封禁! id = {}", adminUserId);
return ResultObject.failed("当前用户已被封禁!请联系系统管理员!"); return ResultObject.failed("当前用户已被封禁!请联系系统管理员!");
} }
log.info("path: /news/create/publish, method: POST, request user id: {}, param: {}", log.info("path: /news/create/publish, method: POST, request user id: {}, param: {}",
adminUserId, objectMapper.writeValueAsString(saveNewsDTO)); adminUserId, objectMapper.writeValueAsString(saveNewsDTO));
boolean isSuccessed = setEditingFlag(saveNewsDTO.getId());
return newsService.createPublish(adminUserId, saveNewsDTO); if (!isSuccessed) {
return ResultObject.failed("该资讯正在审核中,请勿重复操作!");
}
return newsService.createPublish(adminUserId, saveNewsDTO, adminUser.isReviewer());
} catch (Exception e) { } catch (Exception e) {
log.error("解析登陆Token出错!", e); log.error("解析登陆Token出错!", e);
return ResultObject.failed(500, "服务端错误,请联系系统管理员!"); 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: {}", log.info("path: /news/save, method: POST, request user id: {}, param: {}",
adminUser.getId(), objectMapper.writeValueAsString(saveNewsDTO)); adminUser.getId(), objectMapper.writeValueAsString(saveNewsDTO));
return newsService.saveDraft(saveNewsDTO, adminUser.isReviewer());
} catch (Exception e) { } catch (Exception e) {
log.error("解析登陆Token出错!", e); log.error("解析登陆Token出错!", e);
return ResultObject.failed(500, "服务端错误,请联系系统管理员!"); return ResultObject.failed(500, "服务端错误,请联系系统管理员!");
} }
// 这个接口是保存那status应该是1未发布
saveNewsDTO.setStatus(1);
return newsService.saveDraft(saveNewsDTO);
} }
@GetMapping @GetMapping
@ -206,10 +333,12 @@ public class NewsController {
@RequestParam(value = "size", required = false, defaultValue = "10") int size, @RequestParam(value = "size", required = false, defaultValue = "10") int size,
@RequestParam(value = "last", required = false) Integer last, @RequestParam(value = "last", required = false) Integer last,
@RequestParam(value = "current", required = false) Integer current, @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 = "mediaId", required = false) Long mediaId,
@RequestParam(value = "dateline_from", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date datelineFrom, @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)) { if (!StringUtils.hasText(token)) {
return PageObject.failedPage("登陆Token为空!"); return PageObject.failedPage("登陆Token为空!");
@ -230,11 +359,11 @@ public class NewsController {
log.info("path: /news, method: GET, request user id: {}, keyword: {}, column: {}, status: {}, " + log.info("path: /news, method: GET, request user id: {}, keyword: {}, column: {}, status: {}, " +
"page: {}, size: {}, last: {}, current: {}, orderBy: {}, tag: {}, industry: {}", "page: {}, size: {}, last: {}, current: {}, orderBy: {}, tag: {}, industry: {}",
adminUser.getId(), keyword, columnList, 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) { } catch (Exception e) {
log.error("解析登陆Token出错!", e); log.error("解析登陆Token出错!", e);
return PageObject.failedPage(500, "服务端错误,请联系系统管理员!"); return PageObject.failedPage(500, "服务端错误,请联系系统管理员!");
} }
return newsService.queryNews(keyword, columnList, status, page, size, last, current, orderBy, minScore, maxScore, tag, industry, mediaId, datelineFrom, datelineTo);
} }
@GetMapping("/score") @GetMapping("/score")
@ -264,4 +393,97 @@ public class NewsController {
return ResultObject.failed(500, "服务端错误,请联系系统管理员!"); 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; package com.jinrui.reference.admin.controller;
import java.util.Date;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping; 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.admin.service.AdminJwtService;
import com.jinrui.reference.core.model.vo.PageObject; import com.jinrui.reference.core.model.vo.PageObject;
import com.jinrui.reference.core.model.vo.ResultObject; 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.NewsInfoDetailVO;
import com.jinrui.reference.core.model.vo.newsinfo.NewsInfoVO; import com.jinrui.reference.core.model.vo.newsinfo.NewsInfoVO;
import com.jinrui.reference.core.model.vo.newsinfo.SaveNewsInfoDTO; 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); 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) { } catch (Exception e) {
log.error("解析登陆Token出错!", e); log.error("解析登陆Token出错!", e);
return ResultObject.failed(500, "服务端错误,请联系系统管理员!"); return ResultObject.failed(500, "服务端错误,请联系系统管理员!");
@ -168,7 +172,11 @@ public class NewsInfoController {
@RequestParam(value = "size", required = false, defaultValue = "10") int size, @RequestParam(value = "size", required = false, defaultValue = "10") int size,
@RequestParam(value = "current", required = false) Integer current, @RequestParam(value = "current", required = false) Integer current,
@RequestParam(value = "orderBy", required = false, defaultValue = "inputDate") String orderBy, @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)) { if (!StringUtils.hasText(token)) {
return PageObject.failedPage("登陆Token为空!"); return PageObject.failedPage("登陆Token为空!");
@ -193,8 +201,34 @@ public class NewsInfoController {
log.error("解析登陆Token出错!", e); log.error("解析登陆Token出错!", e);
return PageObject.failedPage(500, "服务端错误,请联系系统管理员!"); 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 = "phone", property = "phone"),
@Result(column = "name", property = "name"), @Result(column = "name", property = "name"),
@Result(column = "active", property = "active"), @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 = "create_time", property = "createTime", javaType = Date.class, jdbcType = JdbcType.TIMESTAMP),
@Result(column = "update_time", property = "updateTime", 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 = "phone", property = "phone"),
@Result(column = "name", property = "name"), @Result(column = "name", property = "name"),
@Result(column = "active", property = "active"), @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 = "create_time", property = "createTime", javaType = Date.class, jdbcType = JdbcType.TIMESTAMP),
@Result(column = "update_time", property = "updateTime", 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.Date;
import java.util.Map; import java.util.Map;
import java.util.Objects;
/** /**
* 管理后台用户 * 管理后台用户
@ -32,6 +33,11 @@ public class AdminUser {
* true - 启动 | false - 禁用 * true - 启动 | false - 禁用
*/ */
private boolean active; private boolean active;
/**
* 用户类型
*/
private String userType;
/** /**
* 用户创建时间 * 用户创建时间
@ -62,6 +68,10 @@ public class AdminUser {
this.name = value.asString(); this.name = value.asString();
break; break;
} }
case "userType": {
this.userType = value.asString();
break;
}
case "createTime": { case "createTime": {
this.createTime = new Date(value.asLong()); this.createTime = new Date(value.asLong());
break; break;
@ -122,4 +132,16 @@ public class AdminUser {
public void setUpdateTime(Date updateTime) { public void setUpdateTime(Date updateTime) {
this.updateTime = 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 * 用户登陆Token
*/ */
private String token; private String token;
/**
* 用户类型
*/
private String userType;
/** /**
* 用户创建时间 * 用户创建时间
@ -49,6 +54,7 @@ public class LoginVO {
this.phone = adminUser.getPhone(); this.phone = adminUser.getPhone();
this.name = adminUser.getName(); this.name = adminUser.getName();
this.token = AdminJwtService.generateToken(adminUser); this.token = AdminJwtService.generateToken(adminUser);
this.userType = adminUser.getUserType();
this.createTime = adminUser.getCreateTime(); this.createTime = adminUser.getCreateTime();
this.updateTime = adminUser.getUpdateTime(); this.updateTime = adminUser.getUpdateTime();
} }
@ -100,4 +106,12 @@ public class LoginVO {
public void setUpdateTime(Date updateTime) { public void setUpdateTime(Date updateTime) {
this.updateTime = 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(); Long id = adminUser.getId();
String phone = adminUser.getPhone(); String phone = adminUser.getPhone();
String name = adminUser.getName(); String name = adminUser.getName();
String userType = adminUser.getUserType();
long createTime = adminUser.getCreateTime().getTime(); long createTime = adminUser.getCreateTime().getTime();
long updateTime = adminUser.getUpdateTime().getTime(); long updateTime = adminUser.getUpdateTime().getTime();
JWTCreator.Builder jwtBuilder = JWT.create(); JWTCreator.Builder jwtBuilder = JWT.create();
jwtBuilder.withClaim("id", id); jwtBuilder.withClaim("id", id);
jwtBuilder.withClaim("phone", phone); jwtBuilder.withClaim("phone", phone);
jwtBuilder.withClaim("name", name); jwtBuilder.withClaim("name", name);
jwtBuilder.withClaim("userType", userType);
jwtBuilder.withClaim("createTime", createTime); jwtBuilder.withClaim("createTime", createTime);
jwtBuilder.withClaim("updateTime", updateTime); jwtBuilder.withClaim("updateTime", updateTime);
jwtBuilder.withClaim("timestamp", System.currentTimeMillis()); jwtBuilder.withClaim("timestamp", System.currentTimeMillis());
@ -47,6 +49,7 @@ public final class AdminJwtService {
.withClaimPresence("id") .withClaimPresence("id")
.withClaimPresence("phone") .withClaimPresence("phone")
.withClaimPresence("name") .withClaimPresence("name")
.withClaimPresence("userType")
.withClaimPresence("createTime") .withClaimPresence("createTime")
.withClaimPresence("updateTime") .withClaimPresence("updateTime")
.withClaimPresence("timestamp") .withClaimPresence("timestamp")

View File

@ -1,5 +1,15 @@
package com.jinrui.reference.admin.service; 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.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.jinrui.reference.admin.mapper.AdminUserMapper; 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.admin.model.vo.login.LoginVO;
import com.jinrui.reference.core.model.vo.PageObject; import com.jinrui.reference.core.model.vo.PageObject;
import com.jinrui.reference.core.model.vo.ResultObject; 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.Jedis;
import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPool;
import java.util.List;
import java.util.Objects;
import java.util.Random;
import java.util.stream.Collectors;
@Service @Service
public class AdminUserService { 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: logging:
level: level:
root: DEBUG 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: spring:
datasource: datasource:
type: com.zaxxer.hikari.HikariDataSource type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver 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 username: root
password: Aa123456@ password: Xgf_8000
redis: redis:
host: 127.0.0.1 # host: 192.168.0.172
# port: 6379
# password: Xgf_redis
host: 123.60.153.169
port: 6379 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: server:
port: 13579 port: 13579
compression: compression:
@ -36,7 +39,7 @@ elasticsearch:
scheme: http scheme: http
# 111.13.176.3 部署地址 # 111.13.176.3 部署地址
# 10.127.2.194 本地测试地址 # 10.127.2.194 本地测试地址
host: 10.127.2.194 host: 111.13.176.3
port: 9200 port: 9200
enable: true enable: true
username: elastic 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> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId> <artifactId>spring-boot-starter-web</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency> <dependency>
<groupId>com.mysql</groupId> <groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId> <artifactId>mysql-connector-j</artifactId>
@ -86,6 +90,11 @@
<artifactId>jakarta.json-api</artifactId> <artifactId>jakarta.json-api</artifactId>
<version>${jakartajson.version}</version> <version>${jakartajson.version}</version>
</dependency> </dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>${jedis.version}</version>
</dependency>
</dependencies> </dependencies>
</project> </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; package com.jinrui.reference.core;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry; 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 org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import com.jinrui.reference.core.interceptor.ApiAuthInterceptor;
@Configuration @Configuration
public class WebConfig implements WebMvcConfigurer { public class WebConfig implements WebMvcConfigurer {
@ -18,4 +24,15 @@ public class WebConfig implements WebMvcConfigurer {
.allowedMethods("GET", "POST", "DELETE", "PUT", "PATCH") .allowedMethods("GET", "POST", "DELETE", "PUT", "PATCH")
.maxAge(3600); .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.Insert;
import org.apache.ibatis.annotations.Options; import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Param; 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.Select;
import org.apache.ibatis.annotations.Update; import org.apache.ibatis.annotations.Update;
import com.jinrui.reference.core.model.entity.News; import com.jinrui.reference.core.model.entity.News;
import com.jinrui.reference.core.model.entity.NewsDraft; 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 { public interface NewsMapper {
@Delete("delete from news where id = #{newsId}") @Delete("update news set is_delete = 1, update_time = now(), editor_id = #{editorId} where id = #{newsId} and status = 1")
void deleteNews(@Param("newsId") Long newsId); 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}") @Delete("delete from news_draft where id = #{draftId}")
void deleteDraft(@Param("draftId") Long draftId); void deleteDraft(@Param("draftId") Long draftId);
@ -27,75 +34,60 @@ public interface NewsMapper {
"from news_draft where id = #{id}") "from news_draft where id = #{id}")
NewsDraft getDraftDetail(@Param("id") Long 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, " + "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}") "from news where id = #{id}")
News getNewsDetail(@Param("id") Long id); News getNewsDetail(@Param("id") Long id);
@Update("update news set status = 1, update_time = now() where id = #{id}") @Update("update news set status = #{newStatus}, update_time = now(), editor_id = #{editorId} where id = #{id} and status = #{oldStatus}")
void simpleUnpublish(@Param("id") long id); int changeFrom(@Param("id") long id, @Param("oldStatus") int oldStatus, @Param("newStatus") int newStatus, @Param("editorId") long editorId);
@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 " + @Update("update news " +
"set draft_id = #{draftId}," + "set editor_id = #{news.editorId}," +
"editor_id = #{editorId}," + "draft_id = #{news.draftId}, " +
"title = #{title}," + "llm_title = #{news.title}," +
"summary = #{summary}," + "summary = #{news.summary}," +
"picture = #{picture}," + "picture = #{news.picture}," +
"content = #{content}," + "llm_content = #{news.content}," +
"content_text = #{contentText}," + "content_text = #{news.contentText}," +
"status = #{status}," + "status = #{news.status}," +
"newsinfo_id = #{newsinfoId}," + "newsinfo_id = #{news.newsinfoId}," +
"update_time = now() " + "rating = #{news.rating}," +
"where id = #{id}") "revision = #{news.revision}," +
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}," +
"update_time = now()" + "update_time = now()" +
"where id = #{id}") "where id = #{news.id} and status = #{oldStatus}")
void publishNews(News news); 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); News getById(@Param("id") Long id);
@Select("select last_insert_id()")
Long getLastInsertId();
@Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id") @Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
@Insert("insert into news_draft(title, summary, picture, type, content, create_time, update_time)" + @Insert("insert into news_draft(title, summary, picture, type, content, create_time, update_time)" +
"values (#{title}, #{summary}, #{picture}, #{type}, #{content}, now(), now())") "values (#{title}, #{summary}, #{picture}, #{type}, #{content}, now(), now())")
void saveDraft(NewsDraft newsDraft); void saveDraft(NewsDraft newsDraft);
@Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id") @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)" + @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})") "values (#{draftId}, #{title}, #{summary}, #{picture}, #{type}, #{content}, now(), now(), #{status}, #{publishTime}, #{contentText}, #{newsinfoId}, #{llmTitle}, #{llmContent}, #{rating})")
void saveNews(News news); void saveNews(News news);
@Select("SELECT MAX(id) AS max_id FROM news")
Long getNewsLastInsertId();
@Select("<script>" + @Select("<script>" +
"select distinct " + "select distinct " +
"news.id as id," + "news.id as id," +
"news.draft_id as draftId," + "news.draft_id as draftId," +
"news.title as title," + "news.llm_title as title," +
"news.status as status," + "news.status as status," +
"news.create_time as createTime," + "news.create_time as createTime," +
"news.publish_time as publishTime," + "news.publish_time as publishTime," +
"news.update_time as updateTime, " + "news.update_time as updateTime, " +
"news.newsinfo_id as newsinfoId, " + "news.newsinfo_id as newsinfoId, " +
"news.is_delete as deleted, " +
"news.rating as rating, " +
"news_tags.news_score as score " + "news_tags.news_score as score " +
"from news " + "from news " +
"<if test=\"column != null and !column.isEmpty()\">" + "<if test=\"column != null and !column.isEmpty()\">" +
@ -121,7 +113,7 @@ public interface NewsMapper {
"<if test=\"keywords != null and !keywords.isEmpty()\">" + "<if test=\"keywords != null and !keywords.isEmpty()\">" +
" and " + " and " +
"<foreach collection=\"keywords\" item=\"keyword\" open=\"(\" close=\")\" separator=\"or\">\n" + "<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>" + "</foreach>" +
"</if>" + "</if>" +
"<if test=\"datelineFrom != null\">" + "<if test=\"datelineFrom != null\">" +
@ -142,9 +134,18 @@ public interface NewsMapper {
"<if test=\"status != null\">" + "<if test=\"status != null\">" +
"and news.status = #{status} " + "and news.status = #{status} " +
"</if>" + "</if>" +
"<if test=\"deleted != null\">" +
"and news.is_delete = #{deleted} " +
"</if>" +
"<if test=\"rating != null\">" +
"and news.rating = #{rating} " +
"</if>" +
"<if test=\"last != null\">" + "<if test=\"last != null\">" +
"and news.id &gt; #{last}" + "and news.id &gt; #{last}" +
"</if>" + "</if>" +
"<if test=\"isReviewer == true\">" +
"and news.status in (2, 3) " +
"</if>" +
"</where>" + "</where>" +
"<if test=\"orderBy != null\">" + "<if test=\"orderBy != null\">" +
"order by ${orderBy} " + "order by ${orderBy} " +
@ -163,7 +164,10 @@ public interface NewsMapper {
@Param("tags") List<Long> tags, @Param("tags") List<Long> tags,
@Param("industries") List<Long> industries, @Param("industries") List<Long> industries,
@Param("datelineFrom") Date datelineFrom, @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("<script>" +
"select count(*) from (select distinct news.* from news " + "select count(*) from (select distinct news.* from news " +
@ -192,7 +196,7 @@ public interface NewsMapper {
"<if test=\"keywords != null and !keywords.isEmpty()\">" + "<if test=\"keywords != null and !keywords.isEmpty()\">" +
" and " + " and " +
"<foreach collection=\"keywords\" item=\"keyword\" open=\"(\" close=\")\" separator=\"or\">\n" + "<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>" + "</foreach>" +
"</if>" + "</if>" +
"<if test=\"datelineFrom != null\">" + "<if test=\"datelineFrom != null\">" +
@ -213,6 +217,15 @@ public interface NewsMapper {
"<if test=\"status != null\">" + "<if test=\"status != null\">" +
"and news.status = #{status} " + "and news.status = #{status} " +
"</if>" + "</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" + "</where>) tmp" +
"</script>") "</script>")
int queryTotal(@Param("keywords") List<String> keywords, int queryTotal(@Param("keywords") List<String> keywords,
@ -223,12 +236,74 @@ public interface NewsMapper {
@Param("tags") List<Long> tags, @Param("tags") List<Long> tags,
@Param("industries") List<Long> industries, @Param("industries") List<Long> industries,
@Param("datelineFrom") Date datelineFrom, @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, " + "create_time as createTime, " +
"publish_time as publishTime, " + "publish_time as publishTime, " +
"update_time as updateTime " + "update_time as updateTime " +
"from news where newsinfo_id = #{newsinfoId}") "from news where newsinfo_id = #{newsinfoId}")
News getNewsDetailByNewsInfoId(@Param("newsinfoId") String 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; package com.jinrui.reference.core.mapper;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Result; import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results; import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select; 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.model.entity.NewsTags;
import com.jinrui.reference.core.typehandler.JsonArrayTypeHandler; import com.jinrui.reference.core.typehandler.JsonArrayTypeHandler;
@ -32,4 +34,8 @@ public interface NewsTagsMapper {
}) })
@Select("select * from news_tags where newsinfo_id = #{newsId}") @Select("select * from news_tags where newsinfo_id = #{newsId}")
NewsTags getNewsTagsByNewsId(@Param("newsId") String 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 String contentText;
private Integer status; private Integer status;
private Date publishTime; private Date publishTime;
private Byte rating;
private String revision;
public SaveNewsDTO() {} public SaveNewsDTO() {}
@ -144,4 +148,20 @@ public class SaveNewsDTO {
newsInfo.setInputDate(newsInfo.getCreateTime()); newsInfo.setInputDate(newsInfo.getCreateTime());
return newsInfo; 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; private String contentText;
/** /**
* 新闻状态 0-草稿 | 1-未发布 | 2-已发布 * 新闻状态 0-草稿 | 1-未发布 | 2-已发布 | 3-送审
*/ */
private Integer status; private Integer status;
@ -77,7 +77,17 @@ public class News {
private String newsinfoId; private String newsinfoId;
private Double score; private Double score;
private String llmTitle;
private String llmContent;
private Boolean deleted;
private Byte rating;
private String revision;
public News() {} public News() {}
public News(SaveNewsDTO saveNewsDTO) { public News(SaveNewsDTO saveNewsDTO) {
@ -90,7 +100,10 @@ public class News {
this.content = saveNewsDTO.getContent(); this.content = saveNewsDTO.getContent();
this.contentText = saveNewsDTO.getContentText(); this.contentText = saveNewsDTO.getContentText();
this.publishTime = saveNewsDTO.getPublishTime(); 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.createTime = new Date();
this.updateTime = new Date(); this.updateTime = new Date();
} }
@ -214,4 +227,44 @@ public class News {
public void setScore(Double score) { public void setScore(Double score) {
this.score = 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 CONTENT_ES_NAME = "CN_content";
public static final String SOURCE_ES_NAME = "sourcename.keyword"; public static final String SOURCE_ES_NAME = "sourcename.keyword";
public static final String DELETED_ES_NAME = "deleted.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 * 全量资讯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") @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss", timezone = "Asia/Shanghai")
private Date inputDate; private Date inputDate;
@ -137,6 +139,8 @@ public class NewsInfo {
*/ */
private Long editorId; private Long editorId;
@JsonProperty("news_tags")
private NewsTags newsTags;
public NewsInfo() {} public NewsInfo() {}
@ -315,12 +319,22 @@ public class NewsInfo {
public void setWords(Integer words) { public void setWords(Integer words) {
this.words = words; this.words = words;
} }
public NewsTags getNewsTags() {
return newsTags;
}
public void setNewsTags(NewsTags newsTags) {
this.newsTags = newsTags;
}
public News toNews() { public News toNews() {
News news = new News(); News news = new News();
news.setTitle(this.getTitle()); news.setTitle(this.getTitle());
news.setContent(this.getContent()); news.setContent(this.getContent());
news.setLlmTitle(this.getTitle());
news.setLlmContent(this.getContent());
news.setStatus(this.getStatus()); news.setStatus(this.getStatus());
news.setSummary(this.getSummary()); news.setSummary(this.getSummary());
news.setPublishTime(this.getInputDate()); news.setPublishTime(this.getInputDate());

View File

@ -3,43 +3,67 @@ package com.jinrui.reference.core.model.entity;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
public class NewsTags { public class NewsTags {
@JsonProperty("id")
private Long id; private Long id;
@JsonProperty("abstract")
private String summary; private String summary;
@JsonProperty("title")
private String title; private String title;
@JsonProperty("rewrite_content")
private String rewriteContent; private String rewriteContent;
@JsonProperty("industry_label")
private List<String> industryLabel; private List<String> industryLabel;
@JsonProperty("industry_confidence")
private List<Double> industryConfidence; private List<Double> industryConfidence;
@JsonProperty("industry_score")
private List<Double> industryScore; private List<Double> industryScore;
@JsonProperty("concept_label")
private List<String> conceptLabel; private List<String> conceptLabel;
@JsonProperty("concept_confidence")
private List<Double> conceptConfidence; private List<Double> conceptConfidence;
@JsonProperty("concept_score")
private List<Double> conceptScore; private List<Double> conceptScore;
@JsonProperty("source")
private String source; private String source;
@JsonProperty("source_impact")
private Integer sourceImpact; private Integer sourceImpact;
@JsonProperty("China_factor")
private Double chinaFactor; private Double chinaFactor;
@JsonProperty("public_opinion_score")
private Integer publicOpinionScore; private Integer publicOpinionScore;
@JsonProperty("news_score")
private Double newsScore; private Double newsScore;
@JsonProperty("news_id")
private Long newsId; 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; 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; private Date updateTime;
public List<Double> getIndustryScore() { public List<Double> getIndustryScore() {
@ -170,11 +194,11 @@ public class NewsTags {
this.newsId = newsId; this.newsId = newsId;
} }
public Boolean getDeleted() { public Integer getDeleted() {
return deleted; return deleted;
} }
public void setDeleted(Boolean deleted) { public void setDeleted(Integer deleted) {
this.deleted = 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") @SuppressWarnings("unused")
public class NewsDetailVO { 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 Long id;
private String title; private String title; // 标题
private String summary; private String summary;
private String picture; private String picture;
@ -23,10 +27,22 @@ public class NewsDetailVO {
private List<NewsDetailIndustry> industry; private List<NewsDetailIndustry> industry;
private String content; private String content; // 正文
private Date publishTime; private Date publishTime;
private Boolean titleTranslated; // 大模型翻译标识
private Boolean contentTranslated; // 大模型翻译标识
private Boolean deleted; // 删除标识
private String newsInfoId; // 获取原文和参考译文的字段
private Byte rating; // 打分
private String revision; // 修订说明
public NewsDetailVO() { public NewsDetailVO() {
} }
@ -37,6 +53,10 @@ public class NewsDetailVO {
this.picture = news.getPicture(); this.picture = news.getPicture();
this.content = news.getContent(); this.content = news.getContent();
this.publishTime = news.getPublishTime(); this.publishTime = news.getPublishTime();
this.deleted = news.getDeleted();
this.newsInfoId = news.getNewsinfoId();
this.rating = news.getRating();
this.revision = news.getRevision();
} }
public NewsDetailVO(NewsDraft newsDraft) { public NewsDetailVO(NewsDraft newsDraft) {
@ -117,5 +137,68 @@ public class NewsDetailVO {
public void setPublishTime(Date publishTime) { public void setPublishTime(Date publishTime) {
this.publishTime = 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 Date updateTime;
private Double score; 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) { public NewsVO(News news) {
this.id = news.getId(); this.id = news.getId();
@ -62,6 +74,8 @@ public class NewsVO {
this.publishTime = news.getPublishTime(); this.publishTime = news.getPublishTime();
this.updateTime = news.getUpdateTime(); this.updateTime = news.getUpdateTime();
this.score = news.getScore(); this.score = news.getScore();
this.deleted = news.getDeleted();
this.rating = news.getRating();
} }
public Long getId() { public Long getId() {
@ -123,4 +137,12 @@ public class NewsVO {
public void setScore(Double score) { public void setScore(Double score) {
this.score = 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; private Long editorId;
public NewsInfoVO(NewsInfo newsInfo) { /**
* 资讯评分
*/
private Double score;
public NewsInfoVO(NewsInfo newsInfo) {
this.id = newsInfo.getId(); this.id = newsInfo.getId();
this.title = newsInfo.getTitle(); this.title = newsInfo.getTitle();
this.summary = newsInfo.getSummary(); this.summary = newsInfo.getSummary();
@ -86,6 +91,9 @@ public class NewsInfoVO {
this.createTime = newsInfo.getCreateTime(); this.createTime = newsInfo.getCreateTime();
this.updateTime = newsInfo.getUpdateTime(); this.updateTime = newsInfo.getUpdateTime();
this.inputDate = newsInfo.getInputDate(); this.inputDate = newsInfo.getInputDate();
if (newsInfo.getNewsTags() != null) {
this.score = newsInfo.getNewsTags().getNewsScore();
}
} }
public String getId() { public String getId() {
@ -191,4 +199,12 @@ public class NewsInfoVO {
public void setContent(String content) { public void setContent(String content) {
this.content = 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 news = new News();
news.setTitle(this.getTitle()); news.setTitle(this.getTitle());
news.setSummary(this.getSummary()); news.setSummary(this.getSummary());
news.setLlmTitle(this.getTitle());
news.setLlmContent(this.getContent());
news.setStatus(this.getStatus()); news.setStatus(this.getStatus());
news.setContent(this.getContent()); news.setContent(this.getContent());
news.setCreateTime(new Date()); news.setCreateTime(new Date());
news.setUpdateTime(news.getCreateTime()); news.setUpdateTime(news.getCreateTime());
news.setType(1); news.setType(1);
news.setNewsinfoId(this.getId()); news.setNewsinfoId(this.getId());
// 报道时间
if (this.getPublishTime() != null) {
news.setPublishTime(this.getPublishTime());
} else {
news.setPublishTime(news.getCreateTime());
}
return news; 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; package com.jinrui.reference.core.service;
import java.io.IOException; import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -13,7 +17,6 @@ import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.jinrui.reference.core.mapper.IndustryMapper; 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.NewsDetailIndustry;
import com.jinrui.reference.core.model.vo.news.NewsDetailTag; 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.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.NewsInfoDetailVO;
import com.jinrui.reference.core.model.vo.newsinfo.NewsInfoVO; import com.jinrui.reference.core.model.vo.newsinfo.NewsInfoVO;
import com.jinrui.reference.core.model.vo.newsinfo.SaveNewsInfoDTO; 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.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.ElasticsearchException; import co.elastic.clients.elasticsearch._types.ElasticsearchException;
import co.elastic.clients.elasticsearch._types.Refresh; 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.SortOrder;
import co.elastic.clients.elasticsearch._types.query_dsl.MatchQuery; 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.MultiMatchQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.Operator; 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.Query;
import co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders; 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._types.query_dsl.TermQuery;
import co.elastic.clients.elasticsearch.core.GetResponse; import co.elastic.clients.elasticsearch.core.GetResponse;
import co.elastic.clients.elasticsearch.core.IndexResponse; 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.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Hit; import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.core.search.HitsMetadata; import co.elastic.clients.elasticsearch.core.search.HitsMetadata;
import co.elastic.clients.json.JsonpUtils; import co.elastic.clients.json.JsonData;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
@Service @Service
public class NewsInfoService { public class NewsInfoService {
@ -104,6 +109,14 @@ public class NewsInfoService {
News relNews = newsInfo.toNews(); News relNews = newsInfo.toNews();
relNews.setStatus(1); relNews.setStatus(1);
relNews.setEditorId(editorId); 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); newsMapper.saveNews(relNews);
saveNewsRel(id, relNews.getId()); saveNewsRel(id, relNews.getId());
} }
@ -336,6 +349,7 @@ public class NewsInfoService {
News relateNews = newsMapper.getNewsDetailByNewsInfoId(id); News relateNews = newsMapper.getNewsDetailByNewsInfoId(id);
if (relateNews == null) { if (relateNews == null) {
saveNewsInfoDTO.setPublishTime(newsInfo.getInputDate()); // 报道时间
relateNews = createRelateNews(saveNewsInfoDTO); relateNews = createRelateNews(saveNewsInfoDTO);
NewsInfo updatedNewsInfo = toNewsInfo(relateNews.getId(), 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); 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(); return ResultObject.success();
} }
public ResultObject<Void> deleteNewsInfo(String newsInfoId) { public ResultObject<Void> deleteNewsInfo(String newsInfoId, Long editorId) {
try { try {
GetResponse<NewsInfo> getResp = elasticsearchClient.get(e -> e.index(NewsInfo.INDEX_NAME).id(String.valueOf(newsInfoId)), NewsInfo.class); GetResponse<NewsInfo> getResp = elasticsearchClient.get(e -> e.index(NewsInfo.INDEX_NAME).id(String.valueOf(newsInfoId)), NewsInfo.class);
if (getResp.found()) { if (getResp.found()) {
@ -359,7 +373,7 @@ public class NewsInfoService {
Long newsId = newsInfo.getNewsId(); Long newsId = newsInfo.getNewsId();
// 删除资讯精选中关联的数据 // 删除资讯精选中关联的数据
deleteNewsRel(newsId); deleteNewsRel(newsId);
newsMapper.deleteNews(newsId); newsMapper.deleteNews(newsId, editorId);
NewsInfo deletedNewsInfo = new NewsInfo(); NewsInfo deletedNewsInfo = new NewsInfo();
deletedNewsInfo.setDeleted(1); deletedNewsInfo.setDeleted(1);
@ -385,7 +399,7 @@ public class NewsInfoService {
try { try {
IndexResponse resp = elasticsearchClient.index(c -> c.index(NewsInfo.INDEX_NAME).document(newsInfo).refresh(Refresh.True)); IndexResponse resp = elasticsearchClient.index(c -> c.index(NewsInfo.INDEX_NAME).document(newsInfo).refresh(Refresh.True));
news.setNewsinfoId(resp.id()); news.setNewsinfoId(resp.id());
newsMapper.updateNews(news); newsMapper.updateNews(news, news.getStatus());
} catch(IOException|ElasticsearchException e) { } catch(IOException|ElasticsearchException e) {
log.error("新建全量资讯出错!", e); log.error("新建全量资讯出错!", e);
return ResultObject.failed(500, "服务器错误,请联系系统管理员!"); return ResultObject.failed(500, "服务器错误,请联系系统管理员!");
@ -419,7 +433,7 @@ public class NewsInfoService {
private void updateRelateNews(Long newsId, SaveNewsInfoDTO saveNewsDTO) { private void updateRelateNews(Long newsId, SaveNewsInfoDTO saveNewsDTO) {
News news = saveNewsDTO.toNews(); News news = saveNewsDTO.toNews();
news.setId(newsId); news.setId(newsId);
newsMapper.updateNews(news); newsMapper.updateNews(news, news.getStatus());
tagMapper.deleteNews(newsId); tagMapper.deleteNews(newsId);
industryMapper.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, 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)) { final String orderByField = mappingToEsFieldName(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;
int offset = 0; int offset = 0;
if (current != null) { if (current != null) {
offset = (Math.max(0, current - 1)) * size; offset = (Math.max(0, current - 1)) * size;
@ -542,18 +536,34 @@ public class NewsInfoService {
filters.add(sourcenameQuery); filters.add(sourcenameQuery);
} }
} }
// 评分范围查询
if (conditions.size() == 0) { Query scoreRangeQuery = buildRangeQuery(NewsInfo.SCORE_ES_NAME, minScore, maxScore);
conditions.add(QueryBuilders.matchAll().build()._toQuery()); 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(); 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); 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 -> SearchRequest request = SearchRequest.of(s ->
s.from(from) s.index(NewsInfo.INDEX_NAME).from(from)
.size(size) // 分页参数 .size(size) // 分页参数
.trackTotalHits(e -> e.enabled(true)) .trackTotalHits(e -> e.enabled(true))
.sort(so -> so.field(f -> f.field(orderByField).order(sortOrder))) // 排序字段 .sort(sortOptions) // 排序字段
.query(resultQuery).trackScores(true) .query(resultQuery).trackScores(true)
.highlight(h -> h.preTags("<font color='red'>") .highlight(h -> h.preTags("<font color='red'>")
.postTags("</font>") .postTags("</font>")
@ -592,4 +602,73 @@ public class NewsInfoService {
pageObject.setData(newsInfoList); pageObject.setData(newsInfoList);
return pageObject; 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.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -22,15 +23,18 @@ import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import com.aliyun.oss.common.utils.DateUtil;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; 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.ColumnMapper;
import com.jinrui.reference.core.mapper.IndustryMapper; 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.NewsMapper;
import com.jinrui.reference.core.mapper.NewsTagsMapper; import com.jinrui.reference.core.mapper.NewsTagsMapper;
import com.jinrui.reference.core.mapper.TagMapper; 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.SaveDraftColumn;
import com.jinrui.reference.core.model.dto.news.SaveDraftColumnItem; import com.jinrui.reference.core.model.dto.news.SaveDraftColumnItem;
import com.jinrui.reference.core.model.dto.news.SaveDraftColumnVip; 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.Industry;
import com.jinrui.reference.core.model.entity.News; import com.jinrui.reference.core.model.entity.News;
import com.jinrui.reference.core.model.entity.NewsColumnRel; 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.NewsDraft;
import com.jinrui.reference.core.model.entity.NewsIndustryRel; import com.jinrui.reference.core.model.entity.NewsIndustryRel;
import com.jinrui.reference.core.model.entity.NewsInfo; import com.jinrui.reference.core.model.entity.NewsInfo;
import com.jinrui.reference.core.model.entity.NewsTagRel; import com.jinrui.reference.core.model.entity.NewsTagRel;
import com.jinrui.reference.core.model.entity.NewsTags; import com.jinrui.reference.core.model.entity.NewsTags;
import com.jinrui.reference.core.model.entity.Tag; 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.PageObject;
import com.jinrui.reference.core.model.vo.ResultObject; import com.jinrui.reference.core.model.vo.ResultObject;
import com.jinrui.reference.core.model.vo.column.ColumnVO; 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.NewsDetailColumn;
import com.jinrui.reference.core.model.vo.news.NewsDetailColumnVip; 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.NewsDetailIndustry;
import com.jinrui.reference.core.model.vo.news.NewsDetailTag; 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.NewsDetailTagItem;
import com.jinrui.reference.core.model.vo.news.NewsDetailVO; 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.NewsScoreVO;
import com.jinrui.reference.core.model.vo.news.NewsTranslatorVO;
import com.jinrui.reference.core.model.vo.news.NewsVO; import com.jinrui.reference.core.model.vo.news.NewsVO;
import co.elastic.clients.elasticsearch.ElasticsearchClient; import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.Refresh; import co.elastic.clients.elasticsearch._types.Refresh;
import co.elastic.clients.elasticsearch.core.GetResponse;
@Service @Service
public class NewsService { public class NewsService {
@ -76,6 +86,8 @@ public class NewsService {
private final IndustryMapper industryMapper; private final IndustryMapper industryMapper;
private final ElasticsearchClient elasticsearchClient; private final ElasticsearchClient elasticsearchClient;
private final NewsTagsMapper newsTagsMapper; private final NewsTagsMapper newsTagsMapper;
private final NewsDeletedMapper newsDeletedMapper;
private final UserOperationLogMapper userOperationLogMapper;
public NewsService(NewsMapper newsMapper, public NewsService(NewsMapper newsMapper,
ColumnMapper columnMapper, ColumnMapper columnMapper,
@ -83,7 +95,9 @@ public class NewsService {
TagMapper tagMapper, TagMapper tagMapper,
IndustryMapper industryMapper, IndustryMapper industryMapper,
ElasticsearchClient elasticsearchClient, ElasticsearchClient elasticsearchClient,
NewsTagsMapper newsTagsMapper) { NewsTagsMapper newsTagsMapper,
NewsDeletedMapper newsDeletedMapper,
UserOperationLogMapper userOperationLogMapper) {
this.newsMapper = newsMapper; this.newsMapper = newsMapper;
this.columnMapper = columnMapper; this.columnMapper = columnMapper;
this.objectMapper = objectMapper; this.objectMapper = objectMapper;
@ -91,26 +105,30 @@ public class NewsService {
this.industryMapper = industryMapper; this.industryMapper = industryMapper;
this.elasticsearchClient = elasticsearchClient; this.elasticsearchClient = elasticsearchClient;
this.newsTagsMapper = newsTagsMapper; this.newsTagsMapper = newsTagsMapper;
this.newsDeletedMapper = newsDeletedMapper;
this.userOperationLogMapper = userOperationLogMapper;
} }
public ResultObject<Void> publish(long id, long editorId) { public ResultObject<Void> publish(long id, long editorId) {
News news = newsMapper.getById(id); News news = newsMapper.getById(id);
if (news == null) { if (news == null) {
log.warn("找不到ID为{}的新闻!", id); log.warn("找不到ID为{}的新闻!", id);
return ResultObject.failed("找不到ID为" + id + "的新闻!"); return ResultObject.failed("找不到ID为" + id + "的新闻!");
} }
Integer status = news.getStatus(); Integer oldStatus = news.getStatus();
if (status == 2) { if (oldStatus == 1) {
newsMapper.simpleUnpublish(id); return ResultObject.failed("资讯已被撤稿,请刷新列表页面!");
}
if (oldStatus == 2) {
newsMapper.changeFrom(id, oldStatus, 3, editorId);
return ResultObject.success(); return ResultObject.success();
} }
Long draftId = news.getDraftId(); Long draftId = news.getDraftId();
if (draftId == null) { if (draftId == null) {
newsMapper.simplePublish(id, editorId); newsMapper.changeFrom(id, oldStatus, 2, editorId);
return ResultObject.success(); return ResultObject.success();
} }
ResultObject<NewsDetailVO> resultObject = detail(id); ResultObject<NewsDetailVO> resultObject = detail(id);
int code = resultObject.getCode(); int code = resultObject.getCode();
NewsDetailVO newsDetailVO = resultObject.getData(); NewsDetailVO newsDetailVO = resultObject.getData();
@ -118,7 +136,7 @@ public class NewsService {
return ResultObject.failed(resultObject.getCode(), resultObject.getMsg()); return ResultObject.failed(resultObject.getCode(), resultObject.getMsg());
} }
SaveNewsDTO saveNewsDTO = new SaveNewsDTO(newsDetailVO); SaveNewsDTO saveNewsDTO = new SaveNewsDTO(newsDetailVO);
return createPublish(editorId, saveNewsDTO); return createPublish(editorId, saveNewsDTO, true);
} }
public ResultObject<NewsDetailVO> detail(Long id) { public ResultObject<NewsDetailVO> detail(Long id) {
@ -139,6 +157,10 @@ public class NewsService {
NewsDraft newsDraft = newsMapper.getDraftDetail(draftId); NewsDraft newsDraft = newsMapper.getDraftDetail(draftId);
NewsDetailVO newsDetailVO = new NewsDetailVO(newsDraft); NewsDetailVO newsDetailVO = new NewsDetailVO(newsDraft);
newsDetailVO.setId(id); 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<Long> set = new HashSet<>();
set.add(draftId); set.add(draftId);
@ -221,33 +243,13 @@ public class NewsService {
} }
} }
processLLMFlag(newsDetailVO);
// 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);
// }
return ResultObject.success(newsDetailVO); return ResultObject.success(newsDetailVO);
} }
news = newsMapper.getNewsDetail(id); news = newsMapper.getNewsDetail(id);
NewsDetailVO newsDetailVO = new NewsDetailVO(news); NewsDetailVO newsDetailVO = new NewsDetailVO(news);
processLLMFlag(newsDetailVO);
Set<Long> set = new HashSet<>(); Set<Long> set = new HashSet<>();
set.add(id); set.add(id);
@ -338,15 +340,49 @@ public class NewsService {
} }
return ResultObject.success(newsDetailVO); 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 id = saveNewsDTO.getId();
Long newsId = saveNewsDTO.getId(); Long newsId = saveNewsDTO.getId();
Integer newStatus = (isReviewer ? 2: 3);
Integer oldStatus = (isReviewer ? 3: 1);
News news; News news;
Long draftId = null;
String newsInfoId = null;
if (id != null) { if (id != null) {
news = newsMapper.getById(id); news = newsMapper.getById(id);
Long draftId = news.getDraftId(); draftId = news.getDraftId();
if (draftId != null) { newsInfoId = news.getNewsinfoId();
if (draftId != null && newStatus.intValue() == 2) {
deleteDraft(draftId); deleteDraft(draftId);
} }
} }
@ -356,14 +392,20 @@ public class NewsService {
// 已发布 // 已发布
saveNewsDTO.setStatus(2); saveNewsDTO.setStatus(2);
saveNewsDTO.setPublishTime(new Date()); saveNewsDTO.setPublishTime(new Date());
saveNewDraft(saveNewsDTO, null); saveNewDraft(saveNewsDTO, null, isReviewer);
} }
Long newIdRl = saveNewsDTO.getId(); Long newIdRl = saveNewsDTO.getId();
news = new News(saveNewsDTO); news = new News(saveNewsDTO);
news.setStatus(2); news.setStatus(newStatus);
news.setEditorId(editorId); 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 // zzp
deleteNewsRel(newsId); deleteNewsRel(newsId);
try { 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); 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(); Long id = saveNewsDTO.getId();
if (id == null) { 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(); Long id = saveNewsDTO.getId();
News news = newsMapper.getById(id); News news = newsMapper.getById(id);
Long draftId = news.getDraftId(); Long draftId = news.getDraftId();
if (draftId != null) { if (draftId != null) {
deleteDraft(draftId); 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); News news = newsMapper.getById(newsId);
Integer status = news.getStatus(); Integer status = news.getStatus();
if (status == 2) { if (status == 2) {
return ResultObject.failed("请先手动下架新闻然后进行删除!"); return ResultObject.failed("请先手动下架新闻然后进行删除!");
} }
Long draftId = news.getDraftId();
if (draftId != null) {
try {
deleteDraft(draftId);
} catch (Exception e) {
log.error("删除新闻草稿异常!", e);
return ResultObject.failed(500, "服务器错误,请联系系统管理员!");
}
}
try { try {
deleteNewsRel(newsId); int count = newsMapper.deleteNews(newsId, editorId);
} catch (Exception e) { if (count == 0) {
log.error("删除新闻栏目标签异常!", e); return ResultObject.failed("该资讯正在审核中,请勿重复操作");
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);
} }
} catch (Exception e) { } catch (Exception e) {
log.error("删除新闻异常!", e); log.error("删除新闻异常!", e);
@ -549,7 +571,7 @@ public class NewsService {
industryMapper.deleteDraft(draftId); 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); NewsDraft newsDraft = new NewsDraft(saveNewsDTO);
try { try {
newsMapper.saveDraft(newsDraft); newsMapper.saveDraft(newsDraft);
@ -558,6 +580,7 @@ public class NewsService {
return ResultObject.failed(500, "服务器错误,请联系系统管理员!"); return ResultObject.failed(500, "服务器错误,请联系系统管理员!");
} }
Integer oldStatus = (isReviewer ? 3: 1);
Long draftId = newsDraft.getId(); Long draftId = newsDraft.getId();
if (news == null) { if (news == null) {
news = new News(saveNewsDTO); news = new News(saveNewsDTO);
@ -570,29 +593,19 @@ public class NewsService {
return ResultObject.failed(500, "服务器错误,请联系系统管理员!"); return ResultObject.failed(500, "服务器错误,请联系系统管理员!");
} }
} else { } else {
Integer status = news.getStatus();
String newsInfoId = news.getNewsinfoId(); String newsInfoId = news.getNewsinfoId();
if (status < 2) { news = new News(saveNewsDTO);
news = new News(saveNewsDTO); news.setStatus(oldStatus);
news.setStatus(status); news.setDraftId(draftId);
news.setDraftId(draftId); news.setNewsinfoId(newsInfoId);
news.setNewsinfoId(newsInfoId); try {
try { int count = newsMapper.updateNews(news, oldStatus);
newsMapper.updateNews(news); if (count == 0) {
} catch (Exception e) { return ResultObject.failed("该资讯正在审核中,请勿重复操作");
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, "服务器错误,请联系系统管理员!");
} }
} catch (Exception e) {
log.error("更新新闻报错!", e);
return ResultObject.failed(500, "服务器错误,请联系系统管理员!");
} }
} }
Long newsId = news.getId(); 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, public PageObject<NewsVO> queryNews(String keyword, String columnParam, Integer status, int page, int size,
Integer last, Integer current, String orderBy, Double minScore, Double maxScore, Integer last, Integer current, String orderBy, Double minScore, Double maxScore,
String tag, String industry, Long mediaId, String tag, String industry, Long mediaId,
Date datelineFrom, Date datelineTo) { Date datelineFrom, Date datelineTo, Integer deleted, Byte rating,boolean isReviewer) {
String orderByClause = null; String orderByClause = null;
if (StringUtils.hasText(orderBy)) { if (StringUtils.hasText(orderBy)) {
String orderByStr = orderBy; String orderByStr = orderBy;
@ -800,6 +813,8 @@ public class NewsService {
orderByClause = orderByClause.replace("news.score", "news_tags.news_score"); orderByClause = orderByClause.replace("news.score", "news_tags.news_score");
orderByClause = " IF(ISNULL(news_tags.news_score), 1, 0) asc, " + orderByClause; 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; int offset = 0;
if (current != null) { if (current != null) {
@ -844,7 +859,7 @@ public class NewsService {
List<News> newsList; List<News> newsList;
try { 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) { } catch (Exception e) {
log.error("搜索新闻异常!", e); log.error("搜索新闻异常!", e);
return PageObject.failedPage(500, "服务器错误,请联系系统管理员!"); return PageObject.failedPage(500, "服务器错误,请联系系统管理员!");
@ -853,7 +868,7 @@ public class NewsService {
PageObject<NewsVO> pageObject = new PageObject<>(); PageObject<NewsVO> pageObject = new PageObject<>();
if (page == 1) { if (page == 1) {
try { 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); pageObject.setTotal(total);
} catch (Exception e) { } catch (Exception e) {
log.error("获取新闻总数异常!", e); log.error("获取新闻总数异常!", e);
@ -958,11 +973,172 @@ public class NewsService {
} }
return result; return result;
} }
public static void main(String[] args) { public ResultObject<NewsTranslatorVO> getTranslator(Long id) {
String keyword = "中国 美国 俄罗斯"; News news = newsMapper.getNewsDetail(id);
keyword = keyword.replaceAll("\\s+", "%"); String newsInfoId = news.getNewsinfoId();
System.out.println(keyword); 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> </exclusion>
</exclusions> </exclusions>
</dependency> </dependency>
<dependency> <!-- <dependency>
<groupId>redis.clients</groupId> <groupId>redis.clients</groupId>
<artifactId>jedis</artifactId> <artifactId>jedis</artifactId>
<version>${jedis.version}</version> <version>${jedis.version}</version>
</dependency> </dependency>-->
<dependency> <dependency>
<groupId>org.elasticsearch.client</groupId> <groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId> <artifactId>elasticsearch-rest-client</artifactId>

View File

@ -1,14 +1,10 @@
package com.jinrui.reference.mini; package com.jinrui.reference.mini;
import org.mybatis.spring.annotation.MapperScan; import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
@SpringBootApplication(scanBasePackages = { @SpringBootApplication(scanBasePackages = {
"com.jinrui.reference.mini", "com.jinrui.reference.mini",
@ -23,17 +19,17 @@ public class MiniApplication {
SpringApplication.run(MiniApplication.class, args); SpringApplication.run(MiniApplication.class, args);
} }
@Bean // @Bean
public JedisPool jedisPool(@Value("${redis.host}") String host, // public JedisPool jedisPool(@Value("${redis.host}") String host,
@Value("${redis.port}") int port, // @Value("${redis.port}") int port,
@Value("${redis.timeout:1000}") int timeout, // @Value("${redis.timeout:1000}") int timeout,
@Value("${redis.password:}") String password) { // @Value("${redis.password:}") String password) {
if (StringUtils.hasText(password)) { // if (StringUtils.hasText(password)) {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); // JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
return new JedisPool(jedisPoolConfig, host, port, timeout, password); // return new JedisPool(jedisPoolConfig, host, port, timeout, password);
} // }
return new JedisPool(host, port); // return new JedisPool(host, port);
} // }
@Bean @Bean
public RestTemplate restTemplate() { public RestTemplate restTemplate() {