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)工具类。 *

* 文档:NLP 自学习平台 / 基础文本服务 *

* 调用方式基于 {@code aliyun-java-sdk-alinlp} 的 {@link CommonRequest}: * 统一向 {@code alinlp.cn-hangzhou.aliyuncs.com} 发送 POST 请求,版本号 2020-06-29, * 通过传入不同的 {@code Action} 与 {@code ServiceCode} 调用具体能力。 *

* 配置项(application.yml): *

 * 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
 * 
*/ @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 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 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 segmentChinese(String text, String tokenizer) { if (StringUtils.isBlank(text)) { return Collections.emptyList(); } Map 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 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 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 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 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 result = segmentChinese("阿里巴巴集团的使命是让天下没有难做的生意。"); log.info(result.toString()); } @Test public void testP() { JSONObject result = sentimentAnalysis("我觉得你可能是对的"); log.info(result.toString()); } }