|
@@ -0,0 +1,200 @@
|
|
|
|
|
+package shop.alien.store.controller;
|
|
|
|
|
+
|
|
|
|
|
+import com.alibaba.fastjson2.JSONArray;
|
|
|
|
|
+import com.alibaba.fastjson2.JSONObject;
|
|
|
|
|
+import io.swagger.annotations.Api;
|
|
|
|
|
+import io.swagger.annotations.ApiOperation;
|
|
|
|
|
+import lombok.RequiredArgsConstructor;
|
|
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
+import org.springframework.beans.factory.annotation.Value;
|
|
|
|
|
+import org.springframework.cloud.context.config.annotation.RefreshScope;
|
|
|
|
|
+import org.springframework.http.*;
|
|
|
|
|
+import org.springframework.web.bind.annotation.CrossOrigin;
|
|
|
|
|
+import org.springframework.web.bind.annotation.RequestMapping;
|
|
|
|
|
+import org.springframework.web.bind.annotation.RequestParam;
|
|
|
|
|
+import org.springframework.web.bind.annotation.RestController;
|
|
|
|
|
+import org.springframework.web.client.RestTemplate;
|
|
|
|
|
+import shop.alien.entity.result.R;
|
|
|
|
|
+import shop.alien.store.util.ai.AiAuthTokenUtil;
|
|
|
|
|
+
|
|
|
|
|
+import java.util.ArrayList;
|
|
|
|
|
+import java.util.HashMap;
|
|
|
|
|
+import java.util.List;
|
|
|
|
|
+import java.util.Map;
|
|
|
|
|
+import java.util.regex.Matcher;
|
|
|
|
|
+import java.util.regex.Pattern;
|
|
|
|
|
+
|
|
|
|
|
+@Slf4j
|
|
|
|
|
+@Api(tags = {"ai搜索标签"})
|
|
|
|
|
+@CrossOrigin
|
|
|
|
|
+@RestController
|
|
|
|
|
+@RequestMapping("/aiTags")
|
|
|
|
|
+@RequiredArgsConstructor
|
|
|
|
|
+@RefreshScope
|
|
|
|
|
+public class AiTagsController {
|
|
|
|
|
+
|
|
|
|
|
+ @Value("${third-part-ai-tags.tags-url:http://124.93.18.180:9000/ai/intelligent-analysis/api/v1/tag/get_store_level_tag}")
|
|
|
|
|
+ private String aiTagsUrl;
|
|
|
|
|
+
|
|
|
|
|
+ @Value("${third-part-ai-tags.tags-rating-url:http://124.93.18.180:9000/ai/intelligent-analysis/api/v1/tag/get_ratings_by_tag}")
|
|
|
|
|
+ private String tagsRatingUrl;
|
|
|
|
|
+
|
|
|
|
|
+ private static final Pattern PATTERN = Pattern.compile("([^()]+)\\((\\d+)\\)");
|
|
|
|
|
+
|
|
|
|
|
+ private final RestTemplate restTemplate;
|
|
|
|
|
+ private final AiAuthTokenUtil aiAuthTokenUtil;
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取店铺标签
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param storeId 店铺ID
|
|
|
|
|
+ * @param page 页码
|
|
|
|
|
+ * @param pageNum 每页数量
|
|
|
|
|
+ * @return 店铺标签列表
|
|
|
|
|
+ */
|
|
|
|
|
+ @ApiOperation("获取店铺标签")
|
|
|
|
|
+ @RequestMapping("/storeTags")
|
|
|
|
|
+ public R storeTags(@RequestParam("store_id") Long storeId,
|
|
|
|
|
+ @RequestParam(value = "page", defaultValue = "1") Integer page,
|
|
|
|
|
+ @RequestParam(value = "pageNum", defaultValue = "10") Integer pageNum) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 1. 获取访问令牌(如果外部接口需要,可添加到请求头)
|
|
|
|
|
+ String accessToken = aiAuthTokenUtil.getAccessToken();
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 构建请求头
|
|
|
|
|
+ HttpHeaders aiHeaders = new HttpHeaders();
|
|
|
|
|
+ aiHeaders.setContentType(MediaType.APPLICATION_JSON);
|
|
|
|
|
+ // 如果外部接口需要 token 认证,添加到请求头(根据实际需求调整)
|
|
|
|
|
+ if (accessToken != null && !accessToken.isEmpty()) {
|
|
|
|
|
+ aiHeaders.set("Authorization", "Bearer " + accessToken);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 拼接请求 URL 和参数(重点:按外部接口格式拼接)
|
|
|
|
|
+ // 使用 String.format 拼接参数,注意参数名对应:page_size 而非 pageNum
|
|
|
|
|
+ String requestUrl = String.format("%s?store_id=%d&page=%d&page_size=%d",
|
|
|
|
|
+ aiTagsUrl, storeId, page, pageNum);
|
|
|
|
|
+
|
|
|
|
|
+ HttpEntity<Void> requestEntity = new HttpEntity<>(aiHeaders);
|
|
|
|
|
+ ResponseEntity<String> exchange = restTemplate.exchange(requestUrl, HttpMethod.GET, requestEntity, String.class);
|
|
|
|
|
+// ResponseEntity<String> responseEntity = restTemplate.exchange(requestUrl,"GET",requestEntity,String.class);
|
|
|
|
|
+ if(exchange.getStatusCode().is2xxSuccessful()){
|
|
|
|
|
+ // 4. 解析响应体(根据外部接口返回格式调整)
|
|
|
|
|
+ String responseBody = exchange.getBody();
|
|
|
|
|
+ JSONObject jsonObject = JSONObject.parseObject(responseBody);
|
|
|
|
|
+ JSONArray jsonArray = jsonObject.getJSONObject("data").getJSONArray("list");
|
|
|
|
|
+ if(jsonArray.isEmpty()){
|
|
|
|
|
+ throw new Exception("ai接口失败");
|
|
|
|
|
+ }
|
|
|
|
|
+ String negativeComment = jsonArray.getJSONObject(0).getString("negative_comment");
|
|
|
|
|
+ // 这里简单返回原始字符串(根据实际情况调整)
|
|
|
|
|
+ return R.data(parseComment(negativeComment));
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return R.fail("获取店铺标签失败:" + exchange.getStatusCode());
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ // 补充异常处理:捕获调用外部接口的异常,返回友好提示
|
|
|
|
|
+ e.printStackTrace(); // 生产环境建议用日志框架记录
|
|
|
|
|
+ return R.fail("获取店铺标签失败:" + e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 根据标签获取店铺评分
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param storeId 店铺ID
|
|
|
|
|
+ * @param tag 标签(如:商品质量差)
|
|
|
|
|
+ * @param page 页码
|
|
|
|
|
+ * @param pageNum 每页数量(对应外部接口的page_size)
|
|
|
|
|
+ * @return 店铺评分列表
|
|
|
|
|
+ */
|
|
|
|
|
+ @ApiOperation("根据标签获取店铺评价")
|
|
|
|
|
+ @RequestMapping("/storeRatingsByTag")
|
|
|
|
|
+ public R storeRatingsByTag(@RequestParam("store_id") Long storeId,
|
|
|
|
|
+ @RequestParam("tag") String tag, // 新增标签参数,必填
|
|
|
|
|
+ @RequestParam(value = "page", defaultValue = "1") Integer page,
|
|
|
|
|
+ @RequestParam(value = "pageNum", defaultValue = "10") Integer pageNum) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 1. 获取访问令牌(复用原有逻辑)
|
|
|
|
|
+ String accessToken = aiAuthTokenUtil.getAccessToken();
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 构建请求头
|
|
|
|
|
+ HttpHeaders aiHeaders = new HttpHeaders();
|
|
|
|
|
+ aiHeaders.setContentType(MediaType.APPLICATION_JSON);
|
|
|
|
|
+ // 添加token认证(如有需要)
|
|
|
|
|
+ if (accessToken != null && !accessToken.isEmpty()) {
|
|
|
|
|
+ aiHeaders.set("Authorization", "Bearer " + accessToken);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 拼接请求URL和参数(核心调整:新增tag参数,匹配外部接口参数名)
|
|
|
|
|
+ String requestUrl = String.format("%s?store_id=%d&tag=%s&page=%d&page_size=%d",
|
|
|
|
|
+ tagsRatingUrl, storeId, tag, page, pageNum);
|
|
|
|
|
+
|
|
|
|
|
+ // 4. 发送GET请求
|
|
|
|
|
+ HttpEntity<Void> requestEntity = new HttpEntity<>(aiHeaders);
|
|
|
|
|
+ ResponseEntity<String> exchange = restTemplate.exchange(requestUrl, HttpMethod.GET, requestEntity, String.class);
|
|
|
|
|
+
|
|
|
|
|
+ // 5. 处理响应结果
|
|
|
|
|
+ if (exchange.getStatusCode().is2xxSuccessful()) {
|
|
|
|
|
+ String responseBody = exchange.getBody();
|
|
|
|
|
+ JSONObject jsonObject = JSONObject.parseObject(responseBody);
|
|
|
|
|
+ JSONArray jsonArray = jsonObject.getJSONObject("data").getJSONArray("list");
|
|
|
|
|
+ if(jsonArray.isEmpty()){
|
|
|
|
|
+ throw new Exception("ai接口失败");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 保持和原有接口一致的返回格式:将data节点重新封装(可根据实际需求调整)
|
|
|
|
|
+ return R.data(jsonArray);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return R.fail("获取店铺评价失败:" + exchange.getStatusCode());
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ // 异常日志记录(生产环境建议替换为日志框架,如logback/log4j2)
|
|
|
|
|
+ e.printStackTrace();
|
|
|
|
|
+ return R.fail("获取店铺评分失败:" + e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static List<JSONObject> parseComment(String negativeComment) {
|
|
|
|
|
+ // 最终返回的结果列表
|
|
|
|
|
+ List<JSONObject> resultList = new ArrayList<>();
|
|
|
|
|
+ // 临时存储标签-数值的映射,用于累加相同标签的数值
|
|
|
|
|
+ Map<String, Integer> tagCountMap = new HashMap<>();
|
|
|
|
|
+
|
|
|
|
|
+ // 1. 空值校验:避免空字符串/Null导致后续处理异常
|
|
|
|
|
+ if (negativeComment == null || negativeComment.trim().isEmpty()) {
|
|
|
|
|
+ return resultList;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 按分号;分割成多个片段(如:["差评(1)", "商品质量差(1),商品质量差(1)"])
|
|
|
|
|
+ String[] semicolonParts = negativeComment.split(";");
|
|
|
|
|
+ String trimPart = semicolonParts[1].trim();
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 按逗号,分割当前片段内的多个标签(如:["商品质量差(1)", "商品质量差(1)"])
|
|
|
|
|
+ String[] commaParts = trimPart.split(",");
|
|
|
|
|
+ for (String tagWithNum : commaParts) {
|
|
|
|
|
+ String trimTag = tagWithNum.trim();
|
|
|
|
|
+ if (trimTag.isEmpty()) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 4. 正则匹配标签和数值(如:"差评(1)" → 标签=差评,数值=1)
|
|
|
|
|
+ Matcher matcher = PATTERN.matcher(trimTag);
|
|
|
|
|
+ if (matcher.find()) {
|
|
|
|
|
+ String label = matcher.group(1).trim(); // 标签(如:差评)
|
|
|
|
|
+ int value = Integer.parseInt(matcher.group(2)); // 数值(如:1)
|
|
|
|
|
+
|
|
|
|
|
+ // 5. 累加相同标签的数值
|
|
|
|
|
+ tagCountMap.put(label, tagCountMap.getOrDefault(label, 0) + value);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 6. 将Map转换为指定的List<JSONObject>格式
|
|
|
|
|
+ for (Map.Entry<String, Integer> entry : tagCountMap.entrySet()) {
|
|
|
|
|
+ JSONObject jsonObj = new JSONObject();
|
|
|
|
|
+ jsonObj.put("label", entry.getKey());
|
|
|
|
|
+ jsonObj.put("value", String.valueOf(entry.getValue())); // value转为字符串,匹配示例格式
|
|
|
|
|
+ resultList.add(jsonObj);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return resultList;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|