wlzboy
6 天以前 09e6dc3fb7266620fafb5e341808a8eb36e080a1
feat:增加企业微信消息提醒
19个文件已添加
44个文件已修改
3268 ■■■■■ 已修改文件
app/pages/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/pages/task/index.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/pagesTask/detail.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/sms/NotifyController.java 34 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/QyWechatTestController.java 141 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskController.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/application-dev.yml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/utils/MapValueUtils.java 142 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/QyWechatUserSyncTask.java 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/domain/NotifyChannelConfig.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/domain/NotifySendLog.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/domain/QyWechatArticle.java 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTask.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTaskEmergency.java 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/domain/UserSyncDTO.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/TaskCreateVO.java 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/event/TaskDispatchSyncEvent.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/event/TaskServiceOrderSyncEvent.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/listener/TaskMessageListener.java 155 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/mapper/LegacyTransferSyncMapper.java 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/mapper/NotifySendLogMapper.java 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/INotifyDispatchService.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/INotifySendLogService.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/IQyWechatAccessTokenService.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/IQyWechatService.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/ISysConfigService.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/ISysEmergencyTaskService.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/ISysTaskService.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/LegacySystemSyncServiceImpl.java 188 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/LegacyTransferSyncServiceImpl.java 291 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/NotifyDispatchServiceImpl.java 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/NotifySendLogServiceImpl.java 44 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/QyWechatAccessTokenServiceImpl.java 285 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/QyWechatServiceImpl.java 431 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysConfigServiceImpl.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysEmergencyTaskServiceImpl.java 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTaskServiceImpl.java 114 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/UserSyncServiceImpl.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/LegacyTransferSyncMapper.xml 117 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/NotifySendLogMapper.xml 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/SysTaskEmergencyMapper.xml 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/UserSyncMapper.xml 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/api/system/qywechat/index.js 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/api/system/qywechat/test.js 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/views/system/notify/channelConfig.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/views/system/notify/log/index.vue 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/views/system/qywechat/index.vue 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/views/system/qywechat/test.vue 226 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/views/task/general/detail.vue 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/views/task/general/index.vue 26 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/add_qy_wechat_user_id_to_sys_user.sql 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/notify_dict.sql 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/notify_menu.sql 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/qy_wechat_config.sql 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/qy_wechat_sync_job.sql 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/ry_20250417.sql 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/sys_notify_send_log.sql 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/sys_notify_task.sql 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/sys_task_emergency.sql 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/update_sys_task_emergency.sql 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/update_sys_task_emergency_add_service_ord_class.sql 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/pages/index.vue
@@ -124,7 +124,7 @@
            <!-- 任务编号单独一行 -->
            <view class="task-code-row">
              <text class="task-code">{{ task.taskNo }}</text>
              <text class="task-code">{{ task.showTaskCode }}</text>
            </view>
            <!-- 任务详细信息 -->
app/pages/task/index.vue
@@ -120,7 +120,7 @@
              
              <!-- 任务编号单独一行 -->
              <view class="task-code-row">
                <text class="task-code">{{ task.taskCode }}</text>
                <text class="task-code">{{ task.showTaskCode }}</text>
              </view>
              
              <!-- 任务详细信息 -->
@@ -297,7 +297,7 @@
        // 应用任务编号筛选 - 使用taskCode而不是taskNo
        if (this.searchForm.taskNo) {
          filtered = filtered.filter(task => 
            task.taskCode && task.taskCode.includes(this.searchForm.taskNo)
            task.showTaskCode && task.showTaskCode.includes(this.searchForm.taskNo)
          );
        }
        
app/pagesTask/detail.vue
@@ -12,7 +12,7 @@
        <view class="section-title">基本信息</view>
        <view class="info-item">
          <view class="label">任务编号</view>
          <view class="value">{{ taskDetail.taskCode }}</view>
          <view class="value">{{ taskDetail.showTaskCode }}</view>
        </view>
        <view class="info-item">
          <view class="label">任务类型</view>
ruoyi-admin/src/main/java/com/ruoyi/web/controller/sms/NotifyController.java
@@ -1,14 +1,14 @@
package com.ruoyi.web.controller.sms;
import com.ruoyi.common.annotation.Anonymous;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.system.service.IQyWechatService;
import com.ruoyi.system.service.ISysTaskService;
import com.ruoyi.system.service.IWechatTaskNotifyService;
import org.apache.ibatis.annotations.Param;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@@ -19,6 +19,12 @@
    @Autowired
    private IWechatTaskNotifyService wechatTaskNotifyService;
    @Autowired
    private IQyWechatService qyWechatService;
    @Autowired
    private ISysTaskService taskService;
    @Anonymous
    @PostMapping("/sendWeiXin")
    public String notify(@RequestBody @Validated SendTaskReq req) {
@@ -28,4 +34,24 @@
        System.out.println(result);
        return "success";
    }
    @Anonymous()
    @GetMapping("/dispatchSyncEvent")
    public Boolean dispatchSyncEvent(@RequestParam Long taskId) {
        return taskService.dispatchSyncEvent(taskId);
    }
    @Anonymous()
    @PostMapping("sendQyWeiXin")
    public String sendQyWeiXin(@RequestBody @Validated SendTaskReq req) {
        String title="医疗运转单派送";
        String content="您有新的医疗运转单派送,请及时处理";
        String notifyUrl="https://sys.966120.com.cn/m_DispatchOrder.gds?dispatchId=102311";
        java.lang.Boolean result =qyWechatService.sendNotifyMessage(req.getUserId(), title,content,notifyUrl);
        System.out.println(result);
        return "success";
    }
}
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/QyWechatTestController.java
New file
@@ -0,0 +1,141 @@
package com.ruoyi.web.controller.system;
import com.ruoyi.common.annotation.Anonymous;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.system.service.IQyWechatAccessTokenService;
import com.ruoyi.system.service.IQyWechatService;
import com.ruoyi.system.service.ISysConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
 * 企业微信测试控制器
 * 用于测试企业微信AccessToken获取和消息发送功能
 *
 * @author ruoyi
 * @date 2025-12-11
 */
@Anonymous()
@RestController
@RequestMapping("/system/qywechat/test")
public class QyWechatTestController extends BaseController {
    @Autowired
    private IQyWechatAccessTokenService qyWechatAccessTokenService;
    @Autowired
    private IQyWechatService qyWechatService;
    @Autowired
    private ISysConfigService configService;
    /**
     * 测试获取企业微信AccessToken
     */
    @Anonymous()
    @GetMapping("/token")
    public AjaxResult testGetToken() {
        try {
            String corpId = configService.selectConfigByKey("qy_wechat.corp_id");
            String corpSecret = configService.selectConfigByKey("qy_wechat.corp_secret");
            if (corpId == null || corpSecret == null) {
                return AjaxResult.error("企业微信配置不完整,请检查corp_id和corp_secret配置");
            }
            String accessToken = qyWechatAccessTokenService.getAppAccessToken(corpId, corpSecret);
            if (accessToken != null) {
                return AjaxResult.success("获取AccessToken成功", accessToken);
            } else {
                return AjaxResult.error("获取AccessToken失败");
            }
        } catch (Exception e) {
            return AjaxResult.error("获取AccessToken异常:" + e.getMessage());
        }
    }
    /**
     * 测试刷新企业微信AccessToken
     */
    @Anonymous()
    @PostMapping("/refreshToken")
    public AjaxResult testRefreshToken() {
        try {
            String corpId = configService.selectConfigByKey("qy_wechat.corp_id");
            String corpSecret = configService.selectConfigByKey("qy_wechat.corp_secret");
            if (corpId == null || corpSecret == null) {
                return AjaxResult.error("企业微信配置不完整,请检查corp_id和corp_secret配置");
            }
            String accessToken = qyWechatAccessTokenService.refreshAppAccessToken(corpId, corpSecret);
            if (accessToken != null) {
                return AjaxResult.success("刷新AccessToken成功", accessToken);
            } else {
                return AjaxResult.error("刷新AccessToken失败");
            }
        } catch (Exception e) {
            return AjaxResult.error("刷新AccessToken异常:" + e.getMessage());
        }
    }
    /**
     * 测试发送企业微信消息
     */
    @Anonymous()
    @PostMapping("/sendMessage")
    public AjaxResult testSendMessage(@RequestParam Long userId,
                                     @RequestParam String title,
                                     @RequestParam String content,@RequestParam String notifyUrl) {
        try {
            boolean result = qyWechatService.sendNotifyMessage(userId, title, content,notifyUrl);
            if (result) {
                return AjaxResult.success("发送企业微信消息成功");
            } else {
                return AjaxResult.error("发送企业微信消息失败");
            }
        } catch (Exception e) {
            return AjaxResult.error("发送企业微信消息异常:" + e.getMessage());
        }
    }
    /**
     * 测试发送企业微信文本消息
     */
    @Anonymous()
    @PostMapping("/sendTextMessage")
    public AjaxResult testSendTextMessage(@RequestParam String qyUserId,
                                         @RequestParam String title,
                                         @RequestParam String content,
                                         @RequestParam String notifyUrl) {
        try {
            boolean result = qyWechatService.sendTextMessage(qyUserId, title, content, notifyUrl);
            if (result) {
                return AjaxResult.success("发送企业微信文本消息成功");
            } else {
                return AjaxResult.error("发送企业微信文本消息失败");
            }
        } catch (Exception e) {
            return AjaxResult.error("发送企业微信文本消息异常:" + e.getMessage());
        }
    }
    /**
     * 检查企业微信服务是否启用
     */
    @Anonymous()
    @GetMapping("/enabled")
    public AjaxResult checkEnabled() {
        try {
            boolean enabled = qyWechatService.isEnabled();
            return AjaxResult.success("企业微信服务状态:" + (enabled ? "启用" : "禁用"), enabled);
        } catch (Exception e) {
            return AjaxResult.error("检查企业微信服务状态异常:" + e.getMessage());
        }
    }
}
ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskController.java
@@ -4,6 +4,8 @@
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import com.ruoyi.common.annotation.Anonymous;
import com.ruoyi.system.domain.SysTaskEmergency;
import com.ruoyi.system.service.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.access.prepost.PreAuthorize;
@@ -47,6 +49,9 @@
    
    @Autowired
    private ISysTaskService sysTaskService;
    @Autowired
    private ISysTaskEmergencyService sysTaskEmergencyService;
    
    @Autowired
    private IVehicleInfoService vehicleInfoService;
@@ -70,7 +75,18 @@
    @GetMapping("/admin/list")
    public TableDataInfo adminList(TaskQueryVO queryVO) {
        startPage();
        List<SysTask> list = sysTaskService.selectSysTaskList(queryVO);
        // Handle multi-field task code search
        String searchTaskCode = queryVO.getTaskCode();
        List<SysTask> list;
        if(searchTaskCode != null && !searchTaskCode.trim().isEmpty()){
            // Search across task_code, emergency_info.dispatch_code, and emergency_info.service_code
            list = sysTaskService.selectSysTaskListByMultiCode(queryVO, searchTaskCode);
        } else {
            queryVO.setTaskCode(null);
            list = sysTaskService.selectSysTaskList(queryVO);
        }
        return getDataTable(list);
    }
@@ -167,6 +183,8 @@
        return toAjax(sysTaskService.insertSysTask(createVO));
    }
    /**
     * 新增任务(APP端)
     */
ruoyi-admin/src/main/resources/application-dev.yml
@@ -98,6 +98,10 @@
  appId: wx70f6a7346ee842c0
  appSecret: 2d6c59de85e876b7eadebeba62e5417a
  redirectUri: http://yourdomain.com/evaluation
# 企业微信配置
qyWeixin:
  appId: wx248505bfbab6d0c1
  appSecret: 2MCilqWYC0FWjOQ894sbb-s7Lb5sVH4HHuJgOsd9l1k
# 调度用的weixin配置
transferConfigWeixin:
  appId: wx40692cc44953a8cb
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java
@@ -104,6 +104,12 @@
    /** 微信昵称 */
    private String wechatNickname;
    
    /** 企业微信用户ID */
    private String qyWechatUserId;
    /** 企业微信用户ID更新时间 */
    private Date qyWechatUpdateTime;
    /** 是否可查看所有咨询单(0否 1是) */
    @Excel(name = "可查看所有咨询单", readConverterExp = "0=否,1=是")
    private String canViewAllConsult;
