chore(repo): 导入 M-Z 业务底座 V1.9 基线并建立仓库规范

按周工交付的 SEC-Z-01 清理后源码建立 M-Z 主干,并按 Git 仓库惯例重排目录。

- aomin-gateway/  后端工程 87 文件,原样保留,路径与构建命令未变
- demo/           演示前端与设备模拟器 9 文件(原 02_前端与设备端),非正式三端
- docs/handover/  交付与安全清理材料 6 份,内容未改
- docs/contract/  J-00 规格、OpenAPI、内部五接口、状态机、Mock 样例
- docs/reference/ 指纹与校验清单,含 84 个源码文件的 sha256 清单

目录重排只动位置不动内容:源码树 84 文件哈希与交付一致,逐文件自校验通过;
12 条 Flyway 迁移与交付包内镜像逐字节一致。

未纳入:迁移镜像目录(与工程内一套重复,改留指纹)、target/、证据 raw、
应急恢复脚本、评审用虚构台账数据。

安全:SEC-Z-01 15 处明文已清零,导入前重新扫描无新增命中,本仓库无历史污染。
行尾按交付字节原样保存(.gitattributes 设 * -text),不做归一化以免整树摘要失效。
This commit is contained in:
aomin committed 2026-11-13 17:32:37 +08:00
commit a7605ef8b8
121 files changed
+23629

No files matched your search

