Browse Source

新增ai情感抽取服务

master
maojian 2 months ago
parent
commit
4374148fbe
  1. 40
      ai-service/.classpath
  2. 1
      ai-service/.gitignore
  3. 23
      ai-service/.project
  4. 5
      ai-service/.settings/org.eclipse.core.resources.prefs
  5. 9
      ai-service/.settings/org.eclipse.jdt.core.prefs
  6. 4
      ai-service/.settings/org.eclipse.m2e.core.prefs
  7. 195
      ai-service/pom.xml
  8. 19
      ai-service/src/main/java/com/bw/ai/Application.java
  9. 37
      ai-service/src/main/java/com/bw/ai/cache/ConfigCache.java
  10. 32
      ai-service/src/main/java/com/bw/ai/emotionextract/controller/EmotionTaskReceiveController.java
  11. 18
      ai-service/src/main/java/com/bw/ai/emotionextract/service/EmotionExtractService.java
  12. 14
      ai-service/src/main/java/com/bw/ai/emotionextract/service/EmotionTaskReceiveService.java
  13. 160
      ai-service/src/main/java/com/bw/ai/emotionextract/service/impl/EmotionExtractServiceImpl.java
  14. 50
      ai-service/src/main/java/com/bw/ai/emotionextract/service/impl/EmotionTaskReceiveServiceImpl.java
  15. 38
      ai-service/src/main/java/com/bw/ai/entity/AppResultDoc.java
  16. 110
      ai-service/src/main/java/com/bw/ai/entity/Constants.java
  17. 219
      ai-service/src/main/java/com/bw/ai/handler/MainHandler.java
  18. 39
      ai-service/src/main/java/com/bw/ai/textextract/controller/TextTaskReceiveController.java
  19. 19
      ai-service/src/main/java/com/bw/ai/textextract/service/TextExtractService.java
  20. 17
      ai-service/src/main/java/com/bw/ai/textextract/service/TextTaskReceiveService.java
  21. 139
      ai-service/src/main/java/com/bw/ai/textextract/service/impl/TextExtractServiceImpl.java
  22. 55
      ai-service/src/main/java/com/bw/ai/textextract/service/impl/TextTaskReceiveServiceImpl.java
  23. 48
      ai-service/src/main/java/com/bw/ai/utils/DataUtil.java
  24. 177
      ai-service/src/main/java/com/bw/ai/utils/DateUtil.java
  25. 1004
      ai-service/src/main/java/com/bw/ai/utils/DownLoadUtil.java
  26. 27
      ai-service/src/main/java/com/bw/ai/utils/EncryptionUtil.java
  27. 104
      ai-service/src/main/java/com/bw/ai/utils/FileUtil.java
  28. 99
      ai-service/src/main/java/com/bw/ai/utils/GPTResultParseUtil.java
  29. 33
      ai-service/src/main/java/com/bw/ai/utils/OtherUtils.java
  30. 18
      ai-service/src/main/java/com/bw/ai/utils/QueueUtil.java
  31. 23
      ai-service/src/main/java/com/bw/ai/utils/ThrowMessageUtil.java
  32. 52
      ai-service/src/main/resources/bootstrap.yml
  33. 26
      ai-service/src/main/resources/emotionPrompt
  34. 36
      ai-service/src/main/resources/logback-spring.xml
  35. 30
      ai-service/src/main/resources/moodPrompt
  36. 38
      ai-service/src/main/resources/opinionPrompt
  37. 35
      ai-service/src/main/resources/prompt
  38. 1
      ocr-service/src/main/java/com/bw/ocr/handler/MainHandler.java
  39. 1
      pom.xml
  40. 1
      translate-service/src/main/java/com/bw/translate/handler/MainHandler.java
  41. 3
      translate-service/src/main/java/com/bw/translate/service/impl/TranslateTaskServiceImpl.java

40
ai-service/.classpath

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
<attribute name="optional" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
<attribute name="optional" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

1
ai-service/.gitignore

@ -0,0 +1 @@
/target/

23
ai-service/.project

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>ai-service</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

5
ai-service/.settings/org.eclipse.core.resources.prefs

@ -0,0 +1,5 @@
eclipse.preferences.version=1
encoding//src/main/java=UTF-8
encoding//src/main/resources=UTF-8
encoding//src/test/java=UTF-8
encoding/<project>=UTF-8

9
ai-service/.settings/org.eclipse.jdt.core.prefs

@ -0,0 +1,9 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.methodParameters=generate
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore
org.eclipse.jdt.core.compiler.release=disabled
org.eclipse.jdt.core.compiler.source=1.8

4
ai-service/.settings/org.eclipse.m2e.core.prefs

@ -0,0 +1,4 @@
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1

195
ai-service/pom.xml

@ -0,0 +1,195 @@
<?xml version="1.0"?>
<project
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.bw</groupId>
<artifactId>opai-service-center</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>com.bw</groupId>
<artifactId>ai-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>ai-service</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.17</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.3</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
<version>4.5.13</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.9.3</version>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.poi/poi -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
<version>4.1.2</version>
</dependency>
<!-- 复杂 OOXML 强烈建议 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>4.1.2</version>
</dependency>
</dependencies>
<build>
<!-- <pluginManagement> --><!-- lock down plugins versions to avoid using Maven defaults (may be
moved
to parent pom) -->
<plugins>
<!-- clean lifecycle, see
https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<!-- default lifecycle, jar packaging: see
https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
<!-- site lifecycle, see
https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>3.7.1</version>
</plugin>
<plugin>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>3.0.0</version>
</plugin>
<!-- spring-boot-maven-plugin插件就是打包spring boot应用的 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.bw.ai.Application</mainClass>
<layout>ZIP</layout>
<includes>
<include>
<groupId>${project.groupId}</groupId>
<artifactId>${project.artifactId}</artifactId>
</include>
</includes>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.1.1</version>
<executions>
<execution>
<id>copy</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<type>jar</type>
<includeTypes>jar</includeTypes>
<includeScope>runtime</includeScope>
<outputDirectory>${project.build.directory}/libs</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<!-- </pluginManagement> -->
</build>
</project>

19
ai-service/src/main/java/com/bw/ai/Application.java

@ -0,0 +1,19 @@
package com.bw.ai;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 系统接口启动类
* @author jian.mao
* @date 2025年12月30日
* @description
*/
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

37
ai-service/src/main/java/com/bw/ai/cache/ConfigCache.java

