微爱帮监狱写信寄信小程序智慧天气关怀系统技术方案

一、项目概述

1.1 背景与目标

为服刑人员家属提供其亲人所在监狱的实时天气信息,结合用户设备与环境数据,实现精准、关怀化的天气服务体验。

复制代码
核心目标矩阵:
┌─────────────────┬─────────────────────────────┐
│ 数据维度         │ 技术实现目标                │
├─────────────────┼─────────────────────────────┤
│ 手机型号分析     │ 设备适配与推送优化          │
│ IP位置定位       │ 用户位置智能识别            │
│ 监狱地理信息     │ 精准天气数据匹配            │
│ 用户行为数据     │ 个性化天气关怀              │
└─────────────────┴─────────────────────────────┘

1.2 核心价值

复制代码
温度感知 → 情感连接 → 心理关怀
1. 知道亲人所在地天气冷暖
2. 根据天气发送关怀提醒
3. 建立情感连接桥梁
4. 提供实用生活建议

二、技术架构设计

2.1 系统整体架构

复制代码
┌─────────────────────────────────────────┐
│          数据采集层                       │
│ 设备信息 → IP定位 → 监狱数据库 → 天气API    │
└──────────────┬──────────────────────────┘
                │
                ▼
┌─────────────────────────────────────────┐
│          智能分析引擎                     │
│ 位置匹配 → 天气解析 → 关怀规则 → 个性化生成   │
└──────────────┬──────────────────────────┘
                │
                ▼
┌─────────────────────────────────────────┐
│          服务输出层                       │
│ 天气卡片 → 关怀提醒 → 穿衣建议 → 预警通知    │
└─────────────────────────────────────────┘

2.2 数据流向图

三、核心技术实现

3.1 设备与位置智能识别

复制代码
# 智能位置识别服务
class SmartLocationService:
    """多维度位置识别引擎"""
    
    def __init__(self):
        self.ip_service = IPLocationService()
        self.device_parser = DeviceParser()
        self.prison_db = PrisonDatabase()
        
    async def analyze_user_context(self, request) -> dict:
        """分析用户上下文环境"""
        analysis_result = {}
        
        # 1. 设备信息解析
        device_info = self.parse_device_info(request.headers)
        analysis_result['device'] = device_info
        
        # 2. IP地理位置识别
        ip_location = await self.ip_service.locate(request.remote_addr)
        analysis_result['ip_location'] = ip_location
        
        # 3. 监狱信息匹配(基于用户选择的监狱)
        prison_info = await self.get_prison_info(request.user.prison_id)
        analysis_result['prison_location'] = prison_info['location']
        
        # 4. 位置关系分析
        location_relation = self.analyze_location_relation(
            ip_location, 
            prison_info['location']
        )
        analysis_result['relation'] = location_relation
        
        return analysis_result
    
    def parse_device_info(self, headers: dict) -> dict:
        """解析设备信息(支持多厂商)"""
        user_agent = headers.get('User-Agent', '')
        
        # iOS设备解析
        if 'iPhone' in user_agent or 'iPad' in user_agent:
            return self._parse_ios_device(user_agent)
        
        # 安卓设备解析
        elif 'Android' in user_agent:
            return self._parse_android_device(user_agent)
        
        # 华为鸿蒙设备
        elif 'HarmonyOS' in user_agent:
            return self._parse_harmony_device(user_agent)
        
        # 小米设备
        elif 'Mi' in user_agent or 'XiaoMi' in user_agent:
            return self._parse_xiaomi_device(user_agent)
        
        # 其他设备
        else:
            return self._parse_generic_device(user_agent)
    
    def _parse_android_device(self, ua: str) -> dict:
        """解析安卓设备信息"""
        # 示例UA:Mozilla/5.0 (Linux; Android 13; SM-S9180) AppleWebKit/537.36
        device_info = {
            'os': 'Android',
            'os_version': '',
            'brand': '',
            'model': '',
            'screen_size': '',
            'push_service': 'FCM'  # 默认推送服务
        }
        
        # 提取安卓版本
        if 'Android' in ua:
            import re
            version_match = re.search(r'Android (\d+)', ua)
            if version_match:
                device_info['os_version'] = version_match.group(1)
        
        # 识别品牌和型号
        brands = {
            'SM-': 'Samsung',
            'MI ': 'Xiaomi',
            'Redmi': 'Xiaomi',
            'HUAWEI': 'Huawei',
            'OPPO': 'OPPO',
            'PEDM': 'OPPO',  # OPPO某些型号
            'VOG': 'Huawei', # P30系列
            'VCE': 'Vivo',
            'ONE': 'OnePlus'
        }
        
        for prefix, brand in brands.items():
            if prefix in ua:
                device_info['brand'] = brand
                # 提取具体型号
                model_match = re.search(r'\([^)]*?([A-Z]+-[A-Z0-9]+)\)', ua)
                if model_match:
                    device_info['model'] = model_match.group(1)
                break
        
        # 根据品牌设置推送服务
        push_services = {
            'Huawei': 'HMS',
            'Xiaomi': 'MiPush',
            'OPPO': 'OPPOPush',
            'Vivo': 'VivoPush',
            'Samsung': 'FCM',
            'default': 'FCM'
        }
        
        device_info['push_service'] = push_services.get(
            device_info['brand'], 
            push_services['default']
        )
        
        return device_info
    
    def _parse_ios_device(self, ua: str) -> dict:
        """解析iOS设备信息"""
        # 示例UA:Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X)
        device_info = {
            'os': 'iOS',
            'os_version': '',
            'brand': 'Apple',
            'model': '',
            'push_service': 'APNs'
        }
        
        # 提取iOS版本
        import re
        version_match = re.search(r'CPU (?:iPhone )?OS (\d+_\d+)', ua)
        if version_match:
            device_info['os_version'] = version_match.group(1).replace('_', '.')
        
        # 识别iPhone型号
        if 'iPhone' in ua:
            # 根据分辨率等特征识别具体型号
            if 'iPhone15,3' in ua or 'iPhone15,4' in ua:
                device_info['model'] = 'iPhone 14 Pro'
            elif 'iPhone14,7' in ua:
                device_info['model'] = 'iPhone 14'
            # 更多型号识别...
        
        return device_info

3.2 IP智能定位引擎

复制代码
# IP定位与监狱匹配服务
class IntelligentIPLocator:
    """智能IP定位与监狱匹配"""
    
    def __init__(self):
        # IP定位服务(多源冗余)
        self.ip_services = [
            {'name': 'ipapi', 'url': 'https://ipapi.co/{ip}/json/'},
            {'name': 'ipinfo', 'url': 'https://ipinfo.io/{ip}/json'},
            {'name': 'taobao', 'url': 'https://ip.taobao.com/service/getIpInfo.php?ip={ip}'}
        ]
        
        # 监狱地理位置数据库
        self.prison_locations = self.load_prison_geodata()
        
    async def locate_with_fallback(self, ip_address: str) -> dict:
        """多源IP定位(带降级策略)"""
        location_data = None
        
        # 尝试第一个服务
        try:
            location_data = await self.query_ip_service(0, ip_address)
            if location_data and self.validate_location(location_data):
                return self.format_location_data(location_data)
        except Exception:
            pass
        
        # 尝试第二个服务
        try:
            location_data = await self.query_ip_service(1, ip_address)
            if location_data and self.validate_location(location_data):
                return self.format_location_data(location_data)
        except Exception:
            pass
        
        # 尝试第三个服务
        try:
            location_data = await self.query_ip_service(2, ip_address)
            if location_data and self.validate_location(location_data):
                return self.format_location_data(location_data)
        except Exception:
            pass
        
        # 所有服务都失败,返回默认位置(北京)
        return {
            'city': '北京',
            'province': '北京',
            'country': '中国',
            'latitude': 39.9042,
            'longitude': 116.4074,
            'accuracy': 'low'
        }
    
    async def find_nearest_prisons(self, user_location: dict, limit: int = 3) -> list:
        """查找用户附近的监狱"""
        from geopy.distance import geodesic
        
        user_coords = (user_location['latitude'], user_location['longitude'])
        prisons_with_distance = []
        
        for prison in self.prison_locations:
            prison_coords = (prison['latitude'], prison['longitude'])
            distance = geodesic(user_coords, prison_coords).kilometers
            
            prisons_with_distance.append({
                'prison': prison,
                'distance_km': round(distance, 1),
                'direction': self.calculate_direction(user_coords, prison_coords)
            })
        
        # 按距离排序
        prisons_with_distance.sort(key=lambda x: x['distance_km'])
        
        return prisons_with_distance[:limit]
    
    def calculate_direction(self, from_coords, to_coords) -> str:
        """计算方位角"""
        import math
        
        lat1, lon1 = math.radians(from_coords[0]), math.radians(from_coords[1])
        lat2, lon2 = math.radians(to_coords[0]), math.radians(to_coords[1])
        
        d_lon = lon2 - lon1
        
        y = math.sin(d_lon) * math.cos(lat2)
        x = math.cos(lat1) * math.sin(lat2) - math.sin(lat1) * math.cos(lat2) * math.cos(d_lon)
        
        bearing = math.degrees(math.atan2(y, x))
        bearing = (bearing + 360) % 360
        
        # 转换为中文方位
        directions = ['北', '东北', '东', '东南', '南', '西南', '西', '西北']
        idx = round(bearing / 45) % 8
        
        return directions[idx]

3.3 监狱天气智能匹配

复制代码
# 监狱天气服务
class PrisonWeatherService:
    """监狱天气智能服务"""
    
    def __init__(self):
        self.weather_api = WeatherAPI()
        self.prison_db = PrisonDatabase()
        self.care_rules = CareRules()
        
    async def get_prison_weather(self, prison_id: str, user_context: dict = None) -> dict:
        """获取监狱天气信息(带智能分析)"""
        # 1. 获取监狱基本信息
        prison_info = await self.prison_db.get_prison(prison_id)
        
        # 2. 获取监狱所在地天气
        weather_data = await self.weather_api.get_weather(
            city=prison_info['city'],
            province=prison_info['province']
        )
        
        # 3. 如果有用户上下文,进行对比分析
        if user_context:
            user_weather = await self.get_user_weather(user_context)
            comparison = self.compare_weathers(user_weather, weather_data)
        else:
            comparison = None
        
        # 4. 生成关怀建议
        care_suggestions = self.generate_care_suggestions(
            weather_data, 
            prison_info,
            user_context
        )
        
        # 5. 构建响应
        return {
            'prison_info': {
                'name': prison_info['name'],
                'city': prison_info['city'],
                'province': prison_info['province'],
                'address': prison_info.get('address', ''),
                'climate_zone': prison_info.get('climate_zone', '')  # 气候带
            },
            'weather': {
                'current': self.format_current_weather(weather_data),
                'forecast': self.format_forecast(weather_data),
                'alerts': self.extract_alerts(weather_data)
            },
            'comparison': comparison,
            'care_suggestions': care_suggestions,
            'updated_at': datetime.now().isoformat()
        }
    
    def generate_care_suggestions(self, weather: dict, prison: dict, user_context: dict = None) -> dict:
        """生成关怀建议"""
        suggestions = {
            'dressing': [],
            'health': [],
            'emotional': [],
            'practical': []
        }
        
        # 基于温度的建议
        temp = weather.get('temperature', 20)
        if temp < 0:
            suggestions['dressing'].append('监狱所在地气温低于0°C,建议提醒亲人添加厚衣物')
            suggestions['health'].append('严寒天气,注意防寒保暖,预防感冒')
        elif temp < 10:
            suggestions['dressing'].append('气温较低,建议穿羽绒服或厚外套')
        elif temp > 30:
            suggestions['dressing'].append('气温较高,建议穿轻薄透气的衣物')
            suggestions['health'].append('高温天气,注意防暑降温,多补充水分')
        
        # 基于天气现象的建议
        weather_desc = weather.get('description', '').lower()
        
        if '雨' in weather_desc:
            suggestions['practical'].append('监狱所在地有雨,邮寄物品请注意防水包装')
            suggestions['emotional'].append('雨天易让人情绪低落,可以多写一些鼓励的话语')
        
        if '雪' in weather_desc:
            suggestions['dressing'].append('下雪天气,道路湿滑,注意安全')
            suggestions['emotional'].append('雪景虽美,但天气寒冷,提醒亲人注意保暖')
        
        if '雾' in weather_desc or '霾' in weather_desc:
            suggestions['health'].append('空气质量较差,建议减少户外活动')
        
        # 基于季节的建议
        current_month = datetime.now().month
        if current_month in [12, 1, 2]:  # 冬季
            suggestions['emotional'].append('寒冬时节,一封家书最能温暖人心')
        elif current_month in [6, 7, 8]:  # 夏季
            suggestions['practical'].append('夏季炎热,可以邮寄一些清凉用品')
        
        # 基于用户距离的建议
        if user_context and 'distance_km' in user_context:
            distance = user_context['distance_km']
            if distance > 500:
                suggestions['emotional'].append(f'您距离亲人{distance}公里,虽然遥远,但心在一起')
        
        return suggestions
    
    def compare_weathers(self, user_weather: dict, prison_weather: dict) -> dict:
        """对比用户所在地与监狱所在地天气"""
        comparison = {
            'temperature_diff': prison_weather.get('temperature', 0) - user_weather.get('temperature', 0),
            'weather_similarity': self.calculate_weather_similarity(user_weather, prison_weather),
            'season_same': self.check_same_season(user_weather, prison_weather),
            'notable_differences': []
        }
        
        # 温度差异提醒
        temp_diff = abs(comparison['temperature_diff'])
        if temp_diff > 10:
            comparison['notable_differences'].append(
                f"温度差异较大(相差{temp_diff}°C),请注意气候变化"
            )
        
        # 天气状况差异
        if user_weather.get('description') != prison_weather.get('description'):
            comparison['notable_differences'].append(
                f"您这里{user_weather.get('description', '')},监狱那边{prison_weather.get('description', '')}"
            )
        
        return comparison

3.4 天气数据智能缓存

复制代码
# 天气数据缓存与更新策略
class WeatherCacheManager:
    """天气数据智能缓存"""
    
    def __init__(self):
        self.redis_client = redis.Redis(
            host='localhost',
            port=6379,
            db=0,
            decode_responses=True
        )
        
        # 缓存策略配置
        self.cache_policies = {
            'current_weather': {
                'ttl': 600,  # 10分钟
                'key_pattern': 'weather:current:{city_code}'
            },
            'forecast': {
                'ttl': 3600,  # 1小时
                'key_pattern': 'weather:forecast:{city_code}'
            },
            'alerts': {
                'ttl': 300,  # 5分钟
                'key_pattern': 'weather:alerts:{city_code}'
            }
        }
        
        # 城市到监狱的映射关系
        self.city_prison_map = self.build_city_prison_map()
    
    async def get_weather_with_cache(self, city_code: str, weather_type: str = 'current') -> dict:
        """获取带缓存的天气数据"""
        policy = self.cache_policies.get(weather_type)
        if not policy:
            return await self.fetch_fresh_weather(city_code, weather_type)
        
        cache_key = policy['key_pattern'].format(city_code=city_code)
        
        # 尝试从缓存获取
        cached_data = self.redis_client.get(cache_key)
        if cached_data:
            try:
                data = json.loads(cached_data)
                # 检查数据新鲜度
                if self.is_data_fresh(data, policy['ttl']):
                    return data
            except:
                pass
        
        # 缓存未命中或过期,获取新数据
        fresh_data = await self.fetch_fresh_weather(city_code, weather_type)
        
        # 存入缓存
        if fresh_data:
            cache_data = {
                'data': fresh_data,
                'timestamp': datetime.now().isoformat(),
                'source': 'api'
            }
            self.redis_client.setex(
                cache_key,
                policy['ttl'],
                json.dumps(cache_data)
            )
            
            # 更新相关监狱的缓存
            await self.update_related_prisons_cache(city_code, fresh_data)
        
        return fresh_data
    
    async def update_related_prisons_cache(self, city_code: str, weather_data: dict):
        """更新相关监狱的天气缓存"""
        # 查找该城市的所有监狱
        prisons = self.city_prison_map.get(city_code, [])
        
        for prison_id in prisons:
            prison_key = f"prison:weather:{prison_id}"
            
            # 获取现有监狱天气数据
            cached_prison_weather = self.redis_client.get(prison_key)
            if cached_prison_weather:
                try:
                    prison_weather = json.loads(cached_prison_weather)
                    # 更新天气部分
                    prison_weather['data']['weather'] = weather_data
                    prison_weather['timestamp'] = datetime.now().isoformat()
                    
                    # 重新保存
                    self.redis_client.setex(
                        prison_key,
                        600,  # 10分钟
                        json.dumps(prison_weather)
                    )
                except:
                    pass
    
    def build_city_prison_map(self) -> dict:
        """构建城市到监狱的映射关系"""
        # 从数据库获取所有监狱信息
        prisons = Prison.objects.all().values('id', 'city_code')
        
        city_map = {}
        for prison in prisons:
            city_code = prison['city_code']
            prison_id = prison['id']
            
            if city_code not in city_map:
                city_map[city_code] = []
            
            city_map[city_code].append(prison_id)
        
        return city_map

四、前端展示方案

4.1 天气关怀卡片设计

复制代码
<!-- 监狱天气关怀卡片组件 -->
<template>
  <div class="weather-care-card" :class="weatherClass">
    <!-- 头部:监狱信息 -->
    <div class="card-header">
      <div class="prison-info">
        <h3>{{ prisonName }}</h3>
        <p class="prison-location">
          <i class="icon-location"></i>
          {{ province }} {{ city }}
          <span v-if="distance" class="distance">(距您{{ distance }}km)</span>
        </p>
      </div>
      <div class="weather-icon">
        <i :class="weatherIconClass"></i>
      </div>
    </div>
    
    <!-- 主体:天气信息 -->
    <div class="card-body">
      <div class="current-weather">
        <div class="temperature">
          <span class="temp-value">{{ currentTemp }}</span>
          <span class="temp-unit">°C</span>
        </div>
        <div class="weather-desc">
          <p>{{ weatherDescription }}</p>
          <p class="feels-like">体感温度 {{ feelsLike }}°C</p>
        </div>
      </div>
      
      <!-- 天气详情 -->
      <div class="weather-details">
        <div class="detail-item">
          <i class="icon-humidity"></i>
          <span>湿度 {{ humidity }}%</span>
        </div>
        <div class="detail-item">
          <i class="icon-wind"></i>
          <span>{{ windDirection }}风 {{ windSpeed }}级</span>
        </div>
        <div class="detail-item">
          <i class="icon-pressure"></i>
          <span>气压 {{ pressure }}hPa</span>
        </div>
      </div>
      
      <!-- 天气对比(如果有关联用户) -->
      <div v-if="weatherComparison" class="weather-comparison">
        <div class="comparison-title">
          <i class="icon-comparison"></i>
          <span>与您所在地对比</span>
        </div>
        <div class="comparison-content">
          <p v-if="temperatureDiff > 0">
            监狱比您这里温暖{{ Math.abs(temperatureDiff) }}°C
          </p>
          <p v-else-if="temperatureDiff < 0">
            监狱比您这里寒冷{{ Math.abs(temperatureDiff) }}°C
          </p>
          <p v-else>
            两地温度相近
          </p>
        </div>
      </div>
    </div>
    
    <!-- 底部:关怀建议 -->
    <div class="card-footer">
      <div class="care-suggestions">
        <h4><i class="icon-care"></i> 关怀建议</h4>
        <ul>
          <li v-for="(suggestion, index) in careSuggestions" :key="index">
            {{ suggestion }}
          </li>
        </ul>
      </div>
      
      <!-- 快捷操作 -->
      <div class="quick-actions">
        <button @click="shareWeather" class="btn-share">
          <i class="icon-share"></i> 分享天气
        </button>
        <button @click="sendCareMessage" class="btn-care">
          <i class="icon-message"></i> 发送关怀
        </button>
        <button @click="setReminder" class="btn-reminder">
          <i class="icon-reminder"></i> 天气提醒
        </button>
      </div>
    </div>
    
    <!-- 更新时间 -->
    <div class="card-updated">
      更新时间: {{ updatedTime }}
    </div>
  </div>
</template>

