wlzboy
2025-12-04 af8cab142a6b15c06e131a8474574dd5b00df982
feat: 改造微信accesstoken存放在系统配置表中
4个文件已添加
9个文件已修改
524 ■■■■ 已修改文件
app/pagesTask/components/StaffSelector.vue 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/pagesTask/detail.vue 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/pagesTask/edit-emergency.vue 59 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskAttachmentController.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/wechat/WechatController.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/application.yml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/IWechatAccessTokenService.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/LegacyTransferSyncServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/PaymentSyncServiceImpl.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/WechatAccessTokenServiceImpl.java 123 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/WechatTaskNotifyServiceImpl.java 77 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/wechat_subscribe_message_config.sql 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/wechat_subscribe_message_config_README.md 167 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/pagesTask/components/StaffSelector.vue
@@ -5,7 +5,7 @@
      <view class="staff-list">
        <view class="staff-item" v-for="(staff, index) in selectedStaff" :key="staff.userId">
          <view class="staff-info">
            <text class="staff-name">{{ staff.nickName }}</text>
            <text class="staff-name">{{ getStaffDisplayName(staff) }}</text>
          </view>
          <uni-icons 
            v-if="canRemove(index)"
@@ -454,6 +454,21 @@
    emitChange() {
      this.$emit('input', this.selectedStaff)
      this.$emit('change', this.selectedStaff)
    },
    // 获取人员显示名称(优先显示姓名,如果姓名为空则显示手机号)
    getStaffDisplayName(staff) {
      if (!staff) {
        return '未知人员'
      }
      // 优先显示 nickName,如果为空则显示手机号,都为空则显示 userId
      if (staff.nickName && staff.nickName.trim()) {
        return staff.nickName
      }
      if (staff.phonenumber && staff.phonenumber.trim()) {
        return staff.phonenumber
      }
      return `用户${staff.userId || ''}`
    }
  }
}
app/pagesTask/detail.vue
@@ -804,6 +804,14 @@
      
      // 检查车辆状态并出发
      checkVehicleAndDepart() {
        // 检查出发时间是否为空或1900年(修复:防止无效时间)
        if (!this.taskDetail.plannedStartTime || this.taskDetail.plannedStartTime.startsWith('1900')) {
          this.$modal.confirm('任务的转运时间未设置或无效,需要先修改任务补充转运时间后才能出发。是否现在去修改?').then(() => {
            this.handleEdit()
          }).catch(() => {})
          return
        }
        // 获取任务车辆ID
        const vehicleId = this.getVehicleId();
        if (!vehicleId) {
app/pagesTask/edit-emergency.vue
@@ -19,7 +19,8 @@
      />
      
      <view class="form-item">
        <OrganizationSelector
        <OrganizationSelector
          ref="organizationSelector"
          v-model="selectedOrganizationId"
          :required="true"
          :auto-select-user-dept="false"
@@ -53,6 +54,7 @@
        :required="false"
        :auto-add-current-user="false"
        :current-user-removable="true"
        :branch-dept-ids="allOrganizationIds"
        @change="onStaffChange"
      />
      
@@ -224,6 +226,7 @@
      taskDetail: null,
      selectedVehicleId: null,
      selectedOrganizationId: null,
      allOrganizationIds: [], // 所有可选机构ID数组
      selectedRegion: '',
      mapSelectorType: '',
      // 扩展 addressCoordinates 支持多种键名
@@ -292,6 +295,11 @@
      }, 1500)
    }
  },
  mounted() {
    // 页面挂载后加载所有机构ID
    this.loadAllOrganizationIds()
  },
  methods: {
    // 加载任务详情
    loadTaskDetail() {
@@ -320,8 +328,9 @@
          const info = this.taskDetail.emergencyInfo
          console.log('转运任务信息:', info)
          
          // 转运时间
          this.taskForm.transferTime = this.taskDetail.plannedStartTime || ''
          // 转运时间(修复:1900年的日期显示为空)
          const transferTime = this.taskDetail.plannedStartTime || ''
          this.taskForm.transferTime = transferTime && transferTime.startsWith('1900') ? '' : transferTime
          
          // 患者信息
          this.taskForm.patient.contact = info.patientContact || ''
@@ -376,7 +385,8 @@
        } else {
          console.warn('任务详情中没有emergencyInfo字段,尝试从主对象获取数据')
          // 兼容处理:如果emergencyInfo不存在,尝试从主对象获取
          this.taskForm.transferTime = this.taskDetail.plannedStartTime || ''
          const transferTime = this.taskDetail.plannedStartTime || ''
          this.taskForm.transferTime = transferTime && transferTime.startsWith('1900') ? '' : transferTime
          this.taskForm.transferDistance = this.taskDetail.estimatedDistance ? String(this.taskDetail.estimatedDistance) : ''
        }
        
@@ -421,16 +431,24 @@
          console.log('设置目标地坐标:', this.taskDetail.destinationLongitude, this.taskDetail.destinationLatitude)
        }
        
        // 设置执行人员(修复:确保 assignees 不为 null)
        // 设置执行人员(修复:确保 assignees 不为 null,并正确映射字段)
        if (this.taskDetail.assignees && Array.isArray(this.taskDetail.assignees) && this.taskDetail.assignees.length > 0) {
          console.log('原始执行人员数据:', this.taskDetail.assignees)
          this.selectedStaff = this.taskDetail.assignees.map(assignee => ({
            userId: assignee.userId,
            nickName: assignee.userName,
            type: assignee.userType || 'driver',
            phonenumber: '',
            deptName: ''
          }))
          this.selectedStaff = this.taskDetail.assignees.map(assignee => {
            console.log('处理执行人员:', assignee)
            console.log('  - userName:', assignee.userName)
            console.log('  - nickName:', assignee.nickName)
            console.log('  - phonenumber:', assignee.phonenumber)
            console.log('  - phone:', assignee.phone)
            return {
              userId: assignee.userId,
              nickName: assignee.userName || assignee.nickName || '',
              type: assignee.userType || 'driver',
              phonenumber: assignee.phonenumber || assignee.phone || '',
              deptName: assignee.deptName || ''
            }
          })
          console.log('处理后的执行人员列表:', this.selectedStaff)
        } else {
          console.warn('任务没有分配执行人员或assignees为空')
@@ -455,6 +473,23 @@
      console.log('选中车辆:', vehicle)
    },
    
    // 加载所有机构ID
    loadAllOrganizationIds() {
      // 通过 OrganizationSelector 组件获取所有机构
      const orgSelector = this.$refs.organizationSelector
      if (orgSelector) {
        orgSelector.reload().then(organizations => {
          this.allOrganizationIds = organizations.map(org => org.deptId)
          console.log('所有机构ID:', this.allOrganizationIds)
        })
      } else {
        // 如果组件还未挂载,稍后重试
        setTimeout(() => {
          this.loadAllOrganizationIds()
        }, 100)
      }
    },
    // 归属机构选择变化
    onOrganizationChange(orgData) {
      // orgData 包含:deptId, deptName, serviceOrderClass, region, departureAddress, departureLongitude, departureLatitude
ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskAttachmentController.java
@@ -14,6 +14,7 @@
import com.ruoyi.system.service.ISysTaskEmergencyService;
import com.ruoyi.system.service.ISysUserService;
import com.ruoyi.system.service.ITaskAttachmentSyncService;
import com.ruoyi.system.service.IWechatAccessTokenService;
import com.ruoyi.system.task.ITaskAttachmentService;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
@@ -62,6 +63,9 @@
    @Autowired
    private ISysUserService userService;
    @Autowired
    private IWechatAccessTokenService wechatAccessTokenService;
    /**
     * 查询任务附件列表
     */
@@ -135,6 +139,7 @@
    
    /**
     * 从微信mediaId上传附件(微信小程序专用)
     * 使用应用级缓存的AccessToken
     */
    @Log(title = "任务附件", businessType = BusinessType.INSERT)
    @PostMapping("/uploadFromWechat/{taskId}")
@@ -142,11 +147,8 @@
                                       @RequestParam("mediaId") String mediaId,
                                       @RequestParam(value = "category", required = false) String category) {
        try {
            // 获取微信AccessToken
            String accessToken = WechatUtils.getAccessToken(
                wechatConfig.getAppId(),
                wechatConfig.getAppSecret()
            );
            // 获取微信AccessToken(使用应用级缓存)
            String accessToken = wechatAccessTokenService.getAppAccessToken();
            if (accessToken == null || accessToken.isEmpty()) {
                return error("获取微信AccessToken失败");
            }
ruoyi-admin/src/main/java/com/ruoyi/web/controller/wechat/WechatController.java
@@ -20,6 +20,7 @@
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.system.service.IWechatTaskNotifyService;
import com.ruoyi.system.service.IWechatAccessTokenService;
import java.util.ArrayList;
import java.util.HashMap;
@@ -49,17 +50,17 @@
    @Autowired
    private IWechatTaskNotifyService wechatTaskNotifyService;
    @Autowired
    private IWechatAccessTokenService wechatAccessTokenService;
    
    /**
     * 获取微信AccessToken
     * 获取微信AccessToken(使用应用级缓存)
     */
    @GetMapping("/accessToken")
    public AjaxResult getAccessToken() {
        try {
            String accessToken = WechatUtils.getAccessToken(
                wechatConfig.getAppId(),
                wechatConfig.getAppSecret()
            );
            String accessToken = wechatAccessTokenService.getAppAccessToken();
            if (accessToken == null || accessToken.isEmpty()) {
                return error("获取微信AccessToken失败");
            }
ruoyi-admin/src/main/resources/application.yml
@@ -58,7 +58,7 @@
    basename: i18n/messages
  profiles:
    # 环境 dev|test|prod
    active: dev
    active: prod
  # 文件上传
  servlet:
    multipart:
ruoyi-system/src/main/java/com/ruoyi/system/service/IWechatAccessTokenService.java
New file
@@ -0,0 +1,27 @@
package com.ruoyi.system.service;
/**
 * 微信AccessToken服务接口
 * 提供应用级AccessToken的统一管理,使用sys_config表缓存
 *
 * @author ruoyi
 * @date 2025-12-04
 */
public interface IWechatAccessTokenService {
    /**
     * 获取应用级微信AccessToken(带缓存)
     * 优先从sys_config读取并判断有效期;过期则重新获取并写回sys_config
     *
     * @return Access Token,失败返回null
     */
    String getAppAccessToken();
    /**
     * 强制刷新AccessToken
     * 忽略缓存,直接从微信获取新Token并更新缓存
     *
     * @return 新的Access Token,失败返回null
     */
    String refreshAppAccessToken();
}
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/LegacyTransferSyncServiceImpl.java
@@ -672,7 +672,7 @@
                            if (sysUser != null) {
                                TaskCreateVO.AssigneeInfo assigneeInfo = new TaskCreateVO.AssigneeInfo();
                                assigneeInfo.setUserId(sysUser.getUserId()); // 使用系统用户ID
                                assigneeInfo.setUserName(sysUser.getUserName());
                                assigneeInfo.setUserName(sysUser.getNickName());
                                // 根据EntourageState确定角色类型
                                // 1,2 司机,3,5 医生,4,6 护士
                                if ("1".equals(entourageState) || "2".equals(entourageState)) {
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/PaymentSyncServiceImpl.java
@@ -73,7 +73,6 @@
     * 将新系统支付记录同步到旧系统PaidMoney表
     */
    @Override
    @Transactional
    public boolean syncPaymentToLegacy(SysTaskPayment payment) {
        Long paymentId = payment.getId();
        try {
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/WechatAccessTokenServiceImpl.java
New file
@@ -0,0 +1,123 @@
package com.ruoyi.system.service.impl;
import com.ruoyi.common.config.WechatConfig;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.WechatUtils;
import com.ruoyi.system.domain.SysConfig;
import com.ruoyi.system.mapper.SysConfigMapper;
import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.system.service.IWechatAccessTokenService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
 * 微信AccessToken服务实现
 * 提供应用级AccessToken的统一管理,使用sys_config表缓存
 *
 * @author ruoyi
 * @date 2025-12-04
 */
@Service
public class WechatAccessTokenServiceImpl implements IWechatAccessTokenService {
    private static final Logger log = LoggerFactory.getLogger(WechatAccessTokenServiceImpl.class);
    @Autowired
    private WechatConfig wechatConfig;
    @Autowired
    private ISysConfigService configService;
    @Autowired
    private SysConfigMapper configMapper;
    /**
     * 获取应用级微信AccessToken(带缓存)
     * 优先从sys_config读取并判断有效期;过期则重新获取并写回sys_config
     */
    @Override
    public String getAppAccessToken() {
        try {
            String appId = wechatConfig.getAppId();
            String tokenKey = "weixin.access_token." + appId;
            String expireKey = "weixin.access_token_expires." + appId;
            String cachedToken = configService.selectConfigByKey(tokenKey);
            String cachedExpireStr = configService.selectConfigByKey(expireKey);
            long now = System.currentTimeMillis();
            long expireTs = 0L;
            if (StringUtils.isNotEmpty(cachedExpireStr)) {
                try {
                    expireTs = Long.parseLong(cachedExpireStr);
                } catch (NumberFormatException e) {
                    expireTs = 0L;
                }
            }
            // 缓存有效且未过期(预留60秒安全边界)
            if (StringUtils.isNotEmpty(cachedToken) && expireTs > now + 60000L) {
                log.debug("使用缓存的AccessToken,剩余有效期:{}秒", (expireTs - now) / 1000);
                return cachedToken;
            }
            // 重新获取,并写入sys_config
            log.info("AccessToken已过期或不存在,重新获取");
            return refreshAppAccessToken();
        } catch (Exception e) {
            log.error("获取应用级微信AccessToken失败", e);
            return null;
        }
    }
    /**
     * 强制刷新AccessToken
     * 忽略缓存,直接从微信获取新Token并更新缓存
     */
    @Override
    public String refreshAppAccessToken() {
        try {
            String appId = wechatConfig.getAppId();
            String appSecret = wechatConfig.getAppSecret();
            String tokenKey = "weixin.access_token." + appId;
            String expireKey = "weixin.access_token_expires." + appId;
            String newToken = WechatUtils.getAccessToken(appId, appSecret);
            if (StringUtils.isEmpty(newToken)) {
                log.error("从微信获取AccessToken失败");
                return null;
            }
            long now = System.currentTimeMillis();
            long newExpireTs = now + 7200L * 1000L; // 7200秒
            upsertConfig(tokenKey, newToken);
            upsertConfig(expireKey, String.valueOf(newExpireTs));
            log.info("AccessToken刷新成功,有效期:7200秒");
            return newToken;
        } catch (Exception e) {
            log.error("刷新应用级微信AccessToken失败", e);
            return null;
        }
    }
    /**
     * 根据configKey写入或更新sys_config
     */
    private void upsertConfig(String key, String value) {
        SysConfig exist = configMapper.checkConfigKeyUnique(key);
        if (exist != null && exist.getConfigId() != null) {
            exist.setConfigValue(value);
            configMapper.updateConfig(exist);
        } else {
            SysConfig cfg = new SysConfig();
            cfg.setConfigKey(key);
            cfg.setConfigName(key);
            cfg.setConfigValue(value);
            cfg.setConfigType("Y"); // 内置参数
            configMapper.insertConfig(cfg);
        }
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/WechatTaskNotifyServiceImpl.java
@@ -13,9 +13,8 @@
import com.ruoyi.system.domain.SysTaskEmergency;
import com.ruoyi.system.mapper.SysUserMapper;
import com.ruoyi.system.service.IWechatTaskNotifyService;
import com.ruoyi.system.service.IWechatAccessTokenService;
import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.system.mapper.SysConfigMapper;
import com.ruoyi.system.domain.SysConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -50,69 +49,26 @@
    private WechatConfig wechatConfig;
    
    @Autowired
    private ISysConfigService configService;
    private IWechatAccessTokenService wechatAccessTokenService;
    
    @Autowired
    private SysConfigMapper configMapper;
    private ISysConfigService configService;
    
    
    /**
     * 获取应用级微信AccessToken(带缓存)
     * 优先从sys_config读取并判断有效期;过期则重新获取并写回sys_config
     * 检查是否启用订阅消息发送
     *
     * @return true=启用,false=禁用
     */
    private String getAppAccessToken() {
    private boolean isSubscribeMessageEnabled() {
        try {
            String appId = wechatConfig.getAppId();
            String tokenKey = "weixin.access_token." + appId;
            String expireKey = "weixin.access_token_expires." + appId;
            String cachedToken = configService.selectConfigByKey(tokenKey);
            String cachedExpireStr = configService.selectConfigByKey(expireKey);
            long now = System.currentTimeMillis();
            long expireTs = 0L;
            if (StringUtils.isNotEmpty(cachedExpireStr)) {
                try {
                    expireTs = Long.parseLong(cachedExpireStr);
                } catch (NumberFormatException e) {
                    expireTs = 0L;
                }
            }
            // 缓存有效且未过期(预留60秒安全边界)
            if (StringUtils.isNotEmpty(cachedToken) && expireTs > now + 60000L) {
                return cachedToken;
            }
            // 重新获取,并写入sys_config
            String newToken = WechatUtils.getAccessToken(wechatConfig.getAppId(), wechatConfig.getAppSecret());
            if (StringUtils.isEmpty(newToken)) {
                return null;
            }
            long newExpireTs = now + 7200L * 1000L; // 7200秒
            upsertConfig(tokenKey, newToken);
            upsertConfig(expireKey, String.valueOf(newExpireTs));
            return newToken;
            String enabled = configService.selectConfigByKey("wechat.subscribe.message.enabled");
            return "true".equalsIgnoreCase(enabled);
        } catch (Exception e) {
            log.error("获取应用级微信AccessToken失败", e);
            return null;
        }
    }
    /**
     * 根据configKey写入或更新sys_config
     */
    private void upsertConfig(String key, String value) {
        SysConfig exist = configMapper.checkConfigKeyUnique(key);
        if (exist != null && exist.getConfigId() != null) {
            exist.setConfigValue(value);
            configMapper.updateConfig(exist);
        } else {
            SysConfig cfg = new SysConfig();
            cfg.setConfigKey(key);
            cfg.setConfigName(key);
            cfg.setConfigValue(value);
            cfg.setConfigType("Y"); // 内置参数
            configMapper.insertConfig(cfg);
            log.warn("获取订阅消息开关配置失败,默认启用", e);
            return true; // 默认启用
        }
    }
    
@@ -126,6 +82,11 @@
     */
    @Override
    public int sendTaskNotifyMessage(Long taskId, List<Long> userIds, Long excludeUserId) {
        // 检查订阅消息开关
        if (!isSubscribeMessageEnabled()) {
            log.info("订阅消息发送已关闭,跳过发送,taskId={}", taskId);
            return 0;
        }
        if (taskId == null || userIds == null || userIds.isEmpty()) {
            log.warn("发送微信任务通知参数不完整,taskId={}, userIds={}", taskId, userIds);
            return 0;
@@ -142,7 +103,7 @@
        SysTaskEmergency emergency = sysTaskEmergencyMapper.selectSysTaskEmergencyByTaskId(taskId);
        
        // 获取微信AccessToken(走应用级缓存)
        String accessToken = getAppAccessToken();
        String accessToken = wechatAccessTokenService.getAppAccessToken();
        if (StringUtils.isEmpty(accessToken)) {
            log.error("获取微信AccessToken失败,无法发送任务通知");
            return 0;
sql/wechat_subscribe_message_config.sql
New file
@@ -0,0 +1,18 @@
-- 微信订阅消息发送开关配置
-- 用于控制系统是否启用微信订阅消息推送功能
-- 插入订阅消息开关配置(默认开启)
INSERT INTO sys_config (config_name, config_key, config_value, config_type, create_by, create_time, update_by, update_time, remark)
VALUES
('微信订阅消息开关', 'wechat.subscribe.message.enabled', 'true', 'N', 'admin', NOW(), 'admin', NOW(),
 '控制是否启用微信订阅消息推送功能。true=启用,false=禁用。关闭后系统将不再发送任何订阅消息。');
-- 说明:
-- 1. wechat.subscribe.message.enabled 控制全局订阅消息发送开关
-- 2. 设置为 true 时,系统正常发送订阅消息
-- 3. 设置为 false 时,所有订阅消息发送将被跳过
-- 4. 适用场景:
--    - 生产环境临时关闭消息推送
--    - 测试环境避免误发消息
--    - 系统维护期间暂停通知
-- 5. 可在系统管理->参数设置中动态修改,无需重启服务
sql/wechat_subscribe_message_config_README.md
New file
@@ -0,0 +1,167 @@
# 微信订阅消息开关使用说明
## 一、配置项说明
### 配置键:`wechat.subscribe.message.enabled`
| 属性 | 值 |
|------|-----|
| 配置名称 | 微信订阅消息开关 |
| 配置键 | wechat.subscribe.message.enabled |
| 配置类型 | N (普通参数) |
| 默认值 | true |
| 可选值 | true / false |
### 配置说明
- **true**: 启用微信订阅消息推送(默认)
- **false**: 禁用微信订阅消息推送
## 二、SQL脚本
执行以下SQL脚本添加配置:
```sql
INSERT INTO sys_config (config_name, config_key, config_value, config_type, create_by, create_time, update_by, update_time, remark)
VALUES
('微信订阅消息开关', 'wechat.subscribe.message.enabled', 'true', 'N', 'admin', NOW(), 'admin', NOW(),
 '控制是否启用微信订阅消息推送功能。true=启用,false=禁用。关闭后系统将不再发送任何订阅消息。');
```
## 三、功能说明
### 1. 开关控制范围
该开关控制以下场景的订阅消息发送:
- ✅ 新任务创建时通知执行人
- ✅ 任务更新时通知相关人员
- ✅ 旧系统同步任务时的通知
- ✅ 手动触发的任务通知
- ✅ 所有通过 `IWechatTaskNotifyService` 发送的消息
### 2. 开关关闭后的表现
当开关设置为 `false` 时:
- 系统将跳过所有订阅消息发送
- 日志记录:`订阅消息发送已关闭,跳过发送,taskId=xxx`
- 不影响其他业务逻辑
- 返回值为 0(发送成功数量为0)
### 3. 异常处理
- 如果配置读取失败,默认为 **启用** 状态
- 确保系统正常运行,避免因配置异常导致功能中断
## 四、使用场景
### 1. 测试环境
在测试环境中关闭消息推送,避免测试数据触发真实消息:
```sql
UPDATE sys_config
SET config_value = 'false'
WHERE config_key = 'wechat.subscribe.message.enabled';
```
### 2. 生产维护
在系统维护期间临时关闭消息推送:
```sql
-- 关闭消息推送
UPDATE sys_config
SET config_value = 'false'
WHERE config_key = 'wechat.subscribe.message.enabled';
-- 维护完成后恢复
UPDATE sys_config
SET config_value = 'true'
WHERE config_key = 'wechat.subscribe.message.enabled';
```
### 3. 故障排查
当微信接口异常时,临时关闭消息推送,避免大量错误日志:
```sql
UPDATE sys_config
SET config_value = 'false'
WHERE config_key = 'wechat.subscribe.message.enabled';
```
## 五、管理界面操作
### 通过后台管理界面修改
1. 登录系统管理后台
2. 进入 **系统管理 > 参数设置**
3. 搜索 **微信订阅消息开关**
4. 修改参数值为 `true` 或 `false`
5. 保存后 **立即生效**,无需重启服务
## 六、技术实现
### 代码位置
- **配置SQL**: `/sql/wechat_subscribe_message_config.sql`
- **服务实现**: `/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/WechatTaskNotifyServiceImpl.java`
### 核心代码
```java
/**
 * 检查是否启用订阅消息发送
 */
private boolean isSubscribeMessageEnabled() {
    try {
        String enabled = configService.selectConfigByKey("wechat.subscribe.message.enabled");
        return "true".equalsIgnoreCase(enabled);
    } catch (Exception e) {
        log.warn("获取订阅消息开关配置失败,默认启用", e);
        return true; // 默认启用
    }
}
```
## 七、注意事项
1. ⚠️ 修改配置后立即生效,无需重启服务
2. ⚠️ 关闭开关会影响所有订阅消息发送
3. ⚠️ 默认值为 `true`(启用),确保正常业务不受影响
4. ⚠️ 建议在生产环境保持开启状态
5. ⚠️ 测试环境建议关闭,避免误发消息给真实用户
## 八、验证方法
### 1. 验证配置已添加
```sql
SELECT * FROM sys_config WHERE config_key = 'wechat.subscribe.message.enabled';
```
### 2. 验证开关生效
**关闭开关:**
```sql
UPDATE sys_config SET config_value = 'false' WHERE config_key = 'wechat.subscribe.message.enabled';
```
**创建测试任务:**
- 观察日志是否输出:`订阅消息发送已关闭,跳过发送`
- 确认没有实际发送微信消息
**恢复开关:**
```sql
UPDATE sys_config SET config_value = 'true' WHERE config_key = 'wechat.subscribe.message.enabled';
```
## 九、相关配置
该功能配合以下配置使用:
- `weixin.access_token.{appId}` - 微信AccessToken缓存
- `weixin.access_token_expires.{appId}` - AccessToken过期时间
- `wechat.task.notify.template.id` - 任务通知模板ID (application.yml)
- `wechat.task.detail.page` - 任务详情页路径 (application.yml)