feat: 改造微信accesstoken存放在系统配置表中
| | |
| | | <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)" |
| | |
| | | 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 || ''}` |
| | | } |
| | | } |
| | | } |
| | |
| | | |
| | | // 检查车辆状态并出发 |
| | | 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) { |
| | |
| | | /> |
| | | |
| | | <view class="form-item"> |
| | | <OrganizationSelector |
| | | <OrganizationSelector |
| | | ref="organizationSelector" |
| | | v-model="selectedOrganizationId" |
| | | :required="true" |
| | | :auto-select-user-dept="false" |
| | |
| | | :required="false" |
| | | :auto-add-current-user="false" |
| | | :current-user-removable="true" |
| | | :branch-dept-ids="allOrganizationIds" |
| | | @change="onStaffChange" |
| | | /> |
| | | |
| | |
| | | taskDetail: null, |
| | | selectedVehicleId: null, |
| | | selectedOrganizationId: null, |
| | | allOrganizationIds: [], // 所有可选机构ID数组 |
| | | selectedRegion: '', |
| | | mapSelectorType: '', |
| | | // 扩展 addressCoordinates 支持多种键名 |
| | |
| | | }, 1500) |
| | | } |
| | | }, |
| | | |
| | | mounted() { |
| | | // 页面挂载后加载所有机构ID |
| | | this.loadAllOrganizationIds() |
| | | }, |
| | | methods: { |
| | | // 加载任务详情 |
| | | loadTaskDetail() { |
| | |
| | | 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 || '' |
| | |
| | | } 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) : '' |
| | | } |
| | | |
| | |
| | | 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为空') |
| | |
| | | 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 |
| | |
| | | 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; |
| | |
| | | |
| | | @Autowired |
| | | private ISysUserService userService; |
| | | |
| | | @Autowired |
| | | private IWechatAccessTokenService wechatAccessTokenService; |
| | | /** |
| | | * 查询任务附件列表 |
| | | */ |
| | |
| | | |
| | | /** |
| | | * 从微信mediaId上传附件(微信小程序专用) |
| | | * 使用应用级缓存的AccessToken |
| | | */ |
| | | @Log(title = "任务附件", businessType = BusinessType.INSERT) |
| | | @PostMapping("/uploadFromWechat/{taskId}") |
| | |
| | | @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失败"); |
| | | } |
| | |
| | | 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; |
| | |
| | | |
| | | @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失败"); |
| | | } |
| | |
| | | basename: i18n/messages |
| | | profiles: |
| | | # 环境 dev|test|prod |
| | | active: dev |
| | | active: prod |
| | | # 文件上传 |
| | | servlet: |
| | | multipart: |
| New file |
| | |
| | | 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(); |
| | | } |
| | |
| | | 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)) { |
| | |
| | | * 将新系统支付记录同步到旧系统PaidMoney表 |
| | | */ |
| | | @Override |
| | | @Transactional |
| | | public boolean syncPaymentToLegacy(SysTaskPayment payment) { |
| | | Long paymentId = payment.getId(); |
| | | try { |
| New file |
| | |
| | | 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); |
| | | } |
| | | } |
| | | } |
| | |
| | | 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; |
| | |
| | | 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; // 默认启用 |
| | | } |
| | | } |
| | | |
| | |
| | | */ |
| | | @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; |
| | |
| | | SysTaskEmergency emergency = sysTaskEmergencyMapper.selectSysTaskEmergencyByTaskId(taskId); |
| | | |
| | | // 获取微信AccessToken(走应用级缓存) |
| | | String accessToken = getAppAccessToken(); |
| | | String accessToken = wechatAccessTokenService.getAppAccessToken(); |
| | | if (StringUtils.isEmpty(accessToken)) { |
| | | log.error("获取微信AccessToken失败,无法发送任务通知"); |
| | | return 0; |
| New file |
| | |
| | | -- 微信订阅消息发送开关配置 |
| | | -- 用于控制系统是否启用微信订阅消息推送功能 |
| | | |
| | | -- 插入订阅消息开关配置(默认开启) |
| | | 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. 可在系统管理->参数设置中动态修改,无需重启服务 |
| New file |
| | |
| | | # 微信订阅消息开关使用说明 |
| | | |
| | | ## 一、配置项说明 |
| | | |
| | | ### 配置键:`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) |