<script>
export default {
  name: 'PrisonWeatherCard',
  props: {
    prisonData: {
      type: Object,
      required: true
    },
    userLocation: {
      type: Object,
      default: null
    }
  },
  data() {
    return {
      // 从props中提取数据
      prisonName: this.prisonData.prison_info.name,
      province: this.prisonData.prison_info.province,
      city: this.prisonData.prison_info.city,
      weather: this.prisonData.weather,
      careSuggestions: this.extractCareSuggestions(this.prisonData.care_suggestions),
      distance: this.calculateDistance(),
      weatherComparison: this.prisonData.comparison
    }
  },
  computed: {
    currentTemp() {
      return this.weather.current.temperature || 20
    },
    weatherDescription() {
      return this.weather.current.description || '晴'
    },
    feelsLike() {
      return this.weather.current.feels_like || this.currentTemp
    },
    humidity() {
      return this.weather.current.humidity || 50
    },
    windSpeed() {
      return this.weather.current.wind_speed || 2
    },
    windDirection() {
      return this.weather.current.wind_direction || '东'
    },
    pressure() {
      return this.weather.current.pressure || 1013
    },
    weatherIconClass() {
      const desc = this.weatherDescription.toLowerCase()
      if (desc.includes('晴')) return 'icon-sunny'
      if (desc.includes('云')) return 'icon-cloudy'
      if (desc.includes('雨')) return 'icon-rainy'
      if (desc.includes('雪')) return 'icon-snowy'
      if (desc.includes('雾') || desc.includes('霾')) return 'icon-foggy'
      return 'icon-default'
    },
    weatherClass() {
      const temp = this.currentTemp
      if (temp < 0) return 'weather-freezing'
      if (temp < 10) return 'weather-cold'
      if (temp < 25) return 'weather-comfortable'
      if (temp < 35) return 'weather-warm'
      return 'weather-hot'
    },
    temperatureDiff() {
      return this.weatherComparison?.temperature_diff || 0
    },
    updatedTime() {
      return new Date(this.prisonData.updated_at).toLocaleString('zh-CN')
    }
  },
  methods: {
    extractCareSuggestions(suggestionsObj) {
      const allSuggestions = []
      Object.values(suggestionsObj).forEach(category => {
        allSuggestions.push(...category)
      })
      return allSuggestions.slice(0, 3) // 只显示前3条
    },
    calculateDistance() {
      if (!this.userLocation || !this.prisonData.prison_info.coordinates) {
        return null
      }
      // 计算距离的逻辑
      return this.calcDistance(
        this.userLocation.latitude,
        this.userLocation.longitude,
        this.prisonData.prison_info.coordinates.latitude,
        this.prisonData.prison_info.coordinates.longitude
      )
    },
    calcDistance(lat1, lon1, lat2, lon2) {
      // 简化版距离计算(Haversine公式)
      const R = 6371 // 地球半径(公里)
      const dLat = this.toRad(lat2 - lat1)
      const dLon = this.toRad(lon2 - lon1)
      const a = 
        Math.sin(dLat/2) * Math.sin(dLat/2) +
        Math.cos(this.toRad(lat1)) * Math.cos(this.toRad(lat2)) * 
        Math.sin(dLon/2) * Math.sin(dLon/2)
      const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a))
      return Math.round(R * c)
    },
    toRad(degrees) {
      return degrees * (Math.PI/180)
    },
    shareWeather() {
      // 分享天气信息
      const shareData = {
        title: `${this.prisonName}天气情况`,
        text: `${this.prisonName}(${this.province}${this.city})现在${this.weatherDescription},温度${this.currentTemp}°C`,
        url: window.location.href
      }
      
      if (navigator.share) {
        navigator.share(shareData)
      } else {
        // 降级处理
        this.copyToClipboard(shareData.text)
        this.$toast.success('已复制天气信息到剪贴板')
      }
    },
    sendCareMessage() {
      // 跳转到写信页面,并预填充天气相关关怀内容
      const message = `最近${this.city}天气${this.weatherDescription},温度${this.currentTemp}°C,${this.careSuggestions[0] || '请注意身体'}`
      this.$router.push({
        path: '/write-letter',
        query: {
          preset_message: message,
          prison_id: this.prisonData.prison_info.id
        }
      })
    },
    setReminder() {
      // 设置天气变化提醒
      this.$emit('set-reminder', {
        prisonId: this.prisonData.prison_info.id,
        prisonName: this.prisonName,
        currentWeather: this.weatherDescription,
        currentTemp: this.currentTemp
      })
    }
  }
}
</script>

<style scoped>
.weather-care-card {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  border-radius: 20px;
  color: white;
  padding: 20px;
  margin: 15px;
  box-shadow: 0 10px 30px rgba(0,0,0,0.2);
  position: relative;
  overflow: hidden;
}

