辅助地址拆分
parent
e5d32cce80
commit
7346030a2f
|
|
@ -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<String> startMigration() {
|
||||
public ResponseEntity<String> 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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -71,5 +71,13 @@ public class PoiAddress implements Serializable {
|
|||
|
||||
private String gadwmc;
|
||||
|
||||
private String jzssfjdm;
|
||||
|
||||
private String jzssfjmc;
|
||||
|
||||
private String xqdwdm;
|
||||
|
||||
private String xqdwmc;
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Jqd> 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;
|
||||
|
||||
// 移除 <think>...</think>
|
||||
content = content.replaceAll("(?s)<think>.*?</think>", "");
|
||||
|
||||
// 去掉首尾空白
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
</resultMap>
|
||||
|
||||
<select id="selectJq" resultMap="jqdResult">
|
||||
select sfdz,sfdzfldm,sfdzflmc,xzb,yzb,jzxqdwdm,jzxqdwmc,scbjsj from jqd
|
||||
select sfdz,sfdzfldm,sfdzflmc,xzb,yzb,jzssfjdm,jzssfjmc,xqdwdm,xqdwmc,jzxqdwdm,jzxqdwmc,scbjsj from jqd
|
||||
${ew.getCustomSqlSegment}
|
||||
</select>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue