- 新增用户服务,支持多用户数据隔离与认证 - 新增关系历史查询接口,支持按冲突、积极、时间线等类型过滤 - 新增恋爱决策建议接口,基于图谱分析生成关系健康报告 - 优化前端图谱可视化,增加节点详情面板、图例和边标签显示 - 改进文本分析逻辑,支持实体去重和情感标注 - 新增完整流程测试脚本,验证分析、入库、查询全链路
172 lines
4.1 KiB
JavaScript
172 lines
4.1 KiB
JavaScript
import { randomUUID } from 'crypto';
|
|
|
|
/**
|
|
* 用户服务:管理用户 UUID 和认证
|
|
*/
|
|
export class UserService {
|
|
constructor(driver) {
|
|
this.driver = driver;
|
|
}
|
|
|
|
/**
|
|
* 创建或获取用户
|
|
*/
|
|
async getOrCreateUser(token) {
|
|
const session = this.driver.session();
|
|
try {
|
|
// 尝试通过 token 查找用户
|
|
const result = await session.run(
|
|
`MATCH (u:User {token: $token}) RETURN u`,
|
|
{ token }
|
|
);
|
|
|
|
if (result.records.length > 0) {
|
|
const user = result.records[0].get('u');
|
|
return {
|
|
id: user.properties.id,
|
|
token: user.properties.token,
|
|
createdAt: user.properties.created_at
|
|
};
|
|
}
|
|
|
|
// 创建新用户
|
|
const userId = randomUUID();
|
|
const createdAt = new Date().toISOString();
|
|
|
|
await session.run(
|
|
`CREATE (u:User {id: $id, token: $token, created_at: $created_at})`,
|
|
{ id: userId, token, created_at: createdAt }
|
|
);
|
|
|
|
return { id: userId, token, createdAt };
|
|
} finally {
|
|
await session.close();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 验证用户 token
|
|
*/
|
|
async validateUser(token) {
|
|
if (!token) return null;
|
|
|
|
const session = this.driver.session();
|
|
try {
|
|
const result = await session.run(
|
|
`MATCH (u:User {token: $token}) RETURN u`,
|
|
{ token }
|
|
);
|
|
|
|
if (result.records.length > 0) {
|
|
const user = result.records[0].get('u');
|
|
return {
|
|
id: user.properties.id,
|
|
token: user.properties.token
|
|
};
|
|
}
|
|
return null;
|
|
} finally {
|
|
await session.close();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取用户图谱统计
|
|
*/
|
|
async getUserGraphStats(userId) {
|
|
const session = this.driver.session();
|
|
try {
|
|
const result = await session.run(
|
|
`
|
|
MATCH (p:Person {user_id: $userId})
|
|
RETURN p.id AS id, p.name AS name, 'person' AS type, null AS occurred_at
|
|
LIMIT 200
|
|
`,
|
|
{ userId }
|
|
);
|
|
|
|
const persons = result.records.map(r => ({
|
|
id: r.get('id'),
|
|
name: r.get('name'),
|
|
type: r.get('type'),
|
|
occurred_at: r.get('occurred_at')
|
|
}));
|
|
|
|
const events = await session.run(
|
|
`
|
|
MATCH (e:Event {user_id: $userId})
|
|
RETURN e.id AS id, e.summary AS name, 'event' AS type, e.occurred_at AS occurred_at
|
|
LIMIT 200
|
|
`,
|
|
{ userId }
|
|
);
|
|
|
|
const topics = await session.run(
|
|
`
|
|
MATCH (t:Topic {user_id: $userId})
|
|
RETURN t.name AS id, t.name AS name, 'topic' AS type, null AS occurred_at
|
|
LIMIT 100
|
|
`,
|
|
{ userId }
|
|
);
|
|
|
|
const nodes = [
|
|
...persons,
|
|
...events.records.map(r => ({
|
|
id: r.get('id'),
|
|
name: r.get('name'),
|
|
type: r.get('type'),
|
|
occurred_at: r.get('occurred_at')
|
|
})),
|
|
...topics.records.map(r => ({
|
|
id: r.get('id'),
|
|
name: r.get('name'),
|
|
type: r.get('type'),
|
|
occurred_at: r.get('occurred_at')
|
|
}))
|
|
];
|
|
|
|
// 查询关系
|
|
const personEventRels = await session.run(
|
|
`
|
|
MATCH (p:Person {user_id: $userId})-[:PARTICIPATES_IN]->(e:Event {user_id: $userId})
|
|
RETURN p.id AS source, e.id AS target, 'PARTICIPATES_IN' AS type
|
|
LIMIT 500
|
|
`,
|
|
{ userId }
|
|
);
|
|
|
|
const eventTopicRels = await session.run(
|
|
`
|
|
MATCH (e:Event {user_id: $userId})-[:ABOUT]->(t:Topic {user_id: $userId})
|
|
RETURN e.id AS source, t.name AS target, 'ABOUT' AS type
|
|
LIMIT 300
|
|
`,
|
|
{ userId }
|
|
);
|
|
|
|
const links = [
|
|
...personEventRels.records.map(r => ({
|
|
source: r.get('source'),
|
|
target: r.get('target'),
|
|
type: r.get('type')
|
|
})),
|
|
...eventTopicRels.records.map(r => ({
|
|
source: r.get('source'),
|
|
target: r.get('target'),
|
|
type: r.get('type')
|
|
}))
|
|
];
|
|
|
|
return {
|
|
ok: true,
|
|
nodes,
|
|
links,
|
|
total: nodes.length
|
|
};
|
|
} finally {
|
|
await session.close();
|
|
}
|
|
}
|
|
}
|