From 36a2f15a8858a37f72d0fe227b2d80aaaaf9091c Mon Sep 17 00:00:00 2001 From: ytc1012 <18001193130@163.com> Date: Fri, 13 Feb 2026 15:43:40 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=90=8D=E7=A7=B0=EF=BC=8C?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CLAUDE.md | 372 ++++++++++++++++++ cloudfunctions/cleanExpiredRooms/index.js | 35 ++ cloudfunctions/cleanExpiredRooms/package.json | 9 + cloudfunctions/createRoom/index.js | 4 + cloudfunctions/joinRoom/index.js | 14 +- miniprogram/app.json | 2 +- miniprogram/pages/index/index.json | 2 +- miniprogram/pages/index/index.wxml | 6 +- miniprogram/pages/room/room.js | 8 +- miniprogram/pages/room/room.wxml | 7 +- miniprogram/pages/room/room.wxss | 7 + project.config.json | 2 +- 12 files changed, 452 insertions(+), 16 deletions(-) create mode 100644 CLAUDE.md create mode 100644 cloudfunctions/cleanExpiredRooms/index.js create mode 100644 cloudfunctions/cleanExpiredRooms/package.json diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..4383e0d --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,372 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +**"点线得面"** - A WeChat Mini Program for collaborative meeting point selection. Users create rooms, share with friends, each member adds their location, and the system calculates the optimal meeting venue based on everyone's coordinates. Maximum 8 members per room, and rooms expire after 30 days. + +**Architecture**: WeChat Mini Program frontend + WeChat Cloud Functions backend + MeetSpot Python API (external service) + +## Repository Structure + +``` +WoMenQuNaJu/ +├── miniprogram/ # WeChat Mini Program frontend +│ ├── pages/ # Page components (index, create-room, room, result) +│ ├── app.js/json/wxss # Application entry +│ └── images/ # Static assets +├── cloudfunctions/ # WeChat Cloud Functions (Node.js) +│ ├── calculateMeetSpot/ # Core: calls MeetSpot API to calculate venues +│ ├── createRoom/ # Create a new room +│ ├── joinRoom/ # Add member to room (max 8 people) +│ ├── updateLocation/ # Update current user's location +│ ├── updateMemberLocation/ # Update test member's location (dev only) +│ ├── updateRoomInfo/ # Update room metadata +│ ├── removeMember/ # Remove member from room +│ ├── deleteRoom/ # Delete entire room +│ ├── cleanExpiredRooms/ # Scheduled: delete rooms older than 30 days +│ ├── addTestMember/ # Add test member (dev only) +│ └── getOpenId/ # Get user's WeChat OpenID +└── MeetSpot/ # Python FastAPI backend (has its own CLAUDE.md) +``` + +**Key Insight**: The WeChat Mini Program is a lightweight frontend/coordinator. The heavy lifting (geocoding, POI search, venue ranking) is delegated to the MeetSpot FastAPI service via the `calculateMeetSpot` cloud function. + +## WeChat Mini Program Development + +### Setup + +```bash +# Install WeChat Developer Tools +# Download from: https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html + +# Open project in WeChat Developer Tools +# Project directory: d:\download\WoMenQuNaJu +# AppID: wxb7e6344198f6526f (from project.config.json) +``` + +**Cloud Functions**: Each function in `cloudfunctions/` needs dependencies installed separately: + +```bash +# For cloud functions that use axios (like calculateMeetSpot) +cd cloudfunctions/calculateMeetSpot +npm install + +# Repeat for other functions with package.json +cd ../createRoom +npm install +``` + +**Deploy Cloud Functions**: Use WeChat Developer Tools GUI to upload cloud functions, or use CLI: + +```bash +# Upload a single cloud function +miniprogram-ci cloud uploadFunction --functionName calculateMeetSpot +``` + +### Testing + +**Local Testing**: Use WeChat Developer Tools simulator. The simulator supports: +- Cloud function calls +- Cloud database operations +- Location services (mocked) + +**Backend URL Configuration**: +- `calculateMeetSpot/index.js:78` contains `BACKEND_URL` environment variable +- Default: `https://meetspot.onrender.com` (production MeetSpot API) +- For local testing: Set cloud function env var to `http://localhost:8000` (requires ngrok or similar for Mini Program to reach local machine) + +### Data Model + +**Cloud Database Collection**: `rooms` + +**Data Retention**: Rooms are automatically deleted 30 days after creation. A scheduled cloud function `cleanExpiredRooms` runs periodically to clean up expired data. + +```javascript +{ + _id: "auto-generated", + _openid: "creator's openid", + createdAt: ServerDate, + expireTime: Date, // Auto-delete 30 days after creation + status: "active" | "calculating" | "calculated", + name: "聚会名称", + meetTime: "2026-02-10 18:00", + keywords: "咖啡馆", + requirements: "parking, quiet", + members: [ + { + openid: "wx-openid", + avatarUrl: "https://...", + nickName: "用户昵称", + location: { + lng: 116.32, + lat: 39.99, + address: "北京大学", + name: "北京大学" + }, + joinedAt: Date, + isTestMember: false // true for dev test members + } + ], + result: { + success: true, + venues: [...], + center: { lat: 39.99, lng: 116.32 }, + html_url: "/workspace/js_src/recommendation_xxx.html" + }, + lastCalculateTime: ServerDate, + lastCalculatedMembers: "openid1_lat_lng,openid2_lat_lng,..." // Snapshot for duplicate detection +} +``` + +**Member Limit**: Maximum 8 members per room. Enforced in `joinRoom` cloud function. + +## Architecture Patterns + +### Status Locking Pattern + +**Problem**: Multiple users clicking "Calculate" simultaneously could trigger duplicate API calls. + +**Solution**: `calculateMeetSpot` uses database-level status locking: + +```javascript +// 1. Check if status is "calculating" (early return if locked) +if (data.status === 'calculating') return { msg: '正在计算中' } + +// 2. Acquire lock +await db.collection('rooms').doc(roomId).update({ status: 'calculating' }) + +// 3. Call backend API +const result = await axios.post(BACKEND_URL, ...) + +// 4. Release lock and save result +await db.collection('rooms').doc(roomId).update({ + status: 'calculated', + result: result.data +}) +``` + +**Exception Handling**: If API call fails, the `catch` block releases the lock by setting `status: 'active'`. + +### Duplicate Calculation Prevention + +**Problem**: Users might click "Calculate" multiple times without changing member locations. + +**Solution**: `calculateMeetSpot/index.js:43-61` compares current member positions against `lastCalculatedMembers` snapshot: + +```javascript +const currentSnapshot = validMembers + .map(m => `${m.openid}_${m.location.lat}_${m.location.lng}`) + .sort() + .join(',') + +if (currentSnapshot === data.lastCalculatedMembers) { + return { isDuplicate: true, existingResult: data.result } +} +``` + +### Real-time Synchronization + +**Pattern**: Database Watch API for live updates + +**Implementation**: `miniprogram/pages/room/room.js:125-179` + +```javascript +db.collection('rooms').doc(roomId).watch({ + onChange: snapshot => { + // Sync room state, member list, calculation status + // Auto-navigate to result page when status changes from 'calculating' to 'calculated' + } +}) +``` + +**Critical Detail**: Uses `previousStatus` tracking to only auto-navigate when calculation **just finished**, preventing navigation loops when users return to the room page. + +### Test Member System + +**Purpose**: Allow room creators to add mock members for testing without needing multiple WeChat accounts. + +**Identification**: Test members have `isTestMember: true` and openid format `test__` + +**Special Handling**: +- `addTestMember`: Creates member with random location (Beijing area) +- `updateMemberLocation`: Allows creator to update test member locations +- `removeMember`: Can delete test members +- UI shows "测试" badge and allows creator to edit their locations + +## Cloud Functions + +### Core Function: calculateMeetSpot + +**Responsibility**: Bridge between Mini Program and MeetSpot API + +**Flow**: +1. Validate: At least 2 members with locations +2. Check: Duplicate calculation detection +3. Lock: Set status to `calculating` +4. Transform: Extract `{ lng, lat, address, name }` from members +5. Call: POST to `BACKEND_URL/api/miniprogram/calculate` +6. Store: Save result to database, update status to `calculated` + +**Environment Variables**: Set in WeChat Cloud Console +- `BACKEND_URL`: MeetSpot API endpoint (default: `https://meetspot.onrender.com`) + +**Timeout**: 30 seconds (axios timeout). MeetSpot API typically responds in 3-15 seconds. + +### Database Mutating Functions + +| Function | Mutation | Permission Check | +|----------|----------|------------------| +| `createRoom` | Add document to `rooms` | Anyone (creator = caller's openid) | +| `joinRoom` | Push to `members` array | Anyone (duplicate check by openid) | +| `updateLocation` | Update member's location in array | Own openid only | +| `updateMemberLocation` | Update test member location | Creator only | +| `updateRoomInfo` | Update room metadata | Creator only (checked via `_openid`) | +| `removeMember` | Remove from `members` array | Self-exit OR creator removing others | +| `deleteRoom` | Delete entire document | Creator only | + +**Security Note**: Cloud Functions automatically add `_openid` field (caller's WeChat ID) to database writes. Permission checks compare `wxContext.OPENID` against stored `_openid`. + +## MeetSpot Backend Integration + +The `MeetSpot/` subdirectory is a separate Python/FastAPI project. See [MeetSpot/CLAUDE.md](MeetSpot/CLAUDE.md) for details. + +**Key Integration Point**: `calculateMeetSpot` cloud function calls `POST /api/miniprogram/calculate` endpoint. + +**Expected Request**: +```json +{ + "locations": [ + { "lng": 116.32, "lat": 39.99, "address": "北京大学", "name": "北京大学" } + ], + "keywords": "咖啡馆", + "requirements": "parking, quiet" +} +``` + +**Expected Response**: +```json +{ + "success": true, + "venues": [ /* Array of ranked venues */ ], + "center": { "lat": 39.99, "lng": 116.32 }, + "html_url": "/workspace/js_src/recommendation_xxx.html" +} +``` + +**To run MeetSpot locally**: +```bash +cd MeetSpot +python web_server.py +# Backend will be at http://localhost:8000 +``` + +## Common Development Tasks + +### Adding a New Cloud Function + +1. Create directory: `cloudfunctions/myFunction/` +2. Add `index.js`: +```javascript +const cloud = require('wx-server-sdk') +cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) + +exports.main = async (event, context) => { + const wxContext = cloud.getWXContext() + // Your logic here + return { success: true } +} +``` +3. Add `package.json` if using npm dependencies +4. Install dependencies: `npm install` (in function directory) +5. Upload via WeChat Developer Tools + +### Modifying Room Schema + +**Important**: WeChat Cloud Database is schemaless, but for consistency: + +1. Update `createRoom/index.js` for new fields in initial data +2. Add migration logic in `calculateMeetSpot` or other functions if transforming existing data +3. Update TypeScript interfaces if using typed Mini Program (this project uses plain JS) + +### Changing Keywords/Requirements Options + +**Keywords Picker**: `miniprogram/pages/room/room.js:10` +```javascript +keywordOptions: ['咖啡馆', '餐厅', '公园', ...] +``` + +**Requirements**: Free-text input, no predefined options. MeetSpot API parses natural language. + +### Debugging Cloud Functions + +**Method 1**: WeChat Developer Tools Console +- Right-click function → "Upload and Debug" +- Use `console.log()` - logs appear in Cloud Development Console + +**Method 2**: Local Cloud Function Emulation +- Install `miniprogram-ci` +- Use `npx miniprogram-ci cloud emulator` + +## Performance Considerations + +**Cold Start**: Cloud functions have ~500ms-2s cold start. First calculation in a room may feel slow. + +**Database Watch**: Each page maintains one watch connection. WeChat limits 5 concurrent watches per user. Close watches in `onUnload()`. + +**Calculation Time**: Typical flow takes 5-15 seconds: +- Cloud function startup: 0.5-2s +- MeetSpot API call: 3-12s (includes geocoding, POI search, ranking) +- Database write: 0.1-0.5s + +**Optimization**: Duplicate calculation prevention saves ~90% of redundant API calls. + +## Deployment + +### Mini Program + +1. Update version in WeChat Developer Tools +2. Click "Upload" → Enter version number and description +3. Submit for review in WeChat Public Platform +4. After approval, release to production + +### Cloud Functions + +**Individual Upload**: Right-click function → "Upload and Deploy: Cloud Installation Dependencies" + +**Batch Upload**: Cloud Development Console → Cloud Functions → Batch upload + +**CI/CD**: Use `miniprogram-ci` for automated deployment: +```bash +miniprogram-ci upload --project /path/to/project \ + --appid wxb7e6344198f6526f \ + --version 1.0.0 \ + --desc "Update description" +``` + +## Debugging Common Issues + +| Issue | Cause | Solution | +|-------|-------|----------| +| "未找到云函数" | Function not uploaded | Upload via Developer Tools | +| "正在计算中" stuck | Lock not released after error | Manually set `status: 'active'` in database | +| Empty result page | BACKEND_URL unreachable | Check cloud function logs, verify MeetSpot API is running | +| Location picker fails | Missing permission | Check `app.json` → `permission` → `scope.userLocation` | +| Watch connection lost | Network interruption | Page will auto-reconnect on next data change | +| "至少需要2人" error | Only 1 member has location | Add more member locations or use test members | + +**Logs**: Cloud Development Console → Cloud Functions → Function logs (search by `requestId`) + +## Code Style + +- **JavaScript**: 4-space indent, ES6+ syntax, no semicolons (Mini Program convention) +- **Cloud Functions**: Use `async/await`, avoid callbacks +- **Naming**: `camelCase` for variables/functions, `PascalCase` for pages/components +- **Comments**: Chinese for user-facing strings, English for technical comments acceptable + +## Related Documentation + +- WeChat Mini Program Docs: https://developers.weixin.qq.com/miniprogram/dev/framework/ +- Cloud Functions Guide: https://developers.weixin.qq.com/miniprogram/dev/wxcloud/basis/capabilities.html +- MeetSpot Backend: See `MeetSpot/CLAUDE.md` for Python/FastAPI development guide diff --git a/cloudfunctions/cleanExpiredRooms/index.js b/cloudfunctions/cleanExpiredRooms/index.js new file mode 100644 index 0000000..7113f12 --- /dev/null +++ b/cloudfunctions/cleanExpiredRooms/index.js @@ -0,0 +1,35 @@ +// 云函数入口文件 - 定时清理过期聚会 +const cloud = require('wx-server-sdk') + +cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) +const db = cloud.database() +const _ = db.command + +// 云函数入口函数 +exports.main = async (event, context) => { + try { + const now = new Date() + + // 删除30天前创建的聚会 + const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000) + + const result = await db.collection('rooms').where({ + createdAt: _.lt(thirtyDaysAgo) + }).remove() + + console.log(`清理完成,删除了 ${result.stats.removed} 个过期聚会`) + + return { + success: true, + deleted: result.stats.removed, + msg: `清理完成,删除了 ${result.stats.removed} 个过期聚会` + } + } catch (err) { + console.error('清理过期聚会失败:', err) + return { + success: false, + msg: '清理失败', + error: err + } + } +} diff --git a/cloudfunctions/cleanExpiredRooms/package.json b/cloudfunctions/cleanExpiredRooms/package.json new file mode 100644 index 0000000..1e3f33b --- /dev/null +++ b/cloudfunctions/cleanExpiredRooms/package.json @@ -0,0 +1,9 @@ +{ + "name": "cleanExpiredRooms", + "version": "1.0.0", + "description": "定时清理过期聚会数据", + "main": "index.js", + "dependencies": { + "wx-server-sdk": "~2.6.3" + } +} diff --git a/cloudfunctions/createRoom/index.js b/cloudfunctions/createRoom/index.js index db59fd8..3d8f94e 100644 --- a/cloudfunctions/createRoom/index.js +++ b/cloudfunctions/createRoom/index.js @@ -12,10 +12,14 @@ exports.main = async (event, context) => { const { userInfo, keywords, requirements, name, meetTime } = event try { + // 计算过期时间(30天后) + const expireTime = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000) + const res = await db.collection('rooms').add({ data: { _openid: openid, // 创建者openid createdAt: db.serverDate(), + expireTime: expireTime, // 过期时间 status: 'active', name: name || '未命名聚会', meetTime: meetTime || '', diff --git a/cloudfunctions/joinRoom/index.js b/cloudfunctions/joinRoom/index.js index a532dd8..fb7c484 100644 --- a/cloudfunctions/joinRoom/index.js +++ b/cloudfunctions/joinRoom/index.js @@ -27,9 +27,17 @@ exports.main = async (event, context) => { const members = room.data.members || [] const isMember = members.some(m => m.openid === openid) - if (!isMember) { - // 加入房间 - const newMember = { + if (isMember) { + return { success: true, msg: '已在房间内' } + } + + // 检查人数限制(最多8人) + if (members.length >= 8) { + return { success: false, msg: '房间人数已满(最多8人)' } + } + + // 加入房间 + const newMember = { openid: openid, avatarUrl: userInfo?.avatarUrl || 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0', nickName: userInfo?.nickName || '微信用户', diff --git a/miniprogram/app.json b/miniprogram/app.json index daef82e..b7bfd9c 100644 --- a/miniprogram/app.json +++ b/miniprogram/app.json @@ -8,7 +8,7 @@ "window": { "backgroundTextStyle": "light", "navigationBarBackgroundColor": "#fff", - "navigationBarTitleText": "我们去哪聚", + "navigationBarTitleText": "点线得面", "navigationBarTextStyle": "black" }, "sitemapLocation": "sitemap.json", diff --git a/miniprogram/pages/index/index.json b/miniprogram/pages/index/index.json index 98d4815..41dbb52 100644 --- a/miniprogram/pages/index/index.json +++ b/miniprogram/pages/index/index.json @@ -1,4 +1,4 @@ { "usingComponents": {}, - "navigationBarTitleText": "我们去哪聚" + "navigationBarTitleText": "点线得面" } \ No newline at end of file diff --git a/miniprogram/pages/index/index.wxml b/miniprogram/pages/index/index.wxml index 02de6c6..c02a80b 100644 --- a/miniprogram/pages/index/index.wxml +++ b/miniprogram/pages/index/index.wxml @@ -1,7 +1,7 @@ - 我们去哪聚 - 找到你们的完美聚会点 + 点线得面 + 找到大家的聚会好去处 @@ -50,6 +50,6 @@ - 分享给好友,共同寻找中间点 + 分享给好友,共同寻找聚会好去处 diff --git a/miniprogram/pages/room/room.js b/miniprogram/pages/room/room.js index f0a49ee..010ae63 100644 --- a/miniprogram/pages/room/room.js +++ b/miniprogram/pages/room/room.js @@ -100,7 +100,7 @@ Page({ wx.hideLoading() if (!res.result.success) { - wx.showModal({ title: '错误', content: res.result.msg, showCancel: false }) + wx.showModal({ title: '提示', content: res.result.msg, showCancel: false }) return false } @@ -247,7 +247,7 @@ Page({ onShareAppMessage() { return { - title: '快来填位置,我们去找个中间点聚会!', + title: '点线得面 - 找到大家的聚会好去处!', path: `/pages/room/room?roomId=${this.data.roomId}` } }, @@ -431,9 +431,9 @@ Page({ onEditRoomName() { wx.showModal({ title: '修改聚会名称', - content: '请输入新的聚会名称', + content: this.data.roomName, editable: true, - placeholderText: this.data.roomName, + placeholderText: '请输入聚会名称', success: (res) => { if (res.confirm && res.content.trim()) { const newName = res.content.trim() diff --git a/miniprogram/pages/room/room.wxml b/miniprogram/pages/room/room.wxml index 459ae8f..3541313 100644 --- a/miniprogram/pages/room/room.wxml +++ b/miniprogram/pages/room/room.wxml @@ -68,6 +68,7 @@ | 已就位 {{readyCount}}人 + 最多8人 @@ -94,10 +95,10 @@ - - + + diff --git a/miniprogram/pages/room/room.wxss b/miniprogram/pages/room/room.wxss index e215254..ac2ada1 100644 --- a/miniprogram/pages/room/room.wxss +++ b/miniprogram/pages/room/room.wxss @@ -126,6 +126,13 @@ color: #07c160; } +.limit-tip { + font-size: 24rpx; + color: #999; + text-align: center; + margin-top: 8rpx; +} + /* 成员列表 */ .member-list { flex: 1; diff --git a/project.config.json b/project.config.json index e747479..eb0555c 100644 --- a/project.config.json +++ b/project.config.json @@ -29,7 +29,7 @@ } }, "appid": "wxb7e6344198f6526f", - "projectname": "我们去哪聚", + "projectname": "点线得面", "libVersion": "3.14.1", "condition": {}, "compileType": "miniprogram",