.weather-care-card.weather-freezing {
  background: linear-gradient(135deg, #74b9ff 0%, #0984e3 100%);
}

.weather-care-card.weather-cold {
  background: linear-gradient(135deg, #a8e6cf 0%, #56CCF2 100%);
}

.weather-care-card.weather-comfortable {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}

.weather-care-card.weather-warm {
  background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
}

.weather-care-card.weather-hot {
  background: linear-gradient(135deg, #f6d365 0%, #fda085 100%);
}

.card-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  margin-bottom: 20px;
}

.prison-info h3 {
  margin: 0;
  font-size: 1.3em;
  font-weight: bold;
}

.prison-location {
  margin: 5px 0 0 0;
  font-size: 0.9em;
  opacity: 0.9;
}

.distance {
  font-size: 0.8em;
  opacity: 0.7;
}

.weather-icon {
  font-size: 2.5em;
}

.current-weather {
  display: flex;
  align-items: center;
  margin-bottom: 20px;
}

.temperature {
  font-size: 3em;
  font-weight: bold;
  margin-right: 20px;
}

.temp-unit {
  font-size: 0.5em;
  vertical-align: super;
}

.weather-desc p {
  margin: 5px 0;
}

.feels-like {
  font-size: 0.9em;
  opacity: 0.8;
}

.weather-details {
  display: flex;
  justify-content: space-around;
  margin: 20px 0;
  padding: 15px;
  background: rgba(255,255,255,0.1);
  border-radius: 10px;
}

.detail-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  font-size: 0.9em;
}

.detail-item i {
  font-size: 1.2em;
  margin-bottom: 5px;
}

.weather-comparison {
  background: rgba(255,255,255,0.15);
  border-radius: 10px;
  padding: 15px;
  margin: 20px 0;
}

.comparison-title {
  display: flex;
  align-items: center;
  margin-bottom: 10px;
}

.comparison-title i {
  margin-right: 8px;
}

.card-footer {
  margin-top: 20px;
}

.care-suggestions h4 {
  margin: 0 0 10px 0;
  display: flex;
  align-items: center;
}

.care-suggestions h4 i {
  margin-right: 8px;
}

.care-suggestions ul {
  margin: 0;
  padding-left: 20px;
}

.care-suggestions li {
  margin-bottom: 5px;
  font-size: 0.9em;
}

.quick-actions {
  display: flex;
  justify-content: space-between;
  margin-top: 20px;
}

.quick-actions button {
  flex: 1;
  margin: 0 5px;
  padding: 10px;
  border: none;
  border-radius: 8px;
  background: rgba(255,255,255,0.2);
  color: white;
  font-size: 0.9em;
  cursor: pointer;
  transition: background 0.3s;
  display: flex;
  justify-content: center;
  align-items: center;
}

.quick-actions button:hover {
  background: rgba(255,255,255,0.3);
}

.quick-actions button i {
  margin-right: 5px;
}

.card-updated {
  text-align: center;
  margin-top: 15px;
  font-size: 0.8em;
  opacity: 0.7;
}
</style>

4.2 监狱天气地图组件

复制代码
<!-- 监狱天气地图视图 -->
<template>
  <div class="weather-map-container">
    <!-- 地图区域 -->
    <div class="map-area" ref="mapContainer"></div>
    
    <!-- 侧边栏:监狱列表 -->
    <div class="sidebar">
      <div class="sidebar-header">
        <h3>监狱天气列表</h3>
        <div class="search-box">
          <input 
            v-model="searchQuery"
            placeholder="搜索监狱或城市..."
            type="text"
          />
        </div>
      </div>
      
      <div class="prison-list">
        <div 
          v-for="prison in filteredPrisons"
          :key="prison.id"
          class="prison-item"
          :class="{ active: activePrisonId === prison.id }"
          @click="selectPrison(prison)"
        >
          <div class="prison-item-main">
            <div class="prison-name">{{ prison.name }}</div>
            <div class="prison-location">{{ prison.city }}, {{ prison.province }}</div>
            <div class="prison-weather">
              <span class="weather-temp">{{ prison.weather?.current?.temperature || '--' }}°C</span>
              <span class="weather-desc">{{ prison.weather?.current?.description || '未知' }}</span>
            </div>
          </div>
          <div class="prison-distance" v-if="prison.distance">
            {{ prison.distance }}km
          </div>
        </div>
      </div>
    </div>
    
    <!-- 底部工具栏 -->
    <div class="toolbar">
      <div class="toolbar-left">
        <button @click="locateUser" class="toolbar-btn">
          <i class="icon-location"></i> 定位我
        </button>
        <button @click="toggleTemperatureLayer" class="toolbar-btn">
          <i class="icon-temperature"></i> {{ showTemperature ? '隐藏' : '显示' }}温度
        </button>
        <button @click="toggleWeatherLayer" class="toolbar-btn">
          <i class="icon-weather"></i> {{ showWeather ? '隐藏' : '显示' }}天气
        </button>
      </div>
      
      <div class="toolbar-right">
        <button @click="refreshWeather" class="toolbar-btn refresh-btn">
          <i class="icon-refresh"></i> 刷新天气
        </button>
      </div>
    </div>
  </div>
</template>

<script>
import AMapLoader from '@amap/amap-jsapi-loader'

export default {
  name: 'PrisonWeatherMap',
  data() {
    return {
      map: null,
      markers: [],
      searchQuery: '',
      activePrisonId: null,
      showTemperature: true,
      showWeather: true,
      userLocation: null,
      prisons: []
    }
  },
  computed: {
    filteredPrisons() {
      if (!this.searchQuery) return this.prisons
      
      const query = this.searchQuery.toLowerCase()
      return this.prisons.filter(prison => {
        return (
          prison.name.toLowerCase().includes(query) ||
          prison.city.toLowerCase().includes(query) ||
          prison.province.toLowerCase().includes(query)
        )
      })
    }
  },
  async mounted() {
    await this.initMap()
    await this.loadPrisonData()
    await this.getUserLocation()
    this.renderPrisonMarkers()
  },
  methods: {
    async initMap() {
      try {
        // 加载高德地图
        const AMap = await AMapLoader.load({
          key: 'your-amap-key',
          version: '2.0',
          plugins: ['AMap.Weather', 'AMap.Geolocation']
        })
        
        // 创建地图实例
        this.map = new AMap.Map(this.$refs.mapContainer, {
          zoom: 5,
          center: [116.397428, 39.90923], // 北京中心
          viewMode: '3D'
        })
        
        // 添加天气插件
        this.weatherPlugin = new AMap.Weather()
        
        // 添加定位控件
        const geolocation = new AMap.Geolocation({
          enableHighAccuracy: true,
          timeout: 10000,
          buttonOffset: new AMap.Pixel(10, 20),
          panToLocation: true,
          zoomToAccuracy: true
        })
        
        this.map.addControl(geolocation)
        
        // 监听地图点击
        this.map.on('click', (e) => {
          this.activePrisonId = null
        })
        
      } catch (error) {
        console.error('地图初始化失败:', error)
      }
    },
    
    async loadPrisonData() {
      try {
        // 从API获取监狱数据
        const response = await this.$api.get('/api/prisons/weather')
        this.prisons = response.data
        
        // 为每个监狱获取天气信息
        for (const prison of this.prisons) {
          const weather = await this.$api.get(`/api/weather/prison/${prison.id}`)
          prison.weather = weather.data
        }
      } catch (error) {
        console.error('加载监狱数据失败:', error)
      }
    },
    
    async getUserLocation() {
      try {
        const position = await new Promise((resolve, reject) => {
          navigator.geolocation.getCurrentPosition(resolve, reject, {
            enableHighAccuracy: true,
            timeout: 5000,
            maximumAge: 0
          })
        })
        
        this.userLocation = {
          latitude: position.coords.latitude,
          longitude: position.coords.longitude
        }
        
        // 在地图上标记用户位置
        if (this.map) {
          const AMap = await AMapLoader.load()
          const userMarker = new AMap.Marker({
            position: [this.userLocation.longitude, this.userLocation.latitude],
            title: '我的位置',
            icon: '//a.amap.com/jsapi_demos/static/demo-center/icons/poi-marker-default.png',
            offset: new AMap.Pixel(-13, -30)
          })
          
          userMarker.setMap(this.map)
          
          // 计算每个监狱与用户的距离
          this.calculateDistances()
        }
      } catch (error) {
        console.warn('获取用户位置失败:', error)
      }
    },
    
    calculateDistances() {
      if (!this.userLocation) return
      
      const AMap = window.AMap
      if (!AMap) return
      
      this.prisons.forEach(prison => {
        if (prison.longitude && prison.latitude) {
          const distance = AMap.GeometryUtil.distance(
            [this.userLocation.longitude, this.userLocation.latitude],
            [prison.longitude, prison.latitude]
          )
          prison.distance = Math.round(distance / 1000) // 转换为公里
        }
      })
      
      // 按距离排序
      this.prisons.sort((a, b) => (a.distance || Infinity) - (b.distance || Infinity))
    },
    
    renderPrisonMarkers() {
      if (!this.map || !window.AMap) return
      
      // 清除现有标记
      this.markers.forEach(marker => marker.setMap(null))
      this.markers = []
      
      const AMap = window.AMap
      
      // 为每个监狱创建标记
      this.prisons.forEach(prison => {
        if (!prison.longitude || !prison.latitude) return
        
        // 根据天气设置标记颜色
        const temp = prison.weather?.current?.temperature || 20
        let color = '#4CAF50' // 默认绿色
        
        if (temp < 0) color = '#2196F3'      // 蓝色,寒冷
        if (temp > 30) color = '#FF9800'     // 橙色,炎热
        
        // 创建标记
        const marker = new AMap.Marker({
          position: [prison.longitude, prison.latitude],
          title: `${prison.name}\n温度: ${temp}°C`,
          map: this.map,
          icon: this.createCustomIcon(color, prison.weather?.current?.description),
          extData: prison
        })
        
        // 添加点击事件
        marker.on('click', (e) => {
          this.selectPrison(prison)
          this.map.setCenter([prison.longitude, prison.latitude])
          this.map.setZoom(12)
        })
        
        this.markers.push(marker)
      })
    },
    
    createCustomIcon(color, weatherDesc) {
      // 创建自定义图标
      const size = 40
      const svg = `
        <svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}">
          <circle cx="${size/2}" cy="${size/2}" r="${size/2 - 2}" 
                  fill="${color}" stroke="white" stroke-width="2"/>
          <text x="${size/2}" y="${size/2 + 4}" 
                text-anchor="middle" fill="white" font-size="12">
            ${this.getWeatherIcon(weatherDesc)}
          </text>
        </svg>
      `
      
      return new window.AMap.Icon({
        size: new window.AMap.Size(size, size),
        image: 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svg),
        imageSize: new window.AMap.Size(size, size)
      })
    },
    
    getWeatherIcon(desc) {
      if (!desc) return '☀️'
      
      const descLower = desc.toLowerCase()
      if (descLower.includes('晴')) return '☀️'
      if (descLower.includes('云')) return '☁️'
      if (descLower.includes('雨')) return '🌧️'
      if (descLower.includes('雪')) return '❄️'
      if (descLower.includes('雷')) return '⛈️'
      if (descLower.includes('雾') || descLower.includes('霾')) return '🌫️'
      return '🌤️'
    },
    
    selectPrison(prison) {
      this.activePrisonId = prison.id
      
      // 聚焦到选中的监狱
      if (this.map) {
        this.map.setCenter([prison.longitude, prison.latitude])
        this.map.setZoom(12)
      }
      
      // 触发父组件事件
      this.$emit('prison-selected', prison)
    },
    
    locateUser() {
      if (this.userLocation && this.map) {
        this.map.setCenter([this.userLocation.longitude, this.userLocation.latitude])
        this.map.setZoom(12)
      } else {
        this.$toast.info('正在获取您的位置...')
        this.getUserLocation()
      }
    },
    
    toggleTemperatureLayer() {
      this.showTemperature = !this.showTemperature
      // 切换温度图层显示
    },
    
    toggleWeatherLayer() {
      this.showWeather = !this.showWeather
      // 切换天气图层显示
    },
    
    async refreshWeather() {
      this.$toast.loading('正在更新天气数据...')
      
      try {
        await this.loadPrisonData()
        this.renderPrisonMarkers()
        this.$toast.success('天气数据更新成功')
      } catch (error) {
        this.$toast.error('更新天气数据失败')
      }
    }
  }
}
</script>

