diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3133a97 --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +# 依赖 +node_modules/ +**/node_modules/ + +# 云函数锁文件(可选提交,这里统一忽略) +cloudfunctions/**/package-lock.json + +# 微信开发者工具私有配置(含 token,不入库) +project.private.config.json +**/project.private.config.json + +# 开发者工具在 srcMiniprogramRoot 内生成的重复配置 +miniprogram/project.config.json + +# 编译产物 +miniprogram/**/*.js.map + +# 系统文件 +.DS_Store +Thumbs.db diff --git a/cloudbaserc.json b/cloudbaserc.json new file mode 100644 index 0000000..178416d --- /dev/null +++ b/cloudbaserc.json @@ -0,0 +1,63 @@ +{ + "$schema": "https://static.cloudbase.net/cli/cloudbaserc.schema.json", + "envId": "cloud1-d4g697lte499543d8", + "functionRoot": "cloudfunctions", + "functions": [ + { + "name": "login", + "timeout": 10, + "runtime": "Nodejs20.19", + "handler": "index.main", + "description": "微信登录 & 用户注册" + }, + { + "name": "createPost", + "timeout": 15, + "runtime": "Nodejs20.19", + "handler": "index.main", + "description": "发布帖子" + }, + { + "name": "getPosts", + "timeout": 10, + "runtime": "Nodejs20.19", + "handler": "index.main", + "description": "获取信息流帖子列表" + }, + { + "name": "getPost", + "timeout": 10, + "runtime": "Nodejs20.19", + "handler": "index.main", + "description": "获取单条帖子详情" + }, + { + "name": "likePost", + "timeout": 10, + "runtime": "Nodejs20.19", + "handler": "index.main", + "description": "点赞 / 取消点赞" + }, + { + "name": "getNearbyPets", + "timeout": 15, + "runtime": "Nodejs20.19", + "handler": "index.main", + "description": "附近宠物地理位置查询" + }, + { + "name": "updateLocation", + "timeout": 10, + "runtime": "Nodejs20.19", + "handler": "index.main", + "description": "更新用户地理位置(GeoPoint)" + }, + { + "name": "sendGreeting", + "timeout": 10, + "runtime": "Nodejs20.19", + "handler": "index.main", + "description": "打招呼 / 发起私信" + } + ] +} diff --git a/cloudfunctions/getNearbyPets/index.js b/cloudfunctions/getNearbyPets/index.js index 6aba16d..90b042b 100644 --- a/cloudfunctions/getNearbyPets/index.js +++ b/cloudfunctions/getNearbyPets/index.js @@ -3,74 +3,64 @@ cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) const db = cloud.database() const _ = db.command -// 粗略经纬度范围换算(1度≈111km) -function latDelta(km) { return km / 111 } -function lngDelta(km, lat) { return km / (111 * Math.cos(lat * Math.PI / 180)) } - -function calcDistance(lat1, lng1, lat2, lng2) { - const R = 6371 - const dLat = (lat2 - lat1) * Math.PI / 180 - const dLng = (lng2 - lng1) * Math.PI / 180 - const a = Math.sin(dLat / 2) ** 2 + - Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * Math.sin(dLng / 2) ** 2 - return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)) * 1000 // meters -} - exports.main = async (event, context) => { const { OPENID } = cloud.getWXContext() - const { latitude, longitude, radiusKm = 5 } = event + const { + latitude, + longitude, + maxDistance = 5000, // 默认 5km,单位:米 + page = 0, + pageSize = 20, + } = event - if (!latitude || !longitude) { - return { code: -1, message: '缺少位置信息' } - } - - // 在线判断:60秒内更新过位置的用户为在线 - const onlineThreshold = new Date(Date.now() - 60 * 1000).toISOString() + if (!latitude || !longitude) return { code: -1, message: '缺少位置' } try { - const dLat = latDelta(radiusKm) - const dLng = lngDelta(radiusKm, latitude) - - const usersRes = await db.collection('users') - .where( - _.and([ - { openid: _.neq(OPENID) }, // 排除自己 - { 'lastLocation.latitude': _.gt(latitude - dLat).and(_.lt(latitude + dLat)) }, - { 'lastLocation.longitude': _.gt(longitude - dLng).and(_.lt(longitude + dLng)) }, - { pets: _.exists(true) }, - ]) - ) - .field({ - openid: true, nickName: true, avatarUrl: true, - lastLocation: true, lastSeen: true, isOnline: true, - pets: true, location: true, bio: true, + // ✅ geoNear 聚合管道:依赖 users.lastLocation 字段上的 2dsphere 地理位置索引 + const { list } = await db.collection('users') + .aggregate() + .geoNear({ + near: db.Geo.Point(longitude, latitude), // 注意:CloudBase GeoPoint 是 (lng, lat) 顺序 + spherical: true, + distanceField: 'dist', // 在每条记录上附加实际距离(米) + maxDistance, + query: { + openid: _.neq(OPENID), // 排除自己 + 'pets.0': _.exists(true), // 至少有一只宠物 + }, }) - .limit(30) - .get() - - const nearby = usersRes.data - .map(user => { - const userLat = user.lastLocation?.latitude - const userLng = user.lastLocation?.longitude - if (!userLat || !userLng) return null - - const distance = calcDistance(latitude, longitude, userLat, userLng) - if (distance > radiusKm * 1000) return null - - return { - userId: user.openid, - user, - pet: user.pets?.[0] || null, - distance, - isOnline: user.isOnline && user.lastSeen > onlineThreshold, - location: { latitude: userLat, longitude: userLng }, - } + .skip(page * pageSize) + .limit(pageSize) + .project({ + openid: false, }) - .filter(Boolean) - .sort((a, b) => a.distance - b.distance) + .end() - return { code: 0, data: nearby } + // 在线状态:5 分钟内活跃 + const fiveMin = 5 * 60 * 1000 + const now = Date.now() + + const result = list.map(u => ({ + userId: u._id, + user: u, + pet: u.pets?.[0] || null, + distance: Math.round(u.dist), + distanceText: formatDist(u.dist), + isOnline: u.lastSeen && (now - new Date(u.lastSeen).getTime()) < fiveMin, + location: { + latitude: u.lastLocation?.coordinates?.[1], + longitude: u.lastLocation?.coordinates?.[0], + }, + })) + + return { code: 0, data: result } } catch (e) { return { code: -1, message: e.message } } } + +function formatDist(meters) { + if (!meters && meters !== 0) return '未知' + if (meters < 1000) return Math.round(meters) + 'm' + return (meters / 1000).toFixed(1) + 'km' +} diff --git a/cloudfunctions/updateLocation/index.js b/cloudfunctions/updateLocation/index.js index 8c0de2a..7987aa5 100644 --- a/cloudfunctions/updateLocation/index.js +++ b/cloudfunctions/updateLocation/index.js @@ -13,9 +13,10 @@ exports.main = async (event, context) => { .where({ openid: OPENID }) .update({ data: { - lastLocation: { latitude, longitude }, - isOnline: true, + // ✅ 改用 GeoPoint,才能用地理位置索引做距离查询 + lastLocation: db.Geo.Point(longitude, latitude), // 注意:Geo.Point 是 (lng, lat) 顺序 lastSeen: db.serverDate(), + isOnline: true, }, }) return { code: 0, data: null } diff --git a/miniprogram/app.json b/miniprogram/app.json index 7cba45c..a4ed4c8 100644 --- a/miniprogram/app.json +++ b/miniprogram/app.json @@ -41,7 +41,6 @@ "requiredPrivateInfos": ["getLocation", "chooseLocation"], "sitemapLocation": "sitemap.json", "style": "v2", - "componentFramework": "glass-easel", "lazyCodeLoading": "requiredComponents", "networkTimeout": { "request": 10000, diff --git a/miniprogram/utils/constants.ts b/miniprogram/utils/constants.ts index a1c4cb8..af513fd 100644 --- a/miniprogram/utils/constants.ts +++ b/miniprogram/utils/constants.ts @@ -1,4 +1,4 @@ -export const CLOUD_ENV = 'YOUR_CLOUD_ENV_ID' +export const CLOUD_ENV = 'cloud1-d4g697lte499543d8' export const COLORS = { pink: '#ff4f91', diff --git a/project.config.json b/project.config.json index 8192bd4..f114e2a 100644 --- a/project.config.json +++ b/project.config.json @@ -1,12 +1,19 @@ { - "appid": "YOUR_APPID_HERE", + "appid": "wx8b033e454024200b", "compileType": "miniprogram", - "libVersion": "2.25.0", + "libVersion": "3.16.1", "packOptions": { "ignore": [ - { "type": "regexp", "value": "\\.eslintrc\\.js" }, - { "type": "regexp", "value": "\\.gitignore" } - ] + { + "value": "\\.eslintrc\\.js", + "type": "regexp" + }, + { + "value": "\\.gitignore", + "type": "regexp" + } + ], + "include": [] }, "setting": { "coverView": true, @@ -31,19 +38,26 @@ }, "enableEngineNative": false, "bundle": false, - "useIsolateContext": true, "userConfirmedBundleSwitch": false, "packNpmManually": false, "packNpmRelationList": [], "minifyWXSS": true, "disableUseStrict": false, "ignoreUploadUnusedFiles": true, - "uploadWithSourceMap": true + "compileWorklet": false, + "minifyWXML": true, + "localPlugins": false, + "useCompilerPlugins": ["typescript"], + "condition": false, + "swc": false, + "disableSWC": true }, "condition": {}, "editorSetting": { "tabIndent": "insertSpaces", "tabSize": 2 }, - "srcMiniprogramRoot": "miniprogram/" -} + "miniprogramRoot": "miniprogram/", + "srcMiniprogramRoot": "miniprogram/", + "simulatorPluginLibVersion": {} +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 44ba655..a1aef43 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,8 +18,8 @@ "baseUrl": ".", "paths": { "@/*": ["miniprogram/*"] - }, - "typeRoots": ["./typings"] + } + }, "include": ["miniprogram/**/*.ts"], "exclude": ["node_modules", "miniprogram/**/*.d.ts"]