首页 > 其他分享 >后台实时定位,实现类似位置共享功能,实时显示会员所在位置

后台实时定位,实现类似位置共享功能,实时显示会员所在位置

时间:2025-01-06 11:49:08浏览次数:1  
标签:info uid self redis 实时 connections 所在位置 后台 logger

问题描述

客户要求我们开发一个后台实时定位系统,该系统能够实时监控客户系统里面会员所在的位置,并将会员的位置信息显示在地图上。服务器后端是PHP开发的后台,主要是讲究效率。会员展示的前端是一个微信小程序,那么,前端可通过微信小程序提供的wx.startLocationUpdateBackground、wx.onLocationChange来实时获取当前的位置信息。

具体构思如下:

  1. 在会员进入相应的小程序页面时,调用wx.startLocationUpdateBackground方法,开启后台定位功能。
  2. 当会员的位置发生变化时,会触发wx.onLocationChange事件,我们可以获取到当前的位置信息,并使用地图API将会员的位置提交到API服务。
  3. API服务接收到会员的位置信息后,将会员的位置信息使用redis.publish方法发布到指定的频道,供后台实时定位系统接收。
  4. 使用docker服务部署一个python服务,该服务监听redis的指定频道,并接收到会员的位置信息后,判断是否有操作员在监听该会员的位置信息,如果有,则将会员的位置信息发送给操作员。
  5. 操作员客户端收到会员的位置信息后,将会员的位置信息显示在地图上。

下面是具体的实现步骤:

  1. 准备工作:申请微信小程序的appid和secret,并在微信小程序后台配置好后台定位功能。其实在测试阶段,这些wx.startLocationUpdateBackground、wx.onLocationChange在体验版中都是开放的,但在正式版中需要申请权限。我们可以先在体验版中测试后台定位功能是否正常工作。
  2. 小程序端:在小程序端,我们需要调用wx.startLocationUpdateBackground方法开启后台定位功能,并在wx.onLocationChange事件中获取当前的位置信息。
methods: {
    locationListener() {
        uni.startLocationUpdateBackground({
            success: () => {
                uni.onLocationChange((res) => {
                    this.$api.sendRequest({
                        url: '/api/location/move',
                        data: res
                    })
                });
            },
            fail: err => {
                console.log('监听位置失败 -> ', err);
            }
        })
    },
    locationStart() {
        uni.getSetting({
            success: (res) => {
                if (res.authSetting['scope.userLocationBackground']) {
                    this.locationListener();
                } else {
                    uni.authorize({
                        scope: 'scope.userLocationBackground',
                        success: () => {
                            this.locationListener();
                        },
                        fail: () => {
                            uni.showModal({
                                title: '提示',
                                content: '授权失败,点击右上角设置位置为使用时和离开后!',
                                success: r => {
                                    if (r.confirm) {
                                        uni.openSetting({
                                            complete: t => {
                                                if (t.authSetting['scope.userLocationBackground']) {
                                                    this.locationListener();
                                                } else {
                                                    console.log('授权失败 ->', t);
                                                }
                                            }
                                        })
                                    }
                                }
                            })
                        }
                    })
                }
            }
        })
    }
}
  1. 修改mainifest.json文件,添加后台定位权限
"mp-weixin" : {
    "permissions": {
        "scope.userLocationBackground" : {
            "desc" : "为了给您提供更精准、便捷的服务,我们需要获取您的后台持续获取位置信息。我们承诺会严格保护您的隐私,仅在实现上述功能时使用您的位置信息,感谢您的理解与支持!"
        },
        "scope.userLocation" : {
            "desc" : "为了为您提供更好更精准的服务,需要获得您当前位置信息。"
        }
    },
    "requiredPrivateInfos" : [
        "onLocationChange",
        "startLocationUpdateBackground"
    ],
    "requiredBackgroundModes" : [ "location" ],
}
  1. PHP服务端