@ -0,0 +1,37 @@
package com.bw.ai.cache;
import lombok.extern.slf4j.Slf4j;
import java.util.Map;
import java.util.concurrent.LinkedBlockingDeque;
/**
* @author jian.mao
* @date 2022年11月11日
* @description 静态变量类
*/
@Slf4j
public class ConfigCache {
/**启动条件**/
public static boolean isStart = true;
/*****文本抽取任务队列*****/
public static LinkedBlockingDeque<Map<String, Object>> textExtractTaskQueue = new LinkedBlockingDeque<Map<String,Object>>();
/****情感抽取任务队列*****/
public static LinkedBlockingDeque<Map<String, Object>> emotionExtractTaskQueue = new LinkedBlockingDeque<Map<String,Object>>();
/**
* 队列录入任务
* @param queue
* @param task
*/
public static void putQueue(LinkedBlockingDeque<Map<String, Object>> queue,Map<String, Object> task){
//next app 写入队列准备调出
try {
queue.put(task);
} catch (InterruptedException e) {
log.error("队列写入data失败---");
}
}
}

32
ai-service/src/main/java/com/bw/ai/emotionextract/controller/EmotionTaskReceiveController.java

@ -0,0 +1,32 @@
package com.bw.ai.emotionextract.controller;
import javax.annotation.Resource;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import com.bw.ai.emotionextract.service.EmotionTaskReceiveService;
/**
* 情感抽取控制层接口
* @author jian.mao
* @date 2026年1月16日
* @description
*/
@RestController
@RequestMapping("/emotion")
public class EmotionTaskReceiveController {
@Resource
private EmotionTaskReceiveService emotionTaskReceiveService;
@PostMapping("/put")
@ResponseBody
public String put(@RequestBody String param){
String response = emotionTaskReceiveService.put(param);
return response;
}
}

18
ai-service/src/main/java/com/bw/ai/emotionextract/service/EmotionExtractService.java

@ -0,0 +1,18 @@
package com.bw.ai.emotionextract.service;
import java.util.Map;
/**
* 情感抽取执行接口
* @author jian.mao
* @date 2026年1月16日
* @description
*/
public interface EmotionExtractService {
/**
* 情感抽取
* @param task
*/
public void emotionExtract(Map<String, Object> task);
}

14
ai-service/src/main/java/com/bw/ai/emotionextract/service/EmotionTaskReceiveService.java

@ -0,0 +1,14 @@
package com.bw.ai.emotionextract.service;
/**\
* 情感抽取服务层接口
* @author jian.mao
* @date 2026年1月16日
* @description
*/
public interface EmotionTaskReceiveService {
public String put(String param);
}

160
ai-service/src/main/java/com/bw/ai/emotionextract/service/impl/EmotionExtractServiceImpl.java

@ -0,0 +1,160 @@
package com.bw.ai.emotionextract.service.impl;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Service;
import com.alibaba.fastjson.JSONObject;
import com.bw.ai.emotionextract.service.EmotionExtractService;
import com.bw.ai.entity.AppResultDoc;
import com.bw.ai.entity.Constants;
import com.bw.ai.utils.DownLoadUtil;
import com.bw.ai.utils.FileUtil;
import com.bw.ai.utils.GPTResultParseUtil;
import lombok.extern.slf4j.Slf4j;
@Service
@Slf4j
@RefreshScope
public class EmotionExtractServiceImpl implements EmotionExtractService {
@Value("${api.module-url}")
private String moduleUrl;
@Value("${api.param.model}")
private String model;
@Value("${api.param.max-tokens}")
private Integer maxTokens;
@Value("${api.save-url}")
private String saveUrl;
@Value("${prompt.emotion.path}")
private String emotionPrompt;
@Value("${prompt.mood.path}")
private String moodPrompt;
@Value("${prompt.opinion.path}")
private String opinionPrompt;
@Value("${api.param.authorization}")
private String authorization;
@Value("${file.path-prefix}")
private String downloadFilePathPrefix;
@Override
public void emotionExtract(Map<String, Object> task) {
// TODO Auto-generated method stub
try {
int referenceType = (int) task.get(Constants.REFERENCE_TYPE);
int emotionType = (int) task.get(Constants.EMOTION_TYPE);
//获取参考内容
String reference = null;
if(referenceType == 1) {
/****referenceType==1 文本输入类型************/
reference = (String) task.get(Constants.REFERENCE);
}else {
/****referenceType==2 文件上传类型************/
reference = (String) task.get(Constants.REFERENCE);
//下载文件
String format = reference.replaceAll(".*\\.", Constants.EMPTY);
String fileName = UUID.randomUUID().toString() + "." + format;
String downloadFilePath = downloadFilePathPrefix + fileName;
DownLoadUtil.downloadFile(reference, downloadFilePath);
//删除文件
FileUtil.delFile(downloadFilePath);
reference = FileUtil.readFile(downloadFilePath);
}
//获取
String question = null;
if(emotionType == 1) {
/******type==1 情感分析类型*************/
question = FileUtil.readPrompt(emotionPrompt).replace(Constants.PRE_CONTENT_REP,reference) ;
}else if(emotionType == 2) {
/******type==2 情绪识别类型*************/
question = FileUtil.readPrompt(moodPrompt).replace(Constants.PRE_CONTENT_REP,reference) ;
}else {
/******type==3 意见提取类型*************/
question = FileUtil.readPrompt(opinionPrompt).replace(Constants.PRE_CONTENT_REP,reference) ;
}
//获取抽取子字段and prompt
Map<String, Object> res = modelRequest(question);
if(res.containsKey(Constants.CHOICES)) {
List<Map<String, Object>> choices = (List<Map<String, Object>>) res.get(Constants.CHOICES);
Map<String, Object> resMessage = (Map<String, Object>) choices.get(0).get(Constants.MESSAGE);
String modelContent = (String) resMessage.get(Constants.CONTENT);
//解析结果
Object parseGptRes = GPTResultParseUtil.parse(modelContent);
//组装页面结果
Map<String, Object> result = new HashMap<String, Object>(16);
result.put(Constants.EMOTION_TYPE, emotionType);
result.put(Constants.EMOTION_RESULT, parseGptRes);
//成功 发送结果
AppResultDoc entity = new AppResultDoc();
entity.setTaskId((String)task.get(Constants.TASKID));
entity.setAppId((Integer)task.get(Constants.ID));
long now = System.currentTimeMillis();
entity.setCreateTime(now);
entity.setResult(result);
entity.setStatus(1);
entity.setDel(0);
//回传给api服务保存
DownLoadUtil.doPost(saveUrl, JSONObject.toJSONString(entity));
}else {
throw new Exception("请求结果解析失败");
}
} catch (Throwable e) {
log.error("情感抽取任务异常。e:",e);
//失败直接发送结果
AppResultDoc entity = new AppResultDoc();
entity.setTaskId((String)task.get(Constants.TASKID));
entity.setAppId((Integer)task.get(Constants.ID));
long now = System.currentTimeMillis();
entity.setCreateTime(now);
Map<String, Object> result = new HashMap<String, Object>(16);
result.put(Constants.ERROR, "文本抽取异常");
entity.setResult(result);
entity.setStatus(2);
entity.setDel(0);
//回传给api服务保存
DownLoadUtil.doPost(saveUrl, JSONObject.toJSONString(entity));
}
}
private Map<String, Object> modelRequest(String question){
Map<String, Object> headers = new HashMap<String, Object>(16);
headers.put(Constants.AUTHORIZATION, authorization);
headers.put("Content-Type", "application/json");
Map<String, Object> param = new HashMap<String, Object>(16);
param.put(Constants.MODEL, model);
param.put(Constants.MAX_TOKENS, maxTokens);
List<Map<String, Object>> messages = new ArrayList<Map<String, Object>>();
Map<String, Object> message = new HashMap<String, Object>(16);
message.put(Constants.ROLE, Constants.USER);
message.put(Constants.CONTENT, question);
messages.add(message);
param.put(Constants.MESSAGES, messages);
log.info("请求体:{}",param);
String modelResStr = DownLoadUtil.doPost(moduleUrl, JSONObject.toJSONString(param),headers);
log.info("模型请求结果:{}",modelResStr);
JSONObject res = JSONObject.parseObject(modelResStr);
return res;
}
}