<style scoped>
.weather-map-container {
  position: relative;
  width: 100%;
  height: 600px;
  border-radius: 10px;
  overflow: hidden;
}

.map-area {
  width: 100%;
  height: 100%;
}

.sidebar {
  position: absolute;
  top: 20px;
  left: 20px;
  width: 320px;
  background: white;
  border-radius: 10px;
  box-shadow: 0 4px 20px rgba(0,0,0,0.15);
  max-height: calc(100% - 40px);
  display: flex;
  flex-direction: column;
}

.sidebar-header {
  padding: 20px;
  border-bottom: 1px solid #eee;
}

.sidebar-header h3 {
  margin: 0 0 15px 0;
  color: #333;
}

.search-box input {
  width: 100%;
  padding: 10px 15px;
  border: 1px solid #ddd;
  border-radius: 8px;
  font-size: 14px;
  outline: none;
}

.search-box input:focus {
  border-color: #667eea;
}

.prison-list {
  flex: 1;
  overflow-y: auto;
  max-height: 400px;
}

.prison-item {
  padding: 15px 20px;
  border-bottom: 1px solid #f5f5f5;
  cursor: pointer;
  transition: background-color 0.2s;
}

.prison-item:hover {
  background-color: #f9f9f9;
}

.prison-item.active {
  background-color: #eef2ff;
  border-left: 4px solid #667eea;
}

.prison-item-main {
  flex: 1;
}

.prison-name {
  font-weight: bold;
  color: #333;
  margin-bottom: 5px;
}

.prison-location {
  font-size: 12px;
  color: #666;
  margin-bottom: 8px;
}

.prison-weather {
  display: flex;
  align-items: center;
  gap: 10px;
}

.weather-temp {
  font-size: 16px;
  font-weight: bold;
  color: #f39c12;
}

.weather-desc {
  font-size: 12px;
  color: #666;
  background: #f5f5f5;
  padding: 2px 8px;
  border-radius: 10px;
}

.prison-distance {
  font-size: 12px;
  color: #888;
  background: #f0f0f0;
  padding: 2px 8px;
  border-radius: 10px;
}

.toolbar {
  position: absolute;
  bottom: 20px;
  left: 0;
  right: 0;
  display: flex;
  justify-content: space-between;
  padding: 0 20px;
}

.toolbar-left, .toolbar-right {
  display: flex;
  gap: 10px;
}

.toolbar-btn {
  padding: 10px 15px;
  background: white;
  border: none;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: 14px;
  transition: all 0.2s;
}

.toolbar-btn:hover {
  background: #f5f5f5;
  transform: translateY(-1px);
  box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}

.refresh-btn {
  background: #667eea;
  color: white;
}

.refresh-btn:hover {
  background: #5a67d8;
}
</style>

五、智能推送策略

5.1 天气关怀推送引擎

