陈昶聿
10 小时以前 94fd4658ea62c63a164a06fd5973432b651a23f3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
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());
    }
}