feat: 新增多用户支持、关系历史查询与恋爱决策建议功能
- 新增用户服务,支持多用户数据隔离与认证 - 新增关系历史查询接口,支持按冲突、积极、时间线等类型过滤 - 新增恋爱决策建议接口,基于图谱分析生成关系健康报告 - 优化前端图谱可视化,增加节点详情面板、图例和边标签显示 - 改进文本分析逻辑,支持实体去重和情感标注 - 新增完整流程测试脚本,验证分析、入库、查询全链路
This commit is contained in:
171
OnceLove/oncelove-graphrag/api/src/services/user.service.js
Normal file
171
OnceLove/oncelove-graphrag/api/src/services/user.service.js
Normal file
@@ -0,0 +1,171 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user