50
ai-service/src/main/java/com/bw/ai/emotionextract/service/impl/EmotionTaskReceiveServiceImpl.java

@ -0,0 +1,50 @@
package com.bw.ai.emotionextract.service.impl;
import java.util.HashMap;
import java.util.Map;
import org.springframework.stereotype.Service;
import com.alibaba.fastjson.JSONObject;
import com.bw.ai.cache.ConfigCache;
import com.bw.ai.emotionextract.service.EmotionTaskReceiveService;
import com.bw.ai.entity.Constants;
import lombok.extern.slf4j.Slf4j;
@Service
@Slf4j
public class EmotionTaskReceiveServiceImpl implements EmotionTaskReceiveService {
@Override
public String put(String param) {
Map<String, Object> response = new HashMap<>(16);
int code = 200;
String message = "success";
Map<String, Object> task = null;
try {
task = JSONObject.parseObject(param);
} catch (Exception e) {
log.error("参数结构不合法,", e);
code = 100010;
message = "参数不合法";
}
// 写入队列
try {
if(task.containsKey(Constants.TRACE) && (boolean)task.get(Constants.TRACE)){
ConfigCache.emotionExtractTaskQueue.putFirst(task);
}else{
ConfigCache.emotionExtractTaskQueue.put(task);
}
} catch (InterruptedException e) {
log.error("情感抽取任务写入队列异常,", e);
code = 100011;
message = "情感抽取任务写入队列失败";
}
response.put(Constants.CODE, code);
response.put(Constants.MESSAGE, message);
return JSONObject.toJSONString(response);
}
}

38
ai-service/src/main/java/com/bw/ai/entity/AppResultDoc.java

@ -0,0 +1,38 @@
package com.bw.ai.entity;
import java.io.Serializable;
import java.util.Map;
import lombok.Data;
/**
* ES 索引opai_app_result
* 应用执行结果文档
*
* @author jian.mao
*/
@Data
public class AppResultDoc implements Serializable {
private static final long serialVersionUID = 1L;
/** 任务ID */
private String taskId;
/** 应用id */
private Integer appId;
/** 状态 0 进行中,1成功,2失败 */
private Integer status;
/** 创建时间(毫秒时间戳) */
private Long createTime;
/** 执行结果(可索引) */
private Map<String, Object> result;
/** 逻辑删除标识:0-未删除 1-已删除 */
private Integer del;
}

110
ai-service/src/main/java/com/bw/ai/entity/Constants.java

@ -0,0 +1,110 @@
package com.bw.ai.entity;
/**
* 常量实体类
* @author jian.mao
* @date 2022年11月15日
* @description
*/
public class Constants {
/**
* 空字符串常量
*/
public static final String EMPTY = "";
/************************应用参数*************************************/
public static final String CODE = "code";
public static final String ID = "id";
public static final String MESSAGE = "message";
/******************************api使用*******************************/
public static final String CONTENT = "content";
public static final String ERROR = "error";
public static final String TRACE = "trace";
/**
* 任务id
*/
public static final String TASKID = "taskId";
/**
* 模型参数 model
*/
public static final String MODEL = "model";
/**
* 模型参数 max_tokens
*/
public static final String MAX_TOKENS = "max_tokens";
/**
* 模型参数 messages
*/
public static final String MESSAGES = "messages";
/**
* 模型参数 user
*/
public static final String USER = "user";
/**
* 模型参数 role
*/
public static final String ROLE = "role";
/**
* 模型返回结果参数 choices
*/
public static final String CHOICES = "choices";
/**
* 模型认证参数
*/
public static final String AUTHORIZATION = "authorization";
/**
* 参考资料类型
*/
public static final String REFERENCE_TYPE = "referenceType";
/**
* 情感抽取类型
*/
public static final String EMOTION_TYPE = "emotionType";
/**
* 参考资料
*/
public static final String REFERENCE = "reference";
/**
* 文本抽取规则 key
*/
public static final String PRE_RULES_REP = "{{rules}}";
/**
* 文本抽取 参考资料 key
*/
public static final String PRE_CONTENT_REP = "{{content}}";
/**
* 字段key
*/
public static final String FIELD = "field";
/**
* 抽取字段 key
*/
public static final String EXTRACT_FIELDS = "extractFields";
/**
* 情感抽取结果
*/
public static final String EMOTION_RESULT = "emotionResult";
}

219
ai-service/src/main/java/com/bw/ai/handler/MainHandler.java

