package com.smartor.common;
|
|
import com.alibaba.fastjson2.JSONArray;
|
import com.alibaba.fastjson2.JSONObject;
|
import com.aliyuncs.CommonRequest;
|
import com.aliyuncs.CommonResponse;
|
import com.aliyuncs.DefaultAcsClient;
|
import com.aliyuncs.IAcsClient;
|
import com.aliyuncs.exceptions.ClientException;
|
import com.aliyuncs.exceptions.ServerException;
|
import com.aliyuncs.http.MethodType;
|
import com.aliyuncs.profile.DefaultProfile;
|
import com.aliyuncs.profile.IClientProfile;
|
import com.google.gson.Gson;
|
import lombok.extern.slf4j.Slf4j;
|
import org.apache.commons.lang3.StringUtils;
|
import org.junit.Test;
|
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.stereotype.Component;
|
|
import javax.annotation.PostConstruct;
|
import java.util.Arrays;
|
import java.util.Collections;
|
import java.util.LinkedHashMap;
|
import java.util.List;
|
import java.util.Map;
|
|
/**
|
* 阿里云自然语言处理(NLP)工具类。
|
* <p>
|
* 文档:<a href="https://help.aliyun.com/zh/document_detail/181284.html">NLP 自学习平台 / 基础文本服务</a>
|
* <p>
|
* 调用方式基于 {@code aliyun-java-sdk-alinlp} 的 {@link CommonRequest}:
|
* 统一向 {@code alinlp.cn-hangzhou.aliyuncs.com} 发送 POST 请求,版本号 2020-06-29,
|
* 通过传入不同的 {@code Action} 与 {@code ServiceCode} 调用具体能力。
|
* <p>
|
* 配置项(application.yml):
|
* <pre>
|
* aliyun:
|
* nlp:
|
* access-key-id: ${accessKeyId} # 默认复用主账号 accessKeyId
|
* access-key-secret: ${accessKeySecret}
|
* region-id: cn-hangzhou
|
* domain: alinlp.cn-hangzhou.aliyuncs.com
|
* version: 2020-06-29
|
* read-timeout: 5000
|
* connect-timeout: 3000
|
* </pre>
|
*/
|
@Slf4j
|
@Component
|
public class AliNlpUtil {
|
|
/** 阿里云 AccessKey ID,默认复用主账号配置 */
|
@Value("${aliyun.nlp.access-key-id:${accessKeyId:}}")
|
private String accessKeyId;
|
|
/** 阿里云 AccessKey Secret */
|
@Value("${aliyun.nlp.access-key-secret:${accessKeySecret:}}")
|
private String accessKeySecret;
|
|
/** 地域 ID,公共云固定为 cn-hangzhou */
|
@Value("${aliyun.nlp.region-id:cn-hangzhou}")
|
private String regionId;
|
|
/** 接入域名 */
|
@Value("${aliyun.nlp.domain:alinlp.cn-hangzhou.aliyuncs.com}")
|
private String domain = "alinlp.cn-hangzhou.aliyuncs.com";
|
|
/** API 版本号 */
|
@Value("${aliyun.nlp.version:2020-06-29}")
|
private String version = "2020-06-29";
|
|
/** 读取超时(毫秒) */
|
@Value("${aliyun.nlp.read-timeout:5000}")
|
private int readTimeout = 5000;
|
|
/** 连接超时(毫秒) */
|
@Value("${aliyun.nlp.connect-timeout:3000}")
|
private int connectTimeout = 3000;
|
|
// NLP地域固定 cn-hangzhou
|
private static final String REGION_ID = "cn-hangzhou";
|
private static final String PRODUCT = "nlp";
|
private static final String VERSION = "2020-06-08";
|
|
private static final Gson gson = new Gson();
|
|
private IAcsClient client;
|
|
@PostConstruct
|
private void init() {
|
// String accessKeyId = "LTAI5tPfc1VJzz7VuhzcBwug";
|
// String accessKeySecret = "gG1srKxPFDBNWe2oHfqmK1qsSQkf1e";
|
|
String accessKeyId = "LTAI5t6GBf9nUXfX37CUDi4H";
|
String accessKeySecret = "BtjYAES4yjvde5Lwg46sTqnZefPnOE";
|
String regionId = "cn-hangzhou";
|
if (StringUtils.isBlank(accessKeyId) || StringUtils.isBlank(accessKeySecret)) {
|
log.warn("阿里云 NLP 未配置 AccessKey,AliNlpUtil 将不可用");
|
return;
|
}
|
IClientProfile profile = DefaultProfile.getProfile(regionId, accessKeyId, accessKeySecret);
|
this.client = new DefaultAcsClient(profile);
|
}
|
|
// ============================== 基础调用 ==============================
|
|
/**
|
* 通用调用入口:传入 Action 与业务参数,返回解析后的 JSON 对象。
|
*
|
* @param action API 动作名,例如 {@code GetWsChGeneral}
|
* @param params 业务参数,至少需要包含 {@code ServiceCode}、{@code Text} 等
|
* @return 接口返回的 JSON 对象;失败时返回 {@code null}
|
*/
|
public JSONObject invoke(String action, Map<String, String> params) {
|
if (client == null) {
|
log.error("阿里云 NLP 客户端未初始化,无法调用 action={}", action);
|
return null;
|
}
|
if (StringUtils.isBlank(action)) {
|
throw new IllegalArgumentException("action 不能为空");
|
}
|
|
CommonRequest request = new CommonRequest();
|
request.setSysMethod(MethodType.POST);
|
request.setSysDomain(domain);
|
request.setSysVersion(version);
|
request.setSysAction(action);
|
request.setSysReadTimeout(readTimeout);
|
request.setSysConnectTimeout(connectTimeout);
|
if (params != null) {
|
for (Map.Entry<String, String> e : params.entrySet()) {
|
if (e.getValue() != null) {
|
request.putBodyParameter(e.getKey(), e.getValue());
|
}
|
}
|
}
|
|
try {
|
CommonResponse response = client.getCommonResponse(request);
|
String data = response.getData();
|
if (StringUtils.isBlank(data)) {
|
log.error("阿里云 NLP 返回为空,action={}, params={}", action, params);
|
return null;
|
}
|
return JSONObject.parseObject(data);
|
} catch (ClientException e) {
|
log.error("阿里云 NLP 调用失败 action={}, params={}, errCode={}, errMsg={}",
|
action, params, e.getErrCode(), e.getErrMsg(), e);
|
return null;
|
} catch (Exception e) {
|
log.error("阿里云 NLP 调用异常 action={}, params={}", action, params, e);
|
return null;
|
}
|
}
|
|
// ============================== 便捷方法 ==============================
|
|
/**
|
* 中文分词(通用领域,高级版)。
|
* 对应 Action:{@code GetWsChGeneral},ServiceCode:{@code alinlp}。
|
*
|
* @param text 待分词文本
|
* @param tokenizer 分词粒度,可空。常用值:{@code GENERAL_CHN}(通用基础粒度)
|
* @return 分词结果列表;调用失败返回空列表
|
*/
|
public List<String> segmentChinese(String text, String tokenizer) {
|
if (StringUtils.isBlank(text)) {
|
return Collections.emptyList();
|
}
|
Map<String, String> params = new LinkedHashMap<>();
|
params.put("ServiceCode", "alinlp");
|
params.put("Text", text);
|
if (StringUtils.isNotBlank(tokenizer)) {
|
params.put("TokenizerId", tokenizer);
|
}
|
JSONObject resp = invoke("GetWsChGeneral", params);
|
return parseWordsFromData(resp);
|
}
|
|
/** 中文分词,使用默认通用粒度 {@code GENERAL_CHN}。 */
|
public List<String> segmentChinese(String text) {
|
return segmentChinese(text, "GENERAL_CHN");
|
}
|
|
/**
|
* 情感分析(通用领域,基础版)。
|
* 对应 Action:{@code GetSaChGeneral}。
|
*
|
* @param text 待分析文本
|
* @return 形如 {@code {"sentiment":"positive","positive_prob":0.97,...}} 的对象;失败返回 {@code null}
|
*/
|
public JSONObject sentimentAnalysis(String text) {
|
if (StringUtils.isBlank(text)) {
|
return null;
|
}
|
Map<String, String> params = new LinkedHashMap<>();
|
params.put("ServiceCode", "alinlp");
|
params.put("Text", text);
|
JSONObject resp = invoke("GetSaChGeneral", params);
|
return extractDataObject(resp);
|
}
|
|
// 测试入口
|
@Test
|
public void main() {
|
String text1 = "这家奶茶超级好喝,下次还来!";
|
String text2 = "味道很差,服务态度也不好,不推荐";
|
String text3 = "今天出门买了一瓶矿泉水";
|
|
this.init();
|
JSONObject res1 = sentimentAnalysis(text1);
|
JSONObject res2 = sentimentAnalysis(text2);
|
JSONObject res3 = sentimentAnalysis(text3);
|
|
System.out.println("正面文本结果:" + gson.toJson(res1));
|
System.out.println("负面文本结果:" + gson.toJson(res2));
|
System.out.println("中性文本结果:" + gson.toJson(res3));
|
}
|
|
/**
|
* 命名实体识别(通用领域)。
|
* 对应 Action:{@code GetNerChGeneral}。
|
*
|
* @param text 待识别文本
|
* @return 实体列表(每个元素含 word、tag、startIndex 等字段);失败返回空列表
|
*/
|
public JSONArray namedEntityRecognition(String text) {
|
if (StringUtils.isBlank(text)) {
|
return new JSONArray();
|
}
|
Map<String, String> params = new LinkedHashMap<>();
|
params.put("ServiceCode", "alinlp");
|
params.put("Text", text);
|
JSONObject resp = invoke("GetNerChGeneral", params);
|
JSONObject data = extractDataObject(resp);
|
if (data == null) {
|
return new JSONArray();
|
}
|
JSONArray arr = data.getJSONArray("result");
|
return arr == null ? new JSONArray() : arr;
|
}
|
|
// ============================== 响应解析 ==============================
|
|
/**
|
* 解析阿里 NLP 的 {@code Data} 字段(字符串化的 JSON)。
|
* 顶层响应形如:{@code {"Data":"{...}","RequestId":"..."}}。
|
*/
|
private JSONObject extractDataObject(JSONObject resp) {
|
if (resp == null) {
|
return null;
|
}
|
String dataStr = resp.getString("Data");
|
if (StringUtils.isBlank(dataStr)) {
|
log.warn("阿里云 NLP 响应缺少 Data 字段,resp={}", resp);
|
return null;
|
}
|
try {
|
return JSONObject.parseObject(dataStr);
|
} catch (Exception e) {
|
log.error("阿里云 NLP Data 字段解析失败,dataStr={}", dataStr, e);
|
return null;
|
}
|
}
|
|
/**
|
* 从分词响应里抽取词列表。result 形如 {@code [{"word":"今天","tag":"t"},...]}。
|
*/
|
private List<String> parseWordsFromData(JSONObject resp) {
|
JSONObject data = extractDataObject(resp);
|
if (data == null) {
|
return Collections.emptyList();
|
}
|
JSONArray result = data.getJSONArray("result");
|
if (result == null || result.isEmpty()) {
|
return Collections.emptyList();
|
}
|
String[] words = new String[result.size()];
|
for (int i = 0; i < result.size(); i++) {
|
JSONObject item = result.getJSONObject(i);
|
words[i] = item == null ? "" : item.getString("word");
|
}
|
return Arrays.asList(words);
|
}
|
|
@Test
|
public void test() {
|
List <String> result = segmentChinese("阿里巴巴集团的使命是让天下没有难做的生意。");
|
log.info(result.toString());
|
}
|
|
@Test
|
public void testP() {
|
JSONObject result = sentimentAnalysis("我觉得你可能是对的");
|
log.info(result.toString());
|
}
|
}
|