Cache::store('redis')->publish("location_update", json_encode([
    'uid' => $uid,
    'longitude' => input('longitude', ''),
    'latitude' => input('latitude', ''),
]));
  1. Python服务端 (这里需要注意:访问的redis服务端是宿主机的redis,所以需要将redis的host设置为宿主机的IP地址,否则会出现连接失败的情况,因为服务器的python环境是python2。如果服务器的python本身就是python3,不需要构建docker镜像,直接运行即可。)
#!/usr/bin/env python3
import asyncio
import json
import logging
from redis.asyncio import Redis
from redis.exceptions import RedisError
from websockets.server import serve, WebSocketServerProtocol
from typing import Set, Dict
from datetime import datetime

# 设置更详细的日志格式
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

class LocationServer:
    def __init__(self):
        self.connections: Dict[str, Set[WebSocketServerProtocol]] = {}
        self.redis = None
        self.pubsub = None
        self.redis_config = {
            'host': '127.0.0.1',
            'port': 6379,
            'password': None,
            'db': 0,
            'decode_responses': True,
            'retry_on_timeout': True,
            'socket_keepalive': True
        }

    def log_connections_status(self):
        """记录当前连接状态"""
        total_connections = sum(len(clients) for clients in self.connections.values())
        logger.info(f"Current connections status:")
        logger.info(f"Total connected clients: {total_connections}")
        for uid, clients in self.connections.items():
            logger.info(f"Member {uid}: {len(clients)} client(s)")

    async def init_redis(self):
        max_retries = 5
        retry_count = 0
        
        while retry_count < max_retries:
            try:
                if self.redis:
                    await self.redis.close()

                self.redis = Redis(**self.redis_config)
                self.pubsub = self.redis.pubsub()

                await self.redis.ping()
                logger.info("Redis connection established")
                return True
                
            except Exception as e:
                retry_count += 1
                logger.error(f"Redis connection attempt {retry_count} failed: {e}")
                if retry_count < max_retries:
                    await asyncio.sleep(5)
                else:
                    logger.error("Max retries reached, could not connect to Redis")
                    return False

    async def redis_subscriber(self):
        while True:
            try:
                if self.redis is None or not await self.redis.ping():
                    logger.info("Trying to connect to Redis...")
                    if not await self.init_redis():
                        await asyncio.sleep(5)
                        continue

                await self.pubsub.subscribe('location_update')
                logger.info("Successfully subscribed to location_update channel")

                while True:
                    message = await self.pubsub.get_message(ignore_subscribe_messages=True)
                    if message and message['type'] == 'message':
                        try:
                            data = json.loads(message['data'])
                            uid = str(data.get('uid'))
                            logger.info(f"Received location update for member {uid}")
                            
                            if uid in self.connections and self.connections[uid]:
                                client_count = len(self.connections[uid])
                                logger.info(f"Found {client_count} client(s) for member {uid}")
                                
                                disconnected = set()
                                success_count = 0
                                
                                for websocket in self.connections[uid]:
                                    try:
                                        await websocket.send(json.dumps(data))
                                        success_count += 1
                                        logger.info(f"Successfully sent location update to a client for member {uid}")
                                    except Exception as e:
                                        logger.error(f"Failed to send to websocket: {e}")
                                        disconnected.add(websocket)

                                for ws in disconnected:
                                    self.connections[uid].remove(ws)
                                    logger.info(f"Removed disconnected client for member {uid}")
                                
                                if not self.connections[uid]:
                                    del self.connections[uid]
                                    logger.info(f"Removed member {uid} as no clients remaining")
                                
                                logger.info(f"Location update sent to {success_count}/{client_count} clients for member {uid}")
                            else:
                                logger.info(f"No clients found for member {uid}")
                                
                        except json.JSONDecodeError as e:
                            logger.error(f"Invalid JSON message: {e}")
                    await asyncio.sleep(0.1)

            except RedisError as e:
                logger.error(f"Redis error: {e}")
                await asyncio.sleep(5)
            except Exception as e:
                logger.error(f"Unexpected error: {e}")
                await asyncio.sleep(5)

    async def register(self, websocket: WebSocketServerProtocol, uid: str):
        """注册新的WebSocket连接"""
        if uid not in self.connections:
            self.connections[uid] = set()
        self.connections[uid].add(websocket)
        logger.info(f"New connection registered for uid: {uid}")
        self.log_connections_status()

    async def unregister(self, websocket: WebSocketServerProtocol, uid: str):
        """注销WebSocket连接"""
        if uid in self.connections:
            self.connections[uid].remove(websocket)
            if not self.connections[uid]:
                del self.connections[uid]
            logger.info(f"Connection unregistered for uid: {uid}")
            self.log_connections_status()

    async def handler(self, websocket: WebSocketServerProtocol):
        """处理WebSocket连接"""
        client_address = websocket.remote_address
        logger.info(f"New WebSocket connection from {client_address}")
        
        uid = None
        try:
            message = await websocket.recv()
            logger.info(f"Received initial message from {client_address}: {message}")
            
            data = json.loads(message)
            uid = str(data.get('uid'))
            
            if not uid:
                logger.warning(f"No uid provided from {client_address}")
                await websocket.close(1002, "uid required")
                return

            await self.register(websocket, uid)
            logger.info(f"Client {client_address} registered for member {uid}")
            
            try:
                async for message in websocket:
                    logger.debug(f"Received message from {client_address}: {message}")
            except Exception as e:
                logger.error(f"Error in websocket message loop for {client_address}: {e}")
            finally:
                if uid:
                    await self.unregister(websocket, uid)
                    logger.info(f"Client {client_address} unregistered for member {uid}")
                
        except Exception as e:
            logger.error(f"WebSocket handler error for {client_address}: {e}")
            if uid:
                await self.unregister(websocket, uid)