复制代码
# 智能天气推送服务
class WeatherCarePushService:
    """天气关怀智能推送"""
    
    def __init__(self):
        self.push_clients = {
            'ios': APNsClient(),
            'android_fcm': FCMClient(),
            'huawei': HMSClient(),
            'xiaomi': MiPushClient(),
            'oppo': OPPOPushClient(),
            'vivo': VivoPushClient()
        }
        
        self.push_rules = self.load_push_rules()
    
    async def send_weather_care_push(self, user_id: str, prison_weather: dict):
        """发送天气关怀推送"""
        # 1. 获取用户设备信息
        user_device = await self.get_user_device(user_id)
        if not user_device:
            return
        
        # 2. 获取推送客户端
        push_client = self.get_push_client(user_device)
        
        # 3. 生成个性化推送内容
        push_content = self.generate_push_content(
            user_id, 
            prison_weather,
            user_device
        )
        
        # 4. 发送推送
        try:
            result = await push_client.send(
                device_token=user_device['device_token'],
                title=push_content['title'],
                body=push_content['body'],
                data=push_content['data'],
                badge=push_content.get('badge', 1)
            )
            
            # 5. 记录推送日志
            await self.log_push_activity(
                user_id=user_id,
                push_type='weather_care',
                content=push_content,
                result=result,
                prison_id=prison_weather['prison_info']['id']
            )
            
            return result
            
        except Exception as e:
            logger.error(f"推送发送失败: {str(e)}")
            return None
    
    def generate_push_content(self, user_id: str, prison_weather: dict, device_info: dict) -> dict:
        """生成个性化推送内容"""
        prison_name = prison_weather['prison_info']['name']
        weather = prison_weather['weather']['current']
        
        # 根据天气情况选择推送模板
        temp = weather.get('temperature', 20)
        description = weather.get('description', '')
        
        # 温度相关推送
        if temp < 0:
            title = "❄️ 天寒地冻,牵挂更暖"
            body = f"{prison_name}现在零下{abs(temp)}°C,提醒亲人注意防寒保暖"
        elif temp < 10:
            title = "🧥 天气转凉,添衣保暖"
            body = f"{prison_name}气温{temp}°C,记得提醒亲人加衣"
        elif temp > 30:
            title = "☀️ 炎炎夏日,注意防暑"
            body = f"{prison_name}高温{temp}°C,提醒亲人多喝水防中暑"
        elif '雨' in description:
            title = "🌧️ 雨天关怀,温暖问候"
            body = f"{prison_name}正在下雨,温度{temp}°C,送去您的问候"
        elif '雪' in description:
            title = "❄️ 雪花纷飞,牵挂更浓"
            body = f"{prison_name}正在下雪,温度{temp}°C,提醒亲人注意保暖"
        else:
            title = "🌤️ 天气晴好,送去问候"
            body = f"{prison_name}天气{description},温度{temp}°C,一切安好"
        
        # 添加关怀建议
        care_suggestions = prison_weather['care_suggestions']
        if care_suggestions.get('dressing'):
            body += f",{care_suggestions['dressing'][0]}"
        
        # 构建推送数据
        push_data = {
            'type': 'weather_care',
            'prison_id': prison_weather['prison_info']['id'],
            'prison_name': prison_name,
            'temperature': temp,
            'weather': description,
            'timestamp': int(time.time()),
            'deep_link': f"weiaibang://weather/prison/{prison_weather['prison_info']['id']}"
        }
        
        return {
            'title': title,
            'body': body,
            'data': push_data,
            'sound': 'default',
            'badge': 1
        }
    
    def get_push_client(self, device_info: dict):
        """根据设备信息获取推送客户端"""
        platform = device_info.get('platform', '').lower()
        brand = device_info.get('brand', '').lower()
        
        # 安卓设备根据品牌选择推送服务
        if platform == 'android':
            if 'huawei' in brand:
                return self.push_clients['huawei']
            elif 'xiaomi' in brand or 'redmi' in brand:
                return self.push_clients['xiaomi']
            elif 'oppo' in brand:
                return self.push_clients['oppo']
            elif 'vivo' in brand:
                return self.push_clients['vivo']
            else:
                return self.push_clients['android_fcm']
        
        # iOS设备
        elif platform == 'ios':
            return self.push_clients['ios']
        
        # 默认使用FCM
        else:
            return self.push_clients['android_fcm']
    
    async def schedule_daily_weather_push(self):
        """安排每日天气推送"""
        # 获取所有关注了监狱天气的用户
        users = await self.get_weather_subscribed_users()
        
        for user in users:
            # 获取用户关注的监狱
            prisons = await self.get_user_subscribed_prisons(user['id'])
            
            for prison in prisons:
                # 获取监狱天气
                weather_service = PrisonWeatherService()
                prison_weather = await weather_service.get_prison_weather(prison['id'])
                
                # 检查是否需要推送(根据天气变化或特殊天气)
                should_push = self.should_send_push(user, prison, prison_weather)
                
                if should_push:
                    # 发送推送
                    await self.send_weather_care_push(user['id'], prison_weather)
                    
                    # 避免短时间内发送过多推送
                    await asyncio.sleep(0.1)
    
    def should_send_push(self, user: dict, prison: dict, weather: dict) -> bool:
        """判断是否需要发送推送"""
        # 检查用户推送设置
        if not user.get('weather_push_enabled', True):
            return False
        
        # 检查推送频率限制
        last_push = self.get_last_push_time(user['id'], prison['id'])
        if last_push and time.time() - last_push < 3600:  # 1小时内不重复推送
            return False
        
        # 检查天气变化是否需要推送
        weather_changes = self.check_weather_changes(prison['id'], weather)
        if weather_changes['significant_change']:
            return True
        
        # 特殊天气推送
        current_weather = weather['weather']['current']
        if self.is_special_weather(current_weather):
            return True
        
        # 定时推送(如每日早上8点)
        if self.is_scheduled_push_time():
            return True
        
        return False
    
    def is_special_weather(self, weather: dict) -> bool:
        """判断是否为特殊天气"""
        temp = weather.get('temperature', 20)
        description = weather.get('description', '').lower()
        
        # 极端温度
        if temp < -5 or temp > 35:
            return True
        
        # 恶劣天气
        severe_keywords = ['暴雨', '大雪', '冰雹', '台风', '沙尘', '雾霾']
        for keyword in severe_keywords:
            if keyword in description:
                return True
        
        return False

六、数据监控与分析

6.1 天气服务监控

复制代码
# 天气服务监控器
class WeatherServiceMonitor:
    """天气服务质量监控"""
    
    def __init__(self):
        self.metrics = {
            'api_calls': Counter(),
            'cache_hits': Counter(),
            'response_times': [],
            'error_rates': [],
            'user_satisfaction': []
        }
        
        self.alert_rules = self.load_alert_rules()
    
    async def monitor_weather_service(self):
        """监控天气服务运行状态"""
        while True:
            try:
                # 1. 收集服务指标
                metrics = await self.collect_metrics()
                
                # 2. 分析服务质量
                quality_score = self.analyze_service_quality(metrics)
                
                # 3. 检查告警条件
                alerts = self.check_alerts(metrics, quality_score)
                
                # 4. 发送告警通知
                if alerts:
                    await self.send_alerts(alerts)
                
                # 5. 生成监控报告
                report = self.generate_monitor_report(metrics, quality_score)
                self.save_report(report)
                
                # 6. 等待下一次监控
                await asyncio.sleep(60)  # 每分钟监控一次
                
            except Exception as e:
                logger.error(f"监控任务异常: {str(e)}")
                await asyncio.sleep(30)
    
    async def collect_metrics(self) -> dict:
        """收集服务指标"""
        metrics = {
            'timestamp': datetime.now().isoformat(),
            'api': {
                'total_calls': self.metrics['api_calls'].value(),
                'success_rate': self.calculate_success_rate(),
                'avg_response_time': self.calculate_avg_response_time(),
                'error_codes': self.get_error_codes()
            },
            'cache': {
                'hit_rate': self.calculate_cache_hit_rate(),
                'miss_rate': self.calculate_cache_miss_rate(),
                'avg_cache_time': self.calculate_avg_cache_time()
            },
            'user': {
                'active_users': await self.get_active_users_count(),
                'satisfaction_score': self.calculate_satisfaction_score(),
                'feature_usage': await self.get_feature_usage()
            },
            'performance': {
                'memory_usage': psutil.virtual_memory().percent,
                'cpu_usage': psutil.cpu_percent(),
                'disk_usage': psutil.disk_usage('/').percent
            }
        }
        
        return metrics
    
    def analyze_service_quality(self, metrics: dict) -> float:
        """分析服务质量"""
        quality_factors = {
            'api_success_rate': {
                'weight': 0.3,
                'value': metrics['api']['success_rate'],
                'threshold': 0.95  # 成功率需达到95%
            },
            'avg_response_time': {
                'weight': 0.25,
                'value': metrics['api']['avg_response_time'],
                'threshold': 2000  # 平均响应时间需小于2秒
            },
            'cache_hit_rate': {
                'weight': 0.2,
                'value': metrics['cache']['hit_rate'],
                'threshold': 0.7  # 缓存命中率需达到70%
            },
            'user_satisfaction': {
                'weight': 0.25,
                'value': metrics['user']['satisfaction_score'],
                'threshold': 4.0  # 用户满意度需达到4分(5分制)
            }
        }
        
        # 计算质量得分
        total_score = 0
        for factor_name, factor in quality_factors.items():
            factor_score = min(1.0, factor['value'] / factor['threshold'])
            total_score += factor_score * factor['weight']
        
        return total_score * 100  # 转换为百分比
    
    def check_alerts(self, metrics: dict, quality_score: float) -> list:
        """检查告警条件"""
        alerts = []
        
        # API成功率告警
        if metrics['api']['success_rate'] < 0.9:
            alerts.append({
                'level': 'critical',
                'type': 'api_success_rate',
                'message': f'API成功率低于90%: {metrics["api"]["success_rate"]:.2%}',
                'value': metrics['api']['success_rate']
            })
        
        # 响应时间告警
        if metrics['api']['avg_response_time'] > 5000:
            alerts.append({
                'level': 'warning',
                'type': 'response_time',
                'message': f'平均响应时间超过5秒: {metrics["api"]["avg_response_time"]}ms',
                'value': metrics['api']['avg_response_time']
            })
        
        # 服务质量告警
        if quality_score < 80:
            alerts.append({
                'level': 'warning',
                'type': 'service_quality',
                'message': f'服务质量评分低于80分: {quality_score:.1f}',
                'value': quality_score
            })
        
        # 系统资源告警
        if metrics['performance']['memory_usage'] > 90:
            alerts.append({
                'level': 'critical',
                'type': 'memory_usage',
                'message': f'内存使用率超过90%: {metrics["performance"]["memory_usage"]}%',
                'value': metrics['performance']['memory_usage']
            })
        
        return alerts