@ -0,0 +1,219 @@
package com.bw.ai.handler;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson.JSONObject;
import com.bw.ai.cache.ConfigCache;
import com.bw.ai.emotionextract.service.EmotionExtractService;
import com.bw.ai.textextract.service.TextExtractService;
import com.bw.ai.utils.FileUtil;
import lombok.extern.slf4j.Slf4j;
/**
* @author jian.mao
* @date 2025年1月13日
* @description
*/
@Component
@Order(value = 1)
@Slf4j
public class MainHandler implements ApplicationRunner {
@Value("${task.textextract.task-queue-path}")
private String textExtractTaskPath;
@Value("${task.emotionextract.task-queue-path}")
private String emotionExtractTaskPath;
@Autowired
private TextExtractService textExtractService;
@Autowired
private EmotionExtractService emotionExtractService;
/***线程池参数***/
@Value("${threadPool.corePoolSize}")
private int corePoolSize;
@Value("${threadPool.maximumPoolSize}")
private int maximumPoolSize;
@Value("${threadPool.keepAliveTime}")
private long keepAliveTime;
@Value("${threadPool.queueSize}")
private int queueSize;
/**
*执行入口
*/
@Override
public void run(ApplicationArguments args) throws Exception {
/*********************************************文本抽取********************************************************/
//线程池方式
ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
keepAliveTime,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(queueSize),
new ThreadPoolExecutor.CallerRunsPolicy()
);
//文本抽取任务消费者
Thread textConsumerThread = new Thread(() -> {
while (ConfigCache.isStart) {
try {
// 从队列中获取任务
Map<String, Object> task = ConfigCache.textExtractTaskQueue.take();
// 提交给线程池执行
executor.execute(() -> textExtract(task));
} catch (InterruptedException e) {
// 恢复中断状态
Thread.currentThread().interrupt();
log.error("文本抽取任务消费线程被中断");
break;
}
}
});
textConsumerThread.start();
log.info("文本抽取任务任务消费线程启动-----");
/********************************************情感抽取********************************************************/
//情感抽取线程池方式
ThreadPoolExecutor emotionExecutor = new ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
keepAliveTime,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(queueSize),
new ThreadPoolExecutor.CallerRunsPolicy()
);
//情感抽取任务消费者
Thread emotionConsumerThread = new Thread(() -> {
while (ConfigCache.isStart) {
try {
// 从队列中获取任务
Map<String, Object> task = ConfigCache.emotionExtractTaskQueue.take();
// 提交给线程池执行
emotionExecutor.execute(() -> emotionExtract(task));
} catch (InterruptedException e) {
// 恢复中断状态
Thread.currentThread().interrupt();
log.error("情感抽取任务消费线程被中断");
break;
}
}
});
emotionConsumerThread.start();
log.info("情感抽取任务任务消费线程启动-----");
//启动加载缓存任务
readTask(textExtractTaskPath, ConfigCache.textExtractTaskQueue);
readTask(emotionExtractTaskPath, ConfigCache.emotionExtractTaskQueue);
//停止处理
waitDown();
}
/**
* 创建任务执行方法
* @param task
*/
private void textExtract(Map<String, Object> task) {
textExtractService.extract(task);
}
private void emotionExtract(Map<String, Object> task) {
emotionExtractService.emotionExtract(task);
}
/****************************************************************load******************************************************************************/
/**
* 加载文件中的任务
* @param path 文件地址
* @param queue 队列
*/
@SuppressWarnings("unchecked")
public static void readTask(String path, LinkedBlockingDeque<Map<String, Object>> queue) {
File file = new File(path);
if (file.exists()) {
List<String> tasks = null;
try {
tasks = FileUtils.readLines(file, "UTF-8");
} catch (IOException e) {
e.printStackTrace();
}
for (String taskStr : tasks) {
Map<String, Object> task = JSONObject.parseObject(taskStr);
try {
queue.put(task);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
file.delete();
}
}
/*******************************************************************stop************************************************************************/
/**
* 结束触发钩子
*/
public void waitDown() {
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
// 停止线程
ConfigCache.isStart = false;
log.info("stop-------");
writeTsskToFile();
}
});
}
/**
* 任务持久化到硬盘
*/
public void writeTsskToFile() {
while (true) {
if (ConfigCache.textExtractTaskQueue.size() > 0) {
try {
Map<String, Object> task = ConfigCache.textExtractTaskQueue.take();
FileUtil.writeFile(textExtractTaskPath, JSONObject.toJSONString(task));
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
log.info("taskQueue write is file end");
break;
}
}
while (true) {
if (ConfigCache.emotionExtractTaskQueue.size() > 0) {
try {
Map<String, Object> task = ConfigCache.emotionExtractTaskQueue.take();
FileUtil.writeFile(emotionExtractTaskPath, JSONObject.toJSONString(task));
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
log.info("taskQueue write is file end");
break;
}
}
}
}

39
ai-service/src/main/java/com/bw/ai/textextract/controller/TextTaskReceiveController.java

@ -0,0 +1,39 @@
package com.bw.ai.textextract.controller;
import javax.annotation.Resource;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import com.bw.ai.textextract.service.TextTaskReceiveService;
import lombok.extern.slf4j.Slf4j;
/**
* 文本抽取 任务接收控制层
* @author jian.mao
* @date 2025年1月14日
* @description
*/
@Controller
@RequestMapping("/task")
@Slf4j
public class TextTaskReceiveController {
@Resource
private TextTaskReceiveService textTaskReceiveService;
@PostMapping("/put")
@ResponseBody
public String put(@RequestBody String param){
String response = textTaskReceiveService.put(param);
return response;
}
@RequestMapping(value = "/hello", method = RequestMethod.GET)
@ResponseBody
public String hello(String param, String token) {
return "123";
}
}

19
ai-service/src/main/java/com/bw/ai/textextract/service/TextExtractService.java

@ -0,0 +1,19 @@
package com.bw.ai.textextract.service;
import java.util.Map;
/**
* 抽取服务层接口
* @author jian.mao
* @date 2025年2月18日
* @description
*/
public interface TextExtractService {
/**
* 抽取远端执行方法
* @param task
*/
public void extract(Map<String, Object> task);
}

17
ai-service/src/main/java/com/bw/ai/textextract/service/TextTaskReceiveService.java

@ -0,0 +1,17 @@
package com.bw.ai.textextract.service;
/**
* 任务接收服务层
* @author jian.mao
* @date 2025年1月14日
* @description
*/
public interface TextTaskReceiveService {
/**
* 任务新增
* @param dataJson
* @return
*/
public String put(String dataJson);
}

139
ai-service/src/main/java/com/bw/ai/textextract/service/impl/TextExtractServiceImpl.java

@ -0,0 +1,139 @@
package com.bw.ai.textextract.service.impl;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Service;
import com.alibaba.fastjson.JSONObject;
import com.bw.ai.entity.AppResultDoc;
import com.bw.ai.entity.Constants;
import com.bw.ai.textextract.service.TextExtractService;
import com.bw.ai.utils.DownLoadUtil;
import com.bw.ai.utils.FileUtil;
import com.bw.ai.utils.GPTResultParseUtil;
import lombok.extern.slf4j.Slf4j;
@Service
@Slf4j
@RefreshScope
public class TextExtractServiceImpl implements TextExtractService {
@Value("${api.module-url}")
private String moduleUrl;
@Value("${api.param.model}")
private String model;
@Value("${api.param.max-tokens}")
private Integer maxTokens;
@Value("${api.save-url}")
private String saveUrl;
@Value("${prompt.path}")
private String promptPath;
@Value("${api.param.authorization}")
private String authorization;
@Value("${file.path-prefix}")
private String downloadFilePathPrefix;
@Override
public void extract(Map<String, Object> task) {
// TODO Auto-generated method stub
try {
int referenceType = (int) task.get(Constants.REFERENCE_TYPE);
//获取参考内容
String reference = null;
if(referenceType == 1) {
/****referenceType==1 文本输入类型************/
reference = (String) task.get(Constants.REFERENCE);
}else {
/****referenceType==2 文件上传类型************/
reference = (String) task.get(Constants.REFERENCE);
//下载文件
String format = reference.replaceAll(".*\\.", Constants.EMPTY);
String fileName = UUID.randomUUID().toString() + "." + format;
String downloadFilePath = downloadFilePathPrefix + fileName;
DownLoadUtil.downloadFile(reference, downloadFilePath);
//删除文件
FileUtil.delFile(downloadFilePath);
reference = FileUtil.readFile(downloadFilePath);
}
//获取抽取子字段and prompt
List<Map<String, Object>> extractFields = (List<Map<String, Object>>) task.get(Constants.EXTRACT_FIELDS);
String question = FileUtil.readPrompt(promptPath).replace(Constants.PRE_RULES_REP, JSONObject.toJSONString(extractFields)).replace(Constants.PRE_CONTENT_REP,reference) ;
Map<String, Object> res = modelRequest(question);
if(res.containsKey(Constants.CHOICES)) {
List<Map<String, Object>> choices = (List<Map<String, Object>>) res.get(Constants.CHOICES);
Map<String, Object> resMessage = (Map<String, Object>) choices.get(0).get(Constants.MESSAGE);
String modelContent = (String) resMessage.get(Constants.CONTENT);
//获取返回字段
Map<String, Object> fields = new HashMap<String, Object>(16);
for (Map<String, Object> map : extractFields) {
fields.put((String)map.get(Constants.FIELD), map.get(Constants.FIELD));
}
//解析结果
Map<String, Object> parseGptRes = GPTResultParseUtil.parseGPTResult(fields, modelContent);
//成功 发送结果
AppResultDoc entity = new AppResultDoc();
entity.setTaskId((String)task.get(Constants.TASKID));
entity.setAppId((Integer)task.get(Constants.ID));
long now = System.currentTimeMillis();
entity.setCreateTime(now);
entity.setResult(parseGptRes);
entity.setStatus(1);
entity.setDel(0);
//回传给api服务保存
DownLoadUtil.doPost(saveUrl, JSONObject.toJSONString(entity));
}else {
throw new Exception("请求结果解析失败");
}
} catch (Throwable e) {
log.error("文本抽取任务异常。e:",e);
//失败直接发送结果
AppResultDoc entity = new AppResultDoc();
entity.setTaskId((String)task.get(Constants.TASKID));
entity.setAppId((Integer)task.get(Constants.ID));
long now = System.currentTimeMillis();
entity.setCreateTime(now);
Map<String, Object> result = new HashMap<String, Object>(16);
result.put(Constants.ERROR, "文本抽取异常");
entity.setResult(result);
entity.setStatus(2);
entity.setDel(0);
//回传给api服务保存
DownLoadUtil.doPost(saveUrl, JSONObject.toJSONString(entity));
}
}
private Map<String, Object> modelRequest(String question){
Map<String, Object> headers = new HashMap<String, Object>(16);
headers.put(Constants.AUTHORIZATION, authorization);
headers.put("Content-Type", "application/json");
Map<String, Object> param = new HashMap<String, Object>(16);
param.put(Constants.MODEL, model);
param.put(Constants.MAX_TOKENS, maxTokens);
List<Map<String, Object>> messages = new ArrayList<Map<String, Object>>();
Map<String, Object> message = new HashMap<String, Object>(16);
message.put(Constants.ROLE, Constants.USER);
message.put(Constants.CONTENT, question);
messages.add(message);
param.put(Constants.MESSAGES, messages);
log.info("请求体:{}",param);
String modelResStr = DownLoadUtil.doPost(moduleUrl, JSONObject.toJSONString(param),headers);
log.info("模型请求结果:{}",modelResStr);
JSONObject res = JSONObject.parseObject(modelResStr);
return res;
}
}

55
ai-service/src/main/java/com/bw/ai/textextract/service/impl/TextTaskReceiveServiceImpl.java

@ -0,0 +1,55 @@
package com.bw.ai.textextract.service.impl;
import java.util.HashMap;
import java.util.Map;
import org.springframework.stereotype.Service;
import com.alibaba.fastjson.JSONObject;
import com.bw.ai.cache.ConfigCache;
import com.bw.ai.entity.Constants;
import com.bw.ai.textextract.service.TextTaskReceiveService;
import lombok.extern.slf4j.Slf4j;
/**
* 任务接收服务层实现类
* @author jian.mao
* @date 2025年1月14日
* @description
*/
@Service
@Slf4j
public class TextTaskReceiveServiceImpl implements TextTaskReceiveService {
@Override
public String put(String dataJson) {
Map<String, Object> response = new HashMap<>(16);
int code = 200;
String message = "success";
Map<String, Object> task = null;
try {
task = JSONObject.parseObject(dataJson);
} catch (Exception e) {
log.error("参数结构不合法,", e);
code = 100010;
message = "参数不合法";
}
// 写入队列
try {
if(task.containsKey(Constants.TRACE) && (boolean)task.get(Constants.TRACE)){
ConfigCache.textExtractTaskQueue.putFirst(task);
}else{
ConfigCache.textExtractTaskQueue.put(task);
}
} catch (InterruptedException e) {
log.error("文本抽取任务写入队列异常,", e);
code = 100011;
message = "文本抽取任务写入队列失败";
}
response.put(Constants.CODE, code);
response.put(Constants.MESSAGE, message);
return JSONObject.toJSONString(response);
}
}

48
ai-service/src/main/java/com/bw/ai/utils/DataUtil.java

@ -0,0 +1,48 @@
package com.bw.ai.utils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.JSONPath;
import java.util.Map;
/**
* @author:jinming
* @className:DataUtil
* @version:1.0
* @description: 获取dataValue的值
* @Date:2023/11/1 9:54
*/
public class DataUtil {
/**
*
* @param key 传入的key
* @param dataMap 数据map
* @return 根据传入的参数进行判断解析返回正确的dataValue
*/
public static Object getValue(String key, Map dataMap) {
Object dataValue;
String isJson = "#json#";
if (key.contains(isJson)) {
//进行第一次拆分获取#json#前面的部分
String[] keySplit = key.split(isJson);
String firstDataKey = keySplit[0];
String[] firstDataKeySplit = firstDataKey.split(":");
//取出前半部分对应的JSON数据并转换为JSONObject
String dataJson = (String) dataMap.get(firstDataKeySplit[0]);
JSONObject dataJsonObject = JSON.parseObject(dataJson);
//根据key的后半部分取出对应JSONObject中的值
String firstDataKeyJson = (String) JSONPath.eval(dataJsonObject, firstDataKeySplit[1]);
String secDataKey = keySplit[1];
JSONObject firstDataJsonObject = JSON.parseObject(firstDataKeyJson);
dataValue = JSONPath.eval(firstDataJsonObject, secDataKey);
return dataValue;
}
String[] keySplit = key.split(":");
String jsonPath = keySplit[1];
String dataJson = (String) dataMap.get(keySplit[0]);
JSONObject dataJsonObject = JSON.parseObject(dataJson);
dataValue = JSONPath.eval(dataJsonObject, jsonPath);
return dataValue;
}
}

177
ai-service/src/main/java/com/bw/ai/utils/DateUtil.java

@ -0,0 +1,177 @@
package com.bw.ai.utils;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import lombok.extern.slf4j.Slf4j;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
/**
* 日期工具类
*
* @author jian.mao
* @date 2022年11月15日
* @description
*/
@Slf4j
public class DateUtil {
/**
* @return
*/
public static String getTimeStrForNow() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHH");
return sdf.format(new Date());
}
public static String getTimeStrForDay(long time) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
return sdf.format(new Date(time * 1000));
}
public static String getTimeStrForDay() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
return sdf.format(new Date());
}
public static String getDateTime() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time = sdf.format(new Date());
return time;
}
public static String getDateTime(Long timestap) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time = sdf.format(new Date(timestap));
return time;
}
public static String getDate(Long timestap) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String time = sdf.format(new Date(timestap));
return time;
}
public static String getDateTimeForMonth() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMM");
String time = sdf.format(new Date());
return time;
}
/**
* 休眠
*
* @param millis 毫秒
*/
public static void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 1. @Description:时间戳转时间
* 2. @Author: ying.zhao
* 3. @Date: 2023/3/28
*/
public static String timestampToDate(String time) {
int thirteen = 13;
int ten = 10;
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// if (time.length() == thirteen) {
if (time.length() > ten) {
return sdf.format(new Date(Long.parseLong(time)));
} else {
return sdf.format(new Date(Integer.parseInt(time) * 1000L));
}
}
public static String parseCreated(String jsonTime){
String formattedDateTime = getDateTime();
try {
// 使用fastjson解析JSON数据
JSONObject jsonObject = JSON.parseObject(jsonTime);
// 获取日期和时间的值
JSONObject dateObject = jsonObject.getJSONObject("date");
int day = dateObject.getIntValue("day");
int month = dateObject.getIntValue("month");
int year = dateObject.getIntValue("year");
JSONObject timeObject = jsonObject.getJSONObject("time");
int hour = timeObject.getIntValue("hour");
int minute = timeObject.getIntValue("minute");
int second = timeObject.getIntValue("second");
// 创建LocalDateTime对象
LocalDateTime dateTime = LocalDateTime.of(year, month, day, hour, minute, second);
// 定义日期时间格式化器
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 格式化日期时间
formattedDateTime = dateTime.format(formatter);
} catch (Exception e) {
log.info("日期转换失败:{}",e);
}
return formattedDateTime;
}
/**
* 字符串转换日期
* @param format
* @param date
* @return
*/
public static Date strToDate(String format,String date){
SimpleDateFormat sdf = new SimpleDateFormat(format);
if (date == null || date.equals("")){
return new Date();
}else{
Date ru = null;
try {
ru = sdf.parse(date);
} catch (ParseException e) {
e.printStackTrace();
}
return ru;
}
}
/**
* 日期格式话
* @param format 日期格式
* @param dater 要转换的日期,默认当前时间
* @return
*/
public static String FormatDate(String format,Date date){
String fromatDate = null;
SimpleDateFormat sdf = new SimpleDateFormat(format);
if (date == null){
fromatDate = sdf.format(new Date());
}else{
fromatDate = sdf.format(date);
}
return fromatDate;
}
public static void main(String[] args) {
String time = timestampToDate("955814400000");
System.out.println(time);
}
}

