diff --git a/stwzhj-modules/stwzhj-extract/src/main/java/org/dromara/extract/controller/AddressController.java b/stwzhj-modules/stwzhj-extract/src/main/java/org/dromara/extract/controller/AddressController.java index c970bafd..c906e105 100644 --- a/stwzhj-modules/stwzhj-extract/src/main/java/org/dromara/extract/controller/AddressController.java +++ b/stwzhj-modules/stwzhj-extract/src/main/java/org/dromara/extract/controller/AddressController.java @@ -2,11 +2,13 @@ package org.dromara.extract.controller; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.dromara.common.core.utils.StringUtils; import org.dromara.common.web.core.BaseController; import org.dromara.extract.service.impl.DataMigrationService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.util.HashMap; @@ -25,7 +27,10 @@ public class AddressController extends BaseController { private boolean isRunning = false; @GetMapping("/start") - public ResponseEntity startMigration() { + public ResponseEntity startMigration(@RequestParam String xzqh,@RequestParam String startTime) { + if (StringUtils.isBlank(xzqh) || StringUtils.isBlank(startTime)){ + return ResponseEntity.ok("参数不能为空"); + } if (isRunning) { return ResponseEntity.badRequest() .body("迁移任务已在运行中,请勿重复提交!"); @@ -36,7 +41,7 @@ public class AddressController extends BaseController { new Thread(() -> { try { log.info("🚀 开始异步执行数据迁移任务..."); - dataMigrationService.startMigration(); + dataMigrationService.startMigration(xzqh,startTime); } catch (Exception e) { log.error("❌ 迁移任务执行异常", e); } finally { diff --git a/stwzhj-modules/stwzhj-extract/src/main/java/org/dromara/extract/domain/Jqd.java b/stwzhj-modules/stwzhj-extract/src/main/java/org/dromara/extract/domain/Jqd.java index 92749b9e..a16a926b 100644 --- a/stwzhj-modules/stwzhj-extract/src/main/java/org/dromara/extract/domain/Jqd.java +++ b/stwzhj-modules/stwzhj-extract/src/main/java/org/dromara/extract/domain/Jqd.java @@ -25,6 +25,14 @@ public class Jqd implements Serializable { private String yzb; + private String jzssfjdm; + + private String jzssfjmc; + + private String xqdwdm; + + private String xqdwmc; + private String jzxqdwdm; private String jzxqdwmc; diff --git a/stwzhj-modules/stwzhj-extract/src/main/java/org/dromara/extract/domain/PoiAddress.java b/stwzhj-modules/stwzhj-extract/src/main/java/org/dromara/extract/domain/PoiAddress.java index c70c81f8..4eb74fbb 100644 --- a/stwzhj-modules/stwzhj-extract/src/main/java/org/dromara/extract/domain/PoiAddress.java +++ b/stwzhj-modules/stwzhj-extract/src/main/java/org/dromara/extract/domain/PoiAddress.java @@ -71,5 +71,13 @@ public class PoiAddress implements Serializable { private String gadwmc; + private String jzssfjdm; + + private String jzssfjmc; + + private String xqdwdm; + + private String xqdwmc; + } diff --git a/stwzhj-modules/stwzhj-extract/src/main/java/org/dromara/extract/service/impl/DataMigrationService.java b/stwzhj-modules/stwzhj-extract/src/main/java/org/dromara/extract/service/impl/DataMigrationService.java index faad8a3b..b4c2870c 100644 --- a/stwzhj-modules/stwzhj-extract/src/main/java/org/dromara/extract/service/impl/DataMigrationService.java +++ b/stwzhj-modules/stwzhj-extract/src/main/java/org/dromara/extract/service/impl/DataMigrationService.java @@ -2,6 +2,7 @@ package org.dromara.extract.service.impl; import cn.hutool.core.util.StrUtil; import cn.hutool.http.HttpUtil; +import cn.hutool.json.JSONArray; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; @@ -59,8 +60,63 @@ public class DataMigrationService { // 分页大小 private static final int PAGE_SIZE = 500; + private static final String SYSTEM_PROMPT = """ +你是一个地址解析专家,必须严格、逐条遵守以下规则处理输入地址。直接输出JSON,不思考、不解释。注意:你的任务是执行规则,不是依赖常识或猜测。以下规则优先级高于任何通用知识。 +1. AOI定义:指区域状地理实体,如学校、居民小区、商场、广场、大厦、产业园区、公园、市场等具有明确边界和名称的非行政区划实体。 +✅示例:世纪新都、安徽农业大学教学基地、万达广场、高新银泰、凤凰城二期、九溪江南小区、大摩广场。 +⚠️必须完整保留"期数"或"分区"信息:如"二期"、"B区"、"南区"、"东苑"等, +不得简化为母体名称;❌严格排除:上级行政区划(如省、市、县、区、镇、乡、街道); +类行政区划或功能区:`政务区`、`新区`、`高新区`、`经开区`、`工业区`、`示范区`、`片区`、`功能区`、`园区`(无具体名称时)等; +无明确边界的泛称区域;学校、医院、政府机关、公司等机构内部的分区(如“西区”“南楼”“B座”),即使带“区”“期”字眼,也不视为AOI; +任何包含“社区”“片区”等字眼但无独立命名的区域。 +2. POI定义:指具体的、可独立定位的地理点位。 +允许以下类型:具体商户("庐江百大超市"); +连锁品牌(如"蜜雪冰城""雅韵皖酒厂"); +机构营业网点("中国人寿保险(江淮路营业部)"、"工商银行XX支行"); +具体建筑("邮政大楼"、"科技馆"、"中医院康复楼"); +基层机构(如"杨楼中队""XX派出所""XX卫生院"); +基层地理实体(如"宋庄""葛庄""大王庄"); +交通枢纽(如"孙湾火车站""合肥客运中心""汽车站"); +政府及公共服务机构(如"观堂镇政府""张村老镇政府""车管所""政务服务中心"); +道路交口:格式为"[道路A]与[道路B]交口"或"[道路A]与[道路B]交叉口",且道路名称具体; +红绿灯:仅当命名为"`[地标]红绿灯`"(如"县政府红绿灯")或"`[交口]红绿灯`"(如"南一环与长江路交口红绿灯")时,可作为POI; +功能设施:`加油站`、`公交站`、`地铁站`、`停车场`,但必须依附于一个可定位的主体,且有具体名称(如“亚珠加油站”“中石化加油站”)。 +严格排除(以下情况 POI 必须为空字符串):任何孤立的内部设施:`广场`、`花园`、`凉亭`、`健身区`;任何泛称区域词;加油站等设施无具体品牌名(如“南加油站”“加油站”)。 +⚠️ 方位/模糊词处理原则:如果地址仅由排除词构成(如“门口”“旁边”),或POI完全依赖排除词成立(如“大摩广场对面”),则POI=""; +但如果地址中包含一个独立、可定位的命名实体(如“三阳路小学”“观堂镇政府”“客运中心”“孙湾火车站”“车管所”), +即使后接“门口”“院里”“出站口”“院内”“后面”“东北角”等词,仍应提取该实体作为POI; +“出站口”“进站口”“候车厅”“院内”“门口”等属于内部位置描述,应在address中去除,但不影响主体POI提取。 +3. 语义优先原则:若地址中包含"AOI + 方位词"(如"大摩广场对面"),则AOI = 主体名称,POI = ""; +若地址为"道路交口"且无任何方位词,则POI = 交口名称; +若地址为"XX街道红绿灯",则POI = ""。 +4. address清洗规则:目标:输出一个最简但具备地理可定位性的核心地址; +保留:上一级行政区划;道路交口;可定位设施或地标(如"高新银泰"、"亚珠加油站"); +小区"期数"或"分区"信息; +基层地名(如"宋庄""葛庄"); +去除:模糊方位词本身(包括方言表达如`北头`、`南头`、`桥头`、`靠近`、`旁边`、`附近`、`对面`、`里面`、`内`、`东南角`、`东北角`、`门口`、`院里`、`出站口`、`院内`、`往北`等), +但不删除这些词所修饰的有效实体; +✅正确示例:"祁门路与庐州大道路口大摩广场对面" → address: "祁门路与庐州大道路口大摩广场";"三阳路小学门口(西区)" → address: "三阳路小学(西区)"; +"红星美凯龙东红绿灯" → address: "红星美凯龙";"城西大市场南加油站" → address: "城西大市场";"亚珠加油站桥南" → address: "亚珠加油站"; +"张寨汽贸城南门雅韵皖酒厂后面" → aoi: "张寨汽贸城", poi: "雅韵皖酒厂", address: "张寨汽贸城雅韵皖酒厂"; +"高铁南站南边交控集团" → poi: "交控集团", address: "交控集团";"店集镇蜜雪冰城门口" → poi: "蜜雪冰城", address: "店集镇蜜雪冰城"; +"二桥北头加油站" → aoi: "", poi: "", address: "";"中医院康复楼(庄周西区)" → aoi: "", poi: "中医院康复楼", address: "中医院康复楼(庄周西区)"; +"吉峰农机大市场西门(庄周西区)" → aoi: "吉峰农机大市场", poi: "", address: "吉峰农机大市场(庄周西区)";"陈大镇杨楼中队后面" → poi: "杨楼中队", address: "陈大镇杨楼中队"; +"芦庙镇宋庄" → poi: "宋庄", address: "芦庙镇宋庄";"涡南镇葛庄东北角" → poi: "葛庄", address: "涡南镇葛庄"; +"板桥双陆大王庄" → poi: "大王庄", address: "板桥双陆大王庄";"观堂镇政府门口" → poi: "观堂镇政府", address: "观堂镇政府"; +"张村老镇政府院里" → poi: "张村老镇政府", address: "张村老镇政府";"客运中心门口" → poi: "客运中心", address: "客运中心"; +"孙湾火车站出站口" → poi: "孙湾火车站", address: "孙湾火车站";"车管所院内" → poi: "车管所", address: "车管所"; +"火车站西路口往北" → poi: "火车站西路口", address: "火车站西路口"; +❌错误示例:address为"桥南"、"东红绿灯"、"北头",poi为"南加油站"或"加油站",aoi为"庄周西区"或"西区"(当主体是学校或行政区时)。 +5. 输出格式:仅输出JSON,结构如下:{ +"aoi": "匹配的AOI名称,无则为空字符串", +"poi": "最可能的POI名称,无有效POI则为空字符串", +"address": "清洗后的核心地址,若无有效定位信息则为空字符串"} +注意:输出必须是纯JSON格式,不要有任何额外的解释、思考过程或markdown代码块标记。 +"""; + // 启动迁移 - public void startMigration() { + public void startMigration(String xzqh, String startTime) { + log.info("参数xzqh={},startTime={}",xzqh,startTime); log.info("开始迁移数据..."); // 1. 先加载目标库中已存在的 name(只需一次) @@ -69,8 +125,8 @@ public class DataMigrationService { // 2. 分页读取源数据 LambdaQueryWrapper jqlqw = new LambdaQueryWrapper<>(); - jqlqw.likeRight(Jqd::getXzqh,"3401") - .ge(Jqd::getScbjsj,"2025-07-01") + jqlqw.likeRight(Jqd::getXzqh,xzqh) + .ge(Jqd::getScbjsj,startTime) .isNotNull(Jqd::getSfdz) .ne(Jqd::getSfdz,"地址不详") .ne(Jqd::getSfdz,"地址未知") @@ -145,7 +201,7 @@ public class DataMigrationService { if (StrUtil.isBlank(query)) continue; log.debug("📡 正在调用第三方 API 查询: {}", query); // ✅ 加日志 // 调用接口获取解析结果 - ParsedResult result = callThirdPartyApi(query); + ParsedResult result = callThirdPartyApiNew(query); if (result == null || StrUtil.isBlank(result.getName())) { continue; } @@ -165,6 +221,10 @@ public class DataMigrationService { poi.setAddress(result.getAddress()); poi.setLatitude(src.getYzb()); poi.setLongitude(src.getXzb()); + poi.setJzssfjdm(src.getJzssfjdm()); + poi.setJzssfjmc(src.getJzssfjmc()); + poi.setXqdwdm(src.getXqdwdm()); + poi.setXqdwmc(src.getXqdwmc()); poi.setGadwdm(src.getJzxqdwdm()); poi.setGadwmc(src.getJzxqdwmc()); poi.setPoiTypeCode(src.getSfdzfldm()); @@ -181,6 +241,10 @@ public class DataMigrationService { poi.setAddress(result.getAddress()); poi.setLatitude(src.getYzb()); poi.setLongitude(src.getXzb()); + poi.setJzssfjdm(src.getJzssfjdm()); + poi.setJzssfjmc(src.getJzssfjmc()); + poi.setXqdwdm(src.getXqdwdm()); + poi.setXqdwmc(src.getXqdwmc()); poi.setGadwdm(src.getJzxqdwdm()); poi.setGadwmc(src.getJzxqdwmc()); poi.setPoiTypeCode(src.getSfdzfldm()); @@ -197,6 +261,10 @@ public class DataMigrationService { poi.setAddress(result.getAddress()); poi.setLatitude(src.getYzb()); poi.setLongitude(src.getXzb()); + poi.setJzssfjdm(src.getJzssfjdm()); + poi.setJzssfjmc(src.getJzssfjmc()); + poi.setXqdwdm(src.getXqdwdm()); + poi.setXqdwmc(src.getXqdwmc()); poi.setGadwdm(src.getJzxqdwdm()); poi.setGadwmc(src.getJzxqdwmc()); poi.setPoiTypeCode(src.getSfdzfldm()); @@ -212,6 +280,10 @@ public class DataMigrationService { aoi.setAddress(result.getAddress().replaceAll(result.getPoi(),"")); aoi.setLatitude(src.getYzb()); aoi.setLongitude(src.getXzb()); + poi.setJzssfjdm(src.getJzssfjdm()); + poi.setJzssfjmc(src.getJzssfjmc()); + poi.setXqdwdm(src.getXqdwdm()); + poi.setXqdwmc(src.getXqdwmc()); aoi.setGadwdm(src.getJzxqdwdm()); aoi.setGadwmc(src.getJzxqdwmc()); aoi.setPoiTypeCode(src.getSfdzfldm()); @@ -295,6 +367,114 @@ public class DataMigrationService { } } + + /* + * + * 新版调用大模型解析 + * */ + private ParsedResult callThirdPartyApiNew(String query) { + try { + // 构造 messages + JSONArray messages = new JSONArray(); + messages.add(new JSONObject().set("role", "system").set("content", SYSTEM_PROMPT)); + messages.add(new JSONObject().set("role", "user").set("content", query)); + + // 构建请求体 + JSONObject requestBody = new JSONObject() + .set("model", "Qwen3-32B") + .set("messages", messages) + .set("max_tokens", 3000) + .set("temperature", 0) + .set("seed",42) + .set("top_p", 0.9); + + // 发送 HTTP 请求 + String response = HttpUtil.createPost(apiUrl) + .header("Authorization", "Bearer " + bearerToken) + .header("Content-Type", "application/json") + .timeout(15000) + .body(requestBody.toString()) + .execute() + .body(); + + log.debug("📡 原始响应: {}", response); + + // 解析响应 + JSONObject jsonResponse = JSONUtil.parseObj(response); + JSONArray choices = jsonResponse.getJSONArray("choices"); + if (choices == null || choices.isEmpty()) { + log.warn("❌ API 返回无 choices"); + return null; + } + + String content = choices.get(0, JSONObject.class) + .getJSONObject("message") + .getStr("content"); + + if (StrUtil.isBlank(content)) { + log.warn("❌ content 为空"); + return null; + } + + // 提取纯 JSON + String cleanJson = extractPureJsonFromContent(content); + if (StrUtil.isBlank(cleanJson)) { + log.warn("❌ 无法提取有效 JSON,原始内容: {}", content); + return null; + } + + JSONObject resultJson = JSONUtil.parseObj(cleanJson); + String aoi = resultJson.getStr("aoi", ""); + String poi = resultJson.getStr("poi", ""); + String address = resultJson.getStr("address", ""); + + String name = StrUtil.isNotBlank(aoi) ? aoi : address; + if (StrUtil.isBlank(name)) { + log.warn("❌ name 为空,跳过该结果"); + return null; + } + + return new ParsedResult(name, aoi, poi, address); + + } catch (Exception e) { + log.error("调用新接口失败,query={}", query, e); + return null; + } + } + + /* + * 解析新版大模型返回数据 + * + * */ + private String extractPureJsonFromContent(String content) { + if (StrUtil.isBlank(content)) return null; + + // 移除 ... + content = content.replaceAll("(?s).*?", ""); + + // 去掉首尾空白 + content = content.trim(); + + // 尝试直接解析 + if (JSONUtil.isJson(content)) { + return content; + } + + // 尝试从可能包含的 markdown 或文本中提取 {...} + Pattern pattern = Pattern.compile("\\{\\s*\"aoi\"\\s*:.*?\\}", Pattern.DOTALL); + Matcher matcher = pattern.matcher(content); + if (matcher.find()) { + String jsonCandidate = matcher.group().trim(); + if (JSONUtil.isJson(jsonCandidate)) { + return jsonCandidate; + } + } + + // 最后尝试修复常见问题:补全引号、去掉注释等(可选) + return null; + } + + // 提取 ```json{...}``` 中内容 private String extractJsonFromMarkdown(String answer) { Pattern pattern = Pattern.compile("```json\\s*([\\s\\S]*?)\\s*```", Pattern.CASE_INSENSITIVE); diff --git a/stwzhj-modules/stwzhj-extract/src/main/resources/mapper/extract/JqdMapper.xml b/stwzhj-modules/stwzhj-extract/src/main/resources/mapper/extract/JqdMapper.xml index cc1a03ef..ab8fe696 100644 --- a/stwzhj-modules/stwzhj-extract/src/main/resources/mapper/extract/JqdMapper.xml +++ b/stwzhj-modules/stwzhj-extract/src/main/resources/mapper/extract/JqdMapper.xml @@ -9,7 +9,7 @@