| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133 |
- 'use strict';
- // 校区地图:学校列表(JSON 种子 + 运行时),附带运营数据关联统计。
- // 本期只有只读接口;新建/编辑/启停在下一增量实现。
- const fs = require('fs');
- const path = require('path');
- const { sendJson } = require('../util.js');
- const { loadSchools } = require('../../schools-store.js');
- const { createOpsStore } = require('../../ops-store.js');
- // 导入数据里出现、但还没登记的校区:用本地高校坐标库自动定位(找不到就标未定位)
- let uniCache = null;
- function loadUniversities() {
- if (uniCache) return uniCache;
- try {
- uniCache = JSON.parse(fs.readFileSync(path.join(__dirname, '../../public/vendor/cn-universities.json'), 'utf8'));
- } catch {
- uniCache = {};
- }
- return uniCache;
- }
- function normalizeSchoolName(name) {
- return String(name || '')
- .replace(/\s+/g, '')
- .replace(/[((].*?[))]/g, '')
- .replace(/(校区|分校)$/, '');
- }
- function matchUniversity(name) {
- const uni = loadUniversities();
- const norm = normalizeSchoolName(name);
- if (!norm) return null;
- if (uni[norm]) return { lng: uni[norm].lng, lat: uni[norm].lat, province: uni[norm].province, approx: false };
- let bestKey = '';
- for (const key of Object.keys(uni)) {
- if (key.length < 4) continue;
- if (norm.startsWith(key) && key.length > bestKey.length) bestKey = key;
- }
- if (!bestKey) return null;
- return { lng: uni[bestKey].lng, lat: uni[bestKey].lat, province: uni[bestKey].province, approx: true, base: bestKey };
- }
- function namesOf(school) {
- return new Set([school.name, ...(school.aliases || [])].map((n) => String(n).trim()).filter(Boolean));
- }
- function register(app, ctx) {
- const opsStore = createOpsStore(ctx.config);
- app.get('/api/schools', async ({ res }) => {
- const schools = loadSchools();
- // 关联运营数据:按学校名(含别名)匹配 ops_shop.school 与 ops_row.school
- let shops = [];
- let rows = [];
- let platformRows = [];
- let opsAvailable = false;
- try {
- const summary = await opsStore.summary();
- shops = summary.shops || [];
- rows = await opsStore.queryRows({});
- platformRows = await opsStore.queryPlatformRows({});
- opsAvailable = true;
- } catch { /* ops 库不可用:只影响关联字段 */ }
- // 运营数据里出现、注册表里没有的学校:自动补进地图列表
- const knownNames = new Set();
- for (const school of schools) for (const n of namesOf(school)) knownNames.add(n);
- const discovered = new Set();
- for (const item of [...shops, ...platformRows]) {
- const name = String(item.school || '').trim();
- if (name && !knownNames.has(name)) discovered.add(name);
- }
- const now = new Date().toISOString();
- for (const name of discovered) {
- const hit = matchUniversity(name);
- schools.push({
- id: 'ops_' + Buffer.from(name).toString('hex').slice(0, 12),
- name,
- aliases: [],
- province: (hit && hit.province) || '',
- city: '',
- county: '',
- lng: hit ? hit.lng : null,
- lat: hit ? hit.lat : null,
- source: 'ops',
- seed: false,
- discovered: true,
- approx: !!(hit && hit.approx),
- enabled: !!hit,
- created_at: now,
- updated_at: now,
- });
- }
- const payload = schools.map((school) => {
- const names = namesOf(school);
- // 门店口径:人工表的 shop_key 与平台明细的 shopId 取并集
- const shopKeys = new Set();
- for (const shop of shops) {
- if (names.has(String(shop.school || ''))) shopKeys.add(String(shop.key || shop.shopId || ''));
- }
- for (const row of platformRows) {
- if (names.has(String(row.school || ''))) shopKeys.add(String(row.shopId || ''));
- }
- shopKeys.delete('');
- const shopCount = shopKeys.size;
- let lastDate = '';
- for (const row of rows) {
- if (!names.has(String(row.school || ''))) continue;
- if (!lastDate || row.date > lastDate) lastDate = row.date;
- }
- for (const row of platformRows) {
- if (!names.has(String(row.school || ''))) continue;
- if (!lastDate || row.date > lastDate) lastDate = row.date;
- }
- return {
- ...school,
- ops: opsAvailable ? { shopCount, lastDate: lastDate || null } : null,
- };
- });
- sendJson(res, 200, {
- schools: payload,
- opsAvailable,
- enabledCount: payload.filter((s) => s.enabled && s.lng != null).length,
- });
- });
- }
- module.exports = { register };
|