async def main():
    server = LocationServer()
    logger.info("Starting Location WebSocket Server...")
    
    subscriber_task = asyncio.create_task(server.redis_subscriber())
    logger.info("Redis subscriber task created")
    
    async with serve(server.handler, "0.0.0.0", 30582):
        logger.info("WebSocket server is running on ws://0.0.0.0:30582")
        try:
            await asyncio.Future()
        except Exception as e:
            logger.error(f"Server error: {e}")
        finally:
            subscriber_task.cancel()
            logger.info("Server shutting down...")

if __name__ == "__main__":
    asyncio.run(main())

  1. 操作员客户端
const ws = new WebSocket('ws://demo.com:30582');

ws.onopen = () => {
  console.log('WebSocket 已连接');
}; 

ws.onmessage = (event) => {        
  const data = JSON.parse(event.data);
  // 刷新地图显示当前位置
  consol.log("当前位置发生变化 ->" + data);
  // 这里可以进行地图画线、标注等操作,每个人使用的地图不一样,需要将经纬度转换为自己使用的坐标系
};

ws.onclose = (event) => {
  console.log('WebSocket 已关闭');
};

ws.onerror = (event) => {
  console.log('WebSocket 发生错误');
};

总结

通过以上步骤,我们可以开发出一个后台实时定位系统,该系统能够实时监控客户系统里面会员所在的位置,并将会员的位置信息显示在地图上。服务器后端是PHP开发的后台,最终实现成功也就那么一会,不需要太多的技术难度,比如安装各种库、各种配置、各种环境,仅需要在python运行前运行 pip install websockets redis 即可。调试时,有错误直接修改代码即可,不需要重启服务。

标签:info,uid,self,redis,实时,connections,所在位置,后台,logger
From: https://www.cnblogs.com/quaki/p/18654955

