guanwei 1.3.2 → 1.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +68 -12
- package/dist/assets/index-Bzt3QHIz.js +102 -0
- package/dist/index.html +1 -1
- package/package.json +17 -8
- package/packages/guanwei-api/package.json +26 -0
- package/packages/guanwei-api/src/mcp-http.ts +89 -0
- package/packages/guanwei-api/src/mcp.ts +108 -0
- package/packages/guanwei-api/src/routes/arts.ts +23 -0
- package/packages/guanwei-api/src/routes/chart.ts +24 -0
- package/packages/guanwei-api/src/server.ts +96 -0
- package/server/package.json +24 -0
- package/server/src/index.ts +72 -16
- package/server/src/routes/ai.ts +10 -22
- package/server/src/routes/divine.ts +34 -33
- package/server/src/routes/hour.ts +4 -0
- package/server/src/routes/tarot.ts +30 -7
- package/server/src/routes/users.ts +118 -123
- package/server/src/services/auth.ts +50 -0
- package/server/src/services/dataDir.ts +95 -0
- package/server/src/services/db.ts +131 -0
- package/server/src/services/divineStore.ts +9 -55
- package/server/src/services/duanyu.ts +3 -3
- package/server/src/services/llmProvider.ts +4 -3
- package/server/src/services/usersStore.ts +221 -0
- package/shared/core/data/gua64.ts +5 -5
- package/shared/core/data/qimen.ts +11 -9
- package/shared/core/data/ziwei.ts +16 -15
- package/shared/core/engine/bazi.ts +64 -13
- package/shared/core/engine/calendar.ts +18 -5
- package/shared/core/engine/chart.ts +3 -1
- package/shared/core/engine/liuren.ts +32 -13
- package/shared/core/engine/liuyao.ts +6 -1
- package/shared/core/engine/qimen.ts +102 -60
- package/shared/core/engine/trueSolarTime.ts +9 -6
- package/shared/core/engine/xiaoliuren.ts +6 -2
- package/shared/core/engine/ziwei.ts +4 -3
- package/shared/core/types.ts +7 -7
- package/src/components/arts/XiaoliurenArt.tsx +1 -1
- package/src/pages/AuthPage.tsx +3 -0
- package/src/pages/DemoPage.tsx +1 -1
- package/src/services/api.ts +38 -6
- package/src/utils/userStore.ts +7 -3
- package/vite.config.ts +7 -3
- package/dist/assets/index-BZTUsUN1.js +0 -102
- package/dist/assets/index-BZTUsUN1.js.map +0 -1
- package/scripts/release.sh +0 -81
- package/server/src/data/db.json +0 -7226
- package/server/src/data/guanwei.db +0 -0
|
@@ -157,10 +157,11 @@ export function ziweiCalc(input: ZiweiInput): ZiweiResult {
|
|
|
157
157
|
});
|
|
158
158
|
|
|
159
159
|
/* 3.1 昼夜调整(修正反馈:太阳喜昼、太阴喜夜,brightness 只按宫位未计生时昼夜)
|
|
160
|
-
规则:昼生(卯~申时支
|
|
161
|
-
|
|
160
|
+
规则:昼生(卯~申时支 3..8,时辰序 0=子)太阳+1 级、太阴-1 级;夜生反之;庙/陷为界不越
|
|
161
|
+
(2026-09 修正:原 2..7 实为寅~未,把凌晨寅时误判为昼、下午申时误判为夜;昼夜分界按卯~申对齐) */
|
|
162
|
+
const LV: Record<string, number> = { 不: 1, 陷: 1, 平: 2, 利: 3, 得: 4, 旺: 5, 庙: 6 };
|
|
162
163
|
const LV_NAME = ['', '陷', '平', '利', '得', '旺', '庙'];
|
|
163
|
-
const dayNight: 'day' | 'night' = correctedHour >=
|
|
164
|
+
const dayNight: 'day' | 'night' = correctedHour >= 3 && correctedHour <= 8 ? 'day' : 'night';
|
|
164
165
|
const effBrightness: Record<string, string> = {};
|
|
165
166
|
for (const s of Object.keys(brightness)) {
|
|
166
167
|
let lv = LV[brightness[s]] || 2;
|
package/shared/core/types.ts
CHANGED
|
@@ -87,10 +87,10 @@ export interface ZiweiResult {
|
|
|
87
87
|
fuStars: Record<string, number>; // 辅星位置(0=寅起)
|
|
88
88
|
sihua: { lu: string; quan: string; ke: string; ji: string }; // 生年四化星名
|
|
89
89
|
sihuaPos: { lu?: number; quan?: number; ke?: number; ji?: number }; // 四化落宫位
|
|
90
|
-
brightness: Record<string, string>; // 十四主星亮度('庙'|'旺'|'得'|'利'|'平'|'陷'
|
|
90
|
+
brightness: Record<string, string>; // 十四主星亮度('庙'|'旺'|'得'|'利'|'平'|'陷'|'不'(不得地))
|
|
91
91
|
geju: { key: string; name: string; ji: '吉' | '凶' | '平'; desc: string; why: string }[]; // 格局
|
|
92
92
|
// ─── 修正层(2026-09-05 算法边界反馈)───
|
|
93
|
-
dayNight?: 'day' | 'night'; //
|
|
93
|
+
dayNight?: 'day' | 'night'; // 昼生/夜生(按生时:卯~申为昼,酉~寅为夜;时辰序 0=子)
|
|
94
94
|
effBrightness?: Record<string, string>; // 昼夜调整后有效亮度(太阳喜昼、太阴喜夜)
|
|
95
95
|
borrowedStars?: Record<number, string[]>; // 空宫借对宫主星(十二宫位 → 所借主星)
|
|
96
96
|
}
|
|
@@ -101,12 +101,12 @@ export interface QimenResult {
|
|
|
101
101
|
yin: boolean; ju: number; jqName: string;
|
|
102
102
|
dayGZ: string; hourGZ: string;
|
|
103
103
|
xunShou: string; xunshouName: string;
|
|
104
|
-
zfStar: string; zsMen: string; zfPalace: number;
|
|
104
|
+
zfStar: string; zsMen: string; zfPalace: number; // 值符星/值使门/值符转后落宫(入中寄坤=2)
|
|
105
105
|
pan: Record<number, { yi: string; men: string; star: string }>;
|
|
106
|
-
// ─── 补齐层(2026-08-20
|
|
107
|
-
zsPalace: number; //
|
|
108
|
-
tianYi: Record<number, string>; //
|
|
109
|
-
shen: Record<number, string>; //
|
|
106
|
+
// ─── 补齐层(2026-08-20;2026-09 起转盘五层对齐拆补权威口径)───
|
|
107
|
+
zsPalace: number; // 值使门飞落宫(入中寄坤=2)
|
|
108
|
+
tianYi: Record<number, string>; // 天盘奇仪(地盘旋转,符首→时干宫;中宫原地盘干)
|
|
109
|
+
shen: Record<number, string>; // 八神布宫(阳顺阴逆,值符神落时干宫;阳用勾陈朱雀、阴用白虎玄武)
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
// 梅花输入输出
|
|
@@ -40,7 +40,7 @@ export function XiaoliurenPanel({ onDivine }: PanelProps) {
|
|
|
40
40
|
</div>
|
|
41
41
|
)}
|
|
42
42
|
<div className="field"><label htmlFor="xlr-h">时(时辰序)</label>
|
|
43
|
-
<SongSelect id="xlr-h" value={String(h)} options={['子(
|
|
43
|
+
<SongSelect id="xlr-h" value={String(h)} options={['子(0)', '丑(1)', '寅(2)', '卯(3)', '辰(4)', '巳(5)', '午(6)', '未(7)', '申(8)', '酉(9)', '戌(10)', '亥(11)'].map((t, i) => ({ value: String(i), label: t }))} onChange={v => setH(Number(v))} />
|
|
44
44
|
</div>
|
|
45
45
|
{mode === 'num' && (
|
|
46
46
|
<div className="field"><label htmlFor="xlr-n3">三数</label><input className="input-line" type="number" id="xlr-n3" min={1} max={12} value={n3} onChange={e => setN3(Number(e.target.value))} /></div>
|
package/src/pages/AuthPage.tsx
CHANGED
|
@@ -93,6 +93,9 @@ export default function AuthPage() {
|
|
|
93
93
|
</div>
|
|
94
94
|
<div className="field"><label>名号</label><input className="input-line" placeholder="取一雅号" value={username} onChange={e => setUsername(e.target.value)} /></div>
|
|
95
95
|
<div className="field"><label>密语</label><input className="input-line" type="password" placeholder="四字以上" value={password} onChange={e => setPassword(e.target.value)} /></div>
|
|
96
|
+
<p className="muted" style={{ fontSize: '.78rem', marginTop: '-.4rem' }}>
|
|
97
|
+
本地档案仅作离线体验:口令只在本机校验(未上传服务端),<b>不具备真正的保密能力</b>;请勿在本地档案中填写敏感信息。
|
|
98
|
+
</p>
|
|
96
99
|
<button className="btn-divine" onClick={submit}>{mode === 'register' ? '入 馆' : '入 座'}</button>
|
|
97
100
|
{msg && <p className={"mt-3 " + (msgOk ? 'tag-cool' : 'tag-hot')}>{msg}</p>}
|
|
98
101
|
</div>
|
package/src/pages/DemoPage.tsx
CHANGED
|
@@ -84,7 +84,7 @@ export default function DemoPage() {
|
|
|
84
84
|
case 'meihua': return () => meihuaCalc({ mode: 'number', n1, n2, n3, now: new Date() } as any);
|
|
85
85
|
case 'liuyao': return () => liuyaoCalc(undefined, { y, m, d });
|
|
86
86
|
case 'liuren': return () => liurenCalc(new Date(y, m - 1, d, hour, 0));
|
|
87
|
-
case 'xiaoliuren': return () => xiaoliurenCalc('time', m, d, Math.floor((hour % 24) / 2), n1, n2, n3);
|
|
87
|
+
case 'xiaoliuren': return () => xiaoliurenCalc('time', m, d, Math.floor(((hour % 24) + 1) % 24 / 2), n1, n2, n3);
|
|
88
88
|
case 'tarot': return () => {
|
|
89
89
|
const spread = allTarotSpreads().find((x: any) => x.id === 'three') || allTarotSpreads()[0];
|
|
90
90
|
return { spread, cards: tarotDraw(3) };
|
package/src/services/api.ts
CHANGED
|
@@ -6,13 +6,21 @@ const API_BASE = (import.meta.env.VITE_API_BASE as string | undefined) || '/api'
|
|
|
6
6
|
|
|
7
7
|
// 云同步 token(登录后 localStorage 会话中保存):所有需要归属校验的请求带上,
|
|
8
8
|
// 后端有 token 即以 token 解析用户,堵「自报 username」越权(H-NEW1 修复)
|
|
9
|
-
function authHeaders(): Record<string, string> {
|
|
9
|
+
function authHeaders(username?: string): Record<string, string> {
|
|
10
10
|
try {
|
|
11
11
|
const sess = JSON.parse(localStorage.getItem('guanwei_session') || 'null');
|
|
12
12
|
if (sess?.token) return { 'X-Guanwei-Token': sess.token };
|
|
13
|
+
// 本地占位账号(未注册):带上起占时下发的 claimToken,后端据此校验归属
|
|
14
|
+
const claim = (username && claimTokenOf(username)) || claimTokenOf(sess?.username || '') || currentClaimToken();
|
|
15
|
+
if (claim) return { 'X-Guanwei-Token': claim };
|
|
13
16
|
} catch { /* ignore */ }
|
|
14
17
|
return {};
|
|
15
18
|
}
|
|
19
|
+
|
|
20
|
+
/** 当前生效的占位账号凭据(单机本地流程通常只有一个) */
|
|
21
|
+
function currentClaimToken(): string {
|
|
22
|
+
try { return localStorage.getItem(CLAIM_CURRENT_KEY) || ''; } catch { return ''; }
|
|
23
|
+
}
|
|
16
24
|
// 诊断:全局暴露当前 API 基址
|
|
17
25
|
if (typeof window !== 'undefined') { (window as any).__API_BASE__ = API_BASE; }
|
|
18
26
|
console.log('[观微] API_BASE =', API_BASE);
|
|
@@ -256,7 +264,7 @@ export interface DivineResult {
|
|
|
256
264
|
export async function apiDivine(username: string, artId: string, inputs: unknown, profile?: unknown, question?: string, _profileId?: string): Promise<DivineResult> {
|
|
257
265
|
const res = await fetch(API_BASE + '/divine', {
|
|
258
266
|
method: 'POST',
|
|
259
|
-
headers: { 'Content-Type': 'application/json', ...authHeaders() },
|
|
267
|
+
headers: { 'Content-Type': 'application/json', ...authHeaders(username) },
|
|
260
268
|
body: JSON.stringify({ username, artId, inputs, profile, question }),
|
|
261
269
|
});
|
|
262
270
|
if (!res.ok) {
|
|
@@ -264,7 +272,31 @@ export async function apiDivine(username: string, artId: string, inputs: unknown
|
|
|
264
272
|
try { const e = await res.json(); msg = e.message || e.error || msg; } catch { /* ignore */ }
|
|
265
273
|
throw new Error(msg);
|
|
266
274
|
}
|
|
267
|
-
|
|
275
|
+
const data = await res.json();
|
|
276
|
+
// L-NEW1 闭环:后端在「本次新建占位账号」时下发 claimToken(仅此一次),
|
|
277
|
+
// 保存到本地,供之后在 app 内注册时升级该占位账号(否则注册接口拒绝、拿不到云同步 token)
|
|
278
|
+
if (data?.claimToken) saveClaimToken(username, String(data.claimToken));
|
|
279
|
+
return data;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const CLAIM_KEY = (name: string) => 'guanwei_claim_' + name;
|
|
283
|
+
const CLAIM_CURRENT_KEY = 'guanwei_claim_current';
|
|
284
|
+
|
|
285
|
+
/** 保存/读取占位账号认领凭据(仅本地持有,随注册一次性使用) */
|
|
286
|
+
export function saveClaimToken(username: string, token: string): void {
|
|
287
|
+
try {
|
|
288
|
+
localStorage.setItem(CLAIM_KEY(username), token);
|
|
289
|
+
localStorage.setItem(CLAIM_CURRENT_KEY, token); // 供无 username 上下文的请求(如 AI 解读)回退使用
|
|
290
|
+
} catch { /* ignore */ }
|
|
291
|
+
}
|
|
292
|
+
export function claimTokenOf(username: string): string {
|
|
293
|
+
try { return localStorage.getItem(CLAIM_KEY(username)) || ''; } catch { return ''; }
|
|
294
|
+
}
|
|
295
|
+
export function clearClaimToken(username: string): void {
|
|
296
|
+
try {
|
|
297
|
+
localStorage.removeItem(CLAIM_KEY(username));
|
|
298
|
+
if (localStorage.getItem(CLAIM_CURRENT_KEY) === localStorage.getItem(CLAIM_KEY(username))) localStorage.removeItem(CLAIM_CURRENT_KEY);
|
|
299
|
+
} catch { /* ignore */ }
|
|
268
300
|
}
|
|
269
301
|
|
|
270
302
|
export interface DivineHistoryItem {
|
|
@@ -277,19 +309,19 @@ export interface DivineHistoryItem {
|
|
|
277
309
|
}
|
|
278
310
|
|
|
279
311
|
export async function apiDivineHistory(username: string, page = 1, pageSize = 20, profileId?: string): Promise<{ list: DivineHistoryItem[]; total: number }> {
|
|
280
|
-
const res = await fetch(API_BASE + '/divine?username=' + encodeURIComponent(username) + '&page=' + page + '&pageSize=' + pageSize + (profileId ? '&profileId=' + encodeURIComponent(profileId) : ''), { headers: authHeaders() });
|
|
312
|
+
const res = await fetch(API_BASE + '/divine?username=' + encodeURIComponent(username) + '&page=' + page + '&pageSize=' + pageSize + (profileId ? '&profileId=' + encodeURIComponent(profileId) : ''), { headers: authHeaders(username) });
|
|
281
313
|
if (!res.ok) return { list: [], total: 0 };
|
|
282
314
|
return res.json();
|
|
283
315
|
}
|
|
284
316
|
|
|
285
317
|
export async function apiDivineDetail(id: string, username: string): Promise<Record<string, unknown> | null> {
|
|
286
|
-
const res = await fetch(API_BASE + '/divine/' + id + '?username=' + encodeURIComponent(username), { headers: authHeaders() });
|
|
318
|
+
const res = await fetch(API_BASE + '/divine/' + id + '?username=' + encodeURIComponent(username), { headers: authHeaders(username) });
|
|
287
319
|
if (!res.ok) return null;
|
|
288
320
|
return res.json();
|
|
289
321
|
}
|
|
290
322
|
|
|
291
323
|
export async function apiDivineDelete(id: string, username: string): Promise<boolean> {
|
|
292
|
-
const res = await fetch(API_BASE + '/divine/' + id + '?username=' + encodeURIComponent(username), { method: 'DELETE', headers: authHeaders() });
|
|
324
|
+
const res = await fetch(API_BASE + '/divine/' + id + '?username=' + encodeURIComponent(username), { method: 'DELETE', headers: authHeaders(username) });
|
|
293
325
|
return res.ok;
|
|
294
326
|
}
|
|
295
327
|
|
package/src/utils/userStore.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// 本地用户体系(演示级):注册/登录/会话/出生档案
|
|
2
2
|
// 注意:仅本地存储(localStorage),密码为演示级哈希,不适用于生产环境
|
|
3
3
|
import type { GeoLocation } from '@core/types';
|
|
4
|
+
import { claimTokenOf, clearClaimToken } from '../services/api';
|
|
4
5
|
|
|
5
6
|
export interface UserProfile {
|
|
6
7
|
birthDate: string; // YYYY-MM-DD(公历)
|
|
@@ -133,7 +134,7 @@ export function login(username: string, password: string): AuthResult {
|
|
|
133
134
|
}
|
|
134
135
|
|
|
135
136
|
// 向后端登录/注册换取云同步 token(失败静默——离线纯本地场景不受影响)
|
|
136
|
-
//
|
|
137
|
+
// 前端注册/登录是本地体系;后端用户库(SQLite users 表)无此用户时自动注册(同密码)以获取 token
|
|
137
138
|
async function fetchServerToken(username: string, password: string): Promise<void> {
|
|
138
139
|
try {
|
|
139
140
|
let res = await fetch(API + '/login', {
|
|
@@ -142,13 +143,16 @@ async function fetchServerToken(username: string, password: string): Promise<voi
|
|
|
142
143
|
body: JSON.stringify({ username, password }),
|
|
143
144
|
});
|
|
144
145
|
if (!res.ok) {
|
|
145
|
-
// 后端无此用户 →
|
|
146
|
+
// 后端无此用户 → 注册;若该名号已被「起占自动建档」占位,须携带建档时下发的 claimToken 方可升级
|
|
147
|
+
// (claimToken 由起占响应下发并由 services/api 落地本地,见 L-NEW1 闭环)
|
|
148
|
+
const claimToken = claimTokenOf(username);
|
|
146
149
|
res = await fetch(API + '/register', {
|
|
147
150
|
method: 'POST',
|
|
148
151
|
headers: { 'Content-Type': 'application/json' },
|
|
149
|
-
body: JSON.stringify({ username, password }),
|
|
152
|
+
body: JSON.stringify({ username, password, ...(claimToken ? { claimToken } : {}) }),
|
|
150
153
|
});
|
|
151
154
|
if (!res.ok) return;
|
|
155
|
+
clearClaimToken(username); // 一次性凭据,成功升级后即弃
|
|
152
156
|
}
|
|
153
157
|
const data = await res.json();
|
|
154
158
|
if (data.token) {
|
package/vite.config.ts
CHANGED
|
@@ -3,17 +3,21 @@ import react from '@vitejs/plugin-react'
|
|
|
3
3
|
import tsconfigPaths from "vite-tsconfig-paths";
|
|
4
4
|
|
|
5
5
|
// https://vite.dev/config/
|
|
6
|
+
// 后端地址:跟随 CLI 的 PORT/API_PORT(`guanwei start --api 4000` 时前端代理需同步)
|
|
7
|
+
const API_TARGET = `http://localhost:${process.env.API_PORT || process.env.PORT || 3018}`;
|
|
8
|
+
|
|
6
9
|
export default defineConfig({
|
|
7
10
|
test: {
|
|
8
11
|
environment: 'jsdom',
|
|
9
12
|
},
|
|
10
13
|
build: {
|
|
11
|
-
sourcemap
|
|
14
|
+
// 生产不产出 sourcemap(原 'hidden' 仍生成 .map 文件,随 npm 包分发且可被公开访问 → 源码泄漏)
|
|
15
|
+
sourcemap: false,
|
|
12
16
|
},
|
|
13
17
|
server: {
|
|
14
18
|
proxy: {
|
|
15
19
|
'/api': {
|
|
16
|
-
target:
|
|
20
|
+
target: API_TARGET,
|
|
17
21
|
changeOrigin: true,
|
|
18
22
|
},
|
|
19
23
|
},
|
|
@@ -22,7 +26,7 @@ export default defineConfig({
|
|
|
22
26
|
preview: {
|
|
23
27
|
proxy: {
|
|
24
28
|
'/api': {
|
|
25
|
-
target:
|
|
29
|
+
target: API_TARGET,
|
|
26
30
|
changeOrigin: true,
|
|
27
31
|
},
|
|
28
32
|
},
|