+252
View File
@@ -0,0 +1,252 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<title>选择家的位置</title>
<!--
百度地图选点(在线底图 + 离线兜底)
─────────────────────────────────────────────────────────
· 底图用百度 JS API v2.0,ak 从下方 AK 常量读;
· **坐标系必须转**:百度用 BD-09,我们库里存 GCJ-02,两者差 500~600 米。
取点后 bd09ToGcj02() 转换再回传,否则存进去的家会落在隔壁街道;
· ak 失效 / 没网 / 白名单没放行时,3 秒内自动跳 map-offline.html,不让用户看白屏。
-->
<style>
:root{--brand:#146b7a;--ink:#1b2430;--sub:#6b7784;--line:#e4eaef}
*{box-sizing:border-box;-webkit-tap-highlight-color:transparent}
html,body{margin:0;height:100%;overflow:hidden}
body{font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei",sans-serif;
background:#eef2f4;color:var(--ink);font-size:14px;display:flex;flex-direction:column}
.top{flex:0 0 auto;background:var(--brand);color:#fff;padding:11px 12px;display:flex;align-items:center;gap:9px;z-index:10}
.top button{background:rgba(255,255,255,.16);border:0;color:#fff;border-radius:9px;padding:7px 11px;font-size:13px;cursor:pointer}
.top h1{font-size:15px;margin:0;flex:1;font-weight:600}
.stage{flex:1;min-height:200px;position:relative}
#map{width:100%;height:100%}
/* 中心准星:拖地图选点,比"点哪儿算哪儿"在手机上准得多 */
#pin{position:absolute;left:50%;top:50%;transform:translate(-50%,-100%);pointer-events:none;z-index:5}
#pin svg{filter:drop-shadow(0 2px 4px rgba(0,0,0,.35))}
#pinTip{position:absolute;left:50%;top:50%;transform:translate(-50%,6px);font-size:10.5px;
color:#fff;background:rgba(20,40,55,.62);padding:2px 8px;border-radius:10px;pointer-events:none;z-index:5}
#loading{position:absolute;inset:0;background:#f2f6f8;display:flex;align-items:center;justify-content:center;
flex-direction:column;gap:10px;z-index:8;font-size:13px;color:var(--sub)}
#loading .sp{width:26px;height:26px;border:3px solid #d6e2e6;border-top-color:var(--brand);border-radius:50%;
animation:spin .9s linear infinite}
@keyframes spin{to{transform:rotate(360deg)}}
.bottom{flex:0 0 auto;background:#fff;border-top:1px solid var(--line);padding:10px 12px 12px;z-index:10}
.addr{font-size:13px;color:var(--ink);margin-bottom:5px;min-height:18px;line-height:1.5;font-weight:600}
.sub{font-size:11.5px;color:var(--sub);margin-bottom:9px;line-height:1.5}
.coord{font-family:Consolas,Menlo,monospace;font-size:11.5px;color:var(--brand);margin-bottom:9px}
.row{display:flex;gap:7px;align-items:center;margin-bottom:8px}
.row input{flex:1;min-width:0;border:1px solid var(--line);border-radius:8px;padding:8px 9px;font-size:12.5px;outline:none}
.btn{border:0;border-radius:9px;padding:11px 14px;font-size:14px;font-weight:600;cursor:pointer}
.btn-main{background:var(--brand);color:#fff;width:100%}
.btn-main:disabled{background:#c3d3d7}
.btn-line{background:#fff;border:1px solid var(--line);color:var(--ink);white-space:nowrap}
.BMap_cpyCtrl,.anchorBL{display:none!important}
</style>
</head>
<body>
<div class="top">
<button onclick="cancel()">← 返回</button>
<h1>选择家的位置</h1>
<button onclick="locate()">定位</button>
</div>
<div class="stage">
<div id="map"></div>
<div id="pin"><svg width="30" height="40" viewBox="0 0 30 40">
<path d="M15 0C6.7 0 0 6.7 0 15c0 11 15 25 15 25s15-14 15-25C30 6.7 23.3 0 15 0z" fill="#146b7a"/>
<circle cx="15" cy="15" r="5.5" fill="#fff"/></svg></div>
<div id="pinTip">拖动地图,把准星对准你家</div>
<div id="loading"><div class="sp"></div><span id="ldTxt">正在加载地图…</span></div>
</div>
<div class="bottom">
<div class="addr" id="addr">拖动地图选择位置</div>
<div class="sub" id="sub">地址会自动识别,选不准可以手动改</div>
<div class="coord" id="coord">坐标:—(GCJ-02)</div>
<div class="row">
<input id="mlng" placeholder="经度 113.39" inputmode="decimal">
<input id="mlat" placeholder="纬度 22.52" inputmode="decimal">
<button class="btn btn-line" onclick="applyManual()">填入</button>
</div>
<button class="btn btn-main" id="ok" onclick="confirmPick()">确定这个位置</button>
</div>
<script>
/* 百度 ak —— 不写死在源码里:部署时把 __BAIDU_AK__ 替换成自己的 AK,
或用 window.BAIDU_AK 注入;没有 AK 时本页会自动降级到离线地图。 */
var AK = window.BAIDU_AK || '__BAIDU_AK__';
/* ================= 坐标转换(必须) =================
百度 BD-09 / 国测局 GCJ-02 相差 500~600 米。
库里存 GCJ-02,所以「取点 → 转 GCJ」,「显示已知点 → 转 BD」。 */
function bd09ToGcj02(bdLng, bdLat) {
var x = bdLng - 0.0065, y = bdLat - 0.006;
var z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * Math.PI * 3000 / 180);
var t = Math.atan2(y, x) - 0.000003 * Math.cos(x * Math.PI * 3000 / 180);
return { lng: z * Math.cos(t), lat: z * Math.sin(t) };
}
function gcj02ToBd09(lng, lat) {
var z = Math.sqrt(lng * lng + lat * lat) + 0.00002 * Math.sin(lat * Math.PI * 3000 / 180);
var t = Math.atan2(lat, lng) + 0.000003 * Math.cos(lng * Math.PI * 3000 / 180);
return { lng: z * Math.cos(t) + 0.0065, lat: z * Math.sin(t) + 0.006 };
}
var map = null, marker = null, geocoder = null;
var cur = null; // 当前选的 GCJ-02 坐标
var geoTimer = null;
var HOME = { lng: 113.3926, lat: 22.5176 }; // 中山市政府(GCJ-02)
function fallback(reason) {
var t = document.getElementById('ldTxt');
if (t) t.textContent = reason + ',切换到离线地图…';
setTimeout(function () { location.replace('map-offline.html'); }, 700);
}
function initMap() {
if (map) return; // callback 和轮询都可能触发,只认第一次
try {
map = new BMap.Map('map', { enableMapClick: false });
var c = gcj02ToBd09(HOME.lng, HOME.lat);
map.centerAndZoom(new BMap.Point(c.lng, c.lat), 16);
map.enableScrollWheelZoom(true);
map.enableDragging();
geocoder = new BMap.Geocoder();
document.getElementById('loading').style.display = 'none';
map.addEventListener('moveend', onMoveEnd);
onMoveEnd();
} catch (e) {
fallback('地图初始化失败');
}
}
/* 地图停稳后取中心点,转成 GCJ-02 并反查地址 */
function onMoveEnd() {
if (!map) return;
var c = map.getCenter();
var g = bd09ToGcj02(c.lng, c.lat);
cur = { lng: +g.lng.toFixed(6), lat: +g.lat.toFixed(6) };
document.getElementById('coord').textContent =
'坐标:' + cur.lng + ', ' + cur.lat + '(GCJ-02)';
document.getElementById('mlng').value = cur.lng;
document.getElementById('mlat').value = cur.lat;
// 逆地理编码要联网,慢一点,节流 400ms,别拖一下发十个请求
clearTimeout(geoTimer);
geoTimer = setTimeout(function () {
if (!geocoder) return;
geocoder.getLocation(new BMap.Point(c.lng, c.lat), function (r) {
if (!r) { return; }
var a = r.address || {};
var parts = [a.province, a.city, a.district, a.street, a.streetNumber].filter(Boolean);
var poi = (r.surroundingPois && r.surroundingPois[0]) ? r.surroundingPois[0].title : '';
// 街道名常常拼不出完整地址(百度在小区内部只回商圈),所以依次退:街道串 → 最近POI → 商圈
var main = parts.join('') || poi || a.business || '已选位置';
document.getElementById('addr').textContent = main;
document.getElementById('sub').textContent = poi && main !== poi
? '附近:' + poi
: '地址已识别,可手动补充门牌';
});
}, 400);
}
function applyManual() {
var lng = parseFloat(document.getElementById('mlng').value);
var lat = parseFloat(document.getElementById('mlat').value);
if (!isFinite(lng) || !isFinite(lat)) { alert('请填入有效的经纬度数字'); return; }
if (lng < 112.5 || lng > 114.5 || lat < 21.5 || lat > 23.5) {
alert('这个坐标不在中山附近,请填 113.x / 22.x'); return;
}
cur = { lng: +lng.toFixed(6), lat: +lat.toFixed(6) };
var b = gcj02ToBd09(lng, lat);
if (map) { map.centerAndZoom(new BMap.Point(b.lng, b.lat), 17); }
}
function locate() {
if (!navigator.geolocation) { alert('这个浏览器不支持定位'); return; }
navigator.geolocation.getCurrentPosition(function (p) {
// 浏览器给的是 WGS-84,百度要 BD-09:先粗转 GCJ 再转 BD(市域内误差 <10m)
var g = wgs84ToGcj02(p.coords.longitude, p.coords.latitude);
var b = gcj02ToBd09(g.lng, g.lat);
if (map) map.centerAndZoom(new BMap.Point(b.lng, b.lat), 17);
}, function () {
alert('定位失败(可能是没授权或信号不好),直接拖地图选吧');
}, { timeout: 8000 });
}
/* WGS-84 → GCJ-02(国内加密偏移的常用近似实现) */
function wgs84ToGcj02(lng, lat) {
var a = 6378245.0, ee = 0.00669342162296594323;
var dLat = tfLat(lng - 105, lat - 35), dLng = tfLng(lng - 105, lat - 35);
var radLat = lat / 180 * Math.PI, magic = Math.sin(radLat);
magic = 1 - ee * magic * magic;
var sqrtMagic = Math.sqrt(magic);
dLat = (dLat * 180) / ((a * (1 - ee)) / (magic * sqrtMagic) * Math.PI);
dLng = (dLng * 180) / (a / sqrtMagic * Math.cos(radLat) * Math.PI);
return { lng: lng + dLng, lat: lat + dLat };
}
function tfLat(x, y) {
var r = -100 + 2 * x + 3 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x));
r += (20 * Math.sin(6 * x * Math.PI) + 20 * Math.sin(2 * x * Math.PI)) * 2 / 3;
r += (20 * Math.sin(y * Math.PI) + 40 * Math.sin(y / 3 * Math.PI)) * 2 / 3;
r += (160 * Math.sin(y / 12 * Math.PI) + 320 * Math.sin(y * Math.PI / 30)) * 2 / 3;
return r;
}
function tfLng(x, y) {
var r = 300 + x + 2 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x));
r += (20 * Math.sin(6 * x * Math.PI) + 20 * Math.sin(2 * x * Math.PI)) * 2 / 3;
r += (20 * Math.sin(x * Math.PI) + 40 * Math.sin(x / 3 * Math.PI)) * 2 / 3;
r += (150 * Math.sin(x / 12 * Math.PI) + 300 * Math.sin(x / 30 * Math.PI)) * 2 / 3;
return r;
}
function confirmPick() {
if (!cur) { alert('还没选位置'); return; }
var addr = document.getElementById('addr').textContent || '';
var d = {
type: 'heserv:pick',
lng: cur.lng, lat: cur.lat,
name: addr.length > 24 ? addr.slice(0, 24) : addr,
addr: addr,
coordSys: 'GCJ-02',
source: 'baidu'
};
if (window.parent && window.parent !== window) {
window.parent.postMessage(d, '*');
} else {
alert('已选:' + d.lng + ', ' + d.lat);
}
}
function cancel() {
if (window.parent && window.parent !== window) {
window.parent.postMessage({ type: 'heserv:pick-cancel' }, '*');
} else { history.back(); }
}
/* ================= 加载百度脚本 =================
注意:百度 v2.0 的 loader 内部用 document.write 注入主脚本。
如果用 createElement 动态插入,write 在页面加载完后调用会被浏览器忽略,
BMap 永远不出现 —— 表现就是"一直加载中,最后跳离线版"。
所以这里必须用**静态 script 标签**,并靠 &callback= 拿加载完成的通知。 */
function onBMapReady() {
window.__bmapOk = true;
setTimeout(initMap, 80);
}
(function () {
var timer = setInterval(function () {
if (window.BMap && window.__bmapOk !== false) {
clearInterval(timer);
if (!map) initMap();
return;
}
if (window.__bmapTries === undefined) window.__bmapTries = 0;
if (++window.__bmapTries > 22) { // 约 4.4 秒还没起来
clearInterval(timer);
if (!map) fallback('地图加载超时');
}
}, 200);
})();
</script>
<script src="https://api.map.baidu.com/api?v=2.0&ak=__BAIDU_AK__&callback=onBMapReady"></script>
</body>
</html>