架构与数据设计
应用结构
单体应用通过 MVC 渲染页面,Fetch 调用同源 JSON。Web 负责登录、输入校验、防伪和用户边界;Core 保持画像与评分逻辑可测试;Infrastructure 负责持久化、供应商接入和体验聚合。
flowchart LR
Browser["Razor / 本地 Bootstrap / Chart.js"] --> Web["EARSystem.Web / MVC / Identity"]
Web --> Core["EARSystem.Core / 画像与目标 / 六项匹配"]
Web --> Infra["EARSystem.Infrastructure / 体验事务 / 周报数据"]
Core --> Provider["IPlaceProvider"]
Provider --> Catalog["独立目录 / Demo 与 Collected 分区"]
Provider --> AMap["高德实时地点服务"]
Browser --> Proxy["/_AMapService 受限安全代理"]
Proxy --> JS["高德 JS API 2.0"]
Infra --> SQL["SQL Server / EF Core"]
Catalog --> SQL
交互闭环
flowchart TD
Login["注册 / 登录 / 开发演示登录"] --> Mood["自评心情、精力、压力与想法"]
Mood --> Hint["可选文字规则提示"]
Hint --> Confirm["用户确认候选标签"]
Confirm --> Save["保存私密日记"]
Save --> Profile["最近 7 天按日取最后一条形成画像"]
Profile --> Goal["自动目标;允许本次覆盖"]
Goal --> Location["授权定位 / 地址搜索 / 手动选点"]
Location --> Recall["明确选择在线或演示;召回至多 100 个"]
Recall --> Filter["范围、屏蔽、预算、类别、营业筛选"]
Filter --> Rank["六项评分 / 类别多样性 / 前 10 项"]
Rank --> Detail["查看原因、来源、氛围及详情"]
Detail --> Favorite["收藏独立目录地点"]
Favorite --> Experience["私密到访体验"]
Experience --> Consent["可选贡献结构化氛围 / 同步日记"]
Consent --> Review["趋势、日历、周报"]
Consent --> Feedback["下一次推荐读取最新个人体验与匿名统计"]
Feedback --> Rank
ER 图
Identity 的用户、角色与用户角色表由框架维护;以下包含关键业务关联。MoodEntry.ExperienceId 是应用管理的可选关联:取消同步或删除体验会解除它,日记仍保留。
erDiagram
ApplicationUser ||--o{ MoodEntry : records
ApplicationUser ||--o| UserPreference : configures
ApplicationUser ||--o{ Experience : owns
ApplicationUser ||--o{ Favorite : saves
ApplicationUser ||--o{ RecommendationRecord : requests
Place ||--o| PlaceAtmosphere : has_baseline
Place ||--o{ Experience : receives
Place ||--o{ Favorite : referenced
Experience o|--o| MoodEntry : optional_sync
ApplicationUser {
string Id PK
string Email
string DisplayName
bool IsDemo
}
MoodEntry {
guid Id PK
string UserId FK
int Mood
int Energy
int Stress
string TagsJson
string Thoughts
long RecordedAt
guid Version
guid ExperienceId
}
UserPreference {
string UserId PK
string FavoriteCategoriesJson
string BlockedCategoriesJson
string BlockedPlacesJson
decimal Budget
int RadiusMeters
}
Place {
guid Id PK
string Name
string Address
int Category
int Source
double Longitude
double Latitude
decimal Price
double Rating
int OpenHour
int CloseHour
}
PlaceAtmosphere {
guid PlaceId PK
double Quiet
double Comfort
double Nature
double Liveliness
double Social
double Focus
int BaselineCount
string Evidence
}
Experience {
guid Id PK
string UserId FK
guid PlaceId FK
long VisitedAt
int VisitDay
int Satisfaction
int MoodBefore
int MoodAfter
string Thoughts
bool Contribute
guid Version
}
Favorite {
string UserId PK
guid PlaceId PK
long CreatedAt
}
RecommendationRecord {
guid Id PK
string UserId FK
int Goal
int Mode
string AlgorithmVersion
string ResultsJson
double DurationMs
}
RuleConfiguration {
int Id PK
string Version
string WeightsJson
guid ConcurrencyStamp
datetimeoffset DemoSeededAt
}
Experience 还含六个 1–5 氛围评分。PlaceAtmosphere 保存独立采集或演示的基线;用户贡献保持在 Experience 中,读取时聚合,不覆盖基线。有效人数为基线样本数加去重后的贡献人数;同一人多次体验取其最新同意贡献的记录。
约束与边界
| 对象 | 约束 |
|---|---|
| MoodEntry | UserId+RecordedAt 索引、正文最多 5,000 字、Version 并发令牌 |
| Experience | UserId+PlaceId+VisitDay 唯一、UserId+VisitedAt 及 PlaceId+Contribute 索引、Version 并发令牌 |
| Favorite | UserId+PlaceId 复合主键;“去过”由当前有效体验推导 |
| Place | Source+Category 索引;有收藏/体验时不能删除;已存在地点不可更换数据来源 |
| RuleConfiguration | 单一规则配置、权重总和校验、版本号、ConcurrencyStamp |
| 个人内容 | 会话获取 UserId,所有个人查询/变更同时匹配本人;管理员页面无私人正文入口 |
| 第三方地点 | 在线响应临时用于匹配,不入地点表;持久体验引用独立目录 |
JSON 接口
所有接口需要 Cookie 登录。POST/PUT/DELETE 需要 X-CSRF-TOKEN,可从布局的防伪隐藏字段读取。身份不接受请求中的 userId。
| 方法与路径 | 行为 |
|---|---|
| GET /api/moods?date=YYYY-MM-DD 或 ?days=30 | 本人的时间范围记录 |
| POST /api/moods | 创建自评 |
| PUT /api/moods/ | 编辑,body.version 必须匹配 |
| DELETE /api/moods/?version= | 删除并发校验 |
| POST /api/emotion-tags/preview | 提示标签与触发短语,不持久化 |
| POST /api/recommendations | 位置、坐标系、模式、筛选和本次目标 |
| GET/POST /api/experiences | 本人体验列表与创建 |
| PUT /api/experiences/ | 修改体验,校验 version |
| DELETE /api/experiences/?version= | 删除并重算贡献;关联日记保留 |
| GET/POST /api/favorites | 收藏列表与创建 |
| DELETE /api/favorites/ | 取消本人收藏 |
| GET/PUT /api/preferences | 偏好、预算、距离与屏蔽 |
| GET /api/reports/trend?days=7或30 | 每日最后记录,缺失为 null |
| GET /api/reports/weekly?week=YYYY-MM-DD | 自动对齐周一至周日 |
| POST /api/map/geocode | 在线地址搜索,body.text |
枚举用字符串:Demo/Online、Relax/Explore/Social/Focus、Dining/Cafe/Park/Bookstore/Gallery/Entertainment。坐标系为 WGS84 或 GCJ02。
推荐响应包含 goal、goalName、basis、profile、mode、algorithmVersion、candidateCount、suggestExpand、items 和转换后的 location。每项含 place、distanceMeters、score、parts、confidence、reasons。分项和精确位置只随响应返回,不写入推荐历史。
401 表示未登录,403 权限不足,400 输入无效,404 本人记录不存在,409 并发/唯一约束冲突,429 限流,503 地图失败或超时。前端显示错误并保留编辑输入。