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.
Files changed (48) hide show
  1. package/README.md +68 -12
  2. package/dist/assets/index-Bzt3QHIz.js +102 -0
  3. package/dist/index.html +1 -1
  4. package/package.json +17 -8
  5. package/packages/guanwei-api/package.json +26 -0
  6. package/packages/guanwei-api/src/mcp-http.ts +89 -0
  7. package/packages/guanwei-api/src/mcp.ts +108 -0
  8. package/packages/guanwei-api/src/routes/arts.ts +23 -0
  9. package/packages/guanwei-api/src/routes/chart.ts +24 -0
  10. package/packages/guanwei-api/src/server.ts +96 -0
  11. package/server/package.json +24 -0
  12. package/server/src/index.ts +72 -16
  13. package/server/src/routes/ai.ts +10 -22
  14. package/server/src/routes/divine.ts +34 -33
  15. package/server/src/routes/hour.ts +4 -0
  16. package/server/src/routes/tarot.ts +30 -7
  17. package/server/src/routes/users.ts +118 -123
  18. package/server/src/services/auth.ts +50 -0
  19. package/server/src/services/dataDir.ts +95 -0
  20. package/server/src/services/db.ts +131 -0
  21. package/server/src/services/divineStore.ts +9 -55
  22. package/server/src/services/duanyu.ts +3 -3
  23. package/server/src/services/llmProvider.ts +4 -3
  24. package/server/src/services/usersStore.ts +221 -0
  25. package/shared/core/data/gua64.ts +5 -5
  26. package/shared/core/data/qimen.ts +11 -9
  27. package/shared/core/data/ziwei.ts +16 -15
  28. package/shared/core/engine/bazi.ts +64 -13
  29. package/shared/core/engine/calendar.ts +18 -5
  30. package/shared/core/engine/chart.ts +3 -1
  31. package/shared/core/engine/liuren.ts +32 -13
  32. package/shared/core/engine/liuyao.ts +6 -1
  33. package/shared/core/engine/qimen.ts +102 -60
  34. package/shared/core/engine/trueSolarTime.ts +9 -6
  35. package/shared/core/engine/xiaoliuren.ts +6 -2
  36. package/shared/core/engine/ziwei.ts +4 -3
  37. package/shared/core/types.ts +7 -7
  38. package/src/components/arts/XiaoliurenArt.tsx +1 -1
  39. package/src/pages/AuthPage.tsx +3 -0
  40. package/src/pages/DemoPage.tsx +1 -1
  41. package/src/services/api.ts +38 -6
  42. package/src/utils/userStore.ts +7 -3
  43. package/vite.config.ts +7 -3
  44. package/dist/assets/index-BZTUsUN1.js +0 -102
  45. package/dist/assets/index-BZTUsUN1.js.map +0 -1
  46. package/scripts/release.sh +0 -81
  47. package/server/src/data/db.json +0 -7226
  48. 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
- 规则:昼生(卯~申时支 2..7)太阳+1 级、太阴-1 级;夜生反之;庙/陷为界不越 */
161
- const LV: Record<string, number> = { 陷: 1, 平: 2, 利: 3, 得: 4, 旺: 5, 庙: 6 };
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 >= 2 && correctedHour <= 7 ? 'day' : 'night';
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;
@@ -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={['子(1)', '丑(2)', '寅(3)', '卯(4)', '辰(5)', '巳(6)', '午(7)', '未(8)', '申(9)', '酉(10)', '戌(11)', '亥(12)'].map((t, i) => ({ value: String(i + 1), label: t }))} onChange={v => setH(Number(v))} />
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>
@@ -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>
@@ -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) };
@@ -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
- return res.json();
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
 
@@ -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
- // 前端注册/登录是本地体系;后端 db.json 无此用户时自动注册(同密码)以获取 token
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
- // 后端无此用户 → 注册(自动建档即可;注册接口对已有用户返回 400)
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: 'hidden',
14
+ // 生产不产出 sourcemap(原 'hidden' 仍生成 .map 文件,随 npm 包分发且可被公开访问 → 源码泄漏)
15
+ sourcemap: false,
12
16
  },
13
17
  server: {
14
18
  proxy: {
15
19
  '/api': {
16
- target: 'http://localhost:3018',
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: 'http://localhost:3018',
29
+ target: API_TARGET,
26
30
  changeOrigin: true,
27
31
  },
28
32
  },