Compare commits

...

952 Commits

Author SHA1 Message Date
hang 9ee85bfa3c Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is running
2026-05-11 10:49:20 +08:00
hang 434cdf8529 后端下载合并初步提交 2026-05-11 10:49:18 +08:00
he f42ebcac61 图表功能修改
continuous-integration/drone/push Build is running
2026-05-11 10:21:05 +08:00
he d594c6e52a Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is running
2026-05-09 15:00:27 +08:00
he 9bb32c8779 添加字段 2026-05-09 15:00:24 +08:00
hang 13f983bc21 去除文件类型前缀
continuous-integration/drone/push Build is running
2026-05-09 09:38:46 +08:00
hang 1fa81d1e4f 输出偏移表方便测试
continuous-integration/drone/push Build is running
2026-05-09 09:16:10 +08:00
hang 9bd090da66 偏移表bug修复 2026-05-08 17:31:06 +08:00
hang e40a9fe9cb Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is running
2026-05-08 17:04:02 +08:00
hang 3200c71996 修改文件类型 和优先级设置 2026-05-08 17:04:00 +08:00
he 88dca637c2 处理访视退回 阅片期的临床数据状态
continuous-integration/drone/push Build is running
2026-05-08 14:49:52 +08:00
he c65860796b 重新发布
continuous-integration/drone/push Build is running
2026-05-07 15:28:34 +08:00
he 8ea8572947 功能修改 2026-05-07 14:47:52 +08:00
he 82ec00e9e9 功能修改
continuous-integration/drone/push Build was killed
2026-05-07 14:42:27 +08:00
he 0af8e2f017 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8 2026-05-07 14:39:52 +08:00
he f56dcb0cce 功能修改 2026-05-07 14:39:50 +08:00
hang 7957a33fc4 获取未阅片完成的访视,方便进行下载
continuous-integration/drone/push Build is running
2026-05-07 14:31:27 +08:00
hang e150fbb807 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is failing
2026-05-07 11:28:32 +08:00
hang 32a06be96d 多帧进行拆分上传 2026-05-07 11:28:32 +08:00
he f6c9490124 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is running
2026-05-07 11:27:11 +08:00
he e84c96f4ee 历史版本 2026-05-07 11:27:10 +08:00
hang c330fdc145 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is running
2026-05-07 10:50:05 +08:00
hang d38ce06f90 多帧合并然后遮盖 2026-05-07 10:50:04 +08:00
he fe34788cff Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-05-06 17:05:03 +08:00
he 8983cb59b4 MRIPDFF新标准添加 2026-05-06 17:05:01 +08:00
hang 3179fca112 修改fo-dicom 注册方式测试
continuous-integration/drone/push Build is passing
2026-05-06 15:30:13 +08:00
hang 9ad1bbdd7d 合并冲突
continuous-integration/drone/push Build is failing
2026-05-06 14:31:53 +08:00
hang 74844bf9ce 项目增加字段,维护最大的检查编号 2026-05-06 14:29:27 +08:00
he 6296c54e2a 功能修改
continuous-integration/drone/push Build is running
2026-05-06 14:11:11 +08:00
he 4b8d9bf7bf 版本管理
continuous-integration/drone/push Build is running
2026-05-06 14:01:39 +08:00
he 7bfb6d2c5f Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-05-06 13:37:22 +08:00
he 9e2cede844 添加分割版本表 2026-05-06 13:37:18 +08:00
hang d60229fa99 软删除获取编号最大值修改
continuous-integration/drone/push Build is passing
2026-05-06 10:58:46 +08:00
he 5c29eb5860 稽查修改
continuous-integration/drone/push Build is passing
2026-04-30 16:44:20 +08:00
he 9a6220bda1 稽查修改
continuous-integration/drone/push Build is passing
2026-04-30 14:49:23 +08:00
he 55b75d7c32 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-04-30 14:16:33 +08:00
he 0b9115f6a6 修改临床数据问题 2026-04-30 14:16:32 +08:00
hang ce49d9ed45 重新发布测试
continuous-integration/drone/push Build is passing
2026-04-30 11:17:13 +08:00
hang 13397b0bc8 返回影像字段
continuous-integration/drone/push Build is passing
2026-04-30 10:57:15 +08:00
he 303bf195d2 稽查
continuous-integration/drone/push Build is passing
2026-04-29 16:03:57 +08:00
hang af1d4b3cd1 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-04-29 14:27:13 +08:00
hang 0fa16e2962 本地调试报错不通知企业微信 2026-04-29 14:27:12 +08:00
he 855f66566f 报错修改
continuous-integration/drone/push Build is running
2026-04-29 14:26:39 +08:00
he 913663068c 功能修改
continuous-integration/drone/push Build is passing
2026-04-29 13:44:59 +08:00
he 6f05a9e456 添加单位
continuous-integration/drone/push Build is passing
2026-04-29 13:19:49 +08:00
he 5c86778902 修改分割
continuous-integration/drone/push Build is passing
2026-04-29 11:11:27 +08:00
he cfe9023ddd 添加字段
continuous-integration/drone/push Build is failing
2026-04-29 10:50:15 +08:00
he 04815b9217 添加字段
continuous-integration/drone/push Build is running
2026-04-29 10:48:03 +08:00
he 3a918f1aa5 新增修正患者基本信息
continuous-integration/drone/push Build is passing
2026-04-29 10:35:27 +08:00
he e3ee2cbe2f 添加字段
continuous-integration/drone/push Build is running
2026-04-29 10:32:00 +08:00
he 962f88f94d 添加锁定解锁分割功能
continuous-integration/drone/push Build is passing
2026-04-29 10:12:02 +08:00
he 34f5978aea Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-04-29 09:37:39 +08:00
he 50576c4ba9 重阅复制分割信息 2026-04-29 09:37:37 +08:00
hang b56bfcbfcf 修改打包配置文件
continuous-integration/drone/push Build is passing
2026-04-28 15:52:52 +08:00
hang 80cc578be9 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is running
2026-04-28 15:51:50 +08:00
hang bb9c7066c7 不生成外键测试 2026-04-28 15:51:48 +08:00
he f3d70c0d12 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-04-28 14:23:42 +08:00
he 372c6d5e0d 定圆半径给10 2026-04-28 14:23:39 +08:00
hang eca91b139b Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-04-24 14:01:22 +08:00
hang 0d8626509c 修改固定标识位置 2026-04-24 14:01:21 +08:00
he e7548c08a6 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-04-24 13:49:24 +08:00
he cfadc07b52 添加接口 2026-04-24 13:49:22 +08:00
hang 2f2f49c6c7 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-04-24 11:24:47 +08:00
hang 9a23c31313 批次删除. 2026-04-24 11:24:47 +08:00
he 27bdc95f6e Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-04-24 11:14:12 +08:00
he d74e3a9f60 删除关联问题答案 2026-04-24 11:14:03 +08:00
hang 10f8039b1b 返回恢复成功的列表
continuous-integration/drone/push Build is failing
2026-04-24 10:58:55 +08:00
hang f2adb16189 返回成功列表和错误列表
continuous-integration/drone/push Build is passing
2026-04-24 09:57:46 +08:00
hang d807fe0126 增加随机,同时清理缓存
continuous-integration/drone/push Build is passing
2026-04-23 17:43:45 +08:00
hang fa464e6ed8 质控返回是否匿名标记字段,撤销
continuous-integration/drone/push Build is passing
2026-04-23 16:57:50 +08:00
hang 3c62d16ff8 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-04-23 16:25:02 +08:00
hang 2008a1f104 修改路径 2026-04-23 16:25:01 +08:00
he 3716ad2f62 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-04-23 15:46:05 +08:00
he 6c929043c1 添加字段 2026-04-23 15:46:03 +08:00
hang 3888b6f5d2 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-04-23 15:31:26 +08:00
hang b59415e461 修改路径 2026-04-23 15:31:25 +08:00
he af160e3c96 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is failing
continuous-integration/drone Build is passing
2026-04-23 14:56:28 +08:00
he b5e6a81a52 功能修改 2026-04-23 14:56:25 +08:00
hang 58eb59bd61 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is running
2026-04-23 14:55:16 +08:00
hang 104d44d757 修改模型方法 2026-04-23 14:55:15 +08:00
he b29ed4d6a2 用户配置修改
continuous-integration/drone/push Build is passing
2026-04-23 10:29:52 +08:00
he 34008ef6db 患者数据验证
continuous-integration/drone/push Build is passing
2026-04-22 16:10:14 +08:00
he a1dc17c582 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-04-22 14:30:08 +08:00
he 01f339cb41 代码映射修改 2026-04-22 14:30:06 +08:00
hang 6315f1dc55 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-04-22 11:36:45 +08:00
hang 5df7c5c37d 跳过代码添加 2026-04-22 11:36:44 +08:00
he dee2cae126 阅片工具修改
continuous-integration/drone/push Build is passing
2026-04-22 11:20:04 +08:00
he 4d7ad9255a Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is failing
2026-04-22 09:49:13 +08:00
he ae5e9a75d6 功能修改 2026-04-22 09:49:10 +08:00
hang 53134ee4d2 质控自动任务跳过,增加缓存跳过当前任务
continuous-integration/drone/push Build is passing
2026-04-21 15:47:07 +08:00
hang ea781dcd38 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-04-21 14:51:27 +08:00
hang 633a0bb777 质控跳过在一个任务 2026-04-21 14:51:26 +08:00
he 3d83f18c71 字段修改
continuous-integration/drone/push Build is passing
2026-04-21 13:07:03 +08:00
he 89f6cd4244 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-04-21 11:19:17 +08:00
he 1c154de18b 修改三个字段 2026-04-21 11:19:15 +08:00
hang 6bb3be17ee 撤销遮盖接口初步完成
continuous-integration/drone/push Build is passing
2026-04-21 10:37:01 +08:00
hang 8259e2fea3 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-04-21 09:25:40 +08:00
hang 152c5200cb 同步遮盖影像初步预提交 2026-04-21 09:25:39 +08:00
he 99a1690481 添加字段
continuous-integration/drone/push Build is passing
2026-04-20 17:17:35 +08:00
he fa4564d491 添加 IsBeSegment 标记
continuous-integration/drone/push Build is passing
2026-04-17 13:30:13 +08:00
he be5c25ba5f Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is running
2026-04-16 11:03:50 +08:00
he 65e57aab7f 重置阅片标准的时候 清除分割绑定关系 2026-04-16 11:03:48 +08:00
hang 5e04634837 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-04-15 13:50:24 +08:00
hang c9a8d7ad4c YBR_FULL_422 提交 2026-04-15 13:50:20 +08:00
Hewt 7c2ca13951 功能修改
continuous-integration/drone/push Build is passing
2026-04-15 13:33:15 +08:00
he ebf6c225f3 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-04-15 11:51:12 +08:00
he 7083cc4493 修改默认值 2026-04-15 11:51:10 +08:00
hang 711e2a53a5 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-04-14 18:04:31 +08:00
hang 5fd2f782c2 伪彩图像解析遮盖 2026-04-14 18:04:26 +08:00
he 6147415cea 重新发布
continuous-integration/drone/push Build is passing
2026-04-14 11:41:35 +08:00
he 6c34489aee 添加 CircleRadius
continuous-integration/drone/push Build is failing
2026-04-14 11:28:03 +08:00
he 1618f0454c 添加默认值
continuous-integration/drone/push Build is passing
2026-04-14 11:17:54 +08:00
he 1771e9731c 查询参数修改
continuous-integration/drone/push Build is passing
2026-04-14 09:37:29 +08:00
he 1e509c0ec7 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-04-14 09:13:48 +08:00
he 0d412c245f 字段修改 2026-04-14 09:13:46 +08:00
Hewt 736266d8e8 修复默认SegmentName报错问题
continuous-integration/drone/push Build is passing
2026-04-13 20:08:10 +08:00
hang 05b2ab75a3 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-04-13 18:05:32 +08:00
hang e197a889d4 本地调试错误不发送企业微信+遮盖图像初步测试 2026-04-13 18:05:30 +08:00
he 334e8f6267 添加字段
continuous-integration/drone/push Build is passing
2026-04-13 17:56:56 +08:00
he e430c40b99 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is failing
2026-04-13 15:43:02 +08:00
he 1d6cb765cc 修改字段 2026-04-13 15:43:01 +08:00
hang d7926a309a Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is running
2026-04-13 15:38:24 +08:00
hang cca587fb17 修改异常取信息 2026-04-13 15:38:22 +08:00
he 742b36ed4a Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is failing
2026-04-13 14:50:38 +08:00
he 50da5376b5 功能修改 2026-04-13 14:50:35 +08:00
hang d262057a73 恢复默认配置
continuous-integration/drone/push Build is passing
2026-04-10 17:35:12 +08:00
hang 797b076705 升级fo-dicom 以及漏洞包
continuous-integration/drone/push Build is passing
2026-04-10 17:22:45 +08:00
hang 6633e26175 本地启动项目,直接跳过同步任务,不影响线上任务,否则会重复
continuous-integration/drone/push Build is passing
2026-04-10 14:24:56 +08:00
he 6c1c46601c Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-04-10 10:11:54 +08:00
he 411168be9c 临床数据修改 2026-04-10 10:11:52 +08:00
hang efda03a0c9 去重处理位置2
continuous-integration/drone/push Build is passing
2026-04-09 17:18:22 +08:00
hang d4d632adce 添加项目用户角色去重-多中心加入同一个人
continuous-integration/drone/push Build is passing
2026-04-09 17:01:40 +08:00
hang 040ca6eb77 特殊查询处理测试2
continuous-integration/drone/push Build is passing
2026-04-08 14:25:12 +08:00
hang 9bdfb355f1 特殊查询处理测试
continuous-integration/drone/push Build is running
2026-04-08 14:24:34 +08:00
hang e392c436de 优先级赋值不传默认设置为0
continuous-integration/drone/push Build is passing
2026-04-08 14:10:25 +08:00
hang 294f59c4f3 动态设置-存储删除逻辑修改
continuous-integration/drone/push Build is passing
2026-04-07 16:17:35 +08:00
hang e6bdb51b5b 加强判断完成判断标志以及给默认值
continuous-integration/drone/push Build is passing
2026-04-07 14:51:18 +08:00
hang 5a7796939f 优先级队列,增加hash去重,覆盖优先级测试
continuous-integration/drone/push Build is passing
2026-04-03 16:00:24 +08:00
hang cad9eaf966 增加job状态查询
continuous-integration/drone/push Build is passing
2026-04-03 13:44:35 +08:00
hang 651af672f6 增加搜索条件
continuous-integration/drone/push Build is passing
2026-04-03 11:07:58 +08:00
hang 49375ec5a9 按照 subject visit studyCode 三个维度进行分组的查询列表
continuous-integration/drone/push Build is passing
2026-04-03 09:48:07 +08:00
hang 50f2a6a8b0 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-04-02 17:07:50 +08:00
hang 81280a3fed 增加StudyCode 2026-04-02 17:07:46 +08:00
he 626de2e44a 重新发布
continuous-integration/drone/push Build is passing
2026-04-02 14:22:48 +08:00
he 195d0d406c Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is failing
2026-04-02 14:08:59 +08:00
he 30ec92c805 修改接口 2026-04-02 14:08:59 +08:00
hang 9a276e3d3c 增加执行记录查询接口
continuous-integration/drone/push Build is running
2026-04-02 14:05:08 +08:00
he 75cceee734 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-04-02 09:42:00 +08:00
he 00dcf84abe 修改分组名称 2026-04-02 09:41:59 +08:00
hang 529033c5e4 进行同步任务时,在判断项目同步标志时,同时判断系统配置同步是否打开
continuous-integration/drone/push Build is passing
2026-04-01 14:30:13 +08:00
he fecbc276c8 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-04-01 11:49:03 +08:00
he f778451356 数据字段修改 2026-04-01 11:48:59 +08:00
hang 6b9f108fe1 前后端调试时,不是定义域名操作时,设置上传、目标区域默认值
continuous-integration/drone/push Build is passing
2026-03-31 14:13:45 +08:00
he b848fbdd20 验证分割分组是否保存
continuous-integration/drone/push Build is passing
2026-03-31 10:25:46 +08:00
he beca7bbfc6 关闭并完成医学审核
continuous-integration/drone/push Build is passing
2026-03-31 09:54:39 +08:00
he a8af33a30a 修改排序
continuous-integration/drone/push Build is passing
2026-03-30 17:43:42 +08:00
he 9a40cf66cc 合并
continuous-integration/drone/push Build is passing
2026-03-30 10:55:48 +08:00
he 25f62a35ee 添加分割保存状态 2026-03-30 10:54:09 +08:00
hang 24261a3dcb instance 级别增加排序-四个排序接口都已修改完毕
continuous-integration/drone/push Build is passing
2026-03-30 10:42:17 +08:00
hang 42e34b5e5c 修改访视检查+序列接口排序
continuous-integration/drone/push Build is passing
2026-03-26 15:51:01 +08:00
hang 52f49994ab 影像阅片排序换到后端做
continuous-integration/drone/push Build is passing
2026-03-26 15:21:46 +08:00
Hewt 3f9e506d09 OCT修改 匹配段最小FCT计算逻辑修改
continuous-integration/drone/push Build is passing
2026-03-25 11:19:43 +08:00
Hewt 4c27e66870 OCT修改平均最小FCT计算
continuous-integration/drone/push Build is passing
2026-03-25 10:01:40 +08:00
Hewt 0e909906be OCT计算修改
continuous-integration/drone/push Build is passing
2026-03-25 09:46:03 +08:00
Hewt 94bf9895af 获取绑定关系 获取分组名称
continuous-integration/drone/push Build is passing
2026-03-24 15:37:29 +08:00
Hewt b7f53f4036 返回标记类型
continuous-integration/drone/push Build is passing
2026-03-24 14:37:24 +08:00
Hewt 49ffbad759 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-03-24 11:33:37 +08:00
Hewt a8f405ddc0 OCT平均值计算修改 2026-03-24 11:33:37 +08:00
hang 3396d73077 多加一层判断,兜底存储使用
continuous-integration/drone/push Build is passing
2026-03-24 10:45:22 +08:00
Hewt 5ed24c209f Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-03-24 10:18:39 +08:00
Hewt 14499009b6 稽查修改 2026-03-24 10:18:37 +08:00
hang 77b2d7bed4 屏蔽列表-继续测试
continuous-integration/drone/push Build is passing
2026-03-23 19:03:12 +08:00
hang 2d52102014 不进行统计进行返回
continuous-integration/drone/push Build is passing
2026-03-23 17:37:34 +08:00
hang 4e20a86155 统计数字换成分组统计子查询
continuous-integration/drone/push Build is running
2026-03-23 17:02:01 +08:00
hang de0cb6788e IRC 修改超时时间设置为90s
continuous-integration/drone/push Build is passing
2026-03-23 16:43:20 +08:00
Hewt 3d8fc0776c 稽查先记录
continuous-integration/drone/push Build is passing
2026-03-23 10:57:37 +08:00
Hewt 38d1c2f92a 添加swagger搜索功能
continuous-integration/drone/push Build is passing
2026-03-23 09:21:32 +08:00
Hewt ada55aefd4 绑定问题修改
continuous-integration/drone/push Build is passing
2026-03-20 17:51:02 +08:00
Hewt 3bb1a9b46d 修改
continuous-integration/drone/push Build is passing
2026-03-20 16:55:43 +08:00
Hewt 751121e878 删除分割删除对应的绑定和答案
continuous-integration/drone/push Build is passing
2026-03-20 16:29:05 +08:00
Hewt 61c4d79fa1 查询修改
continuous-integration/drone/push Build is passing
2026-03-20 14:30:50 +08:00
Hewt 22730f063c 查询条件修改
continuous-integration/drone/push Build is passing
2026-03-20 14:13:15 +08:00
Hewt 396244f044 重新发布
continuous-integration/drone/push Build is passing
2026-03-20 11:16:33 +08:00
Hewt dccbe6be77 问题修改
continuous-integration/drone/push Build is failing
2026-03-20 11:06:22 +08:00
Hewt f4fc51b66d 修改
continuous-integration/drone/push Build is running
2026-03-20 11:05:09 +08:00
Hewt 7a8ec6221f 保存分割绑定和答案
continuous-integration/drone/push Build is passing
2026-03-20 09:35:33 +08:00
Hewt c84fed8af1 添加分割绑定
continuous-integration/drone/push Build is passing
2026-03-19 15:24:34 +08:00
Hewt 67216d1c92 修改校验
continuous-integration/drone/push Build is passing
2026-03-19 14:18:03 +08:00
Hewt 24b30b1c4b 修改校验
continuous-integration/drone/push Build is passing
2026-03-19 14:08:12 +08:00
Hewt 62d065fccb Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-03-19 13:52:10 +08:00
Hewt a5d1ad92c5 修改SegmentToolList 2026-03-19 13:52:07 +08:00
hang bfe2478628 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-03-19 11:12:44 +08:00
hang dedab4e6ba 动态域名请求返回token,兼容本地ip测试 2026-03-19 11:12:43 +08:00
Hewt 52d1575890 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-03-19 10:14:03 +08:00
Hewt 3beac6177f 添加分割类型 2026-03-19 10:14:00 +08:00
hang 3813b93dc5 统一aws 配置
continuous-integration/drone/push Build is passing
2026-03-18 17:36:38 +08:00
hang e0bf859cb2 放开非肿瘤CDISC导出限制
continuous-integration/drone/push Build is passing
2026-03-18 15:39:27 +08:00
hang 8ac515acbd Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is running
2026-03-18 15:36:12 +08:00
hang aafc679e45 修改获取临时token 2026-03-18 15:36:11 +08:00
Hewt f172a15e6e Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-03-18 14:50:50 +08:00
Hewt c279733413 临床数据修改 2026-03-18 14:50:48 +08:00
hang 1b808595b8 修改临时token获取
continuous-integration/drone/push Build is passing
2026-03-18 14:01:29 +08:00
hang c833c88fe3 双向同步后端测试完毕
continuous-integration/drone/push Build is passing
2026-03-18 13:43:02 +08:00
hang 89fb8eac00 打开同步配置文件,设置同步域名配置
continuous-integration/drone/push Build is passing
2026-03-18 11:25:22 +08:00
hang 5c15967985 修改配置文件名
continuous-integration/drone/push Build is passing
2026-03-18 09:30:43 +08:00
hang 5ad84eebbf 统一修改配置文件通知人标识,新增SCP服务 Prod_Event_IRC配置文件
continuous-integration/drone/push Build is passing
2026-03-18 09:07:59 +08:00
he e149a45981 OCT修改3
continuous-integration/drone/push Build is passing
2026-03-16 16:09:44 +08:00
he 4969e0f812 OCT修改2
continuous-integration/drone/push Build is passing
2026-03-16 15:43:37 +08:00
he bb7f6f3a1c 分割查询问题修改
continuous-integration/drone/push Build is passing
2026-03-16 14:51:54 +08:00
he 991f1554ec OCT IVUS 修改1
continuous-integration/drone/push Build is passing
2026-03-16 14:46:03 +08:00
he 02a1ffe8b7 添加字段
continuous-integration/drone/push Build is passing
2026-03-16 09:56:06 +08:00
he 56cbe08f39 Segment添加字段 查询修改
continuous-integration/drone/push Build is passing
2026-03-16 09:04:58 +08:00
he b048437b37 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-03-12 16:12:42 +08:00
he 1353fed0ee ReviewerCode 编辑修改 2026-03-12 16:12:36 +08:00
hang 8ec535be53 优先级队列注册为单例模式
continuous-integration/drone/push Build is passing
2026-03-11 17:25:03 +08:00
hang 121ca01be3 修改编译错误
continuous-integration/drone/push Build is passing
2026-03-11 17:00:14 +08:00
hang 229a83720f Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is failing
2026-03-11 16:46:04 +08:00
hang 3bca306702 增加内存优先级队列、后端上传的地方都设置类别,和业务参数、增加启动后台恢复队列任务 2026-03-11 16:46:00 +08:00
he 3c2701fe9d Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-03-10 14:23:58 +08:00
he 01058d42d3 新增分割 2026-03-10 14:23:56 +08:00
hang 59be897fd8 增加请求域名动态返回云存储配置token
continuous-integration/drone/push Build is passing
2026-03-10 14:05:18 +08:00
he 1a39d03c7e FileUploadRecord 表字段
continuous-integration/drone/push Build is passing
2026-03-10 11:44:38 +08:00
he 49eeee35bb 添加表实体
continuous-integration/drone/push Build is running
2026-03-10 11:40:56 +08:00
hang c1743c2934 修改模型编码测试提交
continuous-integration/drone/push Build is running
2026-03-10 11:37:48 +08:00
hang c906284f07 数据同步增加表,增加项目字段,修改项目配置接口,更新邮件发送包2
continuous-integration/drone/push Build is passing
2026-03-09 17:58:17 +08:00
hang 6427d1f5fb 数据同步增加表,增加项目字段,修改项目配置接口,更新邮件发送包
continuous-integration/drone/push Build is failing
2026-03-09 17:35:11 +08:00
hang 4b569e8fdb 增加查询条件以及导表修改
continuous-integration/drone/push Build is passing
2026-03-09 09:51:50 +08:00
he 6b453648ee Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-03-09 09:40:36 +08:00
he 9cd09470d4 脂质角度平均值计算修改 2026-03-09 09:40:33 +08:00
he 216d194d77 脂质角度平均值计算修改
continuous-integration/drone/push Build is passing
2026-03-06 15:12:33 +08:00
hang 29600e9ac2 修改后处理上传判断
continuous-integration/drone/push Build is failing
2026-03-06 14:05:57 +08:00
hang 67fd739b0e 添加非dicom 修改判断逻辑
continuous-integration/drone/push Build is failing
2026-03-06 13:57:17 +08:00
he 083c68b528 OCT导入修改
continuous-integration/drone/push Build is passing
2026-03-05 10:29:07 +08:00
he 121d3a3e64 OCT计算以及导入验证修改
continuous-integration/drone/push Build is passing
2026-03-05 10:01:58 +08:00
he cfa38f2318 修改脂质角度
continuous-integration/drone/push Build is passing
2026-03-04 16:55:57 +08:00
he 20f87c953c Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-03-04 16:27:00 +08:00
he 4f62f2f2d0 修改脂质角度必填 2026-03-04 16:26:56 +08:00
hang ace5afca66 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-03-04 16:16:08 +08:00
hang 3dfffa557a 遗漏上传dicom,导致自动创建非dicomBug修复 2026-03-04 16:16:05 +08:00
he 2c878a90f8 验证修改
continuous-integration/drone/push Build is passing
2026-03-04 15:06:58 +08:00
he 03b5a01400 导入的标识修改
continuous-integration/drone/push Build is passing
2026-03-04 13:27:40 +08:00
he d7e4a810f6 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-03-04 12:19:52 +08:00
he 338ef9d988 修复OTC自动计算报错 2026-03-04 12:19:50 +08:00
hang 65c67835f2 ivus oct 项目,限制添加除oct外的非dicom检查
continuous-integration/drone/push Build is passing
2026-03-04 10:44:10 +08:00
hang d8d9e25191 不质控时,不一致性核查时,返回统计数据为null
continuous-integration/drone/push Build is passing
2026-03-04 10:30:26 +08:00
hewt 3dd7859752 OCT IVUS 病灶标识修改 计算逻辑修改
continuous-integration/drone/push Build is passing
2026-03-03 12:49:24 +08:00
hewt 358699dbd3 计算修改
continuous-integration/drone/push Build is passing
2026-03-03 09:27:46 +08:00
hewt 2754973c8d OCT修改
continuous-integration/drone/push Build is passing
2026-02-28 13:56:04 +08:00
hewt 8865db72d1 IVUS 修改
continuous-integration/drone/push Build is running
2026-02-28 13:52:55 +08:00
hewt 31158fed0e OCT 修改1
continuous-integration/drone/push Build is passing
2026-02-28 11:09:39 +08:00
hewt ae624730aa 临时取消计算
continuous-integration/drone/push Build is passing
2026-02-27 17:33:39 +08:00
he 3fd299bb49 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-02-12 20:00:05 -05:00
he 9acfc4c544 邮件领取人为null 不发送邮件 2026-02-12 20:00:03 -05:00
hang bbcab237d4 周期性任务事件根据环境配置,增加默认语言,方便触发的时候,后台知道以什么语言发送邮件
continuous-integration/drone/push Build is failing
2026-02-12 22:36:36 +08:00
hang 010367d018 导表模板修改
continuous-integration/drone/push Build is passing
2026-02-12 03:57:42 -05:00
hang ea43698ee9 修改通知列表人
continuous-integration/drone/push Build is passing
2026-02-12 03:32:26 -05:00
hang 7155f6c985 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-02-12 03:25:00 -05:00
hang 3bdba35501 增加周期性邮件默认Culture,后台触发邮件报错通知企业微信 2026-02-12 03:24:58 -05:00
he ebcf3c95e2 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-02-12 03:15:56 -05:00
he 75c3c9ee06 邮件发送修改 2026-02-12 03:15:54 -05:00
hang 048193e4f3 项目导表查询数据过滤问题
continuous-integration/drone/push Build is passing
2026-02-11 22:08:43 -05:00
hang 7879504b71 优化生成DIR 查询判断 是否存在TransferSytaxUID为空的记录
continuous-integration/drone/push Build is passing
2026-02-11 20:22:02 -05:00
hang 93558164de sql 超时设置为60s
continuous-integration/drone/push Build is passing
2026-02-11 04:57:17 -05:00
hang 83d123a31e 修改工作台统计文档签署数量,加了发布字段,这里漏处理
continuous-integration/drone/push Build is passing
2026-02-11 04:33:31 -05:00
hang 733de4c942 修改启动配置监听地址,内网其他人可以访问
continuous-integration/drone/push Build is passing
2026-02-11 01:24:30 -05:00
hang 0e7bd5f5dd 解析UserId 加一层判断,不知道前端其他啥其他误调用该接口
continuous-integration/drone/push Build is passing
2026-02-10 03:53:07 -05:00
hang 0ef7ef675c 修改退出登录接口,允许角色为null
continuous-integration/drone/push Build is passing
2026-02-09 21:17:47 -05:00
hang ee76366a1c Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-02-09 20:58:46 -05:00
hang 369a0803f9 MFA 返回两种邮箱 2026-02-09 20:58:45 -05:00
he 1f7b96d9f0 修改
continuous-integration/drone/push Build is passing
2026-02-09 20:36:30 -05:00
he 72ff2bbf15 修改邮件
continuous-integration/drone/push Build is running
2026-02-09 20:32:19 -05:00
he 0862182f8e Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-02-09 17:12:20 +08:00
he 746a8de799 修改 2026-02-09 17:12:12 +08:00
hang 37e8545eba 后端增加微信通知控制开关,同时前后端错误分别通知不同的人处理
continuous-integration/drone/push Build is passing
2026-02-06 21:12:52 +08:00
hang 570318cf3b 增加堆栈错误长度
continuous-integration/drone/push Build is passing
2026-02-05 23:28:48 +08:00
hang 7e13edc440 增加异常通知
continuous-integration/drone/push Build is passing
2026-02-05 23:20:35 +08:00
hang 5579181f54 irc项目打包增加企业微信机器人通知
continuous-integration/drone/push Build is passing
2026-02-05 17:00:40 +08:00
he 39b840f8fc 维护doctorCode
continuous-integration/drone/push Build is passing
2026-02-05 15:15:48 +08:00
he 02fb4c9753 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-02-05 14:42:07 +08:00
he cf15883d52 添加doctor code 2026-02-05 14:42:05 +08:00
hang 84380a0d21 去掉异步
continuous-integration/drone/push Build is passing
2026-02-05 10:48:36 +08:00
hang 76993b4c8b 多个线程使用一个dbcontext bug
continuous-integration/drone/push Build is running
2026-02-05 10:44:52 +08:00
hang 0a4f00e63d 邮件模板修改提交
continuous-integration/drone/push Build is passing
2026-02-05 10:08:33 +08:00
he 9a52e30172 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-02-02 13:24:03 +08:00
he 120d583a5a 重阅复制表单 2026-02-02 13:24:01 +08:00
hang dd8e244e64 密码连续错误次数判断位置修改
continuous-integration/drone/push Build is passing
2026-02-02 10:25:54 +08:00
hang 5d7a083f4a 修改打包配置
continuous-integration/drone/push Build is passing
2026-01-30 22:37:29 +08:00
he b7f22245e1 修改
continuous-integration/drone/push Build is passing
2026-01-30 13:42:54 +08:00
he 876deb102c 修改
continuous-integration/drone/push Build is passing
2026-01-30 11:07:32 +08:00
he 49741e5c44 修改
continuous-integration/drone/push Build is passing
2026-01-30 10:16:35 +08:00
he 29ee2ba800 创建医生简历
continuous-integration/drone/push Build is passing
2026-01-30 09:18:54 +08:00
he 65e2bee680 字段添加
continuous-integration/drone/push Build is passing
2026-01-29 17:41:04 +08:00
he d45871a0db 字段添加
continuous-integration/drone/push Build is passing
2026-01-29 14:05:08 +08:00
he ab3b9bddc8 图表修改
continuous-integration/drone/push Build is running
2026-01-29 14:03:28 +08:00
he 7a5ad83cbb 修改简历
continuous-integration/drone/push Build is passing
2026-01-29 11:37:13 +08:00
he 2d2e0f6f2f 修改
continuous-integration/drone/push Build is passing
2026-01-29 11:18:33 +08:00
he 3b5a735abf Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is running
2026-01-29 11:17:47 +08:00
he e00f8b3549 修改 2026-01-29 11:17:44 +08:00
hang dee8c90319 修改项目文档编辑
continuous-integration/drone/push Build is passing
2026-01-29 09:09:29 +08:00
hang 47fd242200 CRC 提交调研表发送邮件bug
continuous-integration/drone/push Build is running
2026-01-29 09:03:33 +08:00
he 1181540302 添加IsImageQualityControl
continuous-integration/drone/push Build is passing
2026-01-28 16:08:18 +08:00
hang e6b3c662e2 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-01-27 15:18:51 +08:00
hang 3a06ecd66d DIR 生成修改PatientId 2026-01-27 15:18:50 +08:00
hang 7121799900 个人忘记密码后重置 错误次数提交
continuous-integration/drone/push Build is passing
2026-01-26 16:31:46 +08:00
hang c814736427 优化dron打包流程步骤,防止服务器一下子负载过大崩溃 2026-01-26 16:13:02 +08:00
hang 4ac9e83903 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8 2026-01-26 10:58:31 +08:00
hang 782270d462 用户MFA 缓存增加tag,重置密码,修改密码清楚该tag的缓存,从而登录时需要验证MFA 2026-01-26 10:58:29 +08:00
he ad77b66bdd Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-01-23 17:40:57 +08:00
he 77538ab7c2 修改 2026-01-23 17:40:56 +08:00
hang 2e1e042658 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-01-23 14:41:30 +08:00
hang 6736fa96fd 更新影像缩略图 2026-01-23 14:41:26 +08:00
he a965ab9336 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-01-23 13:55:20 +08:00
he 2ce112720a 添加IsBeMark 2026-01-23 13:55:20 +08:00
hang f8b7e7d764 修改时间支持带时区返回
continuous-integration/drone/push Build is passing
2026-01-21 11:48:13 +08:00
hang 1aa1189ae8 时间格式验证增加测试
continuous-integration/drone/push Build is passing
2026-01-21 10:23:30 +08:00
hang 9b90ded79f 修改时间验证
continuous-integration/drone/push Build is passing
2026-01-21 09:42:51 +08:00
hang 61ab33b3f3 efcore 重复跟踪问题解决,以及数组json列,无数据的时候会更新奇怪问题发现
continuous-integration/drone/push Build is passing
2026-01-20 13:18:46 +08:00
hang e6458e66df 修复时间格式bug
continuous-integration/drone/push Build is passing
2026-01-19 20:52:42 +08:00
hang 0ef7fd85d4 修改生成dir路径
continuous-integration/drone/push Build is passing
2026-01-19 17:58:47 +08:00
hang 2bf47515e7 修改模型绑定逻辑
continuous-integration/drone/push Build is passing
2026-01-19 15:33:57 +08:00
hang 296ecfa9d2 修改json 序列化绑定值
continuous-integration/drone/push Build is passing
2026-01-19 15:00:35 +08:00
hang 473d10533a 修改绑定模型逻辑
continuous-integration/drone/push Build is passing
2026-01-19 14:25:59 +08:00
hang 59ba1a6193 修改 可空类型传参处理,添加预处理模型绑定逻辑
continuous-integration/drone/push Build is passing
2026-01-19 13:52:06 +08:00
hang cf2b3f5954 修改有序退回,影响无序退回任务bug
continuous-integration/drone/push Build is passing
2026-01-19 11:03:36 +08:00
hang 7fed9cdceb 修改签署文档
continuous-integration/drone/push Build is passing
2026-01-16 17:23:09 +08:00
hang 0585966215 修改导表bug测试
continuous-integration/drone/push Build is passing
2026-01-16 16:53:09 +08:00
hang 7f9d6afc62 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is running
2026-01-16 16:50:51 +08:00
hang 6dce301ff7 外部人员管理端查看培训记录 2026-01-16 16:50:48 +08:00
he 7a2d95a77c Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is failing
2026-01-16 16:42:03 +08:00
he 74a8ac7575 添加文档发布时间 2026-01-16 16:42:00 +08:00
hang e9c6d123d3 修改导表
continuous-integration/drone/push Build is failing
2026-01-16 15:57:17 +08:00
hang 310c4a0f70 修改spm 同意重阅,影像bm重复跟踪问题-测试
continuous-integration/drone/push Build is passing
2026-01-15 17:17:23 +08:00
hang 571f81f271 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-01-15 13:47:37 +08:00
hang c3a9ca3ca2 增加日志记录 2026-01-15 13:47:36 +08:00
he 6874459046 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-01-15 10:28:02 +08:00
he f4af6d3218 修改 2026-01-15 10:28:00 +08:00
hang ec87ed1caf 返回前端其他部位
continuous-integration/drone/push Build is passing
2026-01-15 10:13:01 +08:00
hang 0bb5d09a23 修改入组PD报告
continuous-integration/drone/push Build is passing
2026-01-14 10:56:08 +08:00
hang f43ac9e649 修改异常返回
continuous-integration/drone/push Build is passing
2026-01-14 09:36:06 +08:00
hang 9e3180b326 修改配置文件
continuous-integration/drone/push Build is passing
2026-01-14 09:06:29 +08:00
hang 1011e704b9 修改aws配置
continuous-integration/drone/push Build is passing
2026-01-13 10:32:13 +08:00
hang 0afcaa7d70 清楚dir缓存修改
continuous-integration/drone/push Build is passing
2026-01-12 15:13:26 +08:00
hang 5fb889bac9 修改路径增加随机
continuous-integration/drone/push Build is passing
2026-01-12 14:48:09 +08:00
hang febf8868f9 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-01-12 11:39:55 +08:00
hang 665d9a27fb 修改IRC scp 匿名化影响 2026-01-12 11:39:51 +08:00
hang ccf16423d2 后期表索引类型更改,同时不影响之前的表
continuous-integration/drone/push Build is passing
2026-01-10 19:30:47 +08:00
hang a5155457b1 更改项目编号,修改邮件
continuous-integration/drone/push Build is passing
2026-01-09 14:46:39 +08:00
hang e358601232 EA 只看已完成的
continuous-integration/drone/push Build is running
2026-01-09 14:44:11 +08:00
hang 1514694e37 修改irc 匿名化
continuous-integration/drone/push Build is passing
2026-01-08 15:18:08 +08:00
hang fbd128138d 影像恢复代码
continuous-integration/drone/push Build is passing
2026-01-08 10:09:19 +08:00
hang eca7871684 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-01-07 16:41:59 +08:00
hang 8eb31d0c81 修改项目停止,归档 2026-01-07 16:41:56 +08:00
he 051094a742 修改
continuous-integration/drone/push Build is passing
2026-01-07 09:16:50 +08:00
he 9012b9c35c 修改
continuous-integration/drone/push Build is passing
2026-01-07 09:08:55 +08:00
he c49cd2e75e 修改
continuous-integration/drone/push Build is passing
2026-01-06 17:48:31 +08:00
he f8b36df86d 修改
continuous-integration/drone/push Build is running
2026-01-06 17:45:52 +08:00
he 8e5b7fb57c Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-01-06 17:31:31 +08:00
he b5a76804ad 修改 2026-01-06 17:31:30 +08:00
hang d4e79e0905 修改多帧处理
continuous-integration/drone/push Build is passing
2026-01-06 15:16:53 +08:00
hang 11e4c39dba 增加fursionCache 修改
continuous-integration/drone/push Build is passing
2026-01-06 15:05:17 +08:00
hang 02dd26188d Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-01-06 14:32:51 +08:00
hang 4f439c6ee7 scp缩略图修改 2026-01-06 14:32:50 +08:00
he bb36240c3c 修改
continuous-integration/drone/push Build is running
2026-01-06 14:32:08 +08:00
he e17b26b41d 代码修改
continuous-integration/drone/push Build is passing
2026-01-06 14:14:15 +08:00
he 3fce7b772e Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-01-06 13:39:41 +08:00
he 8ca740bae3 修改 2026-01-06 13:39:40 +08:00
hang ead07cb1ca Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build encountered an error
2026-01-06 10:27:53 +08:00
hang 951081edb7 修改IRC 多帧文件处理 2026-01-06 10:27:51 +08:00
he 7ceca5d802 修改
continuous-integration/drone/push Build is passing
2026-01-05 18:01:07 +08:00
he 86a3f1d2a5 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-01-05 17:46:27 +08:00
he b0a349b5ff 修改 2026-01-05 17:46:25 +08:00
hang 7523f646fe 修改orr 统计
continuous-integration/drone/push Build is passing
2026-01-05 16:26:10 +08:00
hang fcd0dd9b08 pcwg3 NA字典不一样
continuous-integration/drone/push Build is passing
2026-01-05 16:03:06 +08:00
hang 208cb40525 修改PCWG3
continuous-integration/drone/push Build is passing
2026-01-05 15:51:55 +08:00
hang aa2861c399 修改视图模型
continuous-integration/drone/push Build is passing
2026-01-05 15:39:17 +08:00
hang 4ee064dd14 查询,更新接口修改
continuous-integration/drone/push Build is passing
2026-01-05 15:13:58 +08:00
hang 8803a5f5b2 计算天数按照30.44算,增加控制字段
continuous-integration/drone/push Build is passing
2026-01-05 14:27:23 +08:00
hang 834f6be548 增加中心ORR统计
continuous-integration/drone/push Build is passing
2026-01-05 14:02:15 +08:00
hang df8292b2a5 修改统计值
continuous-integration/drone/push Build is passing
2026-01-05 13:27:54 +08:00
he 312c96ef8f Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-01-05 10:00:12 +08:00
he de29bd7d15 邮件发送修改 2026-01-05 10:00:09 +08:00
hang 3a2c78fe37 修改统计质询
continuous-integration/drone/push Build is passing
2026-01-04 16:46:10 +08:00
he 4465e19987 修改
continuous-integration/drone/push Build is passing
2026-01-04 13:17:10 +08:00
he afe84fdb5b 修改
continuous-integration/drone/push Build is passing
2026-01-04 13:04:46 +08:00
he 728727af54 修改
continuous-integration/drone/push Build is passing
2026-01-04 12:29:22 +08:00
he 987667f59f Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2026-01-04 10:41:54 +08:00
he af1ca2ffc1 修改 2026-01-04 10:41:52 +08:00
hang e71682f8fa 合并
continuous-integration/drone/push Build is passing
2026-01-04 10:37:37 +08:00
hang 339414694d 中心调研修改,oss测试 2026-01-04 10:35:15 +08:00
he 3ad6fad018 修改
continuous-integration/drone/push Build is passing
2025-12-31 16:05:40 +08:00
he adf82e862d 修改
continuous-integration/drone/push Build is passing
2025-12-31 14:52:33 +08:00
he c9e4f63e03 修改验证
continuous-integration/drone/push Build is passing
2025-12-31 11:48:28 +08:00
he ade2ffd9c0 修改
continuous-integration/drone/push Build is passing
2025-12-30 17:59:47 +08:00
he 3919d6a8a3 结构修改
continuous-integration/drone/push Build is failing
2025-12-30 17:38:15 +08:00
he 0fab295995 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is pending
2025-12-30 17:14:17 +08:00
he aa7eac50a5 报错修改 2025-12-30 17:14:16 +08:00
hang 23e0fae6ac Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-12-30 14:13:40 +08:00
hang 27233c1e01 修改日志保留天数以及统计 2025-12-30 14:13:38 +08:00
he c16905101f 修改
continuous-integration/drone/push Build is passing
2025-12-30 11:36:52 +08:00
he 6ef9708bb7 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-12-30 10:19:22 +08:00
he cd9769b39c 获取Series信息 修改 2025-12-30 10:19:21 +08:00
hang 109bdd263e 修改统计
continuous-integration/drone/push Build is passing
2025-12-30 10:09:20 +08:00
he c8e5a5173c 修改
continuous-integration/drone/push Build is passing
2025-12-29 16:58:24 +08:00
he e94e875a8a 修改
continuous-integration/drone/push Build is passing
2025-12-29 16:47:21 +08:00
he 9d7bd3c35b 图表修改
continuous-integration/drone/push Build is passing
2025-12-29 16:40:34 +08:00
he 5725e684c0 修改
continuous-integration/drone/push Build is passing
2025-12-29 13:42:35 +08:00
he 7927ad4161 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-12-29 13:37:10 +08:00
he e070a78872 图表功能添加 2025-12-29 13:37:08 +08:00
hang 1a6ec6b315 中心调研登录
continuous-integration/drone/push Build is passing
2025-12-29 11:07:10 +08:00
he 64ea537dae 修改
continuous-integration/drone/push Build is passing
2025-12-26 17:39:39 +08:00
he 990cf0ec41 修改
continuous-integration/drone/push Build is passing
2025-12-26 15:10:24 +08:00
he 9cdd70f491 发布
continuous-integration/drone/push Build is passing
2025-12-26 14:29:33 +08:00
he a8623ade44 修改
continuous-integration/drone/push Build is passing
2025-12-26 14:18:48 +08:00
he c0e19eaab4 修改
continuous-integration/drone/push Build is passing
2025-12-26 13:30:26 +08:00
he 43241709af 修改
continuous-integration/drone/push Build is passing
2025-12-26 11:16:24 +08:00
he 292263a60c Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-12-26 10:49:17 +08:00
he 760f75061e 修改 2025-12-26 10:49:16 +08:00
hang e11ca4a244 uat-靶段多选-1
continuous-integration/drone/push Build is passing
2025-12-26 10:42:02 +08:00
he 0266072c6a 医学审核单位添加
continuous-integration/drone/push Build is running
2025-12-26 10:41:12 +08:00
he 99b76704d3 修改
continuous-integration/drone/push Build is passing
2025-12-25 17:07:57 +08:00
he b39edee3eb 修改
continuous-integration/drone/push Build is passing
2025-12-25 16:12:13 +08:00
he 9e426308d0 修改
continuous-integration/drone/push Build is passing
2025-12-25 16:02:55 +08:00
he 416d76e03a 发布
continuous-integration/drone/push Build is passing
2025-12-25 15:51:53 +08:00
he ae867ae230 修改
continuous-integration/drone/push Build is passing
2025-12-25 15:44:05 +08:00
he e83dc93dca 修改
continuous-integration/drone/push Build is passing
2025-12-25 14:35:42 +08:00
he da220a3f5a 修改
continuous-integration/drone/push Build is passing
2025-12-25 14:15:05 +08:00
he 8141c64f87 修改
continuous-integration/drone/push Build is passing
2025-12-25 13:35:19 +08:00
he 5aaedffe20 修改融合
continuous-integration/drone/push Build is passing
2025-12-25 13:21:03 +08:00
he 9ec53e39ec Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-12-25 10:05:41 +08:00
he c93807d67f 修改答案 2025-12-25 10:05:39 +08:00
hang 6430f11188 返回标准类型-uat
continuous-integration/drone/push Build is passing
2025-12-25 10:01:20 +08:00
he bc4c523d88 修改
continuous-integration/drone/push Build is passing
2025-12-25 09:56:33 +08:00
he b7aaeee13e 修改拷贝病灶
continuous-integration/drone/push Build is passing
2025-12-24 17:46:55 +08:00
he 504304c341 融合修改
continuous-integration/drone/push Build is passing
2025-12-24 17:28:11 +08:00
he 941bddb771 维护分裂和融合病灶关系
continuous-integration/drone/push Build is passing
2025-12-24 15:17:01 +08:00
he 1990cebd58 修改融合验证
continuous-integration/drone/push Build is passing
2025-12-24 14:37:34 +08:00
he 041d6724e2 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-12-24 13:49:44 +08:00
he 0086ed4723 修改融合判定 2025-12-24 13:49:43 +08:00
hang b83dd29b20 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-12-24 10:53:47 +08:00
hang aa01c68887 修改拷贝病灶状态 2025-12-24 10:53:45 +08:00
he 00f5fa993a Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-12-24 09:31:21 +08:00
he 6b98e23e50 复制病灶修改 2025-12-24 09:31:18 +08:00
hang 3810b33c9e 修改模态
continuous-integration/drone/push Build is passing
2025-12-24 09:15:04 +08:00
he e58b0e5315 默认密码修改
continuous-integration/drone/push Build is passing
2025-12-24 09:05:26 +08:00
he a2548c4f95 邮件名称修改
continuous-integration/drone/push Build is passing
2025-12-23 17:36:59 +08:00
he 0151305ca3 修改
continuous-integration/drone/push Build is passing
2025-12-23 14:55:41 +08:00
he fbdac497fa 修改导入
continuous-integration/drone/push Build is passing
2025-12-23 10:25:44 +08:00
he 052ac1ff8d 导入修改
continuous-integration/drone/push Build is passing
2025-12-23 09:48:00 +08:00
he e113bc9fe1 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-12-23 09:26:49 +08:00
he 03b7f00bcd 修改计算 2025-12-23 09:26:47 +08:00
hang 3bedea2aba 修改邮件默认值
continuous-integration/drone/push Build is passing
2025-12-22 16:02:30 +08:00
hang 90a7e20be4 项目邮件配置修改
continuous-integration/drone/push Build is passing
2025-12-22 14:04:03 +08:00
he 4a02ae2e98 修改
continuous-integration/drone/push Build is passing
2025-12-22 10:19:36 +08:00
he 9852cacb01 导入修改
continuous-integration/drone/push Build is passing
2025-12-19 18:27:46 +08:00
he eda79fd455 修改导入验证
continuous-integration/drone/push Build is passing
2025-12-19 18:20:39 +08:00
he 45af1d7ba6 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-12-19 18:08:52 +08:00
he 7a4520086f 修改导入 2025-12-19 18:08:51 +08:00
hang a252142cef 下载展示列表-2
continuous-integration/drone/push Build is passing
2025-12-19 17:42:07 +08:00
hang 12a0f7ad82 修改退回过滤-ivus-靶段标注处理 2025-12-19 17:42:02 +08:00
hang 3ef3586bf7 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-12-19 17:06:43 +08:00
hang 4aecfb3bed 修改导表结果 2025-12-19 17:06:39 +08:00
hang 9a31e04fad linux服务器部署 / \ 对于路径的影响
continuous-integration/drone/push Build is passing
2025-12-19 13:30:02 +08:00
hang af333e72f5 测试dir 文件名\替换为/ 2025-12-19 13:29:58 +08:00
hang 26f1a2b8ee DIR 增加项目编号匹配 2025-12-19 13:29:53 +08:00
he 497d91f4a4 任务检查部位查看
continuous-integration/drone/push Build is passing
2025-12-19 10:17:53 +08:00
he bd2d698d9f 导入验证修改
continuous-integration/drone/push Build is passing
2025-12-19 10:05:01 +08:00
he b4c884a367 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-12-18 17:46:02 +08:00
he 337dec37b8 检查部位查看 2025-12-18 17:46:01 +08:00
hang c12ecfdcf0 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is running
2025-12-18 17:42:57 +08:00
hang 919e2bcdd7 修改统计bug 2025-12-18 17:42:54 +08:00
he 943dbb28a6 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is running
2025-12-18 17:42:12 +08:00
he 0926da24f7 修改查看裁判 检查部位 2025-12-18 17:42:10 +08:00
hang 6ad80f540d 项目邮件默认值
continuous-integration/drone/push Build is passing
2025-12-18 17:08:42 +08:00
hang 77b9b14cb6 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-12-18 16:57:58 +08:00
hang b9c55871b8 完整返回字典选项 2025-12-18 16:57:55 +08:00
he 848581452a Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-12-18 16:43:18 +08:00
he 2e53cdf29b 修改 2025-12-18 16:43:18 +08:00
hang d9bc12c8d4 增加百分号
continuous-integration/drone/push Build is passing
2025-12-18 16:39:20 +08:00
hang 234f8b074b oss-天然不支持真流式上传,除非大文件在本地盘,否则会占用内存 2025-12-18 16:39:17 +08:00
he 079c11a7e3 修改邮件
continuous-integration/drone/push Build is passing
2025-12-18 15:47:36 +08:00
he 9d9d65e381 修改文档发布
continuous-integration/drone/push Build is passing
2025-12-18 15:02:26 +08:00
he 2517c57778 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-12-18 14:43:48 +08:00
he 5d15ae6fe3 稽查修改 2025-12-18 14:43:47 +08:00
hang f9fbb59c53 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-12-18 13:50:23 +08:00
hang c55f7a427c 修改斑块明细表,增加单位-修改1 2025-12-18 13:47:54 +08:00
he 70b8c23e41 绑定标记
continuous-integration/drone/push Build is passing
2025-12-18 13:46:22 +08:00
he 2591a794bf 稽查调整
continuous-integration/drone/push Build is passing
2025-12-18 10:53:23 +08:00
he 1c4be1012a Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-12-18 10:45:50 +08:00
he 030eeb663b 稽查调整 2025-12-18 10:45:49 +08:00
hang 4b3180bdfe uat-ivus-oct-再次修改15
continuous-integration/drone/push Build is passing
2025-12-18 10:32:34 +08:00
hang 28b7cdb187 uat-ivus-oct-再次修改14 2025-12-18 10:32:31 +08:00
hang f57f6bb389 uat-ivus-oct-再次修改13 2025-12-18 10:32:28 +08:00
hang 673abdfec2 uat-ivus-oct-再次修改12 2025-12-18 10:32:25 +08:00
hang 1d4d068e08 uat-ivus-oct-再次修改11 2025-12-18 10:32:22 +08:00
hang 72d83e9cfd uat-ivus-oct-再次修改10 2025-12-18 10:32:18 +08:00
hang 883783250f uat-ivus-oct-再次修改9 2025-12-18 10:32:08 +08:00
he cc1ae55480 稽查调整
continuous-integration/drone/push Build is running
2025-12-18 10:30:04 +08:00
he 1bc3e77c1a 修改验证
continuous-integration/drone/push Build is passing
2025-12-17 14:17:40 +08:00
he e24d865ce3 修改验证1
continuous-integration/drone/push Build is passing
2025-12-17 14:06:20 +08:00
he 2762c83d84 验证修改
continuous-integration/drone/push Build is passing
2025-12-17 13:52:52 +08:00
he c58ad6142f 验证提交
continuous-integration/drone/push Build is passing
2025-12-17 13:41:17 +08:00
he c45f5ca083 验证修改
continuous-integration/drone/push Build is passing
2025-12-17 11:06:01 +08:00
he fc5c792328 验证修改
continuous-integration/drone/push Build is passing
2025-12-17 10:52:17 +08:00
he 1b00209c55 ivus计算触发
continuous-integration/drone/push Build is passing
2025-12-17 10:37:25 +08:00
he f199d18c4b Ivus 计算修改
continuous-integration/drone/push Build is passing
2025-12-17 10:17:47 +08:00
he 3f1d07727b 添加PAV其他问题答案
continuous-integration/drone/push Build is passing
2025-12-16 17:58:30 +08:00
he 64d70269f9 导入编号限制 只能123
continuous-integration/drone/push Build is passing
2025-12-16 17:37:46 +08:00
hang f0a71f8e70 ivus oct 全量一致性核查过滤空检查
continuous-integration/drone/push Build is passing
2025-12-16 13:30:24 +08:00
hang 8901479dd5 uat-ivus-oct-再次修改8 2025-12-16 13:21:38 +08:00
hang 389456c46b uat-ivus-oct-再次修改7 2025-12-16 13:21:34 +08:00
hang 40483dd4ee Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-12-16 09:57:29 +08:00
hang fe1d7724bc uat-ivus-oct-再次修改6 2025-12-16 09:57:22 +08:00
hang 9204183add uat-ivus-oct-再次修改5 2025-12-16 09:56:53 +08:00
hang db9895077c uat-ivus-oct-再次修改4 2025-12-16 09:55:36 +08:00
hang 36c086136f uat-ivus-oct-再次修改3 2025-12-16 09:55:32 +08:00
hang f3c78c1892 uat-ivus-oct-再次修改2 2025-12-16 09:55:28 +08:00
hang 6d538bf250 uat-ivus-oct-再次修改1 2025-12-16 09:55:24 +08:00
he 7a84eebf98 复制病灶
continuous-integration/drone/push Build is passing
2025-12-16 09:04:48 +08:00
he 064101b155 修改稽查
continuous-integration/drone/push Build is passing
2025-12-15 10:51:52 +08:00
he 91e7d04e4d Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-12-12 16:58:19 +08:00
he 711fa5a2b8 稽查管理 2025-12-12 16:58:17 +08:00
hang 9f7bc4be75 修改返回数据信息
continuous-integration/drone/push Build is passing
2025-12-12 15:16:51 +08:00
hang df7022b2e3 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-12-12 13:31:50 +08:00
hang 6ee51a7f43 增加搜索条件 2025-12-12 13:31:49 +08:00
he aed8a77e13 修改
continuous-integration/drone/push Build is passing
2025-12-12 10:40:10 +08:00
he bcfa22a818 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-12-11 17:19:47 +08:00
he 0840d57689 修改计算 2025-12-11 17:19:45 +08:00
hang deb7e77263 项目文档默认禁用
continuous-integration/drone/push Build is passing
2025-12-11 10:26:16 +08:00
hang aba477a49c Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-12-11 09:37:45 +08:00
hang a9e5a7754e 数据统计修改 2025-12-11 09:37:43 +08:00
he a2d4d100d3 修改邮件
continuous-integration/drone/push Build is passing
2025-12-10 16:36:24 +08:00
he 7f4f5e9d08 修改邮件发送
continuous-integration/drone/push Build is passing
2025-12-10 15:24:57 +08:00
he 55a2301bb5 稽查翻译修改 OCT06
continuous-integration/drone/push Build is passing
2025-12-10 13:59:18 +08:00
he 14ac852eee 修改
continuous-integration/drone/push Build is passing
2025-12-10 13:16:36 +08:00
he d2e53c7b8c 修改
continuous-integration/drone/push Build is passing
2025-12-10 13:11:45 +08:00
he 4a335d2a28 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is running
2025-12-10 10:43:22 +08:00
he 999fde1f5c 仲裁验证修改 2025-12-10 10:43:19 +08:00
hang 024e62103a ivus-oct-导表修改-uat-4
continuous-integration/drone/push Build is failing
2025-12-09 17:49:13 +08:00
hang 73889c51ae ivus-oct-导表修改-uat-3 2025-12-09 17:49:10 +08:00
hang 7a0b29bda6 ivus-oct-导表修改-uat-2
continuous-integration/drone/push Build is failing
2025-12-09 17:32:43 +08:00
hang a592a39fae ivus-oct-导表修改-uat-1 2025-12-09 17:32:40 +08:00
hang aa96ef66c6 修改多选导表
continuous-integration/drone/push Build is failing
2025-12-09 13:10:46 +08:00
he 2b24ce723e OCT05
continuous-integration/drone/push Build is failing
2025-12-09 10:02:44 +08:00
he 867ee32f91 稽查修改 OCT4
continuous-integration/drone/push Build is failing
2025-12-08 17:48:12 +08:00
he 662001accd 稽查修改 OCT4
continuous-integration/drone/push Build is failing
2025-12-08 17:17:57 +08:00
he 334b2fe440 OCT修改3
continuous-integration/drone/push Build is failing
2025-12-08 16:55:47 +08:00
he 2f728629f3 OCT 修改2
continuous-integration/drone/push Build is failing
2025-12-08 16:15:29 +08:00
he eb8628da5b OCT 添加1
continuous-integration/drone/push Build is failing
2025-12-08 15:41:09 +08:00
he 1427940224 邮件日志修改
continuous-integration/drone/push Build is failing
2025-12-08 13:36:41 +08:00
he 06f10e38f5 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-12-08 13:13:51 +08:00
he 5092f5204f 去除多余的逻辑 2025-12-08 13:13:50 +08:00
hang 13f4420e42 修改项目文档过滤
continuous-integration/drone/push Build is passing
2025-12-08 10:38:23 +08:00
he e0e6e60ae0 邮件端口移动到配置文件
continuous-integration/drone/push Build is passing
2025-12-08 10:14:21 +08:00
he 9cac49d539 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-12-05 14:54:24 +08:00
he 97d9aefbeb 项目邮件改为项目的发件信息 2025-12-05 14:54:22 +08:00
hang 340119f697 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-12-05 14:13:30 +08:00
hang a075eb34cd 修改签署文档查看 2025-12-05 14:13:30 +08:00
he 53913d3777 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-12-05 14:02:35 +08:00
he c1d79a7e9f 修改邮件中时间的问题 2025-12-05 14:02:33 +08:00
hang 3ee6f0ceaa Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is running
2025-12-05 14:01:57 +08:00
hang 1821d82ded 中心调研默认配置 2025-12-05 14:01:56 +08:00
hang 476ca95aa3 uat-项目影像下载-dir
continuous-integration/drone/push Build is passing
2025-12-05 13:50:11 +08:00
hang d8b799f057 uat-发布导表-1 2025-12-05 13:50:05 +08:00
he 4c4807c22e 添加字段
continuous-integration/drone/push Build is passing
2025-12-04 17:24:04 +08:00
he 8a9afc2d9a Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-12-04 14:16:50 +08:00
he 6fcb670a89 OCT导入修改1 2025-12-04 14:16:47 +08:00
hang d224b519f7 修改中心调研返回配置对象bug
continuous-integration/drone/push Build is passing
2025-12-04 10:19:23 +08:00
hang b382595767 修改EA 下拉框返回数据
continuous-integration/drone/push Build is passing
2025-12-03 17:53:43 +08:00
hang 64ff6f73d4 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is failing
2025-12-03 16:21:29 +08:00
hang e1632b58ac lugano 长短经导表修改 2025-12-03 16:21:26 +08:00
he 7e39692cc4 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is failing
2025-12-03 15:13:41 +08:00
he 8de44fb7b3 添加重阅复制的逻辑 2025-12-03 15:13:38 +08:00
hang 647aee010a 赋值拷贝表单
continuous-integration/drone/push Build is running
2025-12-03 15:12:04 +08:00
hang 296459d38a 全量一致性核查默认排序
continuous-integration/drone/push Build is passing
2025-12-03 13:55:12 +08:00
hang 7941328a29 CDISC followUp
continuous-integration/drone/push Build is passing
2025-12-03 13:29:27 +08:00
hang 38d9a21a5d lugano 整体肿瘤pd
continuous-integration/drone/push Build is passing
2025-12-03 11:33:11 +08:00
he 7d88f49e68 修改被评估为NE的单个靶病灶
continuous-integration/drone/push Build is passing
2025-12-03 10:39:15 +08:00
he 3c46215a3c Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-12-03 10:19:36 +08:00
he 137ed07b2b 修改 2025-12-03 10:19:33 +08:00
hang 6f05850fbc 全量一致性核查增加列
continuous-integration/drone/push Build is passing
2025-12-02 18:04:38 +08:00
hang 80447f5c7a 修改参数名
continuous-integration/drone/push Build is passing
2025-12-02 17:42:06 +08:00
hang 971d94495a Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-12-02 17:30:05 +08:00
hang 77790a1cb2 中心调研增加字段 2025-12-02 17:30:02 +08:00
he ecfadcb5a3 查询基线病灶
continuous-integration/drone/push Build is passing
2025-12-02 17:24:32 +08:00
he d9e3b642a5 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-12-02 16:32:00 +08:00
he e56e269f2e 修改 2025-12-02 16:31:59 +08:00
hang 98f7ab8c02 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-12-02 15:35:16 +08:00
hang 5d33bd03a7 修改lugano rs导表肿瘤学 2025-12-02 15:35:13 +08:00
he 55882aa0ba Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-12-02 11:27:15 +08:00
he 43d1a94f22 添加字段 2025-12-02 11:27:11 +08:00
hang 53b2b9156f Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-12-02 10:54:31 +08:00
hang fafc075735 uat-lugano NA% 2025-12-02 10:54:28 +08:00
hang 09ee238e50 uat-ivus-增加标识-31
continuous-integration/drone/push Build is passing
2025-12-02 09:18:21 +08:00
hang 7be3813527 CDISC-lugano
continuous-integration/drone/push Build is passing
2025-12-01 14:38:11 +08:00
hang e6491a6203 修改导表错误-uat
continuous-integration/drone/push Build is passing
2025-12-01 13:21:40 +08:00
he 4955a24d6f 修改
continuous-integration/drone/push Build is passing
2025-12-01 10:52:14 +08:00
he 425f53b74c Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-11-28 17:27:13 +08:00
he 2ed9595186 修改 2025-11-28 17:27:12 +08:00
hang 4d1e3beda6 uat-标注-30
continuous-integration/drone/push Build is passing
2025-11-28 16:47:08 +08:00
hang 8b0e1b8988 uat-标注-29 2025-11-28 16:47:04 +08:00
he c77682a9ad 修改
continuous-integration/drone/push Build is passing
2025-11-28 16:15:58 +08:00
he 8e0ff1f2b2 修改
continuous-integration/drone/push Build is passing
2025-11-28 15:31:23 +08:00
he b5e027a553 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is running
2025-11-28 15:28:16 +08:00
he ef712949ad 修改 2025-11-28 15:28:14 +08:00
hang b429b2cf8c uat-标注-28
continuous-integration/drone/push Build is passing
2025-11-28 14:19:24 +08:00
hang 9fb95c3030 uat-标注-27 2025-11-28 14:19:21 +08:00
hang 6883d47508 uat-标注-26 2025-11-28 14:19:19 +08:00
hang dae44745ca uat-标注-25 2025-11-28 14:19:16 +08:00
hang 24b7d2e1b9 uat-标注-24 2025-11-28 14:19:13 +08:00
hang 21ac6850d2 uat-标注-23 2025-11-28 14:19:10 +08:00
hang 8b69a07b93 uat-标注-22 2025-11-28 14:19:07 +08:00
hang f92bf347e3 uat-标注-21 2025-11-28 14:18:51 +08:00
hang 78669051de uat-标注-20 2025-11-28 14:18:05 +08:00
hang ea90b2b38b uat-标注-19 2025-11-28 14:18:02 +08:00
hang bd61a0f2e9 uat-标注-18 2025-11-28 14:17:59 +08:00
hang 888ad8897e uat-标注-17 2025-11-28 14:17:56 +08:00
hang eb18021988 uat-标注-16 2025-11-28 14:17:53 +08:00
he 7b243a9615 修改
continuous-integration/drone/push Build is passing
2025-11-28 14:07:54 +08:00
he 5716c4169a Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is failing
2025-11-28 11:36:49 +08:00
he abb9f47646 邮件临时修改 2025-11-28 11:36:47 +08:00
hang 9a5221ef7d 调整ivus导表位置
continuous-integration/drone/push Build is failing
2025-11-27 17:31:56 +08:00
hang d9eaa06de3 中心调研返回空对象,方便前端渲染
continuous-integration/drone/push Build is failing
2025-11-27 15:20:56 +08:00
hang 4f91874b82 uat-标注-15
continuous-integration/drone/push Build is passing
2025-11-27 10:57:51 +08:00
hang 139438133d uat-标注-14 2025-11-27 10:57:48 +08:00
hang 6705cf0fc0 uat-标注-13 2025-11-27 10:57:46 +08:00
hang 00255e902c uat-标注-12 2025-11-27 10:57:43 +08:00
hang 71e867156f uat-标注-11 2025-11-27 10:57:41 +08:00
hang 658f437f73 uat-标注-10 2025-11-27 10:57:39 +08:00
hang 95bf613263 非dicom检查预览 2025-11-27 10:57:36 +08:00
hang 03d61347ca uat-标注-8 2025-11-27 10:57:32 +08:00
hang 050a0875fe uat-标注-7 2025-11-27 10:57:30 +08:00
hang d9c46acb03 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is running
2025-11-27 10:56:07 +08:00
hang f19fc0e92f ivus-导表修改 2025-11-27 10:56:05 +08:00
he 3bc4b86749 ExtImaging 修改
continuous-integration/drone/push Build is passing
2025-11-26 16:55:05 +08:00
he 43455da7bc Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-11-26 11:37:05 +08:00
he 0030eac22e 修改 2025-11-26 11:37:00 +08:00
hang 671c071a45 uat-标注增加访视Id-6
continuous-integration/drone/push Build is passing
2025-11-25 18:01:23 +08:00
hang 3419015542 uat-标注增加访视Id-5 2025-11-25 18:01:20 +08:00
hang 3eea6cf8b7 uat-标注增加访视Id-4 2025-11-25 18:01:17 +08:00
hang 2c407c6e4e uat-标注增加访视Id-3 2025-11-25 18:01:15 +08:00
hang ab90591e7c uat-标注增加访视Id-2 2025-11-25 18:01:12 +08:00
hang 33a2b7f373 uat-标注增加访视Id-1 2025-11-25 18:01:09 +08:00
hang 9158f28998 医学审核对话导表 2025-11-25 09:56:51 +08:00
hang 680a6cb437 非dicom bodyPart 修改
continuous-integration/drone/push Build is passing
2025-11-24 15:40:12 +08:00
hang 21c19f4b6d Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-11-24 14:29:32 +08:00
hang 8c03167f7e 修改检查部位bug 2025-11-24 14:29:24 +08:00
he b629b2eec4 修改报告图表
continuous-integration/drone/push Build is passing
2025-11-24 13:37:09 +08:00
he 01af8f72d3 修改
continuous-integration/drone/push Build is passing
2025-11-21 11:35:42 +08:00
he 4d10db054c 修改
continuous-integration/drone/push Build is passing
2025-11-21 11:17:54 +08:00
he a019a542b0 修改
continuous-integration/drone/push Build is passing
2025-11-21 11:12:11 +08:00
he b42325337e 修改
continuous-integration/drone/push Build is passing
2025-11-20 14:28:54 +08:00
he a3152bb6e1 页面修改
continuous-integration/drone/push Build is passing
2025-11-20 13:34:02 +08:00
he 4ec0441d28 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-11-20 10:34:16 +08:00
he 9adc8a93b1 修改 2025-11-20 10:34:15 +08:00
hang 3f3d1a562b Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-11-19 17:41:05 +08:00
hang 1b6ba91240 修改肿瘤CDISC 部位 2025-11-19 17:41:01 +08:00
he c406976a6d 修改
continuous-integration/drone/push Build is passing
2025-11-19 16:30:13 +08:00
he d213fa7a99 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is running
2025-11-19 16:29:16 +08:00
he 60707150a6 对话拼接 2025-11-19 16:29:09 +08:00
hang 008906d245 subjectcode 查询掉了
continuous-integration/drone/push Build is passing
2025-11-19 14:23:43 +08:00
hang 762b8b7425 分页处理
continuous-integration/drone/push Build is passing
2025-11-19 14:05:08 +08:00
hang 24228428fa 返回subject 标注的非dicom检查数量
continuous-integration/drone/push Build is passing
2025-11-19 13:59:19 +08:00
he 9148651556 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-11-19 10:33:49 +08:00
he 1c9c3eb893 修改 2025-11-19 10:33:48 +08:00
hang 17e0517e5b Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-11-19 10:24:46 +08:00
hang 7662880352 oct-ivus-修改1 2025-11-19 10:24:43 +08:00
he c3c433244e 报告修改
continuous-integration/drone/push Build is passing
2025-11-18 17:25:07 +08:00
he df65d592ad 添加ShowChartTypeEnum
continuous-integration/drone/push Build is passing
2025-11-18 16:18:59 +08:00
he 7873523c1b 修改
continuous-integration/drone/push Build is passing
2025-11-18 15:48:36 +08:00
he 8b5800b32e 新增受试者需要确认基线计划
continuous-integration/drone/push Build is passing
2025-11-18 11:43:53 +08:00
he 2e46791466 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-11-18 10:40:10 +08:00
he fc0ef2e89f 修改授权 2025-11-18 10:40:08 +08:00
hang 1de687cbf3 放开lugano CDISC 导表
continuous-integration/drone/push Build is passing
2025-11-17 13:39:04 +08:00
he 3a017dbfa3 修改发件人名称
continuous-integration/drone/push Build is passing
2025-11-17 11:09:17 +08:00
he 1f828b403b 发送邮件的时候 替换项目发件人姓名
continuous-integration/drone/push Build is failing
2025-11-17 09:20:55 +08:00
he f75fafaa42 修改路径
continuous-integration/drone/push Build is passing
2025-11-14 15:59:13 +08:00
he 7c561a4656 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-11-14 14:52:54 +08:00
he 033b849bdc 添加字段 2025-11-14 14:52:53 +08:00
hang 8ae0b0d621 提交后允许删除检查特殊逻辑修改
continuous-integration/drone/push Build is passing
2025-11-14 13:26:33 +08:00
he b6bad061b7 修改
continuous-integration/drone/push Build is passing
2025-11-14 11:41:36 +08:00
he 1dd8515ac2 修改
continuous-integration/drone/push Build is passing
2025-11-13 11:30:42 +08:00
he 9f3bb29004 添加 获取报告图表
continuous-integration/drone/push Build is passing
2025-11-13 10:15:43 +08:00
he 52dd676cf9 修改
continuous-integration/drone/push Build is passing
2025-11-13 09:25:03 +08:00
he 342b567e2c 修改
continuous-integration/drone/push Build is passing
2025-11-12 17:00:25 +08:00
he 35ee76be0b Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-11-12 16:39:04 +08:00
he b4d40a4770 修改映射 2025-11-12 16:39:03 +08:00
hang 84bfcb4d3e Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-11-12 16:32:35 +08:00
hang f030ed9b66 修改访视点备注赋值日期 2025-11-12 16:32:33 +08:00
he c6920a1995 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-11-12 16:19:15 +08:00
he 2d681ce6bc 修改 2025-11-12 16:19:14 +08:00
hang 3981f2cd50 百分比处理
continuous-integration/drone/push Build is passing
2025-11-12 16:15:05 +08:00
hang 2b961c7985 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-11-12 15:47:50 +08:00
hang fb2fdd2c27 导表修改排序 2025-11-12 15:47:47 +08:00
he f6bcefd1e8 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-11-12 15:21:34 +08:00
he 1d7b28e7f9 新加阅片关键点 2025-11-12 15:21:31 +08:00
hang 3ea2bdfc92 修改裁判备注
continuous-integration/drone/push Build is passing
2025-11-12 15:14:29 +08:00
hang 4aa0161c41 co表 导出质量评估,备注是空那么就忽略
continuous-integration/drone/push Build is passing
2025-11-12 14:29:14 +08:00
hang ff4ab39025 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-11-12 13:38:09 +08:00
hang bb35ae1b9a 修改输出日期bug 2025-11-12 13:38:08 +08:00
he 0e0ce59d74 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-11-12 11:45:05 +08:00
he 82d3157e2a 添加表 2025-11-12 11:45:03 +08:00
hang 12cb53b167 导表增加备注
continuous-integration/drone/push Build is passing
2025-11-12 11:35:36 +08:00
hang 5fc53a1999 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-11-11 16:37:08 +08:00
hang b712abd1d5 导表co表修改 2025-11-11 16:37:06 +08:00
he a1c13b5850 添加验证
continuous-integration/drone/push Build is passing
2025-11-11 16:30:39 +08:00
he a0fe7be308 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-11-11 14:08:57 +08:00
he bbfa966b57 修改 2025-11-11 14:08:54 +08:00
hang 6d11fc68b4 导出百分比修改
continuous-integration/drone/push Build is passing
2025-11-11 13:20:30 +08:00
hang eef764dd90 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-11-11 10:58:09 +08:00
hang 8423b00bec 修改导表报错 2025-11-11 10:58:07 +08:00
he dbaac006c7 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-11-11 10:45:18 +08:00
he 510c73dd24 修改复制病灶的逻辑 2025-11-11 10:45:16 +08:00
hang 41486798d2 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-11-11 09:58:34 +08:00
hang f40faa5519 修改病灶排序,和拍片日期以及PD 2025-11-11 09:58:32 +08:00
he eecfe6dcae 修改
continuous-integration/drone/push Build is passing
2025-11-10 18:06:53 +08:00
he 8a004ef35f 修改
continuous-integration/drone/push Build is passing
2025-11-10 18:01:48 +08:00
he 369805e6c6 修改
continuous-integration/drone/push Build is passing
2025-11-10 17:37:32 +08:00
he 93b93f6a3a 修改
continuous-integration/drone/push Build is passing
2025-11-10 16:08:12 +08:00
he 9fbee7e0c9 修改
continuous-integration/drone/push Build is passing
2025-11-10 14:16:06 +08:00
he 46704d5299 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-11-10 09:35:33 +08:00
he 4225014cdd 代码修改 2025-11-10 09:35:31 +08:00
hang ae36d4680a 分裂类型对应
continuous-integration/drone/push Build is passing
2025-11-07 15:07:27 +08:00
hang d48f736392 修改tu表bug
continuous-integration/drone/push Build is passing
2025-11-07 14:54:25 +08:00
hang b0d8a49929 分裂病灶加个判断
continuous-integration/drone/push Build is passing
2025-11-07 14:48:35 +08:00
hang e45791c676 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-11-07 14:41:03 +08:00
hang ca425c602b 修改病灶排序 2025-11-07 14:41:01 +08:00
he 44e7ffaeeb 修改筛选
continuous-integration/drone/push Build is passing
2025-11-07 14:31:29 +08:00
he cf42b2e57f 修改
continuous-integration/drone/push Build is passing
2025-11-07 14:20:14 +08:00
he 840de08e69 文档
continuous-integration/drone/push Build is passing
2025-11-07 13:40:25 +08:00
he 929903aa1b 修改
continuous-integration/drone/push Build is passing
2025-11-07 13:19:21 +08:00
he 5eafcc9c8c Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-11-07 11:47:59 +08:00
he a1657f5827 修改 2025-11-07 11:47:57 +08:00
hang dff2e712ad 中心调研发送邮件bug 修改
continuous-integration/drone/push Build is passing
2025-11-07 10:12:24 +08:00
hang a5f3430965 MFA bug 修改
continuous-integration/drone/push Build is passing
2025-11-07 09:19:52 +08:00
hang 5059d66aa1 修改传递参数
continuous-integration/drone/push Build is passing
2025-11-06 15:25:52 +08:00
hang 668af5ee0b Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-11-06 15:05:10 +08:00
hang a70caefe01 肿瘤CDISC 导出初步提交 2025-11-06 15:05:09 +08:00
he 64bd6a5604 修改
continuous-integration/drone/push Build is passing
2025-11-06 13:54:05 +08:00
he b68deafa82 修改
continuous-integration/drone/push Build is passing
2025-11-06 11:09:16 +08:00
he d29c150081 修改
continuous-integration/drone/push Build is passing
2025-11-06 10:47:57 +08:00
he 4d3ecd05dc 邮件修改
continuous-integration/drone/push Build is passing
2025-11-06 09:59:19 +08:00
he 143d7f0c4b 修改
continuous-integration/drone/push Build is passing
2025-11-05 17:05:09 +08:00
he 4d2f04b30e 邮件日志
continuous-integration/drone/push Build is passing
2025-11-05 16:58:02 +08:00
he 955ca4a1a6 添加双屏
continuous-integration/drone/push Build is passing
2025-11-05 13:23:30 +08:00
he 6e92895043 修改
continuous-integration/drone/push Build is passing
2025-11-05 11:46:32 +08:00
he 6102309828 修改
continuous-integration/drone/push Build is passing
2025-11-05 11:37:03 +08:00
he 8f34644d45 修改
continuous-integration/drone/push Build is passing
2025-11-04 16:08:57 +08:00
he f347b34f59 修改
continuous-integration/drone/push Build is passing
2025-11-04 11:11:37 +08:00
he dc7e71dc74 修改
continuous-integration/drone/push Build is passing
2025-11-04 10:48:43 +08:00
he 749bf95761 修改
continuous-integration/drone/push Build is passing
2025-11-04 10:38:05 +08:00
he a67472c9b4 阅片期修改
continuous-integration/drone/push Build is running
2025-11-04 10:37:15 +08:00
he 32c8d70179 修改bug
continuous-integration/drone/push Build is passing
2025-11-03 14:47:56 +08:00
he 510dfcac30 添加阅片期计划
continuous-integration/drone/push Build is passing
2025-11-03 14:22:41 +08:00
he 9b59a55423 添加 阅片期添加类型字段
continuous-integration/drone/push Build is passing
2025-10-31 18:16:14 +08:00
he 0bd269f93e 修改数据库可插入null
continuous-integration/drone/push Build is passing
2025-10-31 16:56:45 +08:00
he e55a13cfb1 修改
continuous-integration/drone/push Build is passing
2025-10-31 16:16:55 +08:00
he 1617de2fc1 修改
continuous-integration/drone/push Build is passing
2025-10-31 13:49:13 +08:00
he 40c3ea65ff 排序处理
continuous-integration/drone/push Build is passing
2025-10-30 14:19:47 +08:00
he 541e000183 PCWG修改
continuous-integration/drone/push Build is passing
2025-10-29 13:55:05 +08:00
he 0e93894ed8 修改
continuous-integration/drone/push Build is passing
2025-10-29 13:24:23 +08:00
he 7f1621915f 邮件修改
continuous-integration/drone/push Build is passing
2025-10-29 10:30:32 +08:00
he 70e4829f32 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-10-29 09:50:40 +08:00
he c77b3d74c8 代码修改 2025-10-29 09:50:37 +08:00
hang 1f4ea09bc6 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-10-28 15:15:21 +08:00
hang c45020f24f 修改数组默认值 2025-10-28 15:15:20 +08:00
he ca679057c4 邮件代码提交
continuous-integration/drone/push Build is passing
2025-10-28 14:35:43 +08:00
he 456793293b 添加实体
continuous-integration/drone/push Build is failing
2025-10-28 14:32:40 +08:00
he 75d3908911 添加邮件日志表
continuous-integration/drone/push Build is passing
2025-10-28 14:21:38 +08:00
he a7fbd00530 邮件日志
continuous-integration/drone/push Build is passing
2025-10-28 11:40:45 +08:00
he 785d5ec869 重置修改
continuous-integration/drone/push Build is passing
2025-10-28 09:38:34 +08:00
he c5feb98ed8 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is running
2025-10-28 09:38:04 +08:00
he 121b1d4eb4 邮件 2025-10-28 09:37:49 +08:00
hang 8e2b99333c 中心调研邮件修改
continuous-integration/drone/push Build is passing
2025-10-28 09:27:12 +08:00
he 5bd055c77d 修改
continuous-integration/drone/push Build is passing
2025-10-27 13:22:05 +08:00
he 5ea9d63cb9 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-10-27 11:22:34 +08:00
he 0134d6c723 修改比例尺修改答案 2025-10-27 11:22:32 +08:00
hang a9c80a670b Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-10-27 10:48:35 +08:00
hang 998b92aa79 评估统计初步增加 2025-10-27 10:48:31 +08:00
he 60ce8c45b2 修改
continuous-integration/drone/push Build is passing
2025-10-24 16:26:41 +08:00
he a66d25f564 添加IsHaveBindingQuestion
continuous-integration/drone/push Build is passing
2025-10-24 16:11:16 +08:00
he 91b66660a6 修改答案
continuous-integration/drone/push Build is passing
2025-10-24 14:19:04 +08:00
he 1031222e47 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-10-24 10:45:12 +08:00
he 2005eacaa9 添加字段 2025-10-24 10:45:09 +08:00
hang b581afebe6 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-10-24 10:12:24 +08:00
hang 1532ac4a7e 修改一致性全量核查 2025-10-24 10:12:22 +08:00
he 231b3f71c5 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-10-23 16:58:05 +08:00
he 308f5140a1 修改比例尺 2025-10-23 16:58:03 +08:00
hang ef673fc44b Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-10-23 16:27:24 +08:00
hang f347b0cace 修改一致性核查bug 2025-10-23 16:27:23 +08:00
he 102cf5f6dc 修改
continuous-integration/drone/push Build is passing
2025-10-23 15:15:38 +08:00
he d9ca71009f 修改标记返回
continuous-integration/drone/push Build is passing
2025-10-23 14:59:50 +08:00
he 114ee50273 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is running
2025-10-23 14:58:32 +08:00
he 54bb66f738 邮件添加 2025-10-23 14:58:28 +08:00
hang 19f52bc133 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-10-23 14:13:03 +08:00
hang 2439dec9fb 中心调研额外配置 2025-10-23 14:12:59 +08:00
he fb0fd7d91f 修改
continuous-integration/drone/push Build is passing
2025-10-22 17:39:54 +08:00
he 3765b390a6 添加字段
continuous-integration/drone/push Build is running
2025-10-22 17:38:35 +08:00
he f6be3186e7 修改非Dicom
continuous-integration/drone/push Build is passing
2025-10-22 15:12:37 +08:00
he 7399568e36 添加标记修改
continuous-integration/drone/push Build is passing
2025-10-22 14:14:36 +08:00
he 37d798627a Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-10-22 13:57:32 +08:00
he fe2a375b4e 标记代码修改 2025-10-22 13:57:29 +08:00
hang 70443b7b8d Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-10-22 10:25:13 +08:00
hang 7a37c01b7f 归档增加字段-三个影像层级表 2025-10-22 10:25:11 +08:00
he 7215c75f5a 修改
continuous-integration/drone/push Build is passing
2025-10-21 15:50:18 +08:00
he a34c47dc3c 获取当前版本的用户协议和隐私采集
continuous-integration/drone/push Build is passing
2025-10-21 15:40:42 +08:00
he 6fdad9a482 添加接口
continuous-integration/drone/push Build is passing
2025-10-21 14:34:20 +08:00
he 08e62e4500 修改
continuous-integration/drone/push Build is passing
2025-10-21 13:34:13 +08:00
he 3cfb4556f3 修改
continuous-integration/drone/push Build is running
2025-10-21 13:32:36 +08:00
he b8e3e83b80 修改
continuous-integration/drone/push Build is passing
2025-10-21 13:24:30 +08:00
he 7e9bf65f9f 修改
continuous-integration/drone/push Build is passing
2025-10-21 13:16:59 +08:00
he 5197e49c1f 添加适用的标准
continuous-integration/drone/push Build is failing
2025-10-21 13:09:53 +08:00
he 7880836f39 修改
continuous-integration/drone/push Build is passing
2025-10-21 11:39:29 +08:00
he 487eb9dca8 保存答案和非Dicom标记绑定关系
continuous-integration/drone/push Build is passing
2025-10-21 11:29:11 +08:00
he 473cee0e30 融合修改
continuous-integration/drone/push Build is passing
2025-10-21 10:40:03 +08:00
he 817cdcc8f7 修改
continuous-integration/drone/push Build is passing
2025-10-21 10:27:33 +08:00
he bf03430958 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is running
2025-10-21 10:25:52 +08:00
he 58321c4c82 非Dicom标记绑定 2025-10-21 10:25:49 +08:00
hang 8cabc8c96a Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-10-20 17:41:02 +08:00
hang 7f050a1dd6 稽查通用缓存添加 2025-10-20 17:41:00 +08:00
he e5e2a0394b 用户隐私修改
continuous-integration/drone/push Build is passing
2025-10-20 17:34:34 +08:00
he a56ab0680b 修改自定义标准 病灶复制前值
continuous-integration/drone/push Build is passing
2025-10-20 17:15:08 +08:00
he ee13d0ae2e Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-10-20 17:05:38 +08:00
he ee2c66c8fb 预设和复制病灶修改 2025-10-20 17:05:37 +08:00
hang f1fb2068de 全量一致性核查初次提交
continuous-integration/drone/push Build is passing
2025-10-20 16:49:46 +08:00
he 95e0239e42 修改预设与复制的冲突
continuous-integration/drone/push Build is passing
2025-10-20 16:19:32 +08:00
he 0b28ef5461 添加字段
continuous-integration/drone/push Build is passing
2025-10-20 15:52:18 +08:00
he 68fe2b75dc Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is failing
2025-10-20 15:34:14 +08:00
he ecfeee1693 添加用户协议与隐私政策 2025-10-20 15:34:11 +08:00
hang 9cda4360ca MFA 需求修改
continuous-integration/drone/push Build is failing
2025-10-20 13:40:52 +08:00
hang 18b9c08101 修改项目编辑映射
continuous-integration/drone/push Build is passing
2025-10-17 16:04:31 +08:00
he d4e89aec20 Lugano 分裂修改
continuous-integration/drone/push Build is passing
2025-10-17 14:53:04 +08:00
he 01a3a9057b 异地登录修改
continuous-integration/drone/push Build is passing
2025-10-17 11:32:17 +08:00
he 2107a42e37 修改验证重复提交
continuous-integration/drone/push Build is passing
2025-10-17 09:40:36 +08:00
he d96e746db7 修改接口重复验证
continuous-integration/drone/push Build is passing
2025-10-16 14:53:43 +08:00
he be034d4d8b 修改
continuous-integration/drone/push Build is passing
2025-10-16 14:46:34 +08:00
he ca44b4786a Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-10-16 14:28:17 +08:00
he 74056167d6 重复请求拦截修改 2025-10-16 14:28:15 +08:00
hang e6aec6b559 MFA状态持久管理修改
continuous-integration/drone/push Build is passing
2025-10-16 14:10:11 +08:00
hang 9a488610da Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-10-16 13:16:34 +08:00
hang f82420b7ef 修改备注 2025-10-16 13:16:32 +08:00
he fbeaec2820 修改
continuous-integration/drone/push Build is passing
2025-10-16 09:23:10 +08:00
he b4907bfe9b 代码修改
continuous-integration/drone/push Build is passing
2025-10-15 17:41:43 +08:00
he 6e75e45942 修改
continuous-integration/drone/push Build is passing
2025-10-15 17:23:18 +08:00
he 3292b9f978 请求频繁验证
continuous-integration/drone/push Build is passing
2025-10-15 15:56:32 +08:00
he 1959045ab2 修改计算
continuous-integration/drone/push Build is passing
2025-10-15 10:12:46 +08:00
he 6a99e3104a 验证修改
continuous-integration/drone/push Build is passing
2025-10-14 14:28:06 +08:00
he ab91d360b1 服务修改
continuous-integration/drone/push Build is passing
2025-10-14 14:20:23 +08:00
he 50a4a82819 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-10-14 14:07:08 +08:00
he 498c02431b 修改luganno 2025-10-14 14:07:06 +08:00
hang fd54d64ae3 用户发送邮件-一致性分析导出代码删除了
continuous-integration/drone/push Build is passing
2025-10-14 10:44:48 +08:00
hang ac80e3a455 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-10-13 16:28:56 +08:00
hang c6a55949b3 增加查询条件修改 2025-10-13 16:28:53 +08:00
he 18547cdec1 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-10-13 16:19:19 +08:00
he d29707c8ee 修改 2025-10-13 16:19:17 +08:00
hang c7e29116dd 稽查记录管理修改-是否查看培训记录
continuous-integration/drone/push Build is passing
2025-10-13 16:01:21 +08:00
hang a85cc1236c Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-10-11 15:38:41 +08:00
hang 7b4c603fa6 修改访视计划确认 2025-10-11 15:38:34 +08:00
he 67c93e654e Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-10-10 15:49:31 +08:00
he 9b0c874314 添加表格预设 2025-10-10 15:49:29 +08:00
hang d172e48c79 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-10-10 14:24:08 +08:00
hang 2a2419af90 记录匿名登录,退出账号用户名 2025-10-10 14:24:04 +08:00
he 7e39094b13 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-10-09 15:38:32 +08:00
he 8936e9db99 PCWG LastTaskState 取值修改 2025-10-09 15:38:30 +08:00
hang bd92d48232 修改备注
continuous-integration/drone/push Build is passing
2025-10-09 10:54:55 +08:00
hang 57f7b29cc7 合并解决
continuous-integration/drone/push Build is passing
2025-09-30 10:02:33 +08:00
hang f55f498626 增加gpt 方法 2025-09-30 10:01:20 +08:00
he 955fe25d30 重新发布
continuous-integration/drone/push Build is passing
2025-09-29 15:19:34 +08:00
he 86c24e6002 修改
continuous-integration/drone/push Build is passing
2025-09-29 15:12:06 +08:00
hang b835a028ad 修改取消授权
continuous-integration/drone/push Build is passing
2025-09-29 13:58:33 +08:00
hang c8d5b94154 人员下拉框过滤
continuous-integration/drone/push Build is passing
2025-09-29 13:42:27 +08:00
hang d03d294e97 优化列表查询,以及稽查记录过滤
continuous-integration/drone/push Build is passing
2025-09-29 13:39:06 +08:00
he 28014139f5 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-09-29 11:01:48 +08:00
he b42a86922e 拷贝和移动 2025-09-29 11:01:46 +08:00
hang 09bf2b4d91 重建闭包表修改
continuous-integration/drone/push Build is passing
2025-09-29 10:44:40 +08:00
hang 3685686447 EA 查看记录 用户 文件级别修改
continuous-integration/drone/push Build is running
2025-09-29 10:42:41 +08:00
hang b6d2128034 编译错误
continuous-integration/drone/push Build is passing
2025-09-29 10:31:57 +08:00
hang 9545e699c2 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is running
2025-09-29 10:30:10 +08:00
hang 87955d2ca0 修改用户列表选择 2025-09-29 10:30:09 +08:00
he 0a8573091e Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is failing
2025-09-29 10:21:46 +08:00
he a713ffdc61 移动文件夹修改 2025-09-29 10:21:44 +08:00
hang 406235cc03 修改稽查记录人员管理
continuous-integration/drone/push Build is failing
2025-09-29 10:17:22 +08:00
hang 1095e07f87 修改授权临时提交
continuous-integration/drone/push Build is passing
2025-09-29 10:09:47 +08:00
hang 14b8cbac23 移动到根目录打开测试
continuous-integration/drone/push Build is passing
2025-09-28 19:44:53 +08:00
hang 92063da2bf 测试拷贝到根目录
continuous-integration/drone/push Build is passing
2025-09-28 19:38:24 +08:00
hang 9c6b835800 修改过滤时间
continuous-integration/drone/push Build is passing
2025-09-28 17:36:33 +08:00
hang f71b71bb54 编译错误
continuous-integration/drone/push Build is passing
2025-09-28 17:19:33 +08:00
hang 6d39e72506 删除稽查记录用户
continuous-integration/drone/push Build is running
2025-09-28 17:18:25 +08:00
hang a90edaff51 修改稽查记录选择
continuous-integration/drone/push Build is failing
2025-09-28 16:54:13 +08:00
hang bb8fb0a968 管理和查看 bug
continuous-integration/drone/push Build is passing
2025-09-28 16:20:22 +08:00
hang 348ca93086 修改授权查询参数
continuous-integration/drone/push Build is passing
2025-09-28 15:49:38 +08:00
hang e3b2046cb8 修改稽查记录管理 默认列表
continuous-integration/drone/push Build is passing
2025-09-28 15:41:39 +08:00
hang 99350c3a17 修改提交事务位置
continuous-integration/drone/push Build is passing
2025-09-28 15:31:19 +08:00
hang 1523450b88 批量授权修改
continuous-integration/drone/push Build is running
2025-09-28 15:30:44 +08:00
hang fb20b1f1b2 修改稽查记录设置授权
continuous-integration/drone/push Build is passing
2025-09-28 15:22:52 +08:00
hang 5c90b66fc9 修改参数名字
continuous-integration/drone/push Build is passing
2025-09-28 15:07:04 +08:00
hang bc1a6562d5 修改编辑人员重复添加
continuous-integration/drone/push Build is passing
2025-09-28 14:58:35 +08:00
hang be1fbf2059 修改稽查文档闭包修改
continuous-integration/drone/push Build is passing
2025-09-28 14:25:05 +08:00
hang 7a3bbbeb19 修改稽查文档查询条件,和返回信息
continuous-integration/drone/push Build is passing
2025-09-28 10:17:49 +08:00
hang d263ecbe2a 闭包针对稽查的修改
continuous-integration/drone/push Build is passing
2025-09-27 23:35:22 +08:00
hang 447e916564 增加稽查记录修改
continuous-integration/drone/push Build is passing
2025-09-26 18:14:03 +08:00
hang 48c9721ab4 手动通过的时候设置时间
continuous-integration/drone/push Build is passing
2025-09-25 16:19:18 +08:00
he e641e78fb3 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-09-23 11:15:43 +08:00
he d37c1cb872 修改 2025-09-23 11:15:41 +08:00
hang da2afd0d5a 后处理检查bug修复2
continuous-integration/drone/push Build is passing
2025-09-22 15:14:07 +08:00
hang 09305928ed 后处理检查bug修复
continuous-integration/drone/push Build is passing
2025-09-22 14:58:08 +08:00
hang e16fd2f4de 后处理增加查询字段
continuous-integration/drone/push Build is passing
2025-09-22 11:53:09 +08:00
hang b2389403d9 推送记录增加监控字段
continuous-integration/drone/push Build is passing
2025-09-19 15:45:34 +08:00
hang 29c137a2c5 影像库增加新的字段,后处理上传、scp服务修改
continuous-integration/drone/push Build is running
2025-09-19 15:43:02 +08:00
hang 2e1a68fa14 修改存放日志路径
continuous-integration/drone/push Build is passing
2025-09-17 15:43:44 +08:00
hang 7ec76f6ce3 恢复数据测试
continuous-integration/drone/push Build is passing
2025-09-17 15:30:11 +08:00
hang 87e70447f3 修改检查数量
continuous-integration/drone/push Build is passing
2025-09-16 17:13:34 +08:00
hang f72e3f4300 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-09-16 17:03:45 +08:00
hang 7154196667 维护数据初步提交 2025-09-16 17:03:41 +08:00
he 0081cf16d0 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-09-16 16:00:56 +08:00
he 2eec5f02da 添加查看检查部位 2025-09-16 16:00:52 +08:00
hang 4b011ee727 修改下载的时候修改任务名字
continuous-integration/drone/push Build is passing
2025-09-16 14:07:35 +08:00
hang b115dd5253 下载过滤非dicom文件
continuous-integration/drone/push Build is passing
2025-09-16 10:35:22 +08:00
hang 84349c19e7 维护数据第二次提交
continuous-integration/drone/push Build is passing
2025-09-16 10:19:14 +08:00
hang 84d08c0378 下载删除标记方法提交 2025-09-16 10:19:11 +08:00
he 197aa9bf0a 排除访视问题必填验证
continuous-integration/drone/push Build is passing
2025-09-16 09:10:27 +08:00
he f83819d0c9 res 同步病灶调整
continuous-integration/drone/push Build is passing
2025-09-15 14:41:44 +08:00
he 43c0885e0a 修改 2025-09-15 14:41:41 +08:00
he 1cff4f7603 MRECISTHCC 同步病灶 2025-09-15 14:41:39 +08:00
hang 71f978609b 影像检查的部位 增加字段
continuous-integration/drone/push Build is passing
2025-09-15 13:24:13 +08:00
hang 774e7ccc25 受试者访视中止 增加原因
continuous-integration/drone/push Build is passing
2025-09-15 11:00:50 +08:00
he c89b65b550 修改bug
continuous-integration/drone/push Build is passing
2025-09-12 16:52:48 +08:00
hang a8800b01f3 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-09-12 16:14:57 +08:00
hang fcc2a68618 新增维护项目数据接口 2025-09-12 16:14:56 +08:00
he a353c90bf7 MRECISTHCCC 同步病灶修改
continuous-integration/drone/push Build is passing
2025-09-12 13:40:32 +08:00
he 8e730fc342 修改复制 recist1.1 淋巴结病灶状态
continuous-integration/drone/push Build is passing
2025-09-12 11:32:30 +08:00
he 53149bf8a0 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is pending
2025-09-12 11:31:32 +08:00
he b2d681da09 删除FristAddTaskNum 2025-09-12 11:31:26 +08:00
hang 49a36b8214 维护数据结束提交
continuous-integration/drone/push Build is passing
2025-09-10 15:43:04 +08:00
hang 270941431d 修改后处理上传统计instance数量 和series数量
continuous-integration/drone/push Build is passing
2025-09-09 09:04:43 +08:00
hang 2e3c209de2 修改后处理上传检查bug
continuous-integration/drone/push Build is passing
2025-09-08 10:40:54 +08:00
hang 0be5763ce3 IRC导出支持新标准
continuous-integration/drone/push Build is passing
2025-09-05 15:14:36 +08:00
hang 7f1232e2ff 修改输出日志展示具体出问题的
continuous-integration/drone/push Build is passing
2025-09-05 11:07:58 +08:00
hang 9669032e6c Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-09-05 10:51:44 +08:00
hang fd5711a31d 下载影像维护处理 2025-09-05 10:51:41 +08:00
he 25c5b32fa7 Revert "修改"
continuous-integration/drone/push Build is passing
This reverts commit 9fa73705ba.
2025-09-03 15:02:02 +08:00
he 5f75413c9c Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-09-03 14:54:30 +08:00
he 9fa73705ba 修改 2025-09-03 14:54:29 +08:00
hang 0f27e246a6 升级缺陷包
continuous-integration/drone/push Build is passing
2025-09-01 16:39:10 +08:00
he 0d26daa3b0 邮件修改
continuous-integration/drone/push Build is passing
2025-08-25 14:32:52 +08:00
he 8186ec1b02 修改病灶标记图像验证 可来自于后处理
continuous-integration/drone/push Build is passing
2025-08-19 15:29:55 +08:00
he 0f0967f893 修改稽查
continuous-integration/drone/push Build is passing
2025-08-15 11:26:27 +08:00
he 56165f5037 Revert "修改维护 文档的稽查"
This reverts commit 0b707117c6.
2025-08-15 11:24:46 +08:00
he 0b707117c6 修改维护 文档的稽查
continuous-integration/drone/push Build is passing
2025-08-15 11:16:47 +08:00
he 5186a530f8 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-08-12 16:19:12 +08:00
he b3ed2dd497 修改验证 2025-08-12 16:19:11 +08:00
hang 5cfa209cc2 Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
2025-08-12 13:59:45 +08:00
he ca5accfaa0 裁判问题
continuous-integration/drone/push Build is passing
2025-08-12 11:10:41 +08:00
455 changed files with 1555853 additions and 6734 deletions
+3 -2
View File
@@ -12,8 +12,9 @@
<PackageReference Include="AlibabaCloud.SDK.Sts20150401" Version="1.1.5" />
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="10.0.0" />
<PackageReference Include="Aliyun.OSS.SDK.NetCore" Version="2.14.1" />
<PackageReference Include="AWSSDK.S3" Version="3.7.416.8" />
<PackageReference Include="AWSSDK.SecurityToken" Version="3.7.401.81" />
<PackageReference Include="AWSSDK.S3" Version="4.0.21" />
<PackageReference Include="AWSSDK.SecurityToken" Version="4.0.5.19" />
<PackageReference Include="DistributedLock.Core" Version="1.0.8" />
<PackageReference Include="DistributedLock.SqlServer" Version="1.0.6" />
<PackageReference Include="fo-dicom" Version="5.2.1" />
+11 -11
View File
@@ -118,7 +118,7 @@ public class AWSTempToken
public string SecretAccessKey { get; set; }
public string BucketName { get; set; }
public string ViewEndpoint { get; set; }
public DateTime Expiration { get; set; }
public DateTime? Expiration { get; set; }
}
public enum ObjectStoreUse
@@ -231,8 +231,8 @@ public class OSSService : IOSSService
//提供awsEndPoint(域名)进行访问配置
var clientConfig = new AmazonS3Config
{
RegionEndpoint = RegionEndpoint.USEast1,
UseHttp = true,
RegionEndpoint = RegionEndpoint.GetBySystemName(awsConfig.Region)
//,UseHttp = true,
};
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
@@ -320,8 +320,8 @@ public class OSSService : IOSSService
//提供awsEndPoint(域名)进行访问配置
var clientConfig = new AmazonS3Config
{
RegionEndpoint = RegionEndpoint.USEast1,
UseHttp = true,
RegionEndpoint = RegionEndpoint.GetBySystemName(awsConfig.Region)
//,UseHttp = true,
};
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
@@ -397,8 +397,8 @@ public class OSSService : IOSSService
//提供awsEndPoint(域名)进行访问配置
var clientConfig = new AmazonS3Config
{
RegionEndpoint = RegionEndpoint.USEast1,
UseHttp = true,
RegionEndpoint = RegionEndpoint.GetBySystemName(awsConfig.Region)
//,UseHttp = true,
};
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
@@ -493,8 +493,8 @@ public class OSSService : IOSSService
//提供awsEndPoint(域名)进行访问配置
var clientConfig = new AmazonS3Config
{
RegionEndpoint = RegionEndpoint.USEast1,
UseHttp = true,
RegionEndpoint = RegionEndpoint.GetBySystemName(awsConfig.Region)
//,UseHttp = true,
};
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
@@ -625,8 +625,8 @@ public class OSSService : IOSSService
//提供awsEndPoint(域名)进行访问配置
var clientConfig = new AmazonS3Config
{
RegionEndpoint = RegionEndpoint.USEast1,
UseHttp = true,
RegionEndpoint = RegionEndpoint.GetBySystemName(awsConfig.Region)
//,UseHttp = true,
};
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
@@ -0,0 +1,175 @@
using IRaCIS.Core.Application.Helper;
using IRaCIS.Core.Application.Service;
using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.Infra.EFCore;
using IRaCIS.Core.SCP.Service;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
namespace IRaCIS.Core.SCP;
public class SyncFileRecoveryService(IServiceScopeFactory _scopeFactory, FileSyncQueue _fileSyncQueue) : BackgroundService
{
private readonly int _pageSize = 500;
/// <summary>
/// 多个程序,如果恢复同一份数据,造成重复同步,SCP服务不恢复任务
/// </summary>
/// <param name="stoppingToken"></param>
/// <returns></returns>
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
//在本地调试的时候,不干涉部署同步任务
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return;
}
await Task.CompletedTask;
//using var scope = _scopeFactory.CreateScope();
//var fileUploadRecordRepository = scope.ServiceProvider.GetRequiredService<IRepository<FileUploadRecord>>();
//// 延迟启动,保证主机快速启动
//await Task.Delay(5000, stoppingToken);
//int page = 0;
//while (!stoppingToken.IsCancellationRequested)
//{
// // 分页获取未入队任务
// var pending = await fileUploadRecordRepository
// .Where(x => x.IsNeedSync == true && (x.IsSync == false || x.IsSync == null))
// .OrderByDescending(x => x.Priority)
// .Select(t => new { t.Id, t.Priority })
// .Skip(page * _pageSize)
// .Take(_pageSize)
// .ToListAsync(stoppingToken);
// if (!pending.Any())
// break; // 扫描完毕,退出循环
// foreach (var file in pending)
// {
// //file.IsQueued = true; // 避免重复入队
// _fileSyncQueue.Enqueue(file.Id, file.Priority ?? 0); // 放入队列
// }
// page++; // 下一页
// await Task.Delay(200, stoppingToken); // 缓解数据库压力
//}
}
}
public class FileSyncWorker(IServiceScopeFactory _scopeFactory, ILogger<FileSyncWorker> _logger, FileSyncQueue _fileSyncQueue) : BackgroundService
{
// ⭐ 自动根据服务器CPU
private readonly int _workerCount = Math.Max(1, Environment.ProcessorCount - 1);
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
for (int i = 0; i < _workerCount; i++)
Task.Run(() => WorkerLoop(stoppingToken));
return Task.CompletedTask;
}
private async Task WorkerLoop(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var id = await _fileSyncQueue.DequeueAsync(stoppingToken);
try
{
using var scope = _scopeFactory.CreateScope();
var _fileUploadRecordRepository = scope.ServiceProvider.GetRequiredService<IRepository<FileUploadRecord>>();
var _uploadFileSyncRecordRepository = scope.ServiceProvider.GetRequiredService<IRepository<UploadFileSyncRecord>>();
var syncConfig = (scope.ServiceProvider.GetRequiredService<IOptionsMonitor<ObjectStoreServiceOptions>>()).CurrentValue;
var oss = scope.ServiceProvider.GetRequiredService<IOSSService>();
var file = await _fileUploadRecordRepository.FirstOrDefaultAsync(t => t.Id == id);
// ✅ 不要 return
if (file == null || file.IsNeedSync != true || syncConfig.IsOpenStoreSync == false)
{
continue;
}
if (syncConfig.SyncConfigList.Any(t => t.UploadRegion == file.UploadRegion && t.IsOpenSync == false))
{
continue;
}
//如果发现系统配置某一边同步进行了关闭,那么就直接返回,不执行任务
if (syncConfig.SyncConfigList.Any(t => t.UploadRegion == file.UploadRegion && t.IsOpenSync == false))
{
return;
}
var log = new UploadFileSyncRecord
{
FileUploadRecordId = id,
StartTime = DateTime.Now,
JobState = jobState.RUNNING
};
await _uploadFileSyncRecordRepository.AddAsync(log);
await _uploadFileSyncRecordRepository.SaveChangesAsync(stoppingToken);
try
{
await oss.SyncFileAsync(file.Path.TrimStart('/'), file.UploadRegion == "CN" ? ObjectStoreUse.AliyunOSS : ObjectStoreUse.AWS, file.UploadRegion == "CN" ? ObjectStoreUse.AWS : ObjectStoreUse.AliyunOSS);
file.IsSync = true;
file.SyncFinishedTime = DateTime.Now;
log.JobState = jobState.SUCCESS;
}
catch (Exception ex)
{
log.JobState = jobState.FAILED;
log.Msg = ex.Message.Length > 300 ? ex.Message.Substring(0, 300) : ex.Message;
}
log.EndTime = DateTime.Now;
await _uploadFileSyncRecordRepository.SaveChangesAsync(stoppingToken);
}
catch (Exception ex)
{
_logger.LogError(ex, "Sync failed {Id}", id);
}
finally
{
// ⭐⭐⭐ 永远执行
_fileSyncQueue.Complete(id);
}
}
}
}
+16 -16
View File
@@ -7,28 +7,28 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AlibabaCloud.SDK.Sts20150401" Version="1.1.5" />
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="10.0.0" />
<PackageReference Include="AlibabaCloud.SDK.Sts20150401" Version="1.2.0" />
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="11.0.0" />
<PackageReference Include="Aliyun.OSS.SDK.NetCore" Version="2.14.1" />
<PackageReference Include="AWSSDK.S3" Version="3.7.416.8" />
<PackageReference Include="AWSSDK.SecurityToken" Version="3.7.401.81" />
<PackageReference Include="DistributedLock.Core" Version="1.0.8" />
<PackageReference Include="DistributedLock.SqlServer" Version="1.0.6" />
<PackageReference Include="fo-dicom" Version="5.2.1" />
<PackageReference Include="fo-dicom.Codecs" Version="5.16.1" />
<PackageReference Include="fo-dicom.Imaging.ImageSharp" Version="5.2.1" />
<PackageReference Include="AWSSDK.S3" Version="4.0.21" />
<PackageReference Include="AWSSDK.SecurityToken" Version="4.0.5.19" />
<PackageReference Include="DistributedLock.Core" Version="1.0.9" />
<PackageReference Include="DistributedLock.SqlServer" Version="1.0.7" />
<PackageReference Include="fo-dicom" Version="5.2.6" />
<PackageReference Include="fo-dicom.Codecs" Version="5.16.7" />
<PackageReference Include="fo-dicom.Imaging.ImageSharp" Version="5.2.6" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.10" />
<PackageReference Include="AutoMapper" Version="13.0.1" />
<PackageReference Include="Minio" Version="6.0.4" />
<PackageReference Include="AutoMapper" Version="16.1.1" />
<PackageReference Include="Minio" Version="7.0.0" />
<PackageReference Include="My.Extensions.Localization.Json" Version="3.3.0">
<TreatAsUsed>true</TreatAsUsed>
</PackageReference>
<PackageReference Include="Panda.DynamicWebApi" Version="1.2.2" />
<PackageReference Include="Serilog.Enrichers.ClientInfo" Version="2.1.2" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="9.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.1" />
<PackageReference Include="Serilog.Enrichers.ClientInfo" Version="2.9.0" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="10.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.1.1" />
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.1.7" />
</ItemGroup>
<ItemGroup>
+8
View File
@@ -63,6 +63,10 @@ builder.Host
#region
var _configuration = builder.Configuration;
builder.Services.AddSingleton<FileSyncQueue>();
builder.Services.AddHostedService<SyncFileRecoveryService>();
builder.Services.AddHostedService<FileSyncWorker>();
//健康检查
builder.Services.AddHealthChecks();
@@ -113,6 +117,9 @@ builder.Services.AddAutoMapper(automapper =>
//EF ORM QueryWithNoLock
builder.Services.AddEFSetup(_configuration);
// FusionCache
builder.Services.AddFusionCache();
builder.Services.AddMediator(cfg =>
{
@@ -191,6 +198,7 @@ app.MapControllers();
Log.Logger = new LoggerConfiguration()
//.MinimumLevel.Information()
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.MinimumLevel.Override("ZiggyCreatures.Caching.Fusion", LogEventLevel.Warning)
.WriteTo.Console()
.WriteTo.File($"{AppContext.BaseDirectory}Serilogs/.log", rollingInterval: RollingInterval.Day)
.CreateLogger();
+477 -106
View File
@@ -1,27 +1,29 @@
using FellowOakDicom.Network;
using FellowOakDicom;
using FellowOakDicom;
using FellowOakDicom.Imaging;
using FellowOakDicom.IO.Buffer;
using FellowOakDicom.Network;
using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Infra.EFCore;
using IRaCIS.Core.Infrastructure;
using IRaCIS.Core.Infrastructure.Extention;
using IRaCIS.Core.SCP.Service;
using Medallion.Threading;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion.Internal;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Serilog;
using SharpCompress.Common;
using SixLabors.ImageSharp.Formats.Jpeg;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using IRaCIS.Core.SCP.Service;
using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.Infra.EFCore;
using Medallion.Threading;
using IRaCIS.Core.Domain.Share;
using Serilog;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion.Internal;
using Microsoft.Extensions.Options;
using System.Data;
using FellowOakDicom.Imaging;
using SharpCompress.Common;
using SixLabors.ImageSharp.Formats.Jpeg;
using IRaCIS.Core.Infrastructure;
using IRaCIS.Core.Infrastructure.Extention;
using ZiggyCreatures.Caching.Fusion;
namespace IRaCIS.Core.SCP.Service
{
@@ -40,7 +42,9 @@ namespace IRaCIS.Core.SCP.Service
{
private IServiceProvider _serviceProvider { get; set; }
private List<Guid> _SCPStudyIdList { get; set; } = new List<Guid>();
private List<Guid> _SCPStudyIdList => _ImageUploadList.Where(t => t.SCPStudyId != Guid.Empty).Select(t => t.SCPStudyId).ToList();
private List<ImageUploadInfo> _ImageUploadList { get; set; } = new List<ImageUploadInfo>();
private SCPImageUpload _upload { get; set; }
@@ -48,6 +52,8 @@ namespace IRaCIS.Core.SCP.Service
private Guid _trialSiteId { get; set; }
private bool _releasedNormally = false;
private static readonly DicomTransferSyntax[] _acceptedTransferSyntaxes = new DicomTransferSyntax[]
@@ -115,7 +121,7 @@ namespace IRaCIS.Core.SCP.Service
var _trialSiteDicomAERepository = _serviceProvider.GetService<IRepository<TrialSiteDicomAE>>();
var findTrialSiteAE = _trialSiteDicomAERepository.Where(t => t.CallingAE == association.CallingAE && t.TrialId==_trialId).FirstOrDefault();
var findTrialSiteAE = _trialSiteDicomAERepository.Where(t => t.CallingAE == association.CallingAE && t.TrialId == _trialId).FirstOrDefault();
if (findTrialSiteAE != null)
{
@@ -124,7 +130,7 @@ namespace IRaCIS.Core.SCP.Service
isCanReceiveIamge = true;
}
}
if (association.CallingAE == "test-callingAE")
@@ -163,27 +169,61 @@ namespace IRaCIS.Core.SCP.Service
public async Task OnReceiveAssociationReleaseRequestAsync()
{
await DataMaintenanceAsaync();
var _distributedLockProvider = _serviceProvider.GetService<IDistributedLockProvider>();
var @lock = _distributedLockProvider.CreateLock($"{_upload.CallingAE}");
using (await @lock.AcquireAsync())
{
await DataMaintenanceAsaync();
//记录监控
await AddUploadLogAsync();
_releasedNormally = true;
Log.Logger.Information($"进入释放连接请求 {_releasedNormally}");
//var _studyRepository = _serviceProvider.GetService<IRepository<SCPStudy>>();
////将检查设置为传输结束
//await _studyRepository.BatchUpdateNoTrackingAsync(t => _SCPStudyIdList.Contains(t.Id), u => new SCPStudy() { IsUploadFinished = true });
//await _studyRepository.SaveChangesAndClearAllTrackingAsync();
}
await SendAssociationReleaseResponseAsync();
}
private async Task AddUploadLogAsync()
{
//记录监控
var _SCPImageUploadRepository = _serviceProvider.GetService<IRepository<SCPImageUpload>>();
_upload.EndTime = DateTime.Now;
_upload.StudyCount = _SCPStudyIdList.Count;
_upload.TrialId = _trialId;
_upload.TrialSiteId = _trialSiteId;
//有时候没有建立连接会走关闭通过日志发现
if (_upload != null)
{
_upload.EndTime = DateTime.Now;
_upload.StudyCount = _ImageUploadList.Count;
_upload.TrialId = _trialId;
_upload.TrialSiteId = _trialSiteId;
await _SCPImageUploadRepository.AddAsync(_upload, true);
_upload.UploadJsonStr = (new SCPImageLog() { UploadList = _ImageUploadList }).ToJsonStr();
//可能是测试echo 导致记录了
if (_upload.FileCount > 0)
{
//可能是测试echo 导致记录了
await _SCPImageUploadRepository.AddAsync(_upload, true);
}
}
var _studyRepository = _serviceProvider.GetService<IRepository<SCPStudy>>();
//将检查设置为传输结束
await _studyRepository.BatchUpdateNoTrackingAsync(t => _SCPStudyIdList.Contains(t.Id), u => new SCPStudy() { IsUploadFinished = true });
await _studyRepository.SaveChangesAndClearAllTrackingAsync();
await SendAssociationReleaseResponseAsync();
}
@@ -238,17 +278,25 @@ namespace IRaCIS.Core.SCP.Service
public async void OnConnectionClosed(Exception exception)
{
/* nothing to do here */
var _studyRepository = _serviceProvider.GetService<IRepository<SCPStudy>>();
//奇怪的bug 上传的时候,用王捷修改的影像,会关闭,重新连接,导致检查id 丢失,然后状态不一致
if (exception == null)
if (exception != null || _releasedNormally == false)
{
//var _studyRepository = _serviceProvider.GetService<IRepository<SCPStudy>>();
////将检查设置为传输结束
//await _studyRepository.BatchUpdateNoTrackingAsync(t => _SCPStudyIdList.Contains(t.Id), u => new SCPStudy() { IsUploadFinished = true });
//客户端断网,恢复后,也是没有异常的,估计是超时走了关闭
//await _studyRepository.SaveChangesAndClearAllTrackingAsync();
await _studyRepository.BatchUpdateNoTrackingAsync(t => _SCPStudyIdList.Contains(t.Id), u => new SCPStudy() { IsUploadFinished = true, IsUploadFaild = true });
//记录日志
await AddUploadLogAsync();
}
else
{
//将检查设置为传输结束
await _studyRepository.BatchUpdateNoTrackingAsync(t => _SCPStudyIdList.Contains(t.Id), u => new SCPStudy() { IsUploadFinished = true, IsUploadFaild = false });
}
await _studyRepository.SaveChangesAndClearAllTrackingAsync();
Log.Logger.Warning($"连接关闭 {exception?.Message} {exception?.InnerException?.Message}");
}
@@ -259,17 +307,24 @@ namespace IRaCIS.Core.SCP.Service
public async Task<DicomCStoreResponse> OnCStoreRequestAsync(DicomCStoreRequest request)
{
string studyInstanceUid = request.Dataset.GetSingleValueOrDefault(DicomTag.StudyInstanceUID,string.Empty);
string studyInstanceUid = request.Dataset.GetSingleValueOrDefault(DicomTag.StudyInstanceUID, string.Empty);
string seriesInstanceUid = request.Dataset.GetSingleValueOrDefault(DicomTag.SeriesInstanceUID, string.Empty);
string sopInstanceUid = request.Dataset.GetSingleValueOrDefault(DicomTag.SOPInstanceUID, string.Empty);
string patientIdStr = request.Dataset.GetSingleValueOrDefault(DicomTag.PatientID, string.Empty);
if(studyInstanceUid.IsNullOrEmpty() || seriesInstanceUid.IsNullOrEmpty() || sopInstanceUid.IsNullOrEmpty())
if (studyInstanceUid.IsNullOrEmpty() || seriesInstanceUid.IsNullOrEmpty() || sopInstanceUid.IsNullOrEmpty())
{
Log.Logger.Error($"接收数据读取StudyInstanceUID{studyInstanceUid}、SeriesInstanceUID{seriesInstanceUid}、SOPInstanceUID{sopInstanceUid}有空 ");
return new DicomCStoreResponse(request, DicomStatus.Success);
}
//确保来了影像集合存在
if (!_ImageUploadList.Any(t => t.StudyInstanceUid == studyInstanceUid))
{
_ImageUploadList.Add(new ImageUploadInfo() { StudyInstanceUid = studyInstanceUid });
}
//Guid studyId = IdentifierHelper.CreateGuid(studyInstanceUid, trialId.ToString());
Guid seriesId = IdentifierHelper.CreateGuid(studyInstanceUid, seriesInstanceUid, _trialId.ToString());
Guid instanceId = IdentifierHelper.CreateGuid(studyInstanceUid, seriesInstanceUid, sopInstanceUid, _trialId.ToString());
@@ -280,6 +335,11 @@ namespace IRaCIS.Core.SCP.Service
var _seriesRepository = _serviceProvider.GetService<IRepository<SCPSeries>>();
var _distributedLockProvider = _serviceProvider.GetService<IDistributedLockProvider>();
var _fusionCache = _serviceProvider.GetService<IFusionCache>();
var _trialSiteRepository = _serviceProvider.GetService<IRepository<TrialSite>>();
var _systemAnonymizationRepository = _serviceProvider.GetService<IRepository<SystemAnonymization>>();
var storeRelativePath = string.Empty;
var ossFolderPath = $"{_trialId}/Image/PACS/{_trialSiteId}/{studyInstanceUid}";
@@ -288,15 +348,383 @@ namespace IRaCIS.Core.SCP.Service
long fileSize = 0;
try
{
// 直接拿 Dataset(已经完整)
var dataset = request.Dataset;
using (MemoryStream ms = new MemoryStream())
#region
var anonymizeList = await _fusionCache.GetOrSetAsync(CacheKeys.SystemAnonymization, _ => CacheHelper.GetSystemAnonymizationListAsync(_systemAnonymizationRepository), TimeSpan.FromDays(7));
var trialSiteInfo = await _fusionCache.GetOrSetAsync(CacheKeys.TrialSiteInfo(_trialSiteId), _ => CacheHelper.GetTrialSiteInfo(_trialSiteId, _trialSiteRepository), TimeSpan.FromMinutes(2));
var fixedFiledList = anonymizeList.Where(t => t.IsFixed).ToList();
var ircFiledList = anonymizeList.Where(t => t.IsFixed == false).ToList();
foreach (var item in fixedFiledList)
{
await request.File.SaveAsync(ms);
//irc 从路径最后一截取Guid
storeRelativePath = await ossService.UploadToOSSAsync(ms, ossFolderPath, instanceId.ToString(), false);
var dicomTag = new DicomTag(Convert.ToUInt16(item.Group, 16), Convert.ToUInt16(item.Element, 16));
dataset.AddOrUpdate(dicomTag, item.ReplaceValue);
}
foreach (var item in ircFiledList)
{
var dicomTag = new DicomTag(Convert.ToUInt16(item.Group, 16), Convert.ToUInt16(item.Element, 16));
if (dicomTag == DicomTag.ClinicalTrialProtocolID)
{
dataset.AddOrUpdate(DicomTag.ClinicalTrialProtocolID, trialSiteInfo.TrialCode);
}
if (dicomTag == DicomTag.ClinicalTrialSiteID)
{
dataset.AddOrUpdate(DicomTag.ClinicalTrialSiteID, trialSiteInfo.TrialSiteCode);
}
if (dicomTag == DicomTag.ClinicalTrialSubjectID)
{
dataset.AddOrUpdate(DicomTag.ClinicalTrialSubjectID, "");
}
if (dicomTag == DicomTag.ClinicalTrialTimePointID)
{
dataset.AddOrUpdate(DicomTag.ClinicalTrialTimePointID, "");
}
if (dicomTag == DicomTag.PatientID)
{
var pid = dataset.GetSingleValueOrDefault(DicomTag.PatientID, string.Empty);
dataset.AddOrUpdate(DicomTag.PatientID, trialSiteInfo.TrialCode + "-" + pid);
}
}
#endregion
// 构造 DicomFile(不用 Open
var dicomFile = new DicomFile(dataset);
#region 1便
var numberOfFrames = dicomFile.Dataset.GetSingleValueOrDefault(DicomTag.NumberOfFrames, 1);
//多帧处理逻辑
if (numberOfFrames > 1)
{
//一定要有像素数据才处理
var pixelData = DicomPixelData.Create(dicomFile.Dataset);
if (pixelData != null)
{
try
{
Log.Logger.Warning($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE} 开始处理多帧instanceId:{instanceId}");
var syntax = pixelData.Syntax;
// 每个 fragment 固定大小 (64KB 示例,可以自己调整)
int fragmentSize = 20 * 1024;
var frag = dicomFile.Dataset.GetDicomItem<DicomOtherByteFragment>(DicomTag.PixelData);
int fragmentCount = frag?.Fragments?.Count() ?? 0;
var originOffsetTable = frag?.OffsetTable; //有可能没有表,需要自己重建
var bot = new List<uint>();
uint botOffset = 0;
//需要拆成固定片段的
if (syntax.IsEncapsulated && fragmentCount == pixelData.NumberOfFrames && numberOfFrames > 1)
{
var newFragments = new DicomOtherByteFragment(DicomTag.PixelData);
#region test
//var newDicomFile = dicomFile.Clone();
//var newDataset = newDicomFile.Dataset;
//var dstPd = DicomPixelData.Create(newDataset, true);
//for (int i = 0; i < pixelData.NumberOfFrames; i++)
//{
// var frame = pixelData.GetFrame(i);
// dstPd.AddFrame(frame);
// var data = frame.Data;
// int offset = 0;
// while (offset < data.Length)
// {
// int size = Math.Min(fragmentSize, data.Length - offset);
// var buffer = new byte[size];
// Buffer.BlockCopy(data, offset, buffer, 0, size);
// newFragments.Fragments.Add(new MemoryByteBuffer(buffer));
// offset += size;
// }
//}
//var newOffsetTable = newDataset.GetDicomItem<DicomOtherByteFragment>(DicomTag.PixelData).OffsetTable;
//newFragments.OffsetTable.AddRange(newOffsetTable.ToArray());
#endregion
#region test fo-dicom auto bot
//var newDicomFile = dicomFile.Clone();
//var newDataset = newDicomFile.Dataset;
//var dstPd = DicomPixelData.Create(newDataset, true);
//for (int i = 0; i < pixelData.NumberOfFrames; i++)
//{
// var frame = pixelData.GetFrame(i);
// dstPd.AddFrame(frame);
//}
//var newOffsetTable = newDataset.GetDicomItem<DicomOtherByteFragment>(DicomTag.PixelData).OffsetTable;
//Console.WriteLine(newOffsetTable.ToJsonStr());
#endregion
#region 使
for (int n = 0; n < pixelData.NumberOfFrames; n++)
{
var frameData = pixelData.GetFrame(n); // 获取完整一帧
var data = frameData.Data;
int offset = 0;
bot.Add(botOffset);
botOffset += (uint)data.Length;
while (offset < data.Length)
{
botOffset += 8;
int size = Math.Min(fragmentSize, data.Length - offset);
var buffer = new byte[size];
Buffer.BlockCopy(data, offset, buffer, 0, size);
newFragments.Fragments.Add(new MemoryByteBuffer(buffer));
offset += size;
}
}
//保留原始偏移表
//if (originOffsetTable.Count == pixelData.NumberOfFrames)
//{
// newFragments.OffsetTable.AddRange(originOffsetTable.ToArray());
//}
//else
{
newFragments.OffsetTable.AddRange(bot.ToArray());
//Console.WriteLine(bot.ToJsonStr());
}
#endregion
dicomFile.Dataset.AddOrUpdate(newFragments);
}
//传递过来的就是拆分的,但是是没有偏移表的,我需要自己创建偏移表,不然生成缩略图失败
else if (syntax.IsEncapsulated && fragmentCount > pixelData.NumberOfFrames && originOffsetTable.Count == 0)
{
uint offset = 0;
bot.Add(offset);
var fistSize = frag.Fragments.FirstOrDefault()?.Size ?? 0;
//和上一个大小不一样
var isDiffrentBefore = false;
uint count = 0;
// 假设你知道每帧对应的 fragment 数量
foreach (var frameFragments in frag.Fragments)
{
count++;
if (frameFragments.Size == fistSize)
{
isDiffrentBefore = false;
// 累加这一帧所有 fragment 的大小
offset += (uint)frameFragments.Size;
continue;
}
else
{
offset += (uint)frameFragments.Size;
isDiffrentBefore = true;
}
if (isDiffrentBefore)
{
//每个Fragment 也占用字节
offset += 8 * count;
bot.Add(offset);
count = 0;
}
}
bot.RemoveAt(bot.Count - 1);
// 设置到新的 PixelData
frag.OffsetTable.AddRange(bot.ToArray());
}
}
catch (Exception mutiEx)
{
Log.Logger.Warning($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE} 处理多帧失败,上传原始文件:{mutiEx.ToString()}");
}
}
}
#endregion
#region
//// --- 保存到本地文件测试 ---
//var localPath = @"D:\TestDicom.dcm";
//using (var fs = new FileStream(localPath, FileMode.Create, FileAccess.Write))
//{
// ms.CopyTo(fs);
//}
//return new DicomCStoreResponse(request, DicomStatus.Success);
#endregion
// 直接写入内存
await using var ms = new MemoryStream();
await dicomFile.SaveAsync(ms);
ms.Position = 0;
//irc 从路径最后一截取Guid
storeRelativePath = await ossService.UploadToOSSAsync(ms, ossFolderPath, instanceId.ToString(), false, uploadInfo: new FileUploadRecordAddOrEdit() { TrialId = _trialId, BatchDataType = BatchDataType.PACSReceive });
fileSize = ms.Length;
var @lock = _distributedLockProvider.CreateLock($"{studyInstanceUid}");
using (await @lock.AcquireAsync())
{
try
{
var scpStudyId = await dicomArchiveService.ArchiveDicomFileAsync(dicomFile, _trialId, _trialSiteId, storeRelativePath, Association.CallingAE, Association.CalledAE, fileSize);
var series = await _seriesRepository.FirstOrDefaultAsync(t => t.Id == seriesId);
//没有缩略图
if (series != null && string.IsNullOrEmpty(series.ImageResizePath))
{
// 生成缩略图
using (var memoryStream = new MemoryStream())
{
DicomImage image = new DicomImage(dicomFile.Dataset);
var sharpimage = image.RenderImage().AsSharpImage();
sharpimage.Save(memoryStream, new JpegEncoder());
// 上传缩略图到 OSS
var seriesPath = await ossService.UploadToOSSAsync(memoryStream, ossFolderPath, $"{seriesId.ToString()}_{instanceId.ToString()}.preview.jpg", false, uploadInfo: new FileUploadRecordAddOrEdit() { TrialId = _trialId, BatchDataType = BatchDataType.PACSReceive });
series.ImageResizePath = seriesPath;
}
}
await _seriesRepository.SaveChangesAsync();
if (_ImageUploadList.Any(t => t.StudyInstanceUid == studyInstanceUid))
{
var find = _ImageUploadList.FirstOrDefault(t => t.StudyInstanceUid.Equals(studyInstanceUid));
find.SuccessImageCount++;
if (!find.PatientNameList.Any(t => t == patientIdStr) && patientIdStr.IsNotNullOrEmpty())
{
find.PatientNameList.Add(patientIdStr);
}
//首次 (默认是Guid 空,数据库归档出了Id)
if (find.SCPStudyId != scpStudyId)
{
find.SCPStudyId = scpStudyId;
}
}
//监控信息设置
_upload.FileCount++;
_upload.FileSize = _upload.FileSize + fileSize;
}
catch (Exception ex)
{
Log.Logger.Warning($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE} 传输处理异常:{ex.ToString()}");
if (_ImageUploadList.Any(t => t.StudyInstanceUid == studyInstanceUid))
{
var find = _ImageUploadList.FirstOrDefault(t => t.StudyInstanceUid.Equals(studyInstanceUid));
find.FailedImageCount++;
}
}
fileSize = ms.Length;
}
Log.Logger.Information($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE} {request.SOPInstanceUID} 上传完成 ");
@@ -307,63 +735,6 @@ namespace IRaCIS.Core.SCP.Service
Log.Logger.Warning($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE} 上传异常 {ec.Message}");
}
var @lock = _distributedLockProvider.CreateLock($"{studyInstanceUid}");
using (await @lock.AcquireAsync())
{
try
{
var scpStudyId = await dicomArchiveService.ArchiveDicomFileAsync(request.Dataset, _trialId, _trialSiteId, storeRelativePath, Association.CallingAE, Association.CalledAE,fileSize);
if (!_SCPStudyIdList.Contains(scpStudyId))
{
_SCPStudyIdList.Add(scpStudyId);
}
var series = await _seriesRepository.FirstOrDefaultAsync(t => t.Id == seriesId);
//没有缩略图
if (series != null && string.IsNullOrEmpty(series.ImageResizePath))
{
// 生成缩略图
using (var memoryStream = new MemoryStream())
{
DicomImage image = new DicomImage(request.Dataset);
var sharpimage = image.RenderImage().AsSharpImage();
sharpimage.Save(memoryStream, new JpegEncoder());
// 上传缩略图到 OSS
var seriesPath = await ossService.UploadToOSSAsync(memoryStream, ossFolderPath, seriesId.ToString() + ".preview.jpg", false);
Console.WriteLine(seriesPath + " Id: " + seriesId);
series.ImageResizePath = seriesPath;
}
}
await _seriesRepository.SaveChangesAsync();
}
catch (Exception ex)
{
Log.Logger.Warning($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE} 传输处理异常:{ex.ToString()}");
}
}
//监控信息设置
_upload.FileCount++;
_upload.FileSize = _upload.FileSize + fileSize;
return new DicomCStoreResponse(request, DicomStatus.Success);
}
+103
View File
@@ -0,0 +1,103 @@
using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.Infra.EFCore;
using Microsoft.EntityFrameworkCore;
namespace IRaCIS.Core.SCP.Service;
public static class CacheKeys
{
//项目缓存
public static string Trial(string trialIdStr) => $"TrialId:{trialIdStr}";
//检查编号递增锁
public static string TrialStudyMaxCode(Guid trialId) => $"TrialStudyMaxCode:{trialId}";
public static string TrialStudyUidUploading(Guid trialId, string studyUid) => $"TrialStudyUid:{trialId}_{studyUid}";
//CRC上传影像提交锁key
public static string TrialStudyUidDBLock(Guid trialId, string studyUid) => $"TrialStudyUidDBLock:{trialId}_{studyUid}";
public static string TrialTaskStudyUidUploading(Guid trialId, Guid visiTaskId, string studyUid) => $"TrialStudyUid:{trialId}_{visiTaskId}_{studyUid}";
//影像后处理上传提交锁key
public static string TrialTaskStudyUidDBLock(Guid trialId, Guid visiTaskId, string studyUid) => $"TrialTaskStudyUidDBLock:{trialId}_{visiTaskId}_{studyUid}";
//系统匿名化
public static string SystemAnonymization => $"SystemAnonymization";
//前端国际化
public static string FrontInternational => $"FrontInternationalList";
//登录挤账号
public static string UserToken(Guid userId) => $"UserToken:{userId}";
//超时没请求接口自动退出
public static string UserAutoLoginOut(Guid userId) => $"UserAutoLoginOut:{userId}";
public static string UserDisable(Guid userId) => $"UserDisable:{userId}";
public static string UserRoleDisable(Guid userRoleId) => $"UserRoleDisable:{userRoleId}";
/// <summary>
/// 用户登录错误 限制登录
/// </summary>
/// <param name="userName"></param>
/// <returns></returns>
public static string UserLoginError(string userName) => $"login-failures:{userName}";
/// <summary>
/// 跳过阅片
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
public static string SkipReadingCacheKey(Guid userId) => $"{userId}SkipReadingCache";
/// <summary>
/// 开始阅片时间
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
public static string StartReadingTimeKey(Guid userId) => $"{userId}StartReadingTime";
/// <summary>
/// 开始休息时间
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
public static string StartRestTime(Guid userId) => $"{userId}StartRestTime";
//每个用户 每个浏览器独立时间
public static string UserMFAVerifyPass(Guid userId, string browserFingerprint) => $"UserMFAVerifyPass:{userId}:{browserFingerprint}";
public static string TrialSiteInfo(Guid trialSiteId) => $"{trialSiteId}TrialSiteInfo";
public static string TrialDataStoreType(Guid trialId) => $"TrialDataStoreType:{trialId}";
}
public static class CacheHelper
{
public static async Task<string?> GetTrialStatusAsync(Guid trialId, IRepository<Trial> _trialRepository)
{
var statusStr = await _trialRepository.Where(t => t.Id == trialId, ignoreQueryFilters: true).Select(t => t.TrialStatusStr).FirstOrDefaultAsync();
return statusStr;
}
public class TrialSiteInfoDTO
{
public string TrialSiteCode { get; set; }
public string TrialCode { get; set; }
}
public static async Task<TrialSiteInfoDTO> GetTrialSiteInfo(Guid trialSiteId, IRepository<TrialSite> _trialSiteRepository)
{
var obj = await _trialSiteRepository.Where(t => t.Id == trialSiteId, ignoreQueryFilters: true).Select(t => new TrialSiteInfoDTO { TrialCode= t.Trial.TrialCode, TrialSiteCode= t.TrialSiteCode }).FirstOrDefaultAsync();
return obj??new TrialSiteInfoDTO();
}
public static async Task<List<SystemAnonymization>> GetSystemAnonymizationListAsync(IRepository<SystemAnonymization> _systemAnonymizationRepository)
{
var list = await _systemAnonymizationRepository.Where(t => t.IsEnable).ToListAsync();
return list;
}
}
+132 -47
View File
@@ -1,48 +1,32 @@
using IRaCIS.Core.Domain.Share;
using System.Text;
using Microsoft.AspNetCore.Hosting;
using IRaCIS.Core.Infrastructure;
using Medallion.Threading;
using FellowOakDicom;
using FellowOakDicom;
using FellowOakDicom.Imaging.Codec;
using System.Data;
using IRaCIS.Core.Domain.Models;
using FellowOakDicom.Network;
using IRaCIS.Core.SCP.Service;
using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Infra.EFCore;
using IRaCIS.Core.Infrastructure;
using IRaCIS.Core.Infrastructure.Extention;
using IRaCIS.Core.SCP.Service;
using MassTransit;
using System.Runtime.Intrinsics.X86;
using Medallion.Threading;
using Microsoft.AspNetCore.Hosting;
using Serilog.Sinks.File;
using System.Data;
using System.Runtime.Intrinsics.X86;
using System.Text;
using ZiggyCreatures.Caching.Fusion;
using static IRaCIS.Core.SCP.Service.CacheHelper;
namespace IRaCIS.Core.SCP.Service
{
public class DicomArchiveService : BaseService, IDicomArchiveService
public class DicomArchiveService(IRepository<SCPPatient> _patientRepository,
IRepository<SCPStudy> _studyRepository,
IRepository<SCPSeries> _seriesRepository,
IRepository<SCPInstance> _instanceRepository
) : BaseService, IDicomArchiveService
{
private readonly IRepository<SCPPatient> _patientRepository;
private readonly IRepository<SCPStudy> _studyRepository;
private readonly IRepository<SCPSeries> _seriesRepository;
private readonly IRepository<SCPInstance> _instanceRepository;
private readonly IRepository<Dictionary> _dictionaryRepository;
private readonly IDistributedLockProvider _distributedLockProvider;
private List<Guid> _instanceIdList = new List<Guid>();
public DicomArchiveService(IRepository<SCPPatient> patientRepository, IRepository<SCPStudy> studyRepository,
IRepository<SCPSeries> seriesRepository,
IRepository<SCPInstance> instanceRepository,
IRepository<Dictionary> dictionaryRepository,
IDistributedLockProvider distributedLockProvider)
{
_distributedLockProvider = distributedLockProvider;
_studyRepository = studyRepository;
_patientRepository = patientRepository;
_seriesRepository = seriesRepository;
_instanceRepository = instanceRepository;
_dictionaryRepository = dictionaryRepository;
}
@@ -52,16 +36,19 @@ namespace IRaCIS.Core.SCP.Service
/// <param name="dataset"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public async Task<Guid> ArchiveDicomFileAsync(DicomDataset dataset, Guid trialId, Guid trialSiteId, string fileRelativePath, string callingAE, string calledAE,long fileSize)
public async Task<Guid> ArchiveDicomFileAsync(DicomFile dicomFile, Guid trialId, Guid trialSiteId, string fileRelativePath, string callingAE, string calledAE, long fileSize)
{
var dataset = dicomFile.Dataset;
string studyInstanceUid = dataset.GetString(DicomTag.StudyInstanceUID);
string seriesInstanceUid = dataset.GetString(DicomTag.SeriesInstanceUID);
string sopInstanceUid = dataset.GetString(DicomTag.SOPInstanceUID);
string patientIdStr = dataset.GetSingleValueOrDefault(DicomTag.PatientID,string.Empty);
string patientIdStr = dataset.GetSingleValueOrDefault(DicomTag.PatientID, string.Empty);
//Guid patientId= IdentifierHelper.CreateGuid(patientIdStr);
Guid studyId = IdentifierHelper.CreateGuid(studyInstanceUid,trialId.ToString());
Guid studyId = IdentifierHelper.CreateGuid(studyInstanceUid, trialId.ToString());
Guid seriesId = IdentifierHelper.CreateGuid(studyInstanceUid, seriesInstanceUid, trialId.ToString());
Guid instanceId = IdentifierHelper.CreateGuid(studyInstanceUid, seriesInstanceUid, sopInstanceUid, trialId.ToString());
@@ -74,15 +61,15 @@ namespace IRaCIS.Core.SCP.Service
//using (@lock.Acquire())
{
var findPatient = await _patientRepository.FirstOrDefaultAsync(t => t.PatientIdStr == patientIdStr && t.TrialSiteId==trialSiteId );
var findStudy = await _studyRepository.FirstOrDefaultAsync(t=>t.Id== studyId);
var findSerice = await _seriesRepository.FirstOrDefaultAsync(t => t.Id == seriesId);
var findPatient = await _patientRepository.FirstOrDefaultAsync(t => t.PatientIdStr == patientIdStr && t.TrialSiteId == trialSiteId);
var findStudy = await _studyRepository.FirstOrDefaultAsync(t => t.Id == studyId);
var findSerice = await _seriesRepository.FirstOrDefaultAsync(t => t.Id == seriesId);
var findInstance = await _instanceRepository.FirstOrDefaultAsync(t => t.Id == instanceId);
DateTime? studyTime = dataset.GetSingleValueOrDefault(DicomTag.StudyDate, string.Empty) == string.Empty ? null : dataset.GetSingleValue<DateTime>(DicomTag.StudyDate).Add(dataset.GetSingleValueOrDefault(DicomTag.StudyTime, string.Empty) == string.Empty ? TimeSpan.Zero : dataset.GetSingleValue<DateTime>(DicomTag.StudyTime).TimeOfDay);
//先传输了修改了患者编号的,又传输了没有修改患者编号的,导致后传输的没有修改患者编号的下面的检查为0
if (findPatient == null && findStudy==null)
if (findPatient == null /*&& findStudy==null*/)
{
isPatientNeedAdd = true;
@@ -90,8 +77,8 @@ namespace IRaCIS.Core.SCP.Service
findPatient = new SCPPatient()
{
Id = NewId.NextSequentialGuid(),
TrialId=trialId,
TrialSiteId=trialSiteId,
TrialId = trialId,
TrialSiteId = trialSiteId,
PatientIdStr = dataset.GetSingleValueOrDefault(DicomTag.PatientID, string.Empty),
PatientName = dataset.GetSingleValueOrDefault(DicomTag.PatientName, string.Empty),
PatientAge = dataset.GetSingleValueOrDefault(DicomTag.PatientAge, string.Empty),
@@ -119,7 +106,7 @@ namespace IRaCIS.Core.SCP.Service
DateTime birthDate;
if (findPatient.PatientAge == string.Empty && studyTime.HasValue && DateTime.TryParse(findPatient.PatientBirthDate,out birthDate))
if (findPatient.PatientAge == string.Empty && studyTime.HasValue && DateTime.TryParse(findPatient.PatientBirthDate, out birthDate))
{
var patientAge = studyTime.Value.Year - birthDate.Year;
// 如果生日还未到,年龄减去一岁
@@ -151,6 +138,32 @@ namespace IRaCIS.Core.SCP.Service
}
findPatient.LatestPushTime = DateTime.Now;
findPatient.PatientName = dataset.GetSingleValueOrDefault(DicomTag.PatientName, string.Empty);
findPatient.PatientSex = dataset.GetSingleValueOrDefault(DicomTag.PatientSex, string.Empty);
findPatient.PatientAge = dataset.GetSingleValueOrDefault(DicomTag.PatientAge, string.Empty);
findPatient.PatientBirthDate = dataset.GetSingleValueOrDefault(DicomTag.PatientBirthDate, string.Empty);
if (findPatient.PatientBirthDate.Length == 8)
{
var birthDateStr = $"{findPatient.PatientBirthDate[0]}{findPatient.PatientBirthDate[1]}{findPatient.PatientBirthDate[2]}{findPatient.PatientBirthDate[3]}-{findPatient.PatientBirthDate[4]}{findPatient.PatientBirthDate[5]}-{findPatient.PatientBirthDate[6]}{findPatient.PatientBirthDate[7]}";
var yearStr = $"{findPatient.PatientBirthDate[0]}{findPatient.PatientBirthDate[1]}{findPatient.PatientBirthDate[2]}{findPatient.PatientBirthDate[3]}";
int year = 0;
var canParse = int.TryParse(yearStr, out year);
if (canParse && year > 1900)
{
findPatient.PatientBirthDate = birthDateStr;
}
else
{
findPatient.PatientBirthDate = string.Empty;
}
}
}
if (findStudy == null)
@@ -167,6 +180,9 @@ namespace IRaCIS.Core.SCP.Service
TrialSiteId = trialSiteId,
StudyInstanceUid = studyInstanceUid,
StudyTime = studyTime,
DicomStudyDate = dataset.GetSingleValueOrDefault(DicomTag.StudyDate, string.Empty),
DicomStudyTime = dataset.GetSingleValueOrDefault(DicomTag.StudyTime, string.Empty),
Modalities = dataset.GetSingleValueOrDefault(DicomTag.Modality, string.Empty),
//ModalityForEdit = modalityForEdit,
Description = dataset.GetSingleValueOrDefault(DicomTag.StudyDescription, string.Empty),
@@ -188,6 +204,18 @@ namespace IRaCIS.Core.SCP.Service
AcquisitionNumber = dataset.GetSingleValueOrDefault(DicomTag.AcquisitionNumber, string.Empty),
TriggerTime = dataset.GetSingleValueOrDefault(DicomTag.TriggerTime, string.Empty),
RadionuclideTotalDose= dataset.GetSingleValueOrDefault(DicomTag.RadionuclideTotalDose, string.Empty),
RadionuclideHalfLife= dataset.GetSingleValueOrDefault(DicomTag.RadionuclideHalfLife, string.Empty),
RadiopharmaceuticalStartTime = dataset.GetSingleValueOrDefault(DicomTag.RadiopharmaceuticalStartTime, string.Empty),
Manufacturer = dataset.GetSingleValueOrDefault(DicomTag.Manufacturer, string.Empty),
ManufacturerModelName = dataset.GetSingleValueOrDefault(DicomTag.ManufacturerModelName, string.Empty),
DeviceSerialNumber = dataset.GetSingleValueOrDefault(DicomTag.DeviceSerialNumber, string.Empty),
DeviceUID = dataset.GetSingleValueOrDefault(DicomTag.DeviceUID, string.Empty),
SoftwareVersions = dataset.GetSingleValueOrDefault(DicomTag.SoftwareVersions, string.Empty),
PatientWeight = dataset.GetSingleValueOrDefault(DicomTag.PatientWeight, string.Empty),
//IsDoubleReview = addtionalInfo.IsDoubleReview,
@@ -201,6 +229,20 @@ namespace IRaCIS.Core.SCP.Service
findStudy.PatientBirthDate = $"{findStudy.PatientBirthDate[0]}{findStudy.PatientBirthDate[1]}{findStudy.PatientBirthDate[2]}{findStudy.PatientBirthDate[3]}-{findStudy.PatientBirthDate[4]}{findStudy.PatientBirthDate[5]}-{findStudy.PatientBirthDate[6]}{findStudy.PatientBirthDate[7]}";
}
}
else
{
findStudy.DicomStudyDate = dataset.GetSingleValueOrDefault(DicomTag.StudyDate, string.Empty);
findStudy.DicomStudyTime = dataset.GetSingleValueOrDefault(DicomTag.StudyTime, string.Empty);
findStudy.CalledAE = calledAE;
findStudy.CallingAE = callingAE;
findStudy.PatientIdStr = patientIdStr;
findStudy.PatientName = dataset.GetSingleValueOrDefault(DicomTag.PatientName, string.Empty);
findStudy.PatientSex = dataset.GetSingleValueOrDefault(DicomTag.PatientSex, string.Empty);
findStudy.PatientAge = dataset.GetSingleValueOrDefault(DicomTag.PatientAge, string.Empty);
findStudy.UpdateTime = DateTime.Now;
await _patientRepository.BatchUpdateNoTrackingAsync(t => t.Id == findStudy.PatientId, u => new SCPPatient() { LatestPushTime = DateTime.Now });
}
if (findSerice == null)
@@ -218,6 +260,9 @@ namespace IRaCIS.Core.SCP.Service
//SeriesTime = dataset.GetSingleValueOrDefault(DicomTag.SeriesDate, DateTime.Now).Add(dataset.GetSingleValueOrDefault(DicomTag.SeriesTime, DateTime.Now).TimeOfDay),
//SeriesTime = DateTime.TryParse(dataset.GetSingleValue<string>(DicomTag.SeriesDate) + dataset.GetSingleValue<string>(DicomTag.SeriesTime), out DateTime dt) ? dt : null,
SeriesTime = dataset.GetSingleValueOrDefault(DicomTag.SeriesDate, string.Empty) == string.Empty ? null : dataset.GetSingleValue<DateTime>(DicomTag.SeriesDate).Add(dataset.GetSingleValueOrDefault(DicomTag.SeriesTime, string.Empty) == string.Empty ? TimeSpan.Zero : dataset.GetSingleValue<DateTime>(DicomTag.SeriesTime).TimeOfDay),
DicomSeriesDate = dataset.GetSingleValueOrDefault(DicomTag.SeriesDate, string.Empty),
DicomSeriesTime = dataset.GetSingleValueOrDefault(DicomTag.SeriesTime, string.Empty),
Modality = dataset.GetSingleValueOrDefault(DicomTag.Modality, string.Empty),
Description = dataset.GetSingleValueOrDefault(DicomTag.SeriesDescription, string.Empty),
SliceThickness = dataset.GetSingleValueOrDefault(DicomTag.SliceThickness, string.Empty),
@@ -233,13 +278,29 @@ namespace IRaCIS.Core.SCP.Service
AcquisitionNumber = dataset.GetSingleValueOrDefault(DicomTag.AcquisitionNumber, string.Empty),
TriggerTime = dataset.GetSingleValueOrDefault(DicomTag.TriggerTime, string.Empty),
RadiopharmaceuticalInformationSequence = dataset.GetSingleValueOrDefault(DicomTag.RadiopharmaceuticalInformationSequence, string.Empty),
AcquisitionDate = dataset.GetSingleValueOrDefault(DicomTag.AcquisitionDate, string.Empty),
InstanceCount = 0
};
++findStudy.SeriesCount;
}
else
{
findSerice.DicomSeriesDate = dataset.GetSingleValueOrDefault(DicomTag.SeriesDate, string.Empty);
findSerice.DicomSeriesTime = dataset.GetSingleValueOrDefault(DicomTag.SeriesTime, string.Empty);
findSerice.UpdateTime = DateTime.Now;
}
var transferSyntaxUID = dicomFile.FileMetaInfo.GetSingleValueOrDefault(DicomTag.TransferSyntaxUID, string.Empty);
var isEncapsulated = false;
if (transferSyntaxUID.IsNotNullOrEmpty())
{
isEncapsulated = DicomTransferSyntax.Lookup(DicomUID.Parse(transferSyntaxUID)).IsEncapsulated;
}
if (findInstance == null)
{
@@ -264,22 +325,46 @@ namespace IRaCIS.Core.SCP.Service
SliceLocation = dataset.GetSingleValueOrDefault(DicomTag.SliceLocation, 0),
SliceThickness = dataset.GetSingleValueOrDefault(DicomTag.SliceThickness, string.Empty),
NumberOfFrames = dataset.GetSingleValueOrDefault(DicomTag.NumberOfFrames, 0),
NumberOfFrames = dataset.GetSingleValueOrDefault(DicomTag.NumberOfFrames, 1),
PixelSpacing = dataset.GetSingleValueOrDefault(DicomTag.PixelSpacing, string.Empty),
ImagerPixelSpacing = dataset.GetSingleValueOrDefault(DicomTag.ImagerPixelSpacing, string.Empty),
FrameOfReferenceUID = dataset.GetSingleValueOrDefault(DicomTag.FrameOfReferenceUID, string.Empty),
WindowCenter = dataset.GetSingleValueOrDefault(DicomTag.WindowCenter, string.Empty),
WindowWidth = dataset.GetSingleValueOrDefault(DicomTag.WindowWidth, string.Empty),
PhotometricInterpretation = dataset.GetSingleValueOrDefault(DicomTag.PhotometricInterpretation, string.Empty),
BitsAllocated = dataset.GetSingleValueOrDefault(DicomTag.BitsAllocated, 0),
PixelRepresentation = dataset.GetSingleValueOrDefault(DicomTag.PixelRepresentation, string.Empty),
RescaleIntercept = dataset.GetSingleValueOrDefault(DicomTag.RescaleIntercept, string.Empty),
RescaleSlope = dataset.GetSingleValueOrDefault(DicomTag.RescaleSlope, string.Empty),
ImagePositionPatient = dataset.GetSingleValueOrDefault(DicomTag.ImagePositionPatient, string.Empty),
ImageOrientationPatient = dataset.GetSingleValueOrDefault(DicomTag.ImageOrientationPatient, string.Empty),
SequenceOfUltrasoundRegions = dataset.GetSingleValueOrDefault(DicomTag.SequenceOfUltrasoundRegions, string.Empty),
FrameTime = dataset.GetSingleValueOrDefault(DicomTag.FrameTime, string.Empty),
CorrectedImage = dataset.GetSingleValueOrDefault(DicomTag.CorrectedImage, string.Empty),
Units = dataset.GetSingleValueOrDefault(DicomTag.Units, string.Empty),
DecayCorrection = dataset.GetSingleValueOrDefault(DicomTag.DecayCorrection, string.Empty),
EncapsulatedDocument = dataset.GetSingleValueOrDefault(DicomTag.EncapsulatedDocument, string.Empty),
Path = fileRelativePath,
FileSize= fileSize,
FileSize = fileSize,
};
++findStudy.InstanceCount;
++findSerice.InstanceCount;
}
else
{
findInstance.SOPClassUID = dataset.GetSingleValueOrDefault(DicomTag.SOPClassUID, string.Empty);
findInstance.MediaStorageSOPClassUID = dataset.GetSingleValueOrDefault(DicomTag.MediaStorageSOPClassUID, string.Empty);
findInstance.TransferSytaxUID = transferSyntaxUID;
findInstance.MediaStorageSOPInstanceUID = dataset.GetSingleValueOrDefault(DicomTag.MediaStorageSOPInstanceUID, string.Empty);
findInstance.IsEncapsulated = isEncapsulated;
findInstance.UpdateTime = DateTime.Now;
}
if (isPatientNeedAdd)
{
@@ -304,7 +389,7 @@ namespace IRaCIS.Core.SCP.Service
}
else
{
await _instanceRepository.BatchUpdateNoTrackingAsync(t => t.Id == instanceId, u => new SCPInstance() { Path = fileRelativePath,FileSize=fileSize });
await _instanceRepository.BatchUpdateNoTrackingAsync(t => t.Id == instanceId, u => new SCPInstance() { Path = fileRelativePath, FileSize = fileSize });
}
await _studyRepository.SaveChangesAsync();
@@ -0,0 +1,317 @@
//--------------------------------------------------------------------
// 此代码由liquid模板自动生成 byzhouhang 20240909
// 生成时间 2026-03-10 06:15:17Z
// 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
//--------------------------------------------------------------------
using AutoMapper;
using IRaCIS.Core.Application.Helper;
using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Infra.EFCore;
using IRaCIS.Core.Infra.EFCore.Common;
using IRaCIS.Core.Infrastructure.Extention;
using IRaCIS.Core.SCP;
using IRaCIS.Core.SCP.Service;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options;
using System.Drawing;
using System.Threading.Channels;
using System.Threading.Tasks;
using ZiggyCreatures.Caching.Fusion;
namespace IRaCIS.Core.SCP.Service;
public class FileUploadRecordAddOrEdit
{
public Guid? Id { get; set; }
public string FileName { get; set; }
public long FileSize { get; set; }
public string FileType { get; set; }
public string Path { get; set; }
public string UploadBatchId { get; set; }
public BatchDataType BatchDataType { get; set; }
public string StudyCode { get; set; }
public Guid? TrialId { get; set; }
public Guid? SubjectId { get; set; }
public Guid? SubjectVisitId { get; set; }
public Guid? DicomStudyId { get; set; }
public Guid? NoneDicomStudyId { get; set; }
public string FileMarkId { get; set; }
public int? Priority { get; set; }
public string IP { get; set; }
public bool? IsNeedSync { get; set; }
public string UploadRegion { get; set; }
public string TargetRegion { get; set; }
}
public interface IFileUploadRecordService
{
Task<IResponseOutput> AddOrUpdateFileUploadRecord(FileUploadRecordAddOrEdit addOrEditFileUploadRecord);
}
[ApiExplorerSettings(GroupName = "Common")]
public class FileUploadRecordService(IRepository<FileUploadRecord> _fileUploadRecordRepository,
IMapper _mapper, IUserInfo _userInfo, IStringLocalizer _localizer, IOptionsMonitor<ObjectStoreServiceOptions> options,
IFusionCache _fusionCache, IRepository<Trial> _trialRepository, FileSyncQueue _fileSyncQueue) : BaseService, IFileUploadRecordService
{
ObjectStoreServiceOptions ObjectStoreServiceConfig = options.CurrentValue;
public async Task<IResponseOutput> AddOrUpdateFileUploadRecord(FileUploadRecordAddOrEdit addOrEditFileUploadRecord)
{
addOrEditFileUploadRecord.IP = _userInfo.IP;
if (ObjectStoreServiceConfig.IsOpenStoreSync && _userInfo.Domain.IsNotNullOrEmpty())
{
var find = ObjectStoreServiceConfig.SyncConfigList.FirstOrDefault(t => t.Domain == _userInfo.Domain);
if (find != null)
{
addOrEditFileUploadRecord.UploadRegion = find.UploadRegion;
addOrEditFileUploadRecord.TargetRegion = find.TargetRegion;
}
else
{
//前后端调试的时候,上传的时候域名不对应,自动按照后端配置设置上传区域和同步区域
var apiDefalut = ObjectStoreServiceConfig.SyncConfigList.FirstOrDefault(t => t.UploadRegion == ObjectStoreServiceConfig.ApiDeployRegion);
if (apiDefalut != null)
{
addOrEditFileUploadRecord.UploadRegion = apiDefalut.UploadRegion;
addOrEditFileUploadRecord.TargetRegion = apiDefalut.TargetRegion;
}
}
}
if (addOrEditFileUploadRecord.TrialId != null)
{
var trialDataStore = await _fusionCache.GetOrSetAsync(CacheKeys.TrialDataStoreType(addOrEditFileUploadRecord.TrialId.Value), async _ =>
{
return await _trialRepository.Where(t => t.Id == addOrEditFileUploadRecord.TrialId).Select(t => t.TrialDataStoreType)
.FirstOrDefaultAsync();
},
TimeSpan.FromDays(7)
);
//项目配置了,那么就设置需要同步
if (trialDataStore == TrialDataStore.MUtiCenter)
{
addOrEditFileUploadRecord.IsNeedSync = true;
addOrEditFileUploadRecord.Priority = 0;
}
else
{
addOrEditFileUploadRecord.IsNeedSync = false;
//addOrEditFileUploadRecord.TargetRegion = "";
}
}
else
{
//系统文件,默认同步
addOrEditFileUploadRecord.IsNeedSync = true;
addOrEditFileUploadRecord.Priority = 0;
}
var entity = await _fileUploadRecordRepository.InsertOrUpdateAsync(addOrEditFileUploadRecord, true);
if (addOrEditFileUploadRecord.IsNeedSync == true)
{
_fileSyncQueue.Enqueue(entity.Id, addOrEditFileUploadRecord.Priority ?? 0);
}
return ResponseOutput.Ok(entity.Id.ToString());
}
}
public sealed class FileSyncQueue
{
/// <summary>
/// 优先级队列(仅负责排序)
/// </summary>
private readonly PriorityQueue<Guid, int> _queue = new();
/// <summary>
/// 当前等待中的任务(唯一真实数据)
/// key = Guid
/// value = 最新 priority
/// </summary>
private readonly Dictionary<Guid, int> _waiting = new();
/// <summary>
/// 正在执行的任务(防止重复执行)
/// </summary>
private readonly HashSet<Guid> _running = new();
/// <summary>
/// worker 等待信号
/// </summary>
private readonly SemaphoreSlim _signal = new(0);
private readonly object _lock = new();
// ============================================================
// Enqueue
// ============================================================
/// <summary>
/// 入队(同 Guid 会覆盖优先级)
/// </summary>
public void Enqueue(Guid id, int priority)
{
bool needSignal = false;
lock (_lock)
{
// 如果正在执行,忽略(防止重复)
if (_running.Contains(id))
return;
// 是否新任务(用于减少 signal 风暴)
if (!_waiting.ContainsKey(id))
needSignal = true;
// 更新为最新优先级(最后一次为准)
_waiting[id] = priority; //等价于添加或者更新
// PriorityQueue 无法更新节点
// 允许旧节点存在,Dequeue 时过滤
_queue.Enqueue(id, -priority);
}
// 只有新增任务才唤醒 worker
if (needSignal)
_signal.Release();
}
// ============================================================
// Dequeue
// ============================================================
/// <summary>
/// 获取一个待执行任务(无任务时自动等待)
/// </summary>
public async Task<Guid> DequeueAsync(CancellationToken ct)
{
while (true)
{
await _signal.WaitAsync(ct);
lock (_lock)
{
while (_queue.Count > 0)
{
var id = _queue.Dequeue();
// 已被覆盖或取消
if (!_waiting.TryGetValue(id, out _))
continue;
// 标记为运行中
_waiting.Remove(id);
_running.Add(id);
return id;
}
}
}
}
// ============================================================
// Complete
// ============================================================
/// <summary>
/// 任务执行完成(必须调用)
/// </summary>
public void Complete(Guid id)
{
lock (_lock)
{
_running.Remove(id);
}
}
// ============================================================
// Snapshot
// ============================================================
/// <summary>
/// 当前等待中的任务快照
/// </summary>
public Guid[] Snapshot()
{
lock (_lock)
{
return _waiting.Keys.ToArray();
}
}
// ============================================================
// 状态信息(调试用)
// ============================================================
public int WaitingCount
{
get
{
lock (_lock)
return _waiting.Count;
}
}
public int RunningCount
{
get
{
lock (_lock)
return _running.Count;
}
}
}
@@ -5,7 +5,7 @@ namespace IRaCIS.Core.SCP.Service
{
public interface IDicomArchiveService
{
Task<Guid> ArchiveDicomFileAsync(DicomDataset dicomDataset,Guid trialId,Guid trialSiteId, string fileRelativePath,string callingAE,string calledAE,long fileSize);
Task<Guid> ArchiveDicomFileAsync(DicomFile dicomFile, Guid trialId,Guid trialSiteId, string fileRelativePath,string callingAE,string calledAE,long fileSize);
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,37 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"ObjectStoreService": {
"ObjectStoreUse": "AliyunOSS",
"AliyunOSS": {
"RegionId": "cn-shanghai",
"InternalEndpoint": "https://oss-cn-shanghai-internal.aliyuncs.com",
"EndPoint": "https://oss-cn-shanghai.aliyuncs.com",
"AccessKeyId": "LTAI5tFUCCmz5TwghZHsj45Y",
"AccessKeySecret": "8evrBy1fVfzJG25i67Jm0xqn9Xcw2T",
"RoleArn": "acs:ram::1078130221702011:role/uat-oss-access",
"BucketName": "tl-med-irc-event-store",
"ViewEndpoint": "https://tl-med-irc-event-store.oss-cn-shanghai.aliyuncs.com",
"Region": "oss-cn-shanghai",
"DurationSeconds": 7200
}
},
"ConnectionStrings": {
"RemoteNew": "Server=101.132.253.119,1435;Database=irc_Prpd_bak;User ID=sa;Password=xc@123456;TrustServerCertificate=true",
"Hangfire": "Server=101.132.253.119,1435;Database=irc_Hangfire_bak;User ID=sa;Password=xc@123456;TrustServerCertificate=true"
},
"DicomSCPServiceConfig": {
"CalledAEList": [
"STORESCP"
],
"ServerPort": 11112
}
}
+10 -4
View File
@@ -25,6 +25,7 @@ using Microsoft.Extensions.Options;
using Org.BouncyCastle.Tls;
using RestSharp;
using RestSharp.Authenticators;
using Serilog;
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -126,7 +127,7 @@ namespace IRaCIS.Api.Controllers
var token = _tokenService.GetToken(new UserTokenInfo()
{
IdentityUserId = Guid.NewGuid(),
UserName = "Share001",
UserName = "ImageShare",
UserTypeEnum = UserTypeEnum.ShareImage,
});
@@ -137,9 +138,14 @@ namespace IRaCIS.Api.Controllers
public async Task<IResponseOutput> GetObjectStoreTokenAsync([FromServices] IOptionsMonitor<ObjectStoreServiceOptions> options, [FromServices] IOSSService _oSSService)
{
var result = _oSSService.GetObjectStoreTempToken();
var domain = HttpContext.Request.Host.Host;
//result.AWS = await GetAWSTemToken(options.CurrentValue);
var result = _oSSService.GetObjectStoreTempToken(domain);
Log.Logger.Information($"使用域名:{domain}请求token.返回{result.ToJsonStr()}");
return ResponseOutput.Ok(result);
@@ -279,7 +285,7 @@ namespace IRaCIS.Api.Controllers
var isExpire = _tokenService.IsTokenExpired(token);
if (!await _useRepository.AnyAsync(t => t.Id == Guid.Parse(userId) && t.EmailToken == token && t.IsFirstAdd) || isExpire)
if ( Guid.TryParse(userId,out _) == false || isExpire || !await _useRepository.AnyAsync(t => t.Id == Guid.Parse(userId) && t.EmailToken == token && t.IsFirstAdd) )
{
decodeUrl = errorUrl + $"?lang={lang}&ErrorMessage={System.Web.HttpUtility.UrlEncode(I18n.T("UserRedirect_InitializationLinkExpire"))} ";
}
@@ -26,6 +26,7 @@ namespace IRaCIS.Core.API.Controllers
ITrialDocumentService _trialDocumentService,
IReadingImageTaskService _iReadingImageTaskService,
ITrialConfigService _trialConfigService,
IStudyService _studyService,
IClinicalAnswerService _clinicalAnswerService,
IReadingClinicalDataService _readingClinicalDataService,
IQCOperationService _qCOperationService,
@@ -146,6 +147,26 @@ namespace IRaCIS.Core.API.Controllers
}
/// <summary>
/// 修正患者基本信息
/// </summary>
/// <param name="opt"></param>
/// <returns></returns>
[HttpPost, Route("Inspection/Study/AmendmentPatientInfo")]
[TrialGlobalLimit("AfterStopCannNotOpt")]
[UnitOfWork]
public async Task<IResponseOutput> AmendmentPatientInfo(DataInspectionDto<EditPatientInfoCommand> opt)
{
var singid = await _inspectionService.RecordSing(opt.SignInfo);
var result = await _studyService.AmendmentPatientInfo(opt.Data);
await _inspectionService.CompletedSign(singid, result);
return result;
}
/// <summary>
/// 医学审核完成
/// </summary>
@@ -155,10 +176,19 @@ namespace IRaCIS.Core.API.Controllers
[TrialGlobalLimit("AfterStopCannNotOpt")]
[UnitOfWork]
public async Task<IResponseOutput> FinishMedicalReview(DataInspectionDto<FinishMedicalReviewInDto> opt)
public async Task<IResponseOutput> FinishMedicalReview(DataInspectionDto<CloseAndFinishMedicalReview> opt)
{
var singid = await _inspectionService.RecordSing(opt.SignInfo);
var result = await _readingMedicalReviewService.FinishMedicalReview(opt.Data);
await _readingMedicalReviewService.ClosedMedicalReviewDialog(new ClosedMedicalReviewDialogInDto() {
DialogCloseReason=opt.Data.DialogCloseReason,
IsClosedDialog=opt.Data.IsClosedDialog,
MedicalDialogCloseEnum=opt.Data.MedicalDialogCloseEnum,
TaskMedicalReviewId=opt.Data.TaskMedicalReviewId
});
var result = await _readingMedicalReviewService.FinishMedicalReview(new FinishMedicalReviewInDto() {
TaskMedicalReviewId=opt.Data.TaskMedicalReviewId
});
await _inspectionService.CompletedSign(singid, result);
return result;
}
@@ -9,6 +9,7 @@ using IRaCIS.Core.Application.Filter;
using IRaCIS.Core.Application.Helper;
using IRaCIS.Core.Application.MassTransit.Command;
using IRaCIS.Core.Application.Service;
using IRaCIS.Core.Application.ViewModel;
using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Infra.EFCore;
@@ -273,6 +274,7 @@ namespace IRaCIS.Core.API.Controllers
public List<OSSFileDTO> UploadedFileList { get; set; } = new List<OSSFileDTO>();
public bool? IsImageSegmentLabel { get; set; }
public class OSSFileDTO
{
@@ -521,7 +523,17 @@ namespace IRaCIS.Core.API.Controllers
}
else
{
await _noneDicomStudyFileRepository.AddAsync(new NoneDicomStudyFile() { FileName = item.FileName, Path = item.FilePath, NoneDicomStudyId = noneDicomStudyId.Value, FileType = item.FileType, FileSize = item.FileFize });
if (incommand.IsImageSegmentLabel == true)
{
await _noneDicomStudyFileRepository.AddAsync(new NoneDicomStudyFile() { FileName = item.FileName, Path = item.FilePath, ImageLabelNoneDicomStudyId = noneDicomStudyId.Value, FileType = item.FileType, FileSize = item.FileFize });
}
else
{
await _noneDicomStudyFileRepository.AddAsync(new NoneDicomStudyFile() { FileName = item.FileName, Path = item.FilePath, NoneDicomStudyId = noneDicomStudyId.Value, FileType = item.FileType, FileSize = item.FileFize });
}
}
@@ -557,6 +569,7 @@ namespace IRaCIS.Core.API.Controllers
/// 一致性核查 excel上传 支持三种格式
/// </summary>
/// <param name="trialId"></param>
/// <param name="isFullCheck"></param>
/// <param name="oSSService"></param>
/// <param name="_inspectionFileRepository"></param>
/// <returns></returns>
@@ -564,12 +577,14 @@ namespace IRaCIS.Core.API.Controllers
[HttpPost("QCOperation/UploadVisitCheckExcel/{trialId:guid}")]
[TrialGlobalLimit("AfterStopCannNotOpt")]
public async Task<IResponseOutput> UploadVisitCheckExcel(Guid trialId, [FromServices] IOSSService oSSService, [FromServices] IRepository<InspectionFile> _inspectionFileRepository)
public async Task<IResponseOutput> UploadVisitCheckExcel(Guid trialId, bool isFullCheck, [FromServices] IOSSService oSSService, [FromServices] IRepository<InspectionFile> _inspectionFileRepository)
{
var fileName = string.Empty;
var templateFileStream = new MemoryStream();
var inspectionFileId = Guid.Empty;
await FileUploadToOSSAsync(async (realFileName, fileStream) =>
{
fileName = realFileName;
@@ -584,9 +599,11 @@ namespace IRaCIS.Core.API.Controllers
templateFileStream.Seek(0, SeekOrigin.Begin);
var ossRelativePath = await oSSService.UploadToOSSAsync(fileStream, $"{trialId.ToString()}/InspectionUpload/DataReconciliation", realFileName);
var ossRelativePath = await oSSService.UploadToOSSAsync(fileStream, $"{trialId.ToString()}/InspectionUpload/DataReconciliation", realFileName, uploadInfo: new FileUploadRecordAddOrEdit() { TrialId = trialId, BatchDataType = BatchDataType.DataReconciliation });
await _inspectionFileRepository.AddAsync(new InspectionFile() { FileName = realFileName, RelativePath = ossRelativePath, TrialId = trialId }, true);
var addEntity = await _inspectionFileRepository.AddAsync(new InspectionFile() { FileName = realFileName, RelativePath = ossRelativePath, TrialId = trialId }, true);
inspectionFileId = addEntity.Id;
return ossRelativePath;
@@ -732,8 +749,11 @@ namespace IRaCIS.Core.API.Controllers
if (etcCheckList == null || etcCheckList.Count == 0)
{
await _inspectionFileRepository.BatchUpdateNoTrackingAsync(t => t.Id == inspectionFileId, u => new InspectionFile() { CheckState = EDCCheckState.Failed });
//---请保证上传数据符合模板文件中的样式,且存在有效数据。
return ResponseOutput.NotOk(_localizer["UploadDownLoad_InvalidData"]);
}
else
{
@@ -753,6 +773,8 @@ namespace IRaCIS.Core.API.Controllers
if (etcCheckList.Count == 0)
{
await _inspectionFileRepository.BatchUpdateNoTrackingAsync(t => t.Id == inspectionFileId, u => new InspectionFile() { CheckState = EDCCheckState.Failed });
//---请保证上传数据符合模板文件中的样式,且存在有效数据。
return ResponseOutput.NotOk(_localizer["UploadDownLoad_InvalidData"]);
}
@@ -764,8 +786,19 @@ namespace IRaCIS.Core.API.Controllers
//var client = _mediator.CreateRequestClient<ConsistenCheckCommand>();
//await client.GetResponse<ConsistenCheckResult>(new ConsistenCheckCommand() { ETCList = etcCheckList, TrialId = trialId });
//不获取结果,不用定义返回类型
await _mediator.Send(new ConsistenCheckCommand() { ETCList = etcCheckList, TrialId = trialId });
if (isFullCheck)
{
await _mediator.Send(new ConsistenFullCheckCommand() { ETCList = etcCheckList, TrialId = trialId, InspectionFileId = inspectionFileId });
}
else
{
//不获取结果,不用定义返回类型
await _mediator.Send(new ConsistenCheckCommand() { ETCList = etcCheckList, TrialId = trialId });
}
return ResponseOutput.Ok();
@@ -824,7 +857,7 @@ namespace IRaCIS.Core.API.Controllers
throw new BusinessValidationFailedException(_localizer["UploadDownLoad_TemplateUploadData"]);
}
var ossRelativePath = await oSSService.UploadToOSSAsync(fileStream, $"{trialId.ToString()}/InspectionUpload/SiteSurvey", realFileName);
var ossRelativePath = await oSSService.UploadToOSSAsync(fileStream, $"{trialId.ToString()}/InspectionUpload/SiteSurvey", realFileName, uploadInfo: new FileUploadRecordAddOrEdit() { TrialId = trialId ,BatchDataType=BatchDataType.SiteUserSurvey});
await _inspectionFileRepository.AddAsync(new InspectionFile() { FileName = realFileName, RelativePath = ossRelativePath, TrialId = trialId }, true);
@@ -972,7 +1005,11 @@ namespace IRaCIS.Core.API.Controllers
EmailBodyHtml = 4,
Other = 5
ReadKeyFile = 5,
Other = 6,
}
/// <summary>
@@ -1004,6 +1041,9 @@ namespace IRaCIS.Core.API.Controllers
case UploadFileType.EmailBodyHtml:
result = await SingleFileUploadAsync((fileName) => FileStoreHelper.GetSystemFileUploadPath(_hostEnvironment, StaticData.Folder.EmailTemplate, fileName));
break;
case UploadFileType.ReadKeyFile:
result = await SingleFileUploadAsync((fileName) => FileStoreHelper.GetSystemFileUploadPath(_hostEnvironment, StaticData.Folder.ReadKetFile, fileName));
break;
default:
result = await SingleFileUploadAsync((fileName) => FileStoreHelper.GetOtherFileUploadPath(_hostEnvironment, StaticData.Folder.TempFile, fileName));
@@ -15,12 +15,13 @@ using Hangfire.Storage;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using MassTransit.Mediator;
using Microsoft.Extensions.Configuration;
namespace IRaCIS.Core.API.HostService;
public class HangfireHostService(IRecurringMessageScheduler _recurringMessageScheduler,
IRepository<TrialEmailNoticeConfig> _trialEmailNoticeConfigRepository,
IConfiguration _config,
IRepository<EmailNoticeConfig> _emailNoticeConfigrepository,
IMediator _mediator,
ILogger<HangfireHostService> _logger) : IHostedService
@@ -56,7 +57,9 @@ public class HangfireHostService(IRecurringMessageScheduler _recurringMessageSch
}
}
string defaultCulture = _config.GetValue<string>("SystemEmailSendConfig:CronEmailDefaultCulture");
// 项目手动选择,周期性邮件
var taskInfoList = await _trialEmailNoticeConfigRepository.Where(t => t.Trial.TrialStatusStr == StaticData.TrialState.TrialOngoing && t.EmailCron != string.Empty && t.IsAutoSend)
.Select(t => new { t.Id, t.Code, TrialCode = t.Trial.TrialCode, t.EmailCron, t.BusinessScenarioEnum, t.TrialId })
.ToListAsync();
@@ -68,11 +71,11 @@ public class HangfireHostService(IRecurringMessageScheduler _recurringMessageSch
var trialId = task.TrialId;
HangfireJobHelper.AddOrUpdateTrialCronJob(jobId, trialId, task.BusinessScenarioEnum, task.EmailCron);
HangfireJobHelper.AddOrUpdateTrialCronJob(jobId, trialId, task.BusinessScenarioEnum, task.EmailCron, defaultCulture);
}
// 系统邮件定时任务
// 系统邮件定时任务 要设置默认语言
var systemTaskInfoList = await _emailNoticeConfigrepository.Where(t => t.EmailCron != string.Empty && t.IsAutoSend)
.Select(t => new { t.Id, t.Code, t.EmailCron, t.BusinessScenarioEnum, })
.ToListAsync();
@@ -82,7 +85,7 @@ public class HangfireHostService(IRecurringMessageScheduler _recurringMessageSch
//利用主键作为任务Id
var jobId = $"{task.Id}_({task.BusinessScenarioEnum})";
HangfireJobHelper.AddOrUpdateTimingCronJob(jobId, task.BusinessScenarioEnum, task.EmailCron);
HangfireJobHelper.AddOrUpdateTimingCronJob(jobId, task.BusinessScenarioEnum, task.EmailCron, defaultCulture);
}
@@ -0,0 +1,168 @@
using IRaCIS.Core.Application.Helper;
using IRaCIS.Core.Application.Service;
using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.Infra.EFCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
namespace IRaCIS.Core.API.HostService;
public class SyncFileRecoveryService(IServiceScopeFactory _scopeFactory, FileSyncQueue _fileSyncQueue) : BackgroundService
{
private readonly int _pageSize = 500;
/// <summary>
/// 多个程序,如果恢复同一份数据,造成重复同步,SCP服务不恢复任务
/// </summary>
/// <param name="stoppingToken"></param>
/// <returns></returns>
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
//在本地调试的时候,不干涉部署同步任务
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return;
}
using var scope = _scopeFactory.CreateScope();
var fileUploadRecordRepository = scope.ServiceProvider.GetRequiredService<IRepository<FileUploadRecord>>();
// 延迟启动,保证主机快速启动
await Task.Delay(5000, stoppingToken);
int page = 0;
while (!stoppingToken.IsCancellationRequested)
{
// 分页获取未入队任务
var pending = await fileUploadRecordRepository
.Where(x => x.IsNeedSync == true && (x.IsSync == false || x.IsSync == null))
.OrderByDescending(x => x.Priority)
.Select(t => new { t.Id, t.Priority })
.Skip(page * _pageSize)
.Take(_pageSize)
.ToListAsync(stoppingToken);
if (!pending.Any())
break; // 扫描完毕,退出循环
foreach (var file in pending)
{
//file.IsQueued = true; // 避免重复入队
_fileSyncQueue.Enqueue(file.Id, file.Priority ?? 0); // 放入队列
}
page++; // 下一页
await Task.Delay(200, stoppingToken); // 缓解数据库压力
}
}
}
public class FileSyncWorker(IServiceScopeFactory _scopeFactory, ILogger<FileSyncWorker> _logger, FileSyncQueue _fileSyncQueue) : BackgroundService
{
// ⭐ 自动根据服务器CPU
private readonly int _workerCount = Math.Max(1, Environment.ProcessorCount - 1);
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
for (int i = 0; i < _workerCount; i++)
Task.Run(() => WorkerLoop(stoppingToken));
return Task.CompletedTask;
}
private async Task WorkerLoop(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var id = await _fileSyncQueue.DequeueAsync(stoppingToken);
try
{
using var scope = _scopeFactory.CreateScope();
var _fileUploadRecordRepository = scope.ServiceProvider.GetRequiredService<IRepository<FileUploadRecord>>();
var _uploadFileSyncRecordRepository = scope.ServiceProvider.GetRequiredService<IRepository<UploadFileSyncRecord>>();
var syncConfig = (scope.ServiceProvider.GetRequiredService<IOptionsMonitor<ObjectStoreServiceOptions>>()).CurrentValue;
var oss = scope.ServiceProvider.GetRequiredService<IOSSService>();
var file = await _fileUploadRecordRepository.FirstOrDefaultAsync(t => t.Id == id);
// ✅ 不要 return
if (file == null || file.IsNeedSync != true || syncConfig.IsOpenStoreSync == false)
{
continue;
}
if (syncConfig.SyncConfigList.Any(t => t.UploadRegion == file.UploadRegion && t.IsOpenSync == false))
{
continue;
}
//如果发现系统配置某一边同步进行了关闭,那么就直接返回,不执行任务
if (syncConfig.SyncConfigList.Any(t => t.UploadRegion == file.UploadRegion && t.IsOpenSync == false))
{
return;
}
var log = new UploadFileSyncRecord
{
FileUploadRecordId = id,
StartTime = DateTime.Now,
JobState = jobState.RUNNING
};
await _uploadFileSyncRecordRepository.AddAsync(log);
await _uploadFileSyncRecordRepository.SaveChangesAsync(stoppingToken);
try
{
await oss.SyncFileAsync(file.Path.TrimStart('/'), file.UploadRegion == "CN" ? ObjectStoreUse.AliyunOSS : ObjectStoreUse.AWS, file.UploadRegion == "CN" ? ObjectStoreUse.AWS : ObjectStoreUse.AliyunOSS);
file.IsSync = true;
file.SyncFinishedTime = DateTime.Now;
log.JobState = jobState.SUCCESS;
}
catch (Exception ex)
{
log.JobState = jobState.FAILED;
log.Msg = ex.Message.Length > 300 ? ex.Message.Substring(0, 300) : ex.Message;
}
log.EndTime = DateTime.Now;
await _uploadFileSyncRecordRepository.SaveChangesAsync(stoppingToken);
}
catch (Exception ex)
{
_logger.LogError(ex, "Sync failed {Id}", id);
}
finally
{
// ⭐⭐⭐ 永远执行
_fileSyncQueue.Complete(id);
}
}
}
}
+17 -2
View File
@@ -136,7 +136,14 @@
<param name="opt"></param>
<returns></returns>
</member>
<member name="M:IRaCIS.Core.API.Controllers.InspectionController.FinishMedicalReview(IRaCIS.Core.Application.Service.Inspection.DTO.DataInspectionDto{IRaCIS.Core.Application.Service.Reading.Dto.FinishMedicalReviewInDto})">
<member name="M:IRaCIS.Core.API.Controllers.InspectionController.AmendmentPatientInfo(IRaCIS.Core.Application.Service.Inspection.DTO.DataInspectionDto{IRaCIS.Core.Application.Contracts.EditPatientInfoCommand})">
<summary>
修正患者基本信息
</summary>
<param name="opt"></param>
<returns></returns>
</member>
<member name="M:IRaCIS.Core.API.Controllers.InspectionController.FinishMedicalReview(IRaCIS.Core.Application.Service.Inspection.DTO.DataInspectionDto{IRaCIS.Core.Application.Service.Reading.Dto.CloseAndFinishMedicalReview})">
<summary>
医学审核完成
</summary>
@@ -318,11 +325,12 @@
<param name="_noneDicomStudyFileRepository"></param>
<returns></returns>
</member>
<member name="M:IRaCIS.Core.API.Controllers.StudyController.UploadVisitCheckExcel(System.Guid,IRaCIS.Core.Application.Helper.IOSSService,IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.InspectionFile})">
<member name="M:IRaCIS.Core.API.Controllers.StudyController.UploadVisitCheckExcel(System.Guid,System.Boolean,IRaCIS.Core.Application.Helper.IOSSService,IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.InspectionFile})">
<summary>
一致性核查 excel上传 支持三种格式
</summary>
<param name="trialId"></param>
<param name="isFullCheck"></param>
<param name="oSSService"></param>
<param name="_inspectionFileRepository"></param>
<returns></returns>
@@ -343,6 +351,13 @@
</summary>
<returns></returns>
</member>
<member name="M:IRaCIS.Core.API.HostService.SyncFileRecoveryService.ExecuteAsync(System.Threading.CancellationToken)">
<summary>
多个程序,如果恢复同一份数据,造成重复同步,SCP服务不恢复任务
</summary>
<param name="stoppingToken"></param>
<returns></returns>
</member>
<member name="T:IRaCIS.Core.API.IpPolicyRateLimitSetup">
<summary>
IPLimit限流 启动服务
+21 -1
View File
@@ -1,6 +1,8 @@
using IRaCIS.Core.API;
using FellowOakDicom;
using IRaCIS.Core.API;
using IRaCIS.Core.API.HostService;
using IRaCIS.Core.Application.BusinessFilter;
using IRaCIS.Core.Application.BusinessFilter.LegacyController.Database.Api;
using IRaCIS.Core.Application.Filter;
using IRaCIS.Core.Application.MassTransit.Consumer;
using IRaCIS.Core.Application.Service;
@@ -13,6 +15,7 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
@@ -21,6 +24,7 @@ using Newtonsoft.Json;
using Serilog;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
@@ -28,6 +32,10 @@ using System.Runtime.InteropServices;
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
AppContext.SetSwitch("Npgsql.DisableDateTimeInfinityConversions", true);
Console.WriteLine("Startup Culture: " + CultureInfo.CurrentCulture.Name);
Console.WriteLine("Startup UI Culture: " + CultureInfo.CurrentUICulture.Name);
#region
//以配置文件为准,否则 从url中取环境值(服务以命令行传递参数启动,配置文件配置了就不需要传递环境参数)
var config = new ConfigurationBuilder()
@@ -82,6 +90,10 @@ builder.Services.ConfigureServices(_configuration);
builder.Services.AddHostedService<HangfireHostService>();
builder.Services.AddSingleton<FileSyncQueue>();
builder.Services.AddHostedService<SyncFileRecoveryService>();
builder.Services.AddHostedService<FileSyncWorker>();
//minimal api 异常处理
builder.Services.AddExceptionHandler<GlobalExceptionHandler>();
//builder.Services.AddProblemDetails();
@@ -95,11 +107,18 @@ builder.Services.AddJsonLocalization(options => options.ResourcesPath = "Resourc
// 异常、参数统一验证过滤器、Json序列化配置、字符串参数绑型统一Trim()
builder.Services.AddControllers(options =>
{
// 关键配置:禁用不可空引用类型的自动 Required 验证
options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true;
// 插到最前,抢在默认绑定器之前
//options.ModelBinderProviders.Insert(0, new SpecificNullableBinderProvider());
options.Filters.Add<ModelActionFilter>();
options.Filters.Add<ProjectExceptionFilter>();
options.Filters.Add<UnitOfWorkFilter>();
options.Filters.Add<LimitUserRequestAuthorization>();
options.Filters.Add<TrialGlobalLimitActionFilter>();
options.Filters.Add<RequestDuplicationFilter>();
})
.AddNewtonsoftJsonSetup(builder.Services); // NewtonsoftJson 序列化 处理
@@ -160,6 +179,7 @@ var env = app.Environment;
#region
DicomSetupBuilder.UseServiceProvider(app.Services);
app.UseMiddleware<EncryptionRequestMiddleware>();
@@ -4,7 +4,7 @@
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:3305",
"applicationUrl": "http://0.0.0.0:3305",
"sslPort": 0
}
},
@@ -23,7 +23,7 @@
"ASPNETCORE_ENVIRONMENT": "Test_IRC",
"ASPNETCORE_OpenSwagger": "true"
},
"applicationUrl": "http://localhost:6100"
"applicationUrl": "http://0.0.0.0:6100"
},
"IRaCIS.Test_IRC_PGSQL": {
"commandName": "Project",
@@ -31,7 +31,7 @@
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Test_IRC_PGSQL"
},
"applicationUrl": "http://localhost:6100"
"applicationUrl": "http://0.0.0.0:6100"
},
"IRaCIS.Event_IRC": {
"commandName": "Project",
@@ -39,7 +39,7 @@
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Event_IRC"
},
"applicationUrl": "http://localhost:6100"
"applicationUrl": "http://0.0.0.0:6100"
},
"Docker": {
"commandName": "Docker",
@@ -53,7 +53,7 @@
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Uat_IRC"
},
"applicationUrl": "http://localhost:6100"
"applicationUrl": "http://0.0.0.0:6100"
},
"IRaCIS.Prod_IRC": {
"commandName": "Project",
@@ -61,7 +61,7 @@
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Prod_IRC"
},
"applicationUrl": "http://localhost:6100"
"applicationUrl": "http://0.0.0.0:6100"
},
"IRaCIS.US_Uat_IRC": {
"commandName": "Project",
@@ -69,7 +69,7 @@
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "US_Uat_IRC"
},
"applicationUrl": "http://localhost:6100"
"applicationUrl": "http://0.0.0.0:6100"
},
"IRaCIS.US_Prod_IRC": {
"commandName": "Project",
@@ -77,7 +77,7 @@
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "US_Prod_IRC"
},
"applicationUrl": "http://localhost:6100"
"applicationUrl": "http://0.0.0.0:6100"
}
}
+1
View File
@@ -1,2 +1,3 @@
{
}
@@ -39,6 +39,8 @@ namespace IRaCIS.Core.API._PipelineExtensions.Serilog
var requestBodyPayload = await ReadRequestBody(context.Request);
context.Items["RequestBody"] = requestBodyPayload;
using (LogContext.PushProperty("RequestBody", requestBodyPayload))
{
//await _next.Invoke()
@@ -8,13 +8,21 @@ namespace IRaCIS.Core.API
{
public static void AddDicomSetup(this IServiceCollection services)
{
// ⭐ 先做全局 DICOM 配置
new DicomSetupBuilder()
.RegisterServices(s => s.AddFellowOakDicom()
.AddTranscoderManager<FellowOakDicom.Imaging.NativeCodec.NativeTranscoderManager>()
.AddImageManager<ImageSharpImageManager>()
)
.SkipValidation()
.Build();
.SkipValidation() // 👈 在这里设置
.Build();
services.AddFellowOakDicom().AddTranscoderManager<FellowOakDicom.Imaging.NativeCodec.NativeTranscoderManager>().AddImageManager<ImageSharpImageManager>();
// new DicomSetupBuilder()
// .RegisterServices(s => s.AddFellowOakDicom()
//.AddTranscoderManager<FellowOakDicom.Imaging.NativeCodec.NativeTranscoderManager>()
// .AddImageManager<ImageSharpImageManager>()
// )
// .SkipValidation()
// .Build();
}
}
}
@@ -40,12 +40,12 @@ namespace IRaCIS.Core.API
var dbType = configuration.GetSection("ConnectionStrings:Db_Type").Value;
if (!string.IsNullOrWhiteSpace(dbType) && dbType == "pgsql")
{
options.UseNpgsql(configuration.GetSection("ConnectionStrings:RemoteNew").Value, contextOptionsBuilder => contextOptionsBuilder.EnableRetryOnFailure());
options.UseNpgsql(configuration.GetSection("ConnectionStrings:RemoteNew").Value, contextOptionsBuilder => contextOptionsBuilder.EnableRetryOnFailure().CommandTimeout(90));
}
else
{
options.UseSqlServer(configuration.GetSection("ConnectionStrings:RemoteNew").Value, contextOptionsBuilder => contextOptionsBuilder.EnableRetryOnFailure());
options.UseSqlServer(configuration.GetSection("ConnectionStrings:RemoteNew").Value, contextOptionsBuilder => contextOptionsBuilder.EnableRetryOnFailure().CommandTimeout(90));
}
@@ -71,29 +71,27 @@ namespace IRaCIS.Core.API
DateTime dateTime;
if (reader.ValueType == typeof(DateTime) || reader.ValueType == typeof(DateTime?))
// 2. 检查目标类型是否可空
bool isNullable = objectType == typeof(DateTime?);
var canConvert = DateTime.TryParse(reader.Value?.ToString(), out dateTime);
if (isNullable == false && canConvert == false)
{
DateTime? nullableDateTime = reader.Value as DateTime?;
if (nullableDateTime != null && nullableDateTime.HasValue)
{
dateTime = nullableDateTime.Value;
}
else
{
return null;
}
throw new JsonSerializationException($"Could not convert string to DateTime: {reader.Value} Path {reader.Path}");
}
else
{
if (DateTime.TryParse((string)reader.Value, out dateTime) == false)
if (canConvert == false)
{
return null;
}
}
////如果前端传递带时区的,那么转换会报错,需要DateTimeKind.Unspecified
//dateTime = DateTime.SpecifyKind(dateTime, DateTimeKind.Unspecified);
// 将客户端时间转换为服务器时区的时间
var serverZoneTime = TimeZoneInfo.ConvertTime(dateTime, _clientTimeZone, TimeZoneInfo.Local);
@@ -113,7 +111,10 @@ namespace IRaCIS.Core.API
//第一个参数默认使用系统本地时区 也就是应用服务器的时区
DateTime clientZoneTime = TimeZoneInfo.ConvertTime(nullableDateTime.Value, _clientTimeZone);
//writer.WriteValue(clientZoneTime);
//// 最简单的方式:创建 DateTimeOffset
//DateTimeOffset dateTimeOffset = new DateTimeOffset(clientZoneTime, _clientTimeZone.GetUtcOffset(clientZoneTime));
//writer.WriteValue(dateTimeOffset.ToString("yyyy-MM-dd HH:mm:sszzz"));
writer.WriteValue(clientZoneTime.ToString(_dateFormat));
}
@@ -126,6 +127,51 @@ namespace IRaCIS.Core.API
}
public class DateOnlyUniversalJsonConverter : JsonConverter
{
private readonly string _format;
public DateOnlyUniversalJsonConverter(string format = "yyyy-MM-dd")
{
_format = format;
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(DateOnly) || objectType == typeof(DateOnly?);
}
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
if (value == null)
{
writer.WriteValue(""); // null -> 空字符串
return;
}
var date = (DateOnly)value;
if (date == default)
{
writer.WriteValue(""); // default(DateOnly) -> 空字符串
}
else
{
writer.WriteValue(date.ToString(_format));
}
}
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
var str = reader.TokenType == JsonToken.Null ? null : reader.Value?.ToString();
if (string.IsNullOrWhiteSpace(str) || !DateOnly.TryParse(str, out var date))
{
return objectType == typeof(DateOnly?) ? null : default(DateOnly);
}
return date;
}
}
#region
@@ -49,6 +49,8 @@ namespace IRaCIS.Core.API
//必须放在后面
options.SerializerSettings.Converters.Add(services.BuildServiceProvider().GetService<JSONTimeZoneConverter>());
//options.SerializerSettings.Converters.Add(new DateOnlyUniversalJsonConverter("yyyy-MM-dd"));
})
.AddControllersAsServices()//动态webApi属性注入需要
@@ -3,8 +3,10 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Reflection.Metadata;
namespace IRaCIS.Core.API
{
@@ -51,34 +53,90 @@ namespace IRaCIS.Core.API
}
public class NullToEmptyStringValueProvider : IValueProvider
{
PropertyInfo _MemberInfo;
PropertyInfo _memberInfo;
private readonly bool _isNullable;
public NullToEmptyStringValueProvider(PropertyInfo memberInfo)
{
_MemberInfo = memberInfo;
_memberInfo = memberInfo;
}
// DTO → JSON 返回前端的时候 处理null 变为"" 方便前端判断
public object GetValue(object target)
{
object result = _MemberInfo.GetValue(target);
if (_MemberInfo.PropertyType == typeof(string) && result == null) result = "";
else if (_MemberInfo.PropertyType == typeof(String[]) && result == null) result = new string[] { };
//else if (_MemberInfo.PropertyType == typeof(Nullable<Int32>) && result == null) result = 0;
//else if (_MemberInfo.PropertyType == typeof(Nullable<Decimal>) && result == null) result = 0.00M;
var result = _memberInfo.GetValue(target);
// 检查类型是否为string或string?
if (_memberInfo.PropertyType == typeof(string) && result == null)
{
#region string string? null ""
//// 如果是string?类型,返回null 可以做到,但是得反射,因为 string string? 是编译层区分的
//var isNullable1 = _memberInfo.CustomAttributes
// .Any(a => a.AttributeType.Name == "NullableAttribute");
////var isNullable2 = _memberInfo.CustomAttributes
//// .Any(a => a.AttributeType.FullName == "System.Runtime.CompilerServices.NullableAttribute");
//if (isNullable1)
//{
// return result;
//}
#endregion
// 如果是string类型,返回空字符串
return string.Empty;
}
return result;
}
//影响 模型绑定时接收前端的值,同时影响 正常 JSON 序列化
public void SetValue(object target, object value)
{
if (_MemberInfo.PropertyType == typeof(string))
{
//去掉前后空格
_MemberInfo.SetValue(target, value == null ? string.Empty : value.ToString() == string.Empty ? value : value/*.ToString().Trim()*/);
_memberInfo.SetValue(target, value);
#region string null
//if (_memberInfo.PropertyType == typeof(string))
//{
// _memberInfo.SetValue(target, value == null ? string.Empty : value);
//}
//else
//{
// _memberInfo.SetValue(target, value);
//}
#endregion
#region string string?
//////接收模型的时候 定义的明明是string 但是上面也有该属性,判断不准的 比如阅片跟踪列表查询
//var isNullable1 = _memberInfo.CustomAttributes.Any(a => a.AttributeType.Name == "NullableAttribute");
////不影响 string? 传递null 变为""
//if (_memberInfo.PropertyType == typeof(string) && isNullable1 == false)
//{
// //如果不处理 前段传递null string不会接收,说前段没传递会验证报错
// _memberInfo.SetValue(target, value == null ? string.Empty : value);
//}
//else
//{
// _memberInfo.SetValue(target, value);
//}
#endregion
}
else
{
_MemberInfo.SetValue(target, value);
}
}
}
@@ -35,7 +35,7 @@ namespace IRaCIS.Core.API
.Enrich.FromLogContext()
.Filter.ByExcluding(logEvent => logEvent.Properties.ContainsKey("RequestPath") && logEvent.Properties["RequestPath"].ToString().Contains("/health"))
.WriteTo.Console()
.WriteTo.File($"{AppContext.BaseDirectory}Serilogs/.log", rollingInterval: RollingInterval.Day,retainedFileCountLimit:60);
.WriteTo.File($"{AppContext.BaseDirectory}Serilogs/.log", rollingInterval: RollingInterval.Day,retainedFileCountLimit:365);
#region
@@ -28,12 +28,13 @@ public static class ServiceCollectionSetup
services.AddOptions().Configure<AliyunOSSOptions>(_configuration.GetSection("AliyunOSS"));
services.AddOptions().Configure<ObjectStoreServiceOptions>(_configuration.GetSection("ObjectStoreService"));
services.AddOptions().Configure<IRCEncreptOption>(_configuration.GetSection("EncrypteResponseConfig"));
services.AddOptions().Configure<RequestDuplicationOptions>(_configuration.GetSection("RequestDuplicationOptions"));
services.AddOptions().Configure<SystemPacsConfig>(_configuration.GetSection("SystemPacsConfig"));
services.Configure<IRaCISBasicConfigOption>(_configuration.GetSection("IRaCISBasicConfig"));
services.Configure<ServiceVerifyConfigOption>(_configuration.GetSection("BasicSystemConfig"));
//转发头设置 获取真实IP
//תͷ ȡʵIP
services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders =
@@ -51,7 +52,7 @@ public static class ServiceCollectionSetup
services.AddScoped<IObtainTaskAutoCancelJob, ObtainTaskAutoCancelJob>();
// 注册以Service 结尾的服务
// עService βķ
services.Scan(scan => scan
.FromAssemblies(typeof(BaseService).Assembly)
.AddClasses(classes => classes.Where(t => t.Name.Contains("Service")))
@@ -70,23 +71,23 @@ public static class ServiceCollectionSetup
#endregion
// MediatR 进程内消息 事件解耦 从程序集中 注册命令和handler对应关系
// MediatR Ϣ ¼ ӳ עhandlerӦϵ
//builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblyContaining<ConsistencyVerificationHandler>());
#region
#region ʷ
//builder.Services.AddMemoryCache();
////上传限制 配置
////ϴ
//builder.Services.Configure<FormOptions>(options =>
//{
// options.MultipartBodyLengthLimit = int.MaxValue;
// options.ValueCountLimit = int.MaxValue;
// options.ValueLengthLimit = int.MaxValue;
//});
//IP 限流 可设置白名单 或者黑名单
//IP ð ߺ
//services.AddIpPolicyRateLimitSetup(_configuration);
// 用户类型 策略授权
// û Ȩ
//services.AddAuthorizationPolicySetup(_configuration);
#endregion
}
@@ -94,25 +95,25 @@ public static class ServiceCollectionSetup
}
#region Autofac
#region Autofac
//public class AutofacModuleSetup : Autofac.Module
//{
// protected override void Load(ContainerBuilder containerBuilder)
// {
// #region byzhouhang 20210917 此处注册泛型仓储 可以减少Domain层 和Infra.EFcore 两层 空的仓储接口定义和 仓储文件定义
// #region byzhouhang 20210917 ˴עͲִ ԼDomain Infra.EFcore յIJִӿڶ ִļ
// containerBuilder.RegisterGeneric(typeof(Repository<>))
// .As(typeof(IRepository<>)).InstancePerLifetimeScope();//注册泛型仓储
// .As(typeof(IRepository<>)).InstancePerLifetimeScope();//עͲִ
// containerBuilder.RegisterType<Repository>().As<IRepository>().InstancePerLifetimeScope();
// #endregion
// #region 指定控制器也由autofac 来进行实例获取 https://www.cnblogs.com/xwhqwer/p/15320838.html
// #region ָҲautofac ʵȡ https://www.cnblogs.com/xwhqwer/p/15320838.html
// //获取所有控制器类型并使用属性注入
// //ȡпͲʹע
// containerBuilder.RegisterAssemblyTypes(typeof(BaseService).Assembly)
// .Where(type => typeof(IDynamicWebApi).IsAssignableFrom(type))
// .PropertiesAutowired();
@@ -128,7 +129,7 @@ public static class ServiceCollectionSetup
// //containerBuilder.RegisterType<UserInfo>().As<IUserInfo>().InstancePerLifetimeScope();
// //注册hangfire任务 依赖注入
// //עhangfire ע
// containerBuilder.RegisterType<ObtainTaskAutoCancelJob>().As<IObtainTaskAutoCancelJob>().InstancePerDependency();
// }
@@ -1,4 +1,4 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
@@ -116,6 +116,9 @@ public static class SwaggerSetup
//DocExpansion设置为none可折叠所有方法
options.DocExpansion(DocExpansion.None);
// 开启Swagger UI的搜索/过滤功能
options.EnableFilter();
//DefaultModelsExpandDepth设置为 - 1 可不显示models
options.DefaultModelsExpandDepth(-1);
});
@@ -1,75 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"ConnectionStrings": {
"RemoteNew": "Server=101.132.253.119,1435;Database=irc_Prpd_bak;User ID=sa;Password=xc@123456;TrustServerCertificate=true",
"Hangfire": "Server=101.132.253.119,1435;Database=irc_Hangfire_bak;User ID=sa;Password=xc@123456;TrustServerCertificate=true"
},
"ObjectStoreService": {
"ObjectStoreUse": "AliyunOSS",
"AliyunOSS": {
"RegionId": "cn-shanghai",
"InternalEndpoint": "https://oss-cn-shanghai-internal.aliyuncs.com",
"EndPoint": "https://oss-cn-shanghai.aliyuncs.com",
"AccessKeyId": "LTAI5tNRTsqL6aWmHkDmTwoH",
"AccessKeySecret": "7mtGz3qrYWI6JMMBZiLeC119VWicZH",
"RoleArn": "acs:ram::1899121822495495:role/irc-oss-access",
"BucketName": "zy-irc-store",
"ViewEndpoint": "https://zy-irc-cache.oss-cn-shanghai.aliyuncs.com",
"Region": "oss-cn-shanghai",
"DurationSeconds": 7200
},
"MinIO": {
"endpoint": "http://192.168.3.68",
"port": "8001",
"useSSL": false,
"accessKey": "IDFkwEpWej0b4DtiuThL",
"secretKey": "Lhuu83yMhVwu7c1SnjvGY6lq74jzpYqifK6Qtj4h",
"bucketName": "test"
}
},
"BasicSystemConfig": {
"OpenUserComplexPassword": true,
"OpenSignDocumentBeforeWork": true,
"OpenLoginLimit": true,
"LoginMaxFailCount": 5,
"LoginFailLockMinutes": 30,
"AutoLoginOutMinutes": 60,
"ContinuousReadingTimeMin": 120,
"ReadingRestTimeMin": 10,
"IsNeedChangePassWord": true,
"ChangePassWordDays": 90,
// 模板类型 1 Elevate 2 Extensive
"TemplateType": 2
},
"SystemEmailSendConfig": {
"Port": 465,
"Host": "smtp.qiye.aliyun.com",
"FromEmail": "uat@extimaging.com",
"FromName": "UAT_IRC",
"AuthorizationCode": "SHzyyl2021",
"SiteUrl": "http://irc.event.extimaging.com/login",
"CompanyName": "Extensive Imaging",
"CompanyNameCN": "上海展影医疗科技有限公司",
"CompanyShortName": "Extensive Imaging",
"CompanyShortNameCN": "展影医疗",
"IsEnv_US": false
}
}
@@ -0,0 +1,124 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"ConnectionStrings": {
"RemoteNew": "Server=101.132.253.119,1435;Database=irc_Prpd_bak;User ID=sa;Password=xc@123456;TrustServerCertificate=true",
"Hangfire": "Server=101.132.253.119,1435;Database=irc_Hangfire_bak;User ID=sa;Password=xc@123456;TrustServerCertificate=true"
},
"WeComNoticeConfig": {
"IsOpenWeComNotice": true,
"WebhookUrl": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=4355b98e-1e72-4678-8dfb-2fc6ad0bf449", //4355b98e-1e72-4678-8dfb-2fc6ad0bf449 //cdd97aab-d256-4f07-9145-a0a2b1555322
"APINoticeUserList": [ "u", "wait..." ],
"VueNoticeUserList": [ "wangxiaoshuang", "6b7717a31647293621b97b96f74e6f3d" ]
},
"ObjectStoreService": {
"ObjectStoreUse": "AliyunOSS",
"AliyunOSS": {
"RegionId": "cn-shanghai",
"InternalEndpoint": "https://oss-cn-shanghai-internal.aliyuncs.com",
"EndPoint": "https://oss-cn-shanghai.aliyuncs.com",
"AccessKeyId": "LTAI5tFUCCmz5TwghZHsj45Y",
"AccessKeySecret": "8evrBy1fVfzJG25i67Jm0xqn9Xcw2T",
"RoleArn": "acs:ram::1078130221702011:role/uat-oss-access",
"BucketName": "tl-med-irc-event-store",
"ViewEndpoint": "https://tl-med-irc-event-store.oss-cn-shanghai.aliyuncs.com",
"Region": "oss-cn-shanghai",
"DurationSeconds": 7200
},
"MinIO": {
"endPoint": "hir-oss.uat.extimaging.com",
"port": "80",
"useSSL": false,
"viewEndpoint": "http://hir-oss.uat.extimaging.com/irc-uat",
//"port": "443",
//"useSSL": true,
//"viewEndpoint": "https://hir-oss.uat.extimaging.com/irc-uat",
"accessKey": "b9Ul0e98xPzt6PwRXA1Q",
"secretKey": "DzMaU2L4OXl90uytwOmDXF2encN0Jf4Nxu2XkYqQ",
"bucketName": "irc-uat"
},
"AWS": {
"Region": "us-east-1",
"EndPoint": "s3.us-east-1.amazonaws.com",
"UseSSL": true,
"RoleArn": "arn:aws:iam::471112624751:role/sts_s3_upload",
"AccessKeyId": "AKIAW3MEAFJXWRCGSX5Z",
"SecretAccessKey": "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",
"BucketName": "ei-med-s3-lili-uat-store",
"ViewEndpoint": "https://ei-med-s3-lili-uat-store.s3.amazonaws.com/",
"DurationSeconds": 7200
}
},
"BasicSystemConfig": {
// 启用质控风险控制功能
"QCRiskControl": true,
"OpenUserComplexPassword": true,
"OpenSignDocumentBeforeWork": true,
"OpenLoginLimit": true,
"LoginMaxFailCount": 5,
"LoginFailLockMinutes": 30,
"AutoLoginOutMinutes": 120,
"OpenLoginMFA": false,
"ContinuousReadingTimeMin": 120,
"ReadingRestTimeMin": 10,
"IsNeedChangePassWord": true,
"ChangePassWordDays": 90,
// 模板类型 1 Elevate 2 Extensive
"TemplateType": 2,
//MFA免验证发送天数
"UserMFAVerifyMinutes": 1440
},
"SystemEmailSendConfig": {
"Port": 465,
"Host": "smtp.qiye.aliyun.com",
"Imap": "imap.qiye.aliyun.com",
"ImapPort": 993,
"FromEmail": "uat@extimaging.com",
"FromName": "Uat IRC Imaging System",
"AuthorizationCode": "SHzyyl2021",
"SiteUrl": "http://irc.uat.extimaging.com/login",
"PlatformName": "EICS",
"PlatformNameCN": "展影云平台",
"SystemShortName": "IRC",
"OrganizationName": "ExtImaging",
"OrganizationNameCN": "ExtImaging",
"CompanyName": "Extensive Imaging",
"CompanyNameCN": "上海展影医疗科技有限公司",
"CompanyShortName": "Extensive Imaging",
"CompanyShortNameCN": "展影医疗",
"IsEnv_US": false,
"IsOpenErrorNoticeEmail": false,
"EmailRegexStr": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
"CronEmailDefaultCulture": "zh-CN",
"ErrorNoticeEmailList": [ "872297557@qq.com" ]
},
"SystemPacsConfig": {
"Port": "11116",
"IP": "101.132.253.119"
},
"RequestDuplicationOptions": {
"IsEnabled": true,
"DuplicationWindowMs": 200,
"CacheTimeSeconds": 5,
"ExcludedPaths": [
]
}
}
+28 -7
View File
@@ -7,11 +7,17 @@
}
},
"ConnectionStrings": {
"RemoteNew": "Server=101.132.193.237,1434;Database=Prod_IRC;User ID=sa;Password=zhanying@2021;TrustServerCertificate=true",
"Hangfire": "Server=101.132.193.237,1434;Database=Prod_IRC_Hangfire;User ID=sa;Password=zhanying@2021;TrustServerCertificate=true"
"RemoteNew": "Server=10.10.10.49,1434;Database=Prod_IRC;User ID=sa;Password=zhanying@2021;TrustServerCertificate=true",
"Hangfire": "Server=10.10.10.49,1434;Database=Prod_IRC_Hangfire;User ID=sa;Password=zhanying@2021;TrustServerCertificate=true"
//"RemoteNew": "Server=prod_mssql_standard,1433;Database=Prod_IRC;User ID=sa;Password=zhanying@2021;TrustServerCertificate=true",
//"Hangfire": "Server=prod_mssql_standard,1433;Database=Prod_IRC_Hangfire;User ID=sa;Password=zhanying@2021;TrustServerCertificate=true"
},
"WeComNoticeConfig": {
"IsOpenWeComNotice": true,
"WebhookUrl": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=4355b98e-1e72-4678-8dfb-2fc6ad0bf449", //4355b98e-1e72-4678-8dfb-2fc6ad0bf449 //cdd97aab-d256-4f07-9145-a0a2b1555322
"APINoticeUserList": [ "u", "wait..." ],
"VueNoticeUserList": [ "wangxiaoshuang", "6b7717a31647293621b97b96f74e6f3d" ]
},
"ObjectStoreService": {
"ObjectStoreUse": "AliyunOSS",
"AliyunOSS": {
@@ -36,7 +42,8 @@
}
},
"BasicSystemConfig": {
// 启用质控风险控制功能
"QCRiskControl": true,
"OpenUserComplexPassword": true,
"OpenSignDocumentBeforeWork": true,
@@ -53,19 +60,26 @@
"IsNeedChangePassWord": true,
"ChangePassWordDays": 90,
// 模板类型 1 Elevate 2 Extensive
"TemplateType": 2
"TemplateType": 2,
//MFA免验证发送天数
"UserMFAVerifyMinutes": 1440
},
"SystemEmailSendConfig": {
"Port": 465,
"Host": "smtp.qiye.aliyun.com",
"Imap": "imap.qiye.aliyun.com",
"ImapPort": 993,
"FromEmail": "irc@extimaging.com",
"FromName": "IRC Imaging System",
"AuthorizationCode": "ExtImg@2022",
"SiteUrl": "http://irc.extimaging.com/login",
"PlatformName": "EICS",
"PlatformNameCN": "展影云平台",
"SystemShortName": "IRC",
"OrganizationName": "Extlmaging",
"OrganizationNameCN": "Extlmaging",
"OrganizationName": "ExtImaging",
"OrganizationNameCN": "ExtImaging",
"CompanyName": "Extensive Imaging",
"CompanyNameCN": "上海展影医疗科技有限公司",
"CompanyShortName": "Extensive Imaging",
@@ -73,11 +87,18 @@
"IsEnv_US": false,
"IsOpenErrorNoticeEmail": false,
"EmailRegexStr": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
"CronEmailDefaultCulture": "zh-CN",
"ErrorNoticeEmailList": [ "872297557@qq.com" ]
},
"SystemPacsConfig": {
"Port": "11113",
"IP": "101.132.193.237"
},
"RequestDuplicationOptions": {
"IsEnabled": true,
"DuplicationWindowMs": 200,
"CacheTimeSeconds": 5,
"ExcludedPaths": [
]
}
}
+54 -5
View File
@@ -18,10 +18,36 @@
// Hangfire 定时任务数据库链接字符串
"Hangfire": "Server=106.14.89.110,1435;Database=Test_IRC_Hangfire;User ID=sa;Password=xc@123456;TrustServerCertificate=true"
},
"WeComNoticeConfig": {
"IsOpenWeComNotice": true,
"WebhookUrl": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=4355b98e-1e72-4678-8dfb-2fc6ad0bf449", //4355b98e-1e72-4678-8dfb-2fc6ad0bf449 //cdd97aab-d256-4f07-9145-a0a2b1555322
"APINoticeUserList": [ "u", "wait..." ],
"VueNoticeUserList": [ "wangxiaoshuang", "6b7717a31647293621b97b96f74e6f3d" ]
},
// 对象存储服务配置
"ObjectStoreService": {
// 使用的对象存储服务类型
"ObjectStoreUse": "AliyunOSS",
"IsOpenStoreSync": true,
"ApiDeployRegion": "CN",
"SyncConfigList": [
{
"Domain": "irc.test.extimaging.com",
"Primary": "AliyunOSS",
"Target": "AWS",
"UploadRegion": "CN",
"TargetRegion": "US",
"IsOpenSync": true
},
{
"Domain": "lili.test.extimaging.com",
"Primary": "AWS",
"Target": "AliyunOSS",
"UploadRegion": "US",
"TargetRegion": "CN",
"IsOpenSync": true
}
],
// 阿里云对象存储服务的配置
"AliyunOSS": {
// 阿里云 OSS 的 Region ID
@@ -88,6 +114,8 @@
},
// 系统配置
"BasicSystemConfig": {
// 启用质控风险控制功能
"QCRiskControl": true,
// 打开用户复杂密码
"OpenUserComplexPassword": false,
// 是否在开始工作前强制签署电子知情同意书
@@ -110,12 +138,14 @@
"IsNeedChangePassWord": true,
// 密码有效期(天),到期后必须修改
"ChangePassWordDays": 90,
// 模板类型 1 Elevate 2 Extensive
// 模板类型 1 ElevateLiLi 2 Extensive(展影)
"TemplateType": 2,
// 是否打开项目关联删除
"OpenTrialRelationDelete": true,
// 转换PDF服务配置
"ThirdPdfUrl": "http://106.14.89.110:30088/api/v1/convert/file/pdf"
"ThirdPdfUrl": "http://106.14.89.110:30088/api/v1/convert/file/pdf",
//MFA免验证发送天数
"UserMFAVerifyMinutes": 1440
},
// 邮件服务配置(用于系统通知、找回密码、错误报警等)
"SystemEmailSendConfig": {
@@ -123,6 +153,10 @@
"Port": 465,
// 企业邮箱SMTP服务器地址
"Host": "smtp.qiye.aliyun.com",
"Imap": "imap.qiye.aliyun.com",
"ImapPort": 993,
// 发件人邮箱地址
"FromEmail": "test@extimaging.com",
// 发件人显示名称
@@ -131,12 +165,15 @@
"AuthorizationCode": "SHzyyl2021",
// 系统对外访问地址
"SiteUrl": "http://irc.test.extimaging.com/login",
// 系统简称
"PlatformName": "EICS",
"PlatformNameCN": "展影云平台",
// 系统简称一致性核查使用
"SystemShortName": "IRC",
// 组织英文名称
"OrganizationName": "Extlmaging",
"OrganizationName": "ExtImaging",
// 组织中文名称
"OrganizationNameCN": "Extlmaging",
"OrganizationNameCN": "ExtImaging",
// 公司英文全称
"CompanyName": "Extensive Imaging",
// 公司中文全称
@@ -151,6 +188,7 @@
"IsOpenErrorNoticeEmail": false,
// 邮箱格式校验正则表达式
"EmailRegexStr": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
"CronEmailDefaultCulture": "zh-CN",
// 接收系统异常报警的邮箱列表
"ErrorNoticeEmailList": [ "872297557@qq.com" ]
},
@@ -160,5 +198,16 @@
"Port": "11113",
// PACS服务器IP地址
"IP": "106.14.89.110"
},
// 重复请求配置
"RequestDuplicationOptions": {
// 是否启用重复请求检测
"IsEnabled": true,
// 重复请求时间窗口(毫秒)
"DuplicationWindowMs": 200,
// 缓存请求时间(秒)
"CacheTimeSeconds": 5,
"ExcludedPaths": [
]
}
}
@@ -47,7 +47,8 @@
},
"BasicSystemConfig": {
// 启用质控风险控制功能
"QCRiskControl": true,
"OpenUserComplexPassword": false,
"OpenSignDocumentBeforeWork": false,
@@ -75,13 +76,15 @@
"SystemEmailSendConfig": {
"Port": 465,
"Host": "smtp.qiye.aliyun.com",
"Imap": "imap.qiye.aliyun.com",
"ImapPort": 993,
"FromEmail": "test@extimaging.com",
"FromName": "Test_IRC",
"AuthorizationCode": "SHzyyl2021",
"SiteUrl": "http://irc.test.extimaging.com/login",
"OrganizationName": "Extlmaging",
"OrganizationNameCN": "Extlmaging",
"OrganizationName": "ExtImaging",
"OrganizationNameCN": "ExtImaging",
"CompanyName": "Extensive Imaging",
"CompanyNameCN": "上海展影医疗科技有限公司",
"CompanyShortName": "Extensive Imaging",
@@ -91,5 +94,12 @@
"SystemPacsConfig": {
"Port": "11113",
"IP": "106.14.89.110"
},
"RequestDuplicationOptions": {
"IsEnabled": true,
"DuplicationWindowMs": 200,
"CacheTimeSeconds": 5,
"ExcludedPaths": [
]
}
}
+24 -3
View File
@@ -12,7 +12,12 @@
//"RemoteNew": "Server=44.210.231.169,1435;Database=US_IRC;User ID=sa;Password=xc@123456;TrustServerCertificate=true",
//"Hangfire": "Server=44.210.231.169,1435;Database=US_IRC_Hangfire;User ID=sa;Password=xc@123456;TrustServerCertificate=true",
},
"WeComNoticeConfig": {
"IsOpenWeComNotice": true,
"WebhookUrl": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=4355b98e-1e72-4678-8dfb-2fc6ad0bf449", //4355b98e-1e72-4678-8dfb-2fc6ad0bf449 //cdd97aab-d256-4f07-9145-a0a2b1555322
"APINoticeUserList": [ "u", "wait..." ],
"VueNoticeUserList": [ "wangxiaoshuang", "6b7717a31647293621b97b96f74e6f3d" ]
},
"ObjectStoreService": {
"ObjectStoreUse": "AWS",
"MinIO": {
@@ -38,7 +43,8 @@
}
},
"BasicSystemConfig": {
// 启用质控风险控制功能
"QCRiskControl": true,
"OpenUserComplexPassword": true,
"OpenSignDocumentBeforeWork": true,
@@ -57,17 +63,24 @@
// 模板类型 1 Elevate 2 Extensive
"TemplateType": 1,
"OpenTrialRelationDelete": false
"OpenTrialRelationDelete": false,
//MFA免验证
"UserMFAVerifyMinutes": 1440
},
"SystemEmailSendConfig": {
"Port": 587,
"Host": "smtp-mail.outlook.com",
"Imap": "imap-mail.outlook.com",
"ImapPort": 993,
"FromEmail": "donotreply@elevateimaging.ai",
"FromName": "LiLi System",
"AuthorizationCode": "Q#669869497420ul",
"PlatformName": "LiLi",
"PlatformNameCN": "LiLi",
"SystemShortName": "LiLi",
"OrganizationName": "Elevate Imaging",
"OrganizationNameCN": "Elevate Imaging",
@@ -79,12 +92,20 @@
"IsEnv_US": true,
"IsOpenErrorNoticeEmail": false,
"EmailRegexStr": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
"CronEmailDefaultCulture": "en-US",
"ErrorNoticeEmailList": [ "872297557@qq.com" ]
},
"SystemPacsConfig": {
"Port": "104",
"IP": "44.210.231.169"
},
"RequestDuplicationOptions": {
"IsEnabled": true,
"DuplicationWindowMs": 200,
"CacheTimeSeconds": 5,
"ExcludedPaths": [
]
}
}
+18 -2
View File
@@ -44,7 +44,8 @@
}
},
"BasicSystemConfig": {
// 启用质控风险控制功能
"QCRiskControl": true,
"OpenUserComplexPassword": true,
"OpenSignDocumentBeforeWork": true,
@@ -66,16 +67,24 @@
"ChangePassWordDays": 90,
// 模板类型 1 Elevate 2 Extensive
"TemplateType": 1,
"OpenLoginMFA": true
"OpenLoginMFA": true,
//MFA免验证发送天数
"UserMFAVerifyMinutes": 1440
},
"SystemEmailSendConfig": {
"Port": 587,
"Host": "smtp-mail.outlook.com",
"Imap": "imap-mail.outlook.com",
"ImapPort": 993,
"FromEmail": "donotreply@elevateimaging.ai",
"FromName": "LiLi System",
"AuthorizationCode": "Q#669869497420ul",
"PlatformName": "LiLi",
"PlatformNameCN": "LiLi",
"SystemShortName": "LiLi",
"OrganizationName": "Elevate Imaging",
"OrganizationNameCN": "Elevate Imaging",
@@ -92,6 +101,13 @@
"SystemPacsConfig": {
"Port": "104",
"IP": "3.226.182.187"
},
"RequestDuplicationOptions": {
"IsEnabled": true,
"DuplicationWindowMs": 200,
"CacheTimeSeconds": 5,
"ExcludedPaths": [
]
}
}
+23 -3
View File
@@ -13,6 +13,12 @@
"RemoteNew": "Server=3.226.182.187,1435;Database=US_Uat_IRC;User ID=sa;Password=xc@123456;TrustServerCertificate=true",
"Hangfire": "Server=3.226.182.187,1435;Database=US_Uat_IRC_Hangfire;User ID=sa;Password=xc@123456;TrustServerCertificate=true"
},
"WeComNoticeConfig": {
"IsOpenWeComNotice": true,
"WebhookUrl": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=4355b98e-1e72-4678-8dfb-2fc6ad0bf449", //4355b98e-1e72-4678-8dfb-2fc6ad0bf449 //cdd97aab-d256-4f07-9145-a0a2b1555322
"APINoticeUserList": [ "u", "wait..." ],
"VueNoticeUserList": [ "wangxiaoshuang", "6b7717a31647293621b97b96f74e6f3d" ]
},
"ObjectStoreService": {
@@ -48,7 +54,8 @@
}
},
"BasicSystemConfig": {
// 启用质控风险控制功能
"QCRiskControl": true,
"OpenUserComplexPassword": true,
"OpenSignDocumentBeforeWork": true,
@@ -65,16 +72,22 @@
"IsNeedChangePassWord": true,
"ChangePassWordDays": 90,
// 模板类型 1 Elevate 2 Extensive
"TemplateType": 1
"TemplateType": 1,
//MFA免验证发送天数
"UserMFAVerifyMinutes": 1440
},
"SystemEmailSendConfig": {
"Port": 587,
"Host": "smtp-mail.outlook.com",
"Imap": "imap-mail.outlook.com",
"ImapPort": 993,
"FromEmail": "donotreply@elevateimaging.ai",
"FromName": "LiLi System",
"AuthorizationCode": "Q#669869497420ul",
"PlatformName": "LiLi",
"PlatformNameCN": "LiLi",
"SystemShortName": "LiLi",
"OrganizationName": "Elevate Imaging",
"OrganizationNameCN": "Elevate Imaging",
@@ -86,12 +99,19 @@
"IsEnv_US": true,
"IsOpenErrorNoticeEmail": false,
"EmailRegexStr": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
"CronEmailDefaultCulture": "en-US",
"ErrorNoticeEmailList": [ "872297557@qq.com" ]
},
"SystemPacsConfig": {
"Port": "104",
"IP": "3.226.182.187"
},
"RequestDuplicationOptions": {
"IsEnabled": true,
"DuplicationWindowMs": 200,
"CacheTimeSeconds": 5,
"ExcludedPaths": [
]
}
}
+25 -4
View File
@@ -10,6 +10,12 @@
"RemoteNew": "Server=101.132.253.119,1435;Database=Uat_IRC;User ID=sa;Password=xc@123456;TrustServerCertificate=true",
"Hangfire": "Server=101.132.253.119,1435;Database=Uat_IRC_Hangfire;User ID=sa;Password=xc@123456;TrustServerCertificate=true"
},
"WeComNoticeConfig": {
"IsOpenWeComNotice": true,
"WebhookUrl": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=4355b98e-1e72-4678-8dfb-2fc6ad0bf449", //4355b98e-1e72-4678-8dfb-2fc6ad0bf449 //cdd97aab-d256-4f07-9145-a0a2b1555322
"APINoticeUserList": [ "u", "wait..." ],
"VueNoticeUserList": [ "wangxiaoshuang", "6b7717a31647293621b97b96f74e6f3d" ]
},
"ObjectStoreService": {
"ObjectStoreUse": "AliyunOSS",
@@ -54,7 +60,8 @@
},
"BasicSystemConfig": {
// 启用质控风险控制功能
"QCRiskControl": true,
"OpenUserComplexPassword": true,
"OpenSignDocumentBeforeWork": true,
@@ -72,20 +79,26 @@
"IsNeedChangePassWord": true,
"ChangePassWordDays": 90,
// 模板类型 1 Elevate 2 Extensive
"TemplateType": 2
"TemplateType": 2,
//MFA免验证发送天数
"UserMFAVerifyMinutes": 1440
},
"SystemEmailSendConfig": {
"Port": 465,
"Host": "smtp.qiye.aliyun.com",
"Imap": "imap.qiye.aliyun.com",
"ImapPort": 993,
"FromEmail": "uat@extimaging.com",
"FromName": "Uat IRC Imaging System",
"AuthorizationCode": "SHzyyl2021",
"SiteUrl": "http://irc.uat.extimaging.com/login",
"PlatformName": "EICS",
"PlatformNameCN": "展影云平台",
"SystemShortName": "IRC",
"OrganizationName": "Extlmaging",
"OrganizationNameCN": "Extlmaging",
"OrganizationName": "ExtImaging",
"OrganizationNameCN": "ExtImaging",
"CompanyName": "Extensive Imaging",
"CompanyNameCN": "上海展影医疗科技有限公司",
"CompanyShortName": "Extensive Imaging",
@@ -93,12 +106,20 @@
"IsEnv_US": false,
"IsOpenErrorNoticeEmail": false,
"EmailRegexStr": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
"CronEmailDefaultCulture": "zh-CN",
"ErrorNoticeEmailList": [ "872297557@qq.com" ]
},
"SystemPacsConfig": {
"Port": "11113",
"IP": "101.132.253.119"
},
"RequestDuplicationOptions": {
"IsEnabled": true,
"DuplicationWindowMs": 200,
"CacheTimeSeconds": 5,
"ExcludedPaths": [
]
}
}
+8 -1
View File
@@ -3,7 +3,7 @@
"SecurityKey": "ShangHaiZhanYing_SecurityKey_SHzyyl@2021",
"Issuer": "Extimaging",
"Audience": "EICS",
"TokenExpireMinute": "10080"//7天
"TokenExpireMinute": "10080" //7天
},
"IpRateLimiting": {
"EnableEndpointRateLimiting": true,
@@ -74,5 +74,12 @@
"redirect_uri": "https://oauthlogin.net/oauth/githubcallback",
"scope": "repo"
}
},
"RequestDuplicationOptions": {
"IsEnabled": true,
"DuplicationWindowMs": 200,
"CacheTimeSeconds": 5,
"ExcludedPaths": [
]
}
}
+17 -3
View File
@@ -1,10 +1,9 @@
<!-- HTML for static distribution bundle build -->
<!-- HTML for static distribution bundle build -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>%(DocumentTitle)</title>
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,700|Source+Code+Pro:300,600|Titillium+Web:400,600,700" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="./swagger-ui.css">
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
@@ -113,8 +112,23 @@
}, info: function () {
return null;
}
},
fn: {
opsFilter: function (taggedOps, phrase) {
var normalPhrase = phrase.toLowerCase();
return taggedOps.map(function (tagObj, tag) {
var operations = tagObj.get("operations").filter(function (op) {
var summary = op.getIn(["operation", "summary"]) || "";
var path = op.get("path") || "";
var tagMatch = tag.toLowerCase().indexOf(normalPhrase) !== -1;
return tagMatch || summary.toLowerCase().indexOf(normalPhrase) !== -1 || path.toLowerCase().indexOf(normalPhrase) !== -1;
});
return tagObj.set("operations", operations);
}).filter(function (tagObj) {
return tagObj.get("operations").size > 0;
});
}
}
}
}
];
@@ -505,8 +505,8 @@ var abp = abp || {};
//Inputs
createInput(modalUxContent, 'tenancyName', 'Tenancy Name (Leave empty for Host)');
createInput(modalUxContent, 'userName', 'Username or email address', 'text', 'cyldev');
createInput(modalUxContent, 'password', 'Password', 'password', '123456');
createInput(modalUxContent, 'userName', 'Username or email address', 'text', 'user1wj');
createInput(modalUxContent, 'password', 'Password', 'password', '1');
createInput(modalUxContent, 'pwdMd5', 'PwdMd5', 'text', '');
createSelect(modalUxContent, 'roleSelect', 'role', [])
@@ -1,14 +1,22 @@
using Microsoft.AspNetCore.Mvc;
using IRaCIS.Core.Application.Helper.OtherTool;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Localization;
using Newtonsoft.Json;
using System;
using System.Reflection;
using static System.Runtime.InteropServices.JavaScript.JSType;
namespace IRaCIS.Core.Application.Filter;
public class ModelActionFilter(IStringLocalizer _localizer) : ActionFilterAttribute, IActionFilter
public class ModelActionFilter(IStringLocalizer _localizer, IConfiguration _config, IUserInfo _userInfo) : ActionFilterAttribute, IActionFilter
{
@@ -23,8 +31,55 @@ public class ModelActionFilter(IStringLocalizer _localizer) : ActionFilterAttrib
.Select(e => e.ErrorMessage)
.ToArray();
var request = context.HttpContext.Request;
try
{
bool isOpenWeComNotice = _config.GetValue<bool>("WeComNoticeConfig:IsOpenWeComNotice");
if (isOpenWeComNotice)
{
string webhook = _config["WeComNoticeConfig:WebhookUrl"] ?? string.Empty;
var uri = new Uri(_config["SystemEmailSendConfig:SiteUrl"]);
var baseUrl = uri.GetLeftPart(UriPartial.Authority);
var userList = _config.GetSection("WeComNoticeConfig:VueNoticeUserList").Get<string[]>();
var requestBody = context.HttpContext.Items["RequestBody"] as string;
// 🔔 异步告警(不要阻塞请求)
_ = WeComNotifier.SendAlertAsync(
webhook: webhook,
alert: new WeComAlert
{
Env = baseUrl,
UserName = _userInfo.UserName.IsNotNullOrEmpty()? $"{_userInfo.UserName}({_userInfo.UserTypeShortName})": "匿名",
Api = $"{request.Method} {request.Path}",
Message = $"前端传递参数,后端模型验证失败\n 具体信息:{JsonConvert.SerializeObject(validationErrors)}",
JsonData = requestBody,
AtUsers = userList ?? []
}
);
}
}
catch (Exception ex)
{
Log.Logger.Error($"模型验证过滤器里发送企业微信出现错误:{ex.Message}");
}
//---提供给接口的参数无效。
context.Result = new JsonResult(ResponseOutput.NotOk(_localizer["ModelAction_InvalidAPIParameter"] + JsonConvert.SerializeObject(validationErrors)));
}
}
}
@@ -1,13 +1,16 @@
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Application.Helper.OtherTool;
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Infrastructure;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
namespace IRaCIS.Core.Application.Filter;
public class ProjectExceptionFilter(ILogger<ProjectExceptionFilter> _logger, IStringLocalizer _localizer) : Attribute, IExceptionFilter
public class ProjectExceptionFilter(ILogger<ProjectExceptionFilter> _logger, IStringLocalizer _localizer, IConfiguration _config, IUserInfo _userInfo) : Attribute, IExceptionFilter
{
public void OnException(ExceptionContext context)
@@ -46,7 +49,44 @@ public class ProjectExceptionFilter(ILogger<ProjectExceptionFilter> _logger, ISt
else
{
context.Result = new JsonResult(ResponseOutput.NotOk(_localizer["Project_ExceptionContactDeveloper"] + (exception.InnerException is null ? (exception.Message)
: (exception.InnerException?.Message )), ApiResponseCodeEnum.ProgramException));
: (exception.Message + "Inner ExceptionMsg:" + exception.InnerException?.Message)), ApiResponseCodeEnum.ProgramException));
try
{
bool isOpenWeComNotice = _config.GetValue<bool>("WeComNoticeConfig:IsOpenWeComNotice");
if (isOpenWeComNotice)
{
string webhook = _config["WeComNoticeConfig:WebhookUrl"] ?? string.Empty;
var uri = new Uri(_config["SystemEmailSendConfig:SiteUrl"]);
var baseUrl = uri.GetLeftPart(UriPartial.Authority);
var userList = _config.GetSection("WeComNoticeConfig:APINoticeUserList").Get<string[]>();
var requestBody = context.HttpContext.Items["RequestBody"] as string;
var alert = new WeComAlert
{
Env = baseUrl,
UserName = $"{_userInfo.UserName}({_userInfo.UserTypeShortName})",
Api = context.HttpContext.Request.Path,
Message = exception.Message,
JsonData = requestBody,
Stack = exception.StackTrace,
AtUsers = userList ?? []
};
_ = WeComNotifier.SendAlertAsync(webhook, alert);
}
}
catch (Exception ex)
{
_logger.LogError($"异常过滤器里发送企业微信出现错误:{ex.Message}");
}
}
@@ -58,6 +98,7 @@ public class ProjectExceptionFilter(ILogger<ProjectExceptionFilter> _logger, ISt
_logger.LogError(errorInfo);
//_logger.LogError(exception, exception.Message);
}
else
@@ -0,0 +1,137 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace IRaCIS.Core.Application.BusinessFilter.LegacyController
{
using Database.Api;
using DocumentFormat.OpenXml.InkML;
using IRaCIS.Core.Application.Service.Common;
using IRaCIS.Core.Infrastructure;
using IRaCIS.Core.Infrastructure.Extention;
using Microsoft.AspNetCore.Components.Endpoints;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Minio.Helper;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Threading.Tasks;
namespace Database.Api
{
/// <summary>
/// 请求拦截 请求前后的操作
/// </summary>
/// <param name="logger">logger</param>
/// <param name="accessor">loggerHelper</param>
public class RequestDuplicationFilter(ILogger<RequestDuplicationFilter> logger, IHttpContextAccessor accessor, IUserInfo _userInfo, IStringLocalizer _localizer, IOptionsMonitor<RequestDuplicationOptions> RequestDuplicationOptionsMonitor) : ExceptionFilterAttribute, IAsyncActionFilter
{
/// <summary>
/// 传入的参数
/// </summary>
public string Intoparam { get; set; }
/// <summary>
/// 这个是正常记录(请求刚进入的时候)
/// </summary>
/// <param name="context">context</param>
/// <param name="next">next</param>
/// <returns>返回的对象</returns>
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
if (RequestDuplicationOptionsMonitor.CurrentValue.IsEnabled)
{
Dictionary<string, object?> dic = new Dictionary<string, object?>();
var desc = context.ActionDescriptor as ControllerActionDescriptor;
foreach (var p in desc.Parameters)
{
// 关键判断:绑定源是否是 Services
if (p.BindingInfo?.BindingSource == BindingSource.Services) continue;
// 普通参数,取值
if (context.ActionArguments.TryGetValue(p.Name, out var value) && value != null)
{
dic.Add(p.Name, value);
}
}
this.Intoparam = JsonConvert.SerializeObject(dic);
try
{
this.RequestDuplication();
}
catch (Exception)
{
}
}
var resultContext = await next();
}
/// <summary>
///
/// </summary>
private void RequestDuplication()
{
var requestPath = accessor?.HttpContext?.Request?.Path.ToString() ?? string.Empty;
// 验证请求频繁情况
if (
!requestPath
.Split("/", StringSplitOptions.RemoveEmptyEntries)
.Any(segment => segment.StartsWith("get", StringComparison.OrdinalIgnoreCase)) &&
_userInfo.UserRoleId != default(Guid)&&
RequestDuplicationOptionsMonitor.CurrentValue.IsEnabled &&
!RequestDuplicationOptionsMonitor.CurrentValue.ExcludePaths.Contains(requestPath))
{
RequestInfo requestInfo = new RequestInfo
{
UserRoleId = _userInfo.UserRoleId,
RequestPath = requestPath,
ParameterHash = GenerateParameterHash(this.Intoparam),
RequestTime = DateTime.Now
};
IRCSystemInfo.RequestRecordList= IRCSystemInfo.RequestRecordList.Where(x => x.RequestTime >= DateTime.Now.AddSeconds(-RequestDuplicationOptionsMonitor.CurrentValue.CacheTimeSeconds)).ToList();
var requestsTimes = IRCSystemInfo.RequestRecordList.Any(x=>
x.RequestTime>= requestInfo.RequestTime.AddMilliseconds(-RequestDuplicationOptionsMonitor.CurrentValue.DuplicationWindowMs)&&
x.RequestKey== requestInfo.RequestKey);
if (requestsTimes)
{
throw new BusinessValidationFailedException(_localizer["RequestDuplicationFilter_RequestDuplication"], ApiResponseCodeEnum.BusinessValidationFailed);
}
IRCSystemInfo.RequestRecordList.Add(requestInfo);
}
}
public string GenerateParameterHash(string parameters)
{
if (string.IsNullOrEmpty(parameters))
return string.Empty;
using var sha256 = SHA256.Create();
var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(parameters));
return Convert.ToBase64String(hashBytes);
}
}
}
}
@@ -29,11 +29,11 @@ public class TrialGlobalLimitActionFilter(IFusionCache _fusionCache, IUserInfo _
var requestHost = context.HttpContext.Request.Host;
// 检查请求是否来自 localhost:6100
//if (requestHost.Host == "localhost" && (requestHost.Port == 6100 || requestHost.Port == 3305))
//{
// await next();
// return;
//}
if (requestHost.Host == "localhost" && (requestHost.Port == 6100 || requestHost.Port == 3305))
{
await next();
return;
}
#region
// 用户类型检查
@@ -7,6 +7,7 @@ namespace IRaCIS.Core.Domain.Share;
[Description("多环境 配置环境实体")]
public class ServiceVerifyConfigOption
{
public bool QCRiskControl { get; set; }
public bool OpenUserComplexPassword { get; set; }
public bool OpenSignDocumentBeforeWork { get; set; }
@@ -38,6 +39,8 @@ public class ServiceVerifyConfigOption
public string ThirdPdfUrl { get; set; }
public int UserMFAVerifyMinutes { get; set; } = 1440;
}
public class SystemEmailSendConfig
@@ -45,6 +48,10 @@ public class SystemEmailSendConfig
public int Port { get; set; }
public string Host { get; set; } = string.Empty;
public string Imap { get; set; } = string.Empty;
public int ImapPort { get; set; }
public string FromEmail { get; set; } = string.Empty;
public string FromName { get; set; } = string.Empty;
@@ -53,7 +60,11 @@ public class SystemEmailSendConfig
public string SiteUrl { get; set; } = string.Empty;
public string SystemShortName { get; set; } = string.Empty;
public string PlatformName { get; set; } = string.Empty;
public string PlatformNameCN { get; set; } = string.Empty;
public string SystemShortName { get; set; } = string.Empty;
public string OrganizationName { get; set; } = string.Empty;
public string OrganizationNameCN { get; set; } = string.Empty;
@@ -72,12 +83,12 @@ public class SystemEmailSendConfig
public string EmailRegexStr { get; set; }
public List<string> ErrorNoticeEmailList { get; set; } =new List<string>();
public List<string> ErrorNoticeEmailList { get; set; } = new List<string>();
}
public class SystemEmailSendConfigView
{
public string SystemShortName { get; set; } = string.Empty;
public string SystemShortName { get; set; } = string.Empty;
public string CompanyName { get; set; } = string.Empty;
public string CompanyNameCN { get; set; } = string.Empty;
@@ -105,6 +116,54 @@ public class IRCEncreptOption
public List<string> ApiPathList { get; set; }
}
/// <summary>
/// 请求缓存配置
/// </summary>
public class RequestDuplicationOptions
{
/// <summary>
/// 缓存时间(秒),默认5秒
/// </summary>
public int CacheTimeSeconds { get; set; } = 5;
/// <summary>
/// 重复请求检测时间窗口(毫秒),默认500毫秒
/// </summary>
public int DuplicationWindowMs { get; set; } = 500;
/// <summary>
/// 是否启用防重复请求
/// </summary>
public bool IsEnabled { get; set; } = true;
/// <summary>
/// 需要排除的路径(不进行重复检测)
/// </summary>
public List<string> ExcludePaths { get; set; } = new List<string>();
}
public static class IRCSystemInfo
{
public static List<RequestInfo> RequestRecordList { get; set; } = new List<RequestInfo>();
}
public class RequestInfo
{
public Guid UserRoleId { get; set; }
public string RequestPath { get; set; } = string.Empty;
public string ParameterHash { get; set; } = string.Empty;
public DateTime RequestTime { get; set; }
/// <summary>
/// 请求的唯一标识
/// </summary>
public string RequestKey => $"{UserRoleId}_{RequestPath}_{ParameterHash}";
}
public class IRaCISBasicConfigOption
{
public string DoctorCodePrefix { get; set; }
+15 -1
View File
@@ -1,4 +1,6 @@
namespace IRaCIS.Core.Application.Helper;
using DocumentFormat.OpenXml.Spreadsheet;
namespace IRaCIS.Core.Application.Helper;
public static class CacheKeys
@@ -61,6 +63,15 @@ public static class CacheKeys
/// <returns></returns>
public static string StartRestTime(Guid userId) => $"{userId}StartRestTime";
//每个用户 每个浏览器独立时间
public static string UserMFAVerifyPass(Guid userId,string browserFingerprint) => $"UserMFAVerifyPass:{userId}:{browserFingerprint}";
public static string UserMFATag(Guid userId) => $"UserMFAVerifyPass:{userId}";
public static string TrialDataStoreType(Guid trialId) => $"TrialDataStoreType:{trialId}";
public static string UserQCSkipTask(Guid userId) => $"UserSKipQCTask:{userId}";
}
public static class CacheHelper
@@ -72,6 +83,9 @@ public static class CacheHelper
return statusStr;
}
public static async Task<List<SystemAnonymization>> GetSystemAnonymizationListAsync(IRepository<SystemAnonymization> _systemAnonymizationRepository)
{
var list = await _systemAnonymizationRepository.Where(t => t.IsEnable).ToListAsync();
@@ -1,5 +1,8 @@
using FellowOakDicom;
using DocumentFormat.OpenXml.Office.CustomUI;
using FellowOakDicom;
using FellowOakDicom.Media;
using IRaCIS.Core.Application.ViewModel;
using IRaCIS.Core.Domain.Models;
using System;
using System.Collections.Generic;
using System.Data;
@@ -13,6 +16,9 @@ namespace IRaCIS.Core.Application.Helper
public class StudyDIRInfo
{
public Guid SubjectId { get; set; }
public bool IsTaskStudy { get; set; }
public Guid SubjectVisitId { get; set; }
// Study
public Guid DicomStudyId { get; set; }
@@ -54,6 +60,11 @@ namespace IRaCIS.Core.Application.Helper
var mappings = new List<string>();
int index = 1;
var trialId = Guid.Empty;
Guid.TryParse(ossFolder.Split('/', StringSplitOptions.RemoveEmptyEntries)[0], out trialId);
var studyUid = list.FirstOrDefault()?.StudyInstanceUid;
var dicomDir = new DicomDirectory();
@@ -97,11 +108,11 @@ namespace IRaCIS.Core.Application.Helper
var dicomFile = new DicomFile(dataset);
// 文件名递增格式:IM_00001, IM_00002, ...
string filename = $@"IMAGE/IM_{index:D5}"; // :D5 表示补足5位
string filename = $@"IMAGE\IM_{index:D5}"; // :D5 表示补足5位
mappings.Add($"{filename} => {item.InstanceId}");
dic.Add(item.InstanceId.ToString(), Path.GetFileName(filename));
dic.Add(item.InstanceId.ToString(), filename.TrimEnd('/', '\\').Split('/', '\\').Last());
dicomDir.AddFile(dicomFile, filename);
@@ -125,7 +136,9 @@ namespace IRaCIS.Core.Application.Helper
// 重置流位置
memoryStream.Position = 0;
await _oSSService.UploadToOSSAsync(memoryStream, ossFolder, "DICOMDIR", false);
var relativePath = await _oSSService.UploadToOSSAsync(memoryStream, ossFolder, "DICOMDIR", true, uploadInfo: new FileUploadRecordAddOrEdit() { TrialId = trialId, BatchDataType = BatchDataType.DICOMDIR });
dic.Add($"{studyUid}_DICOMDIR", relativePath.Split('/').Last());
}
//清理临时文件
@@ -139,7 +152,7 @@ namespace IRaCIS.Core.Application.Helper
var mappingText = string.Join(Environment.NewLine, mappings);
await using var mappingStream = new MemoryStream(Encoding.UTF8.GetBytes(mappingText));
await _oSSService.UploadToOSSAsync(mappingStream, ossFolder, $"Download_{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}", false);
await _oSSService.UploadToOSSAsync(mappingStream, ossFolder, $"Download_{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}", false, uploadInfo: new FileUploadRecordAddOrEdit() { TrialId = trialId, BatchDataType = BatchDataType.DICOMDIR });
#endregion
}
@@ -2,6 +2,7 @@
using MailKit;
using MailKit.Security;
using MimeKit;
using Org.BouncyCastle.Tls;
namespace IRaCIS.Core.Application.Helper;
@@ -9,12 +10,15 @@ namespace IRaCIS.Core.Application.Helper;
public static class SendEmailHelper
{
public static async Task SendEmailAsync(MimeMessage messageToSend, SystemEmailSendConfig _systemEmailConfig, EventHandler<MessageSentEventArgs>? messageSentSuccess = null)
public static async Task<string> SendEmailAsync(MimeMessage messageToSend, SystemEmailSendConfig _systemEmailConfig, EventHandler<MessageSentEventArgs>? messageSentSuccess = null)
{
string result = string.Empty;
result = messageToSend.MessageId;
//没有收件人 那么不发送
if (messageToSend.To.Count == 0)
{
return;
return string.Empty;
}
try
@@ -42,6 +46,7 @@ public static class SendEmailHelper
await smtp.DisconnectAsync(true);
}
}
catch (Exception ex)
@@ -51,7 +56,76 @@ public static class SendEmailHelper
throw new Exception(I18n.T("SendEmail_SendFail"), new Exception(ex.Message));
}
return result;
}
public static async Task<string> SendEmailAsync(MimeMessage messageToSend, Trial trial, EventHandler<MessageSentEventArgs>? messageSentSuccess = null)
{
// 项目的需要重设 发件地址与邮件地址
var fromAddress = messageToSend.From.Mailboxes.FirstOrDefault();
if (fromAddress != null)
{
messageToSend.From.Clear();
messageToSend.From.Add(new MailboxAddress(trial.EmailFromName, trial.EmailFromEmail));
}
string result = string.Empty;
result = messageToSend.MessageId;
//没有收件人 那么不发送
if (messageToSend.To.Count == 0)
{
return string.Empty;
}
try
{
using (var smtp = new MailKit.Net.Smtp.SmtpClient())
{
if (messageSentSuccess != null)
{
smtp.MessageSent += messageSentSuccess;
}
smtp.ServerCertificateValidationCallback = (s, c, h, e) => true;
//await smtp.ConnectAsync("smtp.qq.com", 465, SecureSocketOptions.SslOnConnect);
//await smtp.AuthenticateAsync("zhou941003@qq.com", "sqfhlpfdvnexbcab");
//await smtp.ConnectAsync(_systemEmailConfig.Host, _systemEmailConfig.Port, SecureSocketOptions.Auto);
//await smtp.AuthenticateAsync(_systemEmailConfig.FromEmail, _systemEmailConfig.AuthorizationCode);
await smtp.ConnectAsync(trial.EmailSMTPServerAddress, trial.EmailSMTPServerPort, SecureSocketOptions.Auto);
await smtp.AuthenticateAsync(trial.EmailFromEmail, trial.EmailAuthorizationCode);
await smtp.SendAsync(messageToSend);
await smtp.DisconnectAsync(true);
}
}
catch (Exception ex)
{
//---邮件发送失败,您进行的操作未能成功,请检查邮箱或联系维护人员
throw new Exception(I18n.T("SendEmail_SendFail"), new Exception(ex.Message));
}
return result;
}
public static async Task<bool> TestEmailConfigAsync(SystemEmailSendConfig _systemEmailConfig)
@@ -73,7 +147,7 @@ public static class SendEmailHelper
return true;
}
public static async Task SendEmailAsync(SMTPEmailConfig sMTPEmailConfig, EventHandler<MessageSentEventArgs>? messageSentSuccess = null)
public static async Task SendEmailAsync(SMTPEmailConfig sMTPEmailConfig,Trial? trial, EventHandler<MessageSentEventArgs>? messageSentSuccess = null)
{
var messageToSend = new MimeMessage();
@@ -143,7 +217,7 @@ public static class SendEmailHelper
await smtp.ConnectAsync(sMTPEmailConfig.Host, sMTPEmailConfig.Port, SecureSocketOptions.Auto);
await smtp.AuthenticateAsync(sMTPEmailConfig.UserName, sMTPEmailConfig.AuthorizationCode);
await smtp.AuthenticateAsync(trial.EmailFromEmail, trial.EmailAuthorizationCode);
await smtp.SendAsync(messageToSend);
@@ -60,4 +60,16 @@ namespace IRaCIS.Core.Application.Helper
}
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public class DateTimeTranaslateAttribute : Attribute
{
public string Formart { get; set; }
public DateTimeTranaslateAttribute(string formart)
{
Formart = formart;
}
}
}
@@ -1,19 +1,24 @@
using DocumentFormat.OpenXml.Spreadsheet;
using DocumentFormat.OpenXml.Wordprocessing;
using FellowOakDicom.Imaging.LUT;
using IRaCIS.Application.Contracts;
using IRaCIS.Application.Interfaces;
using IRaCIS.Core.API._ServiceExtensions.NewtonsoftJson;
using IRaCIS.Core.Application.Helper;
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Infra.EFCore.Migrations;
using IRaCIS.Core.Infrastructure.Extention;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Hosting;
using MiniExcelLibs;
using MiniExcelLibs.OpenXml;
using Newtonsoft.Json;
using NPOI.HSSF.UserModel;
using NPOI.SS.Formula.Functions;
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;
using SharpCompress.Common;
using System.Collections;
using System.Globalization;
using Xceed.Document.NET;
@@ -681,13 +686,61 @@ public static class ExcelExportHelper
if (itemDicName.IsNotNullOrEmpty())
{
var translatedItemData = dynamicTranslateDataList[itemDicName].Where(t => t.Code.ToLower() == itemValue?.ToLower()).Select(t => isEn_US ? t.Value : t.ValueCN).FirstOrDefault() ?? String.Empty;
var optionTypeEnumStr = iteObjDic.ContainsKey("OptionTypeEnum") ? iteObjDic["OptionTypeEnum"]?.ToString() : "0";
var translatedItemData = "";
//多选
if (optionTypeEnumStr == "1")
{
int[] enumValues = new int[0];
// 1. 反序列化 JSON 数组 (字符串枚举)
if (!itemValue.StartsWith("[") || !itemValue.EndsWith("]"))
{
enumValues = new int[1] { int.Parse(itemValue) };
}
else
{
enumValues = JsonConvert.DeserializeObject<int[]>(itemValue) ?? new int[0];
}
// 2. 翻译每一项并输出逗号拼接字符串
translatedItemData = string.Join(",",
enumValues.Select(code =>
dynamicTranslateDataList[itemDicName]
.FirstOrDefault(t =>
string.Equals(code.ToString(), t.Code, StringComparison.OrdinalIgnoreCase)
) is var r && r != null
? (isEn_US ? r.Value : r.ValueCN)
: string.Empty
));
}
else
{
translatedItemData = dynamicTranslateDataList[itemDicName].Where(t => t.Code.ToLower() == itemValue?.ToLower()).Select(t => isEn_US ? t.Value : t.ValueCN).FirstOrDefault() ?? String.Empty;
}
row.GetCell(writeIndex).SetCellValue(translatedItemData);
}
else
{
row.GetCell(writeIndex).SetCellValue(itemValue);
var unit = iteObjDic.ContainsKey("Unit") ? iteObjDic["Unit"]?.ToString() : null;
if (unit.IsNotNullOrEmpty() && unit == "9")
{
row.GetCell(writeIndex).SetCellValue(itemValue + "%");
}
else
{
row.GetCell(writeIndex).SetCellValue(itemValue);
}
}
@@ -818,17 +871,16 @@ public static class ExcelExportHelper
//模板路径
var tplPath = physicalPath;
#region sheet
// 打开模板文件
var templateFile = new FileStream(tplPath, FileMode.Open, FileAccess.Read);
// 获取文件流
var templateStream = new MemoryStream();
templateFile.CopyTo(templateStream);
templateStream.Seek(0, SeekOrigin.Begin);
var workbook = new XSSFWorkbook(templateStream);
#region sheet
// 打开模板文件
var templateFileStream = new FileStream(tplPath, FileMode.Open, FileAccess.Read);
var workbook = new XSSFWorkbook(templateFileStream);
int sheetCount = workbook.NumberOfSheets;
@@ -933,14 +985,8 @@ public static class ExcelExportHelper
}
}
using (var memoryStream2 = new MemoryStream())
{
workbook.Write(memoryStream2, true);
memoryStream2.Seek(0, SeekOrigin.Begin);
templateStream = memoryStream2;
}
workbook.Write(templateStream, leaveOpen: true);
templateStream.Position = 0;
}
@@ -1038,6 +1084,91 @@ public static class ExcelExportHelper
public static async Task<IActionResult> MutiSheetDataExportAsync(string code, object data, string exportFileNamePrefix, IRepository<CommonDocument> _commonDocumentRepository, IWebHostEnvironment _hostEnvironment)
{
var (physicalPath, fileName) = await FileStoreHelper.GetCommonDocPhysicalFilePathAsync(_hostEnvironment, _commonDocumentRepository, code);
//模板路径
var tplPath = physicalPath;
var templateStream = new MemoryStream();
#region npoi
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
if (isEn_US)
{
// 打开模板文件
using var templateFileStream = new FileStream(tplPath, FileMode.Open, FileAccess.Read);
using var workbook = new XSSFWorkbook(templateFileStream);
int sheetCount = workbook.NumberOfSheets;
int removeRowIndex = 1; // 要删除的行(0-based
for (int i = 0; i < sheetCount; i++)
{
var sheet = workbook.GetSheetAt(i);
// 2 删除行
var row = sheet.GetRow(removeRowIndex);
if (row != null)
{
sheet.RemoveRow(row);
}
// 3 上移后续行
if (removeRowIndex < sheet.LastRowNum)
{
sheet.ShiftRows(
removeRowIndex + 1,
sheet.LastRowNum,
-1,
true, // copyRowHeight
false // resetOriginalRowHeight
);
}
}
workbook.Write(templateStream, leaveOpen: true);
templateStream.Position = 0;
}
else
{
using (var fs = new FileStream(tplPath, FileMode.Open, FileAccess.Read))
{
fs.CopyTo(templateStream);
}
templateStream.Position = 0;
}
#endregion
var memoryStream = new MemoryStream();
var config = new OpenXmlConfiguration()
{
IgnoreTemplateParameterMissing = true,
};
await MiniExcel.SaveAsByTemplateAsync(memoryStream, templateStream.ToArray(), data, config);
memoryStream.Seek(0, SeekOrigin.Begin);
return new FileStreamResult(memoryStream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
{
FileDownloadName = $"{(string.IsNullOrEmpty(exportFileNamePrefix) ? "" : exportFileNamePrefix + "_")}{Path.GetFileNameWithoutExtension(fileName)}_{DateTime.Now.ToString("yyyyMMddHHmmss")}.xlsx"
};
}
/// <summary>
/// 导出文件模板
/// </summary>
@@ -1119,4 +1250,7 @@ public static class ExcelExportHelper
};
}
}
@@ -2,6 +2,7 @@
using IRaCIS.Core.Application.MassTransit.Consumer;
using IRaCIS.Core.Domain.Share;
using MassTransit.Mediator;
using System.Globalization;
namespace IRaCIS.Core.Application.Helper
{
@@ -57,25 +58,29 @@ namespace IRaCIS.Core.Application.Helper
}
public static void AddOrUpdateTrialCronJob(string jobId, Guid trialId, EmailBusinessScenario businessScenario, string emailCron)
public static void AddOrUpdateTrialCronJob(string jobId, Guid trialId, EmailBusinessScenario businessScenario, string emailCron,string defaultCulture="")
{
if (defaultCulture == "")
{
defaultCulture = CultureInfo.CurrentCulture.Name;
}
switch (businessScenario)
{
case EmailBusinessScenario.QCTask:
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new ImageQCRecurringEvent() { TrialId = trialId }, default), emailCron);
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new ImageQCRecurringEvent() { TrialId = trialId,CultureInfoName= defaultCulture }, default), emailCron);
break;
case EmailBusinessScenario.CRCToQCQuestion:
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new CRCImageQuestionRecurringEvent() { TrialId = trialId }, default), emailCron);
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new CRCImageQuestionRecurringEvent() { TrialId = trialId, CultureInfoName = defaultCulture }, default), emailCron);
break;
case EmailBusinessScenario.QCToCRCImageQuestion:
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new QCImageQuestionRecurringEvent() { TrialId = trialId }, default), emailCron);
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new QCImageQuestionRecurringEvent() { TrialId = trialId, CultureInfoName = defaultCulture }, default), emailCron);
break;
//加急阅片 10分钟
case EmailBusinessScenario.ExpeditedReading:
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new UrgentIRUnReadTaskRecurringEvent() { TrialId = trialId }, default), emailCron);
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new UrgentIRUnReadTaskRecurringEvent() { TrialId = trialId, CultureInfoName = defaultCulture }, default), emailCron);
break;
default:
break;
@@ -83,20 +88,25 @@ namespace IRaCIS.Core.Application.Helper
}
public static void AddOrUpdateTimingCronJob(string jobId, EmailBusinessScenario businessScenario, string emailCron)
public static void AddOrUpdateTimingCronJob(string jobId, EmailBusinessScenario businessScenario, string emailCron, string defaultCulture="")
{
if (defaultCulture == "")
{
defaultCulture = CultureInfo.CurrentCulture.Name;
}
switch (businessScenario)
{
case EmailBusinessScenario.GeneralTraining_ExpirationNotification:
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new SystemDocumentErverDayEvent() { }, default), emailCron);
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new SystemDocumentErverDayEvent() { CultureInfoName = defaultCulture }, default), emailCron);
break;
case EmailBusinessScenario.TrialTraining_ExpirationNotification:
Console.WriteLine("更新项目到期job");
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new TrialDocumentErverDayEvent() { }, default), emailCron);
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new TrialDocumentErverDayEvent() { CultureInfoName = defaultCulture }, default), emailCron);
break;
default:
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,106 @@
using IdentityModel;
using Newtonsoft.Json;
using RestSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Threading.Tasks;
namespace IRaCIS.Core.Application.Helper.OtherTool;
public class WeComAlert
{
public string Env { get; set; } = "";
public string UserName { get; set; } = "";
public string Api { get; set; } = "";
public string Message { get; set; } = "";
public string? JsonData { get; set; }
public string? Stack { get; set; }
public bool HasStack => !string.IsNullOrWhiteSpace(Stack);
public string[] AtUsers { get; set; } = [];
}
public static class WeComNotifier
{
public static async Task SendAlertAsync(string webhook, WeComAlert alert)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
await Task.CompletedTask;
return;
}
try
{
var client = new RestClient();
var request = new RestRequest(webhook, Method.Post);
var time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
// 处理 @
var atText = alert.AtUsers != null && alert.AtUsers.Any()
? string.Join("", alert.AtUsers.Select(u => $" <@{u}>"))
: "";
// 处理堆栈
var stack = alert.Stack;
if (!string.IsNullOrWhiteSpace(stack))
{
stack = stack.Replace("\n", "\n> ");
if (stack.Length > 600)
stack = stack[..600] + "...(已截断)";
}
var markdown = $@"## 🚨 系统告警
> {atText}
> **部署环境:** [{alert.Env}]({alert.Env})
> **发生时间:** {time}
> **操作人:** {alert.UserName}
> **接口地址:** {alert.Api}
### ❗ 告警信息
<font color=""warning"">{alert.Message}</font>";
if (!string.IsNullOrWhiteSpace(alert.JsonData))
{
markdown += $@"
> **📦 请求数据(JSON 格式):**
```json
{alert.JsonData}
```";
}
if (!string.IsNullOrWhiteSpace(stack))
{
markdown += $@"
### 堆栈信息(部分)
<font color=""comment"">{stack}</font>";
}
var payload = new
{
msgtype = "markdown",
markdown = new { content = markdown }
};
request.AddHeader("Content-Type", "application/json");
request.AddStringBody(JsonConvert.SerializeObject(payload), DataFormat.Json);
await client.ExecuteAsync(request);
}
catch (Exception ex)
{
Log.Logger.Error("企业微信告警发送失败: " + ex.Message);
}
}
}
@@ -33,38 +33,37 @@
<ItemGroup>
<PackageReference Include="IdentityModel.OidcClient" Version="6.0.0" />
<PackageReference Include="AlibabaCloud.SDK.Sts20150401" Version="1.1.6" />
<PackageReference Include="AWSSDK.SecurityToken" Version="4.0.1.3" />
<PackageReference Include="AlibabaCloud.SDK.Sts20150401" Version="1.2.0" />
<PackageReference Include="Aliyun.OSS.SDK.NetCore" Version="2.14.1" />
<PackageReference Include="AWSSDK.S3" Version="4.0.4.1" />
<PackageReference Include="DocX" Version="4.0.25105.5786" />
<PackageReference Include="AWSSDK.S3" Version="4.0.21" />
<PackageReference Include="AWSSDK.SecurityToken" Version="4.0.5.19" />
<PackageReference Include="DocX" Version="5.0.0" />
<PackageReference Include="FreeSpire.Doc" Version="12.2.0" />
<PackageReference Include="ExcelDataReader" Version="3.7.0" />
<PackageReference Include="ExcelDataReader.DataSet" Version="3.7.0" />
<PackageReference Include="DistributedLock.Redis" Version="1.0.3" />
<PackageReference Include="DistributedLock.SqlServer" Version="1.0.6" />
<PackageReference Include="fo-dicom" Version="5.2.2" />
<PackageReference Include="fo-dicom.Imaging.ImageSharp" Version="5.2.2" />
<PackageReference Include="fo-dicom.Codecs" Version="5.16.3" />
<PackageReference Include="IP2Region.Net" Version="2.0.2" />
<PackageReference Include="MailKit" Version="4.11.0" />
<PackageReference Include="Masa.Contrib.Service.MinimalAPIs" Version="1.0.0" />
<PackageReference Include="MaxMind.GeoIP2" Version="5.3.0" />
<PackageReference Include="ExcelDataReader" Version="3.8.0" />
<PackageReference Include="ExcelDataReader.DataSet" Version="3.8.0" />
<PackageReference Include="DistributedLock.Redis" Version="1.1.1" />
<PackageReference Include="DistributedLock.SqlServer" Version="1.0.7" />
<PackageReference Include="fo-dicom" Version="5.2.6" />
<PackageReference Include="fo-dicom.Imaging.ImageSharp" Version="5.2.6" />
<PackageReference Include="fo-dicom.Codecs" Version="5.16.7" />
<PackageReference Include="IP2Region.Net" Version="3.0.2" />
<PackageReference Include="MailKit" Version="4.15.1" />
<PackageReference Include="Masa.Contrib.Service.MinimalAPIs" Version="1.1.0" />
<PackageReference Include="MaxMind.GeoIP2" Version="5.4.1" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.10" />
<PackageReference Include="MimeKit" Version="4.11.0" />
<PackageReference Include="MimeKit" Version="4.15.1" />
<PackageReference Include="MiniExcel" Version="1.41.2" />
<PackageReference Include="Minio" Version="6.0.5" />
<PackageReference Include="Minio" Version="6.0.3" />
<PackageReference Include="MiniWord" Version="0.9.2" />
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0" />
<PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
<PackageReference Include="My.Extensions.Localization.Json" Version="3.3.0">
<TreatAsUsed>true</TreatAsUsed>
</PackageReference>
<PackageReference Include="NPOI" Version="2.7.4" />
<PackageReference Include="Panda.DynamicWebApi" Version="1.2.2" />
<PackageReference Include="RestSharp" Version="112.1.0" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.10" />
<PackageReference Include="RestSharp" Version="114.0.0" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.12" />
<PackageReference Include="Swashbuckle.AspNetCore.Filters" Version="8.0.2" />
<PackageReference Include="ZiggyCreatures.FusionCache" Version="2.3.0" />
</ItemGroup>
<ItemGroup>
@@ -75,4 +74,8 @@
<Folder Include="Service\MinimalApiService\CodeTemplate\FrontTemplate\" />
</ItemGroup>
<ItemGroup>
<None Include="Service\TrialSiteUser\TrialStatService.cs" />
</ItemGroup>
</Project>
File diff suppressed because it is too large Load Diff
@@ -1,8 +1,21 @@
using MiniExcelLibs.Attributes;
using IRaCIS.Core.Application.Helper;
using MiniExcelLibs.Attributes;
using System.ComponentModel.DataAnnotations;
namespace IRaCIS.Core.Application.MassTransit.Command
{
/// <summary>
/// 全量一致性核查
/// </summary>
public record ConsistenFullCheckCommand
{
public List<CheckViewModel> ETCList { get; set; } = new List<CheckViewModel>();
public Guid TrialId { get; set; }
public Guid InspectionFileId { get; set; }
}
public record ConsistenCheckCommand
{
public List<CheckViewModel> ETCList { get; set; } = new List<CheckViewModel>();
@@ -17,13 +30,24 @@ namespace IRaCIS.Core.Application.MassTransit.Command
public class CheckDBModel : CheckViewModel
{
public Guid SubjectVisitId { get; set; }
public Guid StudyId { get; set; }
}
public class FullCheckResult: CheckViewModel
{
public string LatestScanDateStr { get; set; } = string.Empty;
public string Modalitys { get; set; } = string.Empty;
public DateTime CheckTime { get; set; }
public string CheckResult { get; set; }
[DictionaryTranslateAttribute("CheckState")]
public CheckStateEnum CheckState { get; set; }
}
//[ExcelImporter(/*ImportResultFilter = typeof(ImportResultFilteTest),*/ IsLabelingError = true)]
@@ -62,8 +86,13 @@ namespace IRaCIS.Core.Application.MassTransit.Command
[ExcelColumnName("Modality")]
public string Modality { get; set; } = string.Empty;
#region
[DictionaryTranslateAttribute("Subject_Visit_Status")]
public SubjectStatus SubjectStatus { get; set; }
#endregion
public override bool Equals(object? obj)
{
@@ -1,53 +1,50 @@
using AutoMapper;
using IRaCIS.Application.Contracts;
using IRaCIS.Application.Interfaces;
using IRaCIS.Core.API._ServiceExtensions.NewtonsoftJson;
using IRaCIS.Core.Application.Contracts;
using IRaCIS.Core.Application.Contracts.DTO;
using IRaCIS.Core.Application.Helper;
using IRaCIS.Core.Application.MassTransit.Command;
using IRaCIS.Core.Application.ViewModel;
using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.Domain.Share;
using MassTransit;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using System.IO;
using System.Text;
namespace IRaCIS.Core.Application.MassTransit.Consumer
{
public class ConsistencyCheckConsumer : IConsumer<ConsistenCheckCommand>
public class ConsistencyCheckConsumer(
IRepository<DicomStudy> _studyRepository,
IUserInfo _userInfo,
IRepository<Subject> _subjectRepository,
IRepository<NoneDicomStudy> _noneDicomStudyRepository,
IRepository<SubjectVisit> _subjectVisitRepository,
IRepository<Dictionary> _dictionaryRepository,
IOSSService _oSSService,
IMapper _mapper,
IDictionaryService _dictionaryService,
IRepository<CommonDocument> _commonDocumentRepository,
IStringLocalizer _localizer,
IWebHostEnvironment _hostEnvironment,
IRepository<InspectionFile> _inspectionFileRepository,
IOptionsMonitor<SystemEmailSendConfig> _systemEmailSendConfig
) : IConsumer<ConsistenCheckCommand>, IConsumer<ConsistenFullCheckCommand>
{
private readonly IRepository<DicomStudy> _studyRepository;
private readonly IUserInfo _userInfo;
private readonly IRepository<Subject> _subjectRepository;
private readonly IRepository<SubjectVisit> _subjectVisitRepository;
private readonly IRepository<TrialSite> _trialSiteRepository;
private readonly IMapper _mapper;
private readonly IRepository<NoneDicomStudy> _noneDicomStudyRepository;
public IStringLocalizer _localizer { get; set; }
private readonly SystemEmailSendConfig _systemEmailConfig;
private readonly IRepository<Dictionary> _dictionaryRepository;
/// <summary>
/// 构造函数注入
/// </summary>
private readonly SystemEmailSendConfig _systemEmailConfig = _systemEmailSendConfig.CurrentValue;
public ConsistencyCheckConsumer(IRepository<DicomStudy> studyRepository, IUserInfo userInfo,
IRepository<Subject> subjectRepository, IRepository<SubjectVisit> subjectVisitRepository,
IRepository<TrialSite> trialSiteRepository, IRepository<NoneDicomStudy> noneDicomStudyRepository,
IMapper mapper, IStringLocalizer localizer, IOptionsMonitor<SystemEmailSendConfig> systemEmailConfig, IRepository<Dictionary> dictionaryRepository)
{
_noneDicomStudyRepository = noneDicomStudyRepository;
_studyRepository = studyRepository;
_userInfo = userInfo;
_subjectRepository = subjectRepository;
_subjectVisitRepository = subjectVisitRepository;
_trialSiteRepository = trialSiteRepository;
_mapper = mapper;
_localizer = localizer;
_systemEmailConfig = systemEmailConfig.CurrentValue;
_dictionaryRepository = dictionaryRepository;
}
@@ -67,8 +64,8 @@ namespace IRaCIS.Core.Application.MassTransit.Consumer
//subjectVisitLambda2= subjectVisitLambda2.And(x => x.CheckState == CheckStateEnum.ToCheck && x.AuditState == AuditStateEnum.QCPassed || (x.CheckState == CheckStateEnum.CVIng && x.AuditState == AuditStateEnum.QCPassed));
Expression<Func<SubjectVisit, bool>> subjectVisitLambda = x => x.TrialId == trialId && x.Subject.IsSubjectQuit == false &&
(x.CheckState == CheckStateEnum.ToCheck && x.AuditState == AuditStateEnum.QCPassed || (x.CheckState == CheckStateEnum.CVIng && x.AuditState == AuditStateEnum.QCPassed));
Expression<Func<SubjectVisit, bool>> subjectVisitLambda = x => x.TrialId == trialId && x.Subject.IsSubjectQuit == false && x.AuditState == AuditStateEnum.QCPassed &&
(x.CheckState == CheckStateEnum.ToCheck || x.CheckState == CheckStateEnum.CVIng);
var dicomQuery = from sv in _subjectVisitRepository.Where(subjectVisitLambda)
join subject in _subjectRepository.AsQueryable() on sv.SubjectId equals subject.Id
@@ -86,7 +83,7 @@ namespace IRaCIS.Core.Application.MassTransit.Consumer
var noneDicomQuey = from sv in _subjectVisitRepository.Where(subjectVisitLambda)
join subject in _subjectRepository.AsQueryable() on sv.SubjectId equals subject.Id
join noneDicomStudy in _noneDicomStudyRepository.AsQueryable() on sv.Id equals noneDicomStudy.SubjectVisitId
join noneDicomStudy in _noneDicomStudyRepository.Where(t => t.FileCount > 0) on sv.Id equals noneDicomStudy.SubjectVisitId
select new CheckDBModel()
{
SubjectVisitId = sv.Id,
@@ -117,7 +114,7 @@ namespace IRaCIS.Core.Application.MassTransit.Consumer
foreach (var sv in svExcelGroup)
{
//Excel 的数据 在IRC 中可以找到该访视
//Excel 的数据 在IRC 中可以找到该访视(一致性核查中,或者待核查)
if (dbCheckList.Any(t => t.SubjectCode == sv.SubjectCode && t.SiteCode == sv.SiteCode && t.VisitName == sv.VisitName))
{
@@ -201,7 +198,7 @@ namespace IRaCIS.Core.Application.MassTransit.Consumer
var dbExceptExcel = dbVisitStudyList.Except(etcVisitStudyList);
// excel 存在
var excelExceptDB = etcVisitStudyList.Except(dbCheckList);
var excelExceptDB = etcVisitStudyList.Except(dbVisitStudyList);
//ETC 和系统的完全一致 两者没有差别
if (dbExceptExcel.Count() == 0 && excelExceptDB.Count() == 0)
@@ -301,12 +298,169 @@ namespace IRaCIS.Core.Application.MassTransit.Consumer
}
await _subjectVisitRepository.SaveChangesAsync();
//await context.RespondAsync<ConsistenCheckResult>(new
//{
//});
}
public async Task Consume(ConsumeContext<ConsistenFullCheckCommand> context)
{
var inspectionFileId = context.Message.InspectionFileId;
var trialId = context.Message.TrialId;
//系统定义MRI 到底是MR 还是MRI
var mriModality = await _dictionaryRepository.Where(t => t.Parent.Code == "Modality").Where(t => t.Code == "MRI").Select(t => t.Value).FirstNotNullAsync();
//处理Excel大小写
context.Message.ETCList.ForEach(t => { t.Modality = t.Modality.ToUpper().Trim(); t.StudyDate = Convert.ToDateTime(t.StudyDate).ToString("yyyy-MM-dd"); t.SiteCode = t.SiteCode.ToUpper().Trim(); t.VisitName = t.VisitName.ToUpper().Trim(); t.SubjectCode = t.SubjectCode.ToUpper().Trim(); });
var etcList = context.Message.ETCList;
Expression<Func<SubjectVisit, bool>> subjectVisitLambda = x => x.TrialId == trialId /*&& x.Subject.IsSubjectQuit == false*/ && x.AuditState == AuditStateEnum.QCPassed;
var dicomQuery = from sv in _subjectVisitRepository.Where(subjectVisitLambda)
join subject in _subjectRepository.AsQueryable() on sv.SubjectId equals subject.Id
join study in _studyRepository.AsQueryable() on sv.Id equals study.SubjectVisitId
select new CheckDBModel()
{
SubjectStatus = sv.Subject.Status,
SubjectVisitId = sv.Id,
SiteCode = sv.TrialSite.TrialSiteCode,
StudyDate = study.StudyTime == null ? string.Empty : ((DateTime)study.StudyTime).ToString("yyyy-MM-dd"),
StudyId = study.Id,
Modality = study.ModalityForEdit,
SubjectCode = subject.Code,
VisitName = sv.VisitName,
};
var noneDicomQuey = from sv in _subjectVisitRepository.Where(subjectVisitLambda)
join subject in _subjectRepository.AsQueryable() on sv.SubjectId equals subject.Id
join noneDicomStudy in _noneDicomStudyRepository.Where(t => t.FileCount > 0) on sv.Id equals noneDicomStudy.SubjectVisitId
select new CheckDBModel()
{
SubjectStatus = sv.Subject.Status,
SubjectVisitId = sv.Id,
SiteCode = sv.TrialSite.TrialSiteCode,
StudyDate = noneDicomStudy.ImageDate.ToString("yyyy-MM-dd"),
StudyId = noneDicomStudy.Id,
Modality = noneDicomStudy.Modality,
SubjectCode = subject.Code,
VisitName = sv.VisitName,
};
var dbList = (await dicomQuery.ToListAsync()).Union(await noneDicomQuey.ToListAsync()).ToList();
//处理数据库 大小写
dbList.ForEach(t => { t.Modality = t.Modality.ToUpper().Trim(); t.SiteCode = t.SiteCode.ToUpper().Trim(); t.VisitName = t.VisitName.ToUpper().Trim(); t.SubjectCode = t.SubjectCode.ToUpper().Trim(); });
var dbCheckList = _mapper.Map<List<CheckViewModel>>(dbList);
//按照Excel数据访视分组 按照数据库的数据 一个个的访视对比
var svExcelGroup = etcList.GroupBy(t => new { t.SiteCode, t.SubjectCode, t.VisitName })
.Select(g => new { g.Key.SubjectCode, g.Key.VisitName, g.Key.SiteCode, ExcelStudyList = g.ToList() }).ToList();
var fullCheckResultList = new List<FullCheckResult>();
foreach (var sv in svExcelGroup)
{
//Excel 的数据 在IRC 中可以找到该访视(一致性核查中,或者待核查)
if (dbCheckList.Any(t => t.SubjectCode == sv.SubjectCode && t.SiteCode == sv.SiteCode && t.VisitName == sv.VisitName))
{
var dbVisitStudyList = dbList.Where(t => t.SubjectCode == sv.SubjectCode && t.SiteCode == sv.SiteCode && t.VisitName == sv.VisitName).ToList();
//找到etc 当前visit site 和subject 一致的检查列表
var etcVisitStudyList = etcList.Where(t => t.SubjectCode == sv.SubjectCode && t.SiteCode == sv.SiteCode && t.VisitName == sv.VisitName).ToList();
//以我们系统数据库为准 ,判断是MR 还是MRI, 把Excel 里的MR 或者MRI 处理成一致,MR MRI 是一致的
foreach (var item in etcVisitStudyList)
{
if (item.Modality == "MR" || item.Modality == "MRI")
{
item.Modality = mriModality;
}
}
//etc 和数据库 并集
var unionList = dbVisitStudyList.Union(etcVisitStudyList);
// 数据库存在
var dbExceptExcel = dbVisitStudyList.Except(etcVisitStudyList);
// excel 存在
var excelExceptDB = etcVisitStudyList.Except(dbVisitStudyList);
var dbCurrentVisitFirst = dbVisitStudyList.First();
//ETC 和系统的完全一致 两者没有差别
if (dbExceptExcel.Count() == 0 && excelExceptDB.Count() == 0)
{
//每个受试者每个访视加一条记录
fullCheckResultList.Add(new FullCheckResult()
{
SubjectStatus = dbCurrentVisitFirst.SubjectStatus,
CheckTime = DateTime.Now,
CheckState = CheckStateEnum.CVPassed,
SiteCode = dbCurrentVisitFirst.SiteCode,
SubjectCode = dbCurrentVisitFirst.SubjectCode,
VisitName = dbCurrentVisitFirst.VisitName,
Modalitys = string.Join('、', dbVisitStudyList.Select(t => t.Modality)),
LatestScanDateStr = dbVisitStudyList.Select(t => t.StudyDate).MaxBy(d => DateTime.Parse(d)) ?? ""
});
}
else
{
var checkResult =
String.Join(" | ", dbExceptExcel.Select(t => $"{_localizer["ConsistencyVerification_EdcL", t.StudyDate, t.Modality/*, _systemEmailConfig.SystemShortName*/]}")) + " | "
+ String.Join(" | ", excelExceptDB.Select(t => $"{_localizer["ConsistencyVerification_IrcLi", t.StudyDate, t.Modality, _systemEmailConfig.SystemShortName]}"));
//每个受试者每个访视加一条记录
fullCheckResultList.Add(new FullCheckResult()
{
SubjectStatus = dbCurrentVisitFirst.SubjectStatus,
CheckState = CheckStateEnum.None,
CheckTime = DateTime.Now,
SiteCode = dbCurrentVisitFirst.SiteCode,
SubjectCode = dbCurrentVisitFirst.SubjectCode,
VisitName = dbCurrentVisitFirst.VisitName,
Modalitys = string.Join('、', dbVisitStudyList.Select(t => t.Modality)),
LatestScanDateStr = dbVisitStudyList.Select(t => t.StudyDate).MaxBy(d => DateTime.Parse(d)) ?? "",
CheckResult = checkResult
});
}
}
}
//导到Excel 上传oss 回更记录状态
var list = fullCheckResultList;
var exportInfo = new ExcelExportInfo();
exportInfo.List = ExportExcelConverterDate.ConvertToClientTimeInObject(list, _userInfo.TimeZoneId);
exportInfo.CurrentTime = ExportExcelConverterDate.DateTimeInternationalToString(DateTime.Now, _userInfo.TimeZoneId);
var fileStreamResult = (FileStreamResult)await ExcelExportHelper.DataExportAsync(StaticData.Export.TrialConsistentFUllCheckList_Export, exportInfo, exportInfo.TrialCode, _commonDocumentRepository, _hostEnvironment, _dictionaryService, typeof(FullCheckResult));
var ossRelativePath = await _oSSService.UploadToOSSAsync(fileStreamResult.FileStream, $"{trialId.ToString()}/InspectionUpload/DataReconciliation", "DataReconciliation", uploadInfo: new FileUploadRecordAddOrEdit() { TrialId = trialId, BatchDataType = BatchDataType.DataReconciliation });
//var add = await _inspectionFileRepository.FindAsync(inspectionFileId);
//add.CheckState = EDCCheckState.Success;
//add.ResultPath = ossRelativePath;
//更新
await _inspectionFileRepository.BatchUpdateNoTrackingAsync(t => t.Id == inspectionFileId, u => new InspectionFile() { CheckState = EDCCheckState.Success, ResultPath = ossRelativePath });
await _subjectVisitRepository.SaveChangesAsync();
}
}
}
@@ -256,7 +256,7 @@ public class ImageConsumer(
}
/// <summary>
/// 发送影像重传相关邮件的通用方法
/// 发送影像重传相关邮件的通用方法 已经处理项目
/// </summary>
private async Task SendImageReuploadEmail(SendImageReuploadEmailInDto inDto)
{
@@ -339,12 +339,12 @@ public class ImageConsumer(
var messageToSend = new MimeMessage();
// 发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
// 添加抄送
foreach (var ccUser in ccUserList)
{
messageToSend.Cc.Add(new MailboxAddress(String.Empty, ccUser.EMail));
messageToSend.Cc.Add(new MailboxAddress(ccUser.FullName, ccUser.EMail));
}
// 格式化邮件内容
Func<(string topicStr, string htmlBodyStr), (string topicStr, string htmlBodyStr)> emailConfigFunc = input =>
@@ -365,9 +365,11 @@ public class ImageConsumer(
return (topicStr, htmlBodyStr);
};
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(inDto.EmailNoticeConfig, messageToSend, emailConfigFunc);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
}
}
@@ -434,7 +436,7 @@ public class ImageConsumer(
}
/// <summary>
/// 发送阅片人筛选相关邮件的通用方法
/// 发送阅片人筛选相关邮件的通用方法 已经处理项目
/// </summary>
private async Task SendReviewerSelectionEmail(EmailNoticeConfig emailNoticeConfig, Guid trialId, List<Guid> enrollIdList)
{
@@ -472,12 +474,12 @@ public class ImageConsumer(
var messageToSend = new MimeMessage();
// 发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
// 添加抄送
foreach (var ccUser in ccUserList)
{
messageToSend.Cc.Add(new MailboxAddress(String.Empty, ccUser.EMail));
messageToSend.Cc.Add(new MailboxAddress(ccUser.FullName, ccUser.EMail));
}
// 格式化邮件内容
@@ -500,7 +502,7 @@ public class ImageConsumer(
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(emailNoticeConfig, messageToSend, emailConfigFunc);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
}
}
}
@@ -83,7 +83,7 @@ public class UrgentMedicalReviewAddedEventConsumer(
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == medicalReview.TrialId);
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
@@ -128,7 +128,7 @@ public class UrgentMedicalReviewAddedEventConsumer(
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
}
@@ -192,7 +192,7 @@ public class UrgentIRRepliedMedicalReviewConsumer(
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == medicalReview.TrialId);
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
@@ -236,7 +236,7 @@ public class UrgentIRRepliedMedicalReviewConsumer(
};
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
}
}
@@ -308,7 +308,7 @@ public class UrgentMIMRepliedMedicalReviewConsumer(
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == medicalReview.TrialId);
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
@@ -353,8 +353,7 @@ public class UrgentMIMRepliedMedicalReviewConsumer(
};
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
}
}
}
@@ -419,7 +418,7 @@ public class UrgentIRApplyedReReadingConsumer(
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == taskInfo.TrialId);
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
@@ -463,7 +462,7 @@ public class UrgentIRApplyedReReadingConsumer(
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
}
}
@@ -2,6 +2,7 @@
using DocumentFormat.OpenXml.Spreadsheet;
using DocumentFormat.OpenXml.Vml;
using DocumentFormat.OpenXml.Wordprocessing;
using IRaCIS.Application.Contracts;
using IRaCIS.Core.Application.Contracts;
using IRaCIS.Core.Application.Helper;
using IRaCIS.Core.Application.MassTransit.Command;
@@ -55,6 +56,7 @@ public class UserSiteSurveySubmitedEventConsumer(
if (emailConfig != null)
{
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == trialId);
var trialUserList = await _trialUserRoleRepository.Where(t => t.TrialId == siteSurveyInfo.TrialId && t.TrialUser.IsDeleted == false)
.Where(t => t.UserRole.UserTypeEnum == UserTypeEnum.SPM || t.UserRole.UserTypeEnum == UserTypeEnum.CPM || t.UserRole.UserTypeEnum == UserTypeEnum.ProjectManager || t.UserRole.UserTypeEnum == UserTypeEnum.APM)
@@ -67,8 +69,8 @@ public class UserSiteSurveySubmitedEventConsumer(
var toUserName = string.Empty;
//有SPM
if (sPMOrCPMList.Count > 0)
//有SPM 并且参与
if (trialInfo.IsSPMJoinSiteSurvey && sPMOrCPMList.Count > 0)
{
foreach (var user in sPMOrCPMList)
{
@@ -96,7 +98,6 @@ public class UserSiteSurveySubmitedEventConsumer(
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == trialId);
var siteInfo = await _trialSiteRepository.FirstOrDefaultAsync(t => t.TrialId == trialId && t.Id == siteSurveyInfo.TrialSiteId, true);
@@ -122,7 +123,7 @@ public class UserSiteSurveySubmitedEventConsumer(
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(emailConfig, messageToSend, emailConfigFunc);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
}
}
@@ -205,7 +206,7 @@ public class SiteSurveySPMSubmitedEventConsumer(
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(emailConfig, messageToSend, emailConfigFunc);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
}
@@ -283,7 +284,7 @@ public class SiteSurverRejectedEventConsumer(
{
//没有SPM PM驳回到CRC
messageToSend.To.Add(new MailboxAddress(String.Empty, siteSurveyInfo.Email));
messageToSend.To.Add(new MailboxAddress(siteSurveyInfo.UserName, siteSurveyInfo.Email));
}
//发件地址
@@ -319,7 +320,7 @@ public class SiteSurverRejectedEventConsumer(
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(emailConfig, messageToSend, emailConfigFunc);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
}
@@ -89,14 +89,14 @@ public class CRCSubmitedAndQCToAuditEventConsumer(
foreach (var userinfo in userinfoList)
{
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
}
var userNames = userinfoList.Select(x => x.FullName).ToList();
foreach (var pm in pmandAPm)
{
messageToSend.Cc.Add(new MailboxAddress(String.Empty, pm.EMail));
messageToSend.Cc.Add(new MailboxAddress(pm.FullName, pm.EMail));
}
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
@@ -122,7 +122,7 @@ public class CRCSubmitedAndQCToAuditEventConsumer(
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
}
}
@@ -181,11 +181,11 @@ public class CRCRepliedQCChallengeEventConsumer(
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
foreach (var pm in pmandAPm)
{
messageToSend.Cc.Add(new MailboxAddress(String.Empty, pm.EMail));
messageToSend.Cc.Add(new MailboxAddress(pm.FullName, pm.EMail));
}
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
@@ -221,7 +221,7 @@ public class CRCRepliedQCChallengeEventConsumer(
};
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
}
}
}
@@ -307,10 +307,10 @@ public class QCRepliedQCChallengeEventConsumer(
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
if (craInfo != null)
{
messageToSend.Cc.Add(new MailboxAddress(String.Empty, craInfo.EMail));
messageToSend.Cc.Add(new MailboxAddress(craInfo.FullName, craInfo.EMail));
}
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
@@ -339,7 +339,7 @@ public class QCRepliedQCChallengeEventConsumer(
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
}
}
}
@@ -439,7 +439,7 @@ public class CRCRepliedCheckChallengeEventConsumer(
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
}
}
}
@@ -517,10 +517,10 @@ public class PMRepliedCheckChallengeEventConsumer(
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
if (craInfo != null)
{
messageToSend.Cc.Add(new MailboxAddress(String.Empty, craInfo.EMail));
messageToSend.Cc.Add(new MailboxAddress(craInfo.FullName, craInfo.EMail));
}
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
@@ -549,7 +549,7 @@ public class PMRepliedCheckChallengeEventConsumer(
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
}
}
}
@@ -620,7 +620,7 @@ public class CheckStateChangedToAuditEventConsumer(
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
@@ -648,7 +648,7 @@ public class CheckStateChangedToAuditEventConsumer(
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
}
}
}
@@ -703,15 +703,19 @@ public class QCClaimTaskEventConsumer(
var userinfo = subjectVisit.CurrentActionUser;
if (userinfo == null)
{
return;
}
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
foreach (var pm in pmandAPm)
{
messageToSend.Cc.Add(new MailboxAddress(String.Empty, pm.EMail));
messageToSend.Cc.Add(new MailboxAddress(pm.FullName, pm.EMail));
}
var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
@@ -757,7 +761,7 @@ public class QCClaimTaskEventConsumer(
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
}
}
}
@@ -1,14 +1,16 @@
using System;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using IRaCIS.Core.Application.Helper.OtherTool;
using IRaCIS.Core.Domain.BaseModel;
using IRaCIS.Core.Domain.Share;
using MassTransit;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using NPOI.SS.Formula.Functions;
using Newtonsoft.Json;
using System;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
public class ConsumeExceptionFilter<T>(ILogger<ConsumeExceptionFilter<T>> _logger) : IFilter<ConsumeContext<T>> where T : DomainEvent
public class ConsumeExceptionFilter<T>(ILogger<ConsumeExceptionFilter<T>> _logger, IConfiguration _config) : IFilter<ConsumeContext<T>> where T : DomainEvent
{
public async Task Send(ConsumeContext<T> context, IPipe<ConsumeContext<T>> next)
@@ -23,6 +25,44 @@ public class ConsumeExceptionFilter<T>(ILogger<ConsumeExceptionFilter<T>> _logge
{
var errorInfo = $"Exception: {exception.Message}[{exception.StackTrace}]" + (exception.InnerException != null ? $" InnerException: {exception.InnerException.Message}[{exception.InnerException.StackTrace}]" : "");
_logger.LogError(errorInfo);
try
{
bool isOpenWeComNotice = _config.GetValue<bool>("WeComNoticeConfig:IsOpenWeComNotice");
if (isOpenWeComNotice)
{
string webhook = _config["WeComNoticeConfig:WebhookUrl"] ?? string.Empty;
var uri = new Uri(_config["SystemEmailSendConfig:SiteUrl"]);
var baseUrl = uri.GetLeftPart(UriPartial.Authority);
var userList = _config.GetSection("WeComNoticeConfig:APINoticeUserList").Get<string[]>();
// 🔔 异步告警(不要阻塞请求)
_ = WeComNotifier.SendAlertAsync(
webhook: webhook,
alert: new WeComAlert
{
Env = baseUrl,
UserName = "MassTransit 自动触发邮件",
Api = "",
Message = $"异常信息:{exception.Message} ",
Stack = exception.StackTrace,
AtUsers = userList ?? []
}
);
}
}
catch (Exception ex)
{
Log.Logger.Error($"MassTransit里发送企业微信出现错误:{ex.Message}");
}
}
}
@@ -5,6 +5,7 @@ using IRaCIS.Core.Application.Contracts;
using IRaCIS.Core.Application.Helper;
using IRaCIS.Core.Application.MassTransit.Consumer;
using IRaCIS.Core.Application.Service.Reading.Dto;
using IRaCIS.Core.Domain.Models;
using MassTransit;
using Microsoft.Extensions.Options;
using MimeKit;
@@ -26,6 +27,7 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
public class UrgentIRUnReadTaskRecurringEventConsumer(
IRepository<ReadingQuestionCriterionTrial> _trialReadingCriterionRepository,
IRepository<VisitTask> _visitTaskRepository,
IRepository<Trial> _trialRepository,
IRepository<Dictionary> _dictionaryRepository,
IRepository<TrialUserRole> _trialUserRoleRepository,
IRepository<TrialEmailNoticeConfig> _trialEmailNoticeConfigrepository,
@@ -35,9 +37,15 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
public async Task Consume(ConsumeContext<UrgentIRUnReadTaskRecurringEvent> context)
{
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
var isEn_US = context.Message.CultureInfoName == StaticData.CultureInfo.en_US;
//设置当前事件传递过来的语言
var culture = context.Message.CultureInfoName;
CultureInfo.CurrentCulture = new CultureInfo(culture);
var trialId = context.Message.TrialId;
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == trialId);
var scenario = EmailBusinessScenario.ExpeditedReading;
var trialEmailConfig = _trialEmailNoticeConfigrepository.Where(t => t.TrialId == trialId && t.BusinessScenarioEnum == scenario && t.IsAutoSend && t.IsEnable).FirstOrDefault();
@@ -161,7 +169,7 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
await CommonEmailHelper.GetTrialEmailSubejctAndHtmlInfoAndBuildAsync(trialEmailConfig, messageToSend, emailConfigFunc);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
//处理标记已通知的任务
@@ -18,7 +18,7 @@ public static class OldRecurringEmailHelper
public static string EmailNamePlaceholder => StaticData.EmailSend.EmailNamePlaceholder;
//发送项目邮件的通用方法 已经处理
public static async Task SendTrialEmailAsync(
IRepository<TrialEmailNoticeConfig> _trialEmailNoticeConfigRepository,
IRepository<Trial> _trialRepository,
@@ -34,6 +34,7 @@ public static class OldRecurringEmailHelper
var trialEmailConfig = await _trialEmailNoticeConfigRepository.Where(t => t.TrialId == trialId && t.BusinessScenarioEnum == businessScenario, ignoreQueryFilters: true)
.Include(t => t.TrialEmailNoticeUserList).Include(t => t.TrialEmailBlackUserList).FirstOrDefaultAsync();
var trialInfo=await _trialRepository.Where(t=>t.Id== trialId).FirstOrDefaultAsync();
if (trialEmailConfig == null || trialEmailConfig.IsAutoSend == false || trialEmailConfig.IsEnable == false)
{
@@ -162,7 +163,7 @@ public static class OldRecurringEmailHelper
if (sendEmailConfig != null)
{
await SendEmailHelper.SendEmailAsync(sendEmailConfig);
await SendEmailHelper.SendEmailAsync(sendEmailConfig, trialInfo);
}
@@ -7,6 +7,7 @@ using MassTransit.Scheduling;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
@@ -14,7 +15,7 @@ using System.Threading.Tasks;
namespace IRaCIS.Core.Application.MassTransit.Consumer;
//项目手动选择 周期性邮件
/// <summary>
@@ -25,14 +26,20 @@ public class QCImageQuestionRecurringEventConsumer(IRepository<Trial> _trialRepo
IRepository<TrialEmailNoticeConfig> _trialEmailNoticeConfigRepository,
IRepository<TrialUserRole> _trialUserRoleRepository,
IRepository<VisitTask> _visitTaskRepository,
IOptionsMonitor<SystemEmailSendConfig> systemEmailConfig,
IRepository<TrialSiteUserRole> _trialSiteUserRoleRepository,
IOptionsMonitor<SystemEmailSendConfig> _SystemEmailSendConfig) : IConsumer<QCImageQuestionRecurringEvent>
{
private readonly SystemEmailSendConfig _systemEmailConfig = systemEmailConfig.CurrentValue;
public async Task Consume(ConsumeContext<QCImageQuestionRecurringEvent> context)
{
var trialId = context.Message.TrialId;
var isEn_us = false;
var isEn_us = context.Message.CultureInfoName == StaticData.CultureInfo.en_US;
//设置当前事件传递过来的语言
var culture = context.Message.CultureInfoName;
CultureInfo.CurrentCulture = new CultureInfo(culture);
var trialInfo = await _trialRepository.Where(t => t.Id == trialId).Select(t => new { t.ResearchProgramNo, t.ExperimentName, t.TrialCode, t.TrialStatusStr }).FirstNotNullAsync();
@@ -64,8 +71,9 @@ public class QCImageQuestionRecurringEventConsumer(IRepository<Trial> _trialRepo
Func<TrialEmailNoticeConfig, (string topicStr, string htmlBodyStr, bool isEn_us, Guid? onlyToUserId)> topicAndHtmlFunc = trialEmailConfig =>
{
var topicStr = string.Format(isEn_us ? trialEmailConfig.EmailTopic : trialEmailConfig.EmailTopicCN, trialInfo.ResearchProgramNo);
var htmlBodyStr = string.Format(isEn_us ? trialEmailConfig.EmailHtmlContent : trialEmailConfig.EmailHtmlContentCN,
user.FullName, DateTime.Now, sendStat.ToBeDealedCount, _SystemEmailSendConfig.CurrentValue.SiteUrl);
var htmlContent = isEn_us ? trialEmailConfig.EmailHtmlContent : trialEmailConfig.EmailHtmlContentCN;
var htmlBodyStr = string.Format(CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, htmlContent),
user.FullName, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), sendStat.ToBeDealedCount, _SystemEmailSendConfig.CurrentValue.SiteUrl);
return (topicStr, htmlBodyStr, isEn_us, userId);
};
@@ -91,13 +99,20 @@ public class CRCImageQuestionRecurringEventConsumer(IRepository<Trial> _trialRep
IRepository<TrialUserRole> _trialUserRoleRepository,
IRepository<VisitTask> _visitTaskRepository,
IRepository<TrialSiteUserRole> _trialSiteUserRoleRepository,
IOptionsMonitor<SystemEmailSendConfig> systemEmailConfig,
IOptionsMonitor<SystemEmailSendConfig> _SystemEmailSendConfig) : IConsumer<CRCImageQuestionRecurringEvent>
{
private readonly SystemEmailSendConfig _systemEmailConfig = systemEmailConfig.CurrentValue;
public async Task Consume(ConsumeContext<CRCImageQuestionRecurringEvent> context)
{
var trialId = context.Message.TrialId;
var isEn_us = false;
var isEn_us = context.Message.CultureInfoName == StaticData.CultureInfo.en_US;
//设置当前事件传递过来的语言
var culture = context.Message.CultureInfoName;
CultureInfo.CurrentCulture = new CultureInfo(culture);
var trialInfo = await _trialRepository.Where(t => t.Id == trialId).Select(t => new { t.ResearchProgramNo, t.ExperimentName, t.TrialCode, t.TrialStatusStr, t.DeclarationTypeEnumList }).FirstNotNullAsync();
//找到 该项目的IQC 用户Id
@@ -131,9 +146,10 @@ public class CRCImageQuestionRecurringEventConsumer(IRepository<Trial> _trialRep
Func<TrialEmailNoticeConfig, (string topicStr, string htmlBodyStr, bool isEn_us, Guid? onlyToUserId)> topicAndHtmlFunc = trialEmailConfig =>
{
var topicStr = string.Format(isEn_us ? trialEmailConfig.EmailTopic : trialEmailConfig.EmailTopicCN, trialInfo.ResearchProgramNo);
var htmlBodyStr = string.Format(isEn_us ? trialEmailConfig.EmailHtmlContent : trialEmailConfig.EmailHtmlContentCN,
var htmlContent = isEn_us ? trialEmailConfig.EmailHtmlContent : trialEmailConfig.EmailHtmlContentCN;
var htmlBodyStr = string.Format(CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, htmlContent),
user.FullName, DateTime.Now, sendStat.ToBeDealedCount - sendStat.ReUploadTobeDealedCount, sendStat.ReUploadTobeDealedCount, _SystemEmailSendConfig.CurrentValue.SiteUrl);
user.FullName, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), sendStat.ToBeDealedCount - sendStat.ReUploadTobeDealedCount, sendStat.ReUploadTobeDealedCount, _SystemEmailSendConfig.CurrentValue.SiteUrl);
return (topicStr, htmlBodyStr, false, userId);
@@ -158,13 +174,20 @@ public class ImageQCRecurringEventConsumer(IRepository<Trial> _trialRepository,
IRepository<TrialUserRole> _trialUserRoleRepository,
IRepository<VisitTask> _visitTaskRepository,
IRepository<TrialSiteUserRole> _trialSiteUserRoleRepository,
IOptionsMonitor<SystemEmailSendConfig> systemEmailConfig,
IOptionsMonitor<SystemEmailSendConfig> _SystemEmailSendConfig) : IConsumer<ImageQCRecurringEvent>
{
private readonly SystemEmailSendConfig _systemEmailConfig = systemEmailConfig.CurrentValue;
public async Task Consume(ConsumeContext<ImageQCRecurringEvent> context)
{
var trialId=context.Message.TrialId;
var isEn_us = false;
var isEn_us = context.Message.CultureInfoName == StaticData.CultureInfo.en_US;
//设置当前事件传递过来的语言
var culture = context.Message.CultureInfoName;
CultureInfo.CurrentCulture = new CultureInfo(culture);
var trialInfo = await _trialRepository.Where(t => t.Id == trialId).Select(t => new { t.ResearchProgramNo, t.ExperimentName, t.TrialCode, t.TrialStatusStr }).FirstNotNullAsync();
@@ -199,9 +222,12 @@ public class ImageQCRecurringEventConsumer(IRepository<Trial> _trialRepository,
Func<TrialEmailNoticeConfig, (string topicStr, string htmlBodyStr, bool isEn_us, Guid? onlyToUserId)> topicAndHtmlFunc = trialEmailConfig =>
{
var topicStr = string.Format(isEn_us ? trialEmailConfig.EmailTopic : trialEmailConfig.EmailTopicCN, trialInfo.ResearchProgramNo);
var htmlBodyStr = string.Format(isEn_us ? trialEmailConfig.EmailHtmlContent : trialEmailConfig.EmailHtmlContentCN,
user.FullName, DateTime.Now, sendStat.ToBeClaimedCount, sendStat.ToBeReviewedCount, _SystemEmailSendConfig.CurrentValue.SiteUrl);
var htmlContent = isEn_us ? trialEmailConfig.EmailHtmlContent : trialEmailConfig.EmailHtmlContentCN;
var htmlBodyStr = string.Format(CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, htmlContent),
user.FullName, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), sendStat.ToBeClaimedCount, sendStat.ToBeReviewedCount, _SystemEmailSendConfig.CurrentValue.SiteUrl);
return (topicStr, htmlBodyStr, false, userId);
};
@@ -42,7 +42,12 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
DateTime now = DateTime.Now;
Console.WriteLine("发送定时过期提醒");
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
var isEn_US = context.Message.CultureInfoName == StaticData.CultureInfo.en_US;
//设置当前事件传递过来的语言
var culture = context.Message.CultureInfoName;
CultureInfo.CurrentCulture = new CultureInfo(culture);
var systemDocQuery =
from sysDoc in _systemDocumentRepository.AsQueryable(false)
from identityUser in _identityUserRepository.AsQueryable(false).Where(t => t.UserRoleList.Where(t => t.IsUserRoleDisabled == false).Any(t => sysDoc.NeedConfirmedUserTypeList.AsQueryable().Any(c => c.NeedConfirmUserTypeId == t.UserTypeId)))
@@ -89,7 +94,7 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
@@ -114,7 +119,7 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
if (emailConfig != null)
{
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(emailConfig, messageToSend, emailConfigFunc);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
}
@@ -150,7 +155,11 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
public async Task Consume(ConsumeContext<SystemDocumentPublishEvent> context)
{
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
var isEn_US = context.Message.CultureInfoName == StaticData.CultureInfo.en_US;
//设置当前事件传递过来的语言
var culture = context.Message.CultureInfoName;
CultureInfo.CurrentCulture = new CultureInfo(culture);
// 记录是否只发送给新增角色的日志
if (context.Message.NewUserTypeIds != null && context.Message.NewUserTypeIds.Any())
@@ -230,7 +239,7 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
@@ -9,10 +9,12 @@ using MassTransit;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using MimeKit;
using NPOI.SS.Formula.Functions;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Reactive.Joins;
using System.Text;
using System.Text.RegularExpressions;
@@ -51,23 +53,25 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
DateTime now = DateTime.Now;
Console.WriteLine("发送定时项目过期提醒");
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
var isEn_US = context.Message.CultureInfoName == StaticData.CultureInfo.en_US;
//设置当前事件传递过来的语言
var culture = context.Message.CultureInfoName;
CultureInfo.CurrentCulture = new CultureInfo(culture);
var trialDocQuery =
from trialDoc in _trialDocumentRepository.AsQueryable(true)
join trialIdentityUser in _trialIdentityUserRepository.Where(x => x.IsDeleted == false) on trialDoc.TrialId equals trialIdentityUser.TrialId
join trialUserRole in _trialUserRoleRepository.Where(x => x.IsDeleted == false) on trialIdentityUser.Id equals trialUserRole.TrialUserId
from identityUser in _identityUserRepository.AsQueryable(false)
.Where(t => t.Status == UserStateEnum.Enable &&
t.Id == trialIdentityUser.IdentityUserId &&
t.UserRoleList.Where(t => t.IsUserRoleDisabled == false)
.Any(t => trialDoc.NeedConfirmedUserTypeList.AsQueryable().Any(c => c.NeedConfirmUserTypeId == t.UserTypeId)))
join identityUser in _identityUserRepository.AsQueryable(false).Where(u => u.Status == UserStateEnum.Enable)
on trialIdentityUser.IdentityUserId equals identityUser.Id
join confirm in _trialDocConfirmedUserRepository.Where() on
new { trialIdentityUser.IdentityUserId, TrialDocumentId = trialDoc.Id } equals new { IdentityUserId = confirm.ConfirmUserId, confirm.TrialDocumentId } into cc
from confirm in cc.DefaultIfEmpty()
where trialIdentityUser.TrialUserRoleList.Any(ur => !ur.IsDeleted && trialDoc.NeedConfirmedUserTypeList.Any(c => c.NeedConfirmUserTypeId == ur.UserRole.UserTypeId))
select new TrialSignDocView()
{
TrialCode = trialDoc.Trial.TrialCode,
@@ -76,6 +80,7 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
CurrentStaffTrainDays = trialDoc.CurrentStaffTrainDays,
NewStaffTrainDays = trialDoc.NewStaffTrainDays,
Id = trialDoc.Id,
TrialId= trialDoc.TrialId,
IsSystemDoc = false,
CreateTime = trialDoc.CreateTime,
FullFilePath = trialDoc.Path,
@@ -110,10 +115,11 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
{
Console.WriteLine($"{index}发送定时过期提醒,邮箱:{userinfo.EMail},姓名{userinfo.UserName}");
index++;
var trialInfo = _trialRepository.Where(x => x.Id == userinfo.TrialId).FirstOrDefault();
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
@@ -138,8 +144,8 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
if (emailConfig != null)
{
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(emailConfig, messageToSend, emailConfigFunc);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
}
@@ -163,6 +169,7 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
IRepository<VisitTask> _visitTaskRepository,
IRepository<TrialDocument> _trialDocumentRepository,
IRepository<IdentityUser> _identityUserRepository,
IRepository<Trial> _trialRepository,
IRepository<TrialIdentityUser> _trialIdentityUserRepository,
IRepository<Dictionary> _dictionaryRepository,
IRepository<TrialUserRole> _trialUserRoleRepository,
@@ -174,7 +181,11 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
public async Task Consume(ConsumeContext<TrialDocumentPublishEvent> context)
{
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
var isEn_US = context.Message.CultureInfoName == StaticData.CultureInfo.en_US;
//设置当前事件传递过来的语言
var culture = context.Message.CultureInfoName;
CultureInfo.CurrentCulture = new CultureInfo(culture);
// 记录是否只发送给新增角色的日志
if (context.Message.NewUserTypeIds != null && context.Message.NewUserTypeIds.Any())
@@ -191,16 +202,15 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
from trialDoc in _trialDocumentRepository.AsQueryable(false).Where(x => context.Message.Ids.Contains(x.Id))
join trialIdentityUser in _trialIdentityUserRepository.Where(x=>x.IsDeleted==false) on trialDoc.TrialId equals trialIdentityUser.TrialId
join trialUserRole in _trialUserRoleRepository.Where(x => x.IsDeleted == false) on trialIdentityUser.Id equals trialUserRole.TrialUserId
from identityUser in _identityUserRepository.AsQueryable(false)
.Where(t => t.Status == UserStateEnum.Enable &&
t.Id == trialIdentityUser.IdentityUserId &&
t.UserRoleList.Where(t => t.IsUserRoleDisabled == false)
.Any(t => context.Message.NewUserTypeIds.Contains(t.UserTypeId) &&
trialDoc.NeedConfirmedUserTypeList.AsQueryable().Any(c => c.NeedConfirmUserTypeId == t.UserTypeId)))
join identityUser in _identityUserRepository.AsQueryable(false).Where(u => u.Status == UserStateEnum.Enable)
on trialIdentityUser.IdentityUserId equals identityUser.Id
where trialIdentityUser.TrialUserRoleList.Any(ur => !ur.IsDeleted && context.Message.NewUserTypeIds.Contains(ur.UserRole.UserTypeId) && trialDoc.NeedConfirmedUserTypeList.Any(c => c.NeedConfirmUserTypeId == ur.UserRole.UserTypeId))
select new UnionDocumentWithConfirmInfoView()
{
IsSystemDoc = true,
Id = trialDoc.Id,
TrialId= trialDoc.TrialId,
CreateTime = trialDoc.CreateTime,
IsDeleted = trialDoc.IsDeleted,
SignViewMinimumMinutes = trialDoc.SignViewMinimumMinutes,
@@ -222,15 +232,19 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
from trialDoc in _trialDocumentRepository.AsQueryable(false).Where(x => context.Message.Ids.Contains(x.Id))
join trialIdentityUser in _trialIdentityUserRepository.Where(x => x.IsDeleted == false) on trialDoc.TrialId equals trialIdentityUser.TrialId
join trialUserRole in _trialUserRoleRepository.Where(x=>x.IsDeleted==false) on trialIdentityUser.Id equals trialUserRole.TrialUserId
from identityUser in _identityUserRepository.AsQueryable(false)
.Where(t => t.Status == UserStateEnum.Enable &&
t.Id== trialIdentityUser.IdentityUserId&&
t.UserRoleList.Where(t => t.IsUserRoleDisabled == false)
.Any(t => trialDoc.NeedConfirmedUserTypeList.AsQueryable().Any(c => c.NeedConfirmUserTypeId == t.UserTypeId)))
join trial in _trialRepository.AsQueryable(false) on trialDoc.TrialId equals trial.Id
join identityUser in _identityUserRepository.AsQueryable(false).Where(u => u.Status == UserStateEnum.Enable)
on trialIdentityUser.IdentityUserId equals identityUser.Id
where trialIdentityUser.TrialUserRoleList.Any(ur => !ur.IsDeleted &&trialDoc.NeedConfirmedUserTypeList.Any(c => c.NeedConfirmUserTypeId == ur.UserRole.UserTypeId))
select new UnionDocumentWithConfirmInfoView()
{
IsSystemDoc = false,
Id = trialDoc.Id,
TrialId = trialDoc.TrialId,
EmailFromName =trial.EmailFromName,
CreateTime = trialDoc.CreateTime,
IsDeleted = trialDoc.IsDeleted,
SignViewMinimumMinutes = trialDoc.SignViewMinimumMinutes,
@@ -245,6 +259,7 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
FullFilePath = trialDoc.Path
};
}
var datalist = await systemDocQuery.IgnoreQueryFilters().ToListAsync();
var confirmUserIdList = datalist.Select(t => t.ConfirmUserId).Distinct().ToList();
@@ -260,7 +275,7 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail));
messageToSend.To.Add(new MailboxAddress(userinfo.FullName, userinfo.EMail));
@@ -286,7 +301,12 @@ namespace IRaCIS.Core.Application.MassTransit.Recurring
{
await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(emailConfig, messageToSend, emailConfigFunc);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
var trial = datalist.Where(x => x.ConfirmUserId == userinfo.Id).FirstOrDefault();
var trialInfo = await _trialRepository.Where(x=>x.Id==trial.TrialId).FirstNotNullAsync();
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
msg += "发送成功";
}
@@ -490,6 +490,8 @@ namespace IRaCIS.Core.Application.ViewModel
public int? RandomOrder { get; set; }
public bool? IsRandomOrderList { get; set; }
public CriterionType? CriterionType { get; set; }
}
@@ -17,7 +17,9 @@ using MassTransit;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Threading.Tasks;
using Subject = IRaCIS.Core.Domain.Models.Subject;
namespace IRaCIS.Core.Application.Service.Allocation;
@@ -41,10 +43,15 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
IRepository<ReadingQuestionCriterionTrial> _trialReadingCriterionRepository,
IRepository<ReadingQuestionCriterionTrial> _readingQuestionCriterionTrialRepository,
IRepository<ReadingTaskQuestionAnswer> _readingTaskQuestionAnswerRepository,
IRepository<Segment> _segmentRepository,
IRepository<Segmentation> _segmentationRepository,
IRepository<SegmentBinding> _segmentBindingRepository,
IRepository<DicomInstance> _dicomInstanceRepository,
IRepository<DicomSeries> _dicomSeriesRepository,
IRepository<SubjectCanceDoctor> _subjectCanceDoctorRepository,
IRepository<ReadingTaskQuestionMark> _readingTaskQuestionMarkRepository,
IRepository<ReadingNoneDicomMark> _readingNoneDicomMarkRepository,
IRepository<ReadingNoneDicomMarkBinding> _readingNoneDicomMarkBindingRepository,
IRepository<ReadingTableAnswerRowInfo> _readingTableAnswerRowInfoRepository,
//IRepository<ReadingCustomTag> _readingCustomTagRepository,
IRepository<TaskInfluence> _taskInfluenceRepository,
@@ -1027,8 +1034,8 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
//满足前序访视不存在 需要签署但是未签署 sql 相当复杂 同时想查询所有未读的统计数字 就无法统计 byzhouhang
//但是加字段 IsFrontTaskNeedSignButNotSign 那么签名临床数据的时候,要对该subject 该标准的有效的任务 这个字段需要在签名的时候维护 采取这种方式 统计数字灵活
//.Where(t => t.Subject.SubjectVisitTaskList.AsQueryable().Where(visitTaskLambda).Any(c => c.IsNeedClinicalDataSign == true && c.IsClinicalDataSign == false && c.VisitTaskNum < t.VisitTaskNum))
.WhereIf(!string.IsNullOrEmpty(inQuery.SubjectCode), t => (t.Subject.Code.Contains(inQuery.SubjectCode!) && t.IsAnalysisCreate == false) || (t.BlindSubjectCode.Contains(inQuery.SubjectCode!) && t.IsAnalysisCreate));
.WhereIf(!string.IsNullOrEmpty(inQuery.SubjectCode), t => (t.Subject.Code.Contains(inQuery.SubjectCode!) && t.IsAnalysisCreate == false) || (t.BlindSubjectCode.Contains(inQuery.SubjectCode!) && t.IsAnalysisCreate))
;
var visitGroupQuery = visitQuery.GroupBy(x => new { x.SubjectId, x.Subject.Code, x.BlindSubjectCode });
@@ -1171,7 +1178,9 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
var visitQuery = _visitTaskRepository.Where(x => x.TrialId == trialId && x.DoctorUserId == _userInfo.UserRoleId && x.TaskState == TaskState.Effect)
.WhereIf(inQuery.SubjectId != null, x => x.SubjectId == inQuery.SubjectId)
.WhereIf(!string.IsNullOrEmpty(subjectCode), t => (t.Subject.Code.Contains(subjectCode!) && t.IsAnalysisCreate == false) || (t.BlindSubjectCode.Contains(subjectCode!) && t.IsAnalysisCreate));
.WhereIf(!string.IsNullOrEmpty(subjectCode), t => (t.Subject.Code.Contains(subjectCode!) && t.IsAnalysisCreate == false) || (t.BlindSubjectCode.Contains(subjectCode!) && t.IsAnalysisCreate))
.WhereIf(critrion.CriterionType == CriterionType.OCT, t => t.ReadingCategory == ReadingCategory.Visit ? t.SourceSubjectVisit.NoneDicomStudyList.Where(t => t.Modality == "OCT").SelectMany(t => t.ImageLabelNoneDicomFileList).Any() : true)
.WhereIf(critrion.CriterionType == CriterionType.IVUS, t => t.ReadingCategory == ReadingCategory.Visit ? t.SourceSubjectVisit.NoneDicomStudyList.Where(t => t.Modality == "IVUS").SelectMany(t => t.ImageLabelNoneDicomFileList).Any() : true);
var visitGroupQuery = visitQuery.GroupBy(x => new { x.SubjectId, x.Subject.Code, x.BlindSubjectCode });
@@ -2081,7 +2090,9 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
{
if (origenalTask.ReadingCategory == ReadingCategory.Visit)
{
CopyForms(newTask, origenalTask);
newTask.IsCopyLesionAnswer = true;
//CopyForms(newTask, origenalTask);
}
@@ -2179,7 +2190,6 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
if (origenalTask.ReadingCategory == ReadingCategory.Visit)
{
CopyForms(newTask, origenalTask);
}
}
@@ -2189,7 +2199,8 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
{
if (origenalTask.ReadingCategory == ReadingCategory.Visit)
{
CopyForms(newTask, origenalTask);
newTask.IsCopyLesionAnswer = true;
//CopyForms(newTask, origenalTask);
}
}
@@ -2212,9 +2223,9 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
}
}
private void CopyForms(VisitTask newTask, VisitTask origenalTask)
private async Task CopyForms(VisitTask newTask, VisitTask origenalTask)
{
newTask.IsCopyLesionAnswer = true;
//自定义
//var readingCustomTagList = _readingCustomTagRepository.Where(t => t.VisitTaskId == origenalTask.Id).ToList();
@@ -2228,6 +2239,7 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
//_ = _readingCustomTagRepository.AddRangeAsync(readingCustomTagList).Result;
var readingTaskQuestionAnswerList = _readingTaskQuestionAnswerRepository.Where(t => t.VisitTaskId == origenalTask.Id).ToList();
@@ -2256,7 +2268,7 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
//ReadingTableAnswerRowInfo ReadingTableQuestionAnswer 一起加
var readingTableAnswerRowInfoList = _readingTableAnswerRowInfoRepository.Where(t => t.VisitTaskId == origenalTask.Id).Include(t => t.LesionAnswerList).ToList();
Dictionary<Guid, Guid> lesionRelationship = new Dictionary<Guid, Guid>() { };
foreach (var item in readingTableAnswerRowInfoList)
{
@@ -2264,7 +2276,7 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
var originalFristAddTaskId = item.FristAddTaskId;
var newRowId = NewId.NextSequentialGuid();
lesionRelationship.Add(item.Id, newRowId);
foreach (var mark in readingTaskQuestionMarkList)
{
mark.RowId = mark.RowId == item.Id ? newRowId : mark.RowId;
@@ -2286,13 +2298,112 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
}
}
#region
Dictionary<Guid, Guid> segmentationRelationship = new Dictionary<Guid, Guid>() { };
var segmentationList = _segmentationRepository.Where(t => t.VisitTaskId == origenalTask.Id).ToList();
foreach (var item in segmentationList)
{
var newSegmentationId = NewId.NextSequentialGuid();
segmentationRelationship.Add(item.Id, newSegmentationId);
item.Id = newSegmentationId;
item.VisitTaskId = newTask.Id;
}
Dictionary<Guid, Guid> segmentRelationship = new Dictionary<Guid, Guid>() { };
var segmentList = _segmentRepository.Where(t => t.VisitTaskId == origenalTask.Id).ToList();
foreach (var item in segmentList)
{
var newSegmentationId = NewId.NextSequentialGuid();
segmentRelationship.Add(item.Id, newSegmentationId);
if (segmentationRelationship.ContainsKey(item.SegmentationId))
{
item.SegmentationId = segmentationRelationship[item.SegmentationId];
}
item.Id = newSegmentationId;
item.VisitTaskId = newTask.Id;
}
var segmentBindingList = _segmentBindingRepository.Where(x => x.VisitTaskId == origenalTask.Id).ToList();
foreach (var item in segmentBindingList)
{
if (segmentationRelationship.ContainsKey(item.SegmentationId))
{
item.SegmentationId = segmentationRelationship[item.SegmentationId];
}
if (segmentRelationship.ContainsKey(item.SegmentId))
{
item.SegmentId = segmentRelationship[item.SegmentId];
}
if (item.RowId != null && lesionRelationship.ContainsKey(item.RowId.Value))
{
item.RowId = lesionRelationship[item.RowId.Value];
}
item.Id = NewId.NextSequentialGuid();
item.VisitTaskId = newTask.Id;
}
_ = _segmentationRepository.AddRangeAsync(segmentationList).Result;
_ = _segmentRepository.AddRangeAsync(segmentList).Result;
_ = _segmentBindingRepository.AddRangeAsync(segmentBindingList).Result;
#endregion
foreach (var item in readingTableAnswerRowInfoList)
{
if (item.SplitRowId != null && lesionRelationship.ContainsKey(item.SplitRowId.Value))
{
item.SplitRowId = lesionRelationship[item.SplitRowId.Value];
}
if (item.MergeRowId != null && lesionRelationship.ContainsKey(item.MergeRowId.Value))
{
item.MergeRowId = lesionRelationship[item.MergeRowId.Value];
}
}
_ = _readingTaskQuestionMarkRepository.AddRangeAsync(readingTaskQuestionMarkList).Result;
_ = _readingTableAnswerRowInfoRepository.AddRangeAsync(readingTableAnswerRowInfoList).Result;
var noneMarkList = _readingNoneDicomMarkRepository.Where(x => x.VisitTaskId == origenalTask.Id).ToList();
var noneMarkBindingList = _readingNoneDicomMarkBindingRepository.Where(x => x.VisitTaskId == origenalTask.Id).ToList();
foreach (var item in noneMarkList)
{
var newid = NewId.NextSequentialGuid();
item.MeasureData = item.MeasureData.Replace(origenalTask.Id.ToString(), newTask.Id.ToString());
item.VisitTaskId = newTask.Id;
foreach (var item1 in noneMarkBindingList)
{
if (item1.NoneDicomMarkId == item.Id)
{
item1.NoneDicomMarkId = newid;
}
}
item.Id = newid;
}
foreach (var item in noneMarkBindingList)
{
item.Id = NewId.NextSequentialGuid();
item.VisitTaskId = newTask.Id;
}
_ = _readingNoneDicomMarkRepository.AddRangeAsync(noneMarkList).Result;
_ = _readingNoneDicomMarkBindingRepository.AddRangeAsync(noneMarkBindingList).Result;
}
@@ -2385,6 +2496,10 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
//另一个阅片人的任务根据任务进度自动进入PM退回或PM申请重阅
filterExpression = filterExpression.And(t => t.VisitTaskNum >= task.VisitTaskNum);
//退回只影响有序的后续所有的,无序的当前访视
filterExpression = filterExpression.And(t => (t.TrialReadingCriterion.IsReadingTaskViewInOrder == ReadingOrder.InOrder) ||
(t.TrialReadingCriterion.IsReadingTaskViewInOrder != ReadingOrder.InOrder && t.SourceSubjectVisitId == task.SourceSubjectVisitId));
var influenceTaskList = await _visitTaskRepository.Where(filterExpression, true).ToListAsync();
@@ -2542,9 +2657,12 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
//删除序列数据
await _subjectCriteriaEvaluationVisitStudyFilterRepository.BatchDeleteNoTrackingAsync(t => t.TrialReadingCriterion.CriterionType == CriterionType.RECIST1Pointt1_MB && t.SubjectVisit.SubjectId == task.SubjectId && t.SubjectVisitId == task.SourceSubjectVisitId);
otherVisitIdList = otherVisitIdList.Where(t => t != task.SourceSubjectVisitId.Value).ToList();
}
//BM后续访视 ,筛选状态不变,任务生成状态重置(实际该访视任务状态 可能是重阅重置了或者失效了,需要后续生成,或者取消分配了,需要后续重新分配)
await _subjectCriteriaEvaluationVisitFilterRepository.UpdatePartialFromQueryAsync(t => t.TrialReadingCriterion.CriterionType == CriterionType.RECIST1Pointt1_MB && t.SubjectVisit.SubjectId == task.SubjectId && otherVisitIdList.Contains(t.SubjectVisitId),
t => new SubjectCriteriaEvaluationVisitFilter()
@@ -2598,6 +2716,19 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
sv.ReviewAuditUserId = null;
sv.SecondReviewState = SecondReviewState.None;
// 处理阅片期的临床数据
var readModuleIdList =await _readModuleRepository.Where(x => x.SubjectVisitId == sv.Id).Select(x => x.Id).ToListAsync();
await _readingClinicalDataReposiotry.UpdatePartialFromQueryAsync(t => readModuleIdList.Contains(t.ReadingId), c => new ReadingClinicalData()
{
IsSign = false,
ReadingClinicalDataState = ReadingClinicalDataStatus.HaveUploaded,
});
if (sv.IsBaseLine)
{
await _readingClinicalDataReposiotry.UpdatePartialFromQueryAsync(t => t.ReadingId == sv.Id && (t.ClinicalDataTrialSet.ClinicalDataLevel == ClinicalLevel.Subject || t.ClinicalDataTrialSet.ClinicalDataLevel == ClinicalLevel.SubjectVisit), c => new ReadingClinicalData() { IsSign = false, ReadingClinicalDataState = ReadingClinicalDataStatus.HaveUploaded });
@@ -2706,6 +2837,13 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
//默认影响的都是该标准的任务
filterExpression = filterExpression.And(t => t.TrialReadingCriterionId == filterObj.TrialReadingCriterionId);
}
//退回只影响有序的后续所有的,无序的当前访视
if (isReReading == false)
{
filterExpression = filterExpression.And(t => (t.TrialReadingCriterion.IsReadingTaskViewInOrder == ReadingOrder.InOrder) ||
(t.TrialReadingCriterion.IsReadingTaskViewInOrder != ReadingOrder.InOrder && t.SourceSubjectVisitId == filterObj.SourceSubjectVisitId));
}
}
@@ -0,0 +1,187 @@
//--------------------------------------------------------------------
// 此代码由liquid模板自动生成 byzhouhang 20240909
// 生成时间 2025-10-28 06:22:47Z
// 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
//--------------------------------------------------------------------
using IRaCIS.Core.Domain.Share;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace IRaCIS.Core.Application.ViewModel;
public class GetReSendEmailInDto : PageInput
{
public Guid Id { get; set; }
public DateTime? EmailStartDate { get; set; }
public DateTime? EmailEndDate { get; set; }
public EmailState? EmailStateEnum { get; set; }
public string ToRecipientName { get; set; } = string.Empty;
public string CcRecipientName { get; set; } = string.Empty;
}
public class EmailLogView : EmailLogAddOrEdit
{
public DateTime CreateTime { get; set; }
public DateTime UpdateTime { get; set; }
public List<EmailRecipientLogView> RecipientList { get; set; }
public List<EmaliAttachmentInfo> AttachmentList { get; set; }
}
public class EmaliAttachmentInfo
{
/// <summary>
/// 附件名称
/// </summary>
public string AttachmentName { get; set; }
/// <summary>
/// 附件路径
/// </summary>
public string AttachmentPath { get; set; }
}
public class EmailRecipientLogView
{
public Guid Id { get; set; }
/// <summary>
/// 邮件Id
/// </summary>
public Guid EmailLogId { get; set; }
/// <summary>
/// 收件人姓名
/// </summary>
public string RecipientName { get; set; }
/// <summary>
/// 收件人地址
/// </summary>
public string RecipientAddress { get; set; }
/// <summary>
/// 收件人类型
/// </summary>
public RecipientType RecipientTypeEnum { get; set; }
/// <summary>
/// 排序
/// </summary>
public int Sort { get; set; }
}
public class EmailLogAddOrEdit
{
public Guid? Id { get; set; }
/// <summary>
/// 邮件Id
/// </summary>
public string MessageId { get; set; }
/// <summary>
/// 唯一Id
/// </summary>
public string UniqueId { get; set; }
/// <summary>
/// 邮件主题
/// </summary>
public string EmailSubject { get; set; } = string.Empty;
/// <summary>
/// 日期
/// </summary>
public DateTime? EmailDate { get; set; }
/// <summary>
/// 错误信息
/// </summary>
public string ErrorInfo { get; set; }
/// <summary>
/// 发件人地址
/// </summary>
public string SenderAddress { get; set; }
/// <summary>
/// 发件人姓名
/// </summary>
public string SenderName { get; set; }
/// <summary>
/// 邮件内容
/// </summary>
public string Content { get; set; }
/// <summary>
/// 邮件状态
/// </summary>
public EmailState EmailStateEnum { get; set; }
}
public class GetEmailInfoOutDto:EmailLogView
{
}
public class GetEmailInfoInDto
{
public Guid Id { get; set; }
public Guid? TrialId { get; set; }
}
public class SynchronizationEmailInDto
{
public Guid? TrialId { get; set; }
}
public class ResendEmailInDto
{
public Guid Id { get; set; }
public Guid? TrialId { get; set; }
}
public class EmailAuthorization
{
public string FromEmail { get; set; } = string.Empty;
public string AuthorizationCode { get; set; } = string.Empty;
}
public class EmailLogQuery:PageInput
{
public Guid? TrialId { get; set; }
public DateTime? EmailStartDate { get; set; }
public DateTime? EmailEndDate { get; set; }
public EmailState? EmailStateEnum { get; set; }
public string ToRecipientName { get; set; } = string.Empty;
public string CcRecipientName { get; set; } = string.Empty;
}
@@ -0,0 +1,234 @@
//--------------------------------------------------------------------
// 此代码由liquid模板自动生成 byzhouhang 20240909
// 生成时间 2026-03-10 06:15:31Z
// 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
//--------------------------------------------------------------------
using System;
using IRaCIS.Core.Domain.Share;
using System.Collections.Generic;
namespace IRaCIS.Core.Application.ViewModel;
public class SubjectFileUploadRecordView
{
public string? SubjectCode { get; set; }
public string? VisitName { get; set; }
public string StudyCode { get; set; }
public Guid? SubjectId { get; set; }
public Guid? SubjectVisitId { get; set; }
public int FileCount { get; set; }
public string? UploadRegion { get; set; }
public string? TargetRegion { get; set; }
public DateTime CreateTime { get; set; }
public DateTime? SyncFinishedTime { get; set; }
public bool IsSync { get; set; }
}
public class FileUploadRecordView : FileUploadRecordAddOrEdit
{
public DateTime CreateTime { get; set; }
public DateTime UpdateTime { get; set; }
[Comment("同步结束时间-最后一个任务的时间")]
public DateTime? SyncFinishedTime { get; set; }
public string? SubjectCode { get; set; }
public string? VisitName { get; set; }
}
public class FileUploadRecordAddOrEdit
{
public Guid? Id { get; set; }
public string FileName { get; set; }
public long FileSize { get; set; }
public string FileType { get; set; }
public string Path { get; set; }
public string UploadBatchId { get; set; }
public BatchDataType BatchDataType { get; set; }
public string StudyCode { get; set; }
public Guid? TrialId { get; set; }
public Guid? SubjectId { get; set; }
public Guid? SubjectVisitId { get; set; }
public Guid? DicomStudyId { get; set; }
public Guid? NoneDicomStudyId { get; set; }
public string FileMarkId { get; set; }
public int? Priority { get; set; }
public string IP { get; set; }
public bool? IsNeedSync { get; set; }
public string UploadRegion { get; set; }
public string TargetRegion { get; set; }
public bool? IsSync { get; set; }
}
public class SubjectFileUploadRecordQuery : PageInput
{
public Guid TrialId { get; set; }
public string? SubjectCode { get; set; }
public string? VisitName { get; set; }
public string? StudyCode { get; set; }
public string? UploadRegion { get; set; }
public string? TargetRegion { get; set; }
public bool? IsSync { get; set; }
}
public class FileUploadRecordQuery : PageInput
{
public BatchDataType? BatchDataType { get; set; }
public Guid? TrialId { get; set; }
public int? DataFileType { get; set; }
public string StudyCode { get; set; }
public string? SubjectCode { get; set; }
public string? VisitName { get; set; }
public Guid? SubjectId { get; set; }
public Guid? SubjectVisitId { get; set; }
public string? FileMarkId { get; set; }
public string? FileName { get; set; }
public string? FileType { get; set; }
public string? IP { get; set; }
public bool? IsNeedSync { get; set; }
public bool? IsSync { get; set; }
public string? Path { get; set; }
public int? Priority { get; set; }
public string? TargetRegion { get; set; }
public string? UploadBatchId { get; set; }
public string? UploadRegion { get; set; }
public DateTime? SyncFinishedStartTime { get; set; }
public DateTime? SyncFinishedEndTime { get; set; }
public DateTime? UploadStartTime { get; set; }
public DateTime? UploadEndTime { get; set; }
}
public class UploadFileSyncRecordView
{
public Guid Id { get; set; }
public DateTime? StartTime { get; set; }
public DateTime? EndTime { get; set; }
public jobState JobState { get; set; }
public string Msg { get; set; }
public DateTime CreateTime { get; set; }
public DateTime UpdateTime { get; set; }
public Guid? FileUploadRecordId { get; set; }
public string? FileName { get; set; }
public string? FileType { get; set; }
public string? Path { get; set; }
public string StudyCode { get; set; }
public string? SubjectCode { get; set; }
public string? VisitName { get; set; }
}
public class UploadFileSyncRecordQuery : PageInput
{
public Guid? FileUploadRecordId { get; set; }
public jobState? JobState { get; set; }
public string? SubjectCode { get; set; }
public string? VisitName { get; set; }
public string? StudyCode { get; set; }
}
public class BatchAddSyncFileCommand
{
public List<Guid> FileUploadRecordIdList { get; set; }
public int? Priority { get; set; }
}
@@ -159,6 +159,12 @@ namespace IRaCIS.Core.Application.ViewModel
public class FrontAuditConfigAddOrEdit
{
public Guid? Id { get; set; }
/// <summary>
/// 适用的标准
/// </summary>
public List<CriterionType> ApplyCriterionList { get; set; } = new List<CriterionType>() { };
public string Value { get; set; } = string.Empty;
public string ValueCN { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace IRaCIS.Core.Application.Service.Common.DTO
{
//public class MailModel
//{
//}
}
@@ -0,0 +1,769 @@
//--------------------------------------------------------------------
// 此代码由liquid模板自动生成 byzhouhang 20240909
// 生成时间 2025-10-28 06:22:42Z
// 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
//--------------------------------------------------------------------
using DocumentFormat.OpenXml.Wordprocessing;
using IdentityModel.Client;
using IRaCIS.Core.Application.Helper;
using IRaCIS.Core.Application.Interfaces;
using IRaCIS.Core.Application.ViewModel;
using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.Infra.EFCore;
using IRaCIS.Core.Infrastructure.Extention;
using MailKit;
using MailKit.Net.Imap;
using MailKit.Search;
using MailKit.Security;
using MassTransit;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using MimeKit;
using Panda.DynamicWebApi.Attributes;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Mail;
using System.Threading.Tasks;
namespace IRaCIS.Core.Application.Service;
/// <summary>
/// 邮件日志
/// </summary>
/// <param name="_emailLogRepository"></param>
/// <param name="systemEmailConfig"></param>
/// <param name="_mapper"></param>
/// <param name="_userInfo"></param>
/// <param name="_localizer"></param>
[ApiExplorerSettings(GroupName = "Common")]
public class EmailLogService(IRepository<EmailLog> _emailLogRepository,
IRepository<Trial> _trialRepository,
IRepository<EmailAttachmentLog> _emailAttachmentLogRepository,
IOSSService oSSService,
IRepository<EmailReSendLog> _emailReSendLog,
IRepository<EmailRecipientLog> _emailRecipientLogRepository,
IOptionsMonitor<SystemEmailSendConfig> systemEmailConfig,
IMapper _mapper, IUserInfo _userInfo, IStringLocalizer _localizer): BaseService, IEmailLogService
{
private readonly SystemEmailSendConfig _systemEmailConfig = systemEmailConfig.CurrentValue;
/// <summary>
/// 获取邮件日志列表
/// </summary>
/// <param name="inQuery"></param>
/// <returns></returns>
[HttpPost]
public async Task<PageOutput<EmailLogView>> GetEmailLogList(EmailLogQuery inDto)
{
var emailFromName = await _trialRepository.Where(x=>x.Id==inDto.TrialId).Select(x=>x.EmailFromName).FirstOrDefaultAsync();
if (emailFromName.IsNullOrEmpty())
{
emailFromName = _systemEmailConfig.FromName;
}
var emailLogQueryable = _emailLogRepository
.Where(x=>x.SenderName== emailFromName)
.WhereIf(inDto.EmailStartDate.HasValue, x => x.EmailDate >= inDto.EmailStartDate.Value)
.WhereIf(inDto.EmailEndDate.HasValue, x => x.EmailDate <= inDto.EmailEndDate.Value)
.WhereIf(inDto.EmailStateEnum.HasValue, x => x.EmailStateEnum == inDto.EmailStateEnum.Value)
.WhereIf(inDto.CcRecipientName.IsNotNullOrEmpty(),x=>x.EmailRecipientLogList.Any(x=>x.RecipientTypeEnum==RecipientType.Cc&&x.RecipientName.Contains(inDto.CcRecipientName)))
.WhereIf(inDto.ToRecipientName.IsNotNullOrEmpty(), x => x.EmailRecipientLogList.Any(x => x.RecipientTypeEnum == RecipientType.To && x.RecipientName.Contains(inDto.ToRecipientName)))
.ProjectTo<EmailLogView>(_mapper.ConfigurationProvider);
var defalutSortArray = new string[] { nameof(EmailLogView.EmailDate) + " desc" };
var pageList = await emailLogQueryable.ToPagedListAsync(inDto, defalutSortArray);
return pageList;
}
/// <summary>
/// 获取重发信息
/// </summary>
/// <param name="inDto"></param>
/// <returns></returns>
[HttpPost]
public async Task<PageOutput<EmailLogView>> GetReSendEmail(GetReSendEmailInDto inDto)
{
var messageId = await _emailLogRepository.Where(x => x.Id == inDto.Id).Select(x => x.MessageId).FirstOrDefaultAsync();
var reSendMessagelist = await _emailReSendLog.Where(x => x.MainMailMessageId == messageId).Select(x => x.ReMailMessageId).ToListAsync();
var emailLogQueryable = _emailLogRepository
.Where(x => reSendMessagelist.Contains(x.MessageId))
.WhereIf(inDto.EmailStartDate.HasValue, x => x.EmailDate >= inDto.EmailStartDate.Value)
.WhereIf(inDto.EmailEndDate.HasValue, x => x.EmailDate <= inDto.EmailEndDate.Value)
.WhereIf(inDto.EmailStateEnum.HasValue, x => x.EmailStateEnum == inDto.EmailStateEnum.Value)
.WhereIf(inDto.CcRecipientName.IsNotNullOrEmpty(), x => x.EmailRecipientLogList.Any(x => x.RecipientTypeEnum == RecipientType.Cc && x.RecipientName.Contains(inDto.CcRecipientName)))
.WhereIf(inDto.ToRecipientName.IsNotNullOrEmpty(), x => x.EmailRecipientLogList.Any(x => x.RecipientTypeEnum == RecipientType.To && x.RecipientName.Contains(inDto.ToRecipientName)))
.ProjectTo<EmailLogView>(_mapper.ConfigurationProvider);
var defalutSortArray = new string[] { nameof(EmailLogView.EmailDate) + " desc" };
var pageList = await emailLogQueryable.ToPagedListAsync(inDto, defalutSortArray);
return pageList;
}
/// <summary>
/// 获取单条邮件日志详情
/// </summary>
/// <param name="inDto"></param>
/// <returns></returns>
[HttpPost]
public async Task<GetEmailInfoOutDto> GetEmailInfo(GetEmailInfoInDto inDto)
{
var emailInfo=await _emailLogRepository
.Where(x => x.Id == inDto.Id).Include(x=>x.EmailRecipientLogList)
.ProjectTo<GetEmailInfoOutDto>(_mapper.ConfigurationProvider).AsNoTracking()
.FirstNotNullAsync();
if (emailInfo.UniqueId.IsNotNullOrEmpty())
{
using (var client = new ImapClient())
{
try
{
await AuthenticateImap(client,inDto.TrialId);
var sentFolder = OpenSentFolder(client);
var uid = new UniqueId(uint.Parse(emailInfo.UniqueId));
var message = sentFolder.GetMessage(uid);
emailInfo.Content = message.HtmlBody ?? message.TextBody ?? string.Empty;
if (emailInfo.AttachmentList.Count == 0)
{
List< EmaliAttachmentInfo > attachmentInfos = new List<EmaliAttachmentInfo>();
foreach (var att in message.Attachments)
{
EmaliAttachmentInfo emaliAttachmentInfo = new EmaliAttachmentInfo();
emaliAttachmentInfo.AttachmentName = att.ContentDisposition?.FileName ?? att.ContentType.Name ?? "unknown";
// 2. 解码后的流直接上传,不落盘
if (att is MimePart part)
{
// 重要:每次上传新建一个独立流,否则迭代过程中流位置会乱
await using var decodeStream = new MemoryStream();
await part.Content.DecodeToAsync(decodeStream);
decodeStream.Position = 0;
emaliAttachmentInfo.AttachmentPath = await oSSService.UploadToOSSAsync(
fileStream: decodeStream,
oosFolderPath: $"EmailAttachment/{emailInfo.Id}", // OSS 虚拟目录
fileRealName: emaliAttachmentInfo.AttachmentName,
isFileNameAddGuid: true,
uploadInfo: new FileUploadRecordAddOrEdit() {TrialId= inDto.TrialId, BatchDataType = BatchDataType.EmailAttach }); // 让方法自己在文件名前加 Guid
attachmentInfos.Add(emaliAttachmentInfo);
}
}
List<EmailAttachmentLog> emailAttachmentLog = attachmentInfos.Select(x => new EmailAttachmentLog()
{
EmailLogId = emailInfo.Id.Value,
AttachmentName = x.AttachmentName,
AttachmentPath = x.AttachmentPath,
}).ToList();
await _emailAttachmentLogRepository.AddRangeAsync(emailAttachmentLog);
await _emailAttachmentLogRepository.SaveChangesAsync();
emailInfo.AttachmentList = attachmentInfos;
}
sentFolder.Close();
}
catch (Exception ex)
{
}
finally
{
client.Disconnect(true);
}
}
}
//var ossRelativePath = await oSSService.UploadToOSSAsync(fileStream, $"{trialId.ToString()}/InspectionUpload/DataReconciliation", realFileName);
return emailInfo;
}
/// <summary>
/// 重发邮件
/// </summary>
/// <param name="inDto"></param>
/// <returns></returns>
[HttpPost]
public async Task<IResponseOutput> ResendEmail(ResendEmailInDto inDto)
{
var emailInfo = await _emailLogRepository
.Where(x => x.Id == inDto.Id)
.ProjectTo<GetEmailInfoOutDto>(_mapper.ConfigurationProvider)
.FirstNotNullAsync();
if (emailInfo.UniqueId.IsNotNullOrEmpty())
{
using (var client = new ImapClient())
{
try
{
await AuthenticateImap(client,inDto.TrialId);
var sentFolder = OpenSentFolder(client);
var uid = new UniqueId(uint.Parse(emailInfo.UniqueId));
var message = sentFolder.GetMessage(uid);
emailInfo.Content = message.HtmlBody ?? message.TextBody ?? string.Empty;
sentFolder.Close();
}
catch (Exception ex)
{
}
finally
{
client.Disconnect(true);
}
}
}
var messageToSend = new MimeMessage();
// 发件地址
messageToSend.From.Add(new MailboxAddress(emailInfo.SenderName, emailInfo.SenderAddress));
foreach (var item in emailInfo.RecipientList.Where(x => x.RecipientTypeEnum == RecipientType.To))
{
messageToSend.To.Add(new MailboxAddress(item.RecipientName, item.RecipientAddress));
}
foreach (var item in emailInfo.RecipientList.Where(x => x.RecipientTypeEnum == RecipientType.Cc))
{
messageToSend.Cc.Add(new MailboxAddress(item.RecipientName, item.RecipientAddress));
}
messageToSend.Subject = emailInfo.EmailSubject;
var builder = new BodyBuilder();
builder.HtmlBody = emailInfo.Content;
messageToSend.Body = builder.ToMessageBody();
SystemEmailSendConfig sendConfig = new SystemEmailSendConfig()
{
};
if (inDto.TrialId != null)
{
sendConfig = await _trialRepository.Where(x => x.Id == inDto.TrialId.Value).Select(x => new SystemEmailSendConfig()
{
Host = _systemEmailConfig.Host,
Port = _systemEmailConfig.Port,
FromEmail = x.EmailFromEmail,
AuthorizationCode = x.EmailAuthorizationCode
}).FirstNotNullAsync();
}
else
{
sendConfig = _systemEmailConfig;
}
var msgid= await SendEmailHelper.SendEmailAsync(messageToSend, sendConfig);
await _emailReSendLog.AddAsync(new EmailReSendLog()
{
MainMailMessageId= emailInfo.MessageId,
ReMailMessageId= msgid
});
await _emailReSendLog.SaveChangesAsync();
await SynchronizationEmail(new SynchronizationEmailInDto() {
TrialId= inDto.TrialId
});
return ResponseOutput.Ok();
}
/// <summary>
/// 同步邮件
/// </summary>
/// <returns></returns>
[HttpPost]
public async Task<IResponseOutput> SynchronizationEmail(SynchronizationEmailInDto inDto)
{
var fromEmail = _systemEmailConfig.FromEmail;
if (inDto.TrialId != null)
{
fromEmail= await _trialRepository.Where(x => x.Id == inDto.TrialId.Value).Select(x => x.EmailFromEmail).FirstNotNullAsync();
}
var maxTime = await _emailLogRepository.Where(x=>x.SenderAddress== fromEmail).MaxAsync(t => t.EmailDate);
var startDate = maxTime ?? DateTime.MinValue;
List<EmailLog> emailList = new List<EmailLog>();
List<EmailRecipientLog> EmailRecipientLogList = new List<EmailRecipientLog>();
// 第一步:同步发件箱邮件
using (var client = new ImapClient())
{
try
{
await AuthenticateImap(client, inDto.TrialId);
var sentFolder = OpenSentFolder(client);
var searchQuery = SearchQuery.All.And(SearchQuery.DeliveredAfter(startDate));
var uids = sentFolder.Search(searchQuery).ToList();
foreach (var uid in uids)
{
try
{
var message = sentFolder.GetMessage(uid);
if (message.Date.DateTime <= startDate)
{
continue;
}
var emaillog = new EmailLog
{
Id = NewId.NextGuid(),
UniqueId = uid.ToString(),
MessageId = message.MessageId ?? string.Empty,
EmailSubject = message.Subject ?? string.Empty,
EmailDate = message.Date.DateTime,
EmailStateEnum = EmailState.Success,
};
var fromMailbox = message.From.Mailboxes.FirstOrDefault();
if (fromMailbox != null)
{
emaillog.SenderAddress = fromMailbox.Address;
emaillog.SenderName = fromMailbox.Name ?? string.Empty;
}
List<EmailRecipientLog> recipientLogs = new List<EmailRecipientLog>();
int sort = 0;
message.To.Mailboxes.ForEach(x =>
{
recipientLogs.Add(new EmailRecipientLog()
{
RecipientName = x.Name ?? string.Empty,
RecipientAddress = x.Address,
EmailLogId = emaillog.Id,
RecipientTypeEnum = RecipientType.To,
Sort = sort++,
});
});
sort = 0;
message.Cc.Mailboxes.ForEach(x =>
{
recipientLogs.Add(new EmailRecipientLog()
{
RecipientName = x.Name ?? string.Empty,
RecipientAddress = x.Address,
EmailLogId = emaillog.Id,
RecipientTypeEnum = RecipientType.Cc,
Sort = sort++,
});
});
emailList.Add(emaillog);
EmailRecipientLogList.AddRange(recipientLogs);
}
catch (Exception ex)
{
Console.WriteLine($"处理邮件 {uid} 失败: {ex.Message}");
}
}
sentFolder.Close();
}
catch (Exception ex)
{
Console.WriteLine($"同步发件箱邮件时出错: {ex.Message}");
}
finally
{
client.Disconnect(true);
}
}
// 保存同步的发件箱邮件
await _emailLogRepository.AddRangeAsync(emailList);
await _emailRecipientLogRepository.AddRangeAsync(EmailRecipientLogList);
await _emailLogRepository.SaveChangesAsync();
// 第二步:处理收件箱中的邮件发送失败通知
try
{
await ProcessInboxFailureNotifications(startDate,inDto.TrialId);
}
catch (Exception ex)
{
Console.WriteLine($"处理收件箱失败通知邮件时出错: {ex.Message}");
Console.WriteLine($"异常堆栈: {ex.StackTrace}");
// 即使处理失败通知邮件出错,也不影响整体同步操作的成功
// 记录警告但不中断主流程
}
return ResponseOutput.Ok();
}
private IMailFolder OpenSentFolder(ImapClient client)
{
IMailFolder folder = null;
try
{
folder = client.GetFolder(SpecialFolder.Sent);
}
catch { }
if (folder == null)
{
var candidates = new[] { "已发送", "已发送邮件", "Sent", "Sent Items", "[Gmail]/Sent Mail" };
foreach (var name in candidates)
{
try
{
var f = client.GetFolder(name);
if (f != null)
{
folder = f;
break;
}
}
catch { }
}
}
if (folder == null)
{
try
{
var personal = client.GetFolder(client.PersonalNamespaces.FirstOrDefault());
if (personal != null)
{
foreach (var sub in personal.GetSubfolders(false))
{
if (string.Equals(sub.FullName, "Sent", StringComparison.OrdinalIgnoreCase) ||
string.Equals(sub.FullName, "Sent Items", StringComparison.OrdinalIgnoreCase))
{
folder = sub;
break;
}
}
}
}
catch { }
}
if (folder == null)
throw new InvalidOperationException("未找到已发送文件夹");
folder.Open(FolderAccess.ReadOnly);
return folder;
}
private IMailFolder OpenInboxFolder(ImapClient client)
{
IMailFolder folder = null;
try
{
folder = client.GetFolder(SpecialFolder.All);
}
catch { }
if (folder == null)
{
var candidates = new[] { "收件箱", "Inbox" };
foreach (var name in candidates)
{
try
{
var f = client.GetFolder(name);
if (f != null)
{
folder = f;
break;
}
}
catch { }
}
}
if (folder == null)
{
try
{
var personal = client.GetFolder(client.PersonalNamespaces.FirstOrDefault());
if (personal != null)
{
foreach (var sub in personal.GetSubfolders(false))
{
if (string.Equals(sub.FullName, "Inbox", StringComparison.OrdinalIgnoreCase))
{
folder = sub;
break;
}
}
}
}
catch { }
}
if (folder == null)
throw new InvalidOperationException("未找到收件箱文件夹");
folder.Open(FolderAccess.ReadOnly);
return folder;
}
private async Task AuthenticateImap(ImapClient client, Guid? trialId)
{
try
{
EmailAuthorization authorization = new EmailAuthorization()
{
FromEmail = _systemEmailConfig.FromEmail,
AuthorizationCode = _systemEmailConfig.AuthorizationCode,
};
if (trialId != null)
{
authorization = await _trialRepository.Where(x => x.Id == trialId.Value).Select(x => new EmailAuthorization()
{
AuthorizationCode = x.EmailAuthorizationCode,
FromEmail = x.EmailFromEmail
}).FirstNotNullAsync();
}
client.Connect(_systemEmailConfig.Imap, _systemEmailConfig.ImapPort, SecureSocketOptions.SslOnConnect);
client.Authenticate(authorization.FromEmail, authorization.AuthorizationCode);
}
catch (AuthenticationException)
{
//if (_systemEmailConfig.UseOAuth2 && _systemEmailConfig.OAuth2AccessToken.IsNotNullOrEmpty())
//{
// var sasl = new SaslMechanismOAuth2(_systemEmailConfig.FromEmail, _systemEmailConfig.OAuth2AccessToken);
// client.Authenticate(sasl);
// return;
//}
//if (_systemEmailConfig.OAuth2AccessToken.IsNullOrEmpty())
//{
// var token = AcquireOAuth2TokenByPassword();
// if (token.IsNotNullOrEmpty())
// {
// _systemEmailConfig.OAuth2AccessToken = token;
// var sasl = new SaslMechanismOAuth2(_systemEmailConfig.FromEmail, token);
// client.Authenticate(sasl);
// return;
// }
//}
throw;
}
}
/// <summary>
/// 处理收件箱中的邮件发送失败通知
/// </summary>
/// <returns></returns>
public async Task ProcessInboxFailureNotifications(DateTime startTime, Guid? trialId)
{
using (var client = new ImapClient())
{
try
{
await AuthenticateImap(client, trialId);
var inboxFolder = OpenInboxFolder(client);
// 搜索可能包含失败通知的邮件
// 通常失败通知邮件会包含"失败"、"错误"、"undeliverable"、"delivery failed"等关键词
var searchQuery = SearchQuery.All.Or(SearchQuery.SubjectContains("失败"))
.Or(SearchQuery.SubjectContains("错误"))
.Or(SearchQuery.SubjectContains("undeliverable"))
.Or(SearchQuery.SubjectContains("delivery failed"))
.Or(SearchQuery.SubjectContains("Delivery Status Notification"))
.Or(SearchQuery.BodyContains("失败"))
.Or(SearchQuery.BodyContains("错误"))
.Or(SearchQuery.BodyContains("undeliverable"))
.Or(SearchQuery.BodyContains("delivery failed")).And(SearchQuery.DeliveredAfter(startTime));
var uids = inboxFolder.Search(searchQuery).ToList();
var processedCount = 0;
var updatedCount = 0;
foreach (var uid in uids)
{
try
{
var message = inboxFolder.GetMessage(uid);
// 尝试从邮件内容中提取原始邮件的Message-Id或相关信息
var originalMessageId = ExtractOriginalMessageId(message);
if (!string.IsNullOrEmpty(originalMessageId))
{
// 查找对应的EmailLog记录
var emailLog = await _emailLogRepository
.Where(x => x.MessageId == originalMessageId)
.FirstOrDefaultAsync();
await _emailLogRepository.BatchUpdateNoTrackingAsync(x => x.MessageId == originalMessageId, x => new EmailLog()
{
EmailStateEnum = EmailState.Error,
ErrorInfo = message.TextBody
});
updatedCount++;
}
processedCount++;
}
catch (Exception ex)
{
}
}
inboxFolder.Close();
}
catch (Exception ex)
{
Console.WriteLine($"连接收件箱处理失败通知邮件时出错: {ex.Message}");
Console.WriteLine($"异常堆栈: {ex.StackTrace}");
}
finally
{
try
{
client.Disconnect(true);
}
catch (Exception ex)
{
Console.WriteLine($"断开IMAP客户端连接时出错: {ex.Message}");
}
}
}
}
/// <summary>
/// 从失败通知邮件中提取原始邮件的Message-Id
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
private string ExtractOriginalMessageId(MimeMessage message)
{
// 首先检查邮件头中是否有原始Message-Id
var originalMessageId = message.Headers["Original-Message-ID"] ??
message.Headers["X-Original-Message-ID"] ??
message.Headers["In-Reply-To"];
if (!string.IsNullOrEmpty(originalMessageId))
{
// 清理Message-Id格式(移除尖括号)
originalMessageId = originalMessageId.Trim('<', '>');
return originalMessageId;
}
// 检查邮件附件中是否包含.eml文件(这是最常见的失败通知格式)
foreach (var attachment in message.Attachments)
{
if (attachment is MessagePart messagePart)
{
// 直接获取嵌入的邮件消息
var originalMessageIdFromAttachment = messagePart.Message?.MessageId;
if (!string.IsNullOrEmpty(originalMessageIdFromAttachment))
{
return originalMessageIdFromAttachment.Trim('<', '>');
}
}
else if (attachment is MimePart mimePart)
{
// 检查文件扩展名是否为.eml
var fileName = mimePart.FileName ?? mimePart.ContentType.Name;
if (!string.IsNullOrEmpty(fileName) &&
(fileName.EndsWith(".eml", StringComparison.OrdinalIgnoreCase) ||
mimePart.ContentType.MimeType.Equals("message/rfc822", StringComparison.OrdinalIgnoreCase)))
{
try
{
// 解析.eml附件内容
using var memoryStream = new MemoryStream();
mimePart.Content.DecodeTo(memoryStream);
memoryStream.Position = 0;
var originalMessage = MimeMessage.Load(memoryStream);
if (!string.IsNullOrEmpty(originalMessage?.MessageId))
{
return originalMessage.MessageId.Trim('<', '>');
}
}
catch (Exception ex)
{
Console.WriteLine($"解析.eml附件时出错: {ex.Message}");
}
}
}
}
// 如果邮件头和附件中都没有,尝试从邮件正文中提取
var content = message.HtmlBody ?? message.TextBody ?? string.Empty;
// 尝试匹配常见的Message-Id格式
var messageIdPattern = @"(?:Message-ID|Message-Id|message-id):\s*<?([^>\s]+)>?";
var match = System.Text.RegularExpressions.Regex.Match(content, messageIdPattern, System.Text.RegularExpressions.RegexOptions.IgnoreCase);
if (match.Success)
{
return match.Groups[1].Value;
}
// 尝试从邮件引用部分提取
var referencePattern = @"^>.*Message-ID:\s*<?([^>\s]+)>?";
var matches = System.Text.RegularExpressions.Regex.Matches(content, referencePattern, System.Text.RegularExpressions.RegexOptions.Multiline | System.Text.RegularExpressions.RegexOptions.IgnoreCase);
if (matches.Count > 0)
{
return matches[0].Groups[1].Value;
}
return null;
}
public async Task<IResponseOutput> AddOrUpdateEmailLog(EmailLogAddOrEdit addOrEditEmailLog)
{
var entity = await _emailLogRepository.InsertOrUpdateAsync(addOrEditEmailLog, true);
return ResponseOutput.Ok(entity.Id.ToString());
}
[HttpDelete("{emailLogId:guid}")]
public async Task<IResponseOutput> DeleteEmailLog(Guid emailLogId)
{
var success = await _emailLogRepository.DeleteFromQueryAsync(t => t.Id == emailLogId,true);
return ResponseOutput.Ok();
}
}
@@ -4,9 +4,18 @@
// 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
//--------------------------------------------------------------------
using DocumentFormat.OpenXml.Spreadsheet;
using IRaCIS.Core.Application.Helper;
using IRaCIS.Core.Domain.Share;
using MailKit;
using MailKit.Net.Imap;
using MailKit.Search;
using MailKit.Security;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using NPOI.SS.Formula.Functions;
using System.Text.RegularExpressions;
@@ -17,8 +26,75 @@ namespace IRaCIS.Core.Application.Contracts
/// </summary>
[ApiExplorerSettings(GroupName = "Common")]
public class EmailNoticeConfigService(IRepository<EmailNoticeConfig> _emailNoticeConfigrepository,
IRepository<EmailNoticeUserType> _emailNoticeUserTypeRepository, IMapper _mapper, IUserInfo _userInfo, IStringLocalizer _localizer) : BaseService, IEmailNoticeConfigService
IRepository<EmailNoticeUserType> _emailNoticeUserTypeRepository,
IOptionsMonitor<SystemEmailSendConfig> systemEmailConfig,
IMapper _mapper, IUserInfo _userInfo, IStringLocalizer _localizer) : BaseService, IEmailNoticeConfigService
{
private readonly SystemEmailSendConfig _systemEmailConfig = systemEmailConfig.CurrentValue;
/// <summary>
/// 获取邮件列表
/// </summary>
/// <returns></returns>
[HttpPost]
public async Task<object> GetEmailList()
{
List<IMessageSummary> emailList = new List<IMessageSummary>();
using (var client = new ImapClient())
{
try
{
// 连接阿里邮箱 IMAP 服务器(使用 SSL)
client.Connect(_systemEmailConfig.Imap, 993, SecureSocketOptions.SslOnConnect);
// 登录
client.Authenticate(_systemEmailConfig.FromEmail, _systemEmailConfig.AuthorizationCode);
// 3. 获取发件箱文件夹 - 使用你找到的“已发送”
var sentFolder = client.GetFolder("已发送");
sentFolder.Open(FolderAccess.ReadOnly);
// 4. 搜索所有邮件(你可以在这里添加更精确的搜索条件)
var uids = sentFolder.Search(SearchQuery.All);
Console.WriteLine($"找到 {uids.Count} 封已发送邮件");
// 5. 遍历并处理邮件(示例中处理前10封)
foreach (var uid in uids.Take(10))
{
var message = sentFolder.GetMessage(uid);
// 输出邮件基本信息
Console.WriteLine($"主题: {message.Subject}");
Console.WriteLine($"收件人: {string.Join(", ", message.To.Mailboxes.Select(m => m.Address))}");
Console.WriteLine($"日期: {message.Date.LocalDateTime:yyyy-MM-dd HH:mm:ss}");
Console.WriteLine($"发件人: {message.From}");
Console.WriteLine("----------------------------------");
}
sentFolder.Close();
}
catch (Exception ex)
{
Console.WriteLine($"操作失败: {ex.Message}");
}
finally
{
client.Disconnect(true);
}
}
return true;
}
[HttpPost]
public async Task<PageOutput<EmailNoticeConfigView>> GetEmailNoticeConfigList(EmailNoticeConfigQuery inQuery)
@@ -0,0 +1,533 @@
using IRaCIS.Core.Application.Contracts;
using IRaCIS.Core.Application.Helper;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace IRaCIS.Core.Application.Service.Common;
public class TumorCommonQustionInfo
{
//问题标识,肿瘤评估用于区分是什么问题
public QuestionType? QuestionType { get; set; }
public OptionType OptionTypeEnum { get; set; }
public Guid QuestionId { get; set; }
public string QuestionName { get; set; }
public string QuestionValue { get; set; }
public string TranslateDicName { get; set; }
public ValueUnit? Unit { get; set; }
public ValueOfType? ValueType { get; set; }
}
public class TumorLessionInfo
{
public Guid Id { get; set; }
public Guid? OrganInfoId { get; set; }
//病灶编号
public string LessionCode { get; set; }
public LesionType? LessionType { get; set; }
public Guid? SplitRowId { get; set; }
public string? DicomModality { get; set; }
public string? NoneDicomModality { get; set; }
//病灶答案
public List<TumorLessionAnswerInfo> LessionAnswerList { get; set; }
}
public class TumorLessionAnswerInfo
{
public QuestionMark? QuestionMark { get; set; }
public OptionType OptionTypeEnum { get; set; }
//病灶Id
public Guid RowId { get; set; }
//如果是4 就取CustomUnit 否则就是字典翻译
[Comment("单位")]
public ValueUnit? Unit { get; set; }
public int ShowOrder { get; set; }
public Guid TableQuesionId { get; set; }
public string QuestionName { get; set; }
public string QuestionValue { get; set; }
public string TranslateDicName { get; set; }
}
public class TumorGlobalQuestionAnserInfo
{
[Comment("原任务ID")]
public Guid TaskId { get; set; }
public GlobalAnswerType GlobalAnswerType { get; set; }
[Comment("问题答案")]
public string Answer { get; set; } = string.Empty;
}
public class TumorExportBaseModel : TU_TR_RSBaseModel
{
public List<TumorLessionInfo> LesionList { get; set; } = new List<TumorLessionInfo>();
public List<TumorCommonQustionInfo> QuestionAnswerList { get; set; }
public List<TumorGlobalQuestionAnserInfo> GlobalResultList { get; set; }
#region
public DateTime? JudgeSignTime { get; set; }
public Guid? SourceSubjectVisitId { get; set; }
public List<decimal> SubjectCriterionReadingPeriodVisitNumList { get; set; }
public decimal VisitTaskNum { get; set; }
public ReadingTaskState ReadingTaskState { get; set; }
public ReadingCategory ReadingCategory { get; set; }
//裁判结果选择的访视或者全局任务Id
public Arm? JudgeArmEnum { get; set; }
//在当前访视触发裁判,或者在截止日期小于等于当前访视的阅片期触发裁判
[DictionaryTranslateAttribute("YesOrNoAudit")]
public bool? IsTrigerJudge { get; set; }
//(如果是访视点裁判,则仅在所选阅片人对应访视 显示;如果是阅片期裁判,则在所选阅片人 阅片期内的所有访视 显示此原因)
public string JudgeNote { get; set; } = string.Empty;
public string VisitNote { get; set; }
#endregion
#region
public List<OncologyExportInfo> OncologyResultList { get; set; }
//public string OncologyUserName { get; set; }
#endregion
}
public class OncologyExportInfo
{
public decimal? VisitTaskNum { get; set; }
public string VisitName { get; set; }
public string OncologyResult { get; set; } = string.Empty;
public string OncologyReason { get; set; } = string.Empty;
}
public class TU_TR_RSBaseModel
{
/// <summary>
/// 方案编号 STUDYID
/// </summary>
public string ResearchProgramNo { get; set; }
/// <summary>
/// 域 DOMAIN TU TR RS
/// </summary>
public string Domain { get; set; }
/// <summary>
/// 取值类型 TUSPID TRSPID RSSPID
/// </summary>
public string ValueType { get; set; }
/// <summary>
/// 受试者编号 USUBJID 实际展示TrialSiteSubjectCode
/// </summary>
public string SubjectCode { get; set; }
/// <summary>
/// 供应商 TUNAM (Extensive Imaging)
/// </summary>
public string Vendor { get; set; } = "Extensive Imaging";
/// <summary>
/// 阅片人 TUEVAL TREVAL RSEVAL
/// </summary>
public string UserName { get; set; }
/// <summary>
/// 阅片人标识 TUEVALID TREVALID RSEVALID
/// </summary>
public Arm ArmEnum { get; set; }
/// <summary>
/// 访视编号 VISITNUM
/// </summary>
public decimal? VisitNum { get; set; }
/// <summary>
/// 访视名称 VISIT
/// </summary>
public string? VisitName { get; set; }
/// <summary>
/// 拍片日期 TUDTC TRDTC RSDTC
/// </summary>
public DateTime? LatestScanDate { get; set; }
public DateTime? EarliestScanDate { get; set; }
/// <summary>
/// eCRF标识 TUREFID TRREFID RSREFID
/// </summary>
public Guid VisitTaskId { get; set; }
#region
/// <summary>
/// RSCAT 阅片标准
/// </summary>
public string CriterionName { get; set; }
/// <summary>
/// RSACPTFL 裁定标记 TUACPTFL
/// </summary>
//裁判选择标记
//根据裁判的任务结果 设置访视任务的这个字段 该字段表示 裁判认同该任务的结果
public bool? IsJudgeSelect { get; set; }
#endregion
public DateTime? SignTime { get; set; }
public string TaskName { get; set; }
#region
public string IsJudgeSelectStr => IsJudgeSelect == true ? "Y" : "";
public bool IsTargetPD { get; set; } = false;
public bool IsOverallResponsePD { get; set; } = false;
//TR表 靶病灶PD了,访视层级的都是最早拍片日期 RS表 整体肿瘤评估PD了,那么疗效评估的拍片日期都给最早的
public bool IsPD => Domain == "TR" ? IsTargetPD : IsOverallResponsePD;
public string ArmEnumStr { get; set; }
public string LatestScanDateStr
{
get
{
var date = IsPD ? EarliestScanDate : LatestScanDate;
return date?.ToString("yyyy-MM-dd") ?? "";
}
}
public string TrialSiteSubjectCode => ResearchProgramNo + SubjectCode;
#endregion
}
public class TU_Export : TU_TR_RSBaseModel
{
///// <summary>
///// 取值类型 TUSPID
///// </summary>
//public string TUValueType { get; set; }
/// <summary>
/// 序号 TUSEQ (同一个访视,所有阅片人选择病灶给个顺序号)
/// </summary>
public int No { get; set; }
/// <summary>
/// 链接ID TULNKID (阅片人角色_病灶编号)不同访视可以重复
/// </summary>
public string ARM_TumorNo { get; set; }
/// <summary>
/// 肿瘤识别简称 TUTESTCD
/// </summary>
public string TumorIdentificationSimple { get; set; }
/// <summary>
/// 肿瘤识别全称 TUTEST
/// </summary>
public string TumorIdentificationFullName { get; set; }
/// <summary>
/// 肿瘤鉴定结果 TUORRES
/// </summary>
public string TumorIdentificationResult { get; set; }
/// <summary>
/// 肿瘤识别结果类型 TUSTRESC
/// </summary>
public string TumorIdentificationResultType { get; set; }
/// <summary>
/// 部位 TULOC (对应病灶表的部位,需要国际化)
/// </summary>
public string BodyPart { get; set; }
/// <summary>
/// 鉴定方法 TUMETHOD (Modality?)
/// </summary>
public string IdentificationMethod { get; set; }
///// <summary>
///// 裁定标记 TUACPTFL
///// </summary>
//public bool? IsJudgeSelect { get; set; }
/// <summary>
/// 部位描述 LOCTEXT
/// </summary>
public string BodyPartDes { get; set; }
}
public class TR_Export : TU_TR_RSBaseModel
{
///// <summary>
///// 取值类型 TRSPID
///// </summary>
//public string TRValueType { get; set; }
/// <summary>
/// 每个subject 按照顺序编号 TRSEQ
/// </summary>
public int TRSEQ { get; set; }
/// <summary>
///TRGRPID 组ID 对应TU表肿瘤鉴定结果 TumorIdentificationResult
/// </summary>
public string TRGRPID { get; set; }
/// <summary>
/// TRLNKID 链接ID 对应TU表的链接ID TumorNo(阅片人角色_病灶编号)
/// </summary>
public string ARM_TumorNo { get; set; }
/// <summary>
///TRLNKGRP 链接组 ARM-任务名(访视名) 对应RS的链接组
/// </summary>
public string ARM_VisitName { get; set; }
/// <summary>
/// 肿瘤评估简称 TRTESTCD
/// </summary>
public string TumorAssessmentSimpleName { get; set; }
/// <summary>
/// 肿瘤评估全称 TRTEST
/// </summary>
public string TumorAssessmentFullName { get; set; }
/// <summary>
/// 原始测量 TRORRES
/// </summary>
public string OriginalMeasurements { get; set; }
/// <summary>
/// 原始单位 TRORRESU
/// </summary>
public string OriginalUnit { get; set; }
/// <summary>
/// 标准结果(字符) TRSTRESC
/// </summary>
public string StandardResult_Character => OriginalMeasurements;
/// <summary>
/// 标准结果(数值) TRORRESU
/// </summary>
public string StandardResult_Numeric => double.TryParse(OriginalMeasurements, out _) ||
(OriginalMeasurements?.EndsWith("%") == true &&
double.TryParse(OriginalMeasurements.TrimEnd('%'), out _))
? OriginalMeasurements
: "";
/// <summary>
/// 标准单位 TRSTRESU
/// </summary>
public string StandardUnit => OriginalUnit;
/// <summary>
/// 完成状态 TRSTAT
/// </summary>
public string CompletionStatus { get; set; }
/// <summary>
/// 完成状态 TRMETHOD
/// </summary>
public string IdentificationMethod { get; set; }
/// <summary>
/// 无法测量原因 TRREASND
/// </summary>
public string NotMeasuredReason { get; set; }
}
public class RS_Export : TU_TR_RSBaseModel
{
/// <summary>
/// RSSEQ 按照subject 的数据顺序编号
/// </summary>
public int RSSEQ { get; set; }
/// <summary>
/// RSLNKGRP 链接组 ARM_任务名(访视名)
/// </summary>
public string ARM_VisitName { get; set; }
/// <summary>
/// RSTESTCD 疗效评估简称
/// </summary>
public string EfficacyEvaluationSimpleName { get; set; }
/// <summary>
/// RSTEST 疗效评估全称
/// </summary>
public string EfficacyEvaluationName { get; set; }
/// <summary>
/// RSORRES 响应评估原始结果
/// </summary>
public string RespondEfficacyAssessment { get; set; }
/// <summary>
/// RSSTRESC 标准疗效评估
/// </summary>
public string StandardEfficacyAssessment => RespondEfficacyAssessment;
/// <summary>
/// RSSTAT 完成状态
/// </summary>
public string CompletionStatus { get; set; }
/// <summary>
/// RSREASND 无法评估原因
/// </summary>
public string NotAssessmentReason { get; set; }
///// <summary>
///// 裁定标记 RSACPTFL
///// </summary>
//public bool? IsJudgeSelect { get; set; }
/// <summary>
/// REASASM 评估原因
/// </summary>
public string AssessmentReason { get; set; }
/// <summary>
/// REASOVR 重新评估原因
/// </summary>
public string ReAssessmentReason { get; set; }
/// <summary>
/// REASUPD 更新评估原因
/// </summary>
public string UpdateAssessmentReason { get; set; }
[JsonIgnore]
public bool? IsOveralResponse { get; set; }
}
public class CO_Export : TU_TR_RSBaseModel
{
/// <summary>
/// 关联域 RS:(访视点备注) 空:裁判选择原因
/// </summary>
public string RDOMAIN { get; set; }
/// <summary>
/// COSEQ 序号
/// </summary>
public int COSEQ { get; set; }
/// <summary>
/// IDVAR 标识变量 RSSEQ 空:裁判选择原因
/// </summary>
public string IdentificationVariable { get; set; }
/// <summary>
/// 标识 IDVARVAL RSSEQ具体的值 空:裁判选择原因
/// </summary>
public string Identification { get; set; }
/// <summary>
/// COREF 备注引用
/// </summary>
public string RemarksQuote { get; set; }
/// <summary>
/// 备注 COVAL
/// </summary>
public string Remarks { get; set; }
/// <summary>
/// 裁决日期 CODTC
/// </summary>
public string CODTC { get; set; }
}
@@ -22,6 +22,8 @@ using System.ComponentModel.Design;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using static IRaCIS.Core.Application.Service.ExcelExportHelper;
using IDictionaryService = IRaCIS.Application.Interfaces.IDictionaryService;
using TrialIdentityUser = IRaCIS.Core.Domain.Models.TrialIdentityUser;
@@ -32,6 +34,8 @@ namespace IRaCIS.Core.Application.Service.Common
[ApiExplorerSettings(GroupName = "Common")]
public class ExcelExportService(IRepository<TrialUserRole> _trialUserRoleRepository,
IRepository<VisitTask> _visitTaskRepository,
IRepository<Dictionary> _dictionaryRepository,
IRepository<Internationalization> _internationalizationRepository,
IRepository<ReadingQuestionCriterionTrial> _readingQuestionCriterionTrialRepository,
IRepository<SystemDocNeedConfirmedUserType> _systemDocNeedConfirmedUserTypeRepository,
IRepository<DicomStudy> _dicomStudyRepository,
@@ -135,7 +139,7 @@ namespace IRaCIS.Core.Application.Service.Common
QuestionAnswerList = g.Select(t => new QCQuestionAnswerExport() { Answer = t.Answer, QuestionName = t.QuesitonName, ShowOrder = t.ShowOrder, QuestionId = t.QuestionId }).OrderBy(t => t.ShowOrder).ToList()
}).OrderBy(t => t.TrialSiteCode).ThenBy(t => t.SubjectCode).ThenBy(t => t.VisitNum).ThenBy(t=>t.CurrentQCEnum).ThenBy(t=>t.AuditTime).ToList();
}).OrderBy(t => t.TrialSiteCode).ThenBy(t => t.SubjectCode).ThenBy(t => t.VisitNum).ThenBy(t => t.CurrentQCEnum).ThenBy(t => t.AuditTime).ToList();
var exportInfo = (await _trialRepository.Where(t => t.Id == inQuery.TrialId).IgnoreQueryFilters().ProjectTo<ExcelExportInfo>(_mapper.ConfigurationProvider).FirstOrDefaultAsync()).IfNullThrowException();
@@ -467,7 +471,10 @@ namespace IRaCIS.Core.Application.Service.Common
.WhereIf(!string.IsNullOrEmpty(inQuery.Code), o => o.TrialCode.Contains(inQuery.Code))
.WhereIf(!string.IsNullOrEmpty(inQuery.ResearchProgramNo), o => o.ResearchProgramNo.Contains(inQuery.ResearchProgramNo))
.WhereIf(!string.IsNullOrWhiteSpace(inQuery.ExperimentName), o => o.ExperimentName.Contains(inQuery.ExperimentName))
.WhereIf(_userInfo.UserTypeEnumInt != (int)UserTypeEnum.SuperAdmin && _userInfo.UserTypeEnumInt != (int)UserTypeEnum.Admin && _userInfo.UserTypeEnumInt != (int)UserTypeEnum.OP, t => t.TrialIdentityUserList.Any(t => t.IdentityUserId == _userInfo.IdentityUserId && t.IsDeleted == false) && t.IsDeleted == false)
.WhereIf(_userInfo.UserTypeEnumInt != (int)UserTypeEnum.SuperAdmin && _userInfo.UserTypeEnumInt != (int)UserTypeEnum.Admin && _userInfo.UserTypeEnumInt != (int)UserTypeEnum.OP,
t => t.TrialIdentityUserList.Any(t => t.IdentityUserId == _userInfo.IdentityUserId && t.IsDeleted == false
&& t.TrialUserRoleList.Any(t => t.UserId == _userInfo.UserRoleId && t.IsDeleted == false))
&& t.IsDeleted == false)
.WhereIf(inQuery.CriterionType != null, o => o.TrialReadingCriterionList.Any(t => t.CriterionType == inQuery.CriterionType && t.IsSigned && t.IsConfirm))
.WhereIf(!string.IsNullOrEmpty(inQuery.PM_EMail), o => o.TrialUserRoleList.Any(t => t.UserRole.IdentityUser.EMail.Contains(inQuery.PM_EMail) && (t.UserRole.UserTypeEnum == UserTypeEnum.ProjectManager || t.UserRole.UserTypeEnum == UserTypeEnum.APM)))
.Select(t => new TrialToBeDoneDto()
@@ -977,7 +984,7 @@ namespace IRaCIS.Core.Application.Service.Common
var query = _subjectVisitRepository.Where(x => x.TrialId == inQuery.TrialId)
.Where(t => t.Subject.IsSubjectQuit == false || t.SubmitState >= SubmitStateEnum.ToSubmit)
.WhereIf(inQuery.SubjectStatus != null, t => t.Subject.Status == inQuery.SubjectStatus)
.Where(t => t.Subject.FinalSubjectVisitId != null ? t.VisitNum <= t.Subject.FinalSubjectVisit.VisitNum : true)
.WhereIf(inQuery.TrialSiteId != null, t => t.TrialSiteId == inQuery.TrialSiteId)
.WhereIf(inQuery.SubjectId != null, t => t.Subject.Id == inQuery.SubjectId)
@@ -1027,7 +1034,7 @@ namespace IRaCIS.Core.Application.Service.Common
var query = _subjectVisitRepository.Where(x => x.TrialId == inQuery.TrialId)
.Where(t => t.Subject.IsSubjectQuit == false || t.AuditState > AuditStateEnum.ToAudit)
.WhereIf(inQuery.SubjectStatus != null, t => t.Subject.Status == inQuery.SubjectStatus)
.WhereIf(inQuery.VisitId != null, t => t.Id == inQuery.VisitId)
.WhereIf(inQuery.CurrentActionUserId != null, t => t.CurrentActionUserId == inQuery.CurrentActionUserId)
.WhereIf(inQuery.ChallengeState != null, t => t.ChallengeState == inQuery.ChallengeState)
@@ -1758,6 +1765,80 @@ namespace IRaCIS.Core.Application.Service.Common
var list = taskMedicalReviewQueryable.OrderBy(t => t.TrialSiteCode).ThenBy(t => t.SubjectCode).ThenBy(t => t.VisitTaskNum).ToList();
#region by he wen tao
Dictionary<string, string> i18NKeys = new Dictionary<string, string>()
{
{ "Msg1","trials:medicalFeedback:message:msg1"},
{ "Msg2","trials:medicalFeedback:message:msg2"},
{ "Msg3","trials:medicalFeedback:message:msg3"},
{ "Msg4", "trials:medicalFeedback:message:msg4"},
{ "Msg5", "trials:medicalFeedback:message:msg5"},
{ "CloseReasonEnum", "trials:medicalFeedback:title:closeReasonEnum"},
{ "IsEndorse", "trials:medicalFeedback:title:isEndorse"},
{ "Reason", "trials:medicalFeedback:title:reason"},
{ "IsRequestReread", "trials:medicalFeedback:title:isRequestReread"},
};
var i18Values = i18NKeys.Select(x => x.Value).ToList();
var i18nList = await _internationalizationRepository.Where(x => i18Values.Contains(x.Code))
.Select(x => new
{
Code = x.Code,
Value = _userInfo.IsEn_Us ? x.Value : x.ValueCN
}).ToListAsync();
List<string> dictionaryCodeList = new List<string>()
{
"AuditAdvice",
"MedicalDialogCloseEnum",
"MedicalReviewDoctorUserIdea",
"YesOrNo",
};
var dictionadParents = await _dictionaryRepository.Where(x => dictionaryCodeList.Contains(x.Parent.Code))
.Select(x => new
{
ParentCode = x.Parent.Code,
Code = x.Code,
Value = _userInfo.IsEn_Us ? x.Value : x.ValueCN
}).ToListAsync();
JointMedicalReviewI18n i18N = new JointMedicalReviewI18n() { };
foreach (var kv in i18NKeys)
{
PropertyInfo? pi = i18N.GetType().GetProperty(kv.Key);
if (pi != null && pi.CanWrite)
{
var value = i18nList.Where(x => x.Code == kv.Value).Select(x => x.Value).FirstOrDefault() ?? string.Empty;
object safeValue = Convert.ChangeType(value, pi.PropertyType);
pi.SetValue(i18N, safeValue);
}
}
foreach (var item in dictionaryCodeList)
{
PropertyInfo? pi = i18N.GetType().GetProperty(item);
if (pi != null && pi.CanWrite)
{
var value = dictionadParents.Where(x => x.ParentCode == item).ToDictionary(x => x.Code, x => x.Value);
object safeValue = Convert.ChangeType(value, pi.PropertyType);
pi.SetValue(i18N, safeValue);
}
}
foreach (var item in list)
{
foreach (var dialog in item.DialogList)
{
dialog.ResultContent = JointMedicalReviewDialog(dialog, i18N);
}
}
#endregion
var exportInfo = (await _trialRepository.Where(t => t.Id == inQuery.TrialId).IgnoreQueryFilters().ProjectTo<ExcelExportInfo>(_mapper.ConfigurationProvider).FirstOrDefaultAsync()).IfNullThrowException();
exportInfo.List = ExportExcelConverterDate.ConvertToClientTimeInObject(list, _userInfo.TimeZoneId);
@@ -1767,6 +1848,88 @@ namespace IRaCIS.Core.Application.Service.Common
return await ExcelExportHelper.DataExportAsync(StaticData.Export.TrialMedicalReviewList_Export, exportInfo, $"{exportInfo.ResearchProgramNo}", _commonDocumentRepository, _hostEnvironment, _dictionaryService, typeof(TaskMedicalReviewExportDto));
}
/// <summary>
/// 获取医学审核对话拼接内容
/// </summary>
/// <param name="record"></param>
/// <param name="i18NAndDic"></param>
/// <returns></returns>
private string JointMedicalReviewDialog(GetMedicalReviewDialogOutDto record, JointMedicalReviewI18n i18NAndDic)
{
StringBuilder str = new StringBuilder() { };
var userTypes = new List<int>() { 14, 30 };
// 这里userTypes.Contains(record.UserTypeEnumInt) 可以提出来 但是还是按照前端写吧 免得不好对照
// v-if="[14, 30].includes(record.UserTypeEnumInt) && record.Questioning
if (userTypes.Contains(record.UserTypeEnumInt) && record.Questioning.IsNotNullOrEmpty())
{
// <!-- 您好,根据医学审核反馈,该阅片任务的评估有如下问题需要您确认或澄清: -->
str.AppendLine(i18NAndDic.Msg1);
str.AppendLine(record.Questioning);
if (record.FileList.Count() > 0)
{
str.AppendLine(i18NAndDic.Msg2);
record.FileList.ForEach(x =>
{
str.AppendLine(x.FileName);
});
}
str.AppendLine(i18NAndDic.Msg3);
str.AppendLine(i18NAndDic.AuditAdvice[record.AuditAdviceEnum.GetEnumInt()]);
str.AppendLine(i18NAndDic.Msg4);
}
//v-if="[14, 30].includes(record.UserTypeEnumInt) && record.MedicalDialogCloseEnum!== null"
if (userTypes.Contains(record.UserTypeEnumInt) && record.MedicalDialogCloseEnum != null)
{
str.AppendLine(i18NAndDic.CloseReasonEnum + i18NAndDic.MedicalDialogCloseEnum[record.MedicalDialogCloseEnum.Value.GetEnumInt()]);
}
// v-if="[14, 30].includes(record.UserTypeEnumInt) && record.Content
if (userTypes.Contains(record.UserTypeEnumInt) && record.Content.IsNotNullOrEmpty())
{
str.AppendLine(record.Content);
}
// v-if="[13].includes(record.UserTypeEnumInt)"
if (13 == record.UserTypeEnumInt)
{
str.AppendLine(i18NAndDic.IsEndorse + i18NAndDic.MedicalReviewDoctorUserIdea[record.DoctorUserIdeaEnum.GetEnumInt()]);
// v-if="record.DoctorUserIdeaEnum===2"
if ((int)record.DoctorUserIdeaEnum == 2)
{
str.AppendLine(i18NAndDic.Reason + record.DisagreeReason);
}
// record.MedicalDialogCloseEnum!== null
if (record.MedicalDialogCloseEnum != null)
{
str.AppendLine(i18NAndDic.IsRequestReread + i18NAndDic.YesOrNo[record.IsApplyHeavyReading.ToString()]);
}
// v-if="record.FileList && record.FileList.length > 0"
if (record.FileList.Count() > 0)
{
record.FileList.ForEach(x =>
{
str.AppendLine(x.FileName);
});
}
}
return str.ToString();
}
/// <summary>
/// 影像下载记录表
@@ -2597,13 +2760,23 @@ namespace IRaCIS.Core.Application.Service.Common
list.Add(new ExportDocumentDes() { Code = StaticData.Export.ReadingLession_Export, ExportCatogory = ExportResult.DetailedTableOfLesions });
}
if (criterion.CriterionType == CriterionType.RECIST1Point1 || criterion.CriterionType == CriterionType.Lugano2014)
{
list.Add(new ExportDocumentDes() { Code = StaticData.Export.TumorCDISC_Export, ExportCatogory = ExportResult.TumorCDISC_Export });
}
if (criterion.CriterionType == CriterionType.OCT)
{
list.Add(new ExportDocumentDes() { Code = StaticData.Export.OCT_ReadingLession_Export, ExportCatogory = ExportResult.OCT_ReadingLession_Export });
list.Add(new ExportDocumentDes() { Code = StaticData.Export.OCT_CDISC_Export, ExportCatogory = ExportResult.OCT_CDISC_Export });
}
//else if (criterion.CriterionType == CriterionType.SelfDefine)
if (criterion.CriterionType == CriterionType.IVUS)
{
list.Add(new ExportDocumentDes() { Code = StaticData.Export.CDISC_Reading_Export, ExportCatogory = ExportResult.CDISC });
list.Add(new ExportDocumentDes() { Code = StaticData.Export.IVUS_CDISC_Export, ExportCatogory = ExportResult.IVUS_CDISC_Export });
}
if (criterion.CriterionGroup != CriterionGroup.Tumor /*&& criterion.CriterionType != CriterionType.OCT && criterion.CriterionType != CriterionType.IVUS*/)
{
list.Add(new ExportDocumentDes() { Code = StaticData.Export.CDISC_Reading_Export, ExportCatogory = ExportResult.NoneTumorCDISC });
}
switch (criterion.ArbitrationRule)
@@ -2730,7 +2903,7 @@ namespace IRaCIS.Core.Application.Service.Common
//阅片结果表
export_Template = StaticData.Export.CommonReading_Export;
}
//斑块表
else if (inQuery.ReadingExportType == ExportResult.OCT_ReadingLession_Export)
{
//OCT
@@ -2784,7 +2957,7 @@ namespace IRaCIS.Core.Application.Service.Common
}
// 非肿瘤标准 包括自定义,以及系统非肿瘤 会配置到表格层级 问题名称是 表格名称_表格子问题名
else if (
criterion.CriterionGroup == CriterionGroup.Nontumorous && inQuery.ReadingExportType != ExportResult.CDISC
criterion.CriterionGroup == CriterionGroup.Nontumorous && inQuery.ReadingExportType != ExportResult.NoneTumorCDISC
)
{
@@ -2801,7 +2974,7 @@ namespace IRaCIS.Core.Application.Service.Common
}
// CDISC 导出 只管到 外层问题层级 和阅片结果表是保持一致
else if (inQuery.ReadingExportType == ExportResult.CDISC)
else if (inQuery.ReadingExportType == ExportResult.NoneTumorCDISC)
{
if (criterion.CriterionGroup == CriterionGroup.Tumor)
@@ -2869,7 +3042,7 @@ namespace IRaCIS.Core.Application.Service.Common
#endregion
if (inQuery.ReadingExportType != ExportResult.CDISC)
if (inQuery.ReadingExportType != ExportResult.NoneTumorCDISC)
{
//最终EXCEL 列
var configCoumNameList = new List<DynamicColumnConfig.ColumItem>();
@@ -2898,7 +3071,8 @@ namespace IRaCIS.Core.Application.Service.Common
{
#region
if (criterion.CriterionType == CriterionType.RECIST1Point1 || criterion.CriterionType == CriterionType.RECIST1Pointt1_MB || criterion.CriterionType == CriterionType.IRECIST1Point1)
if (criterion.CriterionType == CriterionType.RECIST1Point1 || criterion.CriterionType == CriterionType.RECIST1Pointt1_MB
|| criterion.CriterionType == CriterionType.IRECIST1Point1 || criterion.CriterionType == CriterionType.mRECISTHCC)
{
fistLeveLNameList = trialConfigQuestionList.Select(t => new DynamicColumnConfig.ColumItem()
@@ -2941,7 +3115,8 @@ namespace IRaCIS.Core.Application.Service.Common
var extralNameList = new List<DynamicColumnConfig.ColumItem>();
if (criterion.CriterionType == CriterionType.RECIST1Point1 || criterion.CriterionType == CriterionType.RECIST1Pointt1_MB
|| criterion.CriterionType == CriterionType.IRECIST1Point1 || criterion.CriterionType == CriterionType.Lugano2014 || criterion.CriterionType == CriterionType.Lugano2014WithoutPET)
|| criterion.CriterionType == CriterionType.IRECIST1Point1 || criterion.CriterionType == CriterionType.mRECISTHCC
|| criterion.CriterionType == CriterionType.Lugano2014 || criterion.CriterionType == CriterionType.Lugano2014WithoutPET)
{
//if(inQuery.ReadingExportType == ExportResult.DetailedTableOfLesions)
@@ -2982,7 +3157,8 @@ namespace IRaCIS.Core.Application.Service.Common
var addLessionInfoList = new List<CommonQuesionInfo>();
if (criterion.CriterionType == CriterionType.RECIST1Point1 || criterion.CriterionType == CriterionType.RECIST1Pointt1_MB
|| criterion.CriterionType == CriterionType.IRECIST1Point1 || criterion.CriterionType == CriterionType.Lugano2014 || criterion.CriterionType == CriterionType.Lugano2014WithoutPET)
|| criterion.CriterionType == CriterionType.IRECIST1Point1 || criterion.CriterionType == CriterionType.mRECISTHCC
|| criterion.CriterionType == CriterionType.Lugano2014 || criterion.CriterionType == CriterionType.Lugano2014WithoutPET)
{
//病灶编号 和病灶类型没有配置,但是需要有的
addLessionInfoList.Add(new CommonQuesionInfo() { QuestionName = _userInfo.IsEn_Us ? "Lesion ID" : "病灶编号", QuestionValue = lession.LessionCode });
@@ -2994,7 +3170,7 @@ namespace IRaCIS.Core.Application.Service.Common
addLessionInfoList.Add(new CommonQuesionInfo() { QuestionName = _userInfo.IsEn_Us ? "Lesion Type" : "病灶类型", QuestionValue = lession.LessionType, TranslateDicName = "LesionType" });
}
var dynamicPartialLessionInfoList = lession.LessionAnswerList.Select(t => new CommonQuesionInfo() { QuestionName = t.QuestionName, QuestionValue = t.QuestionValue, TranslateDicName = t.TranslateDicName });
var dynamicPartialLessionInfoList = lession.LessionAnswerList.Select(t => new CommonQuesionInfo() { QuestionName = t.QuestionName, OptionTypeEnum = t.OptionTypeEnum, QuestionValue = t.QuestionValue, TranslateDicName = t.TranslateDicName ,Unit=t.Unit});
//有三部分组成 外层问题+ 没有配置病灶编号和类型+ 动态的表格问题
var dynamicLessionInfoList = item.QuestionAnswerList.Union(addLessionInfoList).Union(dynamicPartialLessionInfoList).ToList();
@@ -3022,7 +3198,8 @@ namespace IRaCIS.Core.Application.Service.Common
#endregion
#region list taskList list
if (criterion.CriterionType == CriterionType.RECIST1Point1 || criterion.CriterionType == CriterionType.RECIST1Pointt1_MB || criterion.CriterionType == CriterionType.IRECIST1Point1)
if (criterion.CriterionType == CriterionType.RECIST1Point1 || criterion.CriterionType == CriterionType.RECIST1Pointt1_MB
|| criterion.CriterionType == CriterionType.IRECIST1Point1 || criterion.CriterionType == CriterionType.mRECISTHCC)
{
//针对1.1 整体肿瘤评估 有的两列要合并一列
foreach (var item in list)
@@ -3141,14 +3318,14 @@ namespace IRaCIS.Core.Application.Service.Common
addLessionInfoList.Add(new CommonQuesionInfo() { QuestionId = Guid.Empty, QuestionName = _userInfo.IsEn_Us ? "Table Name" : "表格名称", QuestionValue = firstLessionAnser.TableName });
var dynamicPartialLessionInfoList = lession.LessionAnswerList.Select(t => new CommonQuesionInfo() { QuestionId = t.TableQuesionId, QuestionName = t.TableName + "_" + t.QuestionName, QuestionValue = t.QuestionValue, TranslateDicName = t.TranslateDicName, CDISCCode = t.CDISCCode });
var dynamicPartialLessionInfoList = lession.LessionAnswerList.Select(t => new CommonQuesionInfo() { QuestionId = t.TableQuesionId, OptionTypeEnum = t.OptionTypeEnum, QuestionName = t.TableName + "_" + t.QuestionName, QuestionValue = t.QuestionValue, TranslateDicName = t.TranslateDicName, CDISCCode = t.CDISCCode, Unit = t.Unit });
//有三部分组成 外层问题+ 固定列表格名称 + 动态的表格问题
dynamicLessionInfoList = item.QuestionAnswerList.Union(addLessionInfoList).Union(dynamicPartialLessionInfoList).ToList();
}
else
{
var dynamicPartialLessionInfoList = lession.LessionAnswerList.Select(t => new CommonQuesionInfo() { QuestionId = t.TableQuesionId, QuestionName = t.QuestionName, QuestionValue = t.QuestionValue, TranslateDicName = t.TranslateDicName, CDISCCode = t.CDISCCode });
var dynamicPartialLessionInfoList = lession.LessionAnswerList.Select(t => new CommonQuesionInfo() { QuestionId = t.TableQuesionId, OptionTypeEnum = t.OptionTypeEnum, QuestionName = t.QuestionName, QuestionValue = t.QuestionValue, TranslateDicName = t.TranslateDicName, CDISCCode = t.CDISCCode, Unit = t.Unit });
//两部分组成 外层问题+ 动态的表格问题
dynamicLessionInfoList = item.QuestionAnswerList.Union(dynamicPartialLessionInfoList).ToList();
@@ -3195,7 +3372,7 @@ namespace IRaCIS.Core.Application.Service.Common
TranslateDicNameList = translateDicNameList
};
if (export_Template == StaticData.Export.ReadingLession_Export || export_Template == StaticData.Export.CommonJudgeReadingDetail_Export)
if (export_Template == StaticData.Export.ReadingLession_Export || export_Template == StaticData.Export.CommonJudgeReadingDetail_Export || export_Template== StaticData.Export.OCT_ReadingLession_Export)
{
dynamicColumnConfig.TempalteLastColumnIndex = 8;
}
@@ -3214,7 +3391,7 @@ namespace IRaCIS.Core.Application.Service.Common
var trialConfigTableQuestionList = _trialReadingTableQuestionRepository.Where(t => t.TrialId == trialId && t.TrialCriterionId == trialReadingCriterionId).Where(t => t.ExportResultStr.Contains(((int)inQuery.ReadingExportType).ToString()))
.Select(t => new ExportQuestionBasicInfo()
{
QuestionId = t.Id,
QuestionId = t.Id,
TableName = _userInfo.IsEn_Us ? t.ReadingQuestionTrial.QuestionEnName : t.ReadingQuestionTrial.QuestionName,
QuestionName = _userInfo.IsEn_Us ? t.QuestionEnName : t.QuestionName,
CDISCCode = t.CDISCCode,
@@ -3258,7 +3435,7 @@ namespace IRaCIS.Core.Application.Service.Common
var dynamicLessionInfoList = new List<CommonQuesionInfo>();
var dynamicPartialLessionInfoList = lession.LessionAnswerList.Select(t => new CommonQuesionInfo() { QuestionId = t.TableQuesionId, QuestionName = t.QuestionName, QuestionValue = t.QuestionValue, TranslateDicName = t.TranslateDicName, CDISCCode = t.CDISCCode });
var dynamicPartialLessionInfoList = lession.LessionAnswerList.Select(t => new CommonQuesionInfo() { QuestionId = t.TableQuesionId,OptionTypeEnum=t.OptionTypeEnum, QuestionName = t.QuestionName, QuestionValue = t.QuestionValue, TranslateDicName = t.TranslateDicName, CDISCCode = t.CDISCCode, Unit = t.Unit });
//两部分组成 外层问题+ 动态的表格问题
dynamicLessionInfoList = item.QuestionAnswerList.Union(dynamicPartialLessionInfoList).ToList();
@@ -3471,7 +3648,7 @@ namespace IRaCIS.Core.Application.Service.Common
string fileName = "";
if (inQuery.ReadingExportType == ExportResult.CDISC)
if (inQuery.ReadingExportType == ExportResult.NoneTumorCDISC)
{
(memoryStream, fileName) = await ExcelExportHelper.DataExport_NpoiTestAsync(export_Template, exportInfo, _commonDocumentRepository, _hostEnvironment, _dictionaryService, typeof(CommonEvaluationExport), criterion.CriterionType, dynamicColumnConfig);
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,589 @@
//--------------------------------------------------------------------
// 此代码由liquid模板自动生成 byzhouhang 20240909
// 生成时间 2026-03-10 06:15:17Z
// 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
//--------------------------------------------------------------------
using DocumentFormat.OpenXml.Office2010.ExcelAc;
using IRaCIS.Core.Application.Helper;
using IRaCIS.Core.Application.Interfaces;
using IRaCIS.Core.Application.ViewModel;
using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.Infra.EFCore;
using IRaCIS.Core.Infra.EFCore.Common;
using IRaCIS.Core.Infrastructure.Extention;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Spire.Doc.Interface;
using System.Drawing;
using System.Threading.Channels;
using System.Threading.Tasks;
namespace IRaCIS.Core.Application.Service;
[ApiExplorerSettings(GroupName = "Common")]
public class FileUploadRecordService(IRepository<FileUploadRecord> _fileUploadRecordRepository, IRepository<UploadFileSyncRecord> _uploadFileSyncRecordRepository,
IMapper _mapper, IUserInfo _userInfo, IStringLocalizer _localizer, IOptionsMonitor<ObjectStoreServiceOptions> options,
IFusionCache _fusionCache, IRepository<Trial> _trialRepository, FileSyncQueue _fileSyncQueue) : BaseService, IFileUploadRecordService
{
ObjectStoreServiceOptions ObjectStoreServiceConfig = options.CurrentValue;
/// <summary>
/// 按照 subject visit studyCode 三个维度进行分组的查询列表 (subject相关)
/// </summary>
/// <param name="inQuery"></param>
/// <returns></returns>
[HttpPost]
public async Task<PageOutput<SubjectFileUploadRecordView>> GetSubjectUploadRecordList(SubjectFileUploadRecordQuery inQuery)
{
var query = _fileUploadRecordRepository.Where(t => t.TrialId == inQuery.TrialId && t.SubjectId != null)
.WhereIf(!string.IsNullOrEmpty(inQuery.VisitName), t => t.SubjectVisit.VisitName.Contains(inQuery.VisitName))
.WhereIf(!string.IsNullOrEmpty(inQuery.SubjectCode), t => t.Subject.Code.Contains(inQuery.SubjectCode))
.WhereIf(!string.IsNullOrEmpty(inQuery.StudyCode), t => t.StudyCode.Contains(inQuery.StudyCode))
.WhereIf(!string.IsNullOrEmpty(inQuery.UploadRegion), t => t.UploadRegion == inQuery.UploadRegion)
.WhereIf(!string.IsNullOrEmpty(inQuery.TargetRegion), t => t.TargetRegion == inQuery.TargetRegion)
.WhereIf(inQuery.IsSync != null, t => t.IsSync == inQuery.IsSync)
.GroupBy(t => new { t.StudyCode, SubjectCode = t.Subject.Code, t.SubjectVisit.VisitName, t.SubjectId, t.SubjectVisitId })
.Select(g => new SubjectFileUploadRecordView()
{
SubjectCode = g.Key.SubjectCode,
VisitName = g.Key.VisitName,
StudyCode = g.Key.StudyCode,
SubjectId = g.Key.SubjectId,
SubjectVisitId = g.Key.SubjectVisitId,
FileCount = g.Count(),
CreateTime = g.Max(t => t.CreateTime),
SyncFinishedTime = g.Max(t => t.SyncFinishedTime),
UploadRegion = g.First().UploadRegion,
TargetRegion = g.First().TargetRegion,
IsSync = !g.Any(t => t.IsSync == false || t.IsSync == null)
});
var pageList = await query.ToPagedListAsync(inQuery);
return pageList;
}
/// <summary>
/// 上传记录表--里面包含待同步任务 DataFileType= 0 :代表系统文件 1Subject相关 2:项目相关,但是和subject 没关系
/// </summary>
/// <param name="inQuery"></param>
/// <returns></returns>
[HttpPost]
public async Task<PageOutput<FileUploadRecordView>> GetFileUploadRecordList(FileUploadRecordQuery inQuery)
{
var fileUploadRecordQueryable = _fileUploadRecordRepository
.WhereIf(!string.IsNullOrEmpty(inQuery.FileName), t => t.FileName.Contains(inQuery.FileName))
.WhereIf(!string.IsNullOrEmpty(inQuery.FileType), t => t.FileType.Contains(inQuery.FileType))
.WhereIf(inQuery.TrialId != null, t => t.TrialId == inQuery.TrialId)
.WhereIf(inQuery.SubjectId != null, t => t.SubjectId == inQuery.SubjectId)
.WhereIf(inQuery.SubjectVisitId != null, t => t.SubjectVisitId == inQuery.SubjectVisitId)
.WhereIf(!string.IsNullOrEmpty(inQuery.StudyCode), t => t.StudyCode.Contains(inQuery.StudyCode))
.WhereIf(!string.IsNullOrEmpty(inQuery.VisitName), t => t.SubjectVisit.VisitName.Contains(inQuery.VisitName))
.WhereIf(!string.IsNullOrEmpty(inQuery.SubjectCode), t => t.Subject.Code.Contains(inQuery.SubjectCode))
.WhereIf(inQuery.DataFileType == 1 && inQuery.SubjectId != null && inQuery.SubjectVisitId == null, t => t.SubjectVisitId == null)
.WhereIf(inQuery.DataFileType == 1 && inQuery.SubjectVisitId != null && inQuery.StudyCode == "", t => t.StudyCode == "")
.WhereIf(inQuery.DataFileType == 1 && inQuery.SubjectCode.IsNotNullOrEmpty() && inQuery.VisitName.IsNullOrEmpty(), t => t.SubjectVisitId == null)
.WhereIf(inQuery.DataFileType == 1 && inQuery.VisitName.IsNotNullOrEmpty() && inQuery.StudyCode == "", t => t.StudyCode == "")
.WhereIf(inQuery.DataFileType == 0, t => t.TrialId == null)
.WhereIf(inQuery.DataFileType == 1, t => t.SubjectId != null)
.WhereIf(inQuery.DataFileType == 2, t => t.SubjectId == null)
.WhereIf(inQuery.IsNeedSync != null, t => t.IsNeedSync == inQuery.IsNeedSync)
.WhereIf(inQuery.IsSync != null, t => t.IsSync == inQuery.IsSync)
.WhereIf(inQuery.Priority != null, t => t.Priority == inQuery.Priority)
.WhereIf(inQuery.BatchDataType != null, t => t.BatchDataType == inQuery.BatchDataType)
.WhereIf(!string.IsNullOrEmpty(inQuery.UploadRegion), t => t.UploadRegion == inQuery.UploadRegion)
.WhereIf(!string.IsNullOrEmpty(inQuery.TargetRegion), t => t.TargetRegion == inQuery.TargetRegion)
.WhereIf(!string.IsNullOrEmpty(inQuery.UploadBatchId), t => t.UploadBatchId.Contains(inQuery.UploadBatchId))
.WhereIf(!string.IsNullOrEmpty(inQuery.Path), t => t.Path.Contains(inQuery.Path))
.WhereIf(inQuery.UploadStartTime != null, t => t.CreateTime >= inQuery.UploadStartTime)
.WhereIf(inQuery.UploadEndTime != null, t => t.CreateTime <= inQuery.UploadEndTime)
.WhereIf(inQuery.SyncFinishedStartTime != null, t => t.SyncFinishedTime >= inQuery.SyncFinishedStartTime)
.WhereIf(inQuery.SyncFinishedEndTime != null, t => t.SyncFinishedTime <= inQuery.SyncFinishedEndTime)
.ProjectTo<FileUploadRecordView>(_mapper.ConfigurationProvider);
var pageList = await fileUploadRecordQueryable.ToPagedListAsync(inQuery);
return pageList;
}
/// <summary>
/// 任务具体执行记录表
/// </summary>
/// <param name="inQuery"></param>
/// <returns></returns>
[HttpPost]
public async Task<PageOutput<UploadFileSyncRecordView>> GetUploadFileSyncRecordList(UploadFileSyncRecordQuery inQuery)
{
var fileUploadRecordQueryable = _uploadFileSyncRecordRepository
.WhereIf(inQuery.JobState != null, t => t.JobState == inQuery.JobState)
.WhereIf(inQuery.FileUploadRecordId != null, t => t.FileUploadRecordId == inQuery.FileUploadRecordId)
.WhereIf(!string.IsNullOrEmpty(inQuery.StudyCode), t => t.FileUploadRecord.StudyCode.Contains(inQuery.StudyCode))
.WhereIf(!string.IsNullOrEmpty(inQuery.VisitName), t => t.FileUploadRecord.SubjectVisit.VisitName.Contains(inQuery.VisitName))
.WhereIf(!string.IsNullOrEmpty(inQuery.SubjectCode), t => t.FileUploadRecord.Subject.Code.Contains(inQuery.SubjectCode))
.ProjectTo<UploadFileSyncRecordView>(_mapper.ConfigurationProvider);
var pageList = await fileUploadRecordQueryable.ToPagedListAsync(inQuery);
return pageList;
}
/// <summary>
/// 批量设置为需要同步,并且设置优先级
/// </summary>
/// <param name="inComand"></param>
/// <returns></returns>
public async Task<IResponseOutput> BatchAddSyncFileTask(BatchAddSyncFileCommand inComand)
{
await _fileUploadRecordRepository.BatchUpdateNoTrackingAsync(t => inComand.FileUploadRecordIdList.Contains(t.Id), u => new FileUploadRecord() { IsNeedSync = true, Priority = inComand.Priority ?? 0 });
foreach (var item in inComand.FileUploadRecordIdList)
{
_fileSyncQueue.Enqueue(item, inComand.Priority ?? 0);
}
await _fileUploadRecordRepository.SaveChangesAsync();
return ResponseOutput.Ok();
}
public async Task<IResponseOutput> AddOrUpdateFileUploadRecord(FileUploadRecordAddOrEdit addOrEditFileUploadRecord)
{
addOrEditFileUploadRecord.IP = _userInfo.IP;
if (ObjectStoreServiceConfig.IsOpenStoreSync && _userInfo.Domain.IsNotNullOrEmpty())
{
var find = ObjectStoreServiceConfig.SyncConfigList.FirstOrDefault(t => t.Domain == _userInfo.Domain);
if (find != null)
{
addOrEditFileUploadRecord.UploadRegion = find.UploadRegion;
addOrEditFileUploadRecord.TargetRegion = find.TargetRegion;
}
else
{
//前后端调试的时候,上传的时候域名不对应,自动按照后端配置设置上传区域和同步区域
var apiDefalut = ObjectStoreServiceConfig.SyncConfigList.FirstOrDefault(t => t.UploadRegion == ObjectStoreServiceConfig.ApiDeployRegion);
if (apiDefalut != null)
{
addOrEditFileUploadRecord.UploadRegion = apiDefalut.UploadRegion;
addOrEditFileUploadRecord.TargetRegion = apiDefalut.TargetRegion;
}
}
}
if (addOrEditFileUploadRecord.TrialId != null)
{
var trialDataStore = await _fusionCache.GetOrSetAsync(CacheKeys.TrialDataStoreType(addOrEditFileUploadRecord.TrialId.Value), async _ =>
{
return await _trialRepository.Where(t => t.Id == addOrEditFileUploadRecord.TrialId).Select(t => t.TrialDataStoreType)
.FirstOrDefaultAsync();
},
TimeSpan.FromDays(7)
);
//项目配置了,那么就设置需要同步
if (trialDataStore == TrialDataStore.MUtiCenter)
{
addOrEditFileUploadRecord.IsNeedSync = true;
addOrEditFileUploadRecord.Priority = addOrEditFileUploadRecord.Priority?? 0;
addOrEditFileUploadRecord.IsSync = false;
}
else
{
addOrEditFileUploadRecord.IsNeedSync = false;
//addOrEditFileUploadRecord.TargetRegion = "";
}
}
else
{
//系统文件,默认同步
addOrEditFileUploadRecord.IsNeedSync = true;
addOrEditFileUploadRecord.IsSync = false;
addOrEditFileUploadRecord.Priority = addOrEditFileUploadRecord.Priority ?? 0;
}
var entity = await _fileUploadRecordRepository.InsertOrUpdateAsync(addOrEditFileUploadRecord, true);
if (addOrEditFileUploadRecord.IsNeedSync == true)
{
_fileSyncQueue.Enqueue(entity.Id, addOrEditFileUploadRecord.Priority ?? 0);
}
return ResponseOutput.Ok(entity.Id.ToString());
}
[HttpDelete("{fileUploadRecordId:guid}")]
public async Task<IResponseOutput> DeleteFileUploadRecord(Guid fileUploadRecordId)
{
var success = await _fileUploadRecordRepository.BatchDeleteNoTrackingAsync(t => t.Id == fileUploadRecordId);
return ResponseOutput.Ok();
}
}
#region
public sealed class FileSyncQueue
{
/// <summary>
/// 优先级队列(仅负责排序)
/// </summary>
private readonly PriorityQueue<Guid, int> _queue = new();
/// <summary>
/// 当前等待中的任务(唯一真实数据)
/// key = Guid
/// value = 最新 priority
/// </summary>
private readonly Dictionary<Guid, int> _waiting = new();
/// <summary>
/// 正在执行的任务(防止重复执行)
/// </summary>
private readonly HashSet<Guid> _running = new();
/// <summary>
/// worker 等待信号
/// </summary>
private readonly SemaphoreSlim _signal = new(0);
private readonly object _lock = new();
// ============================================================
// Enqueue
// ============================================================
/// <summary>
/// 入队(同 Guid 会覆盖优先级)
/// </summary>
public void Enqueue(Guid id, int priority)
{
bool needSignal = false;
lock (_lock)
{
// 如果正在执行,忽略(防止重复)
if (_running.Contains(id))
return;
// 是否新任务(用于减少 signal 风暴)
if (!_waiting.ContainsKey(id))
needSignal = true;
// 更新为最新优先级(最后一次为准)
_waiting[id] = priority; //等价于添加或者更新
// PriorityQueue 无法更新节点
// 允许旧节点存在,Dequeue 时过滤
_queue.Enqueue(id, -priority);
}
// 只有新增任务才唤醒 worker
if (needSignal)
_signal.Release();
}
// ============================================================
// Dequeue
// ============================================================
/// <summary>
/// 获取一个待执行任务(无任务时自动等待)
/// </summary>
public async Task<Guid> DequeueAsync(CancellationToken ct)
{
while (true)
{
await _signal.WaitAsync(ct);
lock (_lock)
{
while (_queue.Count > 0)
{
var id = _queue.Dequeue();
// 已被覆盖或取消 如果这个任务已经不是“当前最新版任务”,那它只是 PriorityQueue 里的垃圾数据,直接跳过。
if (!_waiting.TryGetValue(id, out _)) //能从等待任务中取到,那么就是有效的,不能取到那么就是覆盖的
continue;
// 标记为运行中
_waiting.Remove(id);
_running.Add(id);
return id;
}
}
}
}
// ============================================================
// Complete
// ============================================================
/// <summary>
/// 任务执行完成(必须调用)
/// </summary>
public void Complete(Guid id)
{
lock (_lock)
{
_running.Remove(id);
}
}
// ============================================================
// Snapshot
// ============================================================
/// <summary>
/// 当前等待中的任务快照
/// </summary>
public Guid[] Snapshot()
{
lock (_lock)
{
return _waiting.Keys.ToArray();
}
}
// ============================================================
// 状态信息(调试用)
// ============================================================
public int WaitingCount
{
get
{
lock (_lock)
return _waiting.Count;
}
}
public int RunningCount
{
get
{
lock (_lock)
return _running.Count;
}
}
}
///// <summary>
///// 同步队列 信号量
///// </summary>
//public class FileSyncQueue
//{
// private readonly PriorityQueue<Guid, int> _queue = new();
// private readonly SemaphoreSlim _signal = new(0);
// private readonly object _lock = new();
// public void Enqueue(Guid id, int priority)
// {
// lock (_lock)
// {
// // priority 越大越优先
// _queue.Enqueue(id, -priority);
// }
// //类似于计数器,不会产生通知风暴,可消费资源 +1
// //if (有等待线程) 唤醒一个 else 仅增加计数
// _signal.Release(); // 唤醒一个 worker
// }
// /// <summary>
// /// 如果没有任务 → 挂起等待 有任务 → 被唤醒并返回
// /// </summary>
// /// <param name="ct"></param>
// /// <returns></returns>
// public async Task<Guid> DequeueAsync(CancellationToken ct)
// {
// await _signal.WaitAsync(ct);
// lock (_lock)
// {
// return _queue.Dequeue();
// }
// }
// /// <summary>
// /// 获取队列任务Id
// /// </summary>
// /// <returns></returns>
// public Guid[] Snapshot()
// {
// lock (_lock)
// {
// return _queue.UnorderedItems
// .Select(x => x.Element)
// .ToArray();
// }
// }
//}
#region SyncQueueUseChannel SyncScheduler
public class SyncQueueUseChannel
{
// 优先级队列(priority 越大越先执行)
private readonly PriorityQueue<Guid, int> _queue = new();
// Worker 唤醒信号
private readonly Channel<bool> _signal =
Channel.CreateUnbounded<bool>(new UnboundedChannelOptions
{
SingleReader = false,
SingleWriter = false
});
// 队列任务数量(不是CPU数量!)
private int _count = 0;
private readonly object _lock = new();
/// <summary>
/// 入队任务
/// </summary>
public void Enqueue(Guid id, int priority)
{
bool needSignal = false;
lock (_lock)
{
// priority 越大越优先 → 转负数
_queue.Enqueue(id, -priority);
// 只有从 0 → 1 才需要唤醒 worker
if (_count == 0)
needSignal = true;
_count++;
}
// 避免 signal 风暴
if (needSignal)
_signal.Writer.TryWrite(true);
}
/// <summary>
/// Worker 等待并获取任务
/// </summary>
public async Task<Guid> DequeueAsync(CancellationToken ct)
{
// 没任务时挂起(不会占CPU
await _signal.Reader.ReadAsync(ct);
lock (_lock)
{
var id = _queue.Dequeue();
_count--;
// 如果还有任务,继续唤醒下一个 worker
if (_count > 0)
_signal.Writer.TryWrite(true);
return id;
}
}
/// <summary>
/// 当前排队数量(调试用)
/// </summary>
public int Count
{
get
{
lock (_lock)
return _count;
}
}
}
/// <summary>
/// 同步调度器
/// </summary>
public class FileSyncScheduler
{
private readonly FileSyncQueue _queue;
public FileSyncScheduler(FileSyncQueue queue)
{
_queue = queue;
}
public void Enqueue(FileUploadRecord file)
{
if (file.IsNeedSync != true)
return;
_queue.Enqueue(file.Id, file.Priority ?? 0);
}
/// <summary>
/// 如果没有任务 → 挂起等待 有任务 → 被唤醒并返回
/// </summary>
/// <param name="ct"></param>
/// <returns></returns>
public Task<Guid> WaitAsync(CancellationToken ct)
=> _queue.DequeueAsync(ct);
}
#endregion
#endregion
@@ -0,0 +1,23 @@
//--------------------------------------------------------------------
// 此代码由liquid模板自动生成 byzhouhang 20240909
// 生成时间 2025-10-28 06:22:47Z
// 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
//--------------------------------------------------------------------
using System;
using IRaCIS.Core.Infrastructure.Extention;
using System.Threading.Tasks;
using IRaCIS.Core.Application.ViewModel;
namespace IRaCIS.Core.Application.Interfaces;
public interface IEmailLogService
{
Task<PageOutput<EmailLogView>> GetEmailLogList(EmailLogQuery inQuery);
Task<IResponseOutput> AddOrUpdateEmailLog(EmailLogAddOrEdit addOrEditEmailLog);
Task<IResponseOutput> DeleteEmailLog(Guid emailLogId);
}
@@ -0,0 +1,23 @@
//--------------------------------------------------------------------
// 此代码由liquid模板自动生成 byzhouhang 20240909
// 生成时间 2026-03-10 06:15:31Z
// 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
//--------------------------------------------------------------------
using System;
using IRaCIS.Core.Infrastructure.Extention;
using System.Threading.Tasks;
using IRaCIS.Core.Application.ViewModel;
namespace IRaCIS.Core.Application.Interfaces;
public interface IFileUploadRecordService
{
Task<PageOutput<FileUploadRecordView>> GetFileUploadRecordList(FileUploadRecordQuery inQuery);
Task<IResponseOutput> AddOrUpdateFileUploadRecord(FileUploadRecordAddOrEdit addOrEditFileUploadRecord);
Task<IResponseOutput> DeleteFileUploadRecord(Guid fileUploadRecordId);
}
@@ -5,11 +5,16 @@ using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Infrastructure;
using MailKit;
using MailKit.Net.Imap;
using MailKit.Search;
using MailKit.Security;
using Medallion.Threading;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using MimeKit;
using System.Net.Mail;
using System.Runtime.CompilerServices;
namespace IRaCIS.Core.Application.Service
@@ -48,7 +53,7 @@ namespace IRaCIS.Core.Application.Service
public interface IMailVerificationService
{
Task AnolymousSendEmail(string researchProgramNo, string emailAddress, int verificationCode);
Task AnolymousSendEmail(Guid trialId, string emailAddress, int verificationCode);
Task SendEmailVerification(string emailAddress, int verificationCode);
@@ -61,7 +66,7 @@ namespace IRaCIS.Core.Application.Service
Task AddUserSendEmailAsync(Guid userId, string baseUrl, string routeUrl);
Task AdminResetPwdSendEmailAsync(Guid userId, string pwdNotMd5 = "123456");
Task AdminResetPwdSendEmailAsync(Guid userId, string pwdNotMd5 = "123456", string baseUrl="");
Task SiteSurveyUserJoinEmail(Guid trialId, Guid userId, string userTypes, string baseUrl, string rootUrl);
@@ -70,8 +75,14 @@ namespace IRaCIS.Core.Application.Service
Task<(Guid identityUserId, Guid userRoleId)> DoctorJoinTrialEmail(Guid trialId, Guid doctorId, string baseUrl, string rootUrl);
Task UserFeedBackMail(Guid feedBackId);
}
Task AfterUserModifyPasswordSendEmailAsync(Guid userId);
Task SiteSuervyCheckUser(Guid trialId, string email, string name);
Task SiteSuervyUpdateUser(Guid trialSiteId, string email, string name, string url);
}
[ApiExplorerSettings(GroupName = "Common")]
public class MailVerificationService(IRepository<VerificationCode> _verificationCodeRepository,
IRepository<SystemBasicData> _systemBasicDatarepository,
IRepository<VisitTask> _visitTaskRepository,
@@ -86,12 +97,14 @@ namespace IRaCIS.Core.Application.Service
IRepository<UserType> _userTypeRepository,
IRepository<Doctor> _doctorTypeRepository,
IRepository<Dictionary> _dictionaryRepository,
IRepository<EmailNoticeConfig> _emailNoticeConfigrepository,
IOptionsMonitor<SystemEmailSendConfig> systemEmailConfig,
IDistributedLockProvider _distributedLockProvider, IMapper _mapper, IUserInfo _userInfo, IStringLocalizer _localizer) : BaseService, IMailVerificationService
{
private readonly SystemEmailSendConfig _systemEmailConfig = systemEmailConfig.CurrentValue;
private async Task<EmailNoticeConfig> GetEmailSubejctAndHtmlInfoAndBuildAsync(EmailBusinessScenario scenario, MimeMessage messageToSend,
Func<(string topicStr, string htmlBodyStr), (string topicStr, string htmlBodyStr)> emailFunc)
@@ -176,6 +189,14 @@ namespace IRaCIS.Core.Application.Service
return str;
}
private string ReplacePlatformName(string needDealtxt)
{
var platformName = _userInfo.IsEn_Us ? _systemEmailConfig.PlatformName : _systemEmailConfig.PlatformNameCN;
var str = needDealtxt.Replace("{platformName}", platformName);
return str;
}
//MFA
public async Task SenMFAVerifyEmail(Guid userId, string userName, string emailAddress, int verificationCode, UserMFAType mfaType = UserMFAType.Login)
{
@@ -263,10 +284,13 @@ namespace IRaCIS.Core.Application.Service
//中心调研 登陆 发送验证码
public async Task AnolymousSendEmail(string researchProgramNo, string emailAddress, int verificationCode)
public async Task AnolymousSendEmail(Guid trialId, string emailAddress, int verificationCode)
{
//throw new BusinessValidationFailedException("模拟邮件取数据或者发送异常!!!");
var trialInfo = await _trialRepository.FirstOrDefaultAsync(t => t.Id == trialId);
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
@@ -279,7 +303,7 @@ namespace IRaCIS.Core.Application.Service
Func<(string topicStr, string htmlBodyStr), (string topicStr, string htmlBodyStr)> emailConfigFunc = input =>
{
var topicStr = string.Format(input.topicStr, companyName, researchProgramNo);
var topicStr = string.Format(input.topicStr, companyName, trialInfo.ResearchProgramNo);
var htmlBodyStr = string.Format(ReplaceCompanyName(input.htmlBodyStr),
@@ -299,7 +323,7 @@ namespace IRaCIS.Core.Application.Service
var sucessHandle = GetEmailSuccessHandle(Guid.Empty, verificationCode, emailAddress);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig, sucessHandle);
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo, sucessHandle);
}
@@ -330,7 +354,7 @@ namespace IRaCIS.Core.Application.Service
var domain = baseUrl.Substring(0, baseUrl.IndexOf("/login"));
var redirectUrl = $"{domain}/api/User/UserRedirect?url={System.Web.HttpUtility.UrlEncode(routeUrl)}";
var companyName = _userInfo.IsEn_Us ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
@@ -357,7 +381,7 @@ namespace IRaCIS.Core.Application.Service
}
//管理员重置密码发送邮件
public async Task AdminResetPwdSendEmailAsync(Guid userId, string pwdNotMd5 = "123456")
public async Task AdminResetPwdSendEmailAsync(Guid userId, string pwdNotMd5 = "123456", string baseUrl = "")
{
var sysUserInfo = (await _identityUserRepository.Where(t => t.Id == userId).Include(t => t.UserRoleList).ThenInclude(c => c.UserTypeRole).FirstOrDefaultAsync()).IfNullThrowException();
@@ -380,7 +404,9 @@ namespace IRaCIS.Core.Application.Service
sysUserInfo.FullName,
sysUserInfo.UserName,
//string.Join(',', sysUserInfo.UserRoleList.Select(t => t.UserTypeRole.UserTypeShortName)),
pwdNotMd5
pwdNotMd5,
baseUrl
);
return (topicStr, htmlBodyStr);
@@ -520,8 +546,7 @@ namespace IRaCIS.Core.Application.Service
await GetEmailSubejctAndHtmlInfoAndBuildAsync(sysUserInfo.IsFirstAdd ? EmailBusinessScenario.SiteUseOrExternalUserFirstrJoinTrial : EmailBusinessScenario.SiteUserOrExternalUserExistJoinTrial, messageToSend, emailConfigFunc);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig, null);
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo, null);
}
@@ -580,8 +605,7 @@ namespace IRaCIS.Core.Application.Service
await GetEmailSubejctAndHtmlInfoAndBuildAsync(sysUserInfo.IsFirstAdd ? EmailBusinessScenario.SiteUseOrExternalUserFirstrJoinTrial : EmailBusinessScenario.SiteUserOrExternalUserExistJoinTrial, messageToSend, emailConfigFunc);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
}
@@ -710,8 +734,7 @@ namespace IRaCIS.Core.Application.Service
await GetEmailSubejctAndHtmlInfoAndBuildAsync(sysUserInfo.IsFirstAdd ? EmailBusinessScenario.DoctorUserFirstJoinTrial : EmailBusinessScenario.DoctorUserExistJoinTrial, messageToSend, emailConfigFunc);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig, null);
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo, null);
//创建账号 和创建角色 一条,更新的时候才记录更新角色
if (isNeedCreateNewUser == false)
@@ -737,6 +760,8 @@ namespace IRaCIS.Core.Application.Service
var isHaveTrialId = feedBack.TrialId != null;
var trialinfo = await _trialRepository.Where(x => x.Id == feedBack.TrialId).FirstOrDefaultAsync();
var companyName = _userInfo.IsEn_Us ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
var emialScenario = feedBack.VisitTaskId != null ? EmailBusinessScenario.IRImageError : (feedBack.SubjectVisitId != null ? EmailBusinessScenario.TrialSubjectVisitFeedBack : (feedBack.TrialId != null ? EmailBusinessScenario.TrialFeedBack : EmailBusinessScenario.SysFeedBack));
@@ -760,7 +785,7 @@ namespace IRaCIS.Core.Application.Service
if (feedBack.VisitTaskId != null)
{
var emailType = await _dictionaryRepository.Where(t => t.Parent.Code == "Email_BusinessScenario" && t.ParentId != null && t.Code == ((int)EmailBusinessScenario.IRImageError).ToString()).Select(t => _userInfo.IsEn_Us ? t.Value : t.ValueCN).FirstOrDefaultAsync();
var emailType = await _dictionaryRepository.Where(t => t.Parent.Code == "FeedBackTypeToIR" && t.ParentId != null && t.Code == ((int)feedBack.QuestionType).ToString()).Select(t => _userInfo.IsEn_Us ? t.Value : t.ValueCN).FirstOrDefaultAsync();
var info = await _visitTaskRepository.Where(t => t.Id == feedBack.VisitTaskId).Select(t => new { t.Trial.ResearchProgramNo, t.Trial.TrialCode, SubejctCode = t.Subject.Code, t.SourceSubjectVisit.VisitName }).FirstNotNullAsync();
@@ -871,8 +896,122 @@ namespace IRaCIS.Core.Application.Service
await GetEmailSubejctAndHtmlInfoAndBuildAsync(EmailBusinessScenario.SysFeedBack, messageToSend, emailConfigFunc);
}
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
if (trialinfo == null)
{
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
}
else
{
await SendEmailHelper.SendEmailAsync(messageToSend, trialinfo);
}
}
//用户修改密码发送邮件
public async Task AfterUserModifyPasswordSendEmailAsync(Guid userId)
{
var sysUserInfo = (await _identityUserRepository.Where(t => t.Id == userId).FirstOrDefaultAsync()).IfNullThrowException();
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
//收件地址
messageToSend.To.Add(new MailboxAddress(sysUserInfo.FullName, sysUserInfo.EMail));
//主题
//---[来自展影IRC] 关于重置邮箱的提醒
var companyName = _userInfo.IsEn_Us ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
Func<(string topicStr, string htmlBodyStr), (string topicStr, string htmlBodyStr)> emailConfigFunc = input =>
{
var topicStr = string.Format(input.topicStr, companyName);
var htmlBodyStr = string.Format(ReplacePlatformName(ReplaceCompanyName(input.htmlBodyStr)) ,
sysUserInfo.FullName
);
return (topicStr, htmlBodyStr);
};
await GetEmailSubejctAndHtmlInfoAndBuildAsync(EmailBusinessScenario.IdentityUser_ModifyPassword, messageToSend, emailConfigFunc);
await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig);
}
//中心调研核对人员提醒
public async Task SiteSuervyCheckUser(Guid trialId, string email, string name)
{
var trialInfo = await _trialRepository.Where(t => t.Id == trialId).FirstOrDefaultAsync();
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
//收件地址
messageToSend.To.Add(new MailboxAddress(name, email));
//主题
//---[来自展影IRC] 关于重置邮箱的提醒
var companyName = _userInfo.IsEn_Us ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
Func<(string topicStr, string htmlBodyStr), (string topicStr, string htmlBodyStr)> emailConfigFunc = input =>
{
var topicStr = string.Format(input.topicStr, companyName, trialInfo.ResearchProgramNo);
var htmlBodyStr = string.Format(ReplaceCompanyName(input.htmlBodyStr),
name,
trialInfo.TrialCode,
trialInfo.ResearchProgramNo
);
return (topicStr, htmlBodyStr);
};
await GetEmailSubejctAndHtmlInfoAndBuildAsync(EmailBusinessScenario.SiteSurvey_CheckUser, messageToSend, emailConfigFunc);
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
}
public async Task SiteSuervyUpdateUser(Guid trialSiteId, string email, string name, string url)
{
var siteInfo = await _trialSiteRepository.Where(t => t.Id == trialSiteId).Include(x=>x.Trial).FirstOrDefaultAsync();
var trialInfo = siteInfo.Trial;
var messageToSend = new MimeMessage();
//发件地址
messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail));
//收件地址
messageToSend.To.Add(new MailboxAddress(name, email));
//主题
//---[来自展影IRC] 关于重置邮箱的提醒
var companyName = _userInfo.IsEn_Us ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN;
Func<(string topicStr, string htmlBodyStr), (string topicStr, string htmlBodyStr)> emailConfigFunc = input =>
{
var topicStr = string.Format(input.topicStr, companyName, trialInfo.ResearchProgramNo);
var htmlBodyStr = string.Format(ReplaceCompanyName(input.htmlBodyStr),
name,
trialInfo.TrialCode,
siteInfo.TrialSiteCode,
siteInfo.TrialSiteName,
url
);
return (topicStr, htmlBodyStr);
};
await GetEmailSubejctAndHtmlInfoAndBuildAsync(EmailBusinessScenario.SiteSurvey_UpdateUser, messageToSend, emailConfigFunc);
await SendEmailHelper.SendEmailAsync(messageToSend, trialInfo);
}
}
}
File diff suppressed because it is too large Load Diff
@@ -2,6 +2,7 @@
using IRaCIS.Application.Contracts;
using IRaCIS.Core.Application.Contracts;
using IRaCIS.Core.Application.Helper;
using IRaCIS.Core.Application.Service.Common;
using IRaCIS.Core.Application.ViewModel;
namespace IRaCIS.Core.Application.Service
@@ -10,7 +11,18 @@ namespace IRaCIS.Core.Application.Service
{
public CommonConfig()
{
// 在此处拷贝automapper 映射
CreateMap<EmailAttachmentLog, EmaliAttachmentInfo>();
CreateMap<EmailRecipientLog, EmailRecipientLogView>();
CreateMap<EmailLog, EmailLogView>()
.ForMember(t => t.RecipientList, u => u.MapFrom(c => c.EmailRecipientLogList))
.ForMember(t => t.AttachmentList, u => u.MapFrom(c => c.AttachmentList));
CreateMap<EmailLog, GetEmailInfoOutDto>()
.ForMember(t => t.RecipientList, u => u.MapFrom(c => c.EmailRecipientLogList))
.ForMember(t => t.AttachmentList, u => u.MapFrom(c => c.AttachmentList));
CreateMap<EmailLog, EmailLogAddOrEdit>().ReverseMap();
CreateMap<FrontAuditConfig, FrontAuditConfigAddOrEdit>().ReverseMap();
@@ -29,7 +41,7 @@ namespace IRaCIS.Core.Application.Service
.ForMember(t => t.EmailNoticeUserList, u => u.MapFrom(c => c.EmailNoticeUserTypeList));
CreateMap<EmailNoticeConfig, EmailNoticeConfigExportDto>();
CreateMap<Dictionary, DicView>()
.ForMember(t => t.ParentCode, u => u.MapFrom(c => c.Parent.Code));
@@ -77,14 +89,14 @@ namespace IRaCIS.Core.Application.Service
CreateMap<Internationalization, InternationalizationAddOrEdit>().ReverseMap();
CreateMap<InternationalizationAddOrEdit, IRCGlobalInfoDTO>();
CreateMap<Internationalization, BatchInternationalizationDto>().ReverseMap();
CreateMap<Internationalization, IRCGlobalInfoDTO>();
CreateMap<EventStoreRecord, EventStoreRecordView>();
CreateMap<BatchAddInternationalizationDto, InternationalizationAddOrEdit>();
@@ -104,6 +116,29 @@ namespace IRaCIS.Core.Application.Service
.ForMember(d => d.TrialSiteCode, u => u.MapFrom(s => s.Subject.TrialSite.TrialSiteCode));
CreateMap<TumorExportBaseModel, TU_Export>();
CreateMap<TumorExportBaseModel, TR_Export>();
CreateMap<TumorExportBaseModel, RS_Export>();
CreateMap<TumorExportBaseModel, CO_Export>();
CreateMap<IVUS_OCTBaseDto, IvusExportDto>();
CreateMap<IVUS_OCTBaseDto, OctExportDto>();
CreateMap<FileUploadRecord, FileUploadRecordView>()
.ForMember(d => d.SubjectCode, u => u.MapFrom(s => s.Subject.Code))
.ForMember(d => d.VisitName, u => u.MapFrom(s => s.SubjectVisit.VisitName));
CreateMap<FileUploadRecord, FileUploadRecordAddOrEdit>().ReverseMap();
CreateMap<UploadFileSyncRecord, UploadFileSyncRecordView>()
.ForMember(d => d.FileName, u => u.MapFrom(s => s.FileUploadRecord.FileName))
.ForMember(d => d.FileType, u => u.MapFrom(s => s.FileUploadRecord.FileType))
.ForMember(d => d.Path, u => u.MapFrom(s => s.FileUploadRecord.Path))
.ForMember(d => d.StudyCode, u => u.MapFrom(s => s.FileUploadRecord.StudyCode))
.ForMember(d => d.SubjectCode, u => u.MapFrom(s => s.FileUploadRecord.Subject.Code))
.ForMember(d => d.VisitName, u => u.MapFrom(s => s.FileUploadRecord.SubjectVisit.VisitName));
}
}
@@ -183,6 +183,7 @@ namespace IRaCIS.Application.Contracts
public class SelectionReviewerDTO : DoctorDTO
{
public bool IsVacation { get; set; }
public int DoctorTrialState { get; set; }
public bool IsEnroll { get; set; } = false;
@@ -133,6 +133,29 @@ namespace IRaCIS.Core.Application.Service
}
/// <summary>
/// 维护 医生基本信息 Code
/// </summary>
/// <returns></returns>
public async Task ServiceDoctorBasicInfo()
{
var doctorList = await _doctorRepository.Where(t => t.Code == 0 && t.TrialId == null).ToListAsync();
var code = await _doctorRepository.Select(t => t.Code).DefaultIfEmpty().MaxAsync();
foreach (var doctorItem in doctorList)
{
code++;
doctorItem.Code = code;
doctorItem.ReviewerCode = AppSettings.GetCodeStr(doctorItem.Code, nameof(Doctor));
await _doctorRepository.BatchUpdateNoTrackingAsync(x => x.Id == doctorItem.Id, x => new Doctor()
{
Code = doctorItem.Code,
ReviewerCode = doctorItem.ReviewerCode
});
}
}
/// <summary>
/// 新增修改 医生基本信息和工作
/// </summary>
@@ -211,6 +234,7 @@ namespace IRaCIS.Core.Application.Service
//重新插入新的 Title记录
updateModel.TitleIds.ForEach(titleId => adddata.Add(new DoctorDictionary() { DoctorId = updateModel.Id.Value, KeyName = StaticData.Title, DictionaryId = titleId }));
indto.ReviewerCode = doctor.ReviewerCode;
await _doctorDictionaryRepository.AddRangeAsync(adddata);
_mapper.Map(indto, doctor);
@@ -131,6 +131,7 @@ namespace IRaCIS.Core.Application.Service
#region
CreateMap<Doctor, SelectionReviewerDTO>()
.ForMember(d => d.IsVacation, u => u.MapFrom(t => t.VacationList.Any(x=>x.StartDate<DateTime.Now&&x.EndDate>DateTime.Now)))
.ForMember(d => d.DoctorUserName, u => u.MapFrom(t => t.UserRole.IdentityUser.UserName));
File diff suppressed because it is too large Load Diff
@@ -9,6 +9,99 @@ using IRaCIS.Core.Domain.Share;
using System.Collections.Generic;
namespace IRaCIS.Core.Application.ViewModel;
public class AuditRecordView : AuditRecordAddOrEdit
{
public Guid CreateUserId { get; set; }
public DateTime CreateTime { get; set; }
public Guid UpdateUserId { get; set; }
public DateTime UpdateTime { get; set; }
public new List<Guid> IdnetityUserIdList => IdentityUserList.Select(t => t.Id).ToList();
public List<AuditIdentiUserInfo> IdentityUserList { get; set; }
}
public class AuditIdentiUserInfo
{
public Guid Id { get; set; }
public string UserName { get; set; }
public string FullName { get; set; }
}
public class AuditRecordAddOrEdit
{
public Guid? Id { get; set; }
public string CompanyName { get; set; }
public string AuditContent { get; set; }
public DateOnly AuditTime { get; set; }
public DateTime? BeginTime { get; set; }
public DateTime? EndTime { get; set; }
public AuditState AuditState { get; set; }
public AuditType AuditType { get; set; }
public bool IsViewTrainingRecord { get; set; }
//public List<Guid> IdnetityUserIdList { get; set; }
}
public class AddOrDeleteAuditUserCommand
{
public Guid AuditRecordId { get; set; }
public List<Guid> IdentityUserIdList { get; set; }
public bool IsAdd { get; set; }
}
public class AuditRecordQuery : PageInput
{
public string? CompanyName { get; set; }
public string? AuditContent { get; set; }
public DateOnly? BeginAuditTime { get; set; }
public DateOnly? EndAuditTime { get; set; }
public DateTime? BeginTime { get; set; }
public DateTime? EndTime { get; set; }
public AuditState? AuditState { get; set; }
public AuditType? AuditType { get; set; }
public DateTime? BeginCreateTime { get; set; }
public DateTime? EndCreateTime { get; set; }
public string? IdentityUserName { get; set; }
public bool? IsViewTrainingRecord { get; set; }
}
public class SetAuditRecordPermissionCommand
{
public List<Guid> AuditDocumentIdList { get; set; }
public Guid AuditRecordId { get; set; }
public bool IsAuthorization { get; set; }
}
public class DeleteAudit
{
public Guid Id { get; set; }
@@ -24,16 +117,21 @@ public class DeleteAuditDocumentInDto
public List<Guid> Ids { get; set; }
}
public class GetAuditDocumentDataInDto:PageInput
public class GetAuditDocumentDataInDto : PageInput
{
public Guid? Id { get; set; }
public Guid? Id { get; set; }
public Guid? SelfId { get; set; }
public bool? IsAuthorization { get; set; }
public bool? IsCurrentAuditRecordAuthorization { get; set; }
public string Name { get; set; } = string.Empty;
//当前稽查记录Id
public Guid? AuditRecordId { get; set; }
}
public class GetAuditDocumentDataOutDto
@@ -53,17 +151,20 @@ public class AuditDocumentData : AuditDocumentUpdateDto
public int? Version { get; set; }
public int HistoricalVersionsCount { get; set; }
public List<AuditDocumentData> Children { get; set; }=new List<AuditDocumentData> (){ };
public List<AuditDocumentData> Children { get; set; } = new List<AuditDocumentData>() { };
public bool? IsCurrentAuditRecordAuthorization { get; set; }
}
public class AuditDocumentView : AuditDocumentAddOrEdit
{
public DateTime CreateTime { get; set; }
public DateTime UpdateTime { get; set; }
}
public class SetIsAuthorizationInDto
@@ -80,7 +181,7 @@ public class SetCurrentVersionInDto
public class GetHistoricalVersionInDto
{
public Guid Id { get; set; }
public Guid Id { get; set; }
}
public class GetHistoricalVersionOutDto
@@ -93,10 +194,10 @@ public class MovieFileOrFolderInDto
{
public List<Guid> Ids { get; set; }
public Guid ParentId { get; set; }
public Guid? ParentId { get; set; }
}
public class HistoricalVersionDto: AuditDocumentUpdateDto
public class HistoricalVersionDto : AuditDocumentUpdateDto
{
public bool IsCurrentVersion { get; set; } = false;
@@ -113,7 +214,7 @@ public class AuditDocumentUpdateDto
public AuditDocumentType AuditDocumentTypeEnum { get; set; }
public string? FileFormat { get; set; }
public string? FileFormat { get; set; }
public string? FilePath { get; set; }
@@ -133,34 +234,34 @@ public class GetBreadcrumbDataInDto
public Guid Id { get; set; }
}
public class AuditDocumentAddOrEdit: AuditDocumentUpdateDto
public class AuditDocumentAddOrEdit : AuditDocumentUpdateDto
{
public List<AuditDocumentAddOrEdit> Children { get; set; }=new List<AuditDocumentAddOrEdit>() { };
public List<AuditDocumentAddOrEdit> Children { get; set; } = new List<AuditDocumentAddOrEdit>() { };
public bool IsUpdate { get; set; } = true;
}
public class AuditDocumentQuery:PageInput
public class AuditDocumentQuery : PageInput
{
public AuditDocumentType? AuditDocumentTypeEnum { get; set; }
public string? FileFormat { get; set; }
public string? FilePath { get; set; }
public decimal? FileSize { get; set; }
public decimal? FileSize { get; set; }
public bool? IsAuthorization { get; set; }
public Guid? MainFileId { get; set; }
public Guid? MainFileId { get; set; }
public string? Name { get; set; }
public Guid? ParentId { get; set; }
}
public Guid? ParentId { get; set; }
}
@@ -92,11 +92,16 @@ namespace IRaCIS.Core.Application.Contracts
public class UnionDocumentWithConfirmInfoView : UnionDocumentView
{
public DocUserSignType SysDocUserSignType { get; set; }
public bool IsConfirmIdentityUserInner { get; set; }
public Guid TrialId { get; set; }
public bool IsNeedSendEmial { get; set; }
public DateTime UserCreateTime { get; set; }
public string EmailFromName { get; set; }
public DateTime? ConfirmTime { get; set; }
@@ -43,6 +43,10 @@ namespace IRaCIS.Core.Application.Contracts
[NotDefault]
public Guid TrialId { get; set; }
public bool? IsPublish { get; set; }
public string? FileTypeCode { get; set; }
}
///<summary> TrialDocumentAddOrEdit 列表查询参数模型</summary>
@@ -30,6 +30,7 @@ namespace IRaCIS.Core.Application.ViewModel
public new List<UserTypeEnum> CopyUserTypeList => TrialEmailNoticeUserList.Where(t => t.EmailUserType == EmailUserType.Copy).Select(t => t.UserType).ToList();
public List<CriterionType>? SysCriterionTypeList { get; set; }
}
@@ -97,6 +98,8 @@ namespace IRaCIS.Core.Application.ViewModel
[NotDefault]
public Guid TrialId { get; set; }
public string EmailTopic { get; set; } = string.Empty;
public EmailBusinessScenario? BusinessScenarioEnum { get; set; }
public CriterionType? CriterionTypeEnum { get; set; }
@@ -125,7 +128,7 @@ namespace IRaCIS.Core.Application.ViewModel
{
public Guid SubjectId { get; set; }
public Guid TrialReadingCriterionId { get; set; }
public CriterionType CriterionType { get; set; }
public EmailBusinessScenario BusinessScenarioEnum { get; set; }
}
@@ -0,0 +1,77 @@
//--------------------------------------------------------------------
// 此代码由liquid模板自动生成 byzhouhang 20240909
// 生成时间 2025-10-20 06:17:15Z
// 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
//--------------------------------------------------------------------
using System;
using IRaCIS.Core.Domain.Share;
using System.Collections.Generic;
namespace IRaCIS.Core.Application.Service.DTO;
public class UserAgreementView : UserAgreementAddOrEdit
{
public DateTime CreateTime { get; set; }
public DateTime UpdateTime { get; set; }
}
public class UserAgreementAddOrEdit
{
public Guid? Id { get; set; }
public DateTime EffectiveDate { get; set; }
public string FileContent { get; set; }
public string FileEnContent { get; set; }
public string FileName { get; set; }
public string FileVersion { get; set; }
public bool IsCurrentVersion { get; set; }
public DateTime UpdateDate { get; set; }
public UserAgreementType UserAgreementTypeEnum { get; set; }
}
public class SetUserAgreementCurrentVersionInDto
{
public Guid Id { get; set; }
}
public class GetUserAgreementByIdInDto
{
public Guid Id { get; set; }
}
public class UserAgreementQuery:PageInput
{
public DateTime? StartEffectiveDate { get; set; }
public DateTime? EndEffectiveDate { get; set; }
public string? FileContent { get; set; }
public string? FileName { get; set; }
public string? FileVersion { get; set; }
public bool? IsCurrentVersion { get; set; }
public DateTime? StartUpdateDate { get; set; }
public DateTime? EndUpdateDate { get; set; }
public UserAgreementType? UserAgreementTypeEnum { get; set; }
}
@@ -29,6 +29,7 @@ namespace IRaCIS.Core.Application.Service
//入组确认/PD确认
public async Task SendEnrollOrPdEmail(Guid visitTaskId, bool? isEnrollment, bool? isPDConfirm)
{
EmailBusinessScenario businessScenarioEnum;
@@ -109,8 +110,8 @@ namespace IRaCIS.Core.Application.Service
if (sendEmailConfig != null)
{
await SendEmailHelper.SendEmailAsync(sendEmailConfig);
var trialInfo = await _trialRepository.Where(t => t.Id == trialId).FirstOrDefaultAsync();
await SendEmailHelper.SendEmailAsync(sendEmailConfig, trialInfo);
}
}
@@ -19,6 +19,8 @@ namespace IRaCIS.Core.Application.Contracts
Task<IResponseOutput> UserConfirm(UserConfirmCommand userConfirmCommand);
Task<List<TrialUserDto>> GetTrialUserSelect(Guid trialId);
Task<IResponseOutput<PageOutput<UnionDocumentWithConfirmInfoView>>> GetSysDocumentConfirmList(SystemDocQuery inQuery);
//Task<PageOutput<DocumentUnionWithUserStatView>> GetTrialSystemDocumentList(DocumentTrialUnionQuery querySystemDocument);
//List<TrialUserUnionDocumentView> GetTrialUserDocumentList(Guid trialId);
@@ -0,0 +1,23 @@
//--------------------------------------------------------------------
// 此代码由liquid模板自动生成 byzhouhang 20240909
// 生成时间 2025-10-20 06:17:15Z
// 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
//--------------------------------------------------------------------
using System;
using IRaCIS.Core.Infrastructure.Extention;
using System.Threading.Tasks;
using IRaCIS.Core.Application.Service.DTO;
namespace IRaCIS.Core.Application.Service.Interface;
public interface IUserAgreementService
{
Task<PageOutput<UserAgreementView>> GetUserAgreementList(UserAgreementQuery inQuery);
Task<IResponseOutput> AddOrUpdateUserAgreement(UserAgreementAddOrEdit addOrEditUserAgreement);
Task<IResponseOutput> DeleteUserAgreement(Guid userAgreementId);
}

Some files were not shown because too many files have changed in this diff Show More