相关文章

  • 实时协作:如何通过协作文档提升团队生产力
    在当今快节奏的工作环境中,团队协作的效率直接决定了项目的成败。然而,文档管理作为协作的核心环节,却常常成为效率的“隐形杀手”。无论是版本混乱、信息丢失,还是沟通不畅,这些问题都在无形中消耗着团队的时间和精力。而协作文档工具的出现,正是为了解决这些痛点,彻底改变团队的工作方......
  • 中电金信携手华为发布“全链路实时营销解决方案”,重塑金融营销数智新生态
    在数智化转型成为驱动经济社会高质量发展的新引擎背景下,“数智方案”栏目聚焦金融等国计民生重点行业场景,依托中电金信“源启筑基+咨询引领+应用重构”的产品及服务体系,输出市场洞察和行业解决方案、应用案例,旨在全面推动行业IT架构升级、数智化转型。  数智驱动是金融机构营......
  • 如何登录网站后台并修改代码?
    登录网站后台并修改代码是网站维护和开发过程中常见的任务。以下是详细的步骤和注意事项:获取后台登录信息:确保您拥有网站后台的登录凭证,包括用户名和密码。如果忘记了登录信息,可以联系网站管理员或技术支持。登录后台:打开浏览器,输入网站后台的URL,通常是http://域名/ad......
  • 网站后台是否可以修改代码?
    网站后台是否可以修改代码取决于网站的架构和技术实现。以下是详细的分析和建议:后台管理系统的功能:内容管理系统(CMS):如WordPress、Joomla、Drupal等,通常提供后台管理界面,允许用户修改内容、设置主题和插件,但不直接支持修改底层代码。自定义开发的网站:如果网站是自定义开发的,......
  • 虚拟主机升级后后台传图功能上传不了图片
    用户反馈在对虚拟主机进行升级之后,发现网站后台的传图功能无法正常使用,上传图片时会遇到服务器错误。解决方案:当虚拟主机完成升级后,出现后台传图功能失效的情况,通常是由以下几个方面引起的:1.文件权限问题检查上传目录(如uploads)及其子文件夹的读写权限是否正确设置。一般情况......
  • 实时数仓:基于数据湖的实时数仓与数据治理架构
    设计一个基于数据湖的实时数仓与数据治理架构,需要围绕以下几个核心方面展开:实时数据处理、数据存储与管理、数据质量治理、数据权限管理以及数据消费。以下是一个参考架构方案:一、架构整体概览核心组成部分数据源层数据来源:多样化的数据源(OLTP数据库、日志系统、IoT设......
  • 后台管理系统动态面包屑Breadcrumb组件的实现
    在后管理系统开发中,面包屑导航是一个非常常见的功能,通常是根据当前的url自动生成面包屑导航菜单,当跳转路由发生变化时,面包屑导航都会随之发生变化,即动态面包屑。要完成动态面包屑我们需要制作一个动态数组,数组中每个item都表示一个路由信息,在这里我们使用到route.match......
  • 使用IMU和延迟实时运动学GPS进行四旋翼状态估计(Matlab代码实现)
       ......
  • 网站后台无法登录,提示密码错误怎么办?
    当您尝试登录网站后台时遇到“密码错误”的提示,即使密码没有改动过,这种情况确实令人困扰。以下是几个可能的原因及解决方案:检查输入是否准确无误:有时简单的拼写错误也会导致登录失败。请仔细核对用户名和密码是否完全一致,注意大小写敏感性。清除浏览器缓存和Cookie:某些情况下,浏......
  • 处理织梦后台栏目管理不显示内容及报错问题的方法
    用户报告称其织梦(DedeCMS)后台栏目管理页面不显示任何内容,点击子栏目还会报错。这种情况严重影响了网站内容的管理和更新工作,需要找出根本原因并解决。解决方案:检查数据库连接:确保织梦能够正确连接到数据库。检查应用程序配置文件(如PHP脚本中的config.php),确保数据库连接参数(如......