1004
ai-service/src/main/java/com/bw/ai/utils/DownLoadUtil.java
File diff suppressed because it is too large
View File

27
ai-service/src/main/java/com/bw/ai/utils/EncryptionUtil.java

@ -0,0 +1,27 @@
package com.bw.ai.utils;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* @author jian.mao
* @date 2023年3月10日
* @description
*/
public class EncryptionUtil {
public static String md5(String text) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(text.getBytes());
byte[] bytes = md.digest();
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b & 0xff));
}
return sb.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
}
}
}

104
ai-service/src/main/java/com/bw/ai/utils/FileUtil.java

@ -0,0 +1,104 @@
package com.bw.ai.utils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import org.apache.commons.io.FileUtils;
import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.hwpf.extractor.WordExtractor;
import org.apache.poi.xwpf.extractor.XWPFWordExtractor;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
/**
* 文件工具类
* @author jian.mao
* @date 2023年7月14日
* @description
*/
public class FileUtil {
/**
* 数据写入文件
* @param Path 文件路径
* @param result 数据
* @throws IOException
*/
public static void writeFile(String path,String result){
try {
FileWriter fw = new FileWriter(path,true);
fw.write(result+"\n");
fw.flush();
fw.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void delFile(String path) {
try {
File file = new File(path);
file.delete();
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
/**
* 读取prompt
* @param path
* @return
*/
public static String readPrompt(String path){
StringBuffer prompt = new StringBuffer();
File file = new File(path);
if(file.exists()){
List<String> tasks = null;
try {
tasks = FileUtils.readLines(file,"UTF-8");
} catch (IOException e) {
e.printStackTrace();
}
for (String taskStr : tasks) {
prompt.append(taskStr);
}
}
return prompt.toString();
}
/**
* 解析wordtxt文档
* @param filePath 文件路径
* @return
* @throws IOException
*/
public static String readFile(String filePath) throws IOException {
InputStream inputStream = new FileInputStream(filePath);
String fileTypeDoc = "doc";
String fileTypeDocx = "docx";
String fileTypetxt = "txt";
if (filePath.endsWith(fileTypeDoc)) {
try (HWPFDocument document = new HWPFDocument(inputStream)) {
WordExtractor extractor = new WordExtractor(document);
return extractor.getText();
}
} else if (filePath.endsWith(fileTypeDocx)) {
try (XWPFDocument document = new XWPFDocument(inputStream)) {
XWPFWordExtractor extractor = new XWPFWordExtractor(document);
return extractor.getText();
}
} else if (filePath.endsWith(fileTypetxt)) {
StringBuffer sb = new StringBuffer();
for (String line : FileUtils.readLines(new File(filePath))) {
sb.append(line);
}
return sb.toString();
}else {
throw new IllegalArgumentException("Unsupported file format");
}
}
}

99
ai-service/src/main/java/com/bw/ai/utils/GPTResultParseUtil.java

@ -0,0 +1,99 @@
package com.bw.ai.utils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.JSONException;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* gpt解析工具类
*
* @author jian.mao
* @date 2026年1月15日
* @description
*/
public class GPTResultParseUtil {
public static Map<String, Object> parseGPTResult(Map<String, Object> output, String gptContent) {
Map<String, Object> jsonResult = new HashMap<>();
try {
// 替换```json, ``` \n
String jsonContent = gptContent.replace("```json", "").replace("```", "").replace("\n", "");
JSONObject jsonGPT = JSON.parseObject(jsonContent);
if(output != null) {
for (String key : output.keySet()) {
if (jsonGPT.containsKey(key)) {
jsonResult.put(key, jsonGPT.get(key));
}
}
}else {
return jsonGPT;
}
return jsonResult;
} catch (JSONException e) {
try {
// 直接解析失败使用正则表达式匹配外层的 {}
Pattern pattern = Pattern.compile("\\{.*\\}", Pattern.DOTALL);
Matcher matcher = pattern.matcher(gptContent.replace("\n", ""));
if (matcher.find()) {
JSONObject jsonGPT = JSON.parseObject(matcher.group());
if(output != null) {
for (String key : output.keySet()) {
if (jsonGPT.containsKey(key)) {
jsonResult.put(key, jsonGPT.get(key));
}
}
}else {
return jsonGPT;
}
return jsonResult;
} else {
return null;
}
} catch (Exception ex) {
ex.printStackTrace();
return null;
}
}
}
public static Object parse(String gptContent) {
if (gptContent == null || gptContent.trim().isEmpty()) {
return null;
}
try {
// 1. 清理 GPT 常见包裹
String content = gptContent.replace("```json", "").replace("```", "").trim();
// 2. 优先尝试直接解析fastjson 自动识别对象 / 数组
return JSON.parse(content);
} catch (Exception e) {
// 3. 兜底尝试从文本中截取 JSON
try {
String cleaned = gptContent.replace("\n", "").trim();
// 先数组
Pattern arrayPattern = Pattern.compile("\\[.*\\]", Pattern.DOTALL);
Matcher arrayMatcher = arrayPattern.matcher(cleaned);
if (arrayMatcher.find()) {
return JSON.parseArray(arrayMatcher.group());
}
// 再对象
Pattern objectPattern = Pattern.compile("\\{.*\\}", Pattern.DOTALL);
Matcher objectMatcher = objectPattern.matcher(cleaned);
if (objectMatcher.find()) {
return JSON.parseObject(objectMatcher.group());
}
} catch (Exception ignored) {
}
}
return null;
}
}

33
ai-service/src/main/java/com/bw/ai/utils/OtherUtils.java

@ -0,0 +1,33 @@
package com.bw.ai.utils;
import java.security.MessageDigest;
/**
* 其他工具类
* @author jian.mao
* @date 2023年9月19日
* @description
*/
public class OtherUtils {
public static String getMd5(String string) {
try {
MessageDigest md5 = MessageDigest.getInstance("MD5");
byte[] bs = md5.digest(string.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder(40);
for (byte x : bs) {
if ((x & 0xff) >> 4 == 0) {
sb.append("0").append(Integer.toHexString(x & 0xff));
} else {
sb.append(Integer.toHexString(x & 0xff));
}
}
return sb.toString();
} catch (Exception e) {
return "nceaform" + System.currentTimeMillis();
}
}
}

18
ai-service/src/main/java/com/bw/ai/utils/QueueUtil.java

@ -0,0 +1,18 @@
package com.bw.ai.utils;
import java.util.Map;
import java.util.concurrent.LinkedBlockingDeque;
/**
* @author:jinming
* @className:QueueUtil
* @version:1.0
* @description:
* @Date:2023/7/13 15:00
*/
public class QueueUtil {
public static LinkedBlockingDeque<Map<String, Object>> taskQueue = new LinkedBlockingDeque<Map<String, Object>>();
public static LinkedBlockingDeque<String> sendQueue = new LinkedBlockingDeque<String>();
}

23
ai-service/src/main/java/com/bw/ai/utils/ThrowMessageUtil.java

@ -0,0 +1,23 @@
package com.bw.ai.utils;
import java.io.PrintWriter;
import java.io.StringWriter;
/**
* @author jian.mao
* @date 2023年3月22日
* @description
*/
public class ThrowMessageUtil {
/**
* 获取异常信息
* @param t
* @return
*/
public static String getErrmessage(Throwable t){
StringWriter stringWriter=new StringWriter();
t.printStackTrace(new PrintWriter(stringWriter,true));
return stringWriter.getBuffer().toString();
}
}

52
ai-service/src/main/resources/bootstrap.yml

@ -0,0 +1,52 @@
# ==================== 必须文件:bootstrap.yml ====================
# 这个文件用于配置Nacos客户端,优先级最高
spring:
application:
name: ai-service # 服务名,对应Nacos中的Data ID
cloud:
nacos:
# ======== 配置中心 ========
config:
server-addr: 127.0.0.1:8848 # Nacos地址
username: nacos # 用户名
password: nacos # 密码
group: public_dev # 分组
namespace: opai # 命名空间(默认public)
file-extension: yaml # 配置文件格式
timeout: 5000 # 超时时间(ms)
# 核心配置:开启动态刷新
refresh-enabled: true # 必须为true!
# 主配置文件(从Nacos加载)
data-id: ${spring.application.name}.${spring.cloud.nacos.config.file-extension}
# 共享配置文件(可选)
shared-configs[0]:
data-id: application.yaml # 公共配置
group: public_dev # 公共分组
namespace: opai
refresh: true # 公共配置也要刷新
# 扩展配置(可选)
# extension-configs[0]:
# data-id: datasource.yaml
# group: dev
# refresh: true
# ======== 服务发现 ========
discovery:
server-addr: ${spring.cloud.nacos.config.server-addr}
username: ${spring.cloud.nacos.config.username}
password: ${spring.cloud.nacos.config.password}
group: ${spring.cloud.nacos.config.group}
namespace: ${spring.cloud.nacos.config.namespace}
logging:
level:
root: info
com.alibaba.nacos.client.config.impl: WARN
file:
path: ../logs

26
ai-service/src/main/resources/emotionPrompt

@ -0,0 +1,26 @@
你是一位专业的情感分析专家,具备丰富的情绪识别与统计分析经验。
你的任务是:根据我提供的【参考内容】,分析整体情感倾向,并以百分比形式给出情感分布结果。
情感类别仅限以下三种:
- 正面(Positive)
- 负面(Negative)
- 中性(Neutral)
输出要求:
1. 只允许返回 JSON 格式结果,不得输出任何解释、说明或多余文本。
2. JSON 的 key 必须使用英文。
3. 所有数值必须为百分比格式,保留一位小数,并以 % 结尾。
4. confidence 表示本次情感分析结果的整体可信度。
5. 返回的 JSON 结构必须严格符合以下格式,不允许新增、删除或修改字段名称。
返回格式:
{
"positive": "xx.x%",
"negative": "xx.x%",
"neutral": "xx.x%",
"confidence": "xx.x%"
}
参考内容:
{{content}}

36
ai-service/src/main/resources/logback-spring.xml

@ -0,0 +1,36 @@
<configuration>
<!-- 属性文件:在properties文件中找到对应的配置项 -->
<springProperty scope="context" name="log-path" source="logging.file.path"/>
<!--<springProperty scope="context" name="logging.level" source="logging.level.com.bfd"/>-->
<!-- 默认的控制台日志输出,一般生产环境都是后台启动,这个没太大作用 -->
<!-- <appender name="STDOUT"
class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %line %-5level %logger{50} - %msg%n</Pattern>
</encoder>
</appender> -->
<appender name="GLMAPPER-LOGGERONE"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<append>true</append>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>${logging.level}</level>
</filter>
<file>
${log-path}/ai-service.log
</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${log-path}/ai-service.%d{yyyy-MM-dd}</FileNamePattern>
<MaxHistory>7</MaxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %line %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<root level="info">
<appender-ref ref="GLMAPPER-LOGGERONE"/>
<!-- <appender-ref ref="STDOUT"/> -->
</root>
</configuration>

30
ai-service/src/main/resources/moodPrompt

@ -0,0 +1,30 @@
你是一位专业的情绪识别专家,具备丰富的情绪理解与分析经验。
你的任务是:根据我提供的【参考内容】,对文本中体现的情绪进行识别,并给出各类情绪所占的百分比强度。
需要识别的情绪类型仅限以下几种:
- 快乐(Joy)
- 信任(Trust)
- 恐惧(Fear)
- 惊讶(Surprise)
- 悲伤(Sadness)
- 愤怒(Anger)
输出要求:
1. 只允许返回 JSON 格式结果,不得输出任何解释、说明或多余文本。
2. JSON 的 key 必须使用英文,且字段名称必须严格一致。
3. 所有数值必须为百分比格式,并以 % 结尾。
4. 返回的 JSON 结构必须严格符合以下格式,不允许新增、删除或修改字段。
返回格式:
{
"Joy": "xx%",
"Trust": "xx%",
"FearFear": "xx%",
"Surprise": "xx%",
"Sadness": "xx%",
"Anger": "xx%"
}
参考内容:
{{content}}

38
ai-service/src/main/resources/opinionPrompt

@ -0,0 +1,38 @@
你是一名专业的意见挖掘与情感分析专家,擅长从文本中识别观点性陈述,并结合语义理解对其情感倾向与主题进行精准分类。
你的任务是:根据我提供的【参考内容】,提取其中明确表达主观观点或评价的意见陈述,并对每条意见进行情感与主题分类。
情感类型仅限以下三种:
- 正面
- 负面
- 中性
输出要求:
1. 只允许返回 JSON 数组结构,不得输出任何解释、说明或多余文本。
2. 每条意见必须包含 emotion、opinion、topic 三个字段,字段名必须使用英文。
3. emotion 的取值只能是:正面、负面、中性。
4. opinion 使用英文原文表达,保持原句语义,不进行扩写或主观改写。
5. topic 使用英文短语,对意见所属主题进行概括(如 Product Quality、Customer Service、Pricing 等)。
6. 若存在多条意见,请按出现顺序依次输出;若未识别到有效意见,请返回空数组 []。
返回结构示例(结构必须一致):
[
{
"emotion": "正面",
"opinion": "The product quality is excellent and exceeds expectations.",
"topic": "Product Quality"
},
{
"emotion": "负面",
"opinion": "Customer service response time could be improved significantly.",
"topic": "Customer Service"
},
{
"emotion": "中性",
"opinion": "Pricing seems competitive compared to similar offerings in the market.",
"topic": "Pricing"
}
]
参考内容:
{{content}}

35
ai-service/src/main/resources/prompt

@ -0,0 +1,35 @@
你是一个“结构化信息抽取引擎”,不是聊天助手。
你将收到两个部分:
1. 抽取规则(JSON数组)
2. 待分析的原始文本
你的任务是:
严格按照抽取规则,从原始文本中抽取信息,并生成一个 JSON 对象返回。
【强制规则】
- 只允许输出 JSON
- 不得输出任何解释、提示、markdown、代码块或多余文字
- JSON 的 key 必须与抽取规则中的 field 完全一致
- 如果某字段未找到,返回 null
- 如果一个字段有多个结果,返回数组
- 如果只有一个结果,返回字符串
- 所有值必须来自原文,不允许编造
--------------------------------
【抽取规则】
{{rules}}
--------------------------------
【原始文本】
{{content}}
--------------------------------
【你必须输出的格式示例】(仅示例,不要照抄)
{
"time": "...",
"people": "...",
"summary": "..."
}
现在开始抽取。

1
ocr-service/src/main/java/com/bw/ocr/handler/MainHandler.java

@ -35,7 +35,6 @@ import lombok.extern.slf4j.Slf4j;
*/
@Component
@Order(value = 1)
@RefreshScope
@Slf4j
public class MainHandler implements ApplicationRunner {

1
pom.xml

@ -10,6 +10,7 @@
<module>ocr-service</module>
<module>asr-service</module>
<module>translate-service</module>
<module>ai-service</module>
</modules>
<parent>

1
translate-service/src/main/java/com/bw/translate/handler/MainHandler.java

@ -33,7 +33,6 @@ import lombok.extern.slf4j.Slf4j;
*/
@Component
@Order(value = 1)
@RefreshScope
@Slf4j
public class MainHandler implements ApplicationRunner {

3
translate-service/src/main/java/com/bw/translate/service/impl/TranslateTaskServiceImpl.java

@ -58,8 +58,7 @@ public class TranslateTaskServiceImpl implements TranslateTaskService {
//模型请求
Map<String, Object> headers = new HashMap<String, Object>(16);
// headers.put(Constants.AUTHORIZATION, authorization);
headers.put(Constants.AUTHORIZATION, "Bearer sk-c5f56c1c8a6447b3a6c646a3f14085c4");
headers.put(Constants.AUTHORIZATION, authorization);
headers.put("Content-Type", "application/json");
Map<String, Object> param = new HashMap<String, Object>(16);
param.put(Constants.MODEL, model);

Loading…
Cancel
Save