@@ -368,6 +374,26 @@
        this.wechatNickname = wechatNickname;
    }
    
    public String getQyWechatUserId()
    {
        return qyWechatUserId;
    }
    public void setQyWechatUserId(String qyWechatUserId)
    {
        this.qyWechatUserId = qyWechatUserId;
    }
    public Date getQyWechatUpdateTime()
    {
        return qyWechatUpdateTime;
    }
    public void setQyWechatUpdateTime(Date qyWechatUpdateTime)
    {
        this.qyWechatUpdateTime = qyWechatUpdateTime;
    }
    public String getCanViewAllConsult()
    {
        return canViewAllConsult;
@@ -406,6 +432,8 @@
            .append("openId", getOpenId())
            .append("unionId", getUnionId())
            .append("wechatNickname", getWechatNickname())
            .append("qyWechatUserId", getQyWechatUserId())
            .append("qyWechatUpdateTime", getQyWechatUpdateTime())
            .append("canViewAllConsult", getCanViewAllConsult())
            .toString();
ruoyi-common/src/main/java/com/ruoyi/common/utils/MapValueUtils.java
New file
@@ -0,0 +1,142 @@
package com.ruoyi.common.utils;
import java.math.BigDecimal;
import java.util.Date;
import java.util.Map;
import com.ruoyi.common.utils.DateUtils;
/**
 * Map值转换工具类
 * 提供从Map中安全获取各种类型值的方法
 *
 * @author ruoyi
 */
public class MapValueUtils {
    /**
     * 从Map中获取字符串值
     *
     * @param map Map对象
     * @param key 键
     * @return 字符串值,如果键不存在或值为null则返回null
     */
    public static String getStringValue(Map<String, Object> map, String key) {
        Object value = map.get(key);
        return value != null ? value.toString() : null;
    }
    /**
     * 从Map中获取BigDecimal值
     *
     * @param map Map对象
     * @param key 键
     * @return BigDecimal值,如果键不存在或值为null则返回null,转换失败也返回null
     */
    public static BigDecimal getBigDecimalValue(Map<String, Object> map, String key) {
        Object value = map.get(key);
        if (value == null) {
            return null;
        }
        if (value instanceof BigDecimal) {
            return (BigDecimal) value;
        }
        try {
            return new BigDecimal(value.toString());
        } catch (NumberFormatException e) {
            return null;
        }
    }
    /**
     * 从Map中获取Long值
     *
     * @param map Map对象
     * @param key 键
     * @return Long值,如果键不存在或值为null则返回null,转换失败也返回null
     */
    public static Long getLongValue(Map<String, Object> map, String key) {
        Object value = map.get(key);
        if (value == null) {
            return null;
        }
        if (value instanceof Long) {
            return (Long) value;
        }
        try {
            return Long.valueOf(value.toString());
        } catch (NumberFormatException e) {
            return null;
        }
    }
    /**
     * 从Map中获取Integer值
     *
     * @param map Map对象
     * @param key 键
     * @return Integer值,如果键不存在或值为null则返回null,转换失败也返回null
     */
    public static Integer getIntegerValue(Map<String, Object> map, String key) {
        Object value = map.get(key);
        if (value == null) {
            return null;
        }
        if (value instanceof Integer) {
            return (Integer) value;
        }
        try {
            return Integer.valueOf(value.toString());
        } catch (NumberFormatException e) {
            return null;
        }
    }
    /**
     * 从Map中获取Date值
     *
     * @param map Map对象
     * @param key 键
     * @return Date值,如果键不存在或值为null则返回null,如果是字符串会尝试解析
     */
    public static Date getDateValue(Map<String, Object> map, String key) {
        Object value = map.get(key);
        if (value == null) {
            return null;
        }
        if (value instanceof Date) {
            return (Date) value;
        }
        // 如果是字符串,尝试解析
        if (value instanceof String) {
            try {
                return DateUtils.parseDate(value.toString());
            } catch (Exception e) {
                return null;
            }
        }
        return null;
    }
    /**
     * 验证日期字符串格式是否有效
     *
     * @param dateStr 日期字符串
     * @param format 日期格式
     * @return 是否有效
     */
    public static boolean isValidDateFormat(String dateStr, String format) {
        if (StringUtils.isEmpty(dateStr)) {
            return false;
        }
        try {
            java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat(format);
            sdf.setLenient(false);
            sdf.parse(dateStr);
            return true;
        } catch (Exception e) {
            return false;
        }
    }
}
ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/QyWechatUserSyncTask.java
New file
@@ -0,0 +1,68 @@
package com.ruoyi.quartz.task;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.system.domain.UserSyncDTO;
import com.ruoyi.system.service.IUserSyncDataService;
import com.ruoyi.system.service.IUserSyncService;
import java.util.List;
/**
 * 企业微信用户ID同步任务
 *
 * 定期同步OA系统中的企业微信用户ID到本地系统
 *
 * @author ruoyi
 * @date 2025-12-11
 */
@Component("qyWechatUserSyncTask")
public class QyWechatUserSyncTask
{
    private static final Logger log = LoggerFactory.getLogger(QyWechatUserSyncTask.class);
    @Autowired
    private IUserSyncDataService userSyncDataService;
    @Autowired
    private IUserSyncService userSyncService;
    /**
     * 同步企业微信用户ID
     *
     * 任务执行方法,由Quartz调度器调用
     *
     * @return 同步结果
     */
    public AjaxResult syncQyWechatUserIds()
    {
        try
        {
            log.info("开始同步企业微信用户ID...");
            // 1. 从SQL Server查询OA用户数据(包含企业微信用户ID)
            List<UserSyncDTO> oaUsers = userSyncDataService.getOaUsers();
            if (oaUsers == null || oaUsers.isEmpty())
            {
                log.warn("未查询到OA用户数据,跳过同步");
                return AjaxResult.warn("未查询到OA用户数据");
            }
            log.info("从OA系统查询到 {} 条用户数据", oaUsers.size());
            // 2. 同步到MySQL数据库
            AjaxResult result = userSyncService.syncOaUsers(oaUsers);
            log.info("企业微信用户ID同步完成: {}", result.get("msg"));
            return result;
        }
        catch (Exception e)
        {
            log.error("同步企业微信用户ID失败", e);
            return AjaxResult.error("同步失败: " + e.getMessage());
        }
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/domain/NotifyChannelConfig.java
@@ -22,6 +22,8 @@
    public static final String CHANNEL_SITE_MSG = "SITE_MSG";
    /** 渠道:APP推送 */
    public static final String CHANNEL_APP_PUSH = "APP_PUSH";
    /** 渠道:企业微信 */
    public static final String CHANNEL_QY_WECHAT = "QY_WECHAT";
    // ==================== 启用状态常量 ====================
    /** 启用 */
ruoyi-system/src/main/java/com/ruoyi/system/domain/NotifySendLog.java
@@ -49,6 +49,9 @@
    @Excel(name = "发送状态")
    private String sendStatus;
    /** 发送的内容 */
    private String sendContent;
    /** 发送时间 */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @Excel(name = "发送时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
@@ -171,6 +174,14 @@
        this.sendResult = sendResult;
    }
    public String getSendContent() {
        return sendContent;
    }
    public void setSendContent(String sendContent) {
        this.sendContent = sendContent;
    }
    public String getResponseMsg() {
        return responseMsg;
    }
@@ -199,6 +210,7 @@
                .append("sendStatus", getSendStatus())
                .append("sendTime", getSendTime())
                .append("sendResult", getSendResult())
                .append("sendContent", getSendContent())
                .append("retryCount", getRetryCount())
                .append("createTime", getCreateTime())
                .append("createBy", getCreateBy())
ruoyi-system/src/main/java/com/ruoyi/system/domain/QyWechatArticle.java
New file
@@ -0,0 +1,78 @@
package com.ruoyi.system.domain;
import java.io.Serializable;
/**
 * 企业微信文章实体类
 *
 * @author ruoyi
 * @date 2025-12-13
 */
public class QyWechatArticle implements Serializable {
    /** 文章标题 */
    private String title;
    /** 文章描述 */
    private String description;
    /** 点击后跳转的链接 */
    private String url;
    /** 图文消息的图片链接 */
    private String picurl;
    /** 小程序appid */
    private String appid;
    /** 小程序页面路径 */
    private String pagepath;
    // Getters and Setters
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }
    public String getUrl() {
        return url;
    }
    public void setUrl(String url) {
        this.url = url;
    }
    public String getPicurl() {
        return picurl;
    }
    public void setPicurl(String picurl) {
        this.picurl = picurl;
    }
    public String getAppid() {
        return appid;
    }
    public void setAppid(String appid) {
        this.appid = appid;
    }
    public String getPagepath() {
        return pagepath;
    }
    public void setPagepath(String pagepath) {
        this.pagepath = pagepath;
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTask.java
@@ -136,6 +136,28 @@
    /** 福祉车扩展信息 */
    private SysTaskWelfare welfareInfo;
    private String showTaskCode;
    public void setShowTaskCode(String showTaskCode) {
         showTaskCode=this.showTaskCode;
    }
    /**
     * 获取显示任务编号,优先返回急救转运的调度单编号,其次是服务单编号,最后是任务编号
     */
    public String getShowTaskCode(){
        if(this.emergencyInfo!=null){
            String dispatchOrdCode=this.emergencyInfo.getDispatchCode();
            if(dispatchOrdCode!=null){
                return dispatchOrdCode;
            }
            String serviceOrdCode=this.emergencyInfo.getServiceCode();
            if(serviceOrdCode!=null){
                return serviceOrdCode;
            }
        }
        return this.taskCode;
    }
    public void setTaskId(Long taskId) {
        this.taskId = taskId;
    }
ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTaskEmergency.java
@@ -2,6 +2,7 @@
import java.math.BigDecimal;
import com.ruoyi.common.core.domain.BaseEntity;
import com.ruoyi.common.utils.DateUtils;
/**
 * 急救转运任务扩展信息对象 sys_task_emergency
@@ -135,6 +136,40 @@
    /** 旧系统ServiceOrdNo(转运单编号) */
    private String legacyServiceOrdNo;
    /** 旧系统调度单编号 */
    private String legacyDispatchOrdNo;
    /** 旧系统服务通知时间 */
    private java.util.Date legacyServiceNsTime;
    /** 旧系统调度通知时间 */
    private java.util.Date legacyDispatchNsTime;
    /** 旧系统调度单分类 */
    private String legacyDispatchOrdClass;
    /** 旧系统服务单分类 */
    private String legacyServiceOrdClass;
    public String getServiceCode(){
        if(this.legacyServiceOrdClass!=null && this.legacyServiceNsTime!=null && this.legacyServiceOrdNo!=null) {
            String nstime = DateUtils.parseDateToStr(DateUtils.YYYYMMDD, this.legacyServiceNsTime);
            return this.legacyServiceOrdClass + nstime +'-'+ this.legacyServiceOrdNo;
        }
        return null;
    }
    public String getDispatchCode(){
        if(this.legacyDispatchOrdClass!=null && this.legacyDispatchNsTime!=null && this.legacyDispatchOrdNo!=null) {
            String nstime = DateUtils.parseDateToStr(DateUtils.YYYYMMDD, this.legacyDispatchNsTime);
            //serviceOrdNo 这个是数字,固定3位数 ,将32,转成032;将1转成001
            Integer intServiceNo = Integer.valueOf(this.legacyDispatchOrdNo);
            String ordNoStr = String.format("%03d", intServiceNo);
            return this.legacyDispatchOrdClass + nstime + "-" + ordNoStr;
        }else{
            return null;
        }
    }
    public Long getId() {
@@ -465,6 +500,46 @@
        this.legacyServiceOrdNo = legacyServiceOrdNo;
    }
    public String getLegacyDispatchOrdNo() {
        return legacyDispatchOrdNo;
    }
    public void setLegacyDispatchOrdNo(String legacyDispatchOrdNo) {
        this.legacyDispatchOrdNo = legacyDispatchOrdNo;
    }
    public java.util.Date getLegacyServiceNsTime() {
        return legacyServiceNsTime;
    }
    public void setLegacyServiceNsTime(java.util.Date legacyServiceNsTime) {
        this.legacyServiceNsTime = legacyServiceNsTime;
    }
    public java.util.Date getLegacyDispatchNsTime() {
        return legacyDispatchNsTime;
    }
    public void setLegacyDispatchNsTime(java.util.Date legacyDispatchNsTime) {
        this.legacyDispatchNsTime = legacyDispatchNsTime;
    }
    public String getLegacyDispatchOrdClass() {
        return legacyDispatchOrdClass;
    }
    public void setLegacyDispatchOrdClass(String legacyDispatchOrdClass) {
        this.legacyDispatchOrdClass = legacyDispatchOrdClass;
    }
    public String getLegacyServiceOrdClass() {
        return legacyServiceOrdClass;
    }
    public void setLegacyServiceOrdClass(String legacyServiceOrdClass) {
        this.legacyServiceOrdClass = legacyServiceOrdClass;
    }
    @Override
    public String toString() {
        return "SysTaskEmergency{" +
@@ -475,6 +550,12 @@
                ", hospitalInName='" + hospitalInName + '\'' +
                ", transferDistance=" + transferDistance +
                ", transferPrice=" + transferPrice +
                ", legacyServiceOrdNo='" + legacyServiceOrdNo + '\'' +
                ", legacyDispatchOrdNo='" + legacyDispatchOrdNo + '\'' +
                ", legacyServiceNsTime=" + legacyServiceNsTime +
                ", legacyDispatchNsTime=" + legacyDispatchNsTime +
                ", legacyDispatchOrdClass='" + legacyDispatchOrdClass + '\'' +
                ", legacyServiceOrdClass='" + legacyServiceOrdClass + '\'' +
                '}';
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/domain/UserSyncDTO.java
@@ -11,6 +11,9 @@
    /** SQL Server中的OA用户ID */
    private Integer oaUserId;
    /** 企业微信用户ID */
    private String oaWeixinUserId;
    /** 用户账号 */
    private String userName;
@@ -43,6 +46,16 @@
    public void setOaUserId(Integer oaUserId)
    {
        this.oaUserId = oaUserId;
    }
    public String getOaWeixinUserId()
    {
        return oaWeixinUserId;
    }
    public void setOaWeixinUserId(String oaWeixinUserId)
    {
        this.oaWeixinUserId = oaWeixinUserId;
    }
    public String getUserName()
@@ -130,6 +143,7 @@
    {
        return "UserSyncDTO{" +
                "oaUserId=" + oaUserId +
                ", oaWeixinUserId='" + oaWeixinUserId + '\'' +
                ", userName='" + userName + '\'' +
                ", nickName='" + nickName + '\'' +
                ", departmentId=" + departmentId +
ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/TaskCreateVO.java
@@ -121,6 +121,23 @@
    /** 病情ID列表(ICD-10疾病ID列表,用于同步调度单的OrdICD_ID参数) */
    private List<Long> diseaseIds;
    /** 旧系统调度单编号 */
    private String legacyDispatchOrdNo;
    /** 旧系统服务通知时间 */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date legacyServiceNsTime;
    /** 旧系统调度通知时间 */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date legacyDispatchNsTime;
    /** 旧系统调度单分类 */
    private String legacyDispatchOrdClass;
    /** 旧系统服务单分类 */
    private String legacyServiceOrdClass;
    private Date createTime;
    private  String taskStatus;
@@ -567,4 +584,36 @@
    public void setDiseaseIds(List<Long> diseaseIds) {
        this.diseaseIds = diseaseIds;
    }
    public String getLegacyDispatchOrdNo() {
        return legacyDispatchOrdNo;
    }
    public void setLegacyDispatchOrdNo(String legacyDispatchOrdNo) {
        this.legacyDispatchOrdNo = legacyDispatchOrdNo;
    }
    public Date getLegacyServiceNsTime() {
        return legacyServiceNsTime;
    }
    public void setLegacyServiceNsTime(Date legacyServiceNsTime) {
        this.legacyServiceNsTime = legacyServiceNsTime;
    }
    public Date getLegacyDispatchNsTime() {
        return legacyDispatchNsTime;
    }
    public void setLegacyDispatchNsTime(Date legacyDispatchNsTime) {
        this.legacyDispatchNsTime = legacyDispatchNsTime;
    }
    public String getLegacyDispatchOrdClass() {
        return legacyDispatchOrdClass;
    }
    public void setLegacyDispatchOrdClass(String legacyDispatchOrdClass) {
        this.legacyDispatchOrdClass = legacyDispatchOrdClass;
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/event/TaskDispatchSyncEvent.java
New file
@@ -0,0 +1,27 @@
package com.ruoyi.system.event;
/**
 * 任务同步调度单成功后触发事件
 */
public class TaskDispatchSyncEvent extends TaskEvent{
    private Long dispatchOrderId;
    public Long getDispatchOrderId(){
        return this.dispatchOrderId;
    }
    private Integer oaUserId;
    public Integer getOaUserId(){
        return this.oaUserId;
    }
    private Long serviceOrderId;
    public Long getServiceOrderId(){
        return this.serviceOrderId;
    }
    public TaskDispatchSyncEvent(Object source, Long taskId, String taskCode,Long serviceOrderId, Long dispatchOrderId,Integer oaUserId) {
        super(source, taskId, taskCode);
        this.dispatchOrderId=dispatchOrderId;
        this.oaUserId=oaUserId;
        this.serviceOrderId=serviceOrderId;
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/event/TaskServiceOrderSyncEvent.java
New file
@@ -0,0 +1,16 @@
package com.ruoyi.system.event;
/**
 * 服务单同步任务成功后触发事件
 */
public class TaskServiceOrderSyncEvent extends TaskEvent{
    private Long serviceOrderId;
    public Long getServiceOrderId(){
        return this.serviceOrderId;
    }
    public TaskServiceOrderSyncEvent(Object source, Long taskId, String taskCode,Long serviceOrderId) {
        super(source, taskId, taskCode);
        this.serviceOrderId=serviceOrderId;
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/listener/TaskMessageListener.java
@@ -1,5 +1,8 @@
package com.ruoyi.system.listener;
import com.ruoyi.system.domain.*;
import com.ruoyi.system.event.TaskDispatchSyncEvent;
import com.ruoyi.system.service.ISysTaskAssigneeService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -8,10 +11,6 @@
import org.springframework.stereotype.Component;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.domain.SysMessage;
import com.ruoyi.system.domain.SysTask;
import com.ruoyi.system.domain.SysTaskEmergency;
import com.ruoyi.system.domain.NotifyTask;
import com.ruoyi.system.event.TaskCreatedEvent;
import com.ruoyi.system.event.TaskAssignedEvent;
import com.ruoyi.system.event.TaskStatusChangedEvent;
@@ -23,8 +22,12 @@
import com.ruoyi.system.service.INotifyTaskService;
import com.ruoyi.system.service.INotifyDispatchService;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
/**
 * 任务消息监听器
@@ -61,6 +64,28 @@
    /** 待准备状态 - 可以发送短信通知 */
    private static final String TASK_STATUS_PREPARING = "PREPARING";
    @Autowired
    private ISysTaskAssigneeService taskAssigneeService;
    @Async
    @EventListener
    public void handleTaskDispatchEvent(TaskDispatchSyncEvent event) {
        try{
            log.info("收到任务派发同步事件,任务ID:{},任务编号:{},派发单ID:{}", event.getTaskId(), event.getTaskCode(), event.getDispatchOrderId());
            SysTask task=sysTaskMapper.selectSysTaskByTaskId(event.getTaskId());
            SysTaskEmergency emergency = sysTaskEmergencyMapper.selectSysTaskEmergencyByTaskId(event.getTaskId());
            if(emergency != null){
               List<SysTaskAssignee> assignees=taskAssigneeService.getAssigneesByTaskId(emergency.getTaskId());
               if(assignees!=null && !assignees.isEmpty()){
                   List<Long> assigneeIds=assignees.stream().map(SysTaskAssignee::getUserId).collect(Collectors.toList());
                   sendDispatchNotify(assigneeIds, task.getCreatorId(), event.getTaskId(), task.getTaskCode(), buildNotifyContent(task, emergency));
               }
            }
        }catch (Exception ex){
            log.error("处理任务派发同步事件失败", ex);
        }
    }
    /**
     * 监听任务创建事件
     * 
@@ -145,52 +170,8 @@
            // 构建通知内容
            String notifyContent = buildNotifyContent(task, emergency);
            // 收集创建的通知任务
            List<NotifyTask> createdTasks = new ArrayList<>();
            // 为每个执行人创建通知任务
            for (Long assigneeId : event.getAssigneeIds()) {
                // 排除创建人
                if (creatorId != null && creatorId.equals(assigneeId)) {
                    log.debug("跳过创建人,不发送任务分配通知,userId={}", assigneeId);
                    continue;
                }
                // 获取执行人信息
                SysUser assignee = sysUserMapper.selectUserById(assigneeId);
                if (assignee == null) {
                    log.warn("找不到执行人信息,用户ID:{}", assigneeId);
                    continue;
                }
                // 创建通知任务(带防重)
                NotifyTask notifyTask = new NotifyTask();
                notifyTask.setTaskId(event.getTaskId());
                notifyTask.setTaskCode(event.getTaskCode());
                notifyTask.setNotifyType(NotifyTask.NOTIFY_TYPE_TASK_ASSIGN);
                notifyTask.setUserId(assigneeId);
                notifyTask.setUserName(assignee.getNickName());
                notifyTask.setUserPhone(assignee.getPhonenumber());
                notifyTask.setTitle("任务推送");
                notifyTask.setContent(notifyContent);
                notifyTask.setCreateBy(event.getAssignerName() != null ? event.getAssignerName() : "系统");
                NotifyTask created = notifyTaskService.createNotifyTask(notifyTask);
                if (created != null) {
                    createdTasks.add(created);
                    log.info("创建通知任务成功,id={}, userId={}", created.getId(), assigneeId);
                } else {
                    log.info("通知任务已存在,跳过,taskId={}, userId={}", event.getTaskId(), assigneeId);
                }
            }
            this.sendDispatchNotify(event.getAssigneeIds(), creatorId, event.getTaskId(), event.getTaskCode(), notifyContent);
            // 分发通知任务
            if (!createdTasks.isEmpty()) {
                int successCount = notifyDispatchService.dispatchNotifies(createdTasks);
                log.info("通知分发完成,taskId={},创建数量={},成功数量={}",
                        event.getTaskId(), createdTasks.size(), successCount);
            }
            
        } catch (Exception e) {
            log.error("处理任务分配事件失败", e);
@@ -198,11 +179,79 @@
    }
    /**
     * 向执行人发送任务分配通知
     * @param assigneeIds
     * @param creatorId
     * @param taskId
     * @param taskCode
     *
     * @param notifyContent
     */
    private void sendDispatchNotify(List<Long> assigneeIds, Long creatorId,
                            Long taskId,String taskCode,String notifyContent) {
        // 收集创建的通知任务
        List<NotifyTask> createdTasks = new ArrayList<>();
        // 为每个执行人创建通知任务
        for (Long assigneeId : assigneeIds) {
            // 排除创建人
            if (creatorId != null && creatorId.equals(assigneeId)) {
                log.debug("跳过创建人,不发送任务分配通知,userId={}", assigneeId);
                continue;
            }
            // 获取执行人信息
            SysUser assignee = sysUserMapper.selectUserById(assigneeId);
            if (assignee == null) {
                log.warn("找不到执行人信息,用户ID:{}", assigneeId);
                continue;
            }
            // 创建通知任务(带防重)
            NotifyTask notifyTask = new NotifyTask();
            notifyTask.setTaskId(taskId);
            notifyTask.setTaskCode(taskCode);
            notifyTask.setNotifyType(NotifyTask.NOTIFY_TYPE_TASK_ASSIGN);
            notifyTask.setUserId(assigneeId);
            notifyTask.setUserName(assignee.getNickName());
            notifyTask.setUserPhone(assignee.getPhonenumber());
            notifyTask.setTitle("转运单任务派单通知");
            notifyTask.setContent(notifyContent);
            notifyTask.setCreateBy( "系统");
            NotifyTask created = notifyTaskService.createNotifyTask(notifyTask);
            if (created != null) {
                createdTasks.add(created);
                log.info("创建通知任务成功,id={}, userId={}", created.getId(), assigneeId);
            } else {
                log.info("通知任务已存在,跳过,taskId={}, userId={}", taskId, assigneeId);
            }
        }
        // 分发通知任务
        if (!createdTasks.isEmpty()) {
            int successCount = notifyDispatchService.dispatchNotifies(createdTasks);
            log.info("通知分发完成,taskId={},创建数量={},成功数量={}",
                    taskId, createdTasks.size(), successCount);
        }
    }
    /**
     * 构建通知内容
     */
    private String buildNotifyContent(SysTask task, SysTaskEmergency emergency) {
        StringBuilder content = new StringBuilder("您有新的转运任务,请及时处理,任务单号:"+task.getTaskCode());
        //派发单号
        String dispatchCode=emergency.getDispatchCode();
        String taskCode=task.getTaskCode();
        String orderCode=dispatchCode;
        if(dispatchCode==null){
            orderCode=taskCode;
        }
        Date dispatchTime=task.getPlanedStartTime();
        StringBuilder content = new StringBuilder();
        content.append("您有新的转运任务,任务单号:"+orderCode);
        // 添加出发地信息
        String departure = null;
        if (emergency != null && StringUtils.isNotEmpty(emergency.getHospitalOutName())) {
@@ -210,7 +259,9 @@
        } else if (StringUtils.isNotEmpty(task.getDepartureAddress())) {
            departure = task.getDepartureAddress();
        }
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm");
        content.append(",出发时间:").append(df.format(dispatchTime));
        // 添加目的地信息
        String destination = null;
        if (emergency != null && StringUtils.isNotEmpty(emergency.getHospitalInName())) {
ruoyi-system/src/main/java/com/ruoyi/system/mapper/LegacyTransferSyncMapper.java
@@ -32,8 +32,21 @@
     * @param dispatchOrdID 调度单ID
     * @return 转运单数据列表
     */
    List<Map<String, Object>> selectTransferOrdersByIDs(@Param("serviceOrdID") String serviceOrdID, @Param("dispatchOrdID") String dispatchOrdID);
    List<Map<String, Object>> selectTransferOrdersByIDs(@Param("serviceOrdID") Long serviceOrdID, @Param("dispatchOrdID") Long dispatchOrdID);
    /**
     * 根据服务单ID查询转运单数据
     *
     * @param serviceOrdID 服务单ID
     * @return 转运单数据列表
     */
    List<Map<String, Object>> selectByServiceOrdId(@Param("serviceOrdID") Long serviceOrdID);
    /**
     *
     * 根据调度单ID查询转运单数据
     */
    List<Map<String, Object>> selectByDispatchId(@Param("dispatchId") Long dispatchId);
    /**
     * 根据服务单ID查询病情信息
     * 
ruoyi-system/src/main/java/com/ruoyi/system/mapper/NotifySendLogMapper.java
@@ -78,11 +78,13 @@
     * @param id 记录ID
     * @param sendStatus 发送状态
     * @param sendResult 发送结果
     * @param sendContent 发送内容
     * @return 结果
     */
    int updateSendStatus(@Param("id") Long id,
                         @Param("sendStatus") String sendStatus,
                         @Param("sendResult") String sendResult);
                         @Param("sendResult") String sendResult,
                         @Param("sendContent") String sendContent);
    /**
     * 删除通知发送记录
ruoyi-system/src/main/java/com/ruoyi/system/service/INotifyDispatchService.java
@@ -78,4 +78,12 @@
     * @return 是否发送成功
     */
    boolean sendSmsMessage(NotifyTask notifyTask);
    /**
     * 发送企业微信消息
     *
     * @param notifyTask 通知任务
     * @return 是否发送成功
     */
    boolean sendQyWechatMessage(NotifyTask notifyTask);
}
ruoyi-system/src/main/java/com/ruoyi/system/service/INotifySendLogService.java
@@ -56,11 +56,29 @@
     * 
     * @param id 记录ID
     * @param result 发送结果信息
     * @param content 发送内容
     */
    void markSendSuccess(Long id, String result, String content);
    /**
     * 更新发送状态为成功(向后兼容)
     *
     * @param id 记录ID
     * @param result 发送结果信息
     */
    void markSendSuccess(Long id, String result);
    /**
     * 更新发送状态为失败
     *
     * @param id 记录ID
     * @param errorMsg 错误信息
     * @param content 发送内容
     */
    void markSendFailed(Long id, String errorMsg, String content);
    /**
     * 更新发送状态为失败(向后兼容)
     * 
     * @param id 记录ID
     * @param errorMsg 错误信息
@@ -100,10 +118,19 @@
    int deleteNotifySendLogById(Long id);
    /**
     * 根据任务ID和用户ID查询发送记录
     *
     * @param taskId 任务ID
     * @param userId 用户ID
     * @param notifyType 通知类型
     * @param channel 通知渠道
     * @return 通知发送记录
     */
    NotifySendLog selectNotifySendLog(Long taskId, Long userId, String notifyType, String channel);
    /**
     * 查询待重试的失败记录
     * 
     * @param maxRetryCount 最大重试次数
     * @return 失败记录列表
     */
    List<NotifySendLog> selectFailedNotifySendLogs(Integer maxRetryCount);
}
ruoyi-system/src/main/java/com/ruoyi/system/service/IQyWechatAccessTokenService.java
New file
@@ -0,0 +1,35 @@
package com.ruoyi.system.service;
/**
 * 企业微信AccessToken服务接口
 *
 * @author ruoyi
 * @date 2025-12-11
 */
public interface IQyWechatAccessTokenService {
    /**
     * 获取企业微信应用的AccessToken
     *
     * @param corpId 企业ID
     * @param corpSecret 应用密钥
     * @return AccessToken
     */
    String getAppAccessToken(String corpId, String corpSecret);
    /**
     * 刷新企业微信应用的AccessToken
     *
     * @param corpId 企业ID
     * @param corpSecret 应用密钥
     * @return 新的AccessToken
     */
    String refreshAppAccessToken(String corpId, String corpSecret);
    /**
     * 检查企业微信服务是否启用
     *
     * @return true-启用,false-禁用
     */
    boolean isEnabled();
}
ruoyi-system/src/main/java/com/ruoyi/system/service/IQyWechatService.java
New file
@@ -0,0 +1,46 @@
package com.ruoyi.system.service;
/**
 * 企业微信服务接口
 *
 * @author ruoyi
 * @date 2025-12-11
 */
public interface IQyWechatService {
    /**
     * 发送企业微信消息
     *
     * @param userId 用户ID
     * @param title 消息标题
     * @param content 消息内容
     * @return 是否发送成功
     */
    boolean sendNotifyMessage(Long userId, String title, String content, String notifyUrl);
    /**
     * 发送企业微信文本消息
     *
     * @param qyUserId 企业微信用户ID
     * @param title 消息标题
     * @param content 消息内容
     * @param notifyUrl 通知链接
     * @return 是否发送成功
     */
    boolean sendTextMessage(String qyUserId, String title, String content, String notifyUrl);
    /**
     * 获取用户的企业微信ID
     *
     * @param userId 系统用户ID
     * @return 企业微信用户ID
     */
    String getQyUserIdByUserId(Long userId);
    /**
     * 检查企业微信服务是否启用
     *
     * @return true=启用, false=未启用
     */
    boolean isEnabled();
}
ruoyi-system/src/main/java/com/ruoyi/system/service/ISysConfigService.java
@@ -86,4 +86,13 @@
     * @return 结果
     */
    public boolean checkConfigKeyUnique(SysConfig config);
    /**
     * 更新配置值
     *
     * @param configKey 参数键名
     * @param configValue 参数值
     * @return 结果
     */
    public int updateConfigValue(String configKey, String configValue);
}
ruoyi-system/src/main/java/com/ruoyi/system/service/ISysEmergencyTaskService.java
@@ -12,6 +12,7 @@
    void updateEmergencyInfoFromUpdateVO(SysTaskEmergency oldEmergency, TaskUpdateVO updateVO, String userName);
    SysTaskEmergency selectSysTaskEmergencyByTaskId(Long taskId);
    /**
     * 从 TaskCreateVO 更新急救转运任务扩展信息(用于旧系统同步)
     * 
ruoyi-system/src/main/java/com/ruoyi/system/service/ISysTaskService.java
@@ -18,7 +18,8 @@
 * @date 2024-01-15
 */
public interface ISysTaskService {
    public Boolean dispatchSyncEvent(Long taskId);
    /**
     * 查询任务管理
     * 
@@ -36,6 +37,15 @@
    public List<SysTask> selectSysTaskList(TaskQueryVO queryVO);
    /**
     * 根据任务编号、调度单编号或服务单编号查询任务列表
     *
     * @param queryVO 任务查询对象
     * @param taskCode 任务编号
     * @return 任务管理集合
     */
    public List<SysTask> selectSysTaskListByMultiCode(TaskQueryVO queryVO, String taskCode);
    /**
     * 新增任务管理
     * 
     * @param createVO 任务创建对象
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/LegacySystemSyncServiceImpl.java
@@ -16,29 +16,29 @@
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.security.cert.X509Certificate;
import com.ruoyi.common.utils.MapValueUtils;
import com.ruoyi.system.domain.*;
import com.ruoyi.system.event.TaskDispatchSyncEvent;
import com.ruoyi.system.event.TaskServiceOrderSyncEvent;
import com.ruoyi.system.mapper.*;
import com.ruoyi.system.service.*;
import com.ruoyi.system.task.ITaskAttachmentService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.ruoyi.common.config.LegacySystemConfig;
import com.ruoyi.common.utils.MapValueUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.entity.SysDept;
import com.ruoyi.system.domain.vo.TaskCreateVO;
import com.ruoyi.system.mapper.SysTaskMapper;
import com.ruoyi.system.mapper.SysTaskEmergencyMapper;
import com.ruoyi.system.mapper.SysTaskVehicleMapper;
import com.ruoyi.system.mapper.SysTaskAssigneeMapper;
import com.ruoyi.system.mapper.VehicleInfoMapper;
import com.ruoyi.system.mapper.SysUserMapper;
import com.ruoyi.system.mapper.SysDeptMapper;
import com.ruoyi.system.utils.TaskStatusConverter;
import org.springframework.util.CollectionUtils;
/**
 * 旧系统同步Service业务层处理
@@ -91,12 +91,35 @@
    private ITaskAttachmentService taskAttachmentService;
    @Autowired
    private ApplicationEventPublisher eventPublisher;
    
    @Autowired
    private LegacyTransferSyncMapper legacyTransferSyncMapper;
    public Map<String,Object> getLegacyDispatchByDispatchId(Long dispatchId) {
        List<Map<String, Object>> result = legacyTransferSyncMapper.selectByDispatchId(dispatchId);
        if(!CollectionUtils.isEmpty(result)){
            return result.get(0);
        }else{
            return null;
        }
    }
    private Map<String,Object> getLegacyByServiceOrdId(Long serviceOrdId) {
        List<Map<String, Object>> result = legacyTransferSyncMapper.selectByServiceOrdId(serviceOrdId);
        if(!CollectionUtils.isEmpty(result)){
            return result.get(0);
        }else{
            return null;
        }
    }
    /**
     * 同步急救转运任务到旧系统
     */
    @Override
    @Transactional
    public Long syncEmergencyTaskToLegacy(Long taskId) {
        if (!legacyConfig.isEnabled()) {
            log.info("旧系统同步已禁用,跳过任务ID: {}", taskId);
@@ -149,12 +172,26 @@
                emergency.setSyncStatus(2); // 同步成功
                emergency.setSyncTime(new Date());
                emergency.setSyncErrorMsg(null);
                Map<String, Object> legacy = getLegacyByServiceOrdId(serviceOrdId);
                String serviceOrdNo = MapValueUtils.getStringValue(legacy, "ServiceOrdNo");
                if(serviceOrdNo!=null) {
                    emergency.setLegacyServiceOrdNo(serviceOrdNo);
                }
                String serviceOrdClass = MapValueUtils.getStringValue(legacy, "ServiceOrdClass");
                if(serviceOrdClass!=null) {
                    emergency.setLegacyServiceOrdClass(serviceOrdClass);
                }
                Date serviceCCTime = MapValueUtils.getDateValue(legacy, "ServiceOrd_CC_Time");
                if(serviceCCTime!=null) {
                    emergency.setLegacyServiceNsTime(serviceCCTime);
                }
                sysTaskEmergencyService.updateSysTaskEmergency(emergency);
                
                // 更新任务主表同步标记
                task.setLegacySynced(1);
                sysTaskMapper.updateSysTask(task);
                eventPublisher.publishEvent(new TaskServiceOrderSyncEvent(this, taskId, task.getTaskCode(), serviceOrdId));
                log.info("任务同步成功,任务ID: {}, ServiceOrdID: {}", taskId, serviceOrdId);
                return serviceOrdId;
            } else {
@@ -187,7 +224,13 @@
            return null;
        }
    }
    //在这里监听派发的事件
    @EventListener
    public void handleTaskServiceOrderSyncEvent(TaskServiceOrderSyncEvent event) {
        log.info("收到任务服务单同步事件,任务ID:{},任务编号:{},服务单ID:{}", event.getTaskId(), event.getTaskCode(), event.getServiceOrderId());
        syncDispatchOrderToLegacy(event.getTaskId());
    }
    /**
     * 批量同步未同步的急救转运任务
     * 使用分页查询,确保所有符合条件的任务都能被同步
@@ -303,7 +346,6 @@
     * 同步调度单到旧系统(admin_save_24.asp)
     */
    @Override
    @Transactional
    public Long syncDispatchOrderToLegacy(Long taskId) {
        if (!legacyConfig.isEnabled()) {
            log.info("旧系统同步已禁用,跳过调度单同步,任务ID: {}", taskId);
@@ -432,13 +474,18 @@
                emergency.setDispatchSyncStatus(2); // 同步成功
                emergency.setDispatchSyncTime(new Date());
                emergency.setDispatchSyncErrorMsg(null);
                //更新调度单信息开到新系统
                Map<String,Object> dispatchInfo = this.getLegacyDispatchByDispatchId(dispatchOrdId);
                if (dispatchInfo != null) {
                    emergency.setLegacyDispatchNsTime(MapValueUtils.getDateValue(dispatchInfo, "DispatchOrd_NS_Time")); // 同步成功
                    emergency.setLegacyDispatchOrdClass(MapValueUtils.getStringValue(dispatchInfo, "DispatchOrdClass")); // 同步成功
                    emergency.setLegacyDispatchOrdNo(MapValueUtils.getStringValue(dispatchInfo, "DispatchOrdNo")); // 同步成功
                    emergency.setLegacyServiceNsTime(MapValueUtils.getDateValue(dispatchInfo, "ServiceOrd_CC_Time")); // 同步成功
                    emergency.setLegacyServiceOrdClass(MapValueUtils.getStringValue(dispatchInfo, "ServiceOrdClass")); // 同步成功
                }
                sysTaskEmergencyService.updateSysTaskEmergency(emergency);
                List<SysTaskAttachment> taskAttachments= sysTaskService.getAttachmentsByTaskId(taskId);
                if (taskAttachments != null && !taskAttachments.isEmpty()) {
                    //同步附件
                   this.syncAttachmentToLegacy(taskAttachments,serviceOrdId,dispatchOrdId,oaUserID);
                }
                eventPublisher.publishEvent(new TaskDispatchSyncEvent(this, taskId, task.getTaskCode(),serviceOrdId, dispatchOrdId, oaUserID));
                log.info("调度单同步成功,任务ID: {}, DispatchOrdID: {}", taskId, dispatchOrdId);
                return dispatchOrdId;
@@ -472,7 +519,19 @@
            return null;
        }
    }
    @EventListener
    public void handleTaskDispatchSyncEvent(TaskDispatchSyncEvent event) {
        Long taskId = event.getTaskId();
        Long dispatchOrdId = event.getDispatchOrderId();
        Long serviceOrdId = event.getServiceOrderId();
        Integer oaUserID = event.getOaUserId();
        List<SysTaskAttachment> taskAttachments= sysTaskService.getAttachmentsByTaskId(taskId);
        if (taskAttachments != null && !taskAttachments.isEmpty()) {
            //同步附件
            this.syncAttachmentToLegacy(taskAttachments,serviceOrdId,dispatchOrdId,oaUserID);
        }
    }
    /**
     * 批量同步未同步的调度单
     * 使用分页查询,确保所有符合条件的任务都能被同步
@@ -492,17 +551,17 @@
            while (true) {
                // 分页查询已同步服务单但未同步调度单的任务
                List<SysTaskEmergency> pendingTasks = sysTaskEmergencyService.selectPendingDispatchSyncTasks(offset, pageSize);
                log.info("查询到未同步调度单的任务数量: {}", pendingTasks.size());
//                log.info("查询到未同步调度单的任务数量: {}", pendingTasks.size());
                if (pendingTasks == null || pendingTasks.isEmpty()) {
                    log.info("没有更多需要同步调度单的任务,offset: {}", offset);
                    break; // 没有更多数据,退出循环
                }
                
                log.info("开始同步调度单第 {} 页,任务数量: {}", (offset / pageSize) + 1, pendingTasks.size());
//                log.info("开始同步调度单第 {} 页,任务数量: {}", (offset / pageSize) + 1, pendingTasks.size());
                
                int pageSuccessCount = 0;
                for (SysTaskEmergency emergency : pendingTasks) {
                    log.info("开始同步调度单,任务ID: {}", emergency.getTaskId());
//                    log.info("开始同步调度单,任务ID: {}", emergency.getTaskId());
                    Long dispatchOrdId = syncDispatchOrderToLegacy(emergency.getTaskId());
                    if (dispatchOrdId != null && dispatchOrdId > 0) {
@@ -1226,6 +1285,93 @@
        }
    }
    
    // 删除下面的重复方法,因为我们将使用MapValueUtils工具类中的方法
    /*
    private String getStringValue(Map<String, Object> map, String key) {
        Object value = map.get(key);
        return value != null ? value.toString() : null;
    }
    private BigDecimal getBigDecimalValue(Map<String, Object> map, String key) {
        Object value = map.get(key);
        if (value == null) {
            return null;
        }
        if (value instanceof BigDecimal) {
            return (BigDecimal) value;
        }
        try {
            return new BigDecimal(value.toString());
        } catch (NumberFormatException e) {
            return null;
        }
    }
    private Long getLongValue(Map<String, Object> map, String key) {
        Object value = map.get(key);
        if (value == null) {
            return null;
        }
        if (value instanceof Long) {
            return (Long) value;
        }
        try {
            return Long.valueOf(value.toString());
        } catch (NumberFormatException e) {
            return null;
        }
    }
    private Integer getIntegerValue(Map<String, Object> map, String key) {
        Object value = map.get(key);
        if (value == null) {
            return null;
        }
        if (value instanceof Integer) {
            return (Integer) value;
        }
        try {
            return Integer.valueOf(value.toString());
        } catch (NumberFormatException e) {
            return null;
        }
    }
    private Date getDateValue(Map<String, Object> map, String key) {
        Object value = map.get(key);
        if (value == null) {
            return null;
        }
        if (value instanceof Date) {
            return (Date) value;
        }
        // 如果是字符串,尝试解析
        if (value instanceof String) {
            try {
                return DateUtils.parseDate(value.toString());
            } catch (Exception e) {
                return null;
            }
        }
        return null;
    }
    private boolean isValidDateFormat(String dateStr, String format) {
        if (StringUtils.isEmpty(dateStr)) {
            return false;
        }
        try {
            SimpleDateFormat sdf = new SimpleDateFormat(format);
            sdf.setLenient(false);
            sdf.parse(dateStr);
            return true;
        } catch (Exception e) {
            return false;
        }
    }
    */
    /**
     * 重新同步车辆和人员变更的任务到旧系统
     * 当任务的车辆信息或人员信息发生变更时,需要调用旧系统接口重新同步
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/LegacyTransferSyncServiceImpl.java
@@ -3,6 +3,7 @@
import com.ruoyi.common.core.domain.entity.SysDept;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.MapValueUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.domain.SysTaskEmergency;
import com.ruoyi.system.domain.VehicleInfo;
@@ -107,8 +108,8 @@
            for (Map<String, Object> order : transferOrders) {
                processedCount++;
                try {
                    String serviceOrdID = getStringValue(order, "ServiceOrdID");
                    String dispatchOrdID = getStringValue(order, "DispatchOrdID");
                    String serviceOrdID = MapValueUtils.getStringValue(order, "ServiceOrdID");
                    String dispatchOrdID = MapValueUtils.getStringValue(order, "DispatchOrdID");
                    
                    // 检查参数有效性
                    if (StringUtils.isEmpty(serviceOrdID)) {
@@ -141,8 +142,8 @@
                    break;
                } catch (Exception e) {
                    log.error("同步单个转运单失败: ServiceOrdID={}, DispatchOrdID={}", 
                             getStringValue(order, "ServiceOrdID"),
                             getStringValue(order, "DispatchOrdID"), e);
                             MapValueUtils.getStringValue(order, "ServiceOrdID"),
                             MapValueUtils.getStringValue(order, "DispatchOrdID"), e);
                }
            }
            
@@ -173,7 +174,25 @@
                return false;
            }
            // 直接查询指定的转运单信息
            List<Map<String, Object>> transferOrders = legacyTransferSyncMapper.selectTransferOrdersByIDs(serviceOrdID, dispatchOrdID);
            Long serviceOrdIdLong = null;
            Long dispatchOrdIdLong = null;
            try {
                serviceOrdIdLong = Long.valueOf(serviceOrdID);
            } catch (NumberFormatException e) {
                log.error("服务单ID不是有效数字: {}", serviceOrdID);
                return false;
            }
            if (StringUtils.isNotEmpty(dispatchOrdID)) {
                try {
                    dispatchOrdIdLong = Long.valueOf(dispatchOrdID);
                } catch (NumberFormatException e) {
                    log.warn("调度单ID不是有效数字: {}", dispatchOrdID);
                }
            }
            List<Map<String, Object>> transferOrders = legacyTransferSyncMapper.selectTransferOrdersByIDs(serviceOrdIdLong, dispatchOrdIdLong);
            Map<String, Object> order = transferOrders.get(0);
@@ -224,25 +243,25 @@
            }
            sysTaskCode = createTaskVo.getTaskCode();
            // 记录创建的任务信息
            log.debug("准备创建任务: ServiceOrdID={}, DispatchOrdID={}, 患者姓名={}, 转出医院={}, 转入医院={}",
                    serviceOrdID, dispatchOrdID,
                    createTaskVo.getPatient() != null ? createTaskVo.getPatient().getName() : "未知",
                    createTaskVo.getHospitalOut() != null ? createTaskVo.getHospitalOut().getName() : "未知",
                    createTaskVo.getHospitalIn() != null ? createTaskVo.getHospitalIn().getName() : "未知");
//            log.debug("准备创建任务: ServiceOrdID={}, DispatchOrdID={}, 患者姓名={}, 转出医院={}, 转入医院={}",
//                    serviceOrdID, dispatchOrdID,
//                    createTaskVo.getPatient() != null ? createTaskVo.getPatient().getName() : "未知",
//                    createTaskVo.getHospitalOut() != null ? createTaskVo.getHospitalOut().getName() : "未知",
//                    createTaskVo.getHospitalIn() != null ? createTaskVo.getHospitalIn().getName() : "未知");
            /**
             * 开单时间
             */
            Date ServiceOrd_CC_Time= getDateValue(order, "ServiceOrd_CC_Time");
            Date ServiceOrd_CC_Time= MapValueUtils.getDateValue(order, "ServiceOrd_CC_Time");
            // 调用sysTaskService创建任务
            String serviceOrdClass = getStringValue(order,"ServiceOrdClass");
            String serviceOrdNo = getStringValue(order,"ServiceOrdNo");
            String serviceOrdClass = MapValueUtils.getStringValue(order,"ServiceOrdClass");
            String serviceOrdNo = MapValueUtils.getStringValue(order,"ServiceOrdNo");
            Integer oauserId=getIntegerValue(order,"ServiceOrd_CC_ID");
            Integer oauserId=MapValueUtils.getIntegerValue(order,"ServiceOrd_CC_ID");
            if(oauserId==null){
                oauserId=getIntegerValue(order,"ServiceOrd_NS_ID");
                oauserId=MapValueUtils.getIntegerValue(order,"ServiceOrd_NS_ID");
            }
            if(oauserId==null || oauserId==0){
                log.error("创建任务时,获取创建人信息失败,serviceOrdID={}, DispatchOrdID={} ServiceOrd_NS_ID={},ServiceOrd_CC_ID={}", serviceOrdID, dispatchOrdID, getIntegerValue(order,"ServiceOrd_NS_ID"),getIntegerValue(order,"ServiceOrd_CC_ID"));
            if(oauserId==null || oauserId==0) {
                log.error("创建任务时,获取创建人信息失败,serviceOrdID={}, DispatchOrdID={} ServiceOrd_NS_ID={},ServiceOrd_CC_ID={}", serviceOrdID, dispatchOrdID, MapValueUtils.getIntegerValue(order, "ServiceOrd_NS_ID"), MapValueUtils.getIntegerValue(order, "ServiceOrd_CC_ID"));
                return false;
            }
            SysUser sysUser=sysUserService.selectUserByOaUserId(oauserId);
@@ -294,38 +313,39 @@
        try {
            // 构造TaskCreateVO对象
            TaskCreateVO createTaskVo = buildCreateTaskVo(serviceOrdID, dispatchOrdID, order);
            sysTaskCode = createTaskVo.getTaskCode();
            if (createTaskVo == null) {
                log.error("构造TaskCreateVO失败: ServiceOrdID={}, DispatchOrdID={}", serviceOrdID, dispatchOrdID);
                return false;
            }
            sysTaskCode = createTaskVo.getTaskCode();
            // 记录创建的任务信息
            log.debug("准备创建任务: ServiceOrdID={}, DispatchOrdID={}, 患者姓名={}, 转出医院={}, 转入医院={}",
                     serviceOrdID, dispatchOrdID,
                     createTaskVo.getPatient() != null ? createTaskVo.getPatient().getName() : "未知",
                     createTaskVo.getHospitalOut() != null ? createTaskVo.getHospitalOut().getName() : "未知",
                     createTaskVo.getHospitalIn() != null ? createTaskVo.getHospitalIn().getName() : "未知");
//            log.debug("准备创建任务: ServiceOrdID={}, DispatchOrdID={}, 患者姓名={}, 转出医院={}, 转入医院={}",
//                     serviceOrdID, dispatchOrdID,
//                     createTaskVo.getPatient() != null ? createTaskVo.getPatient().getName() : "未知",
//                     createTaskVo.getHospitalOut() != null ? createTaskVo.getHospitalOut().getName() : "未知",
//                     createTaskVo.getHospitalIn() != null ? createTaskVo.getHospitalIn().getName() : "未知");
            /**
             * 开单时间
             */
            Date ServiceOrd_CC_Time= getDateValue(order, "ServiceOrd_CC_Time");
            Date ServiceOrd_CC_Time= MapValueUtils.getDateValue(order, "ServiceOrd_CC_Time");
            // 调用sysTaskService创建任务
            String serviceOrdClass = getStringValue(order,"ServiceOrdClass");
            String serviceOrdNo = getStringValue(order,"ServiceOrdNo");
            String serviceOrdClass = MapValueUtils.getStringValue(order,"ServiceOrdClass");
            String serviceOrdNo = MapValueUtils.getStringValue(order,"ServiceOrdNo");
            /**
             * 创建人ID
             */
            Integer oauserId=getIntegerValue(order,"ServiceOrd_CC_ID");
            Integer oauserId=MapValueUtils.getIntegerValue(order,"ServiceOrd_CC_ID");
            if(oauserId==null || oauserId==0) {
                oauserId=getIntegerValue(order,"ServiceOrd_NS_ID");
                oauserId=MapValueUtils.getIntegerValue(order,"ServiceOrd_NS_ID");
            }
            SysUser sysUser=sysUserService.selectUserByOaUserId(oauserId);
            if(sysUser==null){
                log.error("创建任务时,获取创建人信息失败,serviceOrdID={}, DispatchOrdID={} ServiceOrd_CC_ID:{},ServiceOrd_NS_ID:{}", serviceOrdID, dispatchOrdID, getIntegerValue(order,"ServiceOrd_CC_ID"),getIntegerValue(order,"ServiceOrd_NS_ID"));
            if(sysUser==null) {
                log.error("创建任务时,获取创建人信息失败,serviceOrdID={}, DispatchOrdID={} ServiceOrd_CC_ID:{},ServiceOrd_NS_ID:{}", serviceOrdID, dispatchOrdID, MapValueUtils.getIntegerValue(order, "ServiceOrd_CC_ID"), MapValueUtils.getIntegerValue(order, "ServiceOrd_NS_ID"));
                return false;
            }
            Long taskCreatorId= sysUser.getUserId();
            String createUserName= sysUser.getUserName();
            SysDept dept=sysDeptService.selectDeptByServiceClass(serviceOrdClass);
@@ -410,6 +430,8 @@
        String ServiceOrdNo_Str=String.format("%03d", intServiceNo);
        return serviceOrdClass+ServiceOrd_CC_Time_Str+"-"+ServiceOrdNo_Str;
    }
    /**
     * 构造TaskCreateVO对象用于创建任务
     * 
@@ -433,10 +455,10 @@
                log.error("服务单ID不能为空");
                return null;
            }
            String serviceOrdClass = getStringValue(order, "ServiceOrdClass");
            String serviceOrdClass = MapValueUtils.getStringValue(order, "ServiceOrdClass");
            TaskCreateVO createTaskVo = new TaskCreateVO();
            String serviceOrdCode=this.getServiceOrdCode(getDateValue(order, "ServiceOrd_CC_Time"),serviceOrdClass,getStringValue(order, "ServiceOrdNo"));
            String serviceOrdCode=this.getServiceOrdCode(MapValueUtils.getDateValue(order, "ServiceOrd_CC_Time"),serviceOrdClass,MapValueUtils.getStringValue(order, "ServiceOrdNo"));
            createTaskVo.setTaskCode(serviceOrdCode);
//            log.info("构造TaskCreateVO: ServiceOrdID={}, DispatchOrdID={},taskCode:{}", serviceOrdID, dispatchOrdID,serviceOrdCode);
            // 设置基本信息
@@ -448,26 +470,26 @@
                createTaskVo.setDocumentTypeId(serviceOrdClass);
            }
            
            String serviceOrdType = getStringValue(order, "ServiceOrdType");
            String serviceOrdType = MapValueUtils.getStringValue(order, "ServiceOrdType");
            if (StringUtils.isNotEmpty(serviceOrdType)) {
                createTaskVo.setTaskTypeId(serviceOrdType);
            }
            
            // 设置区域类型
            String serviceOrdAreaType = getStringValue(order, "ServiceOrdAreaType");
            String serviceOrdAreaType = MapValueUtils.getStringValue(order, "ServiceOrdAreaType");
            // 设置用户ID
            Long serviceOrdUserID = getLongValue(order, "ServiceOrdUserID");
            Long serviceOrdUserID = MapValueUtils.getLongValue(order, "ServiceOrdUserID");
            // 设置患者信息
            TaskCreateVO.PatientInfo patientInfo = new TaskCreateVO.PatientInfo();
            patientInfo.setName(getStringValue(order, "ServiceOrdPtName"));
            patientInfo.setPhone(getStringValue(order, "ServiceOrdCoPhone"));
            patientInfo.setIdCard(getStringValue(order, "ServiceOrdPtIDCard"));
            patientInfo.setCondition(getStringValue(order, "ServiceOrdPtCondition"));
            patientInfo.setContact(getStringValue(order, "ServiceOrdCoName"));
            patientInfo.setName(MapValueUtils.getStringValue(order, "ServiceOrdPtName"));
            patientInfo.setPhone(MapValueUtils.getStringValue(order, "ServiceOrdCoPhone"));
            patientInfo.setIdCard(MapValueUtils.getStringValue(order, "ServiceOrdPtIDCard"));
            patientInfo.setCondition(MapValueUtils.getStringValue(order, "ServiceOrdPtCondition"));
            patientInfo.setContact(MapValueUtils.getStringValue(order, "ServiceOrdCoName"));
            String serviceOrdPtSex = getStringValue(order, "ServiceOrdPtSex");
            String serviceOrdPtSex = MapValueUtils.getStringValue(order, "ServiceOrdPtSex");
            if(serviceOrdPtSex!=null){
                if(serviceOrdPtSex.equals("男")){
                    patientInfo.setGender("male");
@@ -477,7 +499,7 @@
            }
            createTaskVo.setPatient(patientInfo);
            //1000公里,提取数字
            String ServiceOrdTraDistance=getStringValue(order, "ServiceOrdTraDistance");
            String ServiceOrdTraDistance=MapValueUtils.getStringValue(order, "ServiceOrdTraDistance");
            if(ServiceOrdTraDistance!=null){
                ServiceOrdTraDistance=ServiceOrdTraDistance.replaceAll("[^0-9]", "");
                createTaskVo.setDistance(new BigDecimal(ServiceOrdTraDistance));
@@ -487,7 +509,7 @@
            // 设置转出医院信息
            TaskCreateVO.HospitalInfo hospitalOutInfo = new TaskCreateVO.HospitalInfo();
            Long hospitalOutId = getLongValue(order, "ServiceOrdPtOutHospID");
            Long hospitalOutId = MapValueUtils.getLongValue(order, "ServiceOrdPtOutHospID");
            hospitalOutInfo.setId(hospitalOutId);
            if (hospitalOutId != null) {
                String hospitalOutName = legacyTransferSyncMapper.selectHospitalNameByHospID(hospitalOutId.toString());
@@ -495,13 +517,13 @@
                    hospitalOutInfo.setName(hospitalOutName);
                }
            }
            String ServiceOrdTraVia=getStringValue(order, "ServiceOrdTraVia");
            String ServiceOrdTraVia=MapValueUtils.getStringValue(order, "ServiceOrdTraVia");
            if(ServiceOrdTraVia!=null){
                hospitalOutInfo.setAddress(ServiceOrdTraVia);
            }
            String hospitalOutDeptId = getStringValue(order, "ServiceOrdPtServicesID");
            String hospitalOutDeptId = MapValueUtils.getStringValue(order, "ServiceOrdPtServicesID");
            hospitalOutInfo.setDepartmentId(hospitalOutDeptId);
            if (StringUtils.isNotEmpty(hospitalOutDeptId)) {
                String hospitalOutDeptName = legacyTransferSyncMapper.selectDepartmentNameByDeptID(hospitalOutDeptId);
@@ -510,7 +532,7 @@
                }
            }
            //转出床位
            String serviceOrdPtServices=getStringValue(order, "ServiceOrdPtServices");
            String serviceOrdPtServices=MapValueUtils.getStringValue(order, "ServiceOrdPtServices");
            if(serviceOrdPtServices!= null){
                hospitalOutInfo.setBedNumber(serviceOrdPtServices);
            }
@@ -518,7 +540,7 @@
            
            // 设置转入医院信息
            TaskCreateVO.HospitalInfo hospitalInInfo = new TaskCreateVO.HospitalInfo();
            Long hospitalInId = getLongValue(order, "ServiceOrdPtInHospID");
            Long hospitalInId = MapValueUtils.getLongValue(order, "ServiceOrdPtInHospID");
            hospitalInInfo.setId(hospitalInId);
            if (hospitalInId != null) {
                String hospitalInName = legacyTransferSyncMapper.selectHospitalNameByHospID(hospitalInId.toString());
@@ -526,18 +548,18 @@
                    hospitalInInfo.setName(hospitalInName);
                }
            }
            String serviceOrdTraEnd = getStringValue(order, "ServiceOrdTraEnd");
            String serviceOrdTraEnd = MapValueUtils.getStringValue(order, "ServiceOrdTraEnd");
            if(serviceOrdTraEnd!= null){
                hospitalInInfo.setAddress(serviceOrdTraEnd);
            }
            //转入床位
            String serviceOrdPtInServices =getStringValue(order, "ServiceOrdPtInServices");
            String serviceOrdPtInServices =MapValueUtils.getStringValue(order, "ServiceOrdPtInServices");
            if(serviceOrdPtInServices!= null){
                hospitalInInfo.setBedNumber(serviceOrdPtInServices);
            }
            String hospitalInDeptId = getStringValue(order, "ServiceOrdPtInServicesID");
            String hospitalInDeptId = MapValueUtils.getStringValue(order, "ServiceOrdPtInServicesID");
            hospitalInInfo.setDepartmentId(hospitalInDeptId);
            if (StringUtils.isNotEmpty(hospitalInDeptId)) {
                String hospitalInDeptName = legacyTransferSyncMapper.selectDepartmentNameByDeptID(hospitalInDeptId);
@@ -548,11 +570,11 @@
            createTaskVo.setHospitalIn(hospitalInInfo);
            
            // 设置地址信息
            createTaskVo.setDepartureAddress(getStringValue(order, "ServiceOrdTraStreet"));
            createTaskVo.setDestinationAddress(getStringValue(order, "ServiceOrdTraEnd"));
            createTaskVo.setDepartureAddress(MapValueUtils.getStringValue(order, "ServiceOrdTraStreet"));
            createTaskVo.setDestinationAddress(MapValueUtils.getStringValue(order, "ServiceOrdTraEnd"));
            
            // 设置价格和距离信息
            createTaskVo.setPrice(getBigDecimalValue(order, "ServiceOrdTraTxnPrice"));
            createTaskVo.setPrice(MapValueUtils.getBigDecimalValue(order, "ServiceOrdTraTxnPrice"));
            // 距离信息需要从其他字段计算或获取
            if(dispatchOrdID!=null) {
@@ -565,7 +587,7 @@
            }
            // 设置车辆信息
            // 车辆ID需要根据DispatchOrdCarID查询获取
            String carID = getStringValue(order, "DispatchOrdCarID");
            String carID = MapValueUtils.getStringValue(order, "DispatchOrdCarID");
            if (StringUtils.isNotEmpty(carID)) {
                String carLicense = legacyTransferSyncMapper.selectCarLicenseByCarID(carID);
                if (StringUtils.isNotEmpty(carLicense)) {
@@ -619,24 +641,24 @@
            
            // 设置备注信息
            String remark = "服务单ID: " + serviceOrdID + ", 调度单ID: " + dispatchOrdID;
            String serviceOrdCoTies = getStringValue(order, "ServiceOrdCoTies");
            String serviceOrdCoTies = MapValueUtils.getStringValue(order, "ServiceOrdCoTies");
            if (StringUtils.isNotEmpty(serviceOrdCoTies)) {
                remark += ", 联系人关系: " + serviceOrdCoTies;
            }
            createTaskVo.setRemark(remark);
            
            // 设置计划开始时间
            Date plannedStartTime = getDateValue(order, "ServiceOrdApptDate");
            Date plannedStartTime = MapValueUtils.getDateValue(order, "ServiceOrdApptDate");
            if (plannedStartTime != null) {
                createTaskVo.setPlannedStartTime(plannedStartTime);
            }
            Date actualStartTime = getDateValue(order, "DispatchOrdActualDate");
            Date actualStartTime = MapValueUtils.getDateValue(order, "DispatchOrdActualDate");
            if (actualStartTime != null) {
                createTaskVo.setActualStartTime(actualStartTime);
            }
            Date actualEndTime = getDateValue(order, "DispatchOrdReturnDate");
            Date actualEndTime = MapValueUtils.getDateValue(order, "DispatchOrdReturnDate");
            if (actualEndTime != null) {
                createTaskVo.setActualEndTime(actualEndTime);
            }
@@ -644,7 +666,7 @@
            
            // 设置创建时间 开单日期
            Date createTime = getDateValue(order, "ServiceOrd_CC_Time");
            Date createTime = MapValueUtils.getDateValue(order, "ServiceOrd_CC_Time");
            if (createTime != null) {
                createTaskVo.setCreateTime(createTime);
            }
@@ -664,14 +686,36 @@
                createTaskVo.setDiseaseIds(diseaseIds);
            }
            Integer dispatchOrdStatus = getIntegerValue(order, "DispatchOrdStatus");
            Integer dispatchOrdStatus = MapValueUtils.getIntegerValue(order, "DispatchOrdStatus");
           TaskStatus status= TaskStatusConverter.convertFromLegacyStatus(dispatchOrdStatus);
           if(status!=null) {
               createTaskVo.setTaskStatus(status.getCode());
           }
//            log.info("TaskCreateVO构造完成: ServiceOrdID={}, DispatchOrdID={}", serviceOrdID, dispatchOrdID);
            // 设置旧系统同步字段
            String dispatchOrdNo = MapValueUtils.getStringValue(order, "DispatchOrdNo");
           if(dispatchOrdNo!=null) {
               createTaskVo.setLegacyDispatchOrdNo(dispatchOrdNo);
           }
           Date ccTime=MapValueUtils.getDateValue(order, "ServiceOrd_CC_Time");
           if(ccTime!=null) {
               createTaskVo.setLegacyServiceNsTime(ccTime);
           }
           Date nsTime=MapValueUtils.getDateValue(order, "DispatchOrd_NS_Time");
           if(nsTime!=null) {
               createTaskVo.setLegacyDispatchNsTime(MapValueUtils.getDateValue(order, "DispatchOrd_NS_Time"));
           }
           String dispatchOrdClass=MapValueUtils.getStringValue(order, "DispatchOrdClass");
           if(dispatchOrdClass!=null) {
               createTaskVo.setLegacyDispatchOrdClass(dispatchOrdClass);
           }
//           String serviceOrdClass=MapValueUtils.getStringValue(order, "ServiceOrdClass");
           if(serviceOrdClass!=null) {
               createTaskVo.setLegacyServiceOrdClass(serviceOrdClass);
           }
            return createTaskVo;
            
        } catch (Exception e) {
@@ -718,8 +762,8 @@
            List<TaskCreateVO.AssigneeInfo> assignees = new ArrayList<>();
            if (assigneeList != null && !assigneeList.isEmpty()) {
                for (Map<String, Object> assigneeMap : assigneeList) {
                    String entourageOAId = getStringValue(assigneeMap, "EntourageOAId");
                    String entourageState = getStringValue(assigneeMap, "EntourageID");
                    String entourageOAId = MapValueUtils.getStringValue(assigneeMap, "EntourageOAId");
                    String entourageState = MapValueUtils.getStringValue(assigneeMap, "EntourageID");
                    
                    if (StringUtils.isNotEmpty(entourageOAId)) {
                        try {
@@ -793,122 +837,7 @@
        }
    }
    
    /**
     * 从Map中获取字符串值
     *
     * @param map Map对象
     * @param key 键
     * @return 字符串值
     */
    private String getStringValue(Map<String, Object> map, String key) {
        Object value = map.get(key);
        return value != null ? value.toString() : null;
    }
    /**
     * 从Map中获取BigDecimal值
     *
     * @param map Map对象
     * @param key 键
     * @return BigDecimal值
     */
    private BigDecimal getBigDecimalValue(Map<String, Object> map, String key) {
        Object value = map.get(key);
        if (value == null) {
            return null;
        }
        if (value instanceof BigDecimal) {
            return (BigDecimal) value;
        }
        try {
            return new BigDecimal(value.toString());
        } catch (NumberFormatException e) {
            return null;
        }
    }
    /**
     * 从Map中获取Long值
     *
     * @param map Map对象
     * @param key 键
     * @return Long值
     */
    private Long getLongValue(Map<String, Object> map, String key) {
        Object value = map.get(key);
        if (value == null) {
            return null;
        }
        if (value instanceof Long) {
            return (Long) value;
        }
        try {
            return Long.valueOf(value.toString());
        } catch (NumberFormatException e) {
            return null;
        }
    }
    private Integer getIntegerValue(Map<String, Object> map, String key) {
        Object value = map.get(key);
        if (value == null) {
            return null;
        }
        if (value instanceof Integer) {
            return (Integer) value;
        }
        try {
            return Integer.valueOf(value.toString());
        } catch (NumberFormatException e) {
            return null;
        }
    }
    /**
     * 从Map中获取Date值
     *
     * @param map Map对象
     * @param key 键
     * @return Date值
     */
    private Date getDateValue(Map<String, Object> map, String key) {
        Object value = map.get(key);
        if (value == null) {
            return null;
        }
        if (value instanceof Date) {
            return (Date) value;
        }
        // 如果是字符串,尝试解析
        if (value instanceof String) {
            try {
                return DateUtils.parseDate(value.toString());
            } catch (Exception e) {
                return null;
            }
        }
        return null;
    }
    /**
     * 验证日期字符串格式是否有效
     *
     * @param dateStr 日期字符串
     * @param format 日期格式
     * @return 是否有效
     */
    private boolean isValidDateFormat(String dateStr, String format) {
        if (StringUtils.isEmpty(dateStr)) {
            return false;
        }
        try {
            SimpleDateFormat sdf = new SimpleDateFormat(format);
            sdf.setLenient(false);
            sdf.parse(dateStr);
            return true;
        } catch (Exception e) {
            return false;
        }
    }
    private void notifyTransferOrderByWechat(Long taskId,
                                             String serviceOrdID,
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/NotifyDispatchServiceImpl.java
@@ -46,6 +46,13 @@
    @Autowired
    private ISmsService smsService;
    @Autowired
    private IQyWechatService qyWechatService;
    @Autowired
    private ISysEmergencyTaskService sysEmergencyTaskService;
    /**
     * 获取指定通知类型启用的渠道列表
     */
@@ -108,6 +115,9 @@
                        break;
                    case NotifyChannelConfig.CHANNEL_SMS:
                        success = sendSmsMessage(notifyTask);
                        break;
                    case NotifyChannelConfig.CHANNEL_QY_WECHAT:
                        success = sendQyWechatMessage(notifyTask);
                        break;
                    default:
                        log.warn("不支持的渠道类型:{}", channel);
@@ -175,6 +185,7 @@
            sendLog.setChannel(channel);
            sendLog.setSendStatus(success ? NotifySendLog.SEND_STATUS_SUCCESS : NotifySendLog.SEND_STATUS_FAILED);
            sendLog.setSendTime(DateUtils.getNowDate());
            sendLog.setSendContent(notifyTask.getContent());
            sendLog.setResponseMsg(errorMsg);
            
            notifySendLogService.insertNotifySendLog(sendLog);
@@ -318,4 +329,47 @@
            return false;
        }
    }
    @Autowired
    private ISysConfigService sysConfigService;
    /**
     * 发送企业微信消息
     */
    @Override
    public boolean sendQyWechatMessage(NotifyTask notifyTask) {
        try {
            // 检查企业微信服务是否启用
            if (!qyWechatService.isEnabled()) {
                log.info("企业微信服务已关闭,跳过发送");
                return false;
            }
            Long taskId= notifyTask.getTaskId();
           SysTaskEmergency emergency = this.sysEmergencyTaskService.selectSysTaskEmergencyByTaskId(taskId);
           if(emergency==null){
               return false;
           }
           Long dispatchOrderId = emergency.getLegacyDispatchOrdId();
          String oldsiteUrl= sysConfigService.selectConfigByKey("oldsite.url");
          if(oldsiteUrl==null){
              oldsiteUrl="https://sys.966120.com.cn/m_DispatchOrder.gds?DispatchOrdID=";
          }
           String url=oldsiteUrl+dispatchOrderId;
            // 发送企业微信消息
            boolean success = qyWechatService.sendNotifyMessage(
                    notifyTask.getUserId(),
                    notifyTask.getTitle(),
                    notifyTask.getContent(),url
            );
            if (success) {
                log.info("企业微信消息发送成功,userId={}", notifyTask.getUserId());
            } else {
                log.warn("企业微信消息发送失败,userId={}", notifyTask.getUserId());
            }
            return success;
        } catch (Exception e) {
            log.error("企业微信消息发送异常,taskId={}, userId={}", notifyTask.getTaskId(), notifyTask.getUserId(), e);
            return false;
        }
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/NotifySendLogServiceImpl.java
@@ -49,6 +49,14 @@
    }
    /**
     * 根据任务ID和用户ID查询发送记录
     */
    @Override
    public NotifySendLog selectNotifySendLog(Long taskId, Long userId, String notifyType, String channel) {
        return notifySendLogMapper.selectNotifySendLog(taskId, userId, notifyType, channel);
    }
    /**
     * 检查是否已发送过通知(防重检查)
     * 
     * @param taskId 任务ID
@@ -114,18 +122,29 @@
    }
    /**
     * 更新发送状态为成功
     * 更新发送状态为成功(向后兼容)
     * 
     * @param id 记录ID
     * @param result 发送结果信息
     */
    @Override
    public void markSendSuccess(Long id, String result) {
        markSendSuccess(id, result, null);
    }
    /**
     * 更新发送状态为成功
     *
     * @param id 记录ID
     * @param result 发送结果信息
     * @param content 发送内容
     */
    @Override
    public void markSendSuccess(Long id, String result, String content) {
        if (id == null) {
            return;
        }
        try {
            notifySendLogMapper.updateSendStatus(id, NotifySendLog.SEND_STATUS_SUCCESS, result);
            notifySendLogMapper.updateSendStatus(id, NotifySendLog.SEND_STATUS_SUCCESS, result, content);
            log.debug("更新通知发送状态为成功,id={}", id);
        } catch (Exception e) {
            log.error("更新通知发送状态失败,id={}", id, e);
@@ -133,13 +152,24 @@
    }
    /**
     * 更新发送状态为失败
     * 更新发送状态为失败(向后兼容)
     * 
     * @param id 记录ID
     * @param errorMsg 错误信息
     */
    @Override
    public void markSendFailed(Long id, String errorMsg) {
        markSendFailed(id, errorMsg, null);
    }
    /**
     * 更新发送状态为失败
     *
     * @param id 记录ID
     * @param errorMsg 错误信息
     * @param content 发送内容
     */
    @Override
    public void markSendFailed(Long id, String errorMsg, String content) {
        if (id == null) {
            return;
        }
@@ -148,7 +178,7 @@
            if (errorMsg != null && errorMsg.length() > 500) {
                errorMsg = errorMsg.substring(0, 500);
            }
            notifySendLogMapper.updateSendStatus(id, NotifySendLog.SEND_STATUS_FAILED, errorMsg);
            notifySendLogMapper.updateSendStatus(id, NotifySendLog.SEND_STATUS_FAILED, errorMsg, content);
            log.debug("更新通知发送状态为失败,id={}, error={}", id, errorMsg);
        } catch (Exception e) {
            log.error("更新通知发送状态失败,id={}", id, e);
@@ -207,7 +237,7 @@
     * @param maxRetryCount 最大重试次数
     * @return 失败记录列表
     */
    @Override
    public List<NotifySendLog> selectFailedNotifySendLogs(Integer maxRetryCount) {
        return notifySendLogMapper.selectFailedNotifySendLogs(maxRetryCount);
    }
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/QyWechatAccessTokenServiceImpl.java
New file
@@ -0,0 +1,285 @@
package com.ruoyi.system.service.impl;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.service.IQyWechatAccessTokenService;
import com.ruoyi.system.service.ISysConfigService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
 * 企业微信AccessToken服务实现类
 *
 * @author ruoyi
 * @date 2025-12-11
 */
@Service
public class QyWechatAccessTokenServiceImpl implements IQyWechatAccessTokenService {
    private static final Logger log = LoggerFactory.getLogger(QyWechatAccessTokenServiceImpl.class);
    @Autowired
    private ISysConfigService configService;
    /**
     * 企业微信获取access_token的URL
     */
    private static final String GET_ACCESS_TOKEN_URL = "https://qyapi.weixin.qq.com/cgi-bin/gettoken";
    /**
     * 获取企业微信应用的AccessToken
     *
     * @param corpId 企业ID
     * @param corpSecret 应用密钥
     * @return AccessToken
     */
    @Override
    public String getAppAccessToken(String corpId, String corpSecret) {
        try {
            // 参数校验
            if (StringUtils.isEmpty(corpId) || StringUtils.isEmpty(corpSecret)) {
                log.warn("企业微信配置参数不完整,corpId或corpSecret为空");
                return null;
            }
            // 检查服务是否启用
            if (!isEnabled()) {
                log.info("企业微信服务已禁用,无法获取AccessToken");
                return null;
            }
            // 构建配置键名
            String tokenKey = "qy_wechat.access_token." + corpId;
            String expiresKey = "qy_wechat.access_token_expires." + corpId;
            // 从配置中获取Token和过期时间
            String accessToken = configService.selectConfigByKey(tokenKey);
            String expiresStr = configService.selectConfigByKey(expiresKey);
            // 检查Token是否存在且未过期
            if (StringUtils.isNotEmpty(accessToken) && StringUtils.isNotEmpty(expiresStr)) {
                try {
                    long expiresTime = Long.parseLong(expiresStr);
                    long currentTime = System.currentTimeMillis();
                    // 预留60秒安全边界,避免临界点过期
                    if (currentTime < expiresTime - 60000) {
                        log.debug("使用缓存的企业微信AccessToken,剩余有效时间: {}秒",
                            (expiresTime - currentTime) / 1000);
                        return accessToken;
                    } else {
                        log.info("企业微信AccessToken已过期或即将过期,需要刷新");
                    }
                } catch (NumberFormatException e) {
                    log.warn("解析企业微信AccessToken过期时间失败: {}", expiresStr);
                }
            }
            // Token不存在或已过期,刷新Token
            return refreshAppAccessToken(corpId, corpSecret);
        } catch (Exception e) {
            log.error("获取企业微信AccessToken失败", e);
            return null;
        }
    }
    /**
     * 刷新企业微信应用的AccessToken
     *
     * @param corpId 企业ID
     * @param corpSecret 应用密钥
     * @return 新的AccessToken
     */
    @Override
    public String refreshAppAccessToken(String corpId, String corpSecret) {
        try {
            log.info("开始刷新企业微信AccessToken");
            // 构建请求URL
            String url = GET_ACCESS_TOKEN_URL + "?corpid=" + corpId + "&corpsecret=" + corpSecret;
            // 发送HTTP请求获取Token
            String response = sendHttpGetRequest(url);
            if (StringUtils.isEmpty(response)) {
                log.error("获取企业微信AccessToken失败,响应为空");
                return null;
            }
            // 解析响应
            QyWechatTokenResponse tokenResponse = parseTokenResponse(response);
            if (tokenResponse == null || StringUtils.isEmpty(tokenResponse.getAccessToken())) {
                log.error("解析企业微信AccessToken响应失败: {}", response);
                return null;
            }
            // 计算过期时间(当前时间 + 有效期 - 60秒安全边界)
            long expiresTime = System.currentTimeMillis() + (tokenResponse.getExpiresIn() * 1000L) - 60000L;
            // 构建配置键名
            String tokenKey = "qy_wechat.access_token." + corpId;
            String expiresKey = "qy_wechat.access_token_expires." + corpId;
            // 保存到系统配置表
            configService.updateConfigValue(tokenKey, tokenResponse.getAccessToken());
            configService.updateConfigValue(expiresKey, String.valueOf(expiresTime));
            log.info("企业微信AccessToken刷新成功,有效期: {}秒", tokenResponse.getExpiresIn());
            return tokenResponse.getAccessToken();
        } catch (Exception e) {
            log.error("刷新企业微信AccessToken失败", e);
            return null;
        }
    }
    /**
     * 检查企业微信服务是否启用
     *
     * @return true-启用,false-禁用
     */
    @Override
    public boolean isEnabled() {
        try {
            String enabled = configService.selectConfigByKey("qy_wechat.enable");
            return !"false".equals(enabled); // 默认启用
        } catch (Exception e) {
            log.warn("获取企业微信服务启用状态失败,使用默认值(true)", e);
            return true;
        }
    }
    /**
     * 发送HTTP GET请求
     *
     * @param url 请求URL
     * @return 响应内容
     */
    private String sendHttpGetRequest(String url) {
        try {
            java.net.HttpURLConnection conn = (java.net.HttpURLConnection) new java.net.URL(url).openConnection();
            conn.setRequestMethod("GET");
            conn.setConnectTimeout(5000);
            conn.setReadTimeout(5000);
            int responseCode = conn.getResponseCode();
            if (responseCode == 200) {
                java.io.BufferedReader reader = new java.io.BufferedReader(
                    new java.io.InputStreamReader(conn.getInputStream(), "UTF-8"));
                StringBuilder response = new StringBuilder();
                String line;
                while ((line = reader.readLine()) != null) {
                    response.append(line);
                }
                reader.close();
                return response.toString();
            } else {
                log.error("HTTP请求失败,响应码: {}", responseCode);
                return null;
            }
        } catch (Exception e) {
            log.error("发送HTTP请求失败", e);
            return null;
        }
    }
    /**
     * 解析企业微信Token响应
     *
     * @param response 响应JSON
     * @return Token响应对象
     */
    private QyWechatTokenResponse parseTokenResponse(String response) {
        try {
            // 简单JSON解析(实际项目中建议使用Jackson或Gson)
            // 示例响应: {"errcode":0,"errmsg":"ok","access_token":"xxxxxx","expires_in":7200}
            // 移除花括号
            String content = response.substring(1, response.length() - 1);
            // 按逗号分割
            String[] pairs = content.split(",");
            QyWechatTokenResponse tokenResponse = new QyWechatTokenResponse();
            for (String pair : pairs) {
                String[] keyValue = pair.split(":");
                if (keyValue.length == 2) {
                    String key = keyValue[0].trim().replaceAll("\"", "");
                    String value = keyValue[1].trim().replaceAll("\"", "");
                    switch (key) {
                        case "errcode":
                            tokenResponse.setErrcode(Integer.parseInt(value));
                            break;
                        case "errmsg":
                            tokenResponse.setErrmsg(value);
                            break;
                        case "access_token":
                            tokenResponse.setAccessToken(value);
                            break;
                        case "expires_in":
                            tokenResponse.setExpiresIn(Integer.parseInt(value));
                            break;
                    }
                }
            }
            // 检查是否有错误
            if (tokenResponse.getErrcode() != 0) {
                log.error("获取企业微信AccessToken失败,错误码: {}, 错误信息: {}",
                    tokenResponse.getErrcode(), tokenResponse.getErrmsg());
                return null;
            }
            return tokenResponse;
        } catch (Exception e) {
            log.error("解析企业微信Token响应失败: {}", response, e);
            return null;
        }
    }
    /**
     * 企业微信Token响应内部类
     */
    private static class QyWechatTokenResponse {
        private int errcode;
        private String errmsg;
        private String accessToken;
        private int expiresIn;
        // Getters and Setters
        public int getErrcode() {
            return errcode;
        }
        public void setErrcode(int errcode) {
            this.errcode = errcode;
        }
        public String getErrmsg() {
            return errmsg;
        }
        public void setErrmsg(String errmsg) {
            this.errmsg = errmsg;
        }
        public String getAccessToken() {
            return accessToken;
        }
        public void setAccessToken(String accessToken) {
            this.accessToken = accessToken;
        }
        public int getExpiresIn() {
            return expiresIn;
        }
        public void setExpiresIn(int expiresIn) {
            this.expiresIn = expiresIn;
        }
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/QyWechatServiceImpl.java
New file
@@ -0,0 +1,431 @@
package com.ruoyi.system.service.impl;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.domain.QyWechatArticle;
import com.ruoyi.system.mapper.SysUserMapper;
import com.ruoyi.system.service.IQyWechatAccessTokenService;
import com.ruoyi.system.service.IQyWechatService;
import com.ruoyi.system.service.ISysConfigService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
 * 企业微信服务实现类
 *
 * @author ruoyi
 * @date 2025-12-11
 */
@Service
public class QyWechatServiceImpl implements IQyWechatService {
    private static final Logger log = LoggerFactory.getLogger(QyWechatServiceImpl.class);
    @Autowired
    private IQyWechatAccessTokenService qyWechatAccessTokenService;
    @Autowired
    private ISysConfigService configService;
    @Autowired
    private SysUserMapper userMapper;
    /**
     * 发送企业微信消息
     */
    @Override
    public boolean sendNotifyMessage(Long userId, String title, String content,String notifyUrl) {
        try {
            // 检查服务是否启用
            if (!isEnabled()) {
                log.info("企业微信服务未启用,跳过消息发送");
                return false;
            }
            // 获取用户的企业微信ID
            String qyUserId = getQyUserIdByUserId(userId);
            if (StringUtils.isEmpty(qyUserId)) {
                log.warn("用户{}未绑定企业微信ID,无法发送消息", userId);
                return false;
            }
            // 发送文本消息
            return sendTextMessage(qyUserId, title, content, notifyUrl);
        } catch (Exception e) {
            log.error("企业微信消息发送异常,userId={}", userId, e);
            return false;
        }
    }
    /**
     * 发送企业微信文本消息
     */
    @Override
    public boolean sendTextMessage(String qyUserId, String title, String content, String notifyUrl) {
        try {
            // 检查服务是否启用
            if (!isEnabled()) {
                log.info("企业微信服务未启用,跳过消息发送");
                return false;
            }
            // 获取企业微信配置
            String corpId = configService.selectConfigByKey("qy_wechat.corp_id");
            String corpSecret = configService.selectConfigByKey("qy_wechat.corp_secret");
            if (StringUtils.isEmpty(corpId) || StringUtils.isEmpty(corpSecret)) {
                log.error("企业微信配置不完整,缺少corpId或corpSecret");
                return false;
            }
            // 获取AccessToken
            String accessToken = qyWechatAccessTokenService.getAppAccessToken(corpId, corpSecret);
            if (StringUtils.isEmpty(accessToken)) {
                log.error("获取企业微信AccessToken失败");
                return false;
            }
            // 构造请求URL
            String url = "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=" + accessToken;
            // 构造文章对象
            QyWechatArticle article = new QyWechatArticle();
            article.setTitle(title);
            article.setDescription(content);
            article.setUrl(notifyUrl);
            // 设置默认图片URL,您可以根据需要修改
            // 构造请求参数
            Map<String, Object> params = new HashMap<>();
            params.put("touser", qyUserId);
            params.put("msgtype", "news");
            params.put("agentid", Integer.parseInt(configService.selectConfigByKey("qy_wechat.agent_id")));
            // 构造文章列表
            List<QyWechatArticle> articles = new ArrayList<>();
            articles.add(article);
            params.put("news", Collections.singletonMap("articles", articles));
            // 发送HTTP POST请求
            String response = sendHttpPostRequest(url, params);
            if (StringUtils.isEmpty(response)) {
                log.error("发送企业微信消息失败,响应为空");
                return false;
            }
            // 解析响应结果
            QyWechatResponse result = parseResponse(response);
            if (result != null && result.getErrcode() == 0) {
                log.info("企业微信消息发送成功,用户ID: {}", qyUserId);
                return true;
            } else {
                log.error("企业微信消息发送失败,错误码: {}, 错误信息: {}",
                        result != null ? result.getErrcode() : "unknown",
                        result != null ? result.getErrmsg() : response);
                return false;
            }
        } catch (Exception e) {
            log.error("企业微信文本消息发送异常,qyUserId={}", qyUserId, e);
            return false;
        }
    }
    /**
     * 获取用户的企业微信ID
     */
    @Override
    public String getQyUserIdByUserId(Long userId) {
        try {
            if (userId == null) {
                log.warn("用户ID不能为空");
                return null;
            }
            // 查询用户信息
            SysUser user = userMapper.selectUserById(userId);
            if (user == null) {
                log.warn("未找到用户,userId={}", userId);
                return null;
            }
            // 返回企业微信用户ID
            return user.getQyWechatUserId();
        } catch (Exception e) {
            log.error("获取用户企业微信ID异常,userId={}", userId, e);
            return null;
        }
    }
    /**
     * 检查企业微信服务是否启用
     */
    @Override
    public boolean isEnabled() {
        try {
            String enabled = configService.selectConfigByKey("qy_wechat.enable");
            return "true".equals(enabled);
        } catch (Exception e) {
            log.warn("获取企业微信服务启用状态失败,使用默认值(false)", e);
            return false;
        }
    }
    /**
     * 发送HTTP POST请求
     *
     * @param url    请求URL
     * @param params 请求参数
     * @return 响应内容
     */
    private String sendHttpPostRequest(String url, Map<String, Object> params) {
        try {
            // 将参数转换为JSON字符串
            String jsonParams = toJsonString(params);
            java.net.HttpURLConnection conn = (java.net.HttpURLConnection) new java.net.URL(url).openConnection();
            conn.setRequestMethod("POST");
            conn.setConnectTimeout(5000);
            conn.setReadTimeout(5000);
            conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
            conn.setDoOutput(true);
            // 发送请求数据
            java.io.OutputStream os = conn.getOutputStream();
            os.write(jsonParams.getBytes("UTF-8"));
            os.flush();
            os.close();
            int responseCode = conn.getResponseCode();
            if (responseCode == 200) {
                java.io.BufferedReader reader = new java.io.BufferedReader(
                        new java.io.InputStreamReader(conn.getInputStream(), "UTF-8"));
                StringBuilder response = new StringBuilder();
                String line;
                while ((line = reader.readLine()) != null) {
                    response.append(line);
                }
                reader.close();
                return response.toString();
            } else {
                log.error("HTTP请求失败,响应码: {}", responseCode);
                return null;
            }
        } catch (Exception e) {
            log.error("发送HTTP POST请求失败", e);
            return null;
        }
    }
    /**
     * 简单的JSON序列化方法
     *
     * @param map 要序列化的Map
     * @return JSON字符串
     */
    private String toJsonString(Map<String, Object> map) {
        StringBuilder json = new StringBuilder("{");
        boolean first = true;
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            if (!first) {
                json.append(",");
            }
            json.append("\"").append(entry.getKey()).append("\":");
            Object value = entry.getValue();
            if (value instanceof String) {
                json.append("\"").append(value).append("\"");
            } else if (value instanceof Map) {
                // 处理嵌套Map
                json.append(toJsonString((Map<String, Object>) value));
            } else if (value instanceof List) {
                // 处理列表
                json.append(toJsonList((List<Object>) value));
            } else {
                json.append(value);
            }
            first = false;
        }
        json.append("}");
        return json.toString();
    }
    /**
     * 序列化列表为JSON
     *
     * @param list 列表
     * @return JSON字符串
     */
    private String toJsonList(List<Object> list) {
        StringBuilder json = new StringBuilder("[");
        boolean first = true;
        for (Object item : list) {
            if (!first) {
                json.append(",");
            }
            if (item instanceof String) {
                json.append("\"").append(item).append("\"");
            } else if (item instanceof Map) {
                json.append(toJsonString((Map<String, Object>) item));
            } else if (item instanceof QyWechatArticle) {
                json.append(toJsonArticle((QyWechatArticle) item));
            } else {
                json.append(item);
            }
            first = false;
        }
        json.append("]");
        return json.toString();
    }
    /**
     * 序列化文章对象为JSON
     *
     * @param article 文章对象
     * @return JSON字符串
     */
    private String toJsonArticle(QyWechatArticle article) {
        StringBuilder json = new StringBuilder("{");
        boolean first = true;
        // 添加非空字段
        if (article.getTitle() != null) {
            if (!first) json.append(",");
            json.append("\"title\":\"").append(escapeJsonString(article.getTitle())).append("\"");
            first = false;
        }
        if (article.getDescription() != null) {
            if (!first) json.append(",");
            json.append("\"description\":\"").append(escapeJsonString(article.getDescription())).append("\"");
            first = false;
        }
        if (article.getUrl() != null) {
            if (!first) json.append(",");
            json.append("\"url\":\"").append(escapeJsonString(article.getUrl())).append("\"");
            first = false;
        }
        if (article.getPicurl() != null) {
            if (!first) json.append(",");
            json.append("\"picurl\":\"").append(escapeJsonString(article.getPicurl())).append("\"");
            first = false;
        }
        if (article.getAppid() != null) {
            if (!first) json.append(",");
            json.append("\"appid\":\"").append(escapeJsonString(article.getAppid())).append("\"");
            first = false;
        }
        if (article.getPagepath() != null) {
            if (!first) json.append(",");
            json.append("\"pagepath\":\"").append(escapeJsonString(article.getPagepath())).append("\"");
            first = false;
        }
        json.append("}");
        return json.toString();
    }
    /**
     * 转义JSON字符串中的特殊字符
     *
     * @param str 原始字符串
     * @return 转义后的字符串
     */
    private String escapeJsonString(String str) {
        if (str == null) return "";
        return str.replace("\\", "\\\\")
                 .replace("\"", "\\\"")
                 .replace("\n", "\\n")
                 .replace("\r", "\\r")
                 .replace("\t", "\\t");
    }
    /**
     * 解析企业微信响应
     *
     * @param response 响应JSON
     * @return 响应对象
     */
    private QyWechatResponse parseResponse(String response) {
        try {
            // 使用简单JSON解析
            QyWechatResponse result = new QyWechatResponse();
            // 移除首尾花括号
            String content = response.trim();
            if (content.startsWith("{")) {
                content = content.substring(1);
            }
            if (content.endsWith("}")) {
                content = content.substring(0, content.length() - 1);
            }
            // 按逗号分割键值对
            String[] pairs = content.split(",");
            for (String pair : pairs) {
                String[] keyValue = pair.split(":", 2); // 只分割第一个冒号
                if (keyValue.length == 2) {
                    String key = keyValue[0].trim().replaceAll("\"", "");
                    String value = keyValue[1].trim().replaceAll("\"", "");
                    switch (key) {
                        case "errcode":
                            result.setErrcode(Integer.parseInt(value));
                            break;
                        case "errmsg":
                            result.setErrmsg(value);
                            break;
                    }
                }
            }
            return result;
        } catch (Exception e) {
            log.error("解析企业微信响应失败: {}", response, e);
            return null;
        }
    }
    /**
     * 企业微信响应内部类
     */
    private static class QyWechatResponse {
        private int errcode;
        private String errmsg;
        // Getters and Setters
        public int getErrcode() {
            return errcode;
        }
        public void setErrcode(int errcode) {
            this.errcode = errcode;
        }
        public String getErrmsg() {
            return errmsg;
        }
        public void setErrmsg(String errmsg) {
            this.errmsg = errmsg;
        }
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysConfigServiceImpl.java
@@ -220,6 +220,47 @@
    }
    /**
     * 更新配置值
     *
     * @param configKey 参数键名
     * @param configValue 参数值
     * @return 结果
     */
    @Override
    public int updateConfigValue(String configKey, String configValue) {
        // 先查询是否存在该配置项
        SysConfig config = new SysConfig();
        config.setConfigKey(configKey);
        SysConfig existingConfig = configMapper.selectConfig(config);
        if (existingConfig != null) {
            // 如果存在,更新配置值
            existingConfig.setConfigValue(configValue);
            existingConfig.setUpdateTime(new java.util.Date());
            int result = configMapper.updateConfig(existingConfig);
            // 更新缓存
            if (result > 0) {
                redisCache.setCacheObject(getCacheKey(configKey), configValue);
            }
            return result;
        } else {
            // 如果不存在,创建新的配置项
            SysConfig newConfig = new SysConfig();
            newConfig.setConfigKey(configKey);
            newConfig.setConfigValue(configValue);
            newConfig.setConfigName("企业微信配置");
            newConfig.setConfigType("N"); // 非系统内置
            newConfig.setCreateBy("system");
            int result = configMapper.insertConfig(newConfig);
            // 更新缓存
            if (result > 0) {
                redisCache.setCacheObject(getCacheKey(configKey), configValue);
            }
            return result;
        }
    }
    /**
     * 设置cache key
     * 
     * @param configKey 参数键
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysEmergencyTaskServiceImpl.java
@@ -143,6 +143,21 @@
        emergencyInfo.setCreateBy(createUserName);
        emergencyInfo.setUpdateBy(createUserName);
        if(createVO.getLegacyDispatchNsTime() != null){
            emergencyInfo.setLegacyDispatchNsTime(createVO.getLegacyDispatchNsTime());
        }
        if(createVO.getLegacyDispatchOrdClass() != null){
            emergencyInfo.setLegacyDispatchOrdClass(createVO.getLegacyDispatchOrdClass());
        }
        if(createVO.getLegacyDispatchOrdNo() != null){
            emergencyInfo.setLegacyDispatchOrdNo(createVO.getLegacyDispatchOrdNo());
        }
        if(createVO.getLegacyServiceNsTime() != null){
            emergencyInfo.setLegacyServiceNsTime(createVO.getLegacyServiceNsTime());
        }
        if(createVO.getLegacyServiceOrdClass() != null){
            emergencyInfo.setLegacyServiceOrdClass(createVO.getLegacyServiceOrdClass());
        }
        sysTaskEmergencyMapper.insertSysTaskEmergency(emergencyInfo);
    }
@@ -276,7 +291,28 @@
        oldEmergency.setUpdateTime(DateUtils.getNowDate());
        oldEmergency.setUpdateBy(userName);
        if( updateVO.getLegacyDispatchNsTime() != null) {
            oldEmergency.setLegacyDispatchNsTime(updateVO.getLegacyDispatchNsTime());
        }
        if( updateVO.getLegacyServiceNsTime() != null){
            oldEmergency.setLegacyServiceNsTime(updateVO.getLegacyServiceNsTime());
        }
        if( updateVO.getLegacyDispatchOrdClass() != null){
            oldEmergency.setLegacyDispatchOrdClass(updateVO.getLegacyDispatchOrdClass());
        }
        if( updateVO.getLegacyDispatchOrdNo() != null){
            oldEmergency.setLegacyDispatchOrdNo(updateVO.getLegacyDispatchOrdNo());
        }
        if( updateVO.getLegacyServiceOrdClass() != null){
            oldEmergency.setLegacyServiceOrdClass(updateVO.getLegacyServiceOrdClass());
        }
        sysTaskEmergencyMapper.updateSysTaskEmergency(oldEmergency);
    }
    @Override
    public SysTaskEmergency selectSysTaskEmergencyByTaskId(Long taskId) {
        return sysTaskEmergencyMapper.selectSysTaskEmergencyByTaskId(taskId);
    }
    @Override
@@ -420,6 +456,22 @@
        // 系统字段
        existingInfo.setUpdateTime(DateUtils.getNowDate());
        existingInfo.setUpdateBy(userName);
        if(createVO.getLegacyDispatchNsTime() != null){
            existingInfo.setLegacyDispatchNsTime(createVO.getLegacyDispatchNsTime());
        }
        if(createVO.getLegacyDispatchOrdNo() != null){
            existingInfo.setLegacyDispatchOrdNo(createVO.getLegacyDispatchOrdNo());
        }
        if(createVO.getLegacyServiceNsTime() != null){
            existingInfo.setLegacyServiceNsTime(createVO.getLegacyServiceNsTime());
        }
        if(createVO.getLegacyDispatchOrdClass() != null){
            existingInfo.setLegacyDispatchOrdClass(createVO.getLegacyDispatchOrdClass());
        }
        if(createVO.getLegacyServiceOrdClass() != null){
            existingInfo.setLegacyServiceOrdClass(createVO.getLegacyServiceOrdClass());
        }
        
        // 执行更新
        sysTaskEmergencyMapper.updateSysTaskEmergency(existingInfo);
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTaskServiceImpl.java
@@ -10,6 +10,7 @@
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.system.domain.vo.*;
import com.ruoyi.system.event.TaskDispatchSyncEvent;
import com.ruoyi.system.mapper.*;
import com.ruoyi.system.service.*;
import com.ruoyi.system.utils.TaskCodeGenerator;
@@ -100,6 +101,16 @@
    @Autowired
    private ISysTaskVehicleService sysTaskVehicleService;
    @Override
    public Boolean dispatchSyncEvent(Long taskId) {
        SysTask task= sysTaskMapper.selectSysTaskByTaskId(taskId);
        SysUser user= sysUserMapper.selectUserById(task.getCreatorId());
        Integer oaUser=user.getOaUserId();
        SysTaskEmergency emergency = sysTaskEmergencyMapper.selectSysTaskEmergencyByTaskId(taskId);
        eventPublisher.publishEvent(new TaskDispatchSyncEvent(this, taskId, task.getTaskCode(),emergency.getLegacyServiceOrdId(), emergency.getLegacyDispatchOrdId(),oaUser));
        return true;
    }
    /**
     * 查询任务管理
     * 
@@ -132,7 +143,68 @@
     */
    @Override
    public List<SysTask> selectSysTaskList(TaskQueryVO queryVO) {
        return sysTaskMapper.selectSysTaskList(queryVO);
        List<SysTask> tasks= sysTaskMapper.selectSysTaskList(queryVO);
        tasks.forEach(task -> {
            if ("EMERGENCY_TRANSFER".equals(task.getTaskType())) {
                SysTaskEmergency emergencyInfo = sysTaskEmergencyMapper.selectSysTaskEmergencyByTaskId(task.getTaskId());
                task.setEmergencyInfo(emergencyInfo);
            }
        });
        return tasks;
    }
    /**
     * 根据任务编号、调度单编号或服务单编号查询任务列表
     *
     * @param queryVO 任务查询对象
     * @param taskCode 任务编号
     * @return 任务管理集合
     */
    @Override
    public List<SysTask> selectSysTaskListByMultiCode(TaskQueryVO queryVO, String taskCode) {
        // Create a new query object without the taskCode filter
        TaskQueryVO newQuery = new TaskQueryVO();
        // Copy all properties except taskCode
        try {
            org.springframework.beans.BeanUtils.copyProperties(queryVO, newQuery, "taskCode");
        } catch (Exception e) {
            // If copy fails, manually copy the important fields
            newQuery.setTaskType(queryVO.getTaskType());
            newQuery.setTaskStatus(queryVO.getTaskStatus());
            newQuery.setVehicleNo(queryVO.getVehicleNo());
            newQuery.setCreatorId(queryVO.getCreatorId());
            newQuery.setAssigneeId(queryVO.getAssigneeId());
            newQuery.setDeptId(queryVO.getDeptId());
            newQuery.setDeptIds(queryVO.getDeptIds());
            newQuery.setPlannedStartTimeBegin(queryVO.getPlannedStartTimeBegin());
            newQuery.setPlannedStartTimeEnd(queryVO.getPlannedStartTimeEnd());
            newQuery.setPlannedEndTimeBegin(queryVO.getPlannedEndTimeBegin());
            newQuery.setPlannedEndTimeEnd(queryVO.getPlannedEndTimeEnd());
            newQuery.setOverdue(queryVO.getOverdue());
        }
        // Get all tasks matching the other criteria
        List<SysTask> allTasks = sysTaskMapper.selectSysTaskList(newQuery);
        allTasks.stream().forEach(task -> {
            if ("EMERGENCY_TRANSFER".equals(task.getTaskType())) {
                SysTaskEmergency emergencyInfo = sysTaskEmergencyMapper.selectSysTaskEmergencyByTaskId(task.getTaskId());
                task.setEmergencyInfo(emergencyInfo);
            }
        });
        return allTasks.stream().filter(task -> {
            if (task.getTaskCode() != null && task.getTaskCode().contains(taskCode)) {
                return true;
            }
            if ("EMERGENCY_TRANSFER".equals(task.getTaskType()) && task.getEmergencyInfo() != null) {
                String dispatchCode = task.getEmergencyInfo().getDispatchCode();
                String serviceCode = task.getEmergencyInfo().getServiceCode();
                return (dispatchCode != null && dispatchCode.contains(taskCode)) ||
                        (serviceCode != null && serviceCode.contains(taskCode));
            }
            return false;
        }).collect(Collectors.toList());
    }
    /**
@@ -215,10 +287,7 @@
            ));
        }
        
        // 发布任务分配事件
        if (result > 0 && createVO.getAssignees() != null && !createVO.getAssignees().isEmpty()) {
            this.sendTaskAssigneeEvent(createVO,task,SecurityUtils.getUserId(),SecurityUtils.getUsername());
        }
        
        // 异步同步急救转运任务到旧系统
        if (result > 0 && "EMERGENCY_TRANSFER".equals(createVO.getTaskType()) && legacySystemSyncService != null) {
@@ -341,10 +410,7 @@
            ));
        }
        
        // 发布任务分配事件
        if (result > 0 && createVO.getAssignees() != null && !createVO.getAssignees().isEmpty()) {
            this.sendTaskAssigneeEvent(createVO,task,userId,userName);
        }
        
        
@@ -491,11 +557,12 @@
                needResync = true;
            }
        }
        Long dispatchOrderId=0L;
        // 更新急救转运扩展信息(检测地址和成交价变更)
        if (result > 0 && "EMERGENCY_TRANSFER".equals(oldTask.getTaskType())) {
            SysTaskEmergency oldEmergency = sysTaskEmergencyMapper.selectSysTaskEmergencyByTaskId(updateVO.getTaskId());
            sysEmergencyTaskService.updateEmergencyInfoFromUpdateVO(oldEmergency, updateVO, userName);
          dispatchOrderId=  oldEmergency.getLegacyDispatchOrdId();
            sysEmergencyTaskService.markNeedResyncIfNecessary(updateVO.getTaskId(), oldTask, updateVO, updateFromLegacy);
        }
        
@@ -518,7 +585,7 @@
                userId, userName);
        }
        if(result > 0 && oldTask.getTaskStatus().equals(TaskStatus.PENDING.getCode()) && updateVO.getAssignees() != null && !updateVO.getAssignees().isEmpty()){
        if(result > 0 && oldTask.getTaskStatus().equals(TaskStatus.PENDING.getCode()) && updateVO.getAssignees() != null && !updateVO.getAssignees().isEmpty() && dispatchOrderId>0L){
            this.sendTaskAssigneeEvent(updateVO,oldTask,userId,userName);
        }
@@ -646,16 +713,14 @@
//            log.info("更新执行人员 ServiceOrdID:{},dispatchOrderId:{}",serviceOrderId,dispatchOrderId);
            sysTaskAssigneeService.updateTaskAssignees(taskId, updateVO.getAssignees(), userName);
        }
        Long dispatchOrderIdLong = 0L;
        // 更新急救转运扩展信息
        if (result > 0) {
            // 更新旧系统ID
            if (serviceOrderId != null) {
                taskEmergency.setLegacyServiceOrdId(Long.parseLong(serviceOrderId));
            }
            taskEmergency.setLegacyServiceOrdId(Long.parseLong(serviceOrderId));
            if (dispatchOrderId != null) {
                taskEmergency.setLegacyDispatchOrdId(Long.parseLong(dispatchOrderId));
                taskEmergency.setLegacyDispatchOrdId(Long.parseLong(dispatchOrderId));
                dispatchOrderIdLong = Long.parseLong(dispatchOrderId);
                taskEmergency.setLegacyDispatchOrdId(dispatchOrderIdLong);
                taskEmergency.setDispatchSyncStatus(2);
                taskEmergency.setDispatchSyncTime(new Date());
                taskEmergency.setDispatchSyncErrorMsg("旧系统同步过来");
@@ -666,16 +731,17 @@
            taskEmergency.setUpdateTime(DateUtils.getNowDate());
            Boolean hasEmergencyInfo = updateVO.getHospitalOut() != null || updateVO.getHospitalIn() != null || updateVO.getPatient() != null;
//            log.info("更新转运任务信息 serviceOrdID:{},dispatchOrderId:{} hasEmergencyInfo:{}",serviceOrderId,
//                    dispatchOrderId,hasEmergencyInfo);
            // 使用TaskCreateVO的字段来更新急救转运信息
            if (hasEmergencyInfo) {
                sysEmergencyTaskService.updateEmergencyInfoFromCreateVO(taskEmergency, updateVO, userName);
            }
           SysTaskEmergency emergency= sysEmergencyTaskService.selectSysTaskEmergencyByTaskId(taskId);
            dispatchOrderIdLong = emergency.getLegacyDispatchOrdId();
        }
        if(updateVO.getTaskStatus()!=null && updateVO.getTaskStatus().equals(TaskStatus.PENDING.getCode()) && updateVO.getAssignees()!=null && !updateVO.getAssignees().isEmpty()){
        if(updateVO.getTaskStatus()!=null && updateVO.getTaskStatus().equals(TaskStatus.PENDING.getCode()) && updateVO.getAssignees()!=null && !updateVO.getAssignees().isEmpty() && dispatchOrderIdLong>0L){
            this.sendTaskAssigneeEvent(updateVO,task,userId,userName);
        }
        
@@ -1026,7 +1092,13 @@
     */
    @Override
    public List<SysTask> selectMyTasks(Long userId) {
        return sysTaskMapper.selectMyTasks(userId);
        List<SysTask> list = sysTaskMapper.selectMyTasks(userId);
        list.stream().forEach(task -> {
            if(task.getTaskType().equals("EMERGENCY_TRANSFER")){
                task.setEmergencyInfo(sysTaskEmergencyMapper.selectSysTaskEmergencyByTaskId(task.getTaskId()));
            }
        });
        return list;
    }
    /**
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/UserSyncServiceImpl.java
@@ -227,6 +227,12 @@
        {
            existingUser.setCanViewAllConsult(dto.getCanViewAllConsult());
        }
        // 同步企业微信用户ID
        if (StringUtils.isNotEmpty(dto.getOaWeixinUserId()))
        {
            existingUser.setQyWechatUserId(dto.getOaWeixinUserId());
            existingUser.setQyWechatUpdateTime(new Date());
        }
        sysUserMapper.updateUser(existingUser);
    }
@@ -248,6 +254,13 @@
            newUser.setCanViewAllConsult(dto.getCanViewAllConsult());
        }
        
        // 设置企业微信用户ID
        if (StringUtils.isNotEmpty(dto.getOaWeixinUserId()))
        {
            newUser.setQyWechatUserId(dto.getOaWeixinUserId());
            newUser.setQyWechatUpdateTime(new Date());
        }
        if (deptId != null)
        {
            newUser.setDeptId(deptId);
ruoyi-system/src/main/resources/mapper/system/LegacyTransferSyncMapper.xml
@@ -44,6 +44,10 @@
        <result property="ServiceOrdTraDistance" column="ServiceOrdTraDistance" />
        <result property="ServiceOrdApptDate" column="ServiceOrdApptDate" />
        <result property="DispatchOrdState" column="DispatchOrdState" />
        <result property="DispatchOrdNo" column="DispatchOrdNo" />
        <result property="DispatchOrdClass" column="DispatchOrdClass" />
        <result property="DispatchOrd_NS_Time" column="DispatchOrd_NS_Time" />
    </resultMap>
    
    <!-- 执行人结果映射 -->
@@ -93,7 +97,11 @@
            b.DispatchOrdCarID,
            a.ServiceOrdPtServices,
            a.ServiceOrdPtInServices,
            a.ServiceOrdPtName
            a.ServiceOrdPtName,
            b.DispatchOrdState,
            b.DispatchOrdNo,
            b.DispatchOrdClass,
            a.ServiceOrdClass
        FROM ServiceOrder as a 
        left JOIN DispatchOrd b on a.ServiceOrdID = b.ServiceOrdIDDt
        WHERE a.ServiceOrdState &lt;= 3
@@ -111,6 +119,7 @@
            a.ServiceOrdApptDate,
            a.ServiceOrdUserID,
            a.ServiceOrd_NS_ID,
            a.ServiceOrd_NS_Time,
            a.ServiceOrd_CC_ID,
            a.ServiceOrd_CC_Time,
            a.ServiceOrdAreaType,
@@ -140,13 +149,115 @@
            a.ServiceOrdPtServices,
            a.ServiceOrdPtInServices,
            a.ServiceOrdPtName,
            b.DispatchOrdState
            b.DispatchOrdState,
            b.DispatchOrdNo,
            b.DispatchOrdClass,
            a.ServiceOrdClass
        FROM ServiceOrder as a 
        left JOIN DispatchOrd b on a.ServiceOrdID = b.ServiceOrdIDDt
        WHERE a.ServiceOrdID = #{serviceOrdID}
        WHERE a.ServiceOrdID = #{serviceOrdID}
            AND (b.DispatchOrdID = #{dispatchOrdID} OR #{dispatchOrdID} IS NULL)
            AND a.ServiceOrdState &lt;=3
    </select>
    <select id="selectByServiceOrdId" resultMap="TransferOrderResult">
        SELECT
        a.ServiceOrdID,
        a.Old_ServiceOrdID_TXT,
        a.ServiceOrdTraVia,
        a.ServiceOrdNo,
        a.ServiceOrdApptDate,
        a.ServiceOrdUserID,
        a.ServiceOrd_NS_ID,
        a.ServiceOrd_NS_Time,
        a.ServiceOrd_CC_ID,
        a.ServiceOrd_CC_Time,
        a.ServiceOrdAreaType,
        a.ServiceOrdType,
        a.ServiceOrdPtSex,
        a.ServiceOrdTraTxnPrice,
        a.ServiceOrdPtOutHospID,
        a.ServiceOrdPtServicesID,
        a.ServiceOrdPtInHospID,
        a.ServiceOrdPtInServicesID,
        a.ServiceOrdCoTies,
        a.ServiceOrdCoName,
        a.ServiceOrdTraDistance,
        a.ServiceOrdCoPhone,
        a.ServiceOrdClass,
        a.ServiceOrdTraStreet,
        a.ServiceOrdTraEnd,
        a.ServiceOrdPtCondition,
        b.DispatchOrd_NS_Time,
        a.ServiceOrdState,
        a.ServiceOrdPtIDCard,
        b.DispatchOrdTraStreet,
        b.DispatchOrdStartDate,
        b.DispatchOrdTraEnd,
        b.DispatchOrdID,
        b.DispatchOrdCarID,
        a.ServiceOrdPtServices,
        a.ServiceOrdPtInServices,
        a.ServiceOrdPtName,
        b.DispatchOrdState,
        b.DispatchOrdNo,
        b.DispatchOrdClass,
        a.ServiceOrdClass
        FROM ServiceOrder as a
        left JOIN DispatchOrd b on a.ServiceOrdID = b.ServiceOrdIDDt
        WHERE a.ServiceOrdID = #{serviceOrdID}
    </select>
    <select id="selectByDispatchId" resultMap="TransferOrderResult">
     SELECT
            a.ServiceOrdID,
            a.Old_ServiceOrdID_TXT,
            a.ServiceOrdTraVia,
            a.ServiceOrdNo,
            a.ServiceOrdApptDate,
            a.ServiceOrdUserID,
            a.ServiceOrd_NS_ID,
            a.ServiceOrd_NS_Time,
            a.ServiceOrd_CC_ID,
            a.ServiceOrd_CC_Time,
            a.ServiceOrdAreaType,
            a.ServiceOrdType,
            a.ServiceOrdPtSex,
            a.ServiceOrdTraTxnPrice,
            a.ServiceOrdPtOutHospID,
            a.ServiceOrdPtServicesID,
            a.ServiceOrdPtInHospID,
            a.ServiceOrdPtInServicesID,
            a.ServiceOrdCoTies,
            a.ServiceOrdCoName,
            a.ServiceOrdTraDistance,
            a.ServiceOrdCoPhone,
            a.ServiceOrdClass,
            a.ServiceOrdTraStreet,
            a.ServiceOrdTraEnd,
            a.ServiceOrdPtCondition,
            b.DispatchOrd_NS_Time,
            a.ServiceOrdState,
            a.ServiceOrdPtIDCard,
            b.DispatchOrdTraStreet,
            b.DispatchOrdStartDate,
            b.DispatchOrdTraEnd,
            b.DispatchOrdID,
            b.DispatchOrdCarID,
            a.ServiceOrdPtServices,
            a.ServiceOrdPtInServices,
            a.ServiceOrdPtName,
            b.DispatchOrdState,
            b.DispatchOrdNo,
            b.DispatchOrdClass,
            a.ServiceOrdClass
        FROM ServiceOrder as a
        left JOIN DispatchOrd b on a.ServiceOrdID = b.ServiceOrdIDDt
        WHERE b.DispatchOrdID = #{dispatchId}
     </select>
    
    <!-- 根据服务单ID查询病情信息 -->
    <select id="selectDiseaseIdsByServiceOrdID" resultType="String">
ruoyi-system/src/main/resources/mapper/system/NotifySendLogMapper.xml
@@ -13,6 +13,7 @@
        <result property="notifyType"   column="notify_type"  />
        <result property="channel"      column="channel"      />
        <result property="sendStatus"   column="send_status"  />
        <result property="sendContent"  column="send_content" />
        <result property="sendTime"     column="send_time"    />
        <result property="sendResult"   column="send_result"  />
        <result property="responseMsg"  column="response_msg" />
@@ -26,7 +27,7 @@
    <sql id="selectNotifySendLogVo">
        select id, notify_task_id, task_id, user_id, user_name, notify_type, channel, send_status, 
               send_time, send_result, response_msg, retry_count, create_time, create_by,
               send_content, send_time, send_result, response_msg, retry_count, create_time, create_by,
               update_time, update_by, remark
        from sys_notify_send_log
    </sql>
@@ -102,6 +103,7 @@
            <if test="notifyType != null and notifyType != ''">notify_type,</if>
            <if test="channel != null and channel != ''">channel,</if>
            <if test="sendStatus != null">send_status,</if>
            <if test="sendContent != null">send_content,</if>
            <if test="sendTime != null">send_time,</if>
            <if test="sendResult != null">send_result,</if>
            <if test="responseMsg != null">response_msg,</if>
@@ -120,6 +122,7 @@
            <if test="notifyType != null and notifyType != ''">#{notifyType},</if>
            <if test="channel != null and channel != ''">#{channel},</if>
            <if test="sendStatus != null">#{sendStatus},</if>
            <if test="sendContent != null">#{sendContent},</if>
            <if test="sendTime != null">#{sendTime},</if>
            <if test="sendResult != null">#{sendResult},</if>
            <if test="responseMsg != null">#{responseMsg},</if>
@@ -141,6 +144,7 @@
            <if test="notifyType != null and notifyType != ''">notify_type = #{notifyType},</if>
            <if test="channel != null and channel != ''">channel = #{channel},</if>
            <if test="sendStatus != null">send_status = #{sendStatus},</if>
            <if test="sendContent != null">send_content = #{sendContent},</if>
            <if test="sendTime != null">send_time = #{sendTime},</if>
            <if test="sendResult != null">send_result = #{sendResult},</if>
            <if test="retryCount != null">retry_count = #{retryCount},</if>
@@ -155,6 +159,7 @@
        update sys_notify_send_log
        set send_status = #{sendStatus},
            send_result = #{sendResult},
            send_content = #{sendContent},
            send_time = now(),
            update_time = now(),
            retry_count = retry_count + 1
ruoyi-system/src/main/resources/mapper/system/SysTaskEmergencyMapper.xml
@@ -46,6 +46,11 @@
        <result property="dispatchSyncErrorMsg"    column="dispatch_sync_error_msg" />
        <result property="needResync"              column="need_resync"             />
        <result property="legacyServiceOrdNo"      column="legacy_service_ord_no"   />
        <result property="legacyDispatchOrdNo"     column="legacy_dispatch_ord_no"  />
        <result property="legacyServiceNsTime"     column="legacy_service_ns_time"  />
        <result property="legacyDispatchNsTime"    column="legacy_dispatch_ns_time" />
        <result property="legacyDispatchOrdClass"  column="legacy_dispatch_ord_class" />
        <result property="legacyServiceOrdClass"   column="legacy_service_ord_class" />
        <result property="createTime"              column="create_time"             />
        <result property="updateTime"              column="update_time"             />
        <result property="createBy"                column="create_by"               />
@@ -60,7 +65,7 @@
               hospital_in_department_id, hospital_in_bed_number, hospital_in_address, hospital_in_longitude, 
               hospital_in_latitude, transfer_distance, transfer_price, passenger_contact, 
               passenger_phone, disease_ids, document_type_id, task_type_id, legacy_service_ord_id, legacy_dispatch_ord_id, 
               sync_status, sync_time, sync_error_msg, dispatch_sync_status, dispatch_sync_time, dispatch_sync_error_msg, need_resync, legacy_service_ord_no,
               sync_status, sync_time, sync_error_msg, dispatch_sync_status, dispatch_sync_time, dispatch_sync_error_msg, need_resync, legacy_service_ord_no, legacy_dispatch_ord_no, legacy_service_ns_time, legacy_dispatch_ns_time, legacy_dispatch_ord_class, legacy_service_ord_class,
               create_time, update_time, create_by, update_by
        from sys_task_emergency
    </sql>
@@ -118,6 +123,11 @@
            <if test="dispatchSyncErrorMsg != null">dispatch_sync_error_msg,</if>
            <if test="needResync != null">need_resync,</if>
            <if test="legacyServiceOrdNo != null">legacy_service_ord_no,</if>
            <if test="legacyDispatchOrdNo != null">legacy_dispatch_ord_no,</if>
            <if test="legacyServiceNsTime != null">legacy_service_ns_time,</if>
            <if test="legacyDispatchNsTime != null">legacy_dispatch_ns_time,</if>
            <if test="legacyDispatchOrdClass != null">legacy_dispatch_ord_class,</if>
            <if test="legacyServiceOrdClass != null">legacy_service_ord_class,</if>
            <if test="createTime != null">create_time,</if>
            <if test="updateTime != null">update_time,</if>
            <if test="createBy != null">create_by,</if>
@@ -164,6 +174,11 @@
            <if test="dispatchSyncErrorMsg != null">#{dispatchSyncErrorMsg},</if>
            <if test="needResync != null">#{needResync},</if>
            <if test="legacyServiceOrdNo != null">#{legacyServiceOrdNo},</if>
            <if test="legacyDispatchOrdNo != null">#{legacyDispatchOrdNo},</if>
            <if test="legacyServiceNsTime != null">#{legacyServiceNsTime},</if>
            <if test="legacyDispatchNsTime != null">#{legacyDispatchNsTime},</if>
            <if test="legacyDispatchOrdClass != null">#{legacyDispatchOrdClass},</if>
            <if test="legacyServiceOrdClass != null">#{legacyServiceOrdClass},</if>
            <if test="createTime != null">#{createTime},</if>
            <if test="updateTime != null">#{updateTime},</if>
            <if test="createBy != null">#{createBy},</if>
@@ -213,6 +228,11 @@
            <if test="dispatchSyncErrorMsg != null">dispatch_sync_error_msg = #{dispatchSyncErrorMsg},</if>
            <if test="needResync != null">need_resync = #{needResync},</if>
            <if test="legacyServiceOrdNo != null">legacy_service_ord_no = #{legacyServiceOrdNo},</if>
            <if test="legacyDispatchOrdNo != null">legacy_dispatch_ord_no = #{legacyDispatchOrdNo},</if>
            <if test="legacyServiceNsTime != null">legacy_service_ns_time = #{legacyServiceNsTime},</if>
            <if test="legacyDispatchNsTime != null">legacy_dispatch_ns_time = #{legacyDispatchNsTime},</if>
            <if test="legacyDispatchOrdClass != null">legacy_dispatch_ord_class = #{legacyDispatchOrdClass},</if>
            <if test="legacyServiceOrdClass != null">legacy_service_ord_class = #{legacyServiceOrdClass},</if>
            <if test="updateTime != null">update_time = #{updateTime},</if>
            <if test="updateBy != null">update_by = #{updateBy},</if>
        </trim>
ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml
@@ -28,6 +28,8 @@
        <result property="openId"       column="open_id"       />
        <result property="unionId"     column="union_id"     />
        <result property="wechatNickname" column="wechat_nickname" />
        <result property="qyWechatUserId" column="qy_wechat_user_id" />
        <result property="qyWechatUpdateTime" column="qy_wechat_update_time" />
        <result property="canViewAllConsult" column="can_view_all_consult" />
        <association property="dept"    javaType="SysDept"         resultMap="deptResult" />
        <collection  property="roles"   javaType="java.util.List"  resultMap="RoleResult" />
@@ -53,7 +55,7 @@
    </resultMap>
    
    <sql id="selectUserVo">
        select u.user_id, u.dept_id, u.user_name,u.oa_user_id, u.oa_order_class, u.nick_name, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.oa_user_id, u.create_by, u.create_time, u.remark,u.open_id,u.union_id,u.wechat_nickname,u.can_view_all_consult,
        select u.user_id, u.dept_id, u.user_name,u.oa_user_id, u.oa_order_class, u.nick_name, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.oa_user_id, u.create_by, u.create_time, u.remark,u.open_id,u.union_id,u.wechat_nickname,u.qy_wechat_user_id,u.qy_wechat_update_time,u.can_view_all_consult,
        d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.status as dept_status,
        r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.status as role_status
        from sys_user u
@@ -170,7 +172,8 @@
             <if test="status != null and status != ''">status,</if>
             <if test="oaUserId != null">oa_user_id,</if>
             <if test="oaOrderClass != null and oaOrderClass != ''">oa_order_class,</if>
             <if test="canViewAllConsult != null and canViewAllConsult != ''">can_view_all_consult,</if>
             <if test="qyWechatUserId != null and qyWechatUserId != ''">qy_wechat_user_id,</if>
             <if test="qyWechatUpdateTime != null">qy_wechat_update_time,</if>
             <if test="createBy != null and createBy != ''">create_by,</if>
             <if test="remark != null and remark != ''">remark,</if>
             create_time
@@ -188,6 +191,8 @@
             <if test="oaUserId != null">#{oaUserId},</if>
             <if test="oaOrderClass != null and oaOrderClass != ''">#{oaOrderClass},</if>
             <if test="canViewAllConsult != null and canViewAllConsult != ''">#{canViewAllConsult},</if>
             <if test="qyWechatUserId != null and qyWechatUserId != ''">#{qyWechatUserId},</if>
             <if test="qyWechatUpdateTime != null">#{qyWechatUpdateTime},</if>
             <if test="createBy != null and createBy != ''">#{createBy},</if>
             <if test="remark != null and remark != ''">#{remark},</if>
             sysdate()
@@ -208,6 +213,8 @@
             <if test="oaUserId != null">oa_user_id = #{oaUserId},</if>
             <if test="oaOrderClass != null">oa_order_class = #{oaOrderClass},</if>
             <if test="canViewAllConsult != null and canViewAllConsult != ''">can_view_all_consult = #{canViewAllConsult},</if>
             <if test="qyWechatUserId != null and qyWechatUserId != ''">qy_wechat_user_id = #{qyWechatUserId},</if>
             <if test="qyWechatUpdateTime != null">qy_wechat_update_time = #{qyWechatUpdateTime},</if>
             <if test="openId != null and openId != ''">open_id = #{openId},</if>
             <if test="unionId != null and unionId != ''">union_id = #{unionId},</if>
             <if test="loginIp != null and loginIp != ''">login_ip = #{loginIp},</if>
ruoyi-system/src/main/resources/mapper/system/UserSyncMapper.xml
@@ -6,6 +6,7 @@
    <resultMap type="UserSyncDTO" id="UserSyncResult">
        <result property="oaUserId" column="OA_UserID" />
        <result property="oaWeixinUserId" column="OA_weixinUserID" />
        <result property="userName" column="user_name" />
        <result property="nickName" column="nick_name" />
        <result property="departmentId" column="department_id" />
@@ -21,6 +22,7 @@
        <![CDATA[
        SELECT
            OA_User_ID AS OA_UserID,
            OA_weixinUserID AS OA_weixinUserID,
            OA_User AS user_name,
            OA_Name AS nick_name,
            OA_departmentID AS department_id,
ruoyi-ui/src/api/system/qywechat/index.js
New file
@@ -0,0 +1,43 @@
import request from '@/utils/request'
// 获取企业微信AccessToken
export function getToken() {
  return request({
    url: '/system/qywechat/test/token',
    method: 'get'
  })
}
// 刷新企业微信AccessToken
export function refreshToken() {
  return request({
    url: '/system/qywechat/test/refreshToken',
    method: 'post'
  })
}
// 发送企业微信通知消息
export function sendMessage(data) {
  return request({
    url: '/system/qywechat/test/sendMessage',
    method: 'post',
    params: data
  })
}
// 发送企业微信文本消息
export function sendTextMessage(data) {
  return request({
    url: '/system/qywechat/test/sendTextMessage',
    method: 'post',
    params: data
  })
}
// 检查企业微信服务状态
export function checkServiceStatus() {
  return request({
    url: '/system/qywechat/test/enabled',
    method: 'get'
  })
}
ruoyi-ui/src/api/system/qywechat/test.js
ruoyi-ui/src/views/system/notify/channelConfig.vue
@@ -132,6 +132,7 @@
            <el-option label="短信" value="SMS" />
            <el-option label="站内消息" value="SITE_MSG" />
            <el-option label="APP推送" value="APP_PUSH" />
            <el-option label="企业微信" value="QY_WECHAT" />
          </el-select>
        </el-form-item>
        <el-form-item label="是否启用" prop="enabled">
ruoyi-ui/src/views/system/notify/log/index.vue
@@ -113,6 +113,7 @@
          <span>{{ parseTime(scope.row.sendTime) }}</span>
        </template>
      </el-table-column>
      <el-table-column label="发送内容" align="center" prop="sendContent" show-overflow-tooltip />
      <el-table-column label="发送结果" align="center" prop="sendResult" show-overflow-tooltip />
      <el-table-column label="重试次数" align="center" prop="retryCount" />
      <el-table-column label="创建时间" align="center" prop="createTime" width="180">
@@ -194,6 +195,19 @@
        </el-row>
        <el-row>
          <el-col :span="24">
            <el-form-item label="发送内容:">
              <el-input
                type="textarea"
                :rows="4"
                placeholder="发送内容"
                v-model="form.sendContent"
                readonly>
              </el-input>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="24">
            <el-form-item label="响应消息:">{{ form.responseMsg }}</el-form-item>
          </el-col>
        </el-row>
ruoyi-ui/src/views/system/qywechat/index.vue
New file
@@ -0,0 +1,18 @@
<template>
  <div class="app-container">
    <h1>企业微信测试页面</h1>
    <p>这是一个测试页面,用于验证企业微信功能。</p>
  </div>
</template>
<script>
// 简单测试导入
import * as qywechatApi from '@/api/system/qywechat/index'
export default {
  name: 'QyWechatIndex',
  mounted() {
    console.log('企业微信API导入测试:', qywechatApi)
  }
}
</script>
ruoyi-ui/src/views/system/qywechat/test.vue
New file
@@ -0,0 +1,226 @@
<template>
  <div class="app-container">
    <el-card class="box-card">
      <div slot="header" class="clearfix">
        <span>企业微信功能测试</span>
      </div>
      <el-tabs v-model="activeTab">
        <!-- AccessToken测试 -->
        <el-tab-pane label="AccessToken测试" name="token">
          <el-form ref="tokenForm" :model="tokenForm" label-width="120px">
            <el-form-item label="CorpID">
              <el-input v-model="tokenForm.corpId" placeholder="请输入企业微信CorpID" />
            </el-form-item>
            <el-form-item label="CorpSecret">
              <el-input v-model="tokenForm.corpSecret" placeholder="请输入企业微信CorpSecret" show-password />
            </el-form-item>
            <el-form-item>
              <el-button type="primary" @click="getToken">获取AccessToken</el-button>
              <el-button type="success" @click="refreshToken">刷新AccessToken</el-button>
            </el-form-item>
          </el-form>
          <el-divider />
          <div v-if="tokenResult">
            <h4>测试结果:</h4>
            <pre>{{ tokenResult }}</pre>
          </div>
        </el-tab-pane>
        <!-- 消息发送测试 -->
        <el-tab-pane label="消息发送测试" name="message">
          <el-form ref="messageForm" :model="messageForm" label-width="120px">
            <el-form-item label="用户ID">
              <el-input v-model="messageForm.userId" placeholder="请输入系统用户ID" />
            </el-form-item>
            <el-form-item label="企业微信用户ID">
              <el-input v-model="messageForm.qyUserId" placeholder="请输入企业微信用户ID" />
            </el-form-item>
            <el-form-item label="消息标题">
              <el-input v-model="messageForm.title" placeholder="请输入消息标题" />
            </el-form-item>
            <el-form-item label="消息内容">
              <el-input v-model="messageForm.content" type="textarea" :rows="4" placeholder="请输入消息内容" />
            </el-form-item>
            <el-form-item label="通知链接">
              <el-input v-model="messageForm.notifyUrl" placeholder="请输入通知链接" />
            </el-form-item>
            <el-form-item>
              <el-button type="primary" @click="sendMessage">发送通知消息</el-button>
              <el-button type="success" @click="sendTextMessage">发送文本消息</el-button>
            </el-form-item>
          </el-form>
          <el-divider />
          <div v-if="messageResult">
            <h4>测试结果:</h4>
            <pre>{{ messageResult }}</pre>
          </div>
        </el-tab-pane>
        <!-- 服务状态检查 -->
        <el-tab-pane label="服务状态检查" name="status">
          <el-button type="primary" @click="checkServiceStatus">检查服务状态</el-button>
          <el-divider />
          <div v-if="statusResult">
            <h4>服务状态:</h4>
            <el-tag :type="statusResult.enabled ? 'success' : 'danger'">
              {{ statusResult.enabled ? '已启用' : '已禁用' }}
            </el-tag>
            <p v-if="statusResult.message">{{ statusResult.message }}</p>
          </div>
        </el-tab-pane>
      </el-tabs>
    </el-card>
  </div>
</template>
<script>
import { getToken, refreshToken, sendMessage, sendTextMessage, checkServiceStatus } from '@/api/system/qywechat/index'
export default {
  name: 'QyWechatTest',
  data() {
    return {
      activeTab: 'token',
      tokenForm: {
        corpId: 'wx248505bfbab6d0c1',
        corpSecret: '2MCilqWYC0FWjOQ894sbb-s7Lb5sVH4HHuJgOsd9l1k'
      },
      messageForm: {
        userId: '',
        qyUserId: '',
        title: '测试消息标题',
        content: '这是一条测试消息内容',
        notifyUrl: 'https://www.example.com'
      },
      tokenResult: null,
      messageResult: null,
      statusResult: null
    }
  },
  methods: {
    // 获取AccessToken
    async getToken() {
      try {
        const response = await getToken()
        this.tokenResult = JSON.stringify(response, null, 2)
        this.$message.success('获取AccessToken成功')
      } catch (error) {
        this.tokenResult = error.message || '获取AccessToken失败'
        this.$message.error('获取AccessToken失败: ' + (error.message || '未知错误'))
      }
    },
    // 刷新AccessToken
    async refreshToken() {
      try {
        const response = await refreshToken()
        this.tokenResult = JSON.stringify(response, null, 2)
        this.$message.success('刷新AccessToken成功')
      } catch (error) {
        this.tokenResult = error.message || '刷新AccessToken失败'
        this.$message.error('刷新AccessToken失败: ' + (error.message || '未知错误'))
      }
    },
    // 发送通知消息
    async sendMessage() {
      if (!this.messageForm.userId) {
        this.$message.warning('请输入用户ID')
        return
      }
      if (!this.messageForm.title) {
        this.$message.warning('请输入消息标题')
        return
      }
      if (!this.messageForm.content) {
        this.$message.warning('请输入消息内容')
        return
      }
      try {
        const params = {
          userId: this.messageForm.userId,
          title: this.messageForm.title,
          content: this.messageForm.content
        }
        const response = await sendMessage(params)
        this.messageResult = JSON.stringify(response, null, 2)
        this.$message.success('发送通知消息成功')
      } catch (error) {
        this.messageResult = error.message || '发送通知消息失败'
        this.$message.error('发送通知消息失败: ' + (error.message || '未知错误'))
      }
    },
    // 发送文本消息
    async sendTextMessage() {
      if (!this.messageForm.qyUserId) {
        this.$message.warning('请输入企业微信用户ID')
        return
      }
      if (!this.messageForm.content) {
        this.$message.warning('请输入消息内容')
        return
      }
      try {
        const params = {
          qyUserId: this.messageForm.qyUserId,
          title: this.messageForm.title || '测试消息',
          content: this.messageForm.content,
          notifyUrl: this.messageForm.notifyUrl
        }
        const response = await sendTextMessage(params)
        this.messageResult = JSON.stringify(response, null, 2)
        this.$message.success('发送文本消息成功')
      } catch (error) {
        this.messageResult = error.message || '发送文本消息失败'
        this.$message.error('发送文本消息失败: ' + (error.message || '未知错误'))
      }
    },
    // 检查服务状态
    async checkServiceStatus() {
      try {
        const response = await checkServiceStatus()
        this.statusResult = {
          enabled: response.data,
          message: response.msg
        }
        this.$message.success('检查服务状态成功')
      } catch (error) {
        this.statusResult = {
          enabled: false,
          message: error.message || '检查服务状态失败'
        }
        this.$message.error('检查服务状态失败: ' + (error.message || '未知错误'))
      }
    }
  }
}
</script>
<style scoped>
.box-card {
  margin-bottom: 20px;
}
.el-divider {
  margin: 20px 0;
}
pre {
  background-color: #f5f5f5;
  padding: 10px;
  border-radius: 4px;
  white-space: pre-wrap;
  word-wrap: break-word;
}
</style>
ruoyi-ui/src/views/task/general/detail.vue
@@ -8,7 +8,7 @@
      
      <!-- 基本信息 -->
      <el-descriptions title="基本信息" :column="2" border>
        <el-descriptions-item label="任务编号">{{ taskDetail.taskCode }}</el-descriptions-item>
        <el-descriptions-item label="任务编号">{{ taskDetail.showTaskCode }}</el-descriptions-item>
        <el-descriptions-item label="任务类型">
          <dict-tag :options="dict.type.sys_task_type" :value="taskDetail.taskType"/>
        </el-descriptions-item>
@@ -81,6 +81,12 @@
          </span>
          <span v-else style="color: #C0C4CC;">--</span>
        </el-descriptions-item>
        <el-descriptions-item label="服务单编码">
          <span v-if="taskDetail.emergencyInfo.serviceCode">
            <el-tag type="success" size="small">{{ taskDetail.emergencyInfo.serviceCode }}</el-tag>
          </span>
          <span v-else style="color: #C0C4CC;">--</span>
        </el-descriptions-item>
        <el-descriptions-item label="服务单同步时间">
          <span v-if="taskDetail.emergencyInfo.syncTime">{{ parseTime(taskDetail.emergencyInfo.syncTime) }}</span>
          <span v-else style="color: #C0C4CC;">--</span>
@@ -107,6 +113,12 @@
        <el-descriptions-item label="调度单号">
          <span v-if="taskDetail.emergencyInfo.legacyDispatchOrdId">
            <el-tag type="primary" size="small">{{ taskDetail.emergencyInfo.legacyDispatchOrdId }}</el-tag>
          </span>
          <span v-else style="color: #C0C4CC;">--</span>
        </el-descriptions-item>
        <el-descriptions-item label="调度单编码">
          <span v-if="taskDetail.emergencyInfo.dispatchCode">
            <el-tag type="success" size="small">{{ taskDetail.emergencyInfo.dispatchCode }}</el-tag>
          </span>
          <span v-else style="color: #C0C4CC;">--</span>
        </el-descriptions-item>
@@ -306,12 +318,12 @@
      </el-descriptions>
      <!-- 操作按钮 -->
      <div style="margin-top: 20px; text-align: center;">
      <!-- <div style="margin-top: 20px; text-align: center;">
        <el-button type="primary" @click="handleEdit" v-hasPermi="['task:general:edit']">编辑任务</el-button>
        <el-button type="success" @click="handleAssign" v-hasPermi="['task:general:assign']">分配任务</el-button>
        <el-button type="warning" @click="handleStatusChange" v-hasPermi="['task:general:status']">状态变更</el-button>
        <el-button type="info" @click="handleVehicleAssign" v-hasPermi="['task:general:assign']">分配车辆</el-button>
      </div>
      </div> -->
    </el-card>
    
    <!-- 执行人员列表 -->
ruoyi-ui/src/views/task/general/index.vue
@@ -102,22 +102,20 @@
    <el-table v-loading="loading" :data="taskList" @selection-change="handleSelectionChange">
      <el-table-column type="selection" width="55" align="center" />
      <el-table-column label="任务编号" align="center" prop="taskCode" min-width="180">
      <el-table-column label="任务编号" align="center" prop="showTaskCode" min-width="120">
        <template slot-scope="scope">
          <el-button
            type="text"
            @click="handleView(scope.row)"
            v-hasPermi="['task:general:query']"
            style="font-family: 'Courier New', monospace; font-size: 13px;"
          >{{ scope.row.taskCode }}</el-button>
          >{{ scope.row.showTaskCode }}</el-button>
        </template>
      </el-table-column>
      <el-table-column label="任务类型" align="center" prop="taskType" width="120">
        <template slot-scope="scope">
          <dict-tag :options="dict.type.sys_task_type" :value="scope.row.taskType"/>
          <el-tag v-if="scope.row.taskType === 'EMERGENCY_TRANSFER'" type="danger" size="mini" style="margin-left: 5px;">
            <i class="el-icon-warning"></i>
          </el-tag>
        </template>
      </el-table-column>
   
@@ -132,8 +130,8 @@
          <span v-else style="color: #C0C4CC;">--</span>
        </template>
      </el-table-column>
      <el-table-column label="出发地址" align="center" prop="departureAddress" show-overflow-tooltip />
      <el-table-column label="目的地址" align="center" prop="destinationAddress" show-overflow-tooltip />
      <!-- <el-table-column label="目的地址" align="center" prop="destinationAddress" show-overflow-tooltip /> -->
      <el-table-column label="预计公里数" align="center" prop="estimatedDistance" width="120">
        <template slot-scope="scope">
          <span v-if="scope.row.estimatedDistance">{{ scope.row.estimatedDistance }} km</span>
@@ -143,7 +141,7 @@
 
     
      <el-table-column label="创建人" align="center" prop="creatorName" />
      <el-table-column label="执行人" align="center" prop="assigneeName" />
      <el-table-column label="创建时间" align="center" prop="createTime" width="180">
        <template slot-scope="scope">
          <span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
@@ -158,27 +156,27 @@
            @click="handleView(scope.row)"
            v-hasPermi="['task:general:query']"
          >查看</el-button>
          <el-button
          <!-- <el-button
            size="mini"
            type="text"
            icon="el-icon-edit"
            @click="handleUpdate(scope.row)"
            v-hasPermi="['task:general:edit']"
          >修改</el-button>
          <el-button
          >修改</el-button> -->
          <!-- <el-button
            size="mini"
            type="text"
            icon="el-icon-user"
            @click="handleAssign(scope.row)"
            v-hasPermi="['task:general:assign']"
          >分配</el-button>
          <el-button
          >分配</el-button> -->
          <!-- <el-button
            size="mini"
            type="text"
            icon="el-icon-refresh"
            @click="handleStatusChange(scope.row)"
            v-hasPermi="['task:general:status']"
          >状态</el-button>
          >状态</el-button> -->
          <el-button
            size="mini"
            type="text"
sql/add_qy_wechat_user_id_to_sys_user.sql
New file
@@ -0,0 +1,11 @@
-- 在sys_user表中添加qy_wechat_user_id字段,用于记录企业微信用户ID
-- 该字段用于企业微信消息推送功能
-- 添加qy_wechat_user_id字段
ALTER TABLE sys_user ADD COLUMN qy_wechat_user_id VARCHAR(100) NULL COMMENT '企业微信用户ID';
-- 为qy_wechat_user_id字段创建索引,提高查询效率
CREATE INDEX idx_qy_wechat_user_id ON sys_user(qy_wechat_user_id);
-- 添加更新时间字段,记录企业微信用户ID的最后更新时间
ALTER TABLE sys_user ADD COLUMN qy_wechat_update_time datetime NULL COMMENT '企业微信用户ID更新时间';
sql/notify_dict.sql
@@ -36,7 +36,8 @@
(1, '微信订阅消息', 'WECHAT', 'sys_notify_channel', '', 'primary', 'Y', '0', 'admin', SYSDATE(), '微信订阅消息'),
(2, '短信', 'SMS', 'sys_notify_channel', '', 'success', 'N', '0', 'admin', SYSDATE(), '短信'),
(3, '站内消息', 'SITE_MSG', 'sys_notify_channel', '', 'info', 'N', '0', 'admin', SYSDATE(), '站内消息'),
(4, 'APP推送', 'APP_PUSH', 'sys_notify_channel', '', 'warning', 'N', '0', 'admin', SYSDATE(), 'APP推送');
(4, 'APP推送', 'APP_PUSH', 'sys_notify_channel', '', 'warning', 'N', '0', 'admin', SYSDATE(), 'APP推送'),
(5, '企业微信', 'QY_WECHAT', 'sys_notify_channel', '', 'primary', 'N', '0', 'admin', SYSDATE(), '企业微信');
-- 4. 通知发送状态字典
INSERT INTO sys_dict_type(dict_name, dict_type, status, create_by, create_time, remark) 
sql/notify_menu.sql
@@ -50,4 +50,20 @@
('渠道配置查询', @channelConfigParentId, 1,  '#', '', 1, 0, 'F', '0', '0', 'system:notify:channel:config:query',        '#', 'admin', SYSDATE(), '', NULL, ''),
('渠道配置新增', @channelConfigParentId, 2,  '#', '', 1, 0, 'F', '0', '0', 'system:notify:channel:config:add',       '#', 'admin', SYSDATE(), '', NULL, ''),
('渠道配置修改', @channelConfigParentId, 3,  '#', '', 1, 0, 'F', '0', '0', 'system:notify:channel:config:edit',       '#', 'admin', SYSDATE(), '', NULL, ''),
('渠道配置删除', @channelConfigParentId, 4,  '#', '', 1, 0, 'F', '0', '0', 'system:notify:channel:config:remove',       '#', 'admin', SYSDATE(), '', NULL, '');
('渠道配置删除', @channelConfigParentId, 4,  '#', '', 1, 0, 'F', '0', '0', 'system:notify:channel:config:remove',       '#', 'admin', SYSDATE(), '', NULL, '');
-- 企业微信测试菜单
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
VALUES
('企业微信测试', @parentId, 4, 'qywechat/test', 'system/qywechat/test', 1, 0, 'C', '0', '0', 'system:qywechat:test:list', 'wechat', 'admin', SYSDATE(), '', NULL, '企业微信测试菜单');
-- 获取刚插入的企业微信测试菜单ID
SET @qywechatTestParentId = (SELECT menu_id FROM sys_menu WHERE menu_name = '企业微信测试' AND menu_type = 'C');
-- 企业微信测试按钮 SQL
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
VALUES
('企业微信测试查询', @qywechatTestParentId, 1,  '#', '', 1, 0, 'F', '0', '0', 'system:qywechat:test:query',        '#', 'admin', SYSDATE(), '', NULL, ''),
('企业微信测试新增', @qywechatTestParentId, 2,  '#', '', 1, 0, 'F', '0', '0', 'system:qywechat:test:add',       '#', 'admin', SYSDATE(), '', NULL, ''),
('企业微信测试修改', @qywechatTestParentId, 3,  '#', '', 1, 0, 'F', '0', '0', 'system:qywechat:test:edit',       '#', 'admin', SYSDATE(), '', NULL, ''),
('企业微信测试删除', @qywechatTestParentId, 4,  '#', '', 1, 0, 'F', '0', '0', 'system:qywechat:test:remove',       '#', 'admin', SYSDATE(), '', NULL, '');
sql/qy_wechat_config.sql
New file
@@ -0,0 +1,19 @@
-- 企业微信配置初始化脚本
-- 该脚本用于初始化企业微信相关的系统配置项
-- 删除已存在的配置项
DELETE FROM sys_config WHERE config_key IN (
    'qy_wechat.enable',
    'qy_wechat.corp_id',
    'qy_wechat.corp_secret',
    'qy_wechat.agent_id'
);
-- 插入企业微信配置项
INSERT INTO sys_config (config_name, config_key, config_value, config_type, create_by, create_time, update_by, update_time, remark) VALUES
('企业微信服务启用状态', 'qy_wechat.enable', 'false', 'Y', 'admin', SYSDATE(), '', NULL, '企业微信服务是否启用,true-启用,false-禁用'),
('企业微信CorpID', 'qy_wechat.corp_id', 'wx248505bfbab6d0c1', 'N', 'admin', SYSDATE(), '', NULL, '企业微信企业ID'),
('企业微信CorpSecret', 'qy_wechat.corp_secret', '2MCilqWYC0FWjOQ894sbb-s7Lb5sVH4HHuJgOsd9l1k', 'N', 'admin', SYSDATE(), '', NULL, '企业微信应用密钥'),
('企业微信AgentId', 'qy_wechat.agent_id', '1000002', 'N', 'admin', SYSDATE(), '', NULL, '企业微信应用AgentId');
COMMIT;
sql/qy_wechat_sync_job.sql
New file
@@ -0,0 +1,86 @@
-- 企业微信用户ID同步定时任务
-- 该任务每天凌晨2点执行一次,同步OA系统中的企业微信用户ID到本地系统
-- 删除已存在的同名任务
DELETE FROM QRTZ_JOB_DETAILS WHERE sched_name = 'RuoyiScheduler' AND job_name = 'qyWechatUserSyncTask' AND job_group = 'DEFAULT';
DELETE FROM QRTZ_TRIGGERS WHERE sched_name = 'RuoyiScheduler' AND trigger_name = 'qyWechatUserSyncTask' AND trigger_group = 'DEFAULT';
DELETE FROM QRTZ_CRON_TRIGGERS WHERE sched_name = 'RuoyiScheduler' AND trigger_name = 'qyWechatUserSyncTask' AND trigger_group = 'DEFAULT';
-- 插入任务详细信息
INSERT INTO QRTZ_JOB_DETAILS (
    sched_name,
    job_name,
    job_group,
    description,
    job_class_name,
    is_durable,
    is_nonconcurrent,
    is_update_data,
    requests_recovery,
    job_data
) VALUES (
    'RuoyiScheduler',
    'qyWechatUserSyncTask',
    'DEFAULT',
    '企业微信用户ID同步任务',
    'com.ruoyi.quartz.task.QyWechatUserSyncTask',
    '1',
    '0',
    '0',
    '0',
    ''
);
-- 插入触发器信息
INSERT INTO QRTZ_TRIGGERS (
    sched_name,
    trigger_name,
    trigger_group,
    job_name,
    job_group,
    description,
    next_fire_time,
    prev_fire_time,
    priority,
    trigger_state,
    trigger_type,
    start_time,
    end_time,
    calendar_name,
    misfire_instr,
    job_data
) VALUES (
    'RuoyiScheduler',
    'qyWechatUserSyncTask',
    'DEFAULT',
    'qyWechatUserSyncTask',
    'DEFAULT',
    '企业微信用户ID同步任务触发器',
    0,
    0,
    5,
    'WAITING',
    'CRON',
    1702281600000,
    0,
    '',
    0,
    ''
);
-- 插入Cron触发器信息(每天凌晨2点执行)
INSERT INTO QRTZ_CRON_TRIGGERS (
    sched_name,
    trigger_name,
    trigger_group,
    cron_expression,
    time_zone_id
) VALUES (
    'RuoyiScheduler',
    'qyWechatUserSyncTask',
    'DEFAULT',
    '0 0 2 * * ?',
    'Asia/Shanghai'
);
COMMIT;
sql/ry_20250417.sql
@@ -59,14 +59,22 @@
  update_by         varchar(64)     default ''                 comment '更新者',
  update_time       datetime                                   comment '更新时间',
  remark            varchar(500)    default null               comment '备注',
  oa_user_id        int(11)         default null               comment 'SQL Server中的OA用户ID',
  oa_order_class    varchar(255)    default null               comment 'OA系统的订单编码列表(如:BF,AB,SA)',
  open_id           varchar(100)    default null               comment '微信OpenID',
  union_id          varchar(100)    default null               comment '微信UnionID',
  wechat_nickname   varchar(100)    default null               comment '微信昵称',
  qy_wechat_user_id varchar(100)    default null               comment '企业微信用户ID',
  qy_wechat_update_time datetime    default null               comment '企业微信用户ID更新时间',
  can_view_all_consult char(1)      default '0'                comment '是否可查看所有咨询单(0否 1是)',
  primary key (user_id)
) engine=innodb auto_increment=100 comment = '用户信息表';
-- ----------------------------
-- 初始化-用户信息表数据
-- ----------------------------
insert into sys_user values(1,  103, 'admin', '若依', '00', 'ry@163.com', '15888888888', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', sysdate(), 'admin', sysdate(), '', null, '管理员');
insert into sys_user values(2,  105, 'ry',    '若依', '00', 'ry@qq.com',  '15666666666', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', sysdate(), 'admin', sysdate(), '', null, '测试员');
insert into sys_user values(1,  103, 'admin', '若依', '00', 'ry@163.com', '15888888888', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', sysdate(), 'admin', sysdate(), '', null, '管理员', null, null, null, null, null, null, null, '0');
insert into sys_user values(2,  105, 'ry',    '若依', '00', 'ry@qq.com',  '15666666666', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', sysdate(), 'admin', sysdate(), '', null, '测试员', null, null, null, null, null, null, null, '0');
-- ----------------------------
sql/sys_notify_send_log.sql
@@ -14,8 +14,9 @@
  `notify_type` varchar(32) NOT NULL COMMENT '通知类型:TASK_ASSIGN-任务分配, STATUS_CHANGE-状态变更, TASK_CREATE-任务创建',
  `channel` varchar(32) NOT NULL COMMENT '通知渠道:WECHAT-微信订阅消息, SMS-短信, APP_PUSH-APP推送, SITE_MSG-站内消息',
  `send_status` char(1) DEFAULT '0' COMMENT '发送状态:0-待发送, 1-发送成功, 2-发送失败',
  `send_content` text COMMENT '发送的内容',
  `send_time` datetime DEFAULT NULL COMMENT '发送时间',
  `send_result` varchar(500) DEFAULT NULL COMMENT '发送结果/错误信息',
  `send_result` text DEFAULT NULL COMMENT '发送结果/错误信息',
  `retry_count` int(11) DEFAULT 0 COMMENT '重试次数',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  `create_by` varchar(64) DEFAULT '' COMMENT '创建者',
sql/sys_notify_task.sql
@@ -62,7 +62,8 @@
INSERT INTO `sys_notify_channel_config` (`notify_type`, `channel`, `enabled`, `priority`, `config_json`, `create_by`, `create_time`, `remark`) VALUES
('TASK_ASSIGN', 'SITE_MSG', '1', 100, NULL, 'admin', NOW(), '任务分配-站内消息'),
('TASK_ASSIGN', 'WECHAT', '1', 90, NULL, 'admin', NOW(), '任务分配-微信订阅消息'),
('TASK_ASSIGN', 'SMS', '0', 80, NULL, 'admin', NOW(), '任务分配-短信(默认关闭)');
('TASK_ASSIGN', 'SMS', '0', 80, NULL, 'admin', NOW(), '任务分配-短信(默认关闭)'),
('TASK_ASSIGN', 'QY_WECHAT', '0', 85, NULL, 'admin', NOW(), '任务分配-企业微信(默认关闭)');
-- 状态变更通知 - 仅启用站内消息
INSERT INTO `sys_notify_channel_config` (`notify_type`, `channel`, `enabled`, `priority`, `config_json`, `create_by`, `create_time`, `remark`) VALUES
sql/sys_task_emergency.sql
@@ -43,6 +43,12 @@
    -- 病情诊断信息
    disease_ids VARCHAR(500) COMMENT '病情ID列表(ICD-10疾病ID列表,逗号分隔,用于同步调度单的OrdICD_ID参数)',
    
    -- 旧系统同步字段
    legacy_dispatch_ord_no VARCHAR(50) COMMENT '旧系统调度单编号',
    legacy_service_ns_time DATETIME COMMENT '旧系统服务通知时间',
    legacy_dispatch_ns_time DATETIME COMMENT '旧系统调度通知时间',
    legacy_dispatch_ord_class VARCHAR(50) COMMENT '旧系统调度单分类',
    -- 系统字段
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
sql/update_sys_task_emergency.sql
New file
@@ -0,0 +1,19 @@
-- ----------------------------
-- 更新急救转运任务扩展表,添加旧系统同步字段
-- ----------------------------
-- 添加旧系统调度单编号字段
ALTER TABLE sys_task_emergency
ADD COLUMN legacy_dispatch_ord_no VARCHAR(50) COMMENT '旧系统调度单编号';
-- 添加旧系统服务通知时间字段
ALTER TABLE sys_task_emergency
ADD COLUMN legacy_service_ns_time DATETIME COMMENT '旧系统服务通知时间';
-- 添加旧系统调度通知时间字段
ALTER TABLE sys_task_emergency
ADD COLUMN legacy_dispatch_ns_time DATETIME COMMENT '旧系统调度通知时间';
-- 添加旧系统调度单分类字段
ALTER TABLE sys_task_emergency
ADD COLUMN legacy_dispatch_ord_class VARCHAR(50) COMMENT '旧系统调度单分类';
sql/update_sys_task_emergency_add_service_ord_class.sql
New file
@@ -0,0 +1,8 @@
-- ----------------------------
-- 为sys_task_emergency表添加legacy_service_ord_class字段
-- 用于存储旧系统服务单分类信息
-- ----------------------------
-- 添加旧系统服务单分类字段
ALTER TABLE sys_task_emergency
ADD COLUMN legacy_service_ord_class VARCHAR(50) COMMENT '旧系统服务单分类';