七、部署与维护

7.1 部署架构

复制代码
# docker-compose.yml 天气服务部署配置
version: '3.8'

services:
  # 天气API服务
  weather-api:
    image: weiaibang/weather-service:latest
    container_name: weather-api
    ports:
      - "8080:8080"
    environment:
      - REDIS_URL=redis://redis:6379
      - DATABASE_URL=postgresql://user:pass@postgres:5432/weather
      - AMAP_API_KEY=${AMAP_API_KEY}
      - WEATHER_API_KEY=${WEATHER_API_KEY}
    volumes:
      - ./logs:/app/logs
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 3
    networks:
      - weiaibang-network

  # Redis缓存
  redis:
    image: redis:7-alpine
    container_name: weather-redis
    ports:
      - "6379:6379"
    volumes:
      - redis-data:/data
    command: redis-server --appendonly yes
    networks:
      - weiaibang-network

  # PostgreSQL数据库
  postgres:
    image: postgres:15-alpine
    container_name: weather-db
    environment:
      - POSTGRES_USER=weather_user
      - POSTGRES_PASSWORD=${DB_PASSWORD}
      - POSTGRES_DB=weather_db
    volumes:
      - postgres-data:/var/lib/postgresql/data
    ports:
      - "5432:5432"
    networks:
      - weiaibang-network

  # 监控服务
  monitor:
    image: weiaibang/weather-monitor:latest
    container_name: weather-monitor
    environment:
      - WEATHER_API_URL=http://weather-api:8080
      - ALERT_WEBHOOK=${ALERT_WEBHOOK}
    volumes:
      - ./monitor-config:/app/config
    networks:
      - weiaibang-network

networks:
  weiaibang-network:
    driver: bridge

volumes:
  redis-data:
  postgres-data:

7.2 监控面板配置

复制代码
// Grafana监控面板配置
const weatherDashboard = {
  dashboard: {
    title: "微爱帮天气服务监控",
    panels: [
      {
        title: "API调用成功率",
        type: "stat",
        targets: [
          {
            expr: 'sum(rate(weather_api_calls_total{status="success"}[5m])) / sum(rate(weather_api_calls_total[5m]))',
            legendFormat: "成功率"
          }
        ],
        fieldConfig: {
          thresholds: {
            steps: [
              { value: 0, color: "red" },
              { value: 0.9, color: "yellow" },
              { value: 0.95, color: "green" }
            ]
          }
        }
      },
      {
        title: "平均响应时间",
        type: "timeseries",
        targets: [
          {
            expr: 'rate(weather_api_duration_seconds_sum[5m]) / rate(weather_api_duration_seconds_count[5m])',
            legendFormat: "响应时间"
          }
        ],
        fieldConfig: {
          unit: "s",
          decimals: 2
        }
      },
      {
        title: "缓存命中率",
        type: "stat",
        targets: [
          {
            expr: 'sum(rate(weather_cache_hits_total[5m])) / sum(rate(weather_cache_total[5m]))',
            legendFormat: "命中率"
          }
        ],
        fieldConfig: {
          unit: "percentunit",
          decimals: 2
        }
      },
      {
        title: "活跃用户数",
        type: "timeseries",
        targets: [
          {
            expr: 'sum(weather_active_users)',
            legendFormat: "活跃用户"
          }
        ]
      },
      {
        title: "用户满意度",
        type: "gauge",
        targets: [
          {
            expr: 'avg(weather_user_satisfaction)',
            legendFormat: "满意度"
          }
        ],
        fieldConfig: {
          min: 0,
          max: 5,
          thresholds: {
            steps: [
              { value: 0, color: "red" },
              { value: 3, color: "yellow" },
              { value: 4, color: "green" }
            ]
          }
        }
      }
    ]
  }
};

八、技术指标与效果

8.1 性能指标

复制代码
性能目标:
  响应时间:
    - 天气查询平均响应: < 500ms
    - 缓存命中响应: < 100ms
    - 推送发送延迟: < 1s
  
  可用性:
    - 服务可用性: > 99.9%
    - API成功率: > 99%
    - 数据新鲜度: < 10分钟
  
  扩展性:
    - 支持并发用户: > 10,000
    - 日请求处理: > 1,000,000
    - 数据存储: > 1TB

8.2 业务效果

复制代码
# 业务效果评估
class BusinessImpactAnalysis:
    """业务影响分析"""
    
    def analyze_weather_feature_impact(self):
        """分析天气功能对业务的影响"""
        impact_metrics = {
            'user_engagement': {
                'description': '用户参与度提升',
                'before': 65,  # 使用前
                'after': 82,   # 使用后
                'improvement': '+26%'
            },
            'retention_rate': {
                'description': '用户留存率提升',
                'before': 40,
                'after': 55,
                'improvement': '+37.5%'
            },
            'care_messages': {
                'description': '关怀消息发送量',
                'before': 1200,
                'after': 3200,
                'improvement': '+166%'
            },
            'user_satisfaction': {
                'description': '用户满意度评分',
                'before': 3.8,
                'after': 4.5,
                'improvement': '+18.4%'
            },
            'push_open_rate': {
                'description': '天气推送打开率',
                'rate': 42,  # 行业平均约25%
                'comparison': '高于行业平均水平68%'
            }
        }
        
        return impact_metrics

技术方案版本 : V2.0
核心功能 : 监狱天气关怀 + 智能推送 + 情感连接
技术栈 : Python + Vue.js + Redis + PostgreSQL + 高德地图API
团队 : 微爱帮技术关怀部
上线时间: 2025年12月

相关推荐
Knight_AL2 小时前
HTTP 状态码一览:理解 2xx、3xx、4xx 和 5xx 分类
网络·网络协议·http
2501_915921432 小时前
iPhone HTTPS 抓包在真机环境下面临的常见问题
android·ios·小程序·https·uni-app·iphone·webview
BBbila2 小时前
百度/微信小程序-跨端开发-兼容性避坑指南
微信小程序·小程序
草根站起来3 小时前
微信小程序request错误
微信小程序·小程序
刺客xs3 小时前
TCP服务器并发编程
服务器·网络协议·tcp/ip
毕设源码-邱学长3 小时前
【开题答辩全过程】以 公司打卡小程序为例,包含答辩的问题和答案
小程序
2501_915918413 小时前
iOS 图片资源保护方法,分析图片在二次打包和资源篡改中的实际风险
android·ios·小程序·https·uni-app·iphone·webview
汽车通信软件大头兵3 小时前
汽车MCU 信息安全--数字证书
服务器·https·ssl
@zulnger3 小时前
网络协议学习笔记_下
笔记·网络协议·学习