Compare commits

...

921 Commits

Author SHA1 Message Date
kuaifan
a3c0decaf0 build 2023-02-13 14:47:17 +08:00
kuaifan
d54e222ff2 perf: 优化会话删除 2023-02-13 14:43:26 +08:00
kuaifan
0c2ff1a000 perf: 优化表情输入 2023-02-13 13:58:28 +08:00
kuaifan
660851e81c perf: 优化根据会员筛选任务 2023-02-13 12:43:53 +08:00
kuaifan
e8e873f0da perf: drawio文件支持导出pdf文件 2023-02-13 11:35:07 +08:00
kuaifan
8b4244d237 完成drawio升级 2023-02-13 10:17:31 +08:00
kuaifan
472ece692d 升级 onlyoffice、php、fileview、drawio。(drawio 的 app.min.js、index.html 未完成) 2023-02-13 07:26:10 +08:00
kuaifan
d5d9eb93a3 perf: 优化任务提醒 2023-02-12 21:01:43 +08:00
kuaifan
81e4220367 perf: 优化state数据结构 2023-02-12 10:00:37 +08:00
kuaifan
0c7adaf67a perf: 聊天设置待办时可快速选择 2023-02-10 15:59:30 +08:00
kuaifan
08a6be9499 fix: 栏目内添加任务应该直接归属此栏目 2023-02-10 15:41:24 +08:00
kuaifan
63bb145224 perf: 完善LDAP 2023-02-09 18:32:48 +08:00
kuaifan
b60b572494 feat: 新增ldap帐号 2023-02-08 22:04:05 +08:00
kuaifan
c9e7fc14a1 faet: 机器人消息自动清理 2023-02-08 16:12:09 +08:00
kuaifan
1d46231f4d build 2023-02-07 13:14:50 +08:00
kuaifan
14827d8f25 perf: 优化移动端(pad) 2023-02-07 13:13:36 +08:00
kuaifan
4dee17983a perf: 优化消息列表数据 2023-02-07 13:13:16 +08:00
kuaifan
ca4f45ce0e build 2023-02-07 09:48:11 +08:00
kuaifan
008908bf92 perf: 优化消息对话框loading 2023-02-07 09:39:32 +08:00
kuaifan
0f0da2ad27 perf: 优化未读消息提示 2023-02-07 09:09:56 +08:00
kuaifan
2ebb963342 build 2023-02-07 04:38:52 +08:00
kuaifan
1b33f2fec7 perf: 优化移动端打开会话 2023-02-07 04:33:45 +08:00
kuaifan
8621ac16ce perf: 回复/引用机器人消息图标移位的问题 2023-02-07 04:30:46 +08:00
kuaifan
a0e84479f2 perf: 会话顶部提示剩余未读消息 2023-02-07 04:30:18 +08:00
kuaifan
daa88a2cc2 perf: 角标最大显示999 2023-02-07 02:44:21 +08:00
kuaifan
1d5cf81d9e fix: 任务首次发消息消失的情况 2023-02-07 02:30:43 +08:00
kuaifan
f578ac9e38 feat: 项目面板支持根据成员筛选任务 2023-02-07 02:13:03 +08:00
kuaifan
51b0aa2ed5 build 2023-02-02 07:45:41 +08:00
kuaifan
98889aa086 no message 2023-02-02 07:22:20 +08:00
kuaifan
296a77df8a perf: 导出签到/任务统计名字新增序号 2023-02-02 07:22:12 +08:00
kuaifan
7c88fbdfeb fix: 导出签到最多只导出20个的问题 2023-02-02 07:21:32 +08:00
kuaifan
2c99634c6f perf: 解决桌面端跨域cookie无法携带的问题 2023-02-02 07:08:18 +08:00
kuaifan
948cb55f89 fix: 截图快捷键的报错 2023-02-02 06:59:27 +08:00
kuaifan
4099d21d2c build 2023-02-01 12:15:38 +08:00
kuaifan
16ff7380a5 fix: 跨月签到记录不显示的问题 2023-02-01 12:14:12 +08:00
kuaifan
7109eb0238 feat: 上班打开每日开心/下班打卡心灵鸡汤 2023-02-01 12:13:37 +08:00
kuaifan
fff929d2b8 feat: 上班打卡新增每日开心 2023-02-01 08:43:28 +08:00
kuaifan
ec2d9b1ca5 build 2023-01-31 14:12:33 +08:00
kuaifan
6f7f29118f perf: 优化查看汇报详情loading 2023-01-31 14:08:09 +08:00
kuaifan
d96e9f4daa perf: 我的工作汇报列表显示汇报对象 2023-01-31 14:07:42 +08:00
kuaifan
b7ac923d36 perf: 工作汇报可留空汇报对象 2023-01-31 14:07:14 +08:00
kuaifan
88362cb1f9 fix: 跨日/周写工作报告导致的覆盖问题 2023-01-31 13:13:24 +08:00
kuaifan
92966ed516 perf: 优化签到打卡提醒 2023-01-31 12:05:25 +08:00
kuaifan
e3877a11a6 perf: 升级office套件 2023-01-31 07:12:06 +08:00
kuaifan
eabbccb980 build 2023-01-30 20:21:27 +08:00
kuaifan
92314495d2 perf: 优化签到通知 2023-01-30 20:18:01 +08:00
kuaifan
fd22cd3265 perf: 任务时间修改提醒 2023-01-30 18:08:30 +08:00
kuaifan
ffafe82e43 build 2023-01-30 17:51:25 +08:00
kuaifan
99453550b9 perf: 优化导出快速选择 2023-01-30 17:46:28 +08:00
kuaifan
b7686df7e6 perf: 优化修改员工mac地址备注 2023-01-30 17:46:02 +08:00
kuaifan
b2cbba08f2 perf: 工作报告模板新增项目名称 2023-01-30 15:24:14 +08:00
kuaifan
01e2fa4694 perf: 优化修改文件名称相同的情况 2023-01-30 15:20:46 +08:00
kuaifan
e45804174c perf: 优化任务APP/邮件提醒 2023-01-30 15:12:17 +08:00
kuaifan
31ea53cc15 no message 2023-01-29 16:00:14 +08:00
kuaifan
acfbcb41f1 build 2023-01-29 15:57:53 +08:00
kuaifan
844b2afaca fix: 时间快选 2023-01-29 15:51:35 +08:00
kuaifan
9df56f9556 perf: 优化主题跟随系统 2023-01-29 11:36:47 +08:00
kuaifan
dcf5d3e910 perf: 优化对话列表加载速度 2023-01-29 11:17:09 +08:00
kuaifan
78f197e7f9 签到通知加上设备备注 2023-01-29 08:05:27 +08:00
kuaifan
c35454f4e1 build 2023-01-29 00:13:20 +08:00
kuaifan
72c991b62b 最近签到记录加载优化 2023-01-29 00:13:08 +08:00
kuaifan
8d89ed6f35 签到通知开关 2023-01-28 23:36:12 +08:00
kuaifan
c21cb25eac build 2023-01-28 22:49:58 +08:00
kuaifan
10936bcdb5 perf: 签到成功通知 2023-01-28 22:44:52 +08:00
kuaifan
3b346046ae perf: 优化引用机器人消息看不到机器人图标的问题 2023-01-28 20:49:13 +08:00
kuaifan
98541007c4 perf: 导出签到/任务统计会员数增加到最多可选100个 2023-01-28 20:46:30 +08:00
kuaifan
786c059732 perf: 会员选择支持全选列表 2023-01-28 20:45:25 +08:00
kuaifan
653ae3ec39 perf: 个人签到设置显示最近签到数据 2023-01-27 14:05:54 +08:00
kuaifan
fa22554824 no message 2023-01-19 12:02:44 +08:00
kuaifan
a9fc93882c perf: 定时清理异步任务记录 2023-01-19 12:01:38 +08:00
kuaifan
907dc3c088 fix: 聊天页面出现滚动溢出的问题 2023-01-19 12:01:18 +08:00
kuaifan
d1afd54ea1 perf: 聊天消息长大超过5000转文件发送 2023-01-19 12:00:40 +08:00
kuaifan
c3abcf0469 feat: 免打扰会话取消邮件通知 2023-01-18 18:24:54 +08:00
kuaifan
3249501950 feat: 免打扰导致推送角标数量不对 2023-01-18 18:24:32 +08:00
kuaifan
271d2c97fa fix: 会话置顶失效 2023-01-18 18:07:42 +08:00
kuaifan
283c03bb9b build 2023-01-17 19:26:48 +08:00
kuaifan
e84961f20f feat: 消息会话支持免打扰 2023-01-17 19:16:26 +08:00
kuaifan
d89377a8fe fix: 标记已读失败 2023-01-16 15:53:40 +08:00
kuaifan
4b5aa7fa2a fix: 因机器人首次安装失败 2023-01-16 15:53:08 +08:00
kuaifan
5c6eb18d74 perf: 机器人支持静默推送 2022-12-18 16:53:47 +08:00
kuaifan
0c8a682fd0 build 2022-12-18 16:07:35 +08:00
kuaifan
f0d0ee69c8 fix: 下载文件出现文件损坏的情况 2022-12-18 16:02:57 +08:00
kuaifan
e18fcd5c43 fix: 清空已完成上传列表 2022-12-18 13:40:53 +08:00
kuaifan
27558c10cd build 2022-12-18 13:12:24 +08:00
kuaifan
98b6466dd5 feat: 新增机器人 2022-12-18 13:01:49 +08:00
kuaifan
090ce1cf18 perf: 优化签到数据 2022-12-13 19:05:06 +08:00
kuaifan
4ff23148e6 no message 2022-12-13 15:54:33 +08:00
kuaifan
111c236fd0 optimization umeng push 2022-12-13 15:46:47 +08:00
kuaifan
c94c4fbbdd build 2022-12-13 10:08:57 +08:00
kuaifan
4efbc7db25 perf: 优化签到数据结构 2022-12-13 10:04:27 +08:00
kuaifan
154b145e33 no message 2022-12-13 07:15:46 +08:00
kuaifan
b2652e98fc no message 2022-12-12 21:13:22 +08:00
kuaifan
1d90451b4e perf: mac地址已存在检查 2022-12-12 21:02:43 +08:00
kuaifan
4f67329154 build 2022-12-12 20:56:25 +08:00
kuaifan
01266ef4c4 perf: 限制截图快捷键 2022-12-12 20:08:44 +08:00
kuaifan
d5998efd91 perf: 查看我自己的签到数据 2022-12-12 20:08:24 +08:00
kuaifan
f917b39432 no message 2022-12-12 19:03:01 +08:00
kuaifan
95f9435ed1 同上 2022-12-12 11:21:04 +08:00
kuaifan
6f4945ac05 同上 2022-12-12 10:58:00 +08:00
kuaifan
6532382f43 perf: 优化导出签到 2022-12-12 10:50:29 +08:00
kuaifan
ab7c4ea73d build 2022-12-12 09:49:04 +08:00
kuaifan
df7a4c8b8f perf: update office manifest 2022-12-12 09:44:20 +08:00
kuaifan
2189337a1d perf: 优化打开个人会话速度 2022-12-12 09:43:59 +08:00
kuaifan
3416f41116 perf: 优化任务超时提醒文案 2022-12-12 09:37:51 +08:00
kuaifan
945e8607b6 perf: 优化导出统计 2022-12-12 09:32:15 +08:00
kuaifan
3e149ed3c2 no message 2022-12-12 09:27:33 +08:00
kuaifan
8b406c594b no message 2022-12-12 08:56:38 +08:00
kuaifan
b2bb2ddb1f perf: 完善签到功能 2022-12-12 08:38:16 +08:00
kuaifan
6937bbace8 perf: 完善签到功能 2022-12-12 01:37:58 +08:00
kuaifan
e3cdb20579 feat: 签到功能 2022-12-11 21:44:28 +08:00
kuaifan
2c7f4837d5 no message 2022-12-11 21:41:54 +08:00
kuaifan
3dc8269406 perf: 优化缓存 2022-12-10 21:21:42 +08:00
kuaifan
0077925fc0 perf: 优化缓存 2022-12-10 13:40:46 +08:00
kuaifan
529b8b682f perf: 缓存迁移 2022-12-10 13:35:10 +08:00
kuaifan
1b23b4621a perf: 优化本地数据 2022-12-09 23:06:51 +08:00
kuaifan
51d878469f build 2022-12-09 10:22:53 +08:00
kuaifan
8bbc1e8e4e perf: 文件右键菜单直接发送至会话 2022-12-09 10:19:58 +08:00
kuaifan
93996d7378 no message 2022-12-09 10:19:26 +08:00
kuaifan
9aac2c1549 perf: 优化删除或归档项目后数量更新 2022-12-09 09:28:38 +08:00
kuaifan
3cd4c23ef7 build 2022-12-08 22:53:08 +08:00
kuaifan
675987d1f2 perf: 消息搜索支持会员结果 2022-12-08 13:50:43 +08:00
kuaifan
6f255189bd no message 2022-12-08 12:38:02 +08:00
kuaifan
6bffc040b3 perf: 优化设置菜单 2022-12-08 12:36:50 +08:00
kuaifan
1513ebfe99 perf: 取消universal版本编译 2022-12-08 10:15:39 +08:00
kuaifan
ea4bf4f87a fix: 客户端打开出现报错 2022-12-08 10:08:04 +08:00
kuaifan
68ec8fb905 build 2022-12-07 23:24:24 +08:00
kuaifan
36bfaf3b94 perf: 优化客户端通知,Mac支持快速回复 2022-12-07 23:00:39 +08:00
kuaifan
4b90086e5a fix: 客户端提交截图空格报错的问题 2022-12-07 21:43:52 +08:00
kuaifan
f4a7b8e2bb perf: 群聊天点击头像进入个人对话 2022-12-07 20:21:49 +08:00
kuaifan
42031982a3 no message 2022-12-07 19:51:35 +08:00
kuaifan
4ef5deac69 perf: 优化会话保留 2022-12-07 19:51:03 +08:00
kuaifan
a3725ca164 no message 2022-12-07 18:24:55 +08:00
kuaifan
c4e360a64d fix: 上传文件没有读取权限 2022-12-07 14:17:47 +08:00
kuaifan
764d252632 build 2022-12-06 21:28:43 +08:00
kuaifan
4adaebb461 perf: 修改搜索成员文案 2022-12-06 14:37:09 +08:00
kuaifan
ac6d4cd522 perf: 聊天和文件模块不限制上传类型 2022-12-06 14:37:09 +08:00
kuaifan
ca3bb6b8f2 perf: 消息列表进行搜索时,条件过长,显示的无结果文案无法完全显示 2022-12-06 14:37:09 +08:00
kuaifan
a669e2b71b perf: 优化全局表格滚动条 2022-12-06 14:37:09 +08:00
kuaifan
27f3591fb5 feat: 添加考勤接口 2022-12-06 09:46:47 +08:00
kuaifan
5627baa1cc no message 2022-12-04 15:40:31 +08:00
kuaifan
19bcc8f3b6 no message 2022-12-04 15:15:59 +08:00
kuaifan
53dbfaffc7 build 2022-12-04 15:11:54 +08:00
kuaifan
0c1ec83796 perf: 优化滚动条 2022-12-04 14:59:31 +08:00
kuaifan
19fa164c75 perf: 优化消息数量 2022-12-03 17:56:42 +08:00
kuaifan
1c090c1e5e no message 2022-12-03 15:35:51 +08:00
kuaifan
fc4c44bbea perf: 优化网络错误提示框 2022-12-03 14:32:49 +08:00
kuaifan
453230d774 perf: 网络错误不清空仪表盘数据 2022-12-03 14:32:31 +08:00
kuaifan
3fcb4ee61e no message 2022-12-02 17:49:56 +08:00
kuaifan
f45ebb5dd2 no message 2022-12-02 17:45:29 +08:00
kuaifan
39cca6ec99 no message 2022-12-02 17:40:38 +08:00
kuaifan
65cbd108de no message 2022-12-02 17:27:08 +08:00
kuaifan
038814400d build 2022-12-02 13:54:25 +08:00
kuaifan
9cf3503a84 perf: 优化消息&符号 2022-12-02 13:36:40 +08:00
kuaifan
3a3ecd920f perf: 优化移动端网络错误提示 2022-12-02 13:33:26 +08:00
kuaifan
bebeb49ae4 no message 2022-12-02 11:55:14 +08:00
kuaifan
a925102881 no message 2022-12-02 09:29:08 +08:00
kuaifan
b0d83f7858 no message 2022-12-02 08:48:10 +08:00
kuaifan
6e5dea9cc7 no message 2022-12-02 07:44:04 +08:00
kuaifan
7b9b59008d no message 2022-12-02 07:42:21 +08:00
kuaifan
0f6c73699f no message 2022-12-02 02:17:32 +08:00
kuaifan
c7ff189b96 perf: 客户端新增截图快捷键 2022-12-02 01:59:50 +08:00
kuaifan
8a6c956940 perf: 截图dev 2022-12-01 21:37:01 +08:00
kuaifan
aad98623e8 build 2022-12-01 20:39:16 +08:00
kuaifan
a280c15c05 fix: 回复数量增长错误的问题 2022-12-01 18:41:51 +08:00
kuaifan
9f7151820f perf: 优化国际化提升访问速度 2022-12-01 18:18:45 +08:00
kuaifan
e5ee52ac7e no message 2022-12-01 18:17:56 +08:00
kuaifan
471708cedf no message 2022-11-29 15:10:11 +08:00
kuaifan
983b2e43e1 no message 2022-11-29 15:02:53 +08:00
kuaifan
535648f918 优化文件链接处理 2022-11-29 14:53:38 +08:00
kuaifan
3a69e1412d no message 2022-11-28 11:27:09 +08:00
kuaifan
c323dab2d4 build 2022-11-24 16:12:25 +08:00
kuaifan
de5cbe994d fix: safari浏览器兼容性 2022-11-24 16:02:59 +08:00
kuaifan
65dedcd9ff perf: 添加小兔子工作中表情 2022-11-24 16:02:39 +08:00
kuaifan
8dd717f2c9 no message 2022-11-24 14:46:03 +08:00
kuaifan
bc4b73cd2e fix: 对话窗口js报错 2022-11-24 14:44:25 +08:00
kuaifan
82d4077e4d 调整聊天内容分享文件的颜色 2022-11-24 14:37:20 +08:00
kuaifan
20dd624c77 ipad仪表盘取消显示客户端下载按钮 2022-11-24 14:22:07 +08:00
kuaifan
151dfce5d9 build 2022-11-23 22:54:25 +08:00
kuaifan
69ced1a02f fix: 链接消息处理问题 2022-11-23 22:51:26 +08:00
kuaifan
e6aef83bbe no message 2022-11-22 21:48:01 +08:00
kuaifan
15d14fc24e build 2022-11-22 21:46:53 +08:00
kuaifan
e198670132 perf: 头像标签部门过长显示优化 2022-11-22 18:11:48 +08:00
kuaifan
5c25bdfa91 perf: 聊天使用~符号分享文件 2022-11-22 17:55:59 +08:00
kuaifan
ab24ca5b4f perf: 修改任务时间添加备注 2022-11-22 11:45:19 +08:00
kuaifan
4a7cb9abc1 build 2022-11-21 14:44:30 +08:00
kuaifan
a63b867a5a perf: @结果相同时避免刷新 2022-11-21 14:22:14 +08:00
kuaifan
4d5c5b052d fix: 转让群主后窗口不关闭的问题 2022-11-21 14:09:35 +08:00
kuaifan
f4d9949ce9 fix: 通知消息显示UserAvatar 2022-11-21 14:08:47 +08:00
kuaifan
66c8a4db23 no message 2022-11-21 14:04:17 +08:00
kuaifan
542e3b9a49 build 2022-11-20 20:02:26 +08:00
kuaifan
bbd19045bc no message 2022-11-20 19:52:32 +08:00
kuaifan
0cc1469589 perf: 离职移交部门 2022-11-20 19:19:28 +08:00
kuaifan
0e71efd714 perf: 离职后退出所有群 2022-11-20 18:50:54 +08:00
kuaifan
623c32113d perf: 升级onlyoffice 2022-11-20 18:35:27 +08:00
kuaifan
087b48f252 build 2022-11-19 20:27:52 +08:00
kuaifan
cd56e3ec6f perf: 个人群支持转让群主 2022-11-19 20:25:03 +08:00
kuaifan
c1275bac56 build 2022-11-19 17:58:59 +08:00
kuaifan
558d004e35 perf: 头像显示部门 2022-11-19 17:41:36 +08:00
kuaifan
f963a00a05 build 2022-11-18 17:43:05 +08:00
kuaifan
1db5d3a3e0 perf: 支持选择已有群为创建部门群 2022-11-18 17:40:14 +08:00
kuaifan
c1381d5f67 perf: 优化表情发送后搜索关键词逻辑 2022-11-18 16:07:31 +08:00
kuaifan
374d3b85cf no message 2022-11-18 13:39:29 +08:00
kuaifan
963474f32e perf: 优化搜索表情 2022-11-18 11:58:29 +08:00
kuaifan
c2d852eb3a no message 2022-11-18 00:44:45 +08:00
kuaifan
df3b3bed98 no message 2022-11-18 00:18:00 +08:00
kuaifan
8cdfe01afa perf: 支持搜索在线表情 2022-11-18 00:16:16 +08:00
kuaifan
f49b157d13 build 2022-11-17 20:58:07 +08:00
kuaifan
865ac80b99 perf: 完善部门群组功能 2022-11-17 20:55:03 +08:00
kuaifan
c817e5815f perf: 选择器的优化 2022-11-17 10:00:56 +08:00
kuaifan
cbadf25623 feat: 新增部门功能 2022-11-16 23:40:34 +08:00
kuaifan
70ec88e57d no message 2022-11-16 20:52:17 +08:00
kuaifan
21b6479dbe build 2022-11-16 14:35:25 +08:00
kuaifan
c1be894d35 perf: task进程添加执行记录 2022-11-16 11:27:55 +08:00
kuaifan
99fc12e8c4 build 2022-11-15 22:39:28 +08:00
kuaifan
9ca6cb4fe4 fix: 未聊天过的任务无法发送聊天表情 2022-11-15 22:30:33 +08:00
kuaifan
05b05883fd feat: 聊天支持联想表情 2022-11-15 22:30:06 +08:00
kuaifan
c2d05fb846 fix: 离职仍受到推送的问题 2022-11-15 20:36:49 +08:00
kuaifan
57f4082cad fix: 任务详情无法右键的问题 2022-11-15 20:33:42 +08:00
kuaifan
a0174cdedd perf: 优化编辑器对象销毁的问题 2022-11-15 20:26:31 +08:00
kuaifan
4e8053470d no message 2022-11-11 16:59:31 +08:00
kuaifan
96a65866db no message 2022-11-11 14:58:04 +08:00
kuaifan
b0cf3ef560 perf: 优化会议聊天 2022-11-11 14:47:40 +08:00
kuaifan
eacc3cc6ef build 2022-11-10 22:48:08 +08:00
kuaifan
efcaabbbd8 fix: Android进入会议没有声音的问题 2022-11-10 22:45:54 +08:00
kuaifan
b556c3ba6c build 2022-11-10 16:20:14 +08:00
kuaifan
d02040e623 fix: iOS点击发送图片表情偶尔不显示的情况 2022-11-10 15:58:25 +08:00
kuaifan
a5abc6d6f7 perf: win通知标题 2022-11-10 15:40:15 +08:00
kuaifan
ed57411bbb perf: 主窗口可以单独关闭到后台 2022-11-10 14:34:52 +08:00
kuaifan
ae32b3c525 perf: 会议支持最小化窗口 2022-11-10 14:03:54 +08:00
kuaifan
31ef3a47e0 no message 2022-11-10 14:03:38 +08:00
kuaifan
1ac069aba4 perf: 优化录音、优化会议 2022-11-10 14:03:15 +08:00
kuaifan
1ca397d19b no message 2022-11-09 22:59:33 +08:00
kuaifan
bb4b09134f no message 2022-11-09 17:19:08 +08:00
kuaifan
78ab3c591a build 2022-11-09 16:42:20 +08:00
kuaifan
b5302758da no message 2022-11-09 16:24:55 +08:00
kuaifan
b6614b0974 feat: 新增任务过期app推送提醒 2022-11-09 14:49:47 +08:00
kuaifan
6112d6950a fix: 搜索文件选择在上层文件夹中显示时如果已经当前文件夹时没有反应的问题 2022-11-09 14:00:39 +08:00
kuaifan
440aa64fae perf: 优化客户端图片浏览器 2022-11-09 13:55:27 +08:00
kuaifan
9f8350da3f perf: 聊天内容图片支持下载 2022-11-09 11:45:43 +08:00
kuaifan
d50d32147b perf: 优化隐私政策弹窗 2022-11-09 11:16:59 +08:00
kuaifan
ec9544db0a fix: 离职员工仍可以接收到邮件的问题 2022-11-09 10:47:07 +08:00
kuaifan
798d62c3e2 fix: 首次聊天因网络问题聊天记录清空的情况 2022-11-09 10:33:08 +08:00
kuaifan
ab11badc84 fix: 编译已发送的消息中含有任务信息时的未定义问题 2022-11-09 09:30:57 +08:00
kuaifan
56867f942f perf: 自己可以转为任务协助人员 2022-11-09 09:25:12 +08:00
kuaifan
19353d3846 perf: 升级element/view-design 2022-11-08 17:00:46 +08:00
kuaifan
1adb0c55c8 no message 2022-11-07 11:56:56 +08:00
kuaifan
8ce7d3689f perf: 优化任务队列 2022-11-03 10:49:00 +08:00
kuaifan
acd7193e55 fix: 新安装出现无法打开其他人员会话的问题 2022-10-30 15:48:13 +08:00
kuaifan
7e74bfe7db perf: 升级election框架 2022-10-07 09:26:17 +08:00
kuaifan
ce162bf414 fix pdf view 2022-09-30 22:53:56 +08:00
kuaifan
0d74e8f04b no message 2022-09-30 17:00:23 +08:00
kuaifan
dd447de467 fix: umeng mi push 2022-09-30 15:46:08 +08:00
kuaifan
02e99db26e 同意用户协议之前不显示sso窗口 2022-09-22 17:52:59 +08:00
kuaifan
8e36b48476 no message 2022-09-22 16:19:30 +08:00
kuaifan
38c4e611c3 app打开默认弹出隐私弹窗 2022-09-22 16:08:31 +08:00
guoshuxin
10c68a7dc5 隐私政策(3) 2022-09-22 11:47:59 +08:00
guoshuxin
d164eb45d5 no message 2022-09-22 11:47:29 +08:00
kuaifan
7ab858088b 隐私政策同意相关 2022-09-22 11:16:28 +08:00
kuaifan
7ab20707c3 更换隐私政策cdn资源 2022-09-22 10:57:45 +08:00
kuaifan
3b36d2039c 更新隐私政策 2022-09-21 18:13:34 +08:00
guoshuxin
dd2e921667 隐私政策 2022-09-21 18:11:05 +08:00
kuaifan
7d772542ee perf: 搜索排序 2022-09-20 17:10:50 +08:00
kuaifan
ef85282777 build 2022-09-18 21:51:24 +08:00
kuaifan
a4b719641b perf: 操作离职隐藏退出群通知 2022-09-18 21:44:57 +08:00
kuaifan
dcede463d7 perf: 调整文件表格列表重命名输入框尺寸 2022-09-18 21:41:28 +08:00
kuaifan
8398248cb8 perf: 群内鼠标悬停成员头像显示聊天按钮 2022-09-18 21:40:42 +08:00
kuaifan
c421fc752b build 2022-09-18 21:26:16 +08:00
kuaifan
4bef2f5287 perf: 优化消息已读 2022-09-18 21:20:24 +08:00
kuaifan
2693cf17ea perf: 文件分享链接显示文件名称 2022-09-18 21:07:51 +08:00
kuaifan
c7b76e1009 feat: 工作报告支持批量标记已读 2022-09-18 19:18:54 +08:00
kuaifan
6c81f828bd no message 2022-09-10 08:33:20 +08:00
kuaifan
a5b0a4f752 build 2022-09-09 15:59:55 +08:00
kuaifan
ec73c3c785 perf: 优化通知 2022-09-09 15:56:06 +08:00
kuaifan
e4b38e4121 no message 2022-09-09 14:43:21 +08:00
kuaifan
9ae8b8dabb no message 2022-09-09 08:50:58 +08:00
kuaifan
46b79a20e3 no message 2022-09-08 21:49:40 +08:00
kuaifan
8508432297 no message 2022-09-08 21:47:55 +08:00
kuaifan
757606ecc5 update loading 2022-09-08 21:32:21 +08:00
kuaifan
081aadf468 build 2022-09-08 17:09:39 +08:00
kuaifan
6c760f159b perf: 优化聊天页面cpu占用 2022-09-08 17:08:22 +08:00
kuaifan
b2b2efc9a8 no message 2022-09-06 16:52:07 +08:00
kuaifan
2347d7c7b7 fix: 聊天、任务中的md文件预览无法滚动 2022-09-06 16:50:13 +08:00
kuaifan
03feb01af4 fix: 修改工作报告弹出多次成功提示的问题 2022-09-06 16:20:16 +08:00
kuaifan
eb6fa0cf63 fix: 安装数据库初始化失败 2022-09-06 16:04:22 +08:00
kuaifan
8c1cea6e6e fix: 消息已读 2022-09-06 16:04:01 +08:00
kuaifan
473eaa040f feat: window客户端任务栏闪烁 2022-09-06 16:03:26 +08:00
kuaifan
b47b3b4b3a perf: 升级框架内核 2022-09-06 16:02:36 +08:00
kuaifan
c185c8c22c no message 2022-07-21 11:08:35 +08:00
kuaifan
86c19ce355 perf: 优化消息发送失败 2022-07-21 10:44:35 +08:00
kuaifan
17e8dbb4d9 build 2022-07-20 14:57:11 +08:00
kuaifan
90fd2deba0 feat: 消息粘贴excel内容自动转成图片 2022-07-20 14:48:20 +08:00
kuaifan
6b00aad904 no message 2022-07-20 12:21:25 +08:00
kuaifan
d013ab1e3c perf: 优化发送图片出现空白的情况 2022-07-20 12:21:17 +08:00
kuaifan
9d14123a66 no message 2022-07-20 11:42:47 +08:00
kuaifan
49744a3671 fix: 无法添加任务的问题 2022-07-20 11:35:19 +08:00
kuaifan
b73c931b8b build 2022-07-19 20:49:33 +08:00
kuaifan
f474f111dc no message 2022-07-19 20:42:31 +08:00
kuaifan
c2fec1953b perf: 消息发送失败支持再次编辑 2022-07-19 16:41:34 +08:00
kuaifan
f7061d1a8d no message 2022-07-19 16:16:22 +08:00
kuaifan
67e2ec4093 perf: 对话支持拼音搜索 2022-07-19 16:14:44 +08:00
kuaifan
5f50fc7b3b perf: 新增注册自动进入全员群开关 2022-07-19 16:02:23 +08:00
kuaifan
3fa2814531 no message 2022-07-19 16:01:35 +08:00
kuaifan
4c6b58cbd5 perf: 移动客户端群消息通知加上群名称 2022-07-19 15:50:08 +08:00
kuaifan
444afc30e4 build 2022-07-19 09:47:17 +08:00
kuaifan
51763d7857 perf: 消息菜单新增复制图片、链接功能 2022-07-19 09:44:21 +08:00
kuaifan
108c82b353 build 2022-07-18 07:53:20 +08:00
kuaifan
ec1226a8ce 搜索文件菜单新增打开文件所在目录 2022-07-18 07:51:16 +08:00
kuaifan
127aaa30f4 仅定位消息位置才显示超时spinner 2022-07-18 07:48:16 +08:00
kuaifan
2462d457a7 build 2022-07-17 18:10:29 +08:00
kuaifan
73ecfc50aa 分类消息新增查看完整对话 2022-07-17 14:48:30 +08:00
kuaifan
95c11f0504 no message 2022-07-17 14:13:04 +08:00
kuaifan
3055eccb82 优化定位消息 2022-07-17 13:59:58 +08:00
kuaifan
aedae961ff 将消息设置新任务时图片使用原图 2022-07-17 13:16:48 +08:00
kuaifan
d38032bb2b perf: 按录音时停止正在播放的 2022-07-17 11:44:02 +08:00
kuaifan
bfd2917043 perf: 优化消息列表 2022-07-17 11:40:03 +08:00
kuaifan
b4085fd1d3 perf: 优化移除群成员与打开成员对话冲突的情况 2022-07-17 08:28:52 +08:00
kuaifan
40e258928e build 2022-07-16 22:13:57 +08:00
kuaifan
4b13e98d43 优化国际化 2022-07-16 21:54:09 +08:00
kuaifan
cfd3996af1 no message 2022-07-16 21:53:53 +08:00
kuaifan
d565501e04 perf: 优化国际化 2022-07-16 19:23:12 +08:00
kuaifan
c7dafa6199 build 2022-07-16 18:06:24 +08:00
kuaifan
1c3de2e889 no message 2022-07-16 17:37:12 +08:00
kuaifan
fa1c917b97 perf: 优化删除成员 2022-07-16 17:22:34 +08:00
kuaifan
29ba6713f0 build 2022-07-16 15:54:49 +08:00
kuaifan
10041b1a30 perf: 优化编辑带有图片的消息 2022-07-16 14:38:38 +08:00
kuaifan
3fe6ab0e79 前端文案“会员”改成“成员” 2022-07-15 18:46:18 +08:00
kuaifan
0a99ecdd9b perf: 支持搜索共享文件 2022-07-15 18:29:23 +08:00
kuaifan
ed9b93c22f build 2022-07-15 16:07:33 +08:00
kuaifan
c8e518b4e6 fix: 无法下载转发文件的问题 2022-07-15 14:59:10 +08:00
kuaifan
8f42e2924d fix: 无法操作离职的问题 2022-07-15 14:47:43 +08:00
kuaifan
d23bff5a48 fix: 编辑@消息的问题 2022-07-15 14:38:08 +08:00
kuaifan
1a1c03b355 优化复制消息 2022-07-15 14:21:01 +08:00
kuaifan
b20506cfa0 no message 2022-07-14 14:45:31 +08:00
kuaifan
0835e5b069 perf: 优化发消息时有时候出现空白需要滚动才出现内容的情况 2022-07-14 14:41:49 +08:00
kuaifan
26d0d07850 调整代码逻辑 2022-07-14 14:41:48 +08:00
韦荣超
b24eca0c5b build 2022-07-14 14:12:38 +08:00
韦荣超
aba04e63a1 fix: 删除账号-提示文案修改 2022-07-14 14:10:36 +08:00
韦荣超
999f22d8ff fix: 删除账号-提示文案修改 2022-07-14 14:07:15 +08:00
韦荣超
438d0344eb fix: 修改邮箱-”发送验证码“倒计时未结束修改 2022-07-14 14:03:55 +08:00
韦荣超
c99edc20a2 build: 删除账户必填加星号;邮箱验证码可以多发送 2022-07-14 12:26:45 +08:00
韦荣超
132dd8134a fix: 删除账户必填加星号;邮箱验证码可以多发送 2022-07-14 12:24:01 +08:00
韦荣超
16ac8d6b06 fix: 修改/删除账号接口无权限问题修改;根据env文件'SYSTEM_SETTING'变量判断是否能修改/删除账号 2022-07-14 11:01:09 +08:00
kuaifan
1a23fff2ce no message 2022-07-13 22:42:25 +08:00
kuaifan
774353c5fe no message 2022-07-13 22:14:55 +08:00
kuaifan
b6598a2b40 Merge branch 'develop' of ssh://git.gezi.vip:6006/gx/dootask
# Conflicts:
#	public/css/app.css
#	public/js/hash
2022-07-13 21:35:45 +08:00
kuaifan
0273fe76ca build 2022-07-13 21:12:29 +08:00
kuaifan
e0ec15653c fix: 音频/视频都不选时无法进入会议的情况 2022-07-13 20:49:56 +08:00
kuaifan
ed9c4f1413 文案统一:账号改成帐号 2022-07-13 20:40:40 +08:00
kuaifan
cda9be878b 调整代码 2022-07-13 20:34:42 +08:00
韦荣超
64d34deb6b build: 新增删除账户功能 2022-07-13 18:44:05 +08:00
韦荣超
1ea5bfa807 feat: 新增删除账户功能 2022-07-13 18:42:01 +08:00
韦荣超
5b9f85f442 build: 修改邮箱-校验邮箱去掉前后空格 2022-07-13 10:12:05 +08:00
韦荣超
8089085bb4 fix: 修改邮箱-校验邮箱去掉前后空格 2022-07-13 10:09:58 +08:00
韦荣超
18b8ba5c5f build: 新增修改邮箱功能 2022-07-12 18:25:46 +08:00
韦荣超
d80943ab6b feat: 新增修改邮箱功能 2022-07-12 18:23:04 +08:00
kuaifan
77ebb225c4 no message 2022-07-12 13:46:33 +08:00
kuaifan
28850338dd no message 2022-07-12 13:38:21 +08:00
kuaifan
1daba51286 build 2022-07-12 13:25:39 +08:00
kuaifan
2a2033ba21 fix: 待办数量与实际的数量不一致 2022-07-12 13:13:32 +08:00
kuaifan
367d7a0ae8 perf: 角标提示待办跟@一起 2022-07-12 13:13:10 +08:00
kuaifan
1ef33700ee perf: 移动端任务打开聊天按钮优化 2022-07-12 12:30:54 +08:00
kuaifan
0bf98ec9f8 perf: 支持转发给最近聊天 2022-07-12 12:15:19 +08:00
kuaifan
fcbbd2b64c no message 2022-07-12 10:37:46 +08:00
kuaifan
4844cc1ea1 perf: 可以通过群成员点击打开对话 2022-07-11 18:09:39 +08:00
kuaifan
0f076b4a16 perf: 展示消息回应详情 2022-07-11 17:42:11 +08:00
kuaifan
8e9cd253e4 搜索关键词去除  2022-07-11 07:48:47 +08:00
kuaifan
43a4e5741a perf: 优化通知类消息字符长度 2022-07-11 07:30:22 +08:00
kuaifan
4561efc3ce perf: 去除通知里的  2022-07-10 21:16:46 +08:00
kuaifan
d0d6965c52 Merge remote-tracking branch 'A_github_pro/master' 2022-07-09 01:24:08 +08:00
kuaifan
07d395e7b2 no msg 2022-07-09 01:22:17 +08:00
kuaifan
f61ad6ca5a no message 2022-07-08 23:55:03 +08:00
kuaifan
82010e7225 no message 2022-07-08 23:11:07 +08:00
kuaifan
36ea882869 no message 2022-07-08 22:59:35 +08:00
kuaifan
a6a0133d37 no message 2022-07-08 22:57:12 +08:00
kuaifan
bd6f6a7106 no message 2022-07-08 22:55:52 +08:00
kuaifan
c3f7ea8ac7 no message 2022-07-08 21:20:02 +08:00
kuaifan
c0f7d042c6 客户端点击版本号查看服务器地址 2022-07-08 21:17:04 +08:00
kuaifan
c3588b4ac3 调整接口版本限制 2022-07-08 20:09:30 +08:00
kuaifan
13049f63c1 去除HitoseaTask,统一使用DooTask 2022-07-08 20:09:08 +08:00
kuaifan
b26cea1aa3 优化头像地址 2022-07-08 19:20:07 +08:00
kuaifan
7fb6135680 build 2022-07-08 18:44:15 +08:00
kuaifan
e60dd0a93e 修复浏览器ua错误的问题 2022-07-08 18:44:06 +08:00
kuaifan
7c11e32b71 fix: 任务列表无法修改优先级的问题 2022-07-08 18:35:40 +08:00
kuaifan
49a361bf66 no message 2022-07-08 17:06:29 +08:00
kuaifan
e4e4224716 升级electron 2022-07-08 17:06:06 +08:00
kuaifan
1542720bc4 优化推送筛选 2022-07-08 17:01:35 +08:00
kuaifan
a5d0cae884 build 2022-07-08 16:34:42 +08:00
kuaifan
6e03415847 no message 2022-07-08 16:23:24 +08:00
kuaifan
f6b7eca0f5 perf: 客户端窗口激活自动获取聊天焦点 2022-07-08 16:23:14 +08:00
kuaifan
82fad2c17d perf: 个人对话窗口支持拨打电话 2022-07-08 16:06:18 +08:00
kuaifan
c13f8bbcc0 perf: 新增联系电话 2022-07-08 15:24:24 +08:00
kuaifan
97d041076d perf: [notice|tag|todo]类型的消息静默推送 2022-07-08 14:45:42 +08:00
kuaifan
d9696d2398 perf: 只给一个月内登录App的帐号推送 2022-07-08 14:08:25 +08:00
kuaifan
883e5c26d9 perf: 显示待办消息数量 2022-07-08 14:00:16 +08:00
kuaifan
6412efd031 no message 2022-07-08 11:50:58 +08:00
kuaifan
ef7400e9d2 build 2022-07-08 09:27:53 +08:00
kuaifan
c76e8d2677 no message 2022-07-08 09:14:56 +08:00
kuaifan
e97f2271fa perf: 待办消息支持指定成员 2022-07-08 09:14:40 +08:00
kuaifan
488259d442 优化待办、回复抽屉 2022-07-08 07:43:34 +08:00
kuaifan
5bbbbb0b02 build 2022-07-07 20:30:42 +08:00
kuaifan
6e186e9186 perf: 支持查看待办完成情况 2022-07-07 20:26:24 +08:00
kuaifan
8ca6b53224 no message 2022-07-07 18:05:07 +08:00
kuaifan
2af899cfea 仅支持设此待办人员取消 2022-07-07 14:45:22 +08:00
kuaifan
72a38f4b45 no message 2022-07-07 14:37:25 +08:00
kuaifan
f24a8230c1 build 2022-07-07 13:43:52 +08:00
kuaifan
5651713032 待办完成功能 2022-07-07 13:24:09 +08:00
kuaifan
3829ec1308 feat: 新增待办消息功能 2022-07-07 09:50:32 +08:00
kuaifan
d085da07cd no message 2022-07-07 09:49:06 +08:00
kuaifan
937d996458 perf: 优化抖动提示 2022-07-07 00:05:55 +08:00
kuaifan
6a58f07a91 perf: 消息新增#我协助的任务 2022-07-06 23:27:12 +08:00
kuaifan
2366e4df17 fix: 任务详情不出现聊天的情况 2022-07-06 23:13:43 +08:00
kuaifan
ed937f5f31 no message 2022-07-06 12:02:24 +08:00
kuaifan
ca65741392 build 2022-07-05 14:41:16 +08:00
kuaifan
ef2b1dd321 no message 2022-07-05 14:34:23 +08:00
kuaifan
8eb956f831 fix: 移动文件夹内文件所有者不变的问题 2022-07-05 12:59:50 +08:00
kuaifan
c3cccbb2c8 perf: 回复或修改消息发送时立即隐藏引用显示 2022-07-05 12:17:36 +08:00
kuaifan
e861e8d878 perf: 搜索对话可以搜索远程的对话 2022-07-05 12:06:58 +08:00
kuaifan
1fb70a4456 no message 2022-07-05 11:34:17 +08:00
kuaifan
277869c9d1 fix: 通知消息一直未读的情况 2022-07-05 11:33:46 +08:00
kuaifan
a13da976ae build 2022-07-05 00:25:33 +08:00
kuaifan
0f6886656e fix: 移动文件所有者错误 2022-07-05 00:14:47 +08:00
kuaifan
63cd4649dd no message 2022-07-05 00:03:56 +08:00
kuaifan
673038bf63 no message 2022-07-04 23:07:04 +08:00
kuaifan
625bb97c35 no message 2022-07-04 16:29:43 +08:00
kuaifan
1f71cc827c no message 2022-07-04 16:24:25 +08:00
kuaifan
50057894e8 build 2022-07-04 16:17:06 +08:00
kuaifan
824399bbf1 perf: 所有项目列表支持筛选个人项目 2022-07-04 16:13:57 +08:00
kuaifan
fb74ef4843 feat: 新增全员群组 2022-07-04 16:01:03 +08:00
kuaifan
e5cbeb032e perf: 调整消息标签位置 2022-07-04 14:41:59 +08:00
kuaifan
2cf68e96c3 build 2022-07-04 00:24:27 +08:00
kuaifan
92ea7eaab7 fix: 无法通过项目点击聊天的情况 2022-07-04 00:20:14 +08:00
kuaifan
8ca22fc209 build 2022-07-03 21:46:10 +08:00
kuaifan
e80a38fa78 no message 2022-07-03 21:42:59 +08:00
kuaifan
9c861ec58c feat: 支持编辑已发送的消息 2022-07-03 21:42:49 +08:00
kuaifan
6b9c20bf93 perf: 添加邮件忽略功能 2022-07-03 18:00:51 +08:00
kuaifan
6cf9c1f4f2 no message 2022-07-03 17:25:51 +08:00
kuaifan
b73e1c27eb 解决消息筛选时计算错误的情况 2022-07-03 10:06:26 +08:00
kuaifan
69fb875421 优化消息滚动 2022-07-03 00:12:32 +08:00
kuaifan
aa7d93e97e no message 2022-07-01 18:09:45 +08:00
kuaifan
bf136c9b2f build 2022-07-01 17:07:21 +08:00
kuaifan
256143fd21 no message 2022-07-01 16:57:03 +08:00
kuaifan
26628b1c89 no message 2022-07-01 16:28:19 +08:00
kuaifan
08883ce558 no message 2022-07-01 16:27:13 +08:00
kuaifan
a9950a4365 点击文件直接下载 2022-07-01 16:17:11 +08:00
kuaifan
42bc25381c no message 2022-07-01 16:11:37 +08:00
kuaifan
830552c121 no message 2022-07-01 15:23:47 +08:00
kuaifan
2728d3c093 build 2022-07-01 11:38:05 +08:00
kuaifan
81b07ea6e8 fix: 任务聊天出现空白的情况 2022-07-01 11:33:34 +08:00
kuaifan
27441eb82e 消息新增链接类型筛选 2022-07-01 11:22:58 +08:00
kuaifan
49f8e23aae feat: 新增消息类型筛选 2022-06-30 19:54:48 +08:00
kuaifan
a368e00b2f no message 2022-06-30 16:07:50 +08:00
kuaifan
500ed3a4d7 feat: 新增标注消息功能 2022-06-30 15:56:09 +08:00
kuaifan
83765c9d2d no message 2022-06-30 11:05:36 +08:00
kuaifan
4c6a20601c perf: 优化协助任务的更新 2022-06-29 19:17:39 +08:00
kuaifan
d6b4cd78d0 build 2022-06-29 13:40:58 +08:00
kuaifan
460ebeb8c5 no message 2022-06-29 13:40:26 +08:00
kuaifan
513bc28686 perf: 管理员新增修改成员邮箱功能 2022-06-29 13:40:03 +08:00
kuaifan
ef6b4e8149 fix: 新建文件不显示的问题 2022-06-29 13:39:33 +08:00
kuaifan
644f415d46 优化确认弹窗 2022-06-29 13:30:40 +08:00
kuaifan
575e569048 优化确认输入框 2022-06-29 11:28:15 +08:00
kuaifan
1a31939eaa no message 2022-06-28 15:17:56 +08:00
kuaifan
d3dcce96fa no message 2022-06-28 15:03:29 +08:00
kuaifan
8669acc663 no message 2022-06-28 14:51:04 +08:00
kuaifan
a9c37cc5a6 优化协助任务列表 2022-06-28 14:49:31 +08:00
kuaifan
d1f22ead93 build 2022-06-28 11:36:17 +08:00
kuaifan
305e67c454 App显示隐私政策 2022-06-28 10:58:21 +08:00
kuaifan
86ceae9a1e build 2022-06-28 10:22:50 +08:00
kuaifan
f1dd5249ae no message 2022-06-28 10:21:33 +08:00
kuaifan
47f3dadfd4 fix: 输入框粘贴后出错的问题 2022-06-28 10:09:56 +08:00
kuaifan
66b7a00b5c perf: 支持查看回复列表 2022-06-28 10:01:09 +08:00
kuaifan
13ca1b18d6 no message 2022-06-28 08:24:14 +08:00
kuaifan
688fcd3c77 no message 2022-06-27 20:31:35 +08:00
kuaifan
58856c6620 perf: 优化消息分页加载 2022-06-27 20:17:59 +08:00
kuaifan
17ce620abf no message 2022-06-27 13:31:17 +08:00
kuaifan
5bb80b5f50 no message 2022-06-27 10:36:15 +08:00
kuaifan
625908dc1b build 2022-06-27 10:18:23 +08:00
kuaifan
ad2b456f07 fix: 任务重复周期 2022-06-27 10:17:23 +08:00
kuaifan
c6402c72f9 显示回复数量 2022-06-27 09:19:36 +08:00
kuaifan
f34133f63e perf: 添加消息回复量 2022-06-24 21:08:53 +08:00
kuaifan
dfb6f23b60 no message 2022-06-24 20:42:46 +08:00
kuaifan
0d158d2403 修复弹窗的消息菜单位置不对 2022-06-24 20:42:24 +08:00
kuaifan
7a63f6d68a no message 2022-06-24 17:21:05 +08:00
kuaifan
16bbab366e build 2022-06-24 15:45:40 +08:00
kuaifan
e63e025bda feat: 新增任务重复周期 2022-06-24 15:40:53 +08:00
kuaifan
d4d3f1245b perf: 仪表盘列表新增显示协助的任务 2022-06-24 10:48:47 +08:00
kuaifan
707c74264b no message 2022-06-23 20:34:05 +08:00
kuaifan
23b91d37ab build 2022-06-23 19:42:24 +08:00
kuaifan
adf9225180 feat: 支持通过拼音搜索联系人 2022-06-23 19:36:10 +08:00
kuaifan
d386b4e7ac perf: 优化@其他成员在线状态 2022-06-23 19:14:45 +08:00
kuaifan
f13472f088 fix: 任务成员应该禁止退出任务群聊 2022-06-23 19:03:39 +08:00
kuaifan
5ec56b4a9e perf: 仅(群聊)且(是群主或没有群主)才可以@成员以外的人 2022-06-23 18:53:57 +08:00
kuaifan
567a77b0be no message 2022-06-23 15:08:49 +08:00
kuaifan
a39802ea48 build 2022-06-23 14:57:41 +08:00
kuaifan
045d585df4 perf: 优化pdf浏览方式 2022-06-23 14:37:54 +08:00
kuaifan
d0bc85346e no message 2022-06-23 11:37:05 +08:00
kuaifan
dee12941c0 fix: 撤回消息导致未读数错误的问题 2022-06-23 10:22:22 +08:00
kuaifan
65018aab5b build 2022-06-23 00:06:31 +08:00
kuaifan
77761d9ec4 no message 2022-06-22 23:39:54 +08:00
kuaifan
68eace11de fix: 部分长按菜单移位的问题 2022-06-22 23:39:43 +08:00
kuaifan
db4678770d no message 2022-06-22 19:31:50 +08:00
kuaifan
a798c61ded build 2022-06-22 19:23:57 +08:00
kuaifan
822bc3ea69 perf: 支持@群聊以外成员 2022-06-22 19:22:54 +08:00
kuaifan
3aefe99bd9 perf: 项目群、任务群可添加成员 2022-06-22 16:21:55 +08:00
kuaifan
bebef77e5c no message 2022-06-21 22:38:58 +08:00
kuaifan
b5ae9c6a8a no message 2022-06-21 22:28:09 +08:00
kuaifan
5f98cc93c7 fix: 无法点击图片预览的问题 2022-06-21 17:30:39 +08:00
kuaifan
c1d27399d9 no message 2022-06-21 15:38:52 +08:00
kuaifan
25f244c5f1 no message 2022-06-21 10:51:30 +08:00
kuaifan
561e205481 no message 2022-06-21 09:59:08 +08:00
kuaifan
9dd22dba19 no message 2022-06-20 15:21:26 +08:00
kuaifan
43b2b4dc3d no message 2022-06-20 07:49:09 +08:00
kuaifan
ce5948d87a no message 2022-06-20 07:31:59 +08:00
kuaifan
3a0d967800 no message 2022-06-20 07:28:25 +08:00
kuaifan
093de52230 no message 2022-06-20 07:15:34 +08:00
kuaifan
f54f86f2b5 no message 2022-06-20 02:25:54 +08:00
kuaifan
d167a91a07 feat: 支持搜索历史消息 2022-06-20 02:23:20 +08:00
kuaifan
4384a2dd91 build 2022-06-19 12:17:01 +08:00
kuaifan
573a5c03e4 no message 2022-06-19 12:14:52 +08:00
kuaifan
7c885cad5b perf: 优化文件操作菜单样式 2022-06-19 11:04:17 +08:00
kuaifan
ba65c21a0b perf: 文件浏览支持滑动返回上一个文件夹 2022-06-19 10:42:39 +08:00
kuaifan
073b1937f0 no msg 2022-06-19 10:07:19 +08:00
kuaifan
793f6152ff 阻止长按图片 2022-06-19 09:26:17 +08:00
kuaifan
cf0a431fff perf: 桌面客户端出现无法关闭窗口的情况 2022-06-19 09:21:08 +08:00
kuaifan
ba2d81da50 build 2022-06-19 02:16:34 +08:00
kuaifan
c278806548 perf: 优化触发回复页面滚动 2022-06-19 02:13:36 +08:00
kuaifan
21ad0632b4 优化文件上传前 2022-06-19 02:06:10 +08:00
kuaifan
8365f69de6 优化全局加载器 2022-06-19 02:05:48 +08:00
kuaifan
db28811027 修改文件最大上传1G 2022-06-19 01:19:11 +08:00
kuaifan
a69a4befc5 no message 2022-06-19 01:03:22 +08:00
kuaifan
d7cc0064b2 build 2022-06-19 00:17:04 +08:00
kuaifan
b3c227d3cb perf: 优化对话详情页 2022-06-19 00:15:31 +08:00
kuaifan
09fd8aa1b0 build 2022-06-18 00:30:51 +08:00
kuaifan
58e8b7b0a0 头像缓存机制 2022-06-18 00:22:58 +08:00
kuaifan
9ea250278d 解决消息滚动出现白屏的情况 2022-06-18 00:22:41 +08:00
kuaifan
17c9a8b3fa no message 2022-06-17 17:57:39 +08:00
kuaifan
9ff2f24529 no message 2022-06-17 17:48:31 +08:00
kuaifan
0ab321f184 build 2022-06-17 16:13:59 +08:00
kuaifan
500e666416 消息列表滚动到底部 2022-06-17 16:04:30 +08:00
kuaifan
88316fda1c 定位回复消息 2022-06-17 15:25:13 +08:00
kuaifan
3bd2e40a98 表情描述 2022-06-17 11:56:03 +08:00
kuaifan
d5c8b33678 定位回复消息位置 2022-06-17 11:50:07 +08:00
kuaifan
f3e9ac2b56 优化等待 2022-06-17 11:19:06 +08:00
kuaifan
3cc6a3317e 优化回复消息 2022-06-17 10:42:06 +08:00
kuaifan
3728b1d3a0 feat: 新增回复消息功能 2022-06-17 08:33:47 +08:00
kuaifan
4c163a8092 perf: 取消置顶标签 2022-06-16 23:32:38 +08:00
kuaifan
bacbf2e3cb perf: 优化移动客户端滚动穿透 2022-06-16 23:14:00 +08:00
kuaifan
f211fe0ae4 build 2022-06-16 20:27:29 +08:00
kuaifan
e67dc0a637 处理消息阅读不消数的问题 2022-06-16 18:33:27 +08:00
kuaifan
3f959918af 优化消息列表 2022-06-16 17:51:47 +08:00
kuaifan
d9cd7a93cc perf: 优化消息列表 2022-06-16 12:45:10 +08:00
kuaifan
4fcdeb0313 头像缓存 2022-06-15 18:40:23 +08:00
kuaifan
8839fb0738 no message 2022-06-14 19:21:20 +08:00
kuaifan
26de1b1745 no message 2022-06-14 18:44:08 +08:00
kuaifan
b1700ca4e6 build 2022-06-14 18:17:31 +08:00
kuaifan
f7f668a032 perf: 默认使用文字头像 2022-06-14 18:11:54 +08:00
kuaifan
d8d707175c 文件链接新增点击复制按钮 2022-06-14 14:48:29 +08:00
kuaifan
de45332543 优化窗口激活机制 2022-06-14 14:19:51 +08:00
kuaifan
dbba2a1b8f no message 2022-06-14 11:56:05 +08:00
kuaifan
8b394819c4 Mac 触发升级时跳动Dock图标 2022-06-14 10:19:36 +08:00
kuaifan
74b1f65e0f perf: 使用系统浏览器打开新窗口链接 2022-06-14 10:13:49 +08:00
kuaifan
8110930458 离线时更新会员最后在线时间 2022-06-14 10:04:21 +08:00
kuaifan
02b2e8cffb 优化消息加载 2022-06-14 09:58:45 +08:00
kuaifan
643ccc4880 取消会员信息加载覆盖层 2022-06-14 09:58:08 +08:00
kuaifan
4deed559c9 build 2022-06-13 16:19:07 +08:00
kuaifan
be3fcb55fb perf: 优化通讯录刷新机制 2022-06-13 16:17:47 +08:00
kuaifan
11308829a6 fix: 员工删除后对话还存在的问题 2022-06-13 15:53:58 +08:00
kuaifan
e2b2da8b46 取消 Content-Security-Policy 2022-06-13 13:51:13 +08:00
kuaifan
b4bd6d17bf no message 2022-06-13 12:31:40 +08:00
kuaifan
08f3925af4 no message 2022-06-13 12:25:31 +08:00
kuaifan
c739448e3c no message 2022-06-13 12:04:11 +08:00
kuaifan
7a3f817469 build 2022-06-13 12:03:12 +08:00
kuaifan
9d522d3c3e perf: 通知自动关闭 2022-06-13 12:01:36 +08:00
kuaifan
c46567782e no message 2022-06-13 11:56:12 +08:00
kuaifan
e071209bb8 修复因为共享文件出现的文件目录层级的问题 2022-06-13 11:15:07 +08:00
kuaifan
2efedf225f no message 2022-06-13 10:39:55 +08:00
kuaifan
5a08c3dec4 perf: 优化excel菜单 2022-06-13 10:12:37 +08:00
kuaifan
734abe59ee no message 2022-06-13 08:47:42 +08:00
kuaifan
6759e2b30c no message 2022-06-12 20:43:59 +08:00
kuaifan
324d7ea487 build 2022-06-12 20:34:51 +08:00
kuaifan
4fd07e4c6e perf: 优化文件重名的问题 2022-06-12 20:32:34 +08:00
kuaifan
afea981a42 no message 2022-06-12 19:16:14 +08:00
kuaifan
df9eaf6e0a no message 2022-06-12 19:07:47 +08:00
kuaifan
caa0fc0a8a Merge branch 'master' of github.com:kuaifan/dootask-pro 2022-06-12 18:39:07 +08:00
kuaifan
6bd8c590ab perf: 优化图片预览缩放 2022-06-12 18:30:11 +08:00
kuaifan
219fbc7fb5 build 2022-06-12 00:31:57 +08:00
kuaifan
cbaa5c3464 build 2022-06-11 23:50:42 +08:00
kuaifan
76e66ab433 移动客户端文件使用新窗口打开 2022-06-11 23:48:43 +08:00
kuaifan
0150b41e17 no message 2022-06-11 22:55:47 +08:00
kuaifan
2dbdc3b780 no message 2022-06-11 22:28:37 +08:00
kuaifan
e7cb48f0b1 整理iframe事件 2022-06-11 21:19:52 +08:00
kuaifan
cae7184a7e no message 2022-06-11 14:50:38 +08:00
kuaifan
5ba63d951c Android取消文件打开动画 2022-06-11 14:41:52 +08:00
kuaifan
b758133f63 perf: 优化预览文件 2022-06-11 14:36:48 +08:00
kuaifan
4024aca002 perf: 优化同时加载同个任务 2022-06-11 11:13:19 +08:00
kuaifan
7826cc8f68 no message 2022-06-11 01:46:00 +08:00
kuaifan
bf9ecb78f8 no message 2022-06-11 01:32:52 +08:00
kuaifan
8783b85b00 no message 2022-06-11 01:27:50 +08:00
kuaifan
d72e9f678f no message 2022-06-10 21:23:16 +08:00
kuaifan
fec3dcc65f build 2022-06-10 21:03:26 +08:00
kuaifan
300798981d perf: 优化键盘关闭 2022-06-10 20:59:01 +08:00
kuaifan
58b5843dd9 build 2022-06-10 17:48:20 +08:00
kuaifan
6b97696593 no message 2022-06-10 17:32:30 +08:00
kuaifan
18e41eba4d fix: win客户端升级签名报错的问题 2022-06-10 17:32:23 +08:00
kuaifan
34c6addde3 perf: 优化office右上角菜单按钮重叠的问题 2022-06-10 17:32:03 +08:00
kuaifan
5655c5440f build 2022-06-10 14:36:45 +08:00
kuaifan
dd25561338 no message 2022-06-10 14:28:34 +08:00
kuaifan
eaabc5852c perf: 优化录音效果 2022-06-10 10:30:40 +08:00
kuaifan
dbb1033911 perf: 移动端只读文件 2022-06-10 10:25:06 +08:00
kuaifan
7c287f6ae5 perf: 优化任务窗口输入框草稿 2022-06-10 10:03:45 +08:00
kuaifan
b9ddc0cf1f no message 2022-06-10 09:55:57 +08:00
kuaifan
9a9ff64576 perf: 头像显示已离职效果 2022-06-10 08:58:33 +08:00
kuaifan
02f75726c5 perf: 文件文本编辑支持command+s保存 2022-06-10 08:17:51 +08:00
kuaifan
b4c1d05d2e fix: 文件md、text互转时文件格式没有变的问题 2022-06-09 16:29:04 +08:00
kuaifan
0035789951 perf: 长文本消息的处理 2022-06-09 16:11:15 +08:00
kuaifan
a11eb11166 perf: 客户端新窗口皮肤不统一的问题 2022-06-09 14:50:36 +08:00
kuaifan
020be75f7b fix: 移动客户端访问本站链接出现需要登录的情况 2022-06-09 12:21:56 +08:00
kuaifan
66961197f6 fix: 不是任务负责人不能通过小窗口发送任务消息的问题 2022-06-09 11:44:30 +08:00
kuaifan
f05914f2a1 fix: 桌面客户端任务独立窗口无法操作任务状态的问题 2022-06-09 11:23:19 +08:00
kuaifan
e161912496 perf: 流程图支持搜索远程图标 2022-06-09 10:29:37 +08:00
kuaifan
abad2c2cc9 no message 2022-06-08 13:16:31 +08:00
kuaifan
efac5401bf no message 2022-06-08 11:42:04 +08:00
kuaifan
e463a844ff build 2022-06-08 11:40:10 +08:00
kuaifan
ddb7d1a6db perf: 升级office插件 2022-06-08 11:37:54 +08:00
kuaifan
c4faa0a316 build 2022-06-08 08:20:53 +08:00
kuaifan
eca066cbd0 perf: 优化消息已读未读 2022-06-08 08:17:36 +08:00
kuaifan
e0bd225ac2 no message 2022-06-07 23:37:55 +08:00
kuaifan
a5571ba1d4 build 2022-06-07 20:32:47 +08:00
kuaifan
a87f2261ce 优化客户端升级提示 2022-06-07 20:29:34 +08:00
kuaifan
33db978e36 perf: 预览图片尺寸的优化 2022-06-07 19:25:28 +08:00
kuaifan
4c068f4a62 no message 2022-06-07 17:54:25 +08:00
kuaifan
2e7f2bc316 build 2022-06-07 15:03:41 +08:00
kuaifan
54ebb327bd perf: 新窗口打开任务时保持日志显示状态 2022-06-07 14:17:36 +08:00
kuaifan
6875b1b8af no message 2022-06-07 14:08:53 +08:00
kuaifan
a32faf1a99 perf: 优化首页加载失败的情况 2022-06-07 12:22:00 +08:00
kuaifan
94b3014051 调整首页底部行距 2022-06-07 12:07:22 +08:00
kuaifan
c63fdb04c2 perf: 文字发送太长转成文件发送 2022-06-07 12:03:38 +08:00
kuaifan
07a4196ed5 fix: 修复任务窗口无法发送表情的问题 2022-06-07 10:35:22 +08:00
kuaifan
7f5eb59ac5 perf: 任务详情窗口尺寸 2022-06-07 10:26:25 +08:00
kuaifan
c9de0c2eba perf: 优化全局任务操作菜单 2022-06-07 10:17:41 +08:00
kuaifan
242932735a build 2022-06-06 21:11:10 +08:00
kuaifan
d0b2daa56b 修复对话窗口首次被键盘遮挡的问题 2022-06-06 21:07:42 +08:00
kuaifan
4bc05e258d no message 2022-06-06 19:19:32 +08:00
kuaifan
902c0262f2 build 2022-06-06 16:46:38 +08:00
kuaifan
917f91249e 修复无法转发别人的信息 2022-06-06 16:46:02 +08:00
kuaifan
d75ee02b4a 身份未确认之前不显示网页框架 2022-06-06 16:45:48 +08:00
kuaifan
c08e217fb8 no message 2022-06-06 16:08:55 +08:00
kuaifan
244a2031a7 build 2022-06-06 14:38:56 +08:00
kuaifan
62b8b623ec no message 2022-06-06 14:35:45 +08:00
kuaifan
c5c9e19f76 no message 2022-06-06 14:03:50 +08:00
kuaifan
2f76e932b0 no message 2022-06-06 11:52:20 +08:00
kuaifan
2c373c388c 更新会话路由 2022-06-06 11:32:06 +08:00
kuaifan
34a1c97e6b no message 2022-06-06 08:44:31 +08:00
kuaifan
113c58a057 全局使用windowSmall、windowLarge 2022-06-06 07:16:15 +08:00
kuaifan
ce88fe426b 移动端消息窗口使用全局模式弹窗 2022-06-06 00:49:08 +08:00
kuaifan
ab24af6e79 no msg 2022-06-05 23:15:40 +08:00
kuaifan
b9b45e8e4f 修复移动端任务弹窗关闭之后键盘没有关闭 2022-06-05 21:38:10 +08:00
kuaifan
5c750f7a6f 聊天内容选择后工具小浮窗被遮挡的问题 2022-06-05 21:30:24 +08:00
kuaifan
c2b754d682 修复可以发一个空格消息的问题 2022-06-05 21:09:19 +08:00
kuaifan
2f649c0a9e 修复转发没有选择转发人也可以提交 2022-06-05 20:54:14 +08:00
kuaifan
d456ea68f7 同时播放两个语音时上一个没有停止动画 2022-06-05 20:49:16 +08:00
kuaifan
7fa2c0d6e7 移动端任务窗口聊天loading优化 2022-06-05 20:13:15 +08:00
kuaifan
dace5b92c7 build 2022-06-04 23:25:43 +08:00
kuaifan
8be22e794d no message 2022-06-04 22:58:46 +08:00
kuaifan
90c23a3fd1 git 图片不用生成缩略图 2022-06-04 22:49:40 +08:00
kuaifan
2bd3ba189e no message 2022-06-04 22:41:02 +08:00
kuaifan
8a49eb742e 移动端内通知手势上滑动关闭 2022-06-04 22:22:48 +08:00
kuaifan
8358a10ce8 修复点击图片无法预览 2022-06-04 22:01:19 +08:00
kuaifan
4ecb1995b8 修复点击消息的菜单无效的情况 2022-06-04 20:14:05 +08:00
kuaifan
ddbef0172f 修复切换图片导致的js报错 2022-06-04 20:06:51 +08:00
kuaifan
7a9ad7163b 修复无法emiji标记别人的消息 2022-06-04 20:03:17 +08:00
kuaifan
50f5b7fcf7 解决语音播放不释放的问题 2022-06-04 20:01:21 +08:00
kuaifan
a494709177 no message 2022-06-04 13:05:51 +08:00
kuaifan
7c40441f41 build 2022-06-04 12:44:35 +08:00
kuaifan
d735dbc72c no message 2022-06-04 12:42:10 +08:00
kuaifan
3042ed2628 no message 2022-06-04 10:01:16 +08:00
kuaifan
8a60916776 build 2022-06-04 09:55:55 +08:00
kuaifan
2ac7d0bc01 feat: 新增消息回复表情功能 2022-06-04 09:52:45 +08:00
kuaifan
b8234adbc2 调整滚动到view 2022-06-03 19:27:39 +08:00
kuaifan
6eeeb733e2 no message 2022-06-03 18:06:34 +08:00
kuaifan
d900355bfd build 2022-06-03 17:21:58 +08:00
kuaifan
e71c13e9ac no message 2022-06-03 17:15:49 +08:00
kuaifan
4c2bf56cea no message 2022-06-03 11:50:36 +08:00
kuaifan
47c815d966 build 2022-06-03 11:48:05 +08:00
kuaifan
6c7c17cd61 优化返回手势抖动问题 2022-06-03 11:41:50 +08:00
kuaifan
b855312cf9 优化返回手势抖动问题 2022-06-03 11:24:07 +08:00
kuaifan
b4c8a45bbf 切换项目显示状态错误的问题 2022-06-03 11:13:47 +08:00
kuaifan
b79daedb94 调整项目列表数据 2022-06-03 11:02:18 +08:00
kuaifan
8b4b5382b5 loading 效果 2022-06-03 11:00:05 +08:00
kuaifan
c08240db42 消息转发功能 2022-06-03 10:43:46 +08:00
kuaifan
a2dcd69efd 整理右键、长按 2022-06-03 10:02:04 +08:00
kuaifan
e15f7f6377 统一长按/右键指令 2022-06-02 23:21:48 +08:00
kuaifan
e6aa7de922 对话框自动获取焦点 2022-06-02 16:46:27 +08:00
kuaifan
856f554a41 build 2022-06-02 16:06:12 +08:00
kuaifan
2e153a658f no message 2022-06-02 15:51:05 +08:00
kuaifan
34e4d9f68c no message 2022-06-02 10:01:04 +08:00
kuaifan
49082bc200 no message 2022-06-02 09:32:00 +08:00
kuaifan
a10e9053f9 no message 2022-06-02 09:30:19 +08:00
kuaifan
777b329181 build 2022-06-02 09:27:27 +08:00
kuaifan
fe3f6d2101 移动端隐藏日历预览弹窗箭头 2022-06-02 08:49:50 +08:00
kuaifan
a1b6dc9e84 获取焦点时防止滑动返回抖动 2022-06-02 08:45:03 +08:00
kuaifan
b4453cacc0 build 2022-06-02 00:38:58 +08:00
kuaifan
4cda99a704 no message 2022-06-02 00:12:41 +08:00
kuaifan
f2a618d0c9 no message 2022-06-02 00:04:52 +08:00
kuaifan
dbee06179c no message 2022-06-01 18:54:37 +08:00
kuaifan
2b8e642aad no message 2022-06-01 18:09:22 +08:00
kuaifan
b42721a2a3 no message 2022-06-01 17:33:51 +08:00
kuaifan
e43123b9b4 build 2022-06-01 17:26:30 +08:00
kuaifan
357f0e4df4 完善会议功能 2022-06-01 17:23:32 +08:00
kuaifan
bf59fae173 完善会议功能 2022-06-01 16:46:27 +08:00
kuaifan
0f8cfa72b6 feat: 添加会议功能 2022-06-01 01:48:19 +08:00
kuaifan
24ed87cc82 no message 2022-05-31 17:37:26 +08:00
kuaifan
d47a514b09 no message 2022-05-31 15:41:47 +08:00
kuaifan
1f8198b36b perf: 优化移动端图片预览 2022-05-31 15:15:54 +08:00
kuaifan
022499300e build 2022-05-30 19:53:58 +08:00
kuaifan
db077c8c9f build 2022-05-30 19:46:08 +08:00
kuaifan
e5aa387ef4 no message 2022-05-30 19:40:23 +08:00
kuaifan
04ead7177a no message 2022-05-30 19:28:11 +08:00
kuaifan
13501eb066 统一调整颜色值 2022-05-30 19:23:31 +08:00
kuaifan
13a36fc471 no message 2022-05-30 19:19:14 +08:00
kuaifan
afff590fa5 no message 2022-05-30 17:06:39 +08:00
kuaifan
dea04429de perf: 移动端长按菜单 2022-05-30 15:34:59 +08:00
kuaifan
cb2ad20e36 no message 2022-05-30 15:14:34 +08:00
kuaifan
72f7cc927c fix: 文件共享人数太多内容溢出 2022-05-29 23:47:00 +08:00
kuaifan
9c3eeb59c0 no message 2022-05-29 22:30:06 +08:00
kuaifan
77f6381eff no message 2022-05-29 22:27:43 +08:00
kuaifan
7c1834107e no message 2022-05-29 19:54:05 +08:00
kuaifan
6132c3fa02 no message 2022-05-29 19:47:11 +08:00
kuaifan
9470ebb1ae build 2022-05-29 19:10:43 +08:00
kuaifan
062f0303b4 fix: 聊天内容加载中刷新导致无法再继续加载的情况 2022-05-29 19:09:27 +08:00
kuaifan
c0ca50edf6 调整路由 2022-05-29 18:44:55 +08:00
kuaifan
461546e914 no message 2022-05-29 16:31:39 +08:00
kuaifan
ba95f33592 检查接口版本 2022-05-29 13:39:06 +08:00
kuaifan
cc1d535311 fix: 对话列表点击任务状态标签无法打开对话 2022-05-29 13:38:31 +08:00
kuaifan
4853d5227b fix: 任务弹窗无法发送语音 2022-05-29 13:06:04 +08:00
kuaifan
e750d2ae80 perf: 触摸返回中禁止滚动消息列表 2022-05-29 12:32:37 +08:00
kuaifan
178b511a24 fix: 焦点会超出输入框的情况 2022-05-29 12:03:31 +08:00
kuaifan
2dc5790a46 最长录音3分钟 2022-05-29 11:36:35 +08:00
kuaifan
38b6a74e3d no message 2022-05-29 11:27:32 +08:00
kuaifan
fb3bf48bbc 录音时长不增加则取消录音 2022-05-29 11:13:17 +08:00
kuaifan
c6ab79db30 优化聊天气泡 2022-05-29 10:58:35 +08:00
kuaifan
06951bbc29 perf: 撤回语音消息时停止正在播放 2022-05-29 10:36:46 +08:00
kuaifan
939da1278f no message 2022-05-29 10:19:16 +08:00
kuaifan
75bc7f2a77 no message 2022-05-29 09:05:50 +08:00
kuaifan
82d6f0b533 no message 2022-05-29 07:52:50 +08:00
kuaifan
834bb28100 no message 2022-05-29 00:45:31 +08:00
kuaifan
d4f3124054 no message 2022-05-28 17:08:25 +08:00
kuaifan
85f95ff89c no message 2022-05-28 17:05:45 +08:00
kuaifan
ca74ba1c15 build 2022-05-27 21:51:42 +08:00
kuaifan
44ff21ffcd feat: 支持发送录音 2022-05-27 21:49:00 +08:00
kuaifan
4dd8658aff no message 2022-05-26 21:12:42 +08:00
kuaifan
31b490916d no message 2022-05-26 20:48:44 +08:00
kuaifan
66a5428ec5 perf: 保留粘贴的a标签 2022-05-26 20:48:09 +08:00
kuaifan
965895de5c no message 2022-05-26 19:49:56 +08:00
kuaifan
ac45ba633b build 2022-05-26 15:57:58 +08:00
kuaifan
173f5c84db no message 2022-05-26 10:24:59 +08:00
kuaifan
1d8b4ba83f perf: 支持会话自己 2022-05-25 23:06:42 +08:00
kuaifan
3ce2ec0a46 no message 2022-05-25 22:14:21 +08:00
kuaifan
f9c01f0a7d perf: 聊天内容链接可点击 2022-05-25 22:14:08 +08:00
kuaifan
ca860d551e feat: 对话窗口新增会员最后在线时间 2022-05-25 22:13:27 +08:00
kuaifan
675edef0d1 no message 2022-05-25 17:00:13 +08:00
kuaifan
3f3e4a94ff no message 2022-05-25 16:45:26 +08:00
kuaifan
6732cca479 build 2022-05-25 16:27:36 +08:00
kuaifan
dc8e5a370f build 2022-05-25 16:07:19 +08:00
kuaifan
0476bdea13 no message 2022-05-25 15:57:28 +08:00
kuaifan
0911bc2dc9 perf: 优化搜索加载提示 2022-05-25 15:06:42 +08:00
kuaifan
c5a0e04242 no message 2022-05-25 11:35:04 +08:00
kuaifan
844cdd734e perf: 项目-任务状态的数量,实时更新数据 2022-05-25 10:40:47 +08:00
kuaifan
fc5ce7c5db perf: 优化聊天窗口样式 2022-05-25 10:40:40 +08:00
kuaifan
85a9e33de1 no message 2022-05-24 12:21:18 +08:00
kuaifan
403462b204 no message 2022-05-24 11:33:45 +08:00
kuaifan
12dda85ca5 no message 2022-05-24 09:42:02 +08:00
kuaifan
e1ea5db49d no message 2022-05-24 01:05:27 +08:00
kuaifan
edd8988961 perf: 移动端聊天窗口返回按钮显示未读信息数 2022-05-24 01:01:11 +08:00
kuaifan
a6425c2859 perf: 优化加载状态 2022-05-24 00:40:02 +08:00
kuaifan
52464e232b no message 2022-05-23 23:56:04 +08:00
kuaifan
d015118199 no message 2022-05-23 16:27:08 +08:00
kuaifan
95ff6940f2 no message 2022-05-23 15:45:36 +08:00
kuaifan
bd3b9315b2 no message 2022-05-23 15:40:09 +08:00
kuaifan
46555fcfa5 no message 2022-05-23 15:07:41 +08:00
kuaifan
883b13e0f3 perf: 客户端本地通知 2022-05-23 14:32:18 +08:00
kuaifan
82b2ef3a73 perf: 聊天输入框草稿 2022-05-23 13:02:31 +08:00
kuaifan
3331370d14 no message 2022-05-23 11:35:26 +08:00
kuaifan
c53d7a5dd2 perf: ws重连后重新获取会员基本信息 2022-05-23 11:27:31 +08:00
kuaifan
464d7bc635 perf: 聊天窗口样式 2022-05-23 11:04:37 +08:00
kuaifan
4fd2a44742 perf: 优化信息邮件格式 2022-05-23 10:52:04 +08:00
kuaifan
2d805b775c no message 2022-05-22 23:35:45 +08:00
kuaifan
7636420b3c no message 2022-05-22 23:23:23 +08:00
kuaifan
fd52db7f71 perf: 优化移交个人项目 2022-05-22 23:10:08 +08:00
kuaifan
9c72558878 fix: 获取首字母失败的情况 2022-05-22 20:48:37 +08:00
kuaifan
98f4cf3644 no message 2022-05-22 13:57:38 +08:00
kuaifan
2e6eb81648 no message 2022-05-22 13:37:56 +08:00
kuaifan
972c01bd67 no message 2022-05-22 12:15:31 +08:00
kuaifan
2c99033f1a no message 2022-05-22 11:53:25 +08:00
kuaifan
ebf9686a05 no message 2022-05-22 10:27:22 +08:00
kuaifan
b469069e92 vconsole 2022-05-22 10:02:55 +08:00
kuaifan
7033f00a0e perf: 优化适配ipad 2022-05-22 02:06:31 +08:00
kuaifan
09ed329967 no message 2022-05-21 20:25:28 +08:00
kuaifan
c23234964f no message 2022-05-21 20:23:30 +08:00
kuaifan
df1d5d97c4 no message 2022-05-21 20:13:29 +08:00
kuaifan
2ab12f74ee perf: 优化客户端生命周期重连ws机制 2022-05-21 19:49:36 +08:00
kuaifan
6a2f1f80f5 perf: 优化更新对话列表机制 2022-05-21 19:22:29 +08:00
kuaifan
d0d6b8ce97 perf: 7天内显示时间m-d H:i 2022-05-21 19:21:31 +08:00
kuaifan
b749426389 no message 2022-05-21 18:39:38 +08:00
kuaifan
a33d095296 no message 2022-05-21 08:10:46 +08:00
kuaifan
44732f5849 no message 2022-05-21 00:04:33 +08:00
kuaifan
a6150821e7 perf: 消息也推送给在其它地方登录的自己 2022-05-20 23:54:13 +08:00
kuaifan
4f5dce282b fix: 邮件通知消息未读对象可能会出错的情况 2022-05-20 23:51:54 +08:00
kuaifan
68a7b55320 umeng推送参数附上离线推送 2022-05-20 19:33:17 +08:00
kuaifan
40ff85304b no message 2022-05-20 18:18:59 +08:00
kuaifan
02de3a1680 no message 2022-05-20 15:55:54 +08:00
kuaifan
7a8fcad27c perf: 聊天输入框支持粘贴文件 2022-05-20 15:23:10 +08:00
kuaifan
35daeb443c no message 2022-05-20 12:00:14 +08:00
kuaifan
22993782ed build 2022-05-20 11:13:02 +08:00
kuaifan
4c2b88a13f no message 2022-05-20 11:05:02 +08:00
kuaifan
7a168977da perf: 优化UserAvatar组件 2022-05-20 10:23:24 +08:00
kuaifan
2e03cee726 perf: 上传或发送图片太大时压缩显示 2022-05-20 10:22:38 +08:00
kuaifan
74b1194398 perf: 仪表盘任务数量、最近打开的任务 2022-05-20 10:22:08 +08:00
kuaifan
88163499fe perf: 优化消息移动端打开动画效果 2022-05-19 12:18:23 +08:00
kuaifan
c0ead0b891 更新onlyoffice版本 2022-05-19 11:35:27 +08:00
kuaifan
6b6cb7473e 更新onlyoffice样式 2022-05-19 11:27:40 +08:00
kuaifan
e0f41f9330 no message 2022-05-18 17:17:17 +08:00
kuaifan
95dcfc961f no message 2022-05-18 16:57:40 +08:00
kuaifan
0178f4b8b5 添加隐私政策 2022-05-18 15:35:29 +08:00
kuaifan
85c4eae2a8 no message 2022-05-18 15:08:28 +08:00
kuaifan
168e5cea45 no message 2022-05-18 13:40:53 +08:00
kuaifan
0f5e393340 no message 2022-05-18 13:39:28 +08:00
kuaifan
41faf72c37 no message 2022-05-18 13:38:25 +08:00
kuaifan
6cf2279d21 no message 2022-05-17 19:57:48 +08:00
kuaifan
6347454098 no message 2022-05-17 19:55:47 +08:00
kuaifan
3f17c0689c perf: 未读消息邮件提醒,提醒时把所有未读消息都加上,而不是只提示指定时间的 2022-05-17 19:30:20 +08:00
kuaifan
e4524ff2c5 fix: 未读消息邮件头像不显示的问题 2022-05-17 18:45:59 +08:00
kuaifan
94e4054cd4 no message 2022-05-17 18:27:20 +08:00
kuaifan
a4a7939eae perf: 优化modal内滚动会传播给其他组件的问题 2022-05-17 18:26:17 +08:00
kuaifan
63fa9eb9e4 perf: 优化任务过多加载卡的情况 2022-05-17 15:43:20 +08:00
kuaifan
9e449fa4ee no message 2022-05-17 14:57:48 +08:00
kuaifan
ca7cd2372a perf: 点击聊天输入框窗口跳动 2022-05-17 14:57:18 +08:00
kuaifan
f4502a5e9d no message 2022-05-17 13:44:14 +08:00
kuaifan
040f1e8c86 移动端取消Tooltip提示 2022-05-17 13:44:07 +08:00
kuaifan
a9c10a3e0b 优化移动客户端登录逻辑 2022-05-17 11:52:34 +08:00
kuaifan
8522b4610d 群聊每条信息显示发送人位置给默认高度 2022-05-17 11:52:01 +08:00
kuaifan
f9d7d4faec build 2022-05-16 20:21:22 +08:00
kuaifan
bc4477cd86 no message 2022-05-16 19:30:51 +08:00
kuaifan
4d6fb6f2ca no message 2022-05-16 17:16:38 +08:00
kuaifan
dafa00c895 build 2022-05-16 14:19:21 +08:00
kuaifan
e8dc71c71f no message 2022-05-16 14:01:06 +08:00
kuaifan
3755402f37 build 2022-05-15 23:02:13 +08:00
kuaifan
8d876c60b2 fix: 修复手机客户端无法预览文件的问题 2022-05-15 22:58:35 +08:00
kuaifan
157e89b5ea perf: 支持上传plist格式文件 2022-05-15 22:58:14 +08:00
kuaifan
5fcc4c730f no message 2022-05-15 22:46:56 +08:00
kuaifan
6e8ee60df0 perf: DrawerOverlay 使用 Model 2022-05-15 22:46:45 +08:00
kuaifan
6610a948c5 no message 2022-05-15 18:20:43 +08:00
kuaifan
5ff1f57017 fix: 客户端选择sso登录输入相同地址时提交无反应的问题 2022-05-15 18:13:19 +08:00
kuaifan
8a870a361d perf: 手机客户端登录页优化sso登录样式 2022-05-15 18:12:42 +08:00
kuaifan
72e0f9b798 perf: 优化手机客户端登录页切换主题提示 2022-05-15 18:11:52 +08:00
kuaifan
04bc5d7d17 fix: 推送收到的群组名称为空的情况 2022-05-14 22:14:05 +08:00
kuaifan
3e19f3991c build 2022-05-13 22:48:45 +08:00
kuaifan
5e324c69b6 no message 2022-05-13 20:38:47 +08:00
kuaifan
bba34ef55e no message 2022-05-13 19:35:26 +08:00
kuaifan
99f9431794 catch Exception 改为 Throwable 2022-05-13 15:58:48 +08:00
kuaifan
279f05fd11 fix: 任务开始邮件提醒错误的问题 2022-05-13 15:57:28 +08:00
kuaifan
8f58797256 no message 2022-05-13 15:25:29 +08:00
kuaifan
6dbbcff0b6 no message 2022-05-12 17:51:33 +08:00
kuaifan
37578cf4a0 no message 2022-05-12 15:10:28 +08:00
kuaifan
a13bc3f0bd perf: 优化消息列表 2022-05-12 15:10:21 +08:00
kuaifan
1d4602db0b 优化移动端滑动返回 2022-05-12 15:08:58 +08:00
kuaifan
ece17b2aee fix: ios键盘遮挡输入框的问题 2022-05-12 08:31:51 +08:00
kuaifan
117fd62c44 优化移动端 2022-05-12 00:15:09 +08:00
kuaifan
4da73dd144 优化移动端 2022-05-11 19:14:01 +08:00
kuaifan
05df135229 no message 2022-05-11 09:59:32 +08:00
kuaifan
8e2a649d57 优化移动端 2022-05-10 18:56:43 +08:00
kuaifan
d2428e43b2 perf: 优化移动端 2022-05-09 08:33:09 +08:00
kuaifan
d03a6f7b00 no message 2022-05-06 16:34:48 +08:00
kuaifan
c1b218c133 no message 2022-05-06 16:32:44 +08:00
kuaifan
b3286c8f74 no message 2022-05-06 12:14:11 +08:00
kuaifan
44b46c396b no message 2022-05-06 07:41:11 +08:00
kuaifan
042fb28ccc no message 2022-05-05 22:18:06 +08:00
kuaifan
2206e0f1e6 no message 2022-05-05 22:15:23 +08:00
kuaifan
632f68660b feat: 邮件通知未读消息 2022-05-05 19:24:17 +08:00
kuaifan
43531d9b8d no message 2022-05-05 08:20:04 +08:00
kuaifan
d28fb9a27a no message 2022-05-04 18:16:03 +08:00
kuaifan
c0fadde527 perf: 优化聊天输入框计算样式 2022-04-29 16:06:55 +08:00
kuaifan
663661c73c perf: 优化正则表达式 2022-04-29 16:06:38 +08:00
kuaifan
4304b66b06 Merge remote-tracking branch 'A_github_pro/master' 2022-04-28 14:52:29 +08:00
kuaifan
99b789bc11 增加php backtrack_limit,解决聊天粘贴大图发送失败的问题 2022-04-28 14:52:10 +08:00
kuaifan
4bbd20b8c0 build 2022-04-28 08:50:27 +08:00
kuaifan
43c77726bb no message 2022-04-28 07:46:32 +08:00
kuaifan
3ceb183d8b no message 2022-04-28 07:37:54 +08:00
kuaifan
46787b187f no message 2022-04-27 16:15:03 +08:00
kuaifan
7b8a6c2a4c perf: 移交项目和任务时记录被移交对象 2022-04-27 16:00:55 +08:00
kuaifan
7b757d68b4 no message 2022-04-27 15:48:09 +08:00
kuaifan
092506e9ab perf: 共享的文件禁止移动到另一个共享文件夹内 2022-04-27 15:47:53 +08:00
kuaifan
529b4bec31 no message 2022-04-27 08:06:38 +08:00
kuaifan
2e88e933c3 no message 2022-04-27 08:03:36 +08:00
kuaifan
031dac36e6 fix: 修复共享文件移动到共享文件夹内共享属性错乱的问题 2022-04-27 08:03:17 +08:00
kuaifan
dc3b666635 调整office映射文件 2022-04-27 07:20:11 +08:00
kuaifan
e45bcf34a3 perf: 优化自定义sso登录 2022-04-27 07:05:50 +08:00
kuaifan
0aa7679a71 build 2022-04-26 11:18:27 +08:00
kuaifan
a46ac38561 fix: 同时删除多个任务负责人或协助人员任务动态显示错误的问题 2022-04-26 11:12:52 +08:00
kuaifan
b1395377a1 fix: 修复 ETooltip 组件 disabled 取消后错位的问题 2022-04-26 10:27:50 +08:00
kuaifan
41e60ee990 perf: 优化文件历史查看 2022-04-26 09:11:01 +08:00
kuaifan
00f80e8db8 no message 2022-04-26 08:45:04 +08:00
kuaifan
f70da2c4a2 perf: 查看文件修改历史时文本编辑器、图表点击编辑历史窗口不隐藏 2022-04-25 22:04:58 +08:00
kuaifan
ee485020d1 fix: 添加任务窗口选择其他项目无效的问题 2022-04-25 21:54:12 +08:00
kuaifan
8cdbb969ff 一键编译所有generic 2022-04-25 21:39:53 +08:00
kuaifan
687b9ca8b1 no message 2022-04-25 09:53:28 +08:00
kuaifan
f486477a41 no message 2022-04-25 08:15:12 +08:00
kuaifan
6f7acc60b5 no message 2022-04-25 08:12:27 +08:00
kuaifan
c748d8f5f6 perf: 优化任务详情右键预览图片 2022-04-25 07:58:17 +08:00
kuaifan
5d1d108811 fix: 修复上传文件夹不立即显示的问题 2022-04-25 07:19:16 +08:00
kuaifan
d8d49d6b5a no message 2022-04-25 05:10:27 +08:00
kuaifan
ebb3ec6784 feat: 新增查看文件历史版本 2022-04-25 05:04:21 +08:00
kuaifan
ab66b70485 no message 2022-04-25 05:03:52 +08:00
kuaifan
a723c2a44a perf: 文件打开保存机制 2022-04-24 14:38:38 +08:00
kuaifan
d0c7ee5e47 fix: 修复已打开文件需刷新网页才显示最新内容的情况 2022-04-22 15:55:56 +08:00
kuaifan
3c386eeaa9 perf: 客户端升级日志 2022-04-22 15:18:40 +08:00
kuaifan
e5c4faf6ef no message 2022-04-22 09:11:15 +08:00
kuaifan
0274a7f6e6 no message 2022-04-22 08:38:30 +08:00
kuaifan
92632cf294 no message 2022-04-22 08:23:14 +08:00
kuaifan
864af174f7 office开启强制保存按钮 2022-04-22 08:23:03 +08:00
kuaifan
b1cbf6f893 优化头像上传 2022-04-22 07:43:47 +08:00
kuaifan
0b0e6951e5 优化高度计算 2022-04-22 07:05:20 +08:00
563 changed files with 49671 additions and 20103 deletions

View File

@@ -53,6 +53,9 @@ PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_APP_CLUSTER=mt1
JUKE_KEY_JOKE=
JUKE_KEY_SOUP=
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"

1
.gitignore vendored
View File

@@ -8,6 +8,7 @@
/vendor
/build
/tmp
/CHANGELOG.md
._*
.env
.idea

3
.gitmodules vendored
View File

@@ -1,3 +1,6 @@
[submodule "resources/drawio"]
path = resources/drawio
url = https://github.com/jgraph/drawio.git
[submodule "resources/mobile"]
path = resources/mobile
url = git@github.com:kuaifan/dootask-app.git

View File

@@ -45,6 +45,16 @@ cd dootask
./cmd port 2222
```
### Change App Url
```bash
# This URL only affects the email reply.
./cmd url {Your domain url}
# example:
./cmd url https://domain.com
```
### Stop server
```bash

21
README_CLIENT.md Normal file
View File

@@ -0,0 +1,21 @@
# 客户端说明
## 1、App客户端
#### 1.1、说明
目录 `resources/mobile`,使用`eeui.app`框架遵从eeui的开发文档进行打包开发app
#### 1.2、编译App
1. 在项目目录执行 `./cmd appbuild` 编译
2. 进入 `resources/mobile` eeui框架内打包Android或iOS应用
## 2、PC/Mac客户端
#### 2.1、说明
目录 `electron`,使用`electron`框架遵从electron的开发文档进行打包客户端
#### 2.2、编译客户端
在项目目录执行 `./cmd electron` 根据提示编译

View File

@@ -45,6 +45,16 @@ cd dootask
./cmd port 2222
```
### 更换URL
```bash
# 此地址仅影响邮件回复功能
./cmd url {域名地址}
# 例如:
./cmd url https://domain.com
```
### 停止服务
```bash

28
README_PUBLISH.md Normal file
View File

@@ -0,0 +1,28 @@
# 发布说明
## 发布前
1. 添加环境变量 `APPLEID``APPLEIDPASS``CSC_LINK`
2. 发布GitHub还需要添加 `GH_PAT`
## 通过 GitHub Actions 发布
1. 执行 `./cmd prod` 编译
2. 执行 `node ./version.js` 制作版本
3. 执行 `git commit` 相关操作
4. 制作标签
5. 推送标签
## 本地发布
1. 执行 `./cmd prod` 编译
2. 执行 `node ./version.js` 制作版本
3. 执行 `git commit` 相关操作
4. 制作标签
5. 执行 `./cmd electron` 相关操作
## 编译App
1. 执行 `./cmd appbuild``./cmd appbuild setting` 编译
2. 进入 `resources/mobile` eeui框架内打包Android或iOS应用

View File

@@ -4,7 +4,7 @@
/**
* A helper file for Laravel, to provide autocomplete information to your IDE
* Generated for Laravel 8.83.2.
* Generated for Laravel 8.83.27.
*
* This file should not be included in your code, only analyzed by your IDE!
*
@@ -2167,6 +2167,17 @@
{
/** @var \Illuminate\Auth\SessionGuard $instance */
return $instance->setRequest($request);
}
/**
* Get the timebox instance used by the guard.
*
* @return \Illuminate\Support\Timebox
* @static
*/
public static function getTimebox()
{
/** @var \Illuminate\Auth\SessionGuard $instance */
return $instance->getTimebox();
}
/**
* Determine if the current user is authenticated. If not, throw an exception.
@@ -8994,7 +9005,7 @@
/**
* Push a new job onto the queue.
*
* @param string $job
* @param string|object $job
* @param mixed $data
* @param string|null $queue
* @return mixed
@@ -9023,7 +9034,7 @@
* Push a new job onto the queue after a delay.
*
* @param \DateTimeInterface|\DateInterval|int $delay
* @param string $job
* @param string|object $job
* @param mixed $data
* @param string|null $queue
* @return mixed
@@ -9038,7 +9049,7 @@
* Push a new job onto the queue.
*
* @param string $queue
* @param string $job
* @param string|object $job
* @param mixed $data
* @return mixed
* @static
@@ -9053,7 +9064,7 @@
*
* @param string $queue
* @param \DateTimeInterface|\DateInterval|int $delay
* @param string $job
* @param string|object $job
* @param mixed $data
* @return mixed
* @static
@@ -11886,6 +11897,7 @@
* @method static \Illuminate\Routing\RouteRegistrar prefix(string $prefix)
* @method static \Illuminate\Routing\RouteRegistrar scopeBindings()
* @method static \Illuminate\Routing\RouteRegistrar where(array $where)
* @method static \Illuminate\Routing\RouteRegistrar withoutMiddleware(array|string $middleware)
* @see \Illuminate\Routing\Router
*/
class Route {
@@ -16037,11 +16049,12 @@
* @param string $fileName
* @param string|null $writerType
* @param mixed $withHeadings
* @param array $responseHeaders
* @static
*/
public static function downloadExcel($fileName, $writerType = null, $withHeadings = false)
public static function downloadExcel($fileName, $writerType = null, $withHeadings = false, $responseHeaders = [])
{
return \Illuminate\Support\Collection::downloadExcel($fileName, $writerType, $withHeadings);
return \Illuminate\Support\Collection::downloadExcel($fileName, $writerType, $withHeadings, $responseHeaders);
}
/**
*
@@ -16107,6 +16120,16 @@
{
/** @var \Facade\FlareClient\Flare $instance */
return $instance->filterExceptionsUsing($filterExceptionsCallable);
}
/**
*
*
* @static
*/
public static function filterReportsUsing($filterReportsCallable)
{
/** @var \Facade\FlareClient\Flare $instance */
return $instance->filterReportsUsing($filterReportsCallable);
}
/**
*

File diff suppressed because it is too large Load Diff

View File

@@ -13,6 +13,7 @@ use App\Module\Base;
use App\Module\Ihttp;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Redirect;
use Request;
/**
@@ -44,11 +45,11 @@ class FileController extends AbstractController
$pid = intval($data['pid']);
//
$permission = 1000;
$builder = File::wherePid($pid);
if ($pid > 0) {
File::permissionFind($pid, 0, $permission);
$builder = File::wherePid($pid);
} else {
$builder = File::whereUserid($user->userid);
$builder->whereUserid($user->userid);
}
//
$array = $builder->take(500)->get()->toArray();
@@ -68,6 +69,22 @@ class FileController extends AbstractController
$temp['permission'] = $file->getPermission($user->userid);
$array[] = $temp;
}
// 去除没有权限的文件
$isUnset = false;
foreach ($array as $index1 => $item1) {
if ($item1['permission'] === -1) {
foreach ($array as $index2 => $item2) {
if ($item2['pid'] === $item1['id']) {
$array[$index2]['pid'] = 0;
}
}
$isUnset = true;
unset($array[$index1]);
}
}
if ($isUnset) {
$array = array_values($array);
}
} else {
// 获取共享相关
DB::statement("SET SQL_MODE=''");
@@ -76,8 +93,7 @@ class FileController extends AbstractController
->join('file_users', 'files.id', '=', 'file_users.file_id')
->where('files.userid', '!=', $user->userid)
->where(function ($query) use ($user) {
$query->where('file_users.userid', 0);
$query->orWhere('file_users.userid', $user->userid);
$query->whereIn('file_users.userid', [0, $user->userid]);
})
->groupBy('files.id')
->take(100)
@@ -92,7 +108,7 @@ class FileController extends AbstractController
}
// 图片直接返回预览地址
foreach ($array as &$item) {
File::handleImageUrl($item);
$item = File::handleImageUrl($item);
}
return Base::retSuccess('success', $array);
}
@@ -155,14 +171,39 @@ class FileController extends AbstractController
$user = User::auth();
//
$key = trim(Request::input('key'));
if (empty($key)) {
return Base::retError('请输入关键词');
// 搜索自己的
$builder = File::whereUserid($user->userid);
if ($key) {
$builder->where("name", "like", "%{$key}%");
}
$array = $builder->take(50)->get()->toArray();
// 搜索共享的
$take = 50 - count($array);
if ($take > 0 && $key) {
$list = File::where("name", "like", "%{$key}%")
->whereIn('pshare', function ($queryA) use ($user) {
$queryA->select('files.id')
->from('files')
->join('file_users', 'files.id', '=', 'file_users.file_id')
->where('files.userid', '!=', $user->userid)
->where(function ($queryB) use ($user) {
$queryB->whereIn('file_users.userid', [0, $user->userid]);
});
})
->take($take)
->get();
if ($list->isNotEmpty()) {
foreach ($list as $file) {
$temp = $file->toArray();
if ($file->pshare === $file->id) {
$temp['pid'] = 0;
}
$array[] = $temp;
}
}
}
//
$builder = File::whereUserid($user->userid)->where("name", "like", "%{$key}%");
$list = $builder->take(50)->get();
//
return Base::retSuccess('success', $list);
return Base::retSuccess('success', $array);
}
/**
@@ -201,6 +242,7 @@ class FileController extends AbstractController
$file = File::permissionFind($id, 1);
//
$file->name = $name;
$file->handleDuplicateName();
$file->save();
$file->pushMsg('update', $file);
return Base::retSuccess('修改成功', $file);
@@ -256,7 +298,8 @@ class FileController extends AbstractController
'userid' => $userid,
'created_id' => $user->userid,
]);
$file->saveBeforePids();
$file->handleDuplicateName();
$file->saveBeforePP();
//
$data = File::find($file->id);
$data->pushMsg('add', $data);
@@ -306,10 +349,11 @@ class FileController extends AbstractController
'userid' => $userid,
'created_id' => $user->userid,
]);
$file->handleDuplicateName();
$data = AbstractModel::transaction(function() use ($file) {
$content = FileContent::select(['content', 'text', 'size'])->whereFid($file->cid)->orderByDesc('id')->first();
$file->size = $content?->size ?: 0;
$file->saveBeforePids();
$file->saveBeforePP();
if ($content) {
$content = $content->toArray();
$content['fid'] = $file->id;
@@ -340,7 +384,7 @@ class FileController extends AbstractController
*/
public function move()
{
User::auth();
$user = User::auth();
//
$ids = Request::input('ids');
$pid = intval(Request::input('pid'));
@@ -351,29 +395,44 @@ class FileController extends AbstractController
if (count($ids) > 100) {
return Base::retError('一次最多只能移动100个文件或文件夹');
}
$toShareFile = false;
if ($pid > 0) {
File::permissionFind($pid, 1);
$tmpFile = File::permissionFind($pid, 1);
$toShareFile = $tmpFile->getShareInfo();
}
//
$files = [];
AbstractModel::transaction(function() use ($pid, $ids, &$files) {
AbstractModel::transaction(function() use ($user, $pid, $ids, $toShareFile, &$files) {
foreach ($ids as $id) {
$file = File::permissionFind($id, 1000);
//
if ($pid > 0) {
$arr = [];
$tid = $pid;
while ($tid > 0) {
$arr[] = $tid;
$tid = intval(File::whereId($tid)->value('pid'));
if ($toShareFile) {
if ($file->share) {
throw new ApiException("{$file->name} 当前正在共享,无法移动到另一个共享文件夹内");
}
if ($file->isSubShare()) {
throw new ApiException("{$file->name} 内含有共享文件,无法移动到另一个共享文件夹内");
}
$file->userid = $toShareFile->userid;
File::where('pids', 'LIKE', "%,{$file->id},%")->update(['userid' => $toShareFile->userid]);
}
if (in_array($id, $arr)) {
throw new ApiException('移动位置错误');
//
$tmpId = $pid;
while ($tmpId > 0) {
if ($id == $tmpId) {
throw new ApiException('移动位置错误');
}
$tmpId = intval(File::whereId($tmpId)->value('pid'));
}
} else {
$file->userid = $user->userid;
File::where('pids', 'LIKE', "%,{$file->id},%")->update(['userid' => $user->userid]);
}
//
$file->pid = $pid;
$file->saveBeforePids();
$file->handleDuplicateName();
$file->saveBeforePP();
$files[] = $file;
}
});
@@ -438,7 +497,9 @@ class FileController extends AbstractController
* - yes
* @apiParam {String} down 直接下载
* - no: 浏览(默认)
* - yes: 下载office文件直接下载
* - yes: 下载office文件直接下载除非是preview
* - preview: 转预览地址
* @apiParam {Number} [history_id] 读取历史记录ID
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
@@ -449,6 +510,7 @@ class FileController extends AbstractController
$id = Request::input('id');
$down = Request::input('down', 'no');
$only_update_at = Request::input('only_update_at', 'no');
$history_id = intval(Request::input('history_id'));
//
if (Base::isNumber($id)) {
User::auth();
@@ -470,7 +532,14 @@ class FileController extends AbstractController
]);
}
//
$content = FileContent::whereFid($file->id)->orderByDesc('id')->first();
$builder = FileContent::whereFid($file->id);
if ($history_id > 0) {
$builder->whereId($history_id);
}
$content = $builder->orderByDesc('id')->first();
if ($down === 'preview') {
return Redirect::to(FileContent::formatPreview($file, $content?->content));
}
return FileContent::formatContent($file, $content?->content, $down == 'yes');
}
@@ -492,7 +561,6 @@ class FileController extends AbstractController
*/
public function content__save()
{
Base::checkClientVersion('0.9.13');
$user = User::auth();
//
$id = Base::getPostInt('id');
@@ -504,13 +572,14 @@ class FileController extends AbstractController
if ($file->type == 'document') {
$data = Base::json2array($content);
$isRep = false;
preg_match_all("/<img\s*src=\"data:image\/(png|jpg|jpeg);base64,(.*?)\"/s", $data['content'], $matchs);
preg_match_all("/<img\s+src=\"data:image\/(png|jpg|jpeg);base64,(.*?)\"/s", $data['content'], $matchs);
foreach ($matchs[2] as $key => $text) {
$tmpPath = "uploads/file/document/" . date("Ym") . "/" . $id . "/attached/";
Base::makeDir(public_path($tmpPath));
$tmpPath .= md5($text) . "." . $matchs[1][$key];
if (file_put_contents(public_path($tmpPath), base64_decode($text))) {
$data['content'] = str_replace($matchs[0][$key], '<img src="' . Base::fillUrl($tmpPath) . '"', $data['content']);
$paramet = getimagesize(public_path($tmpPath));
$data['content'] = str_replace($matchs[0][$key], '<img src="' . Base::fillUrl($tmpPath) . '" original-width="' . $paramet[0] . '" original-height="' . $paramet[1] . '"', $data['content']);
$isRep = true;
}
}
@@ -535,9 +604,10 @@ class FileController extends AbstractController
$contentString = $content;
$file->ext = 'mind';
break;
case 'code':
case 'txt':
$contentString = $content;
case 'code':
$contentArray = Base::json2array($content);
$contentString = $contentArray['content'];
break;
default:
return Base::retError('参数错误');
@@ -628,7 +698,7 @@ class FileController extends AbstractController
* @apiGroup file
* @apiName content__upload
*
* @apiParam {Number} [pid] 父级ID
* @apiParam {Number} [pid] 父级ID
* @apiParam {String} [files] 文件名
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
@@ -656,11 +726,11 @@ class FileController extends AbstractController
}
//
$dirs = explode("/", $webkitRelativePath);
$addItem = [];
while (count($dirs) > 1) {
$dirName = array_shift($dirs);
if ($dirName) {
$pushMsg = [];
AbstractModel::transaction(function () use ($dirName, $user, $userid, &$pid, &$pushMsg) {
AbstractModel::transaction(function () use ($dirName, $user, $userid, &$pid, &$addItem) {
$dirRow = File::wherePid($pid)->whereType('folder')->whereName($dirName)->lockForUpdate()->first();
if (empty($dirRow)) {
$dirRow = File::createInstance([
@@ -670,8 +740,9 @@ class FileController extends AbstractController
'userid' => $userid,
'created_id' => $user->userid,
]);
if ($dirRow->saveBeforePids()) {
$pushMsg[] = File::find($dirRow->id);
$dirRow->handleDuplicateName();
if ($dirRow->saveBeforePP()) {
$addItem[] = File::find($dirRow->id);
}
}
if (empty($dirRow)) {
@@ -679,7 +750,7 @@ class FileController extends AbstractController
}
$pid = $dirRow->id;
});
foreach ($pushMsg as $tmpRow) {
foreach ($addItem as $tmpRow) {
$tmpRow->pushMsg('add', $tmpRow);
}
}
@@ -705,8 +776,8 @@ class FileController extends AbstractController
'xls', 'xlsx' => "excel",
'ppt', 'pptx' => "ppt",
'wps' => "wps",
'jpg', 'jpeg', 'png', 'gif', 'bmp', 'ico', 'raw' => "picture",
'rar', 'zip', 'jar', '7-zip', 'tar', 'gzip', '7z' => "archive",
'jpg', 'jpeg', 'png', 'gif', 'bmp', 'ico', 'raw', 'svg' => "picture",
'rar', 'zip', 'jar', '7-zip', 'tar', 'gzip', '7z', 'gz', 'apk', 'dmg' => "archive",
'tif', 'tiff' => "tif",
'dwg', 'dxf' => "cad",
'ofd' => "ofd",
@@ -718,7 +789,7 @@ class FileController extends AbstractController
'inc', 'phtml', 'shtml', 'php3', 'php4', 'php5', 'phps', 'phpt', 'aw', 'ctp', 'module', 'ps1', 'py', 'r', 'rb', 'ru', 'gemspec', 'rake', 'guardfile', 'rakefile',
'gemfile', 'rs', 'sass', 'scss', 'sh', 'bash', 'bashrc', 'sql', 'sqlserver', 'swift', 'ts', 'typescript', 'str', 'vbs', 'vb', 'v', 'vh', 'sv', 'svh', 'xml',
'rdf', 'rss', 'wsdl', 'xslt', 'atom', 'mathml', 'mml', 'xul', 'xbl', 'xaml', 'yaml', 'yml',
'asp', 'properties', 'gitignore', 'log', 'bas', 'prg', 'python', 'ftl', 'aspx' => "code",
'asp', 'properties', 'gitignore', 'log', 'bas', 'prg', 'python', 'ftl', 'aspx', 'plist' => "code",
'mp3', 'wav', 'mp4', 'flv',
'avi', 'mov', 'wmv', 'mkv', '3gp', 'rm' => "media",
'xmind' => "xmind",
@@ -736,20 +807,26 @@ class FileController extends AbstractController
'userid' => $userid,
'created_id' => $user->userid,
]);
$file->handleDuplicateName();
// 开始创建
return AbstractModel::transaction(function () use ($webkitRelativePath, $type, $user, $data, $file) {
return AbstractModel::transaction(function () use ($addItem, $webkitRelativePath, $type, $user, $data, $file) {
$file->size = $data['size'] * 1024;
$file->saveBeforePids();
$file->saveBeforePP();
//
$data = Base::uploadMove($data, "uploads/file/" . $file->type . "/" . date("Ym") . "/" . $file->id . "/");
$content = [
'from' => '',
'type' => $type,
'ext' => $data['ext'],
'url' => $data['path'],
];
if (isset($data['width'])) {
$content['width'] = $data['width'];
$content['height'] = $data['height'];
}
$content = FileContent::createInstance([
'fid' => $file->id,
'content' => [
'from' => '',
'type' => $type,
'ext' => $data['ext'],
'url' => $data['path']
],
'content' => $content,
'text' => '',
'size' => $file->size,
'userid' => $user->userid,
@@ -759,15 +836,86 @@ class FileController extends AbstractController
$tmpRow = File::find($file->id);
$tmpRow->pushMsg('add', $tmpRow);
//
$data = $tmpRow->toArray();
$data = File::handleImageUrl($tmpRow->toArray());
$data['full_name'] = $webkitRelativePath ?: $data['name'];
File::handleImageUrl($data);
return Base::retSuccess($data['name'] . ' 上传成功', $data);
//
$addItem[] = $data;
return Base::retSuccess($data['name'] . ' 上传成功', $addItem);
});
}
/**
* @api {get} api/file/share 12. 获取共享信息
* @api {get} api/file/content/history 12. 获取内容历史
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup file
* @apiName content__history
*
* @apiParam {Number} id 文件ID
*
* @apiParam {Number} [page] 当前页,默认:1
* @apiParam {Number} [pagesize] 每页显示数量,默认:20最大:100
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function content__history()
{
$id = Request::input('id');
//
$file = File::permissionFind(intval($id));
//
$data = FileContent::select(['id', 'size', 'userid', 'created_at'])
->whereFid($file->id)
->orderByDesc('id')
->paginate(Base::getPaginate(100, 20));
return Base::retSuccess('success', $data);
}
/**
* @api {get} api/file/content/restore 13. 恢复文件历史
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup file
* @apiName content__restore
*
* @apiParam {Number} id 文件ID
* @apiParam {Number} history_id 历史数据ID
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function content__restore()
{
$user = User::auth();
//
$id = intval(Request::input('id'));
$history_id = intval(Request::input('history_id'));
//
$file = File::permissionFind($id);
//
$history = FileContent::whereFid($file->id)->whereId($history_id)->first();
if (empty($history)) {
return Base::retError('历史数据不存在或已被删除');
}
//
$content = $history->replicate();
$content->userid = $user->userid;
$content->save();
//
$file->size = $content->size;
$file->save();
$file->pushMsg('content');
//
return Base::retSuccess('还原成功');
}
/**
* @api {get} api/file/share 14. 获取共享信息
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@@ -803,7 +951,7 @@ class FileController extends AbstractController
}
/**
* @api {get} api/file/share/update 13. 设置共享
* @api {get} api/file/share/update 15. 设置共享
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@@ -868,7 +1016,7 @@ class FileController extends AbstractController
// 设置共享
$action = "update";
if ($force === 0) {
if (File::where("pids", "like", "%,{$file->id},%")->whereShare(1)->exists()) {
if ($file->isSubShare()) {
return Base::retError('此文件夹内已有共享文件夹', [], -3001);
}
}
@@ -893,7 +1041,7 @@ class FileController extends AbstractController
}
/**
* @api {get} api/file/share/out 14. 退出共享
* @api {get} api/file/share/out 16. 退出共享
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@@ -927,7 +1075,7 @@ class FileController extends AbstractController
}
/**
* @api {get} api/file/link 15. 获取链接
* @api {get} api/file/link 17. 获取链接
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@@ -951,28 +1099,8 @@ class FileController extends AbstractController
$refresh = Request::input('refresh', 'no');
//
$file = File::permissionFind($id);
if ($file->type == 'folder') {
return Base::retError('文件夹暂不支持此功能');
}
$fileLink = $file->getShareLink($user->userid, $refresh == 'yes');
//
$fileLink = FileLink::whereFileId($file->id)->whereUserid($user->userid)->first();
if (empty($fileLink)) {
$fileLink = FileLink::createInstance([
'file_id' => $file->id,
'userid' => $user->userid,
'code' => Base::generatePassword(64),
]);
$fileLink->save();
} else {
if ($refresh == 'yes') {
$fileLink->code = Base::generatePassword(64);
$fileLink->save();
}
}
return Base::retSuccess('success', [
'id' => $file->id,
'url' => Base::fillUrl('single/file/' . $fileLink->code),
'num' => $fileLink->num
]);
return Base::retSuccess('success', $fileLink);
}
}

View File

@@ -5,6 +5,7 @@ namespace App\Http\Controllers\Api;
use App\Exceptions\ApiException;
use App\Models\AbstractModel;
use App\Models\File;
use App\Models\FileContent;
use App\Models\Project;
use App\Models\ProjectColumn;
use App\Models\ProjectFlow;
@@ -19,9 +20,11 @@ use App\Models\User;
use App\Models\WebSocketDialog;
use App\Module\Base;
use App\Module\BillExport;
use App\Module\BillMultipleExport;
use Carbon\Carbon;
use Illuminate\Support\Arr;
use Madzipper;
use Redirect;
use Request;
use Response;
use Session;
@@ -42,6 +45,10 @@ class ProjectController extends AbstractController
* @apiName lists
*
* @apiParam {String} [all] 是否查看所有项目(限制管理员)
* @apiParam {String} [type] 项目类型
* - all全部默认
* - team团队项目
* - personal个人项目
* @apiParam {String} [archived] 归档状态
* - all全部
* - no未归档默认
@@ -94,6 +101,7 @@ class ProjectController extends AbstractController
$user = User::auth();
//
$all = Request::input('all');
$type = Request::input('type', 'all');
$archived = Request::input('archived', 'no');
$getcolumn = Request::input('getcolumn', 'no');
//
@@ -108,6 +116,12 @@ class ProjectController extends AbstractController
$builder->with(['projectColumn']);
}
//
if ($type === 'team') {
$builder->where('projects.personal', 0);
} elseif ($type === 'personal') {
$builder->where('projects.personal', 1);
}
//
if ($archived == 'yes') {
$builder->whereNotNull('projects.archived_at');
} elseif ($archived == 'no') {
@@ -212,6 +226,7 @@ class ProjectController extends AbstractController
* @apiParam {String} [flow] 开启流程
* - open: 开启
* - close: 关闭(默认)
* @apiParam {Number} [personal] 个人项目,注册成功时创建(仅支持创建一个个人项目)
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
@@ -220,73 +235,8 @@ class ProjectController extends AbstractController
public function add()
{
$user = User::auth();
// 项目名称
$name = trim(Request::input('name', ''));
$desc = trim(Request::input('desc', ''));
$flow = trim(Request::input('flow', 'close'));
if (mb_strlen($name) < 2) {
return Base::retError('项目名称不可以少于2个字');
} elseif (mb_strlen($name) > 32) {
return Base::retError('项目名称最多只能设置32个字');
}
if (mb_strlen($desc) > 255) {
return Base::retError('项目介绍最多只能设置255个字');
}
// 列表
$columns = explode(",", Request::input('columns'));
$insertColumns = [];
$sort = 0;
foreach ($columns AS $column) {
$column = trim($column);
if ($column) {
$insertColumns[] = [
'name' => $column,
'sort' => $sort++,
];
}
}
if (empty($insertColumns)) {
$insertColumns[] = [
'name' => 'Default',
'sort' => 0,
];
}
if (count($insertColumns) > 30) {
return Base::retError('项目列表最多不能超过30个');
}
// 开始创建
$project = Project::createInstance([
'name' => $name,
'desc' => $desc,
'userid' => $user->userid,
]);
AbstractModel::transaction(function() use ($flow, $insertColumns, $project) {
$project->save();
ProjectUser::createInstance([
'project_id' => $project->id,
'userid' => $project->userid,
'owner' => 1,
])->save();
foreach ($insertColumns AS $column) {
$column['project_id'] = $project->id;
ProjectColumn::createInstance($column)->save();
}
$dialog = WebSocketDialog::createGroup(null, $project->userid, 'project');
if (empty($dialog)) {
throw new ApiException('创建项目聊天室失败');
}
$project->dialog_id = $dialog->id;
$project->save();
//
if ($flow == 'open') {
$project->addFlow(Base::json2array('[{"id":-10,"name":"待处理","status":"start","turns":[-10,-11,-12,-13,-14],"userids":[],"usertype":"add","userlimit":0},{"id":-11,"name":"进行中","status":"progress","turns":[-10,-11,-12,-13,-14],"userids":[],"usertype":"add","userlimit":0},{"id":-12,"name":"待测试","status":"test","turns":[-10,-11,-12,-13,-14],"userids":[],"usertype":"add","userlimit":0},{"id":-13,"name":"已完成","status":"end","turns":[-10,-11,-12,-13,-14],"userids":[],"usertype":"add","userlimit":0},{"id":-14,"name":"已取消","status":"end","turns":[-10,-11,-12,-13,-14],"userids":[],"usertype":"add","userlimit":0}]'));
}
});
//
$data = Project::find($project->id);
$data->addLog("创建项目");
$data->pushMsg('add', $data);
return Base::retSuccess('添加成功', $data);
return Project::createProject(Request::all(), $user->userid);
}
/**
@@ -322,18 +272,22 @@ class ProjectController extends AbstractController
}
//
$project = Project::userProject($project_id, true, true);
//
if ($project->name != $name) {
$project->addLog("修改项目名称", [
'change' => [$project->name, $name]
]);
$project->name = $name;
}
if ($project->desc != $desc) {
$project->desc = $desc;
$project->addLog("修改项目介绍");
}
$project->save();
AbstractModel::transaction(function () use ($desc, $name, $project) {
if ($project->name != $name) {
$project->addLog("修改项目名称", [
'change' => [$project->name, $name]
]);
$project->name = $name;
if ($project->dialog_id) {
WebSocketDialog::updateData(['id' => $project->dialog_id], ['name' => $project->name]);
}
}
if ($project->desc != $desc) {
$project->desc = $desc;
$project->addLog("修改项目介绍");
}
$project->save();
});
$project->pushMsg('update', $project);
//
return Base::retSuccess('修改成功', $project);
@@ -382,6 +336,8 @@ class ProjectController extends AbstractController
}
$project->syncDialogUser();
$project->addLog("修改项目成员");
$project->user_simple = count($array) . "|" . implode(",", array_slice($array, 0, 3));
$project->save();
return $deleteUser->toArray();
});
//
@@ -543,7 +499,7 @@ class ProjectController extends AbstractController
$project = Project::userProject($project_id, true, true);
//
if (!User::whereUserid($owner_userid)->exists()) {
return Base::retError('员不存在');
return Base::retError('员不存在');
}
//
AbstractModel::transaction(function() use ($owner_userid, $project) {
@@ -1022,8 +978,8 @@ class ProjectController extends AbstractController
if (empty($userid) || empty($time)) {
return Base::retError('参数错误');
}
if (count($userid) > 20) {
return Base::retError('导出员限制最多20个');
if (count($userid) > 100) {
return Base::retError('导出员限制最多100个');
}
if (!(is_array($time) && Base::isDateOrTime($time[0]) && Base::isDateOrTime($time[1]))) {
return Base::retError('时间选择错误');
@@ -1112,7 +1068,21 @@ class ProjectController extends AbstractController
} elseif ($task->complete_at) {
$statusText = '已完成';
}
$datas[] = [
if (!isset($datas[$task->ownerid])) {
$datas[$task->ownerid] = [
'index' => 1,
'nickname' => Base::filterEmoji(User::userid2nickname($task->ownerid)),
'styles' => ["A1:P1" => ["font" => ["bold" => true]]],
'data' => [],
];
}
$datas[$task->ownerid]['index']++;
if ($statusText === '未完成') {
$datas[$task->ownerid]['styles']["P{$datas[$task->ownerid]['index']}"] = ["font" => ["color" => ["rgb" => "ff0000"]]]; // 未完成
} elseif ($statusText === '已完成' && $task->end_at && Carbon::parse($task->complete_at)->gt($task->end_at)) {
$datas[$task->ownerid]['styles']["P{$datas[$task->ownerid]['index']}"] = ["font" => ["color" => ["rgb" => "436FF6"]]]; // 已完成超期
}
$datas[$task->ownerid]['data'][] = [
$task->id,
$task->parent_id ?: '-',
Base::filterEmoji($task->project?->name) ?: '-',
@@ -1132,6 +1102,20 @@ class ProjectController extends AbstractController
];
}
});
if (empty($datas)) {
return Base::retError('没有任何数据');
}
//
$sheets = [];
foreach ($userid as $ownerid) {
$data = $datas[$ownerid] ?? [
'nickname' => Base::filterEmoji(User::userid2nickname($ownerid)),
'styles' => ["A1:P1" => ["font" => ["bold" => true]]],
'data' => [],
];
$title = (count($sheets) + 1) . "." . ($data['nickname'] ?: $ownerid);
$sheets[] = BillExport::create()->setTitle($title)->setHeadings($headings)->setData($data['data'])->setStyles($data['styles']);
}
//
$fileName = User::userid2nickname($userid[0]) ?: $userid[0];
if (count($userid) > 1) {
@@ -1139,7 +1123,8 @@ class ProjectController extends AbstractController
}
$fileName .= '任务统计_' . Base::time() . '.xls';
$filePath = "temp/task/export/" . date("Ym", Base::time());
$res = BillExport::create()->setHeadings($headings)->setData($datas)->store($filePath . "/" . $fileName);
$export = new BillMultipleExport($sheets);
$res = $export->store($filePath . "/" . $fileName);
if ($res != 1) {
return Base::retError('导出失败,' . $fileName . '');
}
@@ -1151,7 +1136,7 @@ class ProjectController extends AbstractController
}
try {
Madzipper::make($zipPath)->add($xlsPath)->close();
} catch (\Exception) {
} catch (\Throwable) {
}
//
if (file_exists($zipPath)) {
@@ -1169,9 +1154,8 @@ class ProjectController extends AbstractController
}
/**
* @api {get} api/project/task/down 20. 导出任务(限管理员)
* @api {get} api/project/task/down 20. 下载导出任务
*
* @apiDescription 导出指定范围任务已完成、未完成、已归档返回下载地址需要token身份
* @apiVersion 1.0.0
* @apiGroup project
* @apiName task__down
@@ -1192,7 +1176,7 @@ class ProjectController extends AbstractController
if (empty($file) || !file_exists(storage_path($file))) {
return Base::ajaxError("文件不存在!", [], 0, 502);
}
return response()->download(storage_path($file));
return Response::download(storage_path($file));
}
/**
@@ -1368,6 +1352,9 @@ class ProjectController extends AbstractController
* @apiName task__filedown
*
* @apiParam {Number} file_id 文件ID
* @apiParam {String} down 直接下载
* - yes: 下载(默认)
* - preview: 转预览地址
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
@@ -1378,6 +1365,7 @@ class ProjectController extends AbstractController
User::auth();
//
$file_id = intval(Request::input('file_id'));
$down = Request::input('down', 'yes');
//
$file = ProjectTaskFile::find($file_id);
if (empty($file)) {
@@ -1386,11 +1374,22 @@ class ProjectController extends AbstractController
//
try {
ProjectTask::userTask($file->task_id, null);
} catch (\Exception $e) {
} catch (\Throwable $e) {
abort(403, $e->getMessage() ?: "This file not support download.");
}
//
return Response::download(public_path($file->getRawOriginal('path')), $file->name);
if ($down === 'preview') {
return Redirect::to(FileContent::toPreviewUrl([
'ext' => $file->ext,
'name' => $file->name,
'path' => $file->getRawOriginal('path'),
]));
}
//
$filePath = public_path($file->getRawOriginal('path'));
return Response::streamDownload(function() use ($filePath) {
echo file_get_contents($filePath);
}, $file->name);
}
/**
@@ -1461,6 +1460,7 @@ class ProjectController extends AbstractController
$data['new_column'] = $newColumn;
}
$task->pushMsg('add', $data);
$task->taskPush(null, 0);
return Base::retSuccess('添加成功', $data);
}
@@ -1515,6 +1515,7 @@ class ProjectController extends AbstractController
* @apiParam {Number} task_id 任务ID
* @apiParam {String} [name] 任务描述
* @apiParam {Array} [times] 计划时间(格式:开始时间,结束时间2020-01-01 00:00,2020-01-01 23:59
* @apiParam {String} [loop] 重复周期,数字代表天数(子任务不支持)
* @apiParam {Array} [owner] 修改负责人
* @apiParam {String} [content] 任务详情(子任务不支持)
* @apiParam {String} [color] 背景色(子任务不支持)
@@ -1566,7 +1567,7 @@ class ProjectController extends AbstractController
*/
public function task__dialog()
{
User::auth();
$user = User::auth();
//
$task_id = intval(Request::input('task_id'));
//
@@ -1579,7 +1580,7 @@ class ProjectController extends AbstractController
AbstractModel::transaction(function() use ($task) {
if (empty($task->dialog_id)) {
$task->lockForUpdate();
$dialog = WebSocketDialog::createGroup(null, $task->relationUserids(), 'task');
$dialog = WebSocketDialog::createGroup($task->name, $task->relationUserids(), 'task');
if ($dialog) {
$task->dialog_id = $dialog->id;
$task->save();
@@ -1591,9 +1592,11 @@ class ProjectController extends AbstractController
});
//
$task->pushMsg('dialog');
$dialogData = WebSocketDialog::find($task->dialog_id)?->formatData($user->userid);
return Base::retSuccess('success', [
'id' => $task->id,
'dialog_id' => $task->dialog_id,
'dialog_data' => $dialogData,
]);
}

View File

@@ -0,0 +1,179 @@
<?php
namespace App\Http\Controllers\Api;
use App\Models\User;
use App\Models\UserCheckinMac;
use App\Models\UserCheckinRecord;
use App\Models\WebSocketDialog;
use App\Models\WebSocketDialogMsg;
use App\Module\Base;
use Cache;
use Carbon\Carbon;
use Request;
/**
* @apiDefine public
*
* 公开
*/
class PublicController extends AbstractController
{
/**
* 签到 - 路由器openwrt功能安装脚本
*
* @apiParam {String} key
*
* @return string
*/
public function checkin__install()
{
$key = trim(Request::input('key'));
//
$setting = Base::setting('checkinSetting');
if ($setting['open'] !== 'open') {
return <<<EOF
#!/bin/sh
echo "function off"
EOF;
}
if ($key != $setting['key']) {
return <<<EOF
#!/bin/sh
echo "key error"
EOF;
}
//
$reportUrl = Base::fillUrl("api/public/checkin/report");
return <<<EOE
#!/bin/sh
echo 'installing...'
cat > /etc/init.d/dootask-checkin-report <<EOF
#!/bin/sh
mac=\\\$(awk 'NR!=1&&\\\$3=="0x2" {print \\\$4}' /proc/net/arp | tr "\\n" ",")
tmp='{"key":"{$setting['key']}","mac":"'\\\${mac}'","time":"'\\\$(date +%s)'"}'
curl -4 -X POST "{$reportUrl}" -H "Content-Type: application/json" -d \\\${tmp}
EOF
chmod +x /etc/init.d/dootask-checkin-report
crontab -l >/tmp/cronbak
sed -i '/\/etc\/init.d\/dootask-checkin-report/d' /tmp/cronbak
sed -i '/^$/d' /tmp/cronbak
echo "* * * * * sh /etc/init.d/dootask-checkin-report" >>/tmp/cronbak
crontab /tmp/cronbak
rm -f /tmp/cronbak
/etc/init.d/cron enable
/etc/init.d/cron restart
echo 'installed'
EOE;
}
/**
* {post} 签到 - 路由器openwrt上报
*
* @apiParam {String} key
* @apiParam {String} mac 使用逗号分割多个
* @apiParam {String} time
*
* @return string
*/
public function checkin__report()
{
$key = trim(Request::input('key'));
$mac = trim(Request::input('mac'));
$time = intval(Request::input('time'));
//
$setting = Base::setting('checkinSetting');
if ($setting['open'] !== 'open') {
return 'function off';
}
if ($key != $setting['key']) {
return 'key error';
}
$times = $setting['time'] ? Base::json2array($setting['time']) : ['00:00', '23:59'];
$advance = (intval($setting['advance']) ?: 120) * 60;
$delay = (intval($setting['delay']) ?: 120) * 60;
//
$nowDate = date("Y-m-d");
$nowTime = date("H:i:s");
//
$timeStart = strtotime("{$nowDate} {$times[0]}");
$timeEnd = strtotime("{$nowDate} {$times[1]}");
$timeAdvance = max($timeStart - $advance, strtotime($nowDate));
$timeDelay = min($timeEnd + $delay, strtotime("{$nowDate} 23:59:59"));
if (Base::time() < $timeAdvance || $timeDelay < Base::time()) {
return "not in valid time, valid time is " . date("H:i", $timeAdvance) . "-" . date("H:i", $timeDelay);
}
//
$macs = explode(",", $mac);
$checkins = [];
foreach ($macs as $mac) {
$mac = strtoupper($mac);
if (Base::isMac($mac) && $UserCheckinMac = UserCheckinMac::whereMac($mac)->first()) {
$checkins[] = $UserCheckinMac;
$array = [
'userid' => $UserCheckinMac->userid,
'mac' => $UserCheckinMac->mac,
'date' => $nowDate,
];
$record = UserCheckinRecord::where($array)->first();
if (empty($record)) {
$record = UserCheckinRecord::createInstance($array);
}
$record->times = Base::array2json(array_merge($record->times, [$nowTime]));
$record->report_time = $time;
$record->save();
}
}
//
if ($checkins && $botUser = User::botGetOrCreate('check-in')) {
$getJokeSoup = function($type) {
$pre = $type == "up" ? "每日开心:" : "心灵鸡汤:";
$key = $type == "up" ? "JokeSoupTask:jokes" : "JokeSoupTask:soups";
$array = Base::json2array(Cache::get($key));
if ($array) {
$item = $array[array_rand($array)];
if ($item) {
return $pre . $item;
}
}
return null;
};
$sendMsg = function($type, UserCheckinMac $checkin) use ($getJokeSoup, $botUser, $nowDate) {
$cacheKey = "Checkin::sendMsg-{$nowDate}-{$type}:" . $checkin->userid;
if (Cache::get($cacheKey) === "yes") {
return;
}
Cache::put($cacheKey, "yes", Carbon::now()->addDay());
//
$dialog = WebSocketDialog::checkUserDialog($botUser->userid, $checkin->userid);
if ($dialog) {
$hi = date("H:i");
$pre = $type == "up" ? "上班" : "下班";
$remark = $checkin->remark ? " ({$checkin->remark})": "";
$text = "<p>{$pre}打卡成功,打卡时间: {$hi}{$remark}</p>";
$suff = $getJokeSoup($type);
if ($suff) {
$text = "{$text}<p>----------</p><p>{$suff}</p>";
}
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'text', ['text' => $text], $botUser->userid);
}
};
if ($timeAdvance <= Base::time() && Base::time() < $timeEnd) {
// 上班打卡通知(从最早打卡时间 到 下班打卡时间)
foreach ($checkins as $checkin) {
$sendMsg('up', $checkin);
}
}
if ($timeEnd <= Base::time() && Base::time() <= $timeDelay) {
// 下班打卡通知(下班打卡时间 到 最晚打卡时间)
foreach ($checkins as $checkin) {
$sendMsg('down', $checkin);
}
}
}
return 'success';
}
}

View File

@@ -119,12 +119,13 @@ class ReportController extends AbstractController
* @apiGroup report
* @apiName store
*
* @apiParam {Number} [id] 汇报ID
* @apiParam {String} [title] 汇报标题
* @apiParam {Array} [type] 汇报类型weekly:周报daily:日报
* @apiParam {Number} [content] 内容
* @apiParam {Number} [receive] 汇报对象
* @apiParam {Number} [offset] 偏移量
* @apiParam {Number} id 汇报ID0为新建
* @apiParam {String} [sign] 唯一签名,通过[api/report/template]接口返回
* @apiParam {String} title 汇报标题
* @apiParam {Array} type 汇报类型weekly:周报daily:日报
* @apiParam {Number} content 内容
* @apiParam {Number} [receive] 汇报对象
* @apiParam {Number} offset 时间偏移量
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
@@ -132,8 +133,11 @@ class ReportController extends AbstractController
*/
public function store(): array
{
$user = User::auth();
//
$input = [
"id" => Base::getPostValue("id", 0),
"sign" => Base::getPostValue("sign"),
"title" => Base::getPostValue("title"),
"type" => Base::getPostValue("type"),
"content" => Base::getPostValue("content"),
@@ -146,7 +150,6 @@ class ReportController extends AbstractController
'title' => 'required',
'type' => ['required', Rule::in([Report::WEEKLY, Report::DAILY])],
'content' => 'required',
'receive' => 'required',
'offset' => ['numeric', 'max:0'],
], [
'id.numeric' => 'ID只能是数字',
@@ -154,14 +157,12 @@ class ReportController extends AbstractController
'type.required' => '请选择汇报类型',
'type.in' => '汇报类型错误',
'content.required' => '请填写汇报内容',
'receive.required' => '请选择接收人',
'offset.numeric' => '工作汇报周期格式错误,只能是数字',
'offset.max' => '只能提交当天/本周或者之前的的工作汇报',
]);
if ($validator->fails())
return Base::retError($validator->errors()->first());
$user = User::auth();
// 接收人
if (is_array($input["receive"])) {
// 删除当前登录人
@@ -193,25 +194,24 @@ class ReportController extends AbstractController
]);
} else {
// 生成唯一标识
$sign = Report::generateSign($input["type"], $input["offset"]);
$sign = Base::isNumber($input["sign"]) ? $input["sign"] : Report::generateSign($input["type"], $input["offset"]);
// 检查唯一标识是否存在
if (empty($input["id"])) {
if (Report::query()->whereSign($sign)->whereType($input["type"])->count() > 0)
throw new ApiException("请勿重复提交工作汇报");
if (empty($input["id"]) && Report::query()->whereSign($sign)->whereType($input["type"])->count() > 0) {
throw new ApiException("请勿重复提交工作汇报");
}
$report = Report::createInstance([
"sign" => $sign,
"title" => $input["title"],
"type" => $input["type"],
"content" => htmlspecialchars($input["content"]),
"userid" => $user->userid,
"sign" => $sign,
"content" => htmlspecialchars($input["content"]),
]);
}
$report->save();
if (!empty($input["receive_content"])) {
// 删除关联
$report->Receives()->delete();
// 删除关联
$report->Receives()->delete();
if ($input["receive_content"]) {
// 保存接收人
$report->Receives()->createMany($input["receive_content"]);
}
@@ -286,9 +286,10 @@ class ReportController extends AbstractController
// 如果已经提交了相关汇报
if ($one && $id > 0) {
return Base::retSuccess('success', [
"content" => $one->content,
"title" => $one->title,
"id" => $one->id,
"sign" => $one->sign,
"title" => $one->title,
"content" => $one->content,
]);
}
@@ -306,7 +307,7 @@ class ReportController extends AbstractController
foreach ($complete_task as $task) {
$complete_at = Carbon::parse($task->complete_at);
$pre = $type == Report::WEEKLY ? ('<span>[' . Base::Lang('周' . ['日', '一', '二', '三', '四', '五', '六'][$complete_at->dayOfWeek]) . ']</span>&nbsp;') : '';
$completeContent .= '<li>' . $pre . $task->name . '</li>';
$completeContent .= "<li>{$pre}[{$task->project->name}] {$task->name}</li>";
}
} else {
$completeContent = '<li>&nbsp;</li>';
@@ -327,7 +328,7 @@ class ReportController extends AbstractController
foreach ($unfinished_task as $task) {
empty($task->end_at) || $end_at = Carbon::parse($task->end_at);
$pre = (!empty($end_at) && $end_at->lt($now_dt)) ? '<span style="color:#ff0000;">[' . Base::Lang('超期') . ']</span>&nbsp;' : '';
$unfinishedContent .= '<li>' . $pre . $task->name . '</li>';
$unfinishedContent .= "<li>{$pre}[{$task->project->name}] {$task->name}</li>";
}
} else {
$unfinishedContent = '<li>&nbsp;</li>';
@@ -341,13 +342,14 @@ class ReportController extends AbstractController
}
$data = [
"time" => $start_time->toDateTimeString(),
"complete_task" => $complete_task,
"unfinished_task" => $unfinished_task,
"sign" => $sign,
"title" => $title,
"content" => '<h2>' . Base::Lang('已完成工作') . '</h2><ol>' .
$completeContent . '</ol><h2>' .
Base::Lang('未完成的工作') . '</h2><ol>' .
$unfinishedContent . '</ol>',
"title" => $title,
"complete_task" => $complete_task,
"unfinished_task" => $unfinished_task,
];
if ($one) {
$data['id'] = $one->id;
@@ -393,7 +395,49 @@ class ReportController extends AbstractController
}
/**
* @api {get} api/report/last_submitter 06. 获取最后一次提交的接收人
* @api {get} api/report/mark 06. 标记已读/未读
*
* @apiVersion 1.0.0
* @apiGroup report
* @apiName mark
*
* @apiParam {Number} id 报告id
* @apiParam {Number} action 操作
* - read: 标记已读(默认)
* - unread: 标记未读
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function mark(): array
{
$user = User::auth();
//
$id = Request::input('id');
$action = Request::input('action');
//
if (is_array($id)) {
if (count(Base::arrayRetainInt($id)) > 100) {
return Base::retError("最多只能操作100条数据");
}
$builder = Report::whereIn("id", Base::arrayRetainInt($id));
} else {
$builder = Report::whereId(intval($id));
}
$builder ->chunkById(100, function ($list) use ($action, $user) {
/** @var Report $item */
foreach ($list as $item) {
$item->receivesUser()->updateExistingPivot($user->userid, [
"read" => $action === 'unread' ? 0 : 1,
]);
}
});
return Base::retSuccess("操作成功");
}
/**
* @api {get} api/report/last_submitter 07. 获取最后一次提交的接收人
*
* @apiVersion 1.0.0
* @apiGroup report
@@ -410,7 +454,7 @@ class ReportController extends AbstractController
}
/**
* @api {get} api/report/unread 07. 获取未读
* @api {get} api/report/unread 08. 获取未读
*
* @apiVersion 1.0.0
* @apiGroup report
@@ -434,7 +478,7 @@ class ReportController extends AbstractController
}
/**
* @api {get} api/report/read 08. 标记汇报已读,可批量
* @api {get} api/report/read 09. 标记汇报已读,可批量
*
* @apiVersion 1.0.0
* @apiGroup report
@@ -455,7 +499,7 @@ class ReportController extends AbstractController
}
if (is_string($ids)) {
$ids = explode(",", $ids);
$ids = Base::explodeInt($ids);
}
$data = Report::with(["receivesUser" => function (BelongsToMany $query) use ($user) {

View File

@@ -2,12 +2,26 @@
namespace App\Http\Controllers\Api;
use App\Ldap\LdapUser;
use App\Models\Setting;
use App\Models\User;
use App\Models\UserCheckinRecord;
use App\Module\Base;
use App\Module\BillExport;
use App\Module\BillMultipleExport;
use Arr;
use Carbon\Carbon;
use Config;
use Guanguans\Notify\Factory;
use Guanguans\Notify\Messages\EmailMessage;
use LdapRecord\Auth\PasswordRequiredException;
use LdapRecord\Auth\UsernameRequiredException;
use LdapRecord\Container;
use LdapRecord\LdapRecordException;
use Madzipper;
use Request;
use Response;
use Session;
/**
* @apiDefine system
@@ -27,7 +41,7 @@ class SystemController extends AbstractController
* @apiParam {String} type
* - get: 获取(默认)
* - all: 获取所有(需要管理员权限)
* - save: 保存设置(参数:['reg', 'reg_invite', 'login_code', 'password_policy', 'project_invite', 'chat_nickname', 'auto_archived', 'archived_day', 'start_home', 'home_footer']
* - save: 保存设置(参数:['reg', 'reg_invite', 'login_code', 'password_policy', 'project_invite', 'chat_information', 'auto_archived', 'archived_day', 'all_group_mute', 'all_group_autoin', 'start_home', 'home_footer']
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
@@ -49,9 +63,11 @@ class SystemController extends AbstractController
'login_code',
'password_policy',
'project_invite',
'chat_nickname',
'chat_information',
'auto_archived',
'archived_day',
'all_group_mute',
'all_group_autoin',
'start_home',
'home_footer'
])) {
@@ -82,9 +98,11 @@ class SystemController extends AbstractController
$setting['login_code'] = $setting['login_code'] ?: 'auto';
$setting['password_policy'] = $setting['password_policy'] ?: 'simple';
$setting['project_invite'] = $setting['project_invite'] ?: 'open';
$setting['chat_nickname'] = $setting['chat_nickname'] ?: 'optional';
$setting['chat_information'] = $setting['chat_information'] ?: 'optional';
$setting['auto_archived'] = $setting['auto_archived'] ?: 'close';
$setting['archived_day'] = floatval($setting['archived_day']) ?: 7;
$setting['all_group_mute'] = $setting['all_group_mute'] ?: 'open';
$setting['all_group_autoin'] = $setting['all_group_autoin'] ?: 'yes';
$setting['start_home'] = $setting['start_home'] ?: 'close';
//
return Base::retSuccess('success', $setting ?: json_decode('{}'));
@@ -99,20 +117,21 @@ class SystemController extends AbstractController
*
* @apiParam {String} type
* - get: 获取(默认)
* - save: 保存设置(参数:['smtp_server', 'port', 'account', 'password', 'reg_verify', 'notice', 'task_remind_hours', 'task_remind_hours2']
* - save: 保存设置(参数:['smtp_server', 'port', 'account', 'password', 'reg_verify', 'notice_msg', 'msg_unread_user_minute', 'msg_unread_group_minute', 'ignore_addr']
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function setting__email()
{
User::auth('admin');
$user = User::auth();
//
$type = trim(Request::input('type'));
if ($type == 'save') {
if (env("SYSTEM_SETTING") == 'disabled') {
return Base::retError('当前环境禁止修改');
}
$user->identity('admin');
$all = Request::input();
foreach ($all as $key => $value) {
if (!in_array($key, [
@@ -121,9 +140,10 @@ class SystemController extends AbstractController
'account',
'password',
'reg_verify',
'notice',
'task_remind_hours',
'task_remind_hours2'
'notice_msg',
'msg_unread_user_minute',
'msg_unread_group_minute',
'ignore_addr'
])) {
unset($all[$key]);
}
@@ -138,15 +158,237 @@ class SystemController extends AbstractController
$setting['account'] = $setting['account'] ?: '';
$setting['password'] = $setting['password'] ?: '';
$setting['reg_verify'] = $setting['reg_verify'] ?: 'close';
$setting['notice'] = $setting['notice'] ?: 'open';
$setting['task_remind_hours'] = floatval($setting['task_remind_hours']) ?: 0;
$setting['task_remind_hours2'] = floatval($setting['task_remind_hours2']) ?: 0;
$setting['notice_msg'] = $setting['notice_msg'] ?: 'close';
$setting['msg_unread_user_minute'] = intval($setting['msg_unread_user_minute'] ?? -1);
$setting['msg_unread_group_minute'] = intval($setting['msg_unread_group_minute'] ?? -1);
$setting['ignore_addr'] = $setting['ignore_addr'] ?: '';
//
if ($type != 'save' && !in_array('admin', $user->identity)) {
$setting = array_intersect_key($setting, array_flip(['reg_verify']));
}
//
return Base::retSuccess('success', $setting ?: json_decode('{}'));
}
/**
* @api {get} api/system/demo 03. 获取演示帐号
* @api {get} api/system/setting/meeting 03. 获取会议设置、保存会议设置(限管理员)
*
* @apiVersion 1.0.0
* @apiGroup system
* @apiName setting__meeting
*
* @apiParam {String} type
* - get: 获取(默认)
* - save: 保存设置(参数:['open', 'appid', 'app_certificate']
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function setting__meeting()
{
User::auth('admin');
//
$type = trim(Request::input('type'));
if ($type == 'save') {
if (env("SYSTEM_SETTING") == 'disabled') {
return Base::retError('当前环境禁止修改');
}
$all = Request::input();
foreach ($all as $key => $value) {
if (!in_array($key, [
'open',
'appid',
'app_certificate',
])) {
unset($all[$key]);
}
}
$setting = Base::setting('meetingSetting', Base::newTrim($all));
} else {
$setting = Base::setting('meetingSetting');
}
//
$setting['open'] = $setting['open'] ?: 'close';
//
return Base::retSuccess('success', $setting ?: json_decode('{}'));
}
/**
* @api {get} api/system/setting/checkin 03. 获取签到设置、保存签到设置(限管理员)
*
* @apiVersion 1.0.0
* @apiGroup system
* @apiName setting__checkin
*
* @apiParam {String} type
* - get: 获取(默认)
* - save: 保存设置(参数:['open', 'time', 'advance', 'delay', 'edit', 'key']
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function setting__checkin()
{
User::auth('admin');
//
$type = trim(Request::input('type'));
if ($type == 'save') {
if (env("SYSTEM_SETTING") == 'disabled') {
return Base::retError('当前环境禁止修改');
}
$all = Request::input();
foreach ($all as $key => $value) {
if (!in_array($key, [
'open',
'time',
'advance',
'delay',
'edit',
'key',
])) {
unset($all[$key]);
}
}
if ($all['open'] === 'close') {
$all['key'] = md5(Base::generatePassword(32));
}
$setting = Base::setting('checkinSetting', Base::newTrim($all));
} else {
$setting = Base::setting('checkinSetting');
}
//
if (empty($setting['key'])) {
$setting['key'] = md5(Base::generatePassword(32));
Base::setting('checkinSetting', $setting);
}
//
$setting['open'] = $setting['open'] ?: 'close';
$setting['time'] = $setting['time'] ? Base::json2array($setting['time']) : ['00:00', '23:59'];
$setting['advance'] = intval($setting['advance']) ?: 120;
$setting['delay'] = intval($setting['delay']) ?: 120;
$setting['edit'] = $setting['edit'] ?: 'close';
$setting['cmd'] = "curl -sSL '" . Base::fillUrl("api/public/checkin/install?key={$setting['key']}") . "' | sh";
//
return Base::retSuccess('success', $setting ?: json_decode('{}'));
}
/**
* @api {get} api/system/setting/apppush 04. 获取APP推送设置、保存APP推送设置限管理员
*
* @apiVersion 1.0.0
* @apiGroup system
* @apiName setting__apppush
*
* @apiParam {String} type
* - get: 获取(默认)
* - save: 保存设置(参数:['push', 'ios_key', 'ios_secret', 'android_key', 'android_secret']
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function setting__apppush()
{
User::auth('admin');
//
$type = trim(Request::input('type'));
if ($type == 'save') {
if (env("SYSTEM_SETTING") == 'disabled') {
return Base::retError('当前环境禁止修改');
}
$all = Request::input();
foreach ($all as $key => $value) {
if (!in_array($key, [
'push',
'ios_key',
'ios_secret',
'android_key',
'android_secret',
])) {
unset($all[$key]);
}
}
$setting = Base::setting('appPushSetting', Base::newTrim($all));
} else {
$setting = Base::setting('appPushSetting');
}
//
$setting['push'] = $setting['push'] ?: 'close';
//
return Base::retSuccess('success', $setting ?: json_decode('{}'));
}
/**
* @api {get} api/system/setting/thirdaccess 04. 第三方帐号(限管理员)
*
* @apiVersion 1.0.0
* @apiGroup system
* @apiName setting__thirdaccess
*
* @apiParam {String} type
* - get: 获取(默认)
* - save: 保存设置(参数:['ldap_open', 'ldap_host', 'ldap_port', 'ldap_password', 'ldap_user_dn', 'ldap_base_dn', 'ldap_sync_local']
* - testldap: 测试ldap连接
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function setting__thirdaccess()
{
User::auth('admin');
//
$type = trim(Request::input('type'));
if ($type == 'testldap') {
$all = Base::newTrim(Request::input());
$connection = Container::getDefaultConnection();
try {
$connection->setConfiguration([
"hosts" => [$all['ldap_host']],
"port" => intval($all['ldap_port']),
"password" => $all['ldap_password'],
"username" => $all['ldap_user_dn'],
"base_dn" => $all['ldap_base_dn'],
]);
if ($connection->auth()->attempt($all['ldap_user_dn'], $all['ldap_password'])) {
return Base::retSuccess('验证通过');
} else {
return Base::retError('验证失败');
}
} catch (LdapRecordException $e) {
return Base::retError($e->getMessage() ?: "验证失败:未知错误", config("ldap.connections.default"));
}
} elseif ($type == 'save') {
if (env("SYSTEM_SETTING") == 'disabled') {
return Base::retError('当前环境禁止修改');
}
$all = Base::newTrim(Request::input());
foreach ($all as $key => $value) {
if (!in_array($key, [
'ldap_open',
'ldap_host',
'ldap_port',
'ldap_password',
'ldap_user_dn',
'ldap_base_dn',
'ldap_sync_local'
])) {
unset($all[$key]);
}
}
$all['ldap_port'] = intval($all['ldap_port']) ?: 389;
$setting = Base::setting('thirdAccessSetting', Base::newTrim($all));
} else {
$setting = Base::setting('thirdAccessSetting');
}
//
$setting['ldap_open'] = $setting['ldap_open'] ?: 'close';
$setting['ldap_port'] = intval($setting['ldap_port']) ?: 389;
$setting['ldap_sync_local'] = $setting['ldap_sync_local'] ?: 'close';
//
return Base::retSuccess('success', $setting ?: json_decode('{}'));
}
/**
* @api {get} api/system/demo 05. 获取演示帐号
*
* @apiVersion 1.0.0
* @apiGroup system
@@ -170,7 +412,7 @@ class SystemController extends AbstractController
}
/**
* @api {post} api/system/priority 04. 任务优先级
* @api {post} api/system/priority 06. 任务优先级
*
* @apiDescription 获取任务优先级、保存任务优先级
* @apiVersion 1.0.0
@@ -219,7 +461,7 @@ class SystemController extends AbstractController
}
/**
* @api {post} api/system/column/template 05. 创建项目模板
* @api {post} api/system/column/template 07. 创建项目模板
*
* @apiDescription 获取创建项目模板、保存创建项目模板
* @apiVersion 1.0.0
@@ -266,7 +508,7 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/get/info 06. 获取终端详细信息
* @api {get} api/system/get/info 08. 获取终端详细信息
*
* @apiVersion 1.0.0
* @apiGroup system
@@ -295,7 +537,7 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/get/ip 07. 获取IP地址
* @api {get} api/system/get/ip 09. 获取IP地址
*
* @apiVersion 1.0.0
* @apiGroup system
@@ -310,7 +552,7 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/get/cnip 08. 是否中国IP地址
* @api {get} api/system/get/cnip 10. 是否中国IP地址
*
* @apiVersion 1.0.0
* @apiGroup system
@@ -327,7 +569,7 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/get/ipgcj02 09. 获取IP地址经纬度
* @api {get} api/system/get/ipgcj02 11. 获取IP地址经纬度
*
* @apiVersion 1.0.0
* @apiGroup system
@@ -344,7 +586,7 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/get/ipinfo 10. 获取IP地址详细信息
* @api {get} api/system/get/ipinfo 12. 获取IP地址详细信息
*
* @apiVersion 1.0.0
* @apiGroup system
@@ -361,15 +603,22 @@ class SystemController extends AbstractController
}
/**
* @api {post} api/system/imgupload 11. 上传图片
* @api {post} api/system/imgupload 13. 上传图片
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup system
* @apiName imgupload
*
* @apiParam {String} image64 图片base64
* @apiParam {String} filename 文件名
* @apiParam {File} image post-图片对象
* @apiParam {String} [image64] post-图片base64与'image'二选一)
* @apiParam {String} filename post-文件名
* @apiParam {Number} [width] 压缩图片宽默认0
* @apiParam {Number} [height] 压缩图片高默认0
* @apiParam {String} [whcut] 压缩方式
* - 1裁切默认宽、高非0有效
* - 0缩放
* - -1或'auto':保持等比裁切
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
@@ -380,9 +629,12 @@ class SystemController extends AbstractController
if (User::userid() === 0) {
return Base::retError('身份失效,等重新登录');
}
$scale = [intval(Request::input('width')), intval(Request::input('height'))];
if (!$scale[0] && !$scale[1]) {
$scale = [2160, 4160, -1];
$width = intval(Request::input('width'));
$height = intval(Request::input('height'));
$whcut = intval(Request::input('whcut', 1));
$scale = [2160, 4160, -1];
if ($width > 0 || $height > 0) {
$scale = [$width, $height, $whcut];
}
$path = "uploads/user/picture/" . User::userid() . "/" . date("Ym") . "/";
$image64 = trim(Base::getPostValue('image64'));
@@ -411,7 +663,7 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/get/imgview 12. 浏览图片空间
* @api {get} api/system/get/imgview 14. 浏览图片空间
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@@ -507,7 +759,7 @@ class SystemController extends AbstractController
}
/**
* @api {post} api/system/fileupload 13. 上传文件
* @api {post} api/system/fileupload 15. 上传文件
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@@ -549,7 +801,7 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/get/starthome 14. 启动首页设置信息
* @api {get} api/system/get/starthome 16. 启动首页设置信息
*
* @apiDescription 用于判断注册是否需要启动首页
* @apiVersion 1.0.0
@@ -569,7 +821,7 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/email/check 15. 邮件发送测试(限管理员)
* @api {get} api/system/email/check 17. 邮件发送测试(限管理员)
*
* @apiDescription 测试配置邮箱是否能发送邮件
* @apiVersion 1.0.0
@@ -589,16 +841,20 @@ class SystemController extends AbstractController
return Base::retError('请输入正确的收件人地址');
}
try {
Factory::mailer()
->setDsn("smtp://{$all['account']}:{$all['password']}@{$all['smtp_server']}:{$all['port']}?verify_peer=0")
->setMessage(EmailMessage::create()
->from(env('APP_NAME', 'Task') . " <{$all['account']}>")
->to($all['to'])
->subject('Mail sending test')
->html('<p>收到此电子邮件意味着您的邮箱配置正确。</p><p>Receiving this email means that your mailbox is configured correctly.</p>'))
->send();
Setting::validateAddr($all['to'], function($to) use ($all) {
Factory::mailer()
->setDsn("smtp://{$all['account']}:{$all['password']}@{$all['smtp_server']}:{$all['port']}?verify_peer=0")
->setMessage(EmailMessage::create()
->from(env('APP_NAME', 'Task') . " <{$all['account']}>")
->to($to)
->subject('Mail sending test')
->html('<p>收到此电子邮件意味着您的邮箱配置正确。</p><p>Receiving this email means that your mailbox is configured correctly.</p>'))
->send();
}, function () {
throw new \Exception("收件人地址错误或已被忽略");
});
return Base::retSuccess('成功发送');
} catch (\Exception $e) {
} catch (\Throwable $e) {
// 一般是请求超时
if (str_contains($e->getMessage(), "Timed Out")) {
return Base::retError("language.TimedOut");
@@ -609,4 +865,226 @@ class SystemController extends AbstractController
}
}
}
/**
* @api {get} api/system/checkin/export 17. 导出签到数据(限管理员)
*
* @apiVersion 1.0.0
* @apiGroup system
* @apiName checkin__export
*
* @apiParam {Array} [userid] 指定会员,如:[1, 2]
* @apiParam {Array} [date] 指定日期范围,如:['2020-12-12', '2020-12-30']
* @apiParam {Array} [time] 指定时间范围,如:['09:00', '18:00']
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function checkin__export()
{
User::auth('admin');
//
$setting = Base::setting('checkinSetting');
if ($setting['open'] !== 'open') {
return Base::retError('此功能未开启,请前往系统设置开启');
}
//
$userid = Base::arrayRetainInt(Request::input('userid'), true);
$date = Request::input('date');
$time = Request::input('time');
//
if (empty($userid) || empty($date) || empty($time)) {
return Base::retError('参数错误');
}
if (count($userid) > 100) {
return Base::retError('导出成员限制最多100个');
}
if (!(is_array($date) && Base::isDate($date[0]) && Base::isDate($date[1]))) {
return Base::retError('日期选择错误');
}
if (Carbon::parse($date[1])->timestamp - Carbon::parse($date[0])->timestamp > 35 * 86400) {
return Base::retError('日期范围限制最大35天');
}
if (!(is_array($time) && Base::isTime($time[0]) && Base::isTime($time[1]))) {
return Base::retError('时间选择错误');
}
//
$secondStart = strtotime("2000-01-01 {$time[0]}") - strtotime("2000-01-01 00:00:00");
$secondEnd = strtotime("2000-01-01 {$time[1]}") - strtotime("2000-01-01 00:00:00");
//
$headings = [];
$headings[] = '签到人';
$headings[] = '签到日期';
$headings[] = '班次时间';
$headings[] = '首次签到时间';
$headings[] = '首次签到结果';
$headings[] = '最后签到时间';
$headings[] = '最后签到结果';
$headings[] = '参数数据';
//
$sheets = [];
$startD = Carbon::parse($date[0])->startOfDay();
$endD = Carbon::parse($date[1])->endOfDay();
$users = User::whereIn('userid', $userid)->take(100)->get();
/** @var User $user */
foreach ($users as $user) {
$recordTimes = UserCheckinRecord::getTimes($user->userid, [$startD, $endD]);
//
$nickname = Base::filterEmoji($user->nickname);
$styles = ["A1:H1" => ["font" => ["bold" => true]]];
$datas = [];
$startT = $startD->timestamp;
$endT = $endD->timestamp;
$index = 1;
while ($startT < $endT) {
$index++;
$sameDate = date("Y-m-d", $startT);
$sameTimes = $recordTimes[$sameDate] ?? [];
$sameCollect = UserCheckinRecord::atCollect($sameDate, $sameTimes);
$firstBetween = [Carbon::createFromTimestamp($startT), Carbon::createFromTimestamp($startT + $secondEnd - 1)];
$lastBetween = [Carbon::createFromTimestamp($startT + $secondStart + 1), Carbon::createFromTimestamp($startT + 86400)];
$firstRecord = $sameCollect?->whereBetween("datetime", $firstBetween)->first();
$lastRecord = $sameCollect?->whereBetween("datetime", $lastBetween)->last();
$firstTimestamp = $firstRecord['timestamp'] ?: 0;
$lastTimestamp = $lastRecord['timestamp'] ?: 0;
if (Base::time() < $startT + $secondStart) {
$firstResult = "-";
} else {
$firstResult = "正常";
if (empty($firstTimestamp)) {
$firstResult = "缺卡";
$styles["E{$index}"] = ["font" => ["color" => ["rgb" => "ff0000"]]];
} elseif ($firstTimestamp > $startT + $secondStart) {
$firstResult = "迟到";
$styles["E{$index}"] = ["font" => ["color" => ["rgb" => "436FF6"]]];
}
}
if (Base::time() < $startT + $secondEnd) {
$lastResult = "-";
$lastTimestamp = 0;
} else {
$lastResult = "正常";
if (empty($lastTimestamp) || $lastTimestamp === $firstTimestamp) {
$lastResult = "缺卡";
$styles["G{$index}"] = ["font" => ["color" => ["rgb" => "ff0000"]]];
} elseif ($lastTimestamp < $startT + $secondEnd) {
$lastResult = "早退";
$styles["G{$index}"] = ["font" => ["color" => ["rgb" => "436FF6"]]];
}
}
$firstTimestamp = $firstTimestamp ? date("H:i", $firstTimestamp) : "-";
$lastTimestamp = $lastTimestamp ? date("H:i", $lastTimestamp) : "-";
$section = array_map(function($item) {
return $item[0] . "-" . ($item[1] ?: "None");
}, UserCheckinRecord::atSection($sameTimes));
$datas[] = [
"{$nickname} (ID: {$user->userid})",
$sameDate,
implode("-", $time),
$firstTimestamp,
$firstResult,
$lastTimestamp,
$lastResult,
implode(", ", $section),
];
$startT += 86400;
}
$title = (count($sheets) + 1) . "." . ($nickname ?: $user->userid);
$sheets[] = BillExport::create()->setTitle($title)->setHeadings($headings)->setData($datas)->setStyles($styles);
}
if (empty($sheets)) {
return Base::retError('没有任何数据');
}
//
$fileName = $users[0]->nickname;
if (count($users) > 1) {
$fileName .= "" . count($userid) . "位成员";
}
$fileName .= '签到记录_' . Base::time() . '.xls';
$filePath = "temp/checkin/export/" . date("Ym", Base::time());
$export = new BillMultipleExport($sheets);
$res = $export->store($filePath . "/" . $fileName);
if ($res != 1) {
return Base::retError('导出失败,' . $fileName . '');
}
$xlsPath = storage_path("app/" . $filePath . "/" . $fileName);
$zipFile = "app/" . $filePath . "/" . Base::rightDelete($fileName, '.xls') . ".zip";
$zipPath = storage_path($zipFile);
if (file_exists($zipPath)) {
Base::deleteDirAndFile($zipPath, true);
}
try {
Madzipper::make($zipPath)->add($xlsPath)->close();
} catch (\Throwable) {
}
//
if (file_exists($zipPath)) {
$base64 = base64_encode(Base::array2string([
'file' => $zipFile,
]));
Session::put('checkin::export:userid', $user->userid);
return Base::retSuccess('success', [
'size' => Base::twoFloat(filesize($zipPath) / 1024, true),
'url' => Base::fillUrl('api/system/checkin/down?key=' . urlencode($base64)),
]);
} else {
return Base::retError('打包失败,请稍后再试...');
}
}
/**
* @api {get} api/system/checkin/down 17. 下载导出的签到数据
*
* @apiVersion 1.0.0
* @apiGroup system
* @apiName checkin__down
*
* @apiParam {String} key 通过export接口得到的下载钥匙
*
* @apiSuccess {File} data 返回数据(直接下载文件)
*/
public function checkin__down()
{
$userid = Session::get('checkin::export:userid');
if (empty($userid)) {
return Base::ajaxError("请求已过期,请重新导出!", [], 0, 502);
}
//
$array = Base::string2array(base64_decode(urldecode(Request::input('key'))));
$file = $array['file'];
if (empty($file) || !file_exists(storage_path($file))) {
return Base::ajaxError("文件不存在!", [], 0, 502);
}
return Response::download(storage_path($file));
}
/**
* @api {get} api/system/version 18. 获取版本号
*
* @apiVersion 1.0.0
* @apiGroup system
* @apiName version
*
* @apiSuccess {String} version
* @apiSuccess {String} publish
*/
public function version()
{
$url = url('');
$package = Base::getPackage();
$array = [
'version' => Base::getVersion(),
'publish' => Arr::get($package, 'app.0.publish'),
];
if (is_array($package['app'])) {
foreach ($package['app'] as $item) {
$urls = $item['urls'] && is_array($item['urls']) ? $item['urls'] : $item['url'];
if (is_array($item['publish']) && Base::hostContrast($url, $urls)) {
$array['publish'] = $item['publish'];
}
}
}
return $array;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -2,14 +2,24 @@
namespace App\Http\Controllers;
use App\Models\File;
use App\Module\Base;
use App\Module\Ihttp;
use App\Module\RandomColor;
use App\Tasks\AppPushTask;
use App\Tasks\AutoArchivedTask;
use App\Tasks\DeleteBotMsgTask;
use App\Tasks\DeleteTmpTask;
use App\Tasks\OverdueRemindEmailTask;
use App\Tasks\EmailNoticeTask;
use App\Tasks\JokeSoupTask;
use App\Tasks\LoopTask;
use Arr;
use Cache;
use Hhxsv5\LaravelS\Swoole\Task\Task;
use LasseRafn\InitialAvatarGenerator\InitialAvatar;
use Redirect;
use Request;
use Response;
/**
@@ -69,28 +79,28 @@ class IndexController extends InvokeController
}
$array = [
"office/web-apps/apps/api/documents/api.js?hash=" . Base::getVersion(),
"office/7.0.1-37/web-apps/vendor/requirejs/require.js",
"office/7.0.1-37/web-apps/apps/api/documents/api.js",
"office/7.0.1-37/sdkjs/common/AllFonts.js",
"office/7.0.1-37/web-apps/vendor/xregexp/xregexp-all-min.js",
"office/7.0.1-37/web-apps/vendor/sockjs/sockjs.min.js",
"office/7.0.1-37/web-apps/vendor/jszip/jszip.min.js",
"office/7.0.1-37/web-apps/vendor/jszip-utils/jszip-utils.min.js",
"office/7.0.1-37/sdkjs/common/libfont/wasm/fonts.js",
"office/7.0.1-37/sdkjs/common/Charts/ChartStyles.js",
"office/7.0.1-37/sdkjs/slide/themes//themes.js",
"office/7.3.0-184/web-apps/vendor/requirejs/require.js",
"office/7.3.0-184/web-apps/apps/api/documents/api.js",
"office/7.3.0-184/sdkjs/common/AllFonts.js",
"office/7.3.0-184/web-apps/vendor/xregexp/xregexp-all-min.js",
"office/7.3.0-184/web-apps/vendor/sockjs/sockjs.min.js",
"office/7.3.0-184/web-apps/vendor/jszip/jszip.min.js",
"office/7.3.0-184/web-apps/vendor/jszip-utils/jszip-utils.min.js",
"office/7.3.0-184/sdkjs/common/libfont/wasm/fonts.js",
"office/7.3.0-184/sdkjs/common/Charts/ChartStyles.js",
"office/7.3.0-184/sdkjs/slide/themes//themes.js",
"office/7.0.1-37/web-apps/apps/presentationeditor/main/app.js",
"office/7.0.1-37/sdkjs/slide/sdk-all-min.js",
"office/7.0.1-37/sdkjs/slide/sdk-all.js",
"office/7.3.0-184/web-apps/apps/presentationeditor/main/app.js",
"office/7.3.0-184/sdkjs/slide/sdk-all-min.js",
"office/7.3.0-184/sdkjs/slide/sdk-all.js",
"office/7.0.1-37/web-apps/apps/documenteditor/main/app.js",
"office/7.0.1-37/sdkjs/word/sdk-all-min.js",
"office/7.0.1-37/sdkjs/word/sdk-all.js",
"office/7.3.0-184/web-apps/apps/documenteditor/main/app.js",
"office/7.3.0-184/sdkjs/word/sdk-all-min.js",
"office/7.3.0-184/sdkjs/word/sdk-all.js",
"office/7.0.1-37/web-apps/apps/spreadsheeteditor/main/app.js",
"office/7.0.1-37/sdkjs/cell/sdk-all-min.js",
"office/7.0.1-37/sdkjs/cell/sdk-all.js",
"office/7.3.0-184/web-apps/apps/spreadsheeteditor/main/app.js",
"office/7.3.0-184/sdkjs/cell/sdk-all-min.js",
"office/7.3.0-184/sdkjs/cell/sdk-all.js",
];
foreach ($array as &$item) {
$item = url($item);
@@ -100,25 +110,55 @@ class IndexController extends InvokeController
/**
* 获取版本号
* @return array
* @return \Illuminate\Http\RedirectResponse
*/
public function version()
{
$url = url('');
$package = Base::getPackage();
$array = [
'version' => Base::getVersion(),
'publish' => Arr::get($package, 'app.0.publish'),
];
if (is_array($package['app'])) {
foreach ($package['app'] as $item) {
$urls = $item['urls'] && is_array($item['urls']) ? $item['urls'] : $item['url'];
if (is_array($item['publish']) && Base::hostContrast($url, $urls)) {
$array['publish'] = $item['publish'];
}
}
return Redirect::to(Base::fillUrl('api/system/version'), 301);
}
/**
* 头像
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response
*/
public function avatar()
{
$segment = Request::segment(2);
if ($segment && preg_match('/.*?\.png$/i', $segment)) {
$name = substr($segment, 0, -4);
} else {
$name = Request::input('name', 'H');
}
return $array;
$size = Request::input('size', 128);
$color = Request::input('color');
$background = Request::input('background');
//
if (preg_match('/^[\x{4e00}-\x{9fa5}]+$/u', $name)) {
$name = mb_substr($name, mb_strlen($name) - 2);
}
if (empty($color)) {
$color = '#ffffff';
$cacheKey = "avatarBackgroundColor::" . md5($name);
$background = Cache::rememberForever($cacheKey, function() {
return RandomColor::one(['luminosity' => 'dark']);
});
}
//
$avatar = new InitialAvatar();
$content = $avatar->name($name)
->size($size)
->color($color)
->background($background)
->fontSize(0.35)
->autoFont()
->generate()
->stream('png', 100);
//
return response($content)
->header('Pragma', 'public')
->header('Cache-Control', 'max-age=1814400')
->header('Content-type', 'image/png')
->header('Expires', gmdate('D, d M Y H:i:s \G\M\T', time() + 1814400));
}
/**
@@ -140,13 +180,22 @@ class IndexController extends InvokeController
// 限制内网访问
return "Forbidden Access";
}
// 自动归档
Task::deliver(new AutoArchivedTask());
// 邮件通知
Task::deliver(new EmailNoticeTask());
// App推送
Task::deliver(new AppPushTask());
// 删除过期的临时表数据
Task::deliver(new DeleteTmpTask('wg_tmp_msgs', 1));
Task::deliver(new DeleteTmpTask('task_worker', 12));
Task::deliver(new DeleteTmpTask('tmp', 24));
// 自动归档任务
Task::deliver(new AutoArchivedTask());
// 任务到期邮件提醒
Task::deliver(new OverdueRemindEmailTask());
// 删除机器人消息
Task::deliver(new DeleteBotMsgTask());
// 周期任务
Task::deliver(new LoopTask());
// 获取笑话/心灵鸡汤
Task::deliver(new JokeSoupTask());
return "success";
}
@@ -156,11 +205,14 @@ class IndexController extends InvokeController
*/
public function desktop__publish($name = '')
{
$latestFile = public_path("uploads/desktop/latest");
$genericVersion = Request::header('generic-version');
$latestFile = public_path("uploads/desktop/latest");
$latestVersion = file_exists($latestFile) ? trim(file_get_contents($latestFile)) : "0.0.1";
if (strtolower($name) === 'latest') {
$name = $latestVersion;
}
// 上传
if (preg_match("/^\d+\.\d+\.\d+$/", $genericVersion)) {
$latestVersion = file_exists($latestFile) ? trim(file_get_contents($latestFile)) : "0.0.1";
if (version_compare($genericVersion, $latestVersion) > -1) { // 限制上传版本必须 ≥ 当前版本
$genericPath = "uploads/desktop/{$genericVersion}/";
$res = Base::upload([
@@ -182,7 +234,7 @@ class IndexController extends InvokeController
$lists = Base::readDir($dirPath);
$files = [];
foreach ($lists as $file) {
if (str_ends_with($file, '.yml')) {
if (str_ends_with($file, '.yml') || str_ends_with($file, '.yaml')) {
continue;
}
$fileName = Base::leftDelete($file, $dirPath);
@@ -193,6 +245,29 @@ class IndexController extends InvokeController
'url' => Base::fillUrl($path . $fileName),
];
}
//
$path = "uploads/android";
$dirPath = public_path($path);
$lists = Base::readDir($dirPath);
$apkFile = null;
foreach ($lists as $file) {
if (!str_ends_with($file, '.apk')) {
continue;
}
if ($apkFile && strtotime($apkFile['time']) > fileatime($file)) {
continue;
}
$fileName = Base::leftDelete($file, $dirPath);
$apkFile = [
'name' => substr($fileName, 1),
'time' => date("Y-m-d H:i:s", fileatime($file)),
'size' => Base::readableBytes(filesize($file)),
'url' => Base::fillUrl($path . $fileName),
];
}
if ($apkFile) {
$files = array_merge([$apkFile], $files);
}
return view('desktop', ['version' => $name, 'files' => $files]);
}
// 下载
@@ -201,13 +276,87 @@ class IndexController extends InvokeController
if (preg_match("/^\d+\.\d+\.\d+$/", $genericVersion)) {
$filePath = public_path("uploads/desktop/{$genericVersion}/{$name}");
if (file_exists($filePath)) {
return response()->download($filePath);
return Response::download($filePath);
}
}
}
return abort(404);
}
/**
* Drawio 图标搜索
* @return array|mixed
*/
public function drawio__iconsearch()
{
$query = Request::input('q');
$page = Request::input('p');
$size = Request::input('c');
$url = "https://app.diagrams.net/iconSearch?q={$query}&p={$page}&c={$size}";
$result = Cache::remember("drawioIconsearch::" . md5($url), now()->addDays(15), function () use ($url) {
return Ihttp::ihttp_get($url);
});
if (Base::isSuccess($result)) {
return $result['data'];
}
return [
'icons' => [],
'total_count' => 0
];
}
/**
* 预览文件
* @return array|mixed
*/
public function online__preview()
{
$key = trim(Request::input('key'));
//
$data = parse_url($key);
$path = Arr::get($data, 'path');
$file = public_path($path);
//
if (file_exists($file)) {
parse_str($data['query'], $query);
$name = Arr::get($query, 'name');
$ext = strtolower(Arr::get($query, 'ext'));
$userAgent = strtolower(Request::server('HTTP_USER_AGENT'));
if ($ext === 'pdf'
&& (str_contains($userAgent, 'electron') || str_contains($userAgent, 'chrome'))) {
return Response::download($file, $name, [
'Content-Type' => 'application/pdf'
], 'inline');
}
//
if (in_array($ext, File::localExt)) {
$url = Base::fillUrl($path);
} else {
$url = 'http://' . env('APP_IPPR') . '.3/' . $path;
}
if ($ext !== 'pdf') {
$url = Base::urlAddparameter($url, [
'fullfilename' => $name . '.' . $ext
]);
}
$toUrl = Base::fillUrl("fileview/onlinePreview?url=" . urlencode(base64_encode($url)));
return Redirect::to($toUrl, 301);
}
return abort(404);
}
/**
* 设置语言和皮肤
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
*/
public function setting__theme_language()
{
return view('setting', [
'theme' => Request::input('theme'),
'language' => Request::input('language')
]);
}
/**
* 提取所有中文
* @return array|string

View File

@@ -36,6 +36,9 @@ class VerifyCsrfToken extends Middleware
// 聊天发文本
'api/dialog/msg/sendtext/',
// 聊天发语音
'api/dialog/msg/sendrecord/',
// 聊天发文件
'api/dialog/msg/sendfile/',
@@ -51,6 +54,12 @@ class VerifyCsrfToken extends Middleware
// 保存汇报
'api/report/store/',
// 签到设置
'api/users/checkin/save/',
// 签到上报
'api/public/checkin/report/',
// 发布桌面端
'desktop/publish/',
];

231
app/Ldap/LdapUser.php Normal file
View File

@@ -0,0 +1,231 @@
<?php
namespace App\Ldap;
use App\Exceptions\ApiException;
use App\Models\User;
use App\Module\Base;
use LdapRecord\Configuration\ConfigurationException;
use LdapRecord\Container;
use LdapRecord\LdapRecordException;
use LdapRecord\Models\Model;
class LdapUser extends Model
{
protected static bool $init = false;
/**
* The object classes of the LDAP model.
*
* @var array
*/
public static $objectClasses = [
'inetOrgPerson',
'organizationalPerson',
'person',
'top',
'posixAccount',
];
/**
* @return mixed|null
*/
public function getPhoto()
{
return $this->jpegPhoto && is_array($this->jpegPhoto) ? $this->jpegPhoto[0] : null;
}
/**
* @return mixed|null
*/
public function getDisplayName()
{
$nickname = $this->displayName ?: $this->uid;
return is_array($nickname) ? $nickname[0] : $nickname;
}
/**
* @return LdapUser
*/
public static function static(): LdapUser
{
return new static;
}
/**
* 服务是否打开
* @return bool
*/
public static function isOpen(): bool
{
return Base::settingFind('thirdAccessSetting', 'ldap_open') === 'open';
}
/**
* 同步本地是否打开
* @return bool
*/
public static function isSyncLocal(): bool
{
return Base::settingFind('thirdAccessSetting', 'ldap_sync_local') === 'open';
}
/**
* 初始化配置
* @return void
*/
public static function initConfig()
{
if (self::$init) {
return;
}
self::$init = true;
//
$setting = Base::setting('thirdAccessSetting');
$connection = Container::getDefaultConnection();
try {
$connection->setConfiguration([
"hosts" => [$setting['ldap_host']],
"port" => intval($setting['ldap_port']),
"base_dn" => $setting['ldap_base_dn'],
"username" => $setting['ldap_user_dn'],
"password" => $setting['ldap_password'],
]);
} catch (ConfigurationException $e) {
throw new ApiException($e->getMessage());
}
}
/**
* 获取
* @param $username
* @param $password
* @return Model|null
*/
public static function userFirst($username, $password): ?Model
{
self::initConfig();
try {
return self::static()
->where([
'cn' => $username,
'userPassword' => $password
])->first();
} catch (\Exception) {
return null;
}
}
/**
* 登录
* @param $username
* @param $password
* @param User|null $user
* @return User|mixed|null
*/
public static function userLogin($username, $password, $user = null)
{
self::initConfig();
$row = self::userFirst($username, $password);
if (!$row) {
return null;
}
if (empty($user)) {
$user = User::reg($username, $password);
}
if ($user) {
$userimg = $row->getPhoto();
if ($userimg) {
$path = "uploads/user/ldap/";
Base::makeDir(public_path($path));
file_put_contents(public_path("{$path}{$user->userid}.jpeg"), $userimg);
$user->userimg = "{$path}{$user->userid}.jpeg";
}
$user->nickname = $row->getDisplayName();
$user->save();
}
return $user;
}
/**
* 同步
* @param User $user
* @param $password
* @return void
*/
public static function userSync(User $user, $password)
{
self::initConfig();
//
if (self::isSyncLocal()) {
$row = self::userFirst($user->email, $password);
if ($row) {
return;
}
try {
$userimg = public_path($user->getRawOriginal('userimg'));
if (file_exists($userimg)) {
$userimg = file_get_contents($userimg);
} else {
$userimg = '';
}
self::static()->create([
'cn' => $user->email,
'gidNumber' => 0,
'homeDirectory' => '/home/ldap/dootask/' . env("APP_NAME"),
'sn' => $user->email,
'uid' => $user->email,
'uidNumber' => $user->userid,
'userPassword' => $password,
'displayName' => $user->nickname,
'jpegPhoto' => $userimg,
]);
$user->identity = Base::arrayImplode(array_merge(array_diff($user->identity, ['ldap']), ['ldap']));
$user->save();
} catch (LdapRecordException $e) {
info("[LDAP] sync fail: " . $e->getMessage());
}
}
}
/**
* 更新
* @param $username
* @param $array
* @return void
*/
public static function userUpdate($username, $array)
{
if (empty($array)) {
return;
}
self::initConfig();
$row = self::static()
->where([
'cn' => $username,
])->first();
try {
$row?->update($array);
} catch (LdapRecordException $e) {
throw new ApiException("[LDAP] update fail: " . $e->getMessage());
}
}
/**
* 删除
* @param $username
* @return void
*/
public static function userDelete($username)
{
self::initConfig();
$row = self::static()
->where([
'cn' => $username,
])->first();
try {
$row?->delete();
} catch (LdapRecordException $e) {
throw new ApiException("[LDAP] delete fail: " . $e->getMessage());
}
}
}

View File

@@ -46,7 +46,7 @@ class AbstractModel extends Model
{
try {
return $this->save();
} catch (\Exception $e) {
} catch (\Throwable) {
return false;
}
}

View File

@@ -13,8 +13,8 @@ use Request;
* App\Models\File
*
* @property int $id
* @property string|null $pids 上级ID递归
* @property int|null $pid 上级ID
* @property string|null $pids 上级ID递归
* @property int|null $cid 复制ID
* @property string|null $name 名称
* @property string|null $type 类型
@@ -22,6 +22,7 @@ use Request;
* @property int|null $size 大小(B)
* @property int|null $userid 拥有者ID
* @property int|null $share 是否共享
* @property int|null $pshare 所属分享ID
* @property int|null $created_id 创建者
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
@@ -39,6 +40,7 @@ use Request;
* @method static \Illuminate\Database\Eloquent\Builder|File whereName($value)
* @method static \Illuminate\Database\Eloquent\Builder|File wherePid($value)
* @method static \Illuminate\Database\Eloquent\Builder|File wherePids($value)
* @method static \Illuminate\Database\Eloquent\Builder|File wherePshare($value)
* @method static \Illuminate\Database\Eloquent\Builder|File whereShare($value)
* @method static \Illuminate\Database\Eloquent\Builder|File whereSize($value)
* @method static \Illuminate\Database\Eloquent\Builder|File whereType($value)
@@ -63,7 +65,7 @@ class File extends AbstractModel
'inc', 'phtml', 'shtml', 'php3', 'php4', 'php5', 'phps', 'phpt', 'aw', 'ctp', 'module', 'ps1', 'py', 'r', 'rb', 'ru', 'gemspec', 'rake', 'guardfile', 'rakefile',
'gemfile', 'rs', 'sass', 'scss', 'sh', 'bash', 'bashrc', 'sql', 'sqlserver', 'swift', 'ts', 'typescript', 'str', 'vbs', 'vb', 'v', 'vh', 'sv', 'svh', 'xml',
'rdf', 'rss', 'wsdl', 'xslt', 'atom', 'mathml', 'mml', 'xul', 'xbl', 'xaml', 'yaml', 'yml',
'asp', 'properties', 'gitignore', 'log', 'bas', 'prg', 'python', 'ftl', 'aspx'
'asp', 'properties', 'gitignore', 'log', 'bas', 'prg', 'python', 'ftl', 'aspx', 'plist'
];
/**
@@ -160,6 +162,15 @@ class File extends AbstractModel
return false;
}
/**
* 目录内是否存在共享文件或文件夹
* @return bool
*/
public function isSubShare()
{
return $this->type == 'folder' && File::where("pids", "like", "%,{$this->id},%")->whereShare(1)->exists();
}
/**
* 设置/关闭 共享(同时遍历取消里面的共享)
* @param $share
@@ -174,6 +185,7 @@ class File extends AbstractModel
AbstractModel::transaction(function () use ($share) {
$this->share = $share;
$this->save();
File::where("pids", "like", "%,{$this->id},%")->update(['pshare' => $share ? $this->id : 0]);
if ($share === 0) {
FileUser::deleteFileAll($this->id, $this->userid);
}
@@ -189,16 +201,50 @@ class File extends AbstractModel
}
/**
* 保存前更新pids
* 处理重名
* @return void
*/
public function handleDuplicateName()
{
$builder = self::wherePid($this->pid)->whereUserid($this->userid)->whereExt($this->ext);
$exist = $builder->clone()->whereName($this->name)->exists();
if (!$exist) {
return; // 未重名,不需要处理
}
// 发现重名,自动重命名
$nextNum = 2;
if (preg_match("/(.*?)(\s+\(\d+\))*$/", $this->name)) {
$preName = preg_replace("/(.*?)(\s+\(\d+\))*$/", "$1", $this->name);
$nextNum = $builder->clone()->where("name", "LIKE", "{$preName}%")->count() + 1;
}
$newName = "{$this->name} ({$nextNum})";
if ($builder->clone()->whereName($newName)->exists()) {
$nextNum = rand(100, 9999);
$newName = "{$this->name} ({$nextNum})";
}
$this->name = $newName;
}
/**
* 保存前更新pids/pshare
* @return bool
*/
public function saveBeforePids()
public function saveBeforePP()
{
$pid = $this->pid;
$pshare = $this->share ? $this->id : 0;
$array = [];
while ($pid > 0) {
$array[] = $pid;
$pid = intval(self::whereId($pid)->value('pid'));
$file = self::select(['id', 'pid', 'share'])->find($pid);
if ($file) {
$pid = $file->pid;
if ($file->share) {
$pshare = $file->id;
}
} else {
$pid = 0;
}
}
$opids = $this->pids;
if ($array) {
@@ -207,6 +253,7 @@ class File extends AbstractModel
} else {
$this->pids = '';
}
$this->pshare = $pshare;
if (!$this->save()) {
return false;
}
@@ -215,7 +262,7 @@ class File extends AbstractModel
self::wherePid($this->id)->chunkById(100, function ($lists) {
/** @var self $item */
foreach ($lists as $item) {
$item->saveBeforePids();
$item->saveBeforePP();
}
});
}
@@ -243,6 +290,29 @@ class File extends AbstractModel
return true;
}
/**
* 获取文件分享链接
* @param $userid
* @param $refresh
* @return array
*/
public function getShareLink($userid, $refresh = false)
{
if ($this->type == 'folder') {
throw new ApiException('文件夹不支持分享');
}
return FileLink::generateLink($this->id, $userid, $refresh);
}
/**
* 获取文件名称加后缀
* @return string|null
*/
public function getNameAndExt()
{
return $this->ext ? "{$this->name}.{$this->ext}" : $this->name;
}
/**
* 推送消息
* @param $action
@@ -257,19 +327,7 @@ class File extends AbstractModel
];
}
//
if ($userid === null) {
$userid = [$this->userid];
$builder = WebSocket::select(['userid']);
if ($action == 'content') {
$builder->wherePath("/single/file/{$this->id}");
} elseif ($this->pid > 0) {
$builder->wherePath("/manage/file/{$this->pid}");
} else {
$builder->wherePath("/manage/file");
}
$userid = array_merge($userid, $builder->pluck('userid')->toArray());
$userid = array_values(array_filter(array_unique($userid)));
}
$userid = $this->pushUserid($action, $userid);
if (empty($userid)) {
return;
}
@@ -293,18 +351,62 @@ class File extends AbstractModel
}
/**
* 处理返回图片地址
* @param $item
* @return void
* 获取推送会员
* @param $action
* @param $userid
* @return array|int[]|mixed|null[]
*/
public static function handleImageUrl(&$item)
public function pushUserid($action, $userid = null) {
$wherePath = "/manage/file";
if ($userid === null) {
$array = [$this->userid];
if ($action == 'add' && $this->pid == 0) {
return $array;
}
if ($action == 'content') {
$wherePath = "/single/file/{$this->id}";
} elseif ($this->pid > 0) {
$wherePath = "/manage/file/{$this->pid}";
} else {
$tmpArray = FileUser::whereFileId($this->id)->pluck('userid')->toArray();
if (empty($tmpArray)) {
return $array;
}
if (!in_array(0, $tmpArray)) {
return $tmpArray;
}
}
$tmpArray = WebSocket::wherePath($wherePath)->pluck('userid')->toArray();
if (empty($tmpArray)) {
return $array;
}
$array = array_values(array_filter(array_unique(array_merge($array, $tmpArray))));
} else {
$array = is_array($userid) ? $userid : [$userid];
if (in_array(0, $array)) {
return WebSocket::wherePath($wherePath)->pluck('userid')->toArray();
}
}
return $array;
}
/**
* 处理返回图片地址
* @param array $item
* @return array
*/
public static function handleImageUrl($item)
{
if (in_array($item['ext'], self::imageExt) ) {
$content = Base::json2array(FileContent::whereFid($item['id'])->orderByDesc('id')->value('content'));
if ($content) {
$item['image_url'] = Base::fillUrl($content['url']);
$item['image_width'] = intval($content['width']);
$item['image_height'] = intval($content['height']);
}
}
return $item;
}
/**
@@ -340,6 +442,7 @@ class File extends AbstractModel
*/
public static function formatFileData(array $data)
{
$fileName = $data['name'];
$filePath = $data['path'];
$fileSize = $data['size'];
$fileExt = $data['ext'];
@@ -374,33 +477,28 @@ class File extends AbstractModel
if (in_array($fileExt, self::codeExt) && $fileSize < 2 * 1024 * 1024)
{
// 文本预览限制2M内的文件
$data['content'] = file_get_contents($publicPath) ?: 'Content deleted';
$data['content'] = [
'content' => file_get_contents($publicPath) ?: 'Content deleted',
];
$data['file_mode'] = 'code';
}
elseif (in_array($fileExt, File::officeExt))
{
// office预览
$data['content'] = '';
$data['content'] = json_decode('{}');
$data['file_mode'] = 'office';
}
else
{
// 其他预览
if (in_array($fileExt, File::localExt)) {
$url = Base::fillUrl($filePath);
} else {
$url = 'http://' . env('APP_IPPR') . '.3/' . $filePath;
}
if ($fileExt != 'pdf') {
$fileDotExt = ".{$fileExt}";
$fileName = Base::rightDelete($data['name'], $fileDotExt) . $fileDotExt;
$url = Base::urlAddparameter($url, [
'fullfilename' => $fileName
]);
}
$name = Base::rightDelete($fileName, ".{$fileExt}") . ".{$fileExt}";
$data['content'] = [
'preview' => true,
'url' => base64_encode($url),
'name' => $name,
'key' => urlencode(Base::urlAddparameter($filePath, [
'name' => $name,
'ext' => $fileExt
])),
];
$data['file_mode'] = 'preview';
}
@@ -431,7 +529,8 @@ class File extends AbstractModel
'userid' => $newUserid,
'created_id' => 0,
]);
$file->saveBeforePids();
$file->handleDuplicateName();
$file->saveBeforePP();
// 移交文件
self::whereUserid($originalUserid)->chunkById(100, function($list) use ($file, $newUserid) {
@@ -441,7 +540,7 @@ class File extends AbstractModel
$item->pid = $file->id;
}
$item->userid = $newUserid;
$item->saveBeforePids();
$item->saveBeforePP();
}
});

View File

@@ -40,12 +40,52 @@ class FileContent extends AbstractModel
{
use SoftDeletes;
/**
* 转预览地址
* @param array $array
* @return string
*/
public static function toPreviewUrl($array)
{
$fileExt = $array['ext'];
$fileName = $array['name'];
$filePath = $array['path'];
$name = Base::rightDelete($fileName, ".{$fileExt}") . ".{$fileExt}";
$key = urlencode(Base::urlAddparameter($filePath, [
'name' => $name,
'ext' => $fileExt
]));
return Base::fillUrl("online/preview/{$name}?key={$key}");
}
/**
* 转预览地址
* @param File $file
* @param $content
* @return string
*/
public static function formatPreview($file, $content)
{
$content = Base::json2array($content ?: []);
$filePath = $content['url'];
if (in_array($file->type, ['word', 'excel', 'ppt'])) {
if (empty($content)) {
$filePath = 'assets/office/empty.' . str_replace(['word', 'excel', 'ppt'], ['docx', 'xlsx', 'pptx'], $file->type);
}
}
return self::toPreviewUrl([
'ext' => $file->ext,
'name' => $file->name,
'path' => $filePath,
]);
}
/**
* 获取格式内容(或下载)
* @param File $file
* @param $content
* @param $download
* @return array|\Symfony\Component\HttpFoundation\BinaryFileResponse
* @return array|\Symfony\Component\HttpFoundation\StreamedResponse
*/
public static function formatContent($file, $content, $download = false)
{
@@ -53,9 +93,13 @@ class FileContent extends AbstractModel
$content = Base::json2array($content ?: []);
if (in_array($file->type, ['word', 'excel', 'ppt'])) {
if (empty($content)) {
return Response::download(resource_path('assets/statics/office/empty.' . str_replace(['word', 'excel', 'ppt'], ['docx', 'xlsx', 'pptx'], $file->type)), $name);
$filePath = public_path('assets/office/empty.' . str_replace(['word', 'excel', 'ppt'], ['docx', 'xlsx', 'pptx'], $file->type));
} else {
$filePath = public_path($content['url']);
}
return Response::download(public_path($content['url']), $name);
return Response::streamDownload(function() use ($filePath) {
echo file_get_contents($filePath);
}, $name);
}
if (empty($content)) {
$content = match ($file->type) {
@@ -84,7 +128,9 @@ class FileContent extends AbstractModel
if ($download) {
$filePath = public_path($path);
if (isset($filePath)) {
return Response::download($filePath, $name);
return Response::streamDownload(function() use ($filePath) {
echo file_get_contents($filePath);
}, $name);
} else {
abort(403, "This file not support download.");
}

View File

@@ -2,6 +2,8 @@
namespace App\Models;
use App\Module\Base;
/**
* App\Models\FileLink
*
@@ -34,4 +36,35 @@ class FileLink extends AbstractModel
{
return $this->hasOne(File::class, 'id', 'file_id');
}
/**
* 生成链接
* @param $fileId
* @param $userid
* @param $refresh
* @return array
*/
public static function generateLink($fileId, $userid, $refresh = false)
{
$fileLink = FileLink::whereFileId($fileId)->whereUserid($userid)->first();
if (empty($fileLink)) {
$fileLink = FileLink::createInstance([
'file_id' => $fileId,
'userid' => $userid,
'code' => Base::generatePassword(64),
]);
$fileLink->save();
} else {
if ($refresh == 'yes') {
$fileLink->code = Base::generatePassword(64);
$fileLink->save();
}
}
return [
'id' => $fileId,
'url' => Base::fillUrl('single/file/' . $fileLink->code),
'code' => $fileLink->code,
'num' => $fileLink->num
];
}
}

34
app/Models/Meeting.php Normal file
View File

@@ -0,0 +1,34 @@
<?php
namespace App\Models;
/**
* App\Models\Meeting
*
* @property int $id
* @property string|null $meetingid 会议ID不是数字
* @property string|null $name 会议主题
* @property string|null $channel 频道
* @property int|null $userid 创建人
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property string|null $end_at
* @property \Illuminate\Support\Carbon|null $deleted_at
* @method static \Illuminate\Database\Eloquent\Builder|Meeting newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|Meeting newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|Meeting query()
* @method static \Illuminate\Database\Eloquent\Builder|Meeting whereChannel($value)
* @method static \Illuminate\Database\Eloquent\Builder|Meeting whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|Meeting whereDeletedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|Meeting whereEndAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|Meeting whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|Meeting whereMeetingid($value)
* @method static \Illuminate\Database\Eloquent\Builder|Meeting whereName($value)
* @method static \Illuminate\Database\Eloquent\Builder|Meeting whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|Meeting whereUserid($value)
* @mixin \Eloquent
*/
class Meeting extends AbstractModel
{
}

View File

@@ -5,6 +5,7 @@ namespace App\Models;
use App\Exceptions\ApiException;
use App\Module\Base;
use App\Tasks\PushTask;
use Arr;
use Carbon\Carbon;
use DB;
use Hhxsv5\LaravelS\Swoole\Task\Task;
@@ -18,6 +19,8 @@ use Request;
* @property string|null $name 名称
* @property string|null $desc 描述、备注
* @property int|null $userid 创建人
* @property int|null $personal 是否个人项目
* @property string|null $user_simple 成员总数|1,2,3
* @property int|null $dialog_id 聊天会话ID
* @property string|null $archived_at 归档时间
* @property int|null $archived_userid 归档会员
@@ -45,7 +48,9 @@ use Request;
* @method static \Illuminate\Database\Eloquent\Builder|Project whereDialogId($value)
* @method static \Illuminate\Database\Eloquent\Builder|Project whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|Project whereName($value)
* @method static \Illuminate\Database\Eloquent\Builder|Project wherePersonal($value)
* @method static \Illuminate\Database\Eloquent\Builder|Project whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|Project whereUserSimple($value)
* @method static \Illuminate\Database\Eloquent\Builder|Project whereUserid($value)
* @method static \Illuminate\Database\Query\Builder|Project withTrashed()
* @method static \Illuminate\Database\Query\Builder|Project withoutTrashed()
@@ -202,9 +207,11 @@ class Project extends AbstractModel
WebSocketDialogUser::updateInsert([
'dialog_id' => $this->dialog_id,
'userid' => $userid,
], [
'important' => 1
]);
}
WebSocketDialogUser::whereDialogId($this->dialog_id)->whereNotIn('userid', $userids)->delete();
WebSocketDialogUser::whereDialogId($this->dialog_id)->whereNotIn('userid', $userids)->whereImportant(1)->delete();
});
}
@@ -274,6 +281,7 @@ class Project extends AbstractModel
AbstractModel::transaction(function () {
$dialog = WebSocketDialog::find($this->dialog_id);
$dialog?->deleteDialog();
$dialog?->pushMsg("groupDelete");
$columns = ProjectColumn::whereProjectId($this->id)->get();
foreach ($columns as $column) {
$column->deleteColumn(false);
@@ -461,6 +469,93 @@ class Project extends AbstractModel
});
}
/**
* 创建项目
* @param $params
* - name 项目名称
* - desc
* - flow
* - personal
* - columns
* @return array
*/
public static function createProject($params, $userid)
{
$name = trim(Arr::get($params, 'name', ''));
$desc = trim(Arr::get($params, 'desc', ''));
$flow = trim(Arr::get($params, 'flow', 'close'));
$isPersonal = intval(Arr::get($params, 'personal'));
if (mb_strlen($name) < 2) {
return Base::retError('项目名称不可以少于2个字');
} elseif (mb_strlen($name) > 32) {
return Base::retError('项目名称最多只能设置32个字');
}
if (mb_strlen($desc) > 255) {
return Base::retError('项目介绍最多只能设置255个字');
}
// 列表
$columns = explode(",", Arr::get($params, 'columns'));
$insertColumns = [];
$sort = 0;
foreach ($columns AS $column) {
$column = trim($column);
if ($column) {
$insertColumns[] = [
'name' => $column,
'sort' => $sort++,
];
}
}
if (empty($insertColumns)) {
$insertColumns[] = [
'name' => 'Default',
'sort' => 0,
];
}
if (count($insertColumns) > 30) {
return Base::retError('项目列表最多不能超过30个');
}
// 开始创建
$project = Project::createInstance([
'name' => $name,
'desc' => $desc,
'userid' => $userid,
]);
if ($isPersonal) {
if (Project::whereUserid($userid)->wherePersonal(1)->exists()) {
return Base::retError('个人项目已存在,无须重复创建');
}
$project->personal = 1;
}
AbstractModel::transaction(function() use ($flow, $insertColumns, $project) {
$project->save();
ProjectUser::createInstance([
'project_id' => $project->id,
'userid' => $project->userid,
'owner' => 1,
])->save();
foreach ($insertColumns AS $column) {
$column['project_id'] = $project->id;
ProjectColumn::createInstance($column)->save();
}
$dialog = WebSocketDialog::createGroup($project->name, $project->userid, 'project');
if (empty($dialog)) {
throw new ApiException('创建项目聊天室失败');
}
$project->dialog_id = $dialog->id;
$project->save();
//
if ($flow == 'open') {
$project->addFlow(Base::json2array('[{"id":-10,"name":"待处理","status":"start","turns":[-10,-11,-12,-13,-14],"userids":[],"usertype":"add","userlimit":0},{"id":-11,"name":"进行中","status":"progress","turns":[-10,-11,-12,-13,-14],"userids":[],"usertype":"add","userlimit":0},{"id":-12,"name":"待测试","status":"test","turns":[-10,-11,-12,-13,-14],"userids":[],"usertype":"add","userlimit":0},{"id":-13,"name":"已完成","status":"end","turns":[-10,-11,-12,-13,-14],"userids":[],"usertype":"add","userlimit":0},{"id":-14,"name":"已取消","status":"end","turns":[-10,-11,-12,-13,-14],"userids":[],"usertype":"add","userlimit":0}]'));
}
});
//
$data = Project::find($project->id);
$data->addLog("创建项目");
$data->pushMsg('add', $data);
return Base::retSuccess('添加成功', $data);
}
/**
* 获取项目信息(用于判断会员是否存在项目内)
* @param int $project_id

View File

@@ -7,12 +7,9 @@ use App\Module\Base;
use App\Tasks\PushTask;
use Arr;
use Carbon\Carbon;
use Config;
use DB;
use Exception;
use Hhxsv5\LaravelS\Swoole\Task\Task;
use Illuminate\Database\Eloquent\SoftDeletes;
use Mail;
use Request;
/**
@@ -39,6 +36,8 @@ use Request;
* @property string|null $p_name 优先级名称
* @property string|null $p_color 优先级颜色
* @property int|null $sort 排序(ASC)
* @property string|null $loop 重复周期
* @property string|null $loop_at 下一次重复时间
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property \Illuminate\Support\Carbon|null $deleted_at
@@ -81,6 +80,8 @@ use Request;
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereFlowItemId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereFlowItemName($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereLoop($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereLoopAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereName($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask wherePColor($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask wherePLevel($value)
@@ -303,6 +304,7 @@ class ProjectTask extends AbstractModel
'project_tasks.*',
'project_task_users.owner'
])
->selectRaw("1 AS assist")
->join('project_task_users', 'project_tasks.id', '=', 'project_task_users.task_id')
->where('project_task_users.userid', $userid);
if ($owner !== null) {
@@ -529,7 +531,7 @@ class ProjectTask extends AbstractModel
{
AbstractModel::transaction(function () use ($data, &$updateMarking) {
// 判断版本
Base::checkClientVersion('0.6.0');
Base::checkClientVersion('0.19.0');
// 主任务
$mainTask = $this->parent_id > 0 ? self::find($this->parent_id) : null;
// 工作流
@@ -647,10 +649,13 @@ class ProjectTask extends AbstractModel
'change' => [$this->name, $data['name']]
]);
$this->name = $data['name'];
if ($this->dialog_id) {
WebSocketDialog::updateData(['id' => $this->dialog_id], ['name' => $this->name]);
}
}
// 负责人
if (Arr::exists($data, 'owner')) {
$count = $this->taskUser->where('owner', 1)->count();
$older = $this->taskUser->where('owner', 1)->pluck('userid')->toArray();
$array = [];
$owner = is_array($data['owner']) ? $data['owner'] : [$data['owner']];
if (count($owner) > 10) {
@@ -674,15 +679,16 @@ class ProjectTask extends AbstractModel
}
}
if ($array) {
if ($count == 0 && count($array) == 1 && $array[0] == User::userid()) {
if (count($older) == 0 && count($array) == 1 && $array[0] == User::userid()) {
$this->addLog("认领{任务}");
} else {
$this->addLog("修改{任务}负责人", ['userid' => $array]);
}
$this->taskPush(array_values(array_diff($array, $older)), 0);
}
$rows = ProjectTaskUser::whereTaskId($this->id)->whereOwner(1)->whereNotIn('userid', $array)->get();
if ($rows->isNotEmpty()) {
$this->addLog("删除{任务}负责人", ['userid' => $rows->implode('userid', ',')]);
$this->addLog("删除{任务}负责人", ['userid' => $rows->pluck('userid')]);
foreach ($rows as $row) {
$row->delete();
}
@@ -697,7 +703,7 @@ class ProjectTask extends AbstractModel
$this->start_at = null;
$this->end_at = null;
$times = $data['times'];
list($start, $end) = is_string($times) ? explode(",", $times) : (is_array($times) ? $times : []);
list($start, $end, $desc) = is_string($times) ? explode(",", $times) : (is_array($times) ? $times : []);
if (Base::isDate($start) && Base::isDate($end) && $start != $end) {
$start_at = Carbon::parse($start);
$end_at = Carbon::parse($end);
@@ -758,15 +764,37 @@ class ProjectTask extends AbstractModel
});
}
$newStringAt = $this->start_at ? ($this->start_at->toDateTimeString() . '~' . $this->end_at->toDateTimeString()) : '';
$this->addLog("修改{任务}时间", [
$this->addLog("修改{任务}时间" . ($desc ? "(备注:{$desc}" : ""), [
'change' => [$oldStringAt, $newStringAt]
]);
//修改计划时间需要重置任务邮件提醒日志
ProjectTaskMailLog::whereTaskId($this->id)->delete();
$this->taskPush(null, 3);
}
// 以下顶级任务可修改
// 以下顶级任务可修改
if ($this->parent_id === 0) {
// 重复周期
$loopAt = $this->loop_at;
$loopDesc = $this->loopDesc();
if (Arr::exists($data, 'loop')) {
$this->loop = $data['loop'];
if (!$this->refreshLoop()) {
throw new ApiException('重复周期选择错误');
}
} elseif (Arr::exists($data, 'times')) {
// 更新任务时间也要更新重复周期
$this->refreshLoop();
}
$oldLoop = $loopAt ? Carbon::parse($loopAt)->toDateTimeString() : null;
$newLoop = $this->loop_at ? Carbon::parse($this->loop_at)->toDateTimeString() : null;
if ($oldLoop != $newLoop) {
$this->addLog("修改{任务}下个周期", [
'change' => [$oldLoop, $newLoop]
]);
}
if ($loopDesc != $this->loopDesc()) {
$this->addLog("修改{任务}重复周期", [
'change' => [$loopDesc, $this->loopDesc()]
]);
}
// 协助人员
if (Arr::exists($data, 'assist')) {
$array = [];
@@ -793,7 +821,7 @@ class ProjectTask extends AbstractModel
}
$rows = ProjectTaskUser::whereTaskId($this->id)->whereOwner(0)->whereNotIn('userid', $array)->get();
if ($rows->isNotEmpty()) {
$this->addLog("删除{任务}协助人员", ['userid' => $rows->implode('userid', ',')]);
$this->addLog("删除{任务}协助人员", ['userid' => $rows->pluck('userid')]);
foreach ($rows as $row) {
$row->delete();
}
@@ -860,6 +888,141 @@ class ProjectTask extends AbstractModel
return true;
}
/**
* 刷新重复周期时间
* @param bool $save 是否执行保存
* @return bool
*/
public function refreshLoop($save = false)
{
$success = true;
if ($this->start_at) {
$base = Carbon::parse($this->start_at);
if ($base->lt(Carbon::today())) {
// 如果任务开始时间小于今天则基数时间为今天
$base = Carbon::parse(date("Y-m-d {$base->toTimeString()}"));
}
} else {
// 未设置任务时间时基数时间为今天
$base = Carbon::today();
}
switch ($this->loop) {
case "day":
$this->loop_at = $base->addDay();
break;
case "weekdays":
$this->loop_at = $base->addWeekday();
break;
case "week":
$this->loop_at = $base->addWeek();
break;
case "twoweeks":
$this->loop_at = $base->addWeeks(2);
break;
case "month":
$this->loop_at = $base->addMonth();
break;
case "year":
$this->loop_at = $base->addYear();
break;
case "never":
$this->loop_at = null;
break;
default:
if (Base::isNumber($this->loop)) {
$this->loop_at = $base->addDays($this->loop);
} else {
$this->loop_at = null;
$success = false;
}
break;
}
if ($success && $save) {
$this->save();
}
return $success;
}
/**
* 获取周期描述
* @return string
*/
public function loopDesc() {
$loopDesc = "从不";
switch ($this->loop) {
case "day":
$loopDesc = "每天";
break;
case "weekdays":
$loopDesc = "每个工作日";
break;
case "week":
$loopDesc = "每周";
break;
case "twoweeks":
$loopDesc = "每两周";
break;
case "month":
$loopDesc = "每月";
break;
case "year":
$loopDesc = "每年";
break;
default:
if (Base::isNumber($this->loop)) {
$loopDesc = "{$this->loop}";
}
break;
}
return $loopDesc;
}
/**
* 复制任务
* @return self
*/
public function copyTask()
{
if ($this->parent_id > 0) {
throw new ApiException('子任务禁止复制');
}
return AbstractModel::transaction(function() {
// 复制任务
$task = $this->replicate();
$task->dialog_id = 0;
$task->archived_at = null;
$task->archived_userid = 0;
$task->archived_follow = 0;
$task->complete_at = null;
$task->created_at = Carbon::now();
$task->save();
// 复制任务内容
if ($this->content) {
$tmp = $this->content->replicate();
$tmp->task_id = $task->id;
$tmp->created_at = Carbon::now();
$tmp->save();
}
// 复制任务附件
foreach ($this->taskFile as $taskFile) {
$tmp = $taskFile->replicate();
$tmp->task_id = $task->id;
$tmp->created_at = Carbon::now();
$tmp->save();
}
// 复制任务成员
foreach ($this->taskUser as $taskUser) {
$tmp = $taskUser->replicate();
$tmp->task_id = $task->id;
$tmp->task_pid = $task->id;
$tmp->created_at = Carbon::now();
$tmp->save();
}
//
return $task;
});
}
/**
* 同步项目成员至聊天室
*/
@@ -879,9 +1042,11 @@ class ProjectTask extends AbstractModel
WebSocketDialogUser::updateInsert([
'dialog_id' => $this->dialog_id,
'userid' => $userid,
], [
'important' => 1
]);
}
WebSocketDialogUser::whereDialogId($this->dialog_id)->whereNotIn('userid', $userids)->delete();
WebSocketDialogUser::whereDialogId($this->dialog_id)->whereNotIn('userid', $userids)->whereImportant(1)->delete();
});
}
@@ -929,11 +1094,18 @@ class ProjectTask extends AbstractModel
/**
* 权限版本
* @param int $level 1-负责人2-协助人/负责人3-创建人/协助人/负责人
* @param int $level
* 1负责人
* 2协助人/负责人
* 3创建人/协助人/负责人
* 4任务群聊成员/3
* @return bool
*/
public function permission($level = 1)
{
if ($level >= 4) {
return $this->permission(3) || $this->existDialogUser();
}
if ($level >= 3 && $this->isCreater()) {
return true;
}
@@ -943,6 +1115,15 @@ class ProjectTask extends AbstractModel
return $this->isOwner();
}
/**
* 判断是否在任务对话里
* @return bool
*/
public function existDialogUser()
{
return $this->dialog_id && WebSocketDialogUser::whereDialogId($this->dialog_id)->whereUserid(User::userid())->exists();
}
/**
* 判断是否创建者
* @return bool
@@ -1003,10 +1184,16 @@ class ProjectTask extends AbstractModel
public function completeTask($complete_at)
{
AbstractModel::transaction(function () use ($complete_at) {
$addMsg = $this->parent_id == 0 && $this->dialog_id > 0;
if ($complete_at === null) {
// 标记未完成
$this->complete_at = null;
$this->addLog("标记{任务}未完成");
if ($addMsg) {
WebSocketDialogMsg::sendMsg(null, $this->dialog_id, 'notice', [
'notice' => '标记任务未完成'
], 0, true, true);
}
} else {
// 标记已完成
if ($this->parent_id == 0) {
@@ -1019,6 +1206,11 @@ class ProjectTask extends AbstractModel
}
$this->complete_at = $complete_at;
$this->addLog("标记{任务}已完成");
if ($addMsg) {
WebSocketDialogMsg::sendMsg(null, $this->dialog_id, 'notice', [
'notice' => '标记任务已完成'
], 0, true, true);
}
}
$this->save();
});
@@ -1089,6 +1281,7 @@ class ProjectTask extends AbstractModel
if ($this->dialog_id) {
$dialog = WebSocketDialog::find($this->dialog_id);
$dialog?->deleteDialog();
$dialog?->pushMsg("groupDelete");
}
self::whereParentId($this->id)->delete();
$this->deleted_userid = User::userid();
@@ -1175,28 +1368,62 @@ class ProjectTask extends AbstractModel
$data = $data->toArray();
}
//
$array = [$userid, []];
$userids = [];
if ($userid === null) {
$array[0] = $this->project->relationUserids();
$userids = $this->project->relationUserids();
} elseif (!is_array($userid)) {
$array[0] = [$userid];
$userids = [$userid];
}
//
if (isset($data['owner'])) {
$owners = ProjectTaskUser::whereTaskId($data['id'])->whereOwner(1)->pluck('userid')->toArray();
$array = [array_intersect($array[0], $owners), array_diff($array[0], $owners)];
}
foreach ($array as $index => $item) {
if ($index > 0) {
$data['owner'] = 0;
$array = [];
if (empty($data['parent_id'])) {
if (Arr::exists($data, 'owner') || Arr::exists($data, 'assist')) {
$taskUser = ProjectTaskUser::select(['userid', 'owner'])->whereTaskId($data['id'])->get();
// 负责人
$owners = $taskUser->where('owner', 1)->pluck('userid')->toArray();
$owners = array_intersect($userids, $owners);
if ($owners) {
$array[] = [
'userid' => array_values($owners),
'data' => array_merge($data, [
'owner' => 1,
'assist' => 1,
])
];
}
// 协助人
$assists = $taskUser->pluck('userid')->toArray();
$assists = array_intersect($userids, array_diff($assists, $owners));
if ($assists) {
$array[] = [
'userid' => array_values($assists),
'data' => array_merge($data, [
'owner' => 0,
'assist' => 1,
])
];
}
// 项目成员(其他人)
$userids = array_diff($userids, $owners, $assists);
$data = array_merge($data, [
'owner' => 0,
'assist' => 0,
]);
}
}
$array[] = [
'userid' => array_values($userids),
'data' => $data
];
//
foreach ($array as $item) {
$params = [
'ignoreFd' => Request::header('fd'),
'userid' => array_values($item),
'msg' => [
'type' => 'projectTask',
'action' => $action,
'data' => $data,
'data' => $item['data'],
]
];
$task = new PushTask($params, false);
@@ -1204,6 +1431,62 @@ class ProjectTask extends AbstractModel
}
}
/**
* 任务提醒
* @param $userids
* @param int $type 0-新任务、1-即将超时、2-已超时、3-修改时间
* @return void
*/
public function taskPush($userids, int $type)
{
if ($userids === null) {
$userids = $this->taskUser->where('owner', 1)->pluck('userid')->toArray();
}
if (empty($userids)) {
return;
}
$owners = $this->taskUser->pluck('owner', 'userid')->toArray();
$users = User::whereIn('userid', $userids)->whereNull('disable_at')->get();
if (empty($users)) {
return;
}
$botUser = User::botGetOrCreate('task-alert');
if (empty($botUser)) {
return;
}
$taskHtml = "<span class=\"mention task\" data-id=\"{$this->id}\">#{$this->name}</span>";
$text = match ($type) {
1 => "您的任务 {$taskHtml} 即将超时。",
2 => "您的任务 {$taskHtml} 已经超时。",
3 => "您的任务 {$taskHtml} 时间已修改。",
default => "您有一个新任务 {$taskHtml}",
};
/** @var User $user */
foreach ($users as $user) {
$data = [
'type' => $type,
'userid' => $user->userid,
'task_id' => $this->id,
];
$pushLog = ProjectTaskPushLog::where($data)->exists();
if ($pushLog) {
continue;
}
//
$replace = $owners[$user->userid] ? "您负责的任务" : "您协助的任务";
$dialog = WebSocketDialog::checkUserDialog($botUser->userid, $data['userid']);
if ($dialog) {
ProjectTaskPushLog::createInstance($data)->save();
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'text', [
'text' => str_replace("您的任务", $replace, $text)
], $botUser->userid);
}
}
}
/**
* 获取任务
* @param $task_id
@@ -1211,7 +1494,15 @@ class ProjectTask extends AbstractModel
*/
public static function oneTask($task_id)
{
return self::with(['taskUser', 'taskTag'])->allData()->where("project_tasks.id", intval($task_id))->first();
$data = self::with(['taskUser', 'taskTag'])->allData()->where("project_tasks.id", intval($task_id))->first();
if ($data && $data->parent_id === 0) {
if ($data->owner || ProjectTaskUser::select(['owner'])->whereTaskId($data->id)->whereUserid(User::userid())->exists()) {
$data->assist = 1;
} else {
$data->assist = 0;
}
}
return $data;
}
/**
@@ -1219,7 +1510,10 @@ class ProjectTask extends AbstractModel
* @param int $task_id
* @param bool $archived true:仅限未归档, false:仅限已归档, null:不限制
* @param bool $trashed true:仅限未删除, false:仅限已删除, null:不限制
* @param int|bool $permission 0|false:不限制, 1|true:限制项目负责人、任务负责人、协助人员及任务创建者, 2:已有负责人才限制true (子任务时如果是主任务负责人也可以)
* @param int|bool $permission
* - 0|false 限制:项目成员、任务成员、任务群聊成员(任务成员 = 任务创建人+任务协助人+任务负责人)
* - 1|true 限制:项目负责人、任务成员
* - 2 已有负责人才限制true (子任务时如果是主任务负责人也可以)
* @param array $with
* @return self
*/
@@ -1245,20 +1539,21 @@ class ProjectTask extends AbstractModel
//
try {
$project = Project::userProject($task->project_id);
} catch (Exception $e) {
if ($task->owner === null) {
} catch (\Throwable $e) {
if ($task->owner !== null || (!$permission && $task->permission(4))) {
$project = Project::find($task->project_id);
if (empty($project)) {
throw new ApiException('项目不存在或已被删除', [ 'task_id' => $task_id ], -4002);
}
} else {
throw new ApiException($e->getMessage(), [ 'task_id' => $task_id ], -4002);
}
$project = Project::find($task->project_id);
if (empty($project)) {
throw new ApiException('项目不存在或已被删除', [ 'task_id' => $task_id ], -4002);
}
}
//
if ($permission === 2) {
if ($permission >= 2) {
$permission = $task->hasOwner() ? 1 : 0;
}
if (($permission === 1 || $permission === true) && !$project->owner && !$task->permission(3)) {
if ($permission && !$project->owner && !$task->permission(3)) {
throw new ApiException('仅限项目负责人、任务负责人、协助人员或任务创建者操作');
}
//

View File

@@ -61,13 +61,14 @@ class ProjectTaskContent extends AbstractModel
{
$path = 'uploads/task/content/' . date("Ym") . '/' . $task_id . '/';
//
preg_match_all("/<img\s*src=\"data:image\/(png|jpg|jpeg);base64,(.*?)\"/s", $content, $matchs);
preg_match_all("/<img\s+src=\"data:image\/(png|jpg|jpeg);base64,(.*?)\"/s", $content, $matchs);
foreach ($matchs[2] as $key => $text) {
$tmpPath = $path . 'attached/';
Base::makeDir(public_path($tmpPath));
$tmpPath .= md5($text) . "." . $matchs[1][$key];
if (file_put_contents(public_path($tmpPath), base64_decode($text))) {
$content = str_replace($matchs[0][$key], '<img src="{{RemoteURL}}' . $tmpPath . '"', $content);
$paramet = getimagesize(public_path($tmpPath));
$content = str_replace($matchs[0][$key], '<img src="{{RemoteURL}}' . $tmpPath . '" original-width="' . $paramet[0] . '" original-height="' . $paramet[1] . '"', $content);
}
}
$pattern = '/<img(.*?)src=("|\')https*:\/\/(.*?)\/(uploads\/task\/content\/(.*?))\2/is';

View File

@@ -3,6 +3,7 @@
namespace App\Models;
use App\Module\Base;
use Cache;
/**
* App\Models\ProjectTaskFile
@@ -19,6 +20,8 @@ use App\Module\Base;
* @property int|null $download 下载次数
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read int $height
* @property-read int $width
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFile newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFile newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFile query()
@@ -38,6 +41,11 @@ use App\Module\Base;
*/
class ProjectTaskFile extends AbstractModel
{
protected $appends = [
'width',
'height',
];
/**
* 地址
* @param $value
@@ -57,4 +65,50 @@ class ProjectTaskFile extends AbstractModel
{
return Base::fillUrl($value ?: Base::extIcon($this->ext));
}
/**
* 宽
* @return int
*/
public function getWidthAttribute()
{
$this->generateSizeData();
return $this->appendattrs['width'];
}
/**
* 高
* @return int
*/
public function getHeightAttribute()
{
$this->generateSizeData();
return $this->appendattrs['height'];
}
/**
* 生成尺寸数据
*/
private function generateSizeData()
{
if (!isset($this->appendattrs['width'])) {
$width = -1;
$height = -1;
if (in_array($this->ext, ['jpg', 'jpeg', 'gif', 'png'])) {
$path = public_path($this->getRawOriginal('path'));
[$width, $height] = Cache::remember("File::size-" . md5($path), now()->addDays(7), function () use ($path) {
$width = -1;
$height = -1;
if (file_exists($path)) {
$paramet = getimagesize($path);
$width = $paramet[0];
$height = $paramet[1];
}
return [$width, $height];
});
}
$this->appendattrs['width'] = $width;
$this->appendattrs['height'] = $height;
}
}
}

View File

@@ -8,37 +8,31 @@ namespace App\Models;
use Illuminate\Database\Eloquent\SoftDeletes;
/**
* App\Models\ProjectTaskMailLog
* App\Models\ProjectTaskPushLog
*
* @property int $id
* @property int|null $userid 用户id
* @property int|null $task_id 任务id
* @property string|null $email 电子邮箱
* @property int|null $type 提醒类型1第一次任务提醒2第二次任务超期提醒
* @property int|null $is_send 邮件发送是否成功0否1是
* @property string|null $send_error 邮件发送错误详情
* @property int|null $type 提醒类型0 任务开始提醒1 距离到期提醒2到期超时提醒
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property \Illuminate\Support\Carbon|null $deleted_at
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog newQuery()
* @method static \Illuminate\Database\Query\Builder|ProjectTaskMailLog onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog query()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog whereDeletedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog whereEmail($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog whereIsSend($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog whereSendError($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog whereTaskId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog whereType($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog whereUserid($value)
* @method static \Illuminate\Database\Query\Builder|ProjectTaskMailLog withTrashed()
* @method static \Illuminate\Database\Query\Builder|ProjectTaskMailLog withoutTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog newQuery()
* @method static \Illuminate\Database\Query\Builder|ProjectTaskPushLog onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog query()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog whereDeletedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog whereTaskId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog whereType($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog whereUserid($value)
* @method static \Illuminate\Database\Query\Builder|ProjectTaskPushLog withTrashed()
* @method static \Illuminate\Database\Query\Builder|ProjectTaskPushLog withoutTrashed()
* @mixin \Eloquent
*/
class ProjectTaskMailLog extends AbstractModel
class ProjectTaskPushLog extends AbstractModel
{
use SoftDeletes;

View File

@@ -46,7 +46,7 @@ class ProjectTaskUser extends AbstractModel
*/
public static function transfer($originalUserid, $newUserid)
{
self::whereUserid($originalUserid)->chunk(100, function ($list) use ($newUserid) {
self::whereUserid($originalUserid)->chunk(100, function ($list) use ($originalUserid, $newUserid) {
$tastIds = [];
/** @var self $item */
foreach ($list as $item) {
@@ -62,7 +62,7 @@ class ProjectTaskUser extends AbstractModel
$item->save();
}
if ($item->projectTask) {
$item->projectTask->addLog("移交{任务}身份", ['userid' => [$newUserid]]);
$item->projectTask->addLog("移交{任务}身份", ['userid' => [$originalUserid, ' => ',$newUserid]]);
if (!in_array($item->task_pid, $tastIds)) {
$tastIds[] = $item->task_pid;
$item->projectTask->syncDialogUser();

View File

@@ -46,7 +46,7 @@ class ProjectUser extends AbstractModel
*/
public static function transfer($originalUserid, $newUserid)
{
self::whereUserid($originalUserid)->chunkById(100, function ($list) use ($newUserid) {
self::whereUserid($originalUserid)->chunkById(100, function ($list) use ($originalUserid, $newUserid) {
/** @var self $item */
foreach ($list as $item) {
$row = self::whereProjectId($item->project_id)->whereUserid($newUserid)->first();
@@ -61,7 +61,12 @@ class ProjectUser extends AbstractModel
$item->save();
}
if ($item->project) {
$item->project->addLog("移交项目身份给", ['userid' => $newUserid]);
if ($item->project->personal) {
$name = User::userid2nickname($originalUserid) ?: ('ID:' . $originalUserid);
$item->project->name = "{$name}{$item->project->name}";
$item->project->save();
}
$item->project->addLog("移交项目身份", ['userid' => [$originalUserid, ' => ', $newUserid]]);
$item->project->syncDialogUser();
}
}

View File

@@ -2,6 +2,8 @@
namespace App\Models;
use App\Module\Base;
/**
* App\Models\Setting
*
@@ -24,5 +26,40 @@ namespace App\Models;
*/
class Setting extends AbstractModel
{
/**
* 验证邮箱地址(过滤忽略地址)
* @param $array
* @param \Closure $resultClosure
* @param \Closure|null $emptyClosure
* @return array|mixed
*/
public static function validateAddr($array, $resultClosure, $emptyClosure = null)
{
if (!is_array($array)) {
$array = [$array];
}
$ignoreAddr = Base::settingFind('emailSetting', 'ignore_addr');
$ignoreAddr = explode("\n", $ignoreAddr);
$ignoreArray = ['admin@dootask.com', 'test@dootask.com'];
foreach ($ignoreAddr as $item) {
if (Base::isEmail($item)) {
$ignoreArray[] = trim($item);
}
}
if ($ignoreArray) {
$array = array_diff($array, $ignoreArray);
}
if ($array) {
if ($resultClosure instanceof \Closure) {
foreach ($array as $value) {
$resultClosure($value);
}
}
} else {
if ($emptyClosure instanceof \Closure) {
$emptyClosure();
}
}
return $array;
}
}

37
app/Models/TaskWorker.php Normal file
View File

@@ -0,0 +1,37 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\SoftDeletes;
/**
* App\Models\TaskWorker
*
* @property int $id
* @property string|null $args
* @property string|null $error
* @property string|null $start_at 开始时间
* @property string|null $end_at 结束时间
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property \Illuminate\Support\Carbon|null $deleted_at
* @method static \Illuminate\Database\Eloquent\Builder|TaskWorker newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|TaskWorker newQuery()
* @method static \Illuminate\Database\Query\Builder|TaskWorker onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|TaskWorker query()
* @method static \Illuminate\Database\Eloquent\Builder|TaskWorker whereArgs($value)
* @method static \Illuminate\Database\Eloquent\Builder|TaskWorker whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|TaskWorker whereDeletedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|TaskWorker whereEndAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|TaskWorker whereError($value)
* @method static \Illuminate\Database\Eloquent\Builder|TaskWorker whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|TaskWorker whereStartAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|TaskWorker whereUpdatedAt($value)
* @method static \Illuminate\Database\Query\Builder|TaskWorker withTrashed()
* @method static \Illuminate\Database\Query\Builder|TaskWorker withoutTrashed()
* @mixin \Eloquent
*/
class TaskWorker extends AbstractModel
{
use SoftDeletes;
}

171
app/Models/UmengAlias.php Normal file
View File

@@ -0,0 +1,171 @@
<?php
namespace App\Models;
use App\Module\Base;
use Carbon\Carbon;
use Hedeqiang\UMeng\Android;
use Hedeqiang\UMeng\IOS;
/**
* App\Models\UmengAlias
*
* @property int $id
* @property int|null $userid 会员ID
* @property string|null $alias 别名
* @property string|null $platform 平台类型
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @method static \Illuminate\Database\Eloquent\Builder|UmengAlias newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|UmengAlias newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|UmengAlias query()
* @method static \Illuminate\Database\Eloquent\Builder|UmengAlias whereAlias($value)
* @method static \Illuminate\Database\Eloquent\Builder|UmengAlias whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|UmengAlias whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|UmengAlias wherePlatform($value)
* @method static \Illuminate\Database\Eloquent\Builder|UmengAlias whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|UmengAlias whereUserid($value)
* @mixin \Eloquent
*/
class UmengAlias extends AbstractModel
{
protected $table = 'umeng_alias';
/**
* 获取推送配置
* @return array|false
*/
public static function getPushConfig()
{
$setting = Base::setting('appPushSetting');
if ($setting['push'] !== 'open') {
return false;
}
$config = [];
if ($setting['ios_key']) {
$config['iOS'] = [
'appKey' => $setting['ios_key'],
'appMasterSecret' => $setting['ios_secret'],
'production_mode' => true,
];
}
if ($setting['android_key']) {
$config['Android'] = [
'appKey' => $setting['android_key'],
'appMasterSecret' => $setting['android_secret'],
'production_mode' => true,
];
}
return $config;
}
/**
* 推送消息
* @param string $alias
* @param string $platform
* @param array $array [title, subtitle, body, description, extra, seconds, badge]
* @return array|false
*/
public static function pushMsgToAlias($alias, $platform, $array)
{
$config = self::getPushConfig();
if ($config === false) {
return false;
}
//
$title = $array['title'] ?: ''; // 标题
$subtitle = $array['subtitle'] ?: ''; // 副标题iOS
$body = $array['body'] ?: ''; // 通知内容
$description = $array['description'] ?: 'no description'; // 描述
$extra = is_array($array['extra']) ? $array['extra'] : []; // 额外参数
$seconds = intval($array['seconds']) ?: 86400; // 有效时间(单位:秒)
$badge = intval($array['badge']) ?: 0; // 角标数iOS
//
switch ($platform) {
case 'ios':
if (!isset($config['iOS'])) {
return false;
}
$ios = new IOS($config);
return $ios->send([
'description' => $description,
'payload' => array_merge([
'aps' => [
'alert' => [
'title' => $title,
'subtitle' => $subtitle,
'body' => $body,
],
'sound' => 'default',
'badge' => $badge,
],
], $extra),
'type' => 'customizedcast',
'alias_type' => 'userid',
'alias' => $alias,
'policy' => [
'expire_time' => Carbon::now()->addSeconds($seconds)->toDateTimeString(),
],
]);
case 'android':
if (!isset($config['Android'])) {
return false;
}
$android = new Android($config);
return $android->send([
'description' => $description,
'payload' => array_merge([
'display_type' => 'notification',
'body' => [
'ticker' => $title,
'text' => $body,
'title' => $title,
'after_open' => 'go_app',
'play_sound' => true,
],
], $extra),
'type' => 'customizedcast',
'alias_type' => 'userid',
'alias' => $alias,
'mipush' => true,
'mi_activity' => 'app.eeui.umeng.activity.MfrMessageActivity',
'policy' => [
'expire_time' => Carbon::now()->addSeconds($seconds)->toDateTimeString(),
]
]);
default:
return false;
}
}
/**
* 推送给指定会员
* @param array|int $userid
* @param array $array
* @return void
*/
public static function pushMsgToUserid($userid, $array)
{
$builder = self::select(['id', 'platform', 'alias', 'userid'])->where('updated_at', '>', Carbon::now()->subMonth());
if (is_array($userid)) {
$builder->whereIn('userid', $userid);
} elseif (Base::isNumber($userid)) {
$builder->whereUserid($userid);
}
$builder
->orderByDesc('id')
->chunkById(100, function ($datas) use ($array) {
$uids = $datas->groupBy('userid');
foreach ($uids as $uid => $rows) {
$array['badge'] = WebSocketDialogMsgRead::whereUserid($uid)->whereSilence(0)->whereReadAt(null)->count();
$lists = $rows->groupBy('platform');
foreach ($lists as $platform => $list) {
$alias = $list->pluck('alias')->implode(',');
self::pushMsgToAlias($alias, $platform, $array);
}
}
});
}
}

View File

@@ -13,8 +13,11 @@ use Carbon\Carbon;
*
* @property int $userid
* @property array $identity 身份
* @property array $department 所属部门
* @property string|null $az A-Z
* @property string|null $pinyin 拼音(主要用于搜索)
* @property string|null $email 邮箱
* @property string|null $tel 联系电话
* @property string $nickname 昵称
* @property string|null $profession 职位/职称
* @property string $userimg 头像
@@ -30,6 +33,7 @@ use Carbon\Carbon;
* @property string|null $created_ip 注册IP
* @property string|null $disable_at 禁用时间(离职时间)
* @property int|null $email_verity 邮箱是否已验证
* @property int|null $bot 是否机器人
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @method static \Database\Factories\UserFactory factory(...$parameters)
@@ -37,9 +41,11 @@ use Carbon\Carbon;
* @method static \Illuminate\Database\Eloquent\Builder|User newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|User query()
* @method static \Illuminate\Database\Eloquent\Builder|User whereAz($value)
* @method static \Illuminate\Database\Eloquent\Builder|User whereBot($value)
* @method static \Illuminate\Database\Eloquent\Builder|User whereChangepass($value)
* @method static \Illuminate\Database\Eloquent\Builder|User whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|User whereCreatedIp($value)
* @method static \Illuminate\Database\Eloquent\Builder|User whereDepartment($value)
* @method static \Illuminate\Database\Eloquent\Builder|User whereDisableAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|User whereEmail($value)
* @method static \Illuminate\Database\Eloquent\Builder|User whereEmailVerity($value)
@@ -52,8 +58,10 @@ use Carbon\Carbon;
* @method static \Illuminate\Database\Eloquent\Builder|User whereLoginNum($value)
* @method static \Illuminate\Database\Eloquent\Builder|User whereNickname($value)
* @method static \Illuminate\Database\Eloquent\Builder|User wherePassword($value)
* @method static \Illuminate\Database\Eloquent\Builder|User wherePinyin($value)
* @method static \Illuminate\Database\Eloquent\Builder|User whereProfession($value)
* @method static \Illuminate\Database\Eloquent\Builder|User whereTaskDialogId($value)
* @method static \Illuminate\Database\Eloquent\Builder|User whereTel($value)
* @method static \Illuminate\Database\Eloquent\Builder|User whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|User whereUserid($value)
* @method static \Illuminate\Database\Eloquent\Builder|User whereUserimg($value)
@@ -67,6 +75,12 @@ class User extends AbstractModel
'updated_at',
];
// 默认头像类型auto自动生成system系统默认
public static $defaultAvatarMode = 'auto';
// 基本信息的字段
public static $basicField = ['userid', 'email', 'nickname', 'profession', 'department', 'userimg', 'bot', 'az', 'pinyin', 'line_at', 'disable_at'];
/**
* 更新数据校验
* @param array $param
@@ -97,11 +111,7 @@ class User extends AbstractModel
*/
public function getUserimgAttribute($value)
{
if ($value) {
return Base::fillUrl($value);
}
$name = ($this->userid - 1) % 21 + 1;
return url("images/avatar/default_{$name}.png");
return self::getAvatar($this->userid, $value, $this->email, $this->nickname);
}
/**
@@ -117,23 +127,62 @@ class User extends AbstractModel
return array_filter(is_array($value) ? $value : explode(",", trim($value, ",")));
}
/**
* 部门
* @param $value
* @return array
*/
public function getDepartmentAttribute($value)
{
if (empty($value)) {
return [];
}
return array_filter(is_array($value) ? $value : Base::explodeInt($value));
}
/**
* 获取所属部门名称
* @return string
*/
public function getDepartmentName()
{
if (empty($this->department)) {
return "";
}
$list = UserDepartment::select(['id', 'owner_userid', 'name'])->whereIn('id', $this->department)->take(10)->get();
$array = [];
foreach ($list as $item) {
$array[] = $item->name . ($item->owner_userid === $this->userid ? '(M)' : '');
}
return implode(', ', $array);
}
/**
* 是否在线
* @return bool
*/
public function getOnlineStatus()
{
$online = intval(Cache::get("User::online:" . $this->userid, 0));
$online = $this->bot || intval(Cache::get("User::online:" . $this->userid, 0)) > 0;
if ($online) {
return true;
}
return WebSocket::whereUserid($this->userid)->exists();
}
/**
* 返回是否LDAP用户
* @return bool
*/
public function isLdap()
{
return in_array('ldap', $this->identity);
}
/**
* 判断是否管理员
*/
public function isAdmin()
public function checkAdmin()
{
$this->identity('admin');
}
@@ -170,12 +219,32 @@ class User extends AbstractModel
/**
* 删除会员
* @param $reason
* @return bool|null
*/
public function deleteUser()
public function deleteUser($reason)
{
UserEmailVerification::whereEmail($this->email)->delete();
return $this->delete();
return AbstractModel::transaction(function () use ($reason) {
// 删除原因
$userDelete = UserDelete::createInstance([
'operator' => User::userid(),
'userid' => $this->userid,
'email' => $this->email,
'reason' => $reason,
'cache' => array_merge($this->getRawOriginal(), [
'department_name' => $this->getDepartmentName()
])
]);
$userDelete->save();
// 删除未读
WebSocketDialogMsgRead::whereUserid($this->userid)->delete();
// 删除待办
WebSocketDialogMsgTodo::whereUserid($this->userid)->delete();
// 删除邮箱验证记录
UserEmailVerification::whereEmail($this->email)->delete();
//
return $this->delete();
});
}
/** ***************************************************************************************** */
@@ -218,8 +287,15 @@ class User extends AbstractModel
$inArray = array_merge($inArray, $other);
}
$user = User::createInstance($inArray);
$user->save();
User::AZUpdate($user->userid);
$user->az = Base::getFirstCharter($user->nickname);
$user->pinyin = Base::cn2pinyin($user->nickname);
if ($user->save()) {
// 加入全员群组
if (Base::settingFind('system', 'all_group_autoin', 'yes') === 'yes') {
$dialog = WebSocketDialog::whereGroupType('all')->orderByDesc('id')->first();
$dialog?->joinGroup($user->userid, 0);
}
}
return $user->find($user->userid);
}
@@ -357,9 +433,12 @@ class User extends AbstractModel
if ($authInfo['userid'] > 0) {
$loginValid = floatval(Base::settingFind('system', 'loginValid')) ?: 720;
$loginValid *= 3600;
if ($authInfo['timestamp'] + $loginValid > time()) {
if ($authInfo['timestamp'] + $loginValid > time() || $authInfo['timestamp'] === -1) {
$row = self::whereUserid($authInfo['userid'])->whereEmail($authInfo['email'])->whereEncrypt($authInfo['encrypt'])->first();
if ($row) {
if (!$row->bot && $authInfo['timestamp'] === -1) {
return $_A["__static_auth"] = false; // 非机器人token时间不允许-1
}
$upArray = [];
if (Base::getIp() && $row->line_ip != Base::getIp()) {
$upArray['line_ip'] = Base::getIp();
@@ -386,7 +465,8 @@ class User extends AbstractModel
*/
public static function token($userinfo)
{
$userinfo->token = base64_encode($userinfo->userid . '#$' . $userinfo->email . '#$' . $userinfo->encrypt . '#$' . time() . '#$' . Base::generatePassword(6));
$time = $userinfo->bot ? -1 : time();
$userinfo->token = base64_encode($userinfo->userid . '#$' . $userinfo->email . '#$' . $userinfo->encrypt . '#$' . $time . '#$' . Base::generatePassword(6));
unset($userinfo->encrypt);
unset($userinfo->password);
return $userinfo->token;
@@ -419,10 +499,10 @@ class User extends AbstractModel
if (isset($_A["__static_userid2basic_" . $userid])) {
return $_A["__static_userid2basic_" . $userid];
}
$fields = ['userid', 'email', 'nickname', 'profession', 'userimg'];
$userInfo = self::whereUserid($userid)->select($fields)->first();
$userInfo = self::whereUserid($userid)->select(User::$basicField)->first();
if ($userInfo) {
$userInfo->online = $userInfo->getOnlineStatus();
$userInfo->department_name = $userInfo->getDepartmentName();
}
return $_A["__static_userid2basic_" . $userid] = ($userInfo ?: []);
}
@@ -439,19 +519,6 @@ class User extends AbstractModel
return $basic ? $basic->nickname : '';
}
/**
* 更新首字母
* @param $userid
*/
public static function AZUpdate($userid)
{
$row = self::whereUserid($userid)->first();
if ($row) {
$row->az = Base::getFirstCharter($row->nickname);
$row->save();
}
}
/**
* 是否需要验证码
* @param $email
@@ -476,6 +543,39 @@ class User extends AbstractModel
}
}
/**
* 获取头像
* @param $userid
* @param $userimg
* @param $email
* @param $nickname
* @return string
*/
public static function getAvatar($userid, $userimg, $email, $nickname)
{
// 自定义头像
if ($userimg && !str_contains($userimg, 'avatar/')) {
return Base::fillUrl($userimg);
}
// 机器人头像
if ($email == 'system-msg@bot.system') {
return url("images/avatar/default_system.png");
} elseif ($email == 'task-alert@bot.system') {
return url("images/avatar/default_task.png");
} elseif ($email == 'check-in@bot.system') {
return url("images/avatar/default_checkin.png");
} elseif ($email == 'bot-manager@bot.system') {
return url("images/avatar/default_bot.png");
}
// 生成文字头像
if (self::$defaultAvatarMode === 'auto') {
return url("avatar/" . urlencode($nickname) . ".png");
}
// 系统默认头像
$name = ($userid - 1) % 21 + 1;
return url("images/avatar/default_{$name}.png");
}
/**
* 检测密码策略是否符合
* @param $password
@@ -506,4 +606,53 @@ class User extends AbstractModel
}
}
}
/**
* 获取机器人或创建
* @param $key
* @param $update
* @param $userid
* @return self
*/
public static function botGetOrCreate($key, $update = [], $userid = 0)
{
$email = "{$key}@bot.system";
$botUser = self::whereEmail($email)->first();
if (empty($botUser)) {
$encrypt = Base::generatePassword(6);
$botUser = self::createInstance([
'bot' => 1,
'encrypt' => $encrypt,
'email' => $email,
'password' => Base::md52(Base::generatePassword(32), $encrypt),
'created_ip' => Base::getIp(),
]);
$botUser->save();
if ($userid > 0) {
UserBot::createInstance([
'userid' => $userid,
'bot_id' => $botUser->userid,
])->save();
}
//
if ($key === 'system-msg') {
$update['nickname'] = '系统消息';
} elseif ($key === 'task-alert') {
$update['nickname'] = '任务提醒';
} elseif ($key === 'check-in') {
$update['nickname'] = '签到打卡';
} elseif ($key === 'bot-manager') {
$update['nickname'] = '机器人管理';
}
}
if ($update) {
$botUser->updateInstance($update);
if (isset($update['nickname'])) {
$botUser->az = Base::getFirstCharter($botUser->nickname);
$botUser->pinyin = Base::cn2pinyin($botUser->nickname);
}
$botUser->save();
}
return $botUser;
}
}

30
app/Models/UserBot.php Normal file
View File

@@ -0,0 +1,30 @@
<?php
namespace App\Models;
/**
* App\Models\UserBot
*
* @property int $id
* @property int|null $userid 所属人ID
* @property int|null $bot_id 机器人ID
* @property int|null $clear_day 消息自动清理天数
* @property string|null $clear_at 下一次清理时间
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @method static \Illuminate\Database\Eloquent\Builder|UserBot newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|UserBot newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|UserBot query()
* @method static \Illuminate\Database\Eloquent\Builder|UserBot whereBotId($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserBot whereClearAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserBot whereClearDay($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserBot whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserBot whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserBot whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserBot whereUserid($value)
* @mixin \Eloquent
*/
class UserBot extends AbstractModel
{
}

View File

@@ -0,0 +1,66 @@
<?php
namespace App\Models;
use App\Exceptions\ApiException;
use App\Module\Base;
/**
* App\Models\UserCheckinMac
*
* @property int $id
* @property int|null $userid 会员id
* @property string|null $mac MAC地址
* @property string|null $remark 备注
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinMac newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinMac newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinMac query()
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinMac whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinMac whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinMac whereMac($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinMac whereRemark($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinMac whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinMac whereUserid($value)
* @mixin \Eloquent
*/
class UserCheckinMac extends AbstractModel
{
/**
* 保存mac地址
* @param $userid
* @param $array
* @return mixed
*/
public static function saveMac($userid, $array)
{
return AbstractModel::transaction(function() use ($array, $userid) {
$ids = [];
$list = [];
foreach ($array as $item) {
if (self::whereMac($item['mac'])->where('userid', '!=', $userid)->exists()) {
throw new ApiException("{$item['mac']} 已被其他成员设置");
}
$update = [];
if ($item['remark']) {
$update = [
'remark' => $item['remark']
];
}
$row = self::updateInsert([
'userid' => $userid,
'mac' => $item['mac']
], $update);
if ($row) {
$ids[] = $row->id;
$list[] = $row;
}
}
self::whereUserid($userid)->whereNotIn('id', $ids)->delete();
//
return Base::retSuccess('修改成功', $list);
});
}
}

View File

@@ -0,0 +1,134 @@
<?php
namespace App\Models;
use App\Module\Base;
/**
* App\Models\UserCheckinRecord
*
* @property int $id
* @property int|null $userid 会员id
* @property string|null $mac MAC地址
* @property string|null $date 签到日期
* @property array $times 签到时间
* @property int|null $report_time 上报的时间戳
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinRecord newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinRecord newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinRecord query()
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinRecord whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinRecord whereDate($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinRecord whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinRecord whereMac($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinRecord whereReportTime($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinRecord whereTimes($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinRecord whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinRecord whereUserid($value)
* @mixin \Eloquent
*/
class UserCheckinRecord extends AbstractModel
{
/**
* 签到记录
* @param $value
* @return array
*/
public function getTimesAttribute($value)
{
if (is_array($value)) {
return $value;
}
return Base::json2array($value);
}
/**
* 获取签到时间
* @param int $userid
* @param array $betweenTimes
* @return array
*/
public static function getTimes(int $userid, array $betweenTimes)
{
$array = [];
$records = self::whereUserid($userid)->whereBetween('created_at', $betweenTimes)->orderBy('id')->get();
/** @var self $record */
foreach ($records as $record) {
$times = array_map(function ($time) {
return preg_replace("/(\d+):(\d+):\d+$/", "$1:$2", $time);
}, $record->times);
if (isset($array[$record->date])) {
$array[$record->date] = array_merge($array[$record->date], $times);
} else {
$array[$record->date] = $times;
}
}
//
foreach ($array as $date => $times) {
$times = array_values(array_filter(array_unique($times)));
$inOrder = [];
foreach ($times as $key => $time) {
$inOrder[$key] = strtotime("2022-01-01 {$time}");
}
array_multisort($inOrder, SORT_ASC, $times);
$array[$date] = $times;
}
//
return $array;
}
/**
* 时间收集
* @param string $data
* @param array $times
* @return \Illuminate\Support\Collection
*/
public static function atCollect($data, $times)
{
$sameTimes = array_map(function($time) use ($data) {
return [
"datetime" => "{$data} {$time}",
"timestamp" => strtotime("{$data} {$time}")
];
}, $times);
return collect($sameTimes);
}
/**
* 签到时段
* @param array $times
* @param int $diff 多长未签到算失效(秒)
* @return array
*/
public static function atSection($times, $diff = 3600)
{
$start = "";
$end = "";
$array = [];
foreach ($times as $time) {
$time = preg_replace("/(\d+):(\d+):\d+$/", "$1:$2", $time);
if (empty($start)) {
$start = $time;
continue;
}
if (empty($end)) {
$end = $time;
continue;
}
if (strtotime("2022-01-01 {$time}") - strtotime("2022-01-01 {$end}") > $diff) {
$array[] = [$start, $end];
$start = $time;
$end = "";
continue;
}
$end = $time;
}
if ($start) {
$array[] = [$start, $end];
}
return $array;
}
}

71
app/Models/UserDelete.php Normal file
View File

@@ -0,0 +1,71 @@
<?php
namespace App\Models;
use App\Module\Base;
/**
* App\Models\UserDelete
*
* @property int $id
* @property int|null $operator 操作人员
* @property int|null $userid 用户id
* @property string|null $email 邮箱帐号
* @property string|null $reason 注销原因
* @property string $cache 会员资料缓存
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @method static \Illuminate\Database\Eloquent\Builder|UserDelete newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|UserDelete newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|UserDelete query()
* @method static \Illuminate\Database\Eloquent\Builder|UserDelete whereCache($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserDelete whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserDelete whereEmail($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserDelete whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserDelete whereOperator($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserDelete whereReason($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserDelete whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserDelete whereUserid($value)
* @mixin \Eloquent
*/
class UserDelete extends AbstractModel
{
/**
* 昵称
* @param $value
* @return string
*/
public function getCacheAttribute($value)
{
if (!is_array($value)) {
$value = Base::json2array($value);
// 昵称
if (!$value['nickname']) {
$value['nickname'] = Base::cardFormat($value['email']);
}
// 头像
$value['userimg'] = User::getAvatar($value['userid'], $value['userimg'], $value['email'], $value['nickname']);
// 部门
$value['department'] = array_filter(is_array($value['department']) ? $value['department'] : Base::explodeInt($value['department']));
}
return $value;
}
/**
* userid 获取 基础信息
* @param int $userid 会员ID
* @return array|null
*/
public static function userid2basic($userid)
{
$row = self::whereUserid($userid)->first();
if (empty($row) || empty($row->cache)) {
return null;
}
$cache = $row->cache;
$cache = array_intersect_key($cache, array_flip(array_merge(User::$basicField, ['department_name'])));
$cache['delete_at'] = $row->created_at->format($row->dateFormat ?: 'Y-m-d H:i:s');
return $cache;
}
}

View File

@@ -0,0 +1,153 @@
<?php
namespace App\Models;
use App\Exceptions\ApiException;
/**
* App\Models\UserDepartment
*
* @property int $id
* @property string|null $name 部门名称
* @property int|null $dialog_id 聊天会话ID
* @property int|null $parent_id 上级部门
* @property int|null $owner_userid 部门负责人
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @method static \Illuminate\Database\Eloquent\Builder|UserDepartment newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|UserDepartment newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|UserDepartment query()
* @method static \Illuminate\Database\Eloquent\Builder|UserDepartment whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserDepartment whereDialogId($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserDepartment whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserDepartment whereName($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserDepartment whereOwnerUserid($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserDepartment whereParentId($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserDepartment whereUpdatedAt($value)
* @mixin \Eloquent
*/
class UserDepartment extends AbstractModel
{
/**
* 保存部门
* @param $data
* @param $dialogUseid
*/
public function saveDepartment($data = [], $dialogUseid = 0) {
AbstractModel::transaction(function () use ($dialogUseid, $data) {
$oldUser = null;
$newUser = null;
if ($data['owner_userid'] !== $this->owner_userid) {
$oldUser = User::find($this->owner_userid);
$newUser = User::find($data['owner_userid']);
}
$this->updateInstance($data);
//
if ($this->dialog_id > 0) {
// 已有群
$dialog = WebSocketDialog::find($this->dialog_id);
if ($dialog) {
$dialog->name = $this->name;
$dialog->owner_id = $this->owner_userid;
if ($dialog->save()) {
$dialog->joinGroup($this->owner_userid, 0, true);
$dialog->pushMsg("groupUpdate", [
'id' => $dialog->id,
'name' => $dialog->name,
'owner_id' => $dialog->owner_id,
]);
}
}
} elseif ($dialogUseid > 0) {
// 使用现有群
$dialog = WebSocketDialog::whereType('group')->whereGroupType('user')->find($dialogUseid);
if (empty($dialog)) {
throw new ApiException("选择现有聊天群不存在");
}
$dialog->name = $this->name;
$dialog->owner_id = $this->owner_userid;
$dialog->group_type = 'department';
if ($dialog->save()) {
$dialog->joinGroup($this->owner_userid, 0, true);
$dialog->pushMsg("groupUpdate", [
'id' => $dialog->id,
'name' => $dialog->name,
'owner_id' => $dialog->owner_id,
'group_type' => $dialog->group_type,
]);
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'notice', [
'notice' => User::nickname() . " 将此群改为部门群"
], User::userid(), true, true);
}
$this->dialog_id = $dialog->id;
} else {
// 创建群
$dialog = WebSocketDialog::createGroup($this->name, [$this->owner_userid], 'department', $this->owner_userid);
if (empty($dialog)) {
throw new ApiException("创建群组失败");
}
$this->dialog_id = $dialog->id;
}
$this->save();
//
if ($oldUser) {
$oldUser->department = array_diff($oldUser->department, [$this->id]);
$oldUser->department = "," . implode(",", $oldUser->department) . ",";
$oldUser->save();
}
if ($newUser) {
$newUser->department = array_diff($newUser->department, [$this->id]);
$newUser->department = array_merge($newUser->department, [$this->id]);
$newUser->department = "," . implode(",", $newUser->department) . ",";
$newUser->save();
}
});
}
/**
* 删除部门
* @return void
*/
public function deleteDepartment() {
// 删除子部门
$list = self::whereParentId($this->id)->get();
foreach ($list as $item) {
$item->deleteDepartment();
}
// 移出成员
User::where("department", "like", "%,{$this->id},%")->chunk(100, function($items) {
/** @var User $user */
foreach ($items as $user) {
$user->department = array_diff($user->department, [$this->id]);
$user->department = "," . implode(",", $user->department) . ",";
$user->save();
}
});
// 解散群组
$dialog = WebSocketDialog::find($this->dialog_id);
if ($dialog) {
$dialog->deleteDialog();
$dialog->pushMsg("groupDelete");
}
//
$this->delete();
}
/**
* 移交部门身份
* @param $originalUserid
* @param $newUserid
* @return void
*/
public static function transfer($originalUserid, $newUserid)
{
self::whereOwnerUserid($originalUserid)->chunkById(100, function ($list) use ($originalUserid, $newUserid) {
/** @var self $item */
foreach ($list as $item) {
$item->saveDepartment([
'owner_userid' => $newUserid,
]);
}
});
}
}

View File

@@ -16,6 +16,7 @@ use Guanguans\Notify\Messages\EmailMessage;
* @property string|null $code 验证参数
* @property string|null $email 电子邮箱
* @property int|null $status 0-未验证1-已验证
* @property int|null $type 邮件类型1-邮箱认证2-修改邮箱
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @method static \Illuminate\Database\Eloquent\Builder|UserEmailVerification newModelQuery()
@@ -26,6 +27,7 @@ use Guanguans\Notify\Messages\EmailMessage;
* @method static \Illuminate\Database\Eloquent\Builder|UserEmailVerification whereEmail($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserEmailVerification whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserEmailVerification whereStatus($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserEmailVerification whereType($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserEmailVerification whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserEmailVerification whereUserid($value)
* @mixin \Eloquent
@@ -36,38 +38,54 @@ class UserEmailVerification extends AbstractModel
/**
* 发验证邮箱
* @param User $user
* @param int $type
* @param null $email
*/
public static function userEmailSend(User $user)
public static function userEmailSend(User $user, $type = 1, $email = null)
{
$res = self::whereUserid($user->userid)->where('created_at', '>', Carbon::now()->subMinutes(30))->first();
if ($res) return;
$email = $type == 1 ? $user->email : $email;
$res = self::whereEmail($email)->where('created_at', '>', Carbon::now()->subMinutes(30))->whereType($type)->first();
if ($res && $type == 1) return;
//删除
self::whereUserid($user->userid)->delete();
$userEmailVerification = self::createInstance([
self::whereUserid($email)->delete();
$code = $type == 1 ? Base::generatePassword(64) : rand(100000, 999999);
$row = self::createInstance([
'userid' => $user->userid,
'email' => $user->email,
'code' => Base::generatePassword(64),
'email' => $email,
'code' => $code,
'status' => 0,
'type' => $type
]);
$userEmailVerification->save();
$row->save();
$setting = Base::setting('emailSetting');
$url = Base::fillUrl('single/valid/email') . '?code=' . $userEmailVerification->code;
try {
if (!Base::isEmail($user->email)) {
throw new \Exception("User email '{$user->email}' address error");
if (!Base::isEmail($email)) {
throw new \Exception("User email '{$email}' address error");
}
switch ($type) {
case 2:
$subject = env('APP_NAME') . "修改邮箱验证";
$content = "<p>{$user->nickname} 您好,您正在修改 " . env('APP_NAME') . " 的邮箱验证码如下。请在30分钟内输入验证码</p><p style='color: #0000DD;'><u>$code</u></p><p>如果不是本人操作,您的帐号可能存在风险,请及时修改密码!</p>";
break;
case 3:
$subject = env('APP_NAME') . "注销帐号验证";
$content = "<p>{$user->nickname} 您好,您正在注销 " . env('APP_NAME') . " 的帐号验证码如下。请在30分钟内输入验证码</p><p style='color: #0000DD;'><u>$code</u></p><p>如果不是本人操作,您的帐号可能存在风险,请及时修改密码!</p>";
break;
default:
$url = Base::fillUrl('single/valid/email') . '?code=' . $row->code;
$subject = env('APP_NAME') . "绑定邮箱验证";
$content = "<p>{$user->nickname} 您好,您正在绑定 " . env('APP_NAME') . " 的邮箱请于30分钟之内点击以下链接完成验证 :</p><p style='display: flex; justify-content: center;'><a href='{$url}' target='_blank'>{$url}</a></p>";
break;
}
$subject = env('APP_NAME') . " 绑定邮箱验证";
$content = "<p>{$user->nickname} 您好,您正在绑定 " . env('APP_NAME') . " 的邮箱请于30分钟之内点击以下链接完成验证 :</p><p style='display: flex; justify-content: center;'><a href='{$url}' target='_blank'>{$url}</a></p>";
Factory::mailer()
->setDsn("smtp://{$setting['account']}:{$setting['password']}@{$setting['smtp_server']}:{$setting['port']}?verify_peer=0")
->setMessage(EmailMessage::create()
->from(env('APP_NAME', 'Task') . " <{$setting['account']}>")
->to($user->email)
->to($email)
->subject($subject)
->html($content))
->send();
} catch (\Exception $e) {
} catch (\Throwable $e) {
if (str_contains($e->getMessage(), "Timed Out")) {
throw new ApiException("language.TimedOut");
} elseif ($e->getCode() === 550) {
@@ -77,4 +95,39 @@ class UserEmailVerification extends AbstractModel
}
}
}
/**
* 校验验证码
* @param $email
* @param $code
* @param int $type
* @return bool
*/
public static function verify($email, $code, $type = 1)
{
if (!$code) {
throw new ApiException('请输入验证码');
}
/** @var UserEmailVerification $emailVerify */
$emailVerify = self::whereEmail($email)->whereType($type)->orderByDesc('id')->first();
if (empty($emailVerify) || $emailVerify->code != $code) {
throw new ApiException('验证码错误');
}
$oldTime = Carbon::parse($emailVerify->created_at)->timestamp;
$time = Base::Time();
// 30分钟失效
if (abs($time - $oldTime) > 1800) {
throw new ApiException('验证码已失效');
}
self::whereEmail($email)->whereCode($code)->whereType($type)->update([
'status' => 1
]);
return true;
}
}

View File

@@ -35,11 +35,37 @@ class UserTransfer extends AbstractModel
*/
public function start()
{
// 移交部门
UserDepartment::transfer($this->original_userid, $this->new_userid);
// 移交项目身份
ProjectUser::transfer($this->original_userid, $this->new_userid);
// 移交任务身份
ProjectTaskUser::transfer($this->original_userid, $this->new_userid);
// 移交文件
File::transfer($this->original_userid, $this->new_userid);
// 离职移出群组
WebSocketDialog::select(['web_socket_dialogs.*'])
->join('web_socket_dialog_users as u', 'web_socket_dialogs.id', '=', 'u.dialog_id')
->where('web_socket_dialogs.type', 'group')
->where('u.userid', $this->original_userid)
->orderByDesc('web_socket_dialogs.id')
->chunk(100, function($list) {
/** @var WebSocketDialog $dialog */
foreach ($list as $dialog) {
// 离职员工退出群
$dialog->exitGroup($this->original_userid, 'remove', false, false);
if ($dialog->owner_id === $this->original_userid) {
// 如果是群主则把交接人设为群主
$dialog->owner_id = $this->new_userid;
if ($dialog->save()) {
$dialog->joinGroup($this->new_userid, 0);
$dialog->pushMsg("groupUpdate", [
'id' => $dialog->id,
'owner_id' => $dialog->owner_id,
]);
}
}
}
});
}
}

View File

@@ -3,7 +3,9 @@
namespace App\Models;
use App\Exceptions\ApiException;
use App\Module\Base;
use App\Tasks\PushTask;
use Cache;
use Carbon\Carbon;
use Hhxsv5\LaravelS\Swoole\Task\Task;
use Illuminate\Database\Eloquent\SoftDeletes;
@@ -54,88 +56,196 @@ class WebSocketDialog extends AbstractModel
/**
* 格式化对话
* @param int $userid 会员ID
* @param bool $hasData
* @return $this
*/
public function formatData($userid)
public function formatData($userid, $hasData = false)
{
// 最后消息
$last_msg = WebSocketDialogMsg::whereDialogId($this->id)->orderByDesc('id')->first();
$this->last_msg = $last_msg;
// 未读信息
$unreadBuilder = WebSocketDialogMsgRead::whereDialogId($this->id)->whereUserid($userid)->whereReadAt(null);
$this->unread = $unreadBuilder->count();
$this->mention = $unreadBuilder->whereMention(1)->count();
$this->mark_unread = $this->mark_unread ?? WebSocketDialogUser::whereDialogId($this->id)->whereUserid($userid)->value('mark_unread');
// 对话人数
$builder = WebSocketDialogUser::whereDialogId($this->id);
$this->people = $builder->count();
$dialogUserFun = function ($key, $default = null) use ($userid) {
$data = Cache::remember("Dialog::formatData-{$this->id}-{$userid}", now()->addSeconds(10), function () use ($userid) {
return WebSocketDialogUser::whereDialogId($this->id)->whereUserid($userid)->first()?->toArray();
});
return $data[$key] ?? $default;
};
//
if (isset($this->search_msg_id)) {
// 最后消息 (搜索预览消息)
$this->last_msg = WebSocketDialogMsg::whereDialogId($this->id)->find($this->search_msg_id);
$this->last_at = $this->last_msg?->created_at;
} else {
// 最后消息
$this->last_msg = WebSocketDialogMsg::whereDialogId($this->id)->orderByDesc('id')->first();
// 未读信息
$unreadBuilder = WebSocketDialogMsgRead::whereDialogId($this->id)->whereUserid($userid)->whereReadAt(null);
$this->unread = $unreadBuilder->count();
$this->mention = 0;
$this->last_umid = 0;
$this->first_umid = 0; // 第一条未读消息
if ($this->unread > 0) {
$this->mention = $unreadBuilder->clone()->whereMention(1)->count();
$this->last_umid = intval($unreadBuilder->clone()->orderByDesc('msg_id')->value('msg_id'));
if ($hasData === true) {
$this->first_umid = intval($unreadBuilder->clone()->orderBy('msg_id')->value('msg_id'));
}
}
$this->mark_unread = $this->mark_unread ?? $dialogUserFun('mark_unread');
// 是否免打扰
$this->silence = $this->silence ?? $dialogUserFun('silence');
// 对话人数
$builder = WebSocketDialogUser::whereDialogId($this->id);
$this->people = $builder->count();
// 有待办
$this->todo_num = WebSocketDialogMsgTodo::whereDialogId($this->id)->whereUserid($userid)->whereDoneAt(null)->count();
}
// 对方信息
$this->dialog_user = null;
$this->group_info = null;
$this->top_at = $this->top_at ?? WebSocketDialogUser::whereDialogId($this->id)->whereUserid($userid)->value('top_at');
$this->top_at = $this->top_at ?? $dialogUserFun('top_at');
$this->bot = 0;
switch ($this->type) {
case "user":
$dialog_user = $builder->where('userid', '!=', $userid)->first();
$this->name = User::userid2nickname($dialog_user->userid);
$dialog_user = WebSocketDialogUser::whereDialogId($this->id)->where('userid', '!=', $userid)->first();
if ($dialog_user->userid === 0) {
$dialog_user->userid = $userid;
}
$basic = User::userid2basic($dialog_user->userid);
if ($basic) {
$this->name = $basic->nickname;
$this->bot = $basic->bot;
} else {
$this->name = 'non-existent';
$this->dialog_delete = 1;
}
$this->dialog_user = $dialog_user;
break;
case "group":
if ($this->group_type === 'project') {
$this->group_info = Project::withTrashed()->select(['id', 'name', 'archived_at', 'deleted_at'])->whereDialogId($this->id)->first()?->cancelAppend()->cancelHidden();
$this->name = $this->group_info ? $this->group_info->name : '';
} elseif ($this->group_type === 'task') {
$this->group_info = ProjectTask::withTrashed()->select(['id', 'name', 'complete_at', 'archived_at', 'deleted_at'])->whereDialogId($this->id)->first()?->cancelAppend()->cancelHidden();
$this->name = $this->group_info ? $this->group_info->name : '';
switch ($this->group_type) {
case 'project':
$this->group_info = Project::withTrashed()->select(['id', 'name', 'archived_at', 'deleted_at'])->whereDialogId($this->id)->first()?->cancelAppend()->cancelHidden();
if ($this->group_info) {
$this->name = $this->group_info->name;
} else {
$this->name = '[Delete]';
$this->dialog_delete = 1;
}
break;
case 'task':
$this->group_info = ProjectTask::withTrashed()->select(['id', 'name', 'complete_at', 'archived_at', 'deleted_at'])->whereDialogId($this->id)->first()?->cancelAppend()->cancelHidden();
if ($this->group_info) {
$this->name = $this->group_info->name;
} else {
$this->name = '[Delete]';
$this->dialog_delete = 1;
}
break;
case 'all':
$this->name = Base::Lang('全体成员');
$this->all_group_mute = Base::settingFind('system', 'all_group_mute');
break;
}
break;
}
if ($hasData === true) {
$msgBuilder = WebSocketDialogMsg::whereDialogId($this->id);
$this->has_tag = $msgBuilder->clone()->where('tag', '>', 0)->exists();
$this->has_image = $msgBuilder->clone()->whereMtype('image')->exists();
$this->has_file = $msgBuilder->clone()->whereMtype('file')->exists();
$this->has_link = $msgBuilder->clone()->whereLink(1)->exists();
}
$this->pinyin = Base::cn2pinyin($this->name);
return $this;
}
/**
* 加入聊天室
* @param int|array $userid 加入的会员ID或会员ID组
* @param int|array $userid 加入的会员ID或会员ID组
* @param int $inviter 邀请人
* @param bool|null $important 重要人员(null不修改、bool修改)
* @return bool
*/
public function joinGroup($userid)
public function joinGroup($userid, $inviter, $important = null)
{
AbstractModel::transaction(function () use ($userid) {
AbstractModel::transaction(function () use ($important, $inviter, $userid) {
foreach (is_array($userid) ? $userid : [$userid] as $value) {
if ($value > 0) {
$updateData = [
'inviter' => $inviter,
];
if (is_bool($important)) {
$updateData['important'] = $important ? 1 : 0;
}
$isInsert = false;
WebSocketDialogUser::updateInsert([
'dialog_id' => $this->id,
'userid' => $value,
]);
], $updateData, [], $isInsert);
if ($isInsert) {
WebSocketDialogMsg::sendMsg(null, $this->id, 'notice', [
'notice' => User::userid2nickname($value) . " 已加入群组"
], $inviter, true, true);
}
}
}
});
$this->pushMsg("groupUpdate", [
'id' => $this->id,
'people' => WebSocketDialogUser::whereDialogId($this->id)->count()
]);
return true;
}
/**
* 退出聊天室
* @param int|array $userid 加入的会员ID或会员ID组
* @return bool
* @param int|array $userid 退出的会员ID或会员ID组
* @param string $type exit|remove
* @param bool $checkDelete 是否检查删除
* @param bool $pushMsg 是否推送消息
*/
public function exitGroup($userid)
public function exitGroup($userid, $type = 'exit', $checkDelete = true, $pushMsg = true)
{
$builder = WebSocketDialogUser::whereDialogId($this->id);
if (is_array($userid)) {
$builder->whereIn('userid', $userid);
} else {
$builder->whereUserid($userid);
}
$builder->chunkById(100, function($list) {
/** @var WebSocketDialogUser $item */
foreach ($list as $item) {
if ($item->userid == $this->owner_id) {
// 群主不可退出
continue;
}
$item->delete();
$typeDesc = $type === 'remove' ? '移出' : '退出';
AbstractModel::transaction(function () use ($pushMsg, $checkDelete, $typeDesc, $type, $userid) {
$builder = WebSocketDialogUser::whereDialogId($this->id);
if (is_array($userid)) {
$builder->whereIn('userid', $userid);
} else {
$builder->whereUserid($userid);
}
$builder->chunkById(100, function($list) use ($pushMsg, $checkDelete, $typeDesc, $type) {
/** @var WebSocketDialogUser $item */
foreach ($list as $item) {
if ($checkDelete) {
if ($type === 'remove' && !in_array(User::userid(), [$this->owner_id, $item->inviter])) {
throw new ApiException('只有群主或邀请人可以移出成员');
}
if ($item->userid == $this->owner_id) {
throw new ApiException('群主不可' . $typeDesc);
}
if ($item->important) {
throw new ApiException('部门成员、项目人员或任务人员不可' . $typeDesc);
}
}
//
$item->delete();
//
if ($pushMsg) {
if ($type === 'remove') {
$notice = User::nickname() . "" . User::userid2nickname($item->userid) . " 移出群组";
} else {
$notice = User::userid2nickname($item->userid) . " 退出群组";
}
WebSocketDialogMsg::sendMsg(null, $this->id, 'notice', [
'notice' => $notice
], User::userid(), true, true);
}
}
});
});
return true;
//
$this->pushMsg("groupUpdate", [
'id' => $this->id,
'people' => WebSocketDialogUser::whereDialogId($this->id)->count()
]);
}
/**
@@ -167,22 +277,72 @@ class WebSocketDialog extends AbstractModel
/**
* 检查群组类型
* @param string|array|null $groupType
* @return void
*/
public function checkGroup($groupType = 'user')
public function checkGroup($groupType = null)
{
if ($this->type !== 'group') {
throw new ApiException('仅限群组操作');
}
if ($this->group_type !== $groupType) {
throw new ApiException('操作的群组类型错误');
if ($groupType) {
$groupTypes = is_array($groupType) ? $groupType : [$groupType];
if (!in_array($this->group_type, $groupTypes)) {
throw new ApiException('操作的群组类型错误');
}
}
}
/**
* 检查禁言
* @param $userid
* @return void
*/
public function checkMute($userid)
{
if ($this->group_type === 'all') {
$allGroupMute = Base::settingFind('system', 'all_group_mute');
switch ($allGroupMute) {
case 'all':
throw new ApiException('当前会话全员禁言');
case 'user':
if (!User::find($userid)?->checkAdmin()) {
throw new ApiException('当前会话禁言');
}
}
}
}
/**
* 获取群组名称
* @return mixed|string|null
*/
public function getGroupName()
{
if (!isset($this->appendattrs['groupName'])) {
$name = $this->name;
if ($this->type == "group") {
switch ($this->group_type) {
case 'project':
$name = \DB::table('projects')->where('dialog_id', $this->id)->value('name');
break;
case 'task':
$name = \DB::table('project_tasks')->where('dialog_id', $this->id)->value('name');
break;
case 'all':
$name = Base::Lang('全体成员');
break;
}
}
$this->appendattrs['groupName'] = $name;
}
return $this->appendattrs['groupName'];
}
/**
* 推送消息
* @param $action
* @param array $data 发送内容,默认为[id=>项目ID]
* @param array $data 发送内容,默认为[id=>会话ID]
* @param array $userid 指定会员,默认为群组所有成员
* @return void
*/
@@ -208,10 +368,24 @@ class WebSocketDialog extends AbstractModel
Task::deliver($task);
}
/**
* 更新对话最后消息时间
* @return WebSocketDialogMsg|\Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Model|\Illuminate\Database\Query\Builder|object|null
*/
public function updateMsgLastAt()
{
$lastMsg = WebSocketDialogMsg::whereDialogId($this->id)->orderByDesc('id')->first();
if ($lastMsg) {
$this->last_at = $lastMsg->created_at;
$this->save();
}
return $lastMsg;
}
/**
* 获取对话(同时检验对话身份)
* @param $dialog_id
* @param bool $checkOwner 是否校验群组身份
* @param bool|string $checkOwner 是否校验群组身份'auto'时有群主为true无群主为false
* @return self
*/
public static function checkDialog($dialog_id, $checkOwner = false)
@@ -222,11 +396,14 @@ class WebSocketDialog extends AbstractModel
}
//
$userid = User::userid();
if ($checkOwner === 'auto') {
$checkOwner = $dialog->owner_id > 0;
}
if ($checkOwner === true && $dialog->owner_id != $userid) {
throw new ApiException('仅限群主操作');
}
//
if ($dialog->type === 'group' && $dialog->group_type === 'task') {
if ($dialog->group_type === 'task') {
// 任务群对话校验是否在项目内
$project_id = intval(ProjectTask::whereDialogId($dialog->id)->value('project_id'));
if ($project_id > 0) {
@@ -257,7 +434,7 @@ class WebSocketDialog extends AbstractModel
'name' => $name ?: '',
'group_type' => $group_type,
'owner_id' => $owner_id,
'last_at' => $group_type === 'user' ? Carbon::now() : null,
'last_at' => in_array($group_type, ['user', 'department', 'all']) ? Carbon::now() : null,
]);
$dialog->save();
foreach (is_array($userid) ? $userid : [$userid] as $value) {
@@ -265,6 +442,7 @@ class WebSocketDialog extends AbstractModel
WebSocketDialogUser::createInstance([
'dialog_id' => $dialog->id,
'userid' => $value,
'important' => !in_array($group_type, ['user', 'all'])
])->save();
}
}
@@ -280,6 +458,9 @@ class WebSocketDialog extends AbstractModel
*/
public static function checkUserDialog($userid, $userid2)
{
if ($userid == $userid2) {
$userid2 = 0;
}
$dialogUser = self::select(['web_socket_dialogs.*'])
->join('web_socket_dialog_users as u1', 'web_socket_dialogs.id', '=', 'u1.dialog_id')
->join('web_socket_dialog_users as u2', 'web_socket_dialogs.id', '=', 'u2.dialog_id')
@@ -306,5 +487,4 @@ class WebSocketDialog extends AbstractModel
return $dialog;
});
}
}

View File

@@ -15,15 +15,26 @@ use Illuminate\Database\Eloquent\SoftDeletes;
*
* @property int $id
* @property int|null $dialog_id 对话ID
* @property string|null $dialog_type 对话类型
* @property int|null $userid 发送会员ID
* @property string|null $type 消息类型
* @property string|null $mtype 消息类型(用于搜索)
* @property array|mixed $msg 详细消息
* @property array|mixed $emoji emoji回复
* @property string|null $key 搜索关键词
* @property int|null $read 已阅数量
* @property int|null $send 发送数量
* @property int|null $tag 标注会员ID
* @property int|null $todo 设为待办会员ID
* @property int|null $link 是否存在链接
* @property int|null $modify 是否编辑
* @property int|null $reply_num 有多少条回复
* @property int|null $reply_id 回复ID
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property \Illuminate\Support\Carbon|null $deleted_at
* @property-read int|mixed $percentage
* @property-read \App\Models\WebSocketDialogMsg|null $reply_data
* @property-read \App\Models\WebSocketDialog|null $webSocketDialog
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg newQuery()
@@ -32,10 +43,20 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereDeletedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereDialogId($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereDialogType($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereEmoji($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereKey($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereLink($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereModify($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereMsg($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereMtype($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereRead($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereReplyId($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereReplyNum($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereSend($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereTag($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereTodo($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereType($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereUserid($value)
@@ -49,9 +70,11 @@ class WebSocketDialogMsg extends AbstractModel
protected $appends = [
'percentage',
'reply_data',
];
protected $hidden = [
'key',
'updated_at',
];
@@ -75,6 +98,21 @@ class WebSocketDialogMsg extends AbstractModel
return $this->appendattrs['percentage'];
}
/**
* 回复消息详情
* @return WebSocketDialogMsg|null
*/
public function getReplyDataAttribute()
{
if (!isset($this->appendattrs['reply_data'])) {
$this->appendattrs['reply_data'] = null;
if ($this->reply_id > 0) {
$this->appendattrs['reply_data'] = self::find($this->reply_id, ['id', 'userid', 'type', 'msg'])?->cancelAppend() ?: null;
}
}
return $this->appendattrs['reply_data'];
}
/**
* 消息格式化
* @param $value
@@ -90,10 +128,25 @@ class WebSocketDialogMsg extends AbstractModel
$value['type'] = in_array($value['ext'], ['jpg', 'jpeg', 'png', 'gif']) ? 'img' : 'file';
$value['path'] = Base::fillUrl($value['path']);
$value['thumb'] = Base::fillUrl($value['thumb'] ?: Base::extIcon($value['ext']));
} else if ($this->type === 'record') {
$value['path'] = Base::fillUrl($value['path']);
}
return $value;
}
/**
* emoji回复格式化
* @param $value
* @return array|mixed
*/
public function getEmojiAttribute($value)
{
if (is_array($value)) {
return $value;
}
return Base::json2array($value);
}
/**
* 获取占比
* @param bool|int $increment 是否新增阅读数
@@ -157,11 +210,236 @@ class WebSocketDialogMsg extends AbstractModel
return true;
}
/**
* emoji回复
* @param $symbol
* @param int $sender 发送的会员ID
* @return mixed
*/
public function emojiMsg($symbol, $sender)
{
$exist = false;
$array = $this->emoji;
foreach ($array as $index => &$item) {
if ($item['symbol'] === $symbol) {
if (in_array($sender, $item['userids'])) {
// 已存在 去除
$item['userids'] = array_values(array_diff($item['userids'], [$sender]));
if (empty($item['userids'])) {
unset($array[$index]);
$array = array_values($array);
}
} else {
// 未存在 添加
array_unshift($item['userids'], $sender);
}
$exist = true;
break;
}
}
if (!$exist) {
array_unshift($array, [
'symbol' => $symbol,
'userids' => [$sender]
]);
}
//
$this->emoji = Base::array2json($array);
$this->save();
$resData = [
'id' => $this->id,
'emoji' => $array,
];
//
$dialog = WebSocketDialog::find($this->dialog_id);
$dialog?->pushMsg('update', $resData);
//
return Base::retSuccess('sucess', $resData);
}
/**
* 标注、取消标注
* @param int $sender 标注的会员ID
* @return mixed
*/
public function toggleTagMsg($sender)
{
if (in_array($this->type, ['tag', 'todo', 'notice'])) {
return Base::retError('此消息不支持标注');
}
$before = $this->tag;
$this->tag = $before ? 0 : $sender;
$this->save();
$resData = [
'id' => $this->id,
'tag' => $this->tag,
];
//
$data = [
'update' => $resData
];
$res = self::sendMsg(null, $this->dialog_id, 'tag', [
'action' => $this->tag ? 'add' : 'remove',
'data' => [
'id' => $this->id,
'type' => $this->type,
'msg' => $this->quoteTextMsg(),
]
], $sender);
if (Base::isSuccess($res)) {
$data['add'] = $res['data'];
$dialog = WebSocketDialog::find($this->dialog_id);
$dialog->pushMsg('update', $resData);
} else {
$this->tag = $before;
$this->save();
}
//
return Base::retSuccess($this->tag ? '标注成功' : '取消成功', $data);
}
/**
* 设待办、取消待办
* @param int $sender 设待办的会员ID
* @param array $userids 设置给指定会员
* @return mixed
*/
public function toggleTodoMsg($sender, $userids = [])
{
if (in_array($this->type, ['tag', 'todo', 'notice'])) {
return Base::retError('此消息不支持设待办');
}
if ($this->todo && $this->todo != $sender) {
return Base::retError('仅支持设此待办人员【' . User::userid2nickname($this->todo) . '】取消');
}
$before = $this->todo;
$this->todo = $before ? 0 : $sender;
$this->save();
$resData = [
'id' => $this->id,
'todo' => $this->todo,
];
//
$data = [
'update' => $resData
];
$res = self::sendMsg(null, $this->dialog_id, 'todo', [
'action' => $this->todo ? 'add' : 'remove',
'data' => [
'id' => $this->id,
'type' => $this->type,
'msg' => $this->quoteTextMsg(),
'userids' => implode(",", $userids),
]
], $sender);
if (Base::isSuccess($res)) {
$data['add'] = $res['data'];
$dialog = WebSocketDialog::find($this->dialog_id);
$dialog->pushMsg('update', array_merge($resData, ['dialog_id' => $this->dialog_id]));
//
if ($this->todo) {
$useridList = $dialog->dialogUser->pluck('userid')->toArray();
foreach ($useridList as $userid) {
if ($userids && !in_array($userid, $userids)) {
continue;
}
if (empty($userid)) {
continue;
}
WebSocketDialogMsgTodo::createInstance([
'dialog_id' => $this->dialog_id,
'msg_id' => $this->id,
'userid' => $userid,
])->saveOrIgnore();
}
} else {
WebSocketDialogMsgTodo::whereMsgId($this->id)->delete();
}
} else {
$this->todo = $before;
$this->save();
}
//
return Base::retSuccess($this->todo ? '设置成功' : '取消成功', $data);
}
/**
* 转发消息
* @param array|int $dialogids
* @param array|int $userids
* @param int $sender 发送的会员ID
* @return mixed
*/
public function forwardMsg($dialogids, $userids, $sender)
{
return AbstractModel::transaction(function() use ($dialogids, $sender, $userids) {
$originalMsg = Base::json2array($this->getRawOriginal('msg'));
$msgs = [];
$already = [];
if ($dialogids) {
if (!is_array($dialogids)) {
$dialogids = [$dialogids];
}
foreach ($dialogids as $dialogid) {
$res = self::sendMsg(null, $dialogid, $this->type, $originalMsg, $sender);
if (Base::isSuccess($res)) {
$msgs[] = $res['data'];
$already[] = $dialogid;
}
}
}
if ($userids) {
if (!is_array($userids)) {
$userids = [$userids];
}
foreach ($userids as $userid) {
if (!User::whereUserid($userid)->exists()) {
continue;
}
$dialog = WebSocketDialog::checkUserDialog($sender, $userid);
if ($dialog && !in_array($dialog->id, $already)) {
$res = self::sendMsg(null, $dialog->id, $this->type, $originalMsg, $sender);
if (Base::isSuccess($res)) {
$msgs[] = $res['data'];
}
}
}
}
return Base::retSuccess('转发成功', [
'msgs' => $msgs
]);
});
}
/**
* 删除消息
* @param array|int $ids
* @return void
*/
public function deleteMsg()
public static function deleteMsgs($ids) {
$ids = Base::arrayRetainInt(is_array($ids) ? $ids : [$ids], true);
AbstractModel::transaction(function() use ($ids) {
$dialogIds = WebSocketDialogMsg::select('dialog_id')->whereIn("id", $ids)->distinct()->get()->pluck('dialog_id');
$replyIds = WebSocketDialogMsg::select('reply_id')->whereIn("id", $ids)->distinct()->get()->pluck('reply_id');
//
WebSocketDialogMsgRead::whereIn('msg_id', $ids)->whereNull('read_at')->delete(); // 未阅读记录不需要软删除,直接删除即可
WebSocketDialogMsgTodo::whereIn('msg_id', $ids)->delete();
self::whereIn('id', $ids)->delete();
//
foreach ($dialogIds as $id) {
WebSocketDialog::find($id)?->updateMsgLastAt();
}
foreach ($replyIds as $id) {
self::whereId($id)->update(['reply_num' => self::whereReplyId($id)->count()]);
}
});
}
/**
* 撤回消息
* @return void
*/
public function withdrawMsg()
{
$send_dt = Carbon::parse($this->created_at)->addDay();
if ($send_dt->lt(Carbon::now())) {
@@ -171,14 +449,11 @@ class WebSocketDialogMsg extends AbstractModel
$deleteRead = WebSocketDialogMsgRead::whereMsgId($this->id)->whereNull('read_at')->delete(); // 未阅读记录不需要软删除,直接删除即可
$this->delete();
//
$last_msg = null;
if ($this->webSocketDialog) {
$last_msg = WebSocketDialogMsg::whereDialogId($this->dialog_id)->orderByDesc('id')->first();
$this->webSocketDialog->last_at = $last_msg->created_at;
$this->webSocketDialog->save();
if ($this->reply_id > 0) {
self::whereId($this->reply_id)->decrement('reply_num');
}
//
$dialog = WebSocketDialog::find($this->dialog_id);
$dialog = $this->webSocketDialog;
if ($dialog) {
$userids = $dialog->dialogUser->pluck('userid')->toArray();
PushTask::push([
@@ -189,15 +464,105 @@ class WebSocketDialogMsg extends AbstractModel
'data' => [
'id' => $this->id,
'dialog_id' => $this->dialog_id,
'last_msg' => $last_msg,
'last_msg' => $dialog->updateMsgLastAt(),
'update_read' => $deleteRead ? 1 : 0
],
]
]);
}
//
WebSocketDialogMsgTodo::whereMsgId($this->id)->delete();
});
}
/**
* 预览消息
* @param bool $preserveHtml 保留html格式
* @param null|array $data
* @return string
*/
public function previewMsg($preserveHtml = false, $data = null)
{
if ($data === null) {
$data = [
'type' => $this->type,
'msg' => $this->msg,
];
}
switch ($data['type']) {
case 'text':
return $this->previewTextMsg($data['msg']['text'], $preserveHtml);
case 'record':
return "[语音]";
case 'meeting':
return "[会议] ${$data['msg']['name']}";
case 'file':
if ($data['msg']['type'] == 'img') {
return "[图片]";
}
return "[文件] {$data['msg']['name']}";
case 'tag':
$action = $data['msg']['action'] === 'remove' ? '取消标注' : '标注';
return "[{$action}] {$this->previewMsg(false, $data['msg']['data'])}";
case 'todo':
$action = $data['msg']['action'] === 'remove' ? '取消待办' : ($data['msg']['action'] === 'done' ? '完成' : '设待办');
return "[{$action}] {$this->previewMsg(false, $data['msg']['data'])}";
case 'notice':
return $data['msg']['notice'];
default:
return "[未知的消息]";
}
}
/**
* 生成关键词
* @return string
*/
public function generateMsgKey()
{
return match ($this->type) {
'text' => str_replace("&nbsp;", " ", strip_tags($this->msg['text'])),
'meeting', 'file' => $this->msg['name'],
default => '',
};
}
/**
* 返回引用消息(如果是文本消息则截取)
* @param int $strlen
* @return array|mixed
*/
public function quoteTextMsg($strlen = 30)
{
$msg = $this->msg;
if ($this->type === 'text') {
$msg['text'] = $this->previewTextMsg($msg['text']);
if (mb_strlen($msg['text']) > $strlen) {
$msg['text'] = mb_substr($msg['text'], 0, $strlen - 3) . "...";
}
}
return $msg;
}
/**
* 返回文本预览消息
* @param $text
* @param bool $preserveHtml 保留html格式
* @return string|string[]|null
*/
private function previewTextMsg($text, $preserveHtml = false)
{
if (!$text) return '';
$text = preg_replace("/<img\s+class=\"emoticon\"[^>]*?alt=\"(\S+)\"[^>]*?>/", "[$1]", $text);
$text = preg_replace("/<img\s+class=\"emoticon\"[^>]*?>/", "[动画表情]", $text);
$text = preg_replace("/<img\s+class=\"browse\"[^>]*?>/", "[图片]", $text);
if (!$preserveHtml) {
$text = strip_tags($text);
$text = str_replace(["&nbsp;", "&amp;", "&lt;", "&gt;"], [" ", "&", "<", ">"], $text);
}
return $text;
}
/**
* 处理文本消息内容,用于发送前
* @param $text
@@ -206,72 +571,280 @@ class WebSocketDialogMsg extends AbstractModel
*/
public static function formatMsg($text, $dialog_id)
{
@ini_set("pcre.backtrack_limit", 999999999);
// 基础处理
$text = preg_replace("/<(\/[a-zA-Z]+)\s*>/s", "<$1>", $text);
// 图片 [:IMAGE:className:width:height:src:alt:]
preg_match_all("/<img\s*src=\"data:image\/(png|jpg|jpeg);base64,(.*?)\"(.*?)>(<\/img>)*/s", $text, $matchs);
preg_match_all("/<img\s+src=\"data:image\/(png|jpg|jpeg|gif);base64,(.*?)\"(.*?)>(<\/img>)*/s", $text, $matchs);
foreach ($matchs[2] as $key => $base64) {
$tmpPath = "uploads/chat/" . date("Ym") . "/" . $dialog_id . "/";
Base::makeDir(public_path($tmpPath));
$tmpPath .= md5s($base64) . "." . $matchs[1][$key];
if (file_put_contents(public_path($tmpPath), base64_decode($base64))) {
$imagesize = getimagesize(public_path($tmpPath));
$text = str_replace($matchs[0][$key], "[:IMAGE:browse:{$imagesize[0]}:{$imagesize[1]}:{$tmpPath}::]", $text);
$imagePath = "uploads/chat/" . date("Ym") . "/" . $dialog_id . "/";
Base::makeDir(public_path($imagePath));
$imagePath .= md5s($base64) . "." . $matchs[1][$key];
if (file_put_contents(public_path($imagePath), base64_decode($base64))) {
$imageSize = getimagesize(public_path($imagePath));
if (Base::imgThumb(public_path($imagePath), public_path($imagePath) . "_thumb.jpg", 320, 0)) {
$imagePath .= "_thumb.jpg";
}
$text = str_replace($matchs[0][$key], "[:IMAGE:browse:{$imageSize[0]}:{$imageSize[1]}:{$imagePath}::]", $text);
}
}
// 表情图片
preg_match_all("/<img class=\"emoticon\"(.*?)>/s", $text, $matchs);
preg_match_all("/<img\s+class=\"emoticon\"(.*?)>/s", $text, $matchs);
foreach ($matchs[1] as $key => $str) {
preg_match("/data-asset=\"(.*?)\"/", $str, $matchAsset);
preg_match("/data-name=\"(.*?)\"/", $str, $matchName);
if (file_exists(public_path($matchAsset[1]))) {
$imagesize = getimagesize(public_path($matchAsset[1]));
$text = str_replace($matchs[0][$key], "[:IMAGE:emoticon:{$imagesize[0]}:{$imagesize[1]}:{$matchAsset[1]}:{$matchName[1]}:]", $text);
$imageSize = null;
$imagePath = "";
$imageName = "";
if ($matchAsset[1] === "emosearch") {
preg_match("/src=\"(.*?)\"/", $str, $matchSrc);
if ($matchSrc) {
$srcMd5 = md5($matchSrc[1]);
$imagePath = "uploads/emosearch/" . substr($srcMd5, 0, 2) . "/" . substr($srcMd5, 32 - 2) . "/";
Base::makeDir(public_path($imagePath));
$imagePath .= md5s($matchSrc[1]);
if (file_exists(public_path($imagePath))) {
$imageSize = getimagesize(public_path($imagePath));
} else {
$image = file_get_contents($matchSrc[1]);
if ($image && file_put_contents(public_path($imagePath), $image)) {
$imageSize = getimagesize(public_path($imagePath));
// 添加后缀
if ($imageSize && !str_contains($imagePath, '.')) {
preg_match("/^image\/(png|jpg|jpeg|gif)$/", $imageSize['mime'], $matchMine);
if ($matchMine) {
$imageNewPath = $imagePath . "." . $matchMine[1];
if (rename(public_path($imagePath), public_path($imageNewPath))) {
$imagePath = $imageNewPath;
}
}
}
}
}
}
} elseif (file_exists(public_path($matchAsset[1]))) {
$imagePath = $matchAsset[1];
$imageName = $matchName[1];
$imageSize = getimagesize(public_path($matchAsset[1]));
}
if ($imageSize) {
$text = str_replace($matchs[0][$key], "[:IMAGE:emoticon:{$imageSize[0]}:{$imageSize[1]}:{$imagePath}:{$imageName}:]", $text);
} else {
$text = str_replace($matchs[0][$key], "[:IMAGE:browse:90:90:images/other/imgerr.jpg::]", $text);
}
}
// @成员、#任务
preg_match_all("/<span class=\"mention\"(.*?)>.*?<\/span>.*?<\/span>.*?<\/span>/s", $text, $matchs);
// 其他网络图片
preg_match_all("/<img[^>]*?src=([\"'])(.*?\.(png|jpg|jpeg|gif))\\1[^>]*?>/is", $text, $matchs);
foreach ($matchs[2] as $key => $str) {
if (str_starts_with($str, "{{RemoteURL}}")) {
$imagePath = Base::leftDelete($str, "{{RemoteURL}}");
$imagePath = Base::rightDelete($imagePath, "_thumb.jpg");
} else {
$imagePath = "uploads/chat/" . date("Ym") . "/" . $dialog_id . "/";
Base::makeDir(public_path($imagePath));
$imagePath .= md5s($str) . "." . $matchs[3][$key];
}
if (file_exists(public_path($imagePath))) {
$imageSize = getimagesize(public_path($imagePath));
if (Base::imgThumb(public_path($imagePath), public_path($imagePath) . "_thumb.jpg", 320, 0)) {
$imagePath .= "_thumb.jpg";
}
$text = str_replace($matchs[0][$key], "[:IMAGE:browse:{$imageSize[0]}:{$imageSize[1]}:{$imagePath}::]", $text);
} else {
$image = file_get_contents($str);
if (empty($image)) {
$text = str_replace($matchs[0][$key], "[:IMAGE:browse:90:90:images/other/imgerr.jpg::]", $text);
} else if (file_put_contents(public_path($imagePath), $image)) {
$imageSize = getimagesize(public_path($imagePath));
if (Base::imgThumb(public_path($imagePath), public_path($imagePath) . "_thumb.jpg", 320, 0)) {
$imagePath .= "_thumb.jpg";
}
$text = str_replace($matchs[0][$key], "[:IMAGE:browse:{$imageSize[0]}:{$imageSize[1]}:{$imagePath}::]", $text);
}
}
}
// @成员、#任务、~文件
preg_match_all("/<span\s+class=\"mention\"(.*?)>.*?<\/span>.*?<\/span>.*?<\/span>/s", $text, $matchs);
foreach ($matchs[1] as $key => $str) {
preg_match("/data-denotation-char=\"(.*?)\"/", $str, $matchChar);
preg_match("/data-id=\"(.*?)\"/", $str, $matchId);
preg_match("/data-value=\"(.*?)\"/", $str, $matchValye);
$text = str_replace($matchs[0][$key], "[:{$matchChar[1]}:{$matchId[1]}:{$matchValye[1]}:]", $text);
$keyId = $matchId[1];
if ($matchChar[1] === "~") {
if (Base::isNumber($keyId)) {
$file = File::permissionFind($keyId);
if ($file->type == 'folder') {
throw new ApiException('文件夹不支持分享');
}
$fileLink = $file->getShareLink(User::userid());
$keyId = $fileLink['code'];
} else {
preg_match("/\/single\/file\/(.*?)$/i", $keyId, $match);
if ($match && strlen($match[1]) >= 32) {
$keyId = $match[1];
} else {
throw new ApiException('文件分享错误');
}
}
}
$text = str_replace($matchs[0][$key], "[:{$matchChar[1]}:{$keyId}:{$matchValye[1]}:]", $text);
}
// 处理链接标签
preg_match_all("/<a[^>]*?href=([\"'])(.*?)\\1[^>]*?>(.*?)<\/a>/is", $text, $matchs);
foreach ($matchs[0] as $key => $str) {
$herf = $matchs[2][$key];
$title = $matchs[3][$key] ?: $herf;
preg_match("/\/single\/file\/(.*?)$/i", strip_tags($title), $match);
if ($match && strlen($match[1]) >= 32) {
$file = File::select(['files.id', 'files.name', 'files.ext'])->join('file_links as L', 'files.id', '=', 'L.file_id')->where('L.code', $match[1])->first();
if ($file && $file->name) {
$name = $file->ext ? "{$file->name}.{$file->ext}" : $file->name;
$text = str_replace($str, "[:~:{$match[1]}:{$name}:]", $text);
continue;
}
}
$herf = base64_encode($herf);
$title = base64_encode($title);
$text = str_replace($str, "[:LINK:{$herf}:{$title}:]", $text);
}
// 文件分享链接
preg_match_all("/(https*:\/\/)((\w|=|\?|\.|\/|&|-|:|\+|%|;|#)+)/i", $text, $matchs);
if ($matchs) {
foreach ($matchs[0] as $str) {
preg_match("/\/single\/file\/(.*?)$/i", $str, $match);
if ($match && strlen($match[1]) >= 32) {
$file = File::select(['files.id', 'files.name', 'files.ext'])->join('file_links as L', 'files.id', '=', 'L.file_id')->where('L.code', $match[1])->first();
if ($file && $file->name) {
$name = $file->ext ? "{$file->name}.{$file->ext}" : $file->name;
$text = str_replace($str, "[:~:{$match[1]}:{$name}:]", $text);
}
}
}
}
// 过滤标签
$text = strip_tags($text, '<blockquote> <strong> <pre> <ol> <ul> <li> <em> <p> <s> <u>');
$text = preg_replace("/\<(blockquote|strong|pre|ol|ul|li|em|p|s|u).*?\>/i", "<$1>", $text);
$text = strip_tags($text, '<blockquote> <strong> <pre> <ol> <ul> <li> <em> <p> <s> <u> <a>');
$text = preg_replace("/\<(blockquote|strong|pre|ol|ul|li|em|p|s|u).*?\>/is", "<$1>", $text); // 不用去除a标签上面已经处理过了
$text = preg_replace("/\[:IMAGE:(.*?):(.*?):(.*?):(.*?):(.*?):\]/i", "<img class=\"$1\" width=\"$2\" height=\"$3\" src=\"{{RemoteURL}}$4\" alt=\"$5\"/>", $text);
$text = preg_replace("/\[:@:(.*?):(.*?):\]/i", "<span class=\"mention user\" data-id=\"$1\">@$2</span>", $text);
$text = preg_replace("/\[:#:(.*?):(.*?):\]/i", "<span class=\"mention task\" data-id=\"$1\">#$2</span>", $text);
$text = preg_replace("/\[:~:(.*?):(.*?):\]/i", "<a class=\"mention file\" href=\"{{RemoteURL}}single/file/$1\" target=\"_blank\">~$2</a>", $text);
$text = preg_replace_callback("/\[:LINK:(.*?):(.*?):\]/i", function (array $match) {
return "<a href=\"" . base64_decode($match[1]) . "\" target=\"_blank\">" . base64_decode($match[2]) . "</a>";
}, $text);
return preg_replace("/^(<p><\/p>)+|(<p><\/p>)+$/i", "", $text);
}
/**
* 发送消息
* @param int $dialog_id 会话ID即 聊天室ID
* @param string $type 消息类型
* @param array $msg 发送的消息
* @param int $sender 发送的会员ID默认自己0为系统
* 发送消息、修改消息
* @param string $action 动作
* - reply-98回复消息ID=98
* - update-99更新消息ID=99
* @param int $dialog_id 会话ID即 聊天室ID
* @param string $type 消息类型
* @param array $msg 发送的消息
* @param int $sender 发送的会员ID默认自己0为系统
* @param bool $push_self 推送-是否推给自己
* @param bool $push_retry 推送-失败后重试1次有时候在事务里执行数据还没生成时会出现找不到消息的情况
* @param bool|null $push_silence 推送-静默
* - type = [text|file|record|meeting] 默认为false
* @return array
*/
public static function sendMsg($dialog_id, $type, $msg, $sender = 0)
public static function sendMsg($action, $dialog_id, $type, $msg, $sender = 0, $push_self = false, $push_retry = false, $push_silence = null)
{
$dialogMsg = self::createInstance([
'userid' => $sender ?: User::userid(),
'type' => $type,
'msg' => $msg,
'read' => 0,
]);
AbstractModel::transaction(function () use ($dialog_id, $msg, $dialogMsg) {
$dialog = WebSocketDialog::find($dialog_id);
if (empty($dialog)) {
throw new ApiException('获取会话失败');
$link = 0;
$mtype = $type;
if ($type === 'text') {
if (str_contains($msg['text'], '<a ') || preg_match("/https*:\/\//", $msg['text'])) {
$link = 1;
}
$dialog->last_at = Carbon::now();
$dialog->save();
$dialogMsg->send = 1;
$dialogMsg->dialog_id = $dialog->id;
if (str_contains($msg['text'], '<img ')) {
$mtype = str_contains($msg['text'], '"emoticon"') ? 'emoticon' : 'image';
}
} elseif ($type === 'file') {
if (in_array($msg['ext'], ['jpg', 'jpeg', 'png', 'gif'])) {
$mtype = 'image';
}
}
if ($push_silence === null) {
$push_silence = !in_array($type, ["text", "file", "record", "meeting"]);
}
//
$update_id = preg_match("/^update-(\d+)$/", $action, $match) ? $match[1] : 0;
$reply_id = preg_match("/^reply-(\d+)$/", $action, $match) ? $match[1] : 0;
$sender = $sender ?: User::userid();
//
$dialog = WebSocketDialog::find($dialog_id);
if (empty($dialog)) {
throw new ApiException('获取会话失败');
}
$dialog->checkMute($sender);
//
if ($update_id) {
// 修改
$dialogMsg = self::whereId($update_id)->whereDialogId($dialog_id)->first();
if (empty($dialogMsg)) {
throw new ApiException('消息不存在');
}
if ($dialogMsg->type !== 'text') {
throw new ApiException('此消息不支持此操作');
}
if ($dialogMsg->userid != $sender) {
throw new ApiException('仅支持修改自己的消息');
}
//
$updateData = [
'mtype' => $mtype,
'link' => $link,
'msg' => $msg,
'modify' => 1,
];
$dialogMsg->updateInstance($updateData);
$dialogMsg->key = $dialogMsg->generateMsgKey();
$dialogMsg->save();
});
Task::deliver(new WebSocketDialogMsgTask($dialogMsg->id));
return Base::retSuccess('发送成功', $dialogMsg);
//
$dialog->pushMsg('update', array_merge($updateData, [
'id' => $dialogMsg->id
]));
//
return Base::retSuccess('修改成功', $dialogMsg);
} else {
// 发送
if ($reply_id && !self::whereId($reply_id)->increment('reply_num')) {
throw new ApiException('回复的消息不存在');
}
//
$dialogMsg = self::createInstance([
'dialog_id' => $dialog_id,
'dialog_type' => $dialog->type,
'reply_id' => $reply_id,
'userid' => $sender,
'type' => $type,
'mtype' => $mtype,
'link' => $link,
'msg' => $msg,
'read' => 0,
]);
AbstractModel::transaction(function () use ($dialog, $dialogMsg) {
$dialog->last_at = Carbon::now();
$dialog->save();
$dialogMsg->send = 1;
$dialogMsg->key = $dialogMsg->generateMsgKey();
$dialogMsg->save();
});
//
$task = new WebSocketDialogMsgTask($dialogMsg->id);
if ($push_self) {
$task->setIgnoreFd(null);
}
if ($push_retry) {
$task->setMsgNotExistRetry(true);
}
if ($push_silence) {
$task->setSilence($push_silence);
}
Task::deliver($task);
//
return Base::retSuccess('发送成功', $dialogMsg);
}
}
}

View File

@@ -10,8 +10,10 @@ use Carbon\Carbon;
* @property int $id
* @property int|null $dialog_id 对话ID
* @property int|null $msg_id 消息ID
* @property int|null $userid 发送会员ID
* @property int|null $userid 接收会员ID
* @property int|null $mention 是否提及(被@
* @property int|null $silence 是否免打扰0否1是
* @property int|null $email 是否发了邮件
* @property int|null $after 在阅读之后才添加的记录
* @property string|null $read_at 阅读时间
* @property-read \App\Models\WebSocketDialogMsg|null $webSocketDialogMsg
@@ -20,10 +22,12 @@ use Carbon\Carbon;
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgRead query()
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgRead whereAfter($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgRead whereDialogId($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgRead whereEmail($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgRead whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgRead whereMention($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgRead whereMsgId($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgRead whereReadAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgRead whereSilence($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgRead whereUserid($value)
* @mixin \Eloquent
*/

View File

@@ -0,0 +1,47 @@
<?php
namespace App\Models;
/**
* App\Models\WebSocketDialogMsgTodo
*
* @property int $id
* @property int|null $dialog_id 对话ID
* @property int|null $msg_id 消息ID
* @property int|null $userid 接收会员ID
* @property string|null $done_at 完成时间
* @property-read array|mixed $msg_data
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgTodo newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgTodo newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgTodo query()
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgTodo whereDialogId($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgTodo whereDoneAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgTodo whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgTodo whereMsgId($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgTodo whereUserid($value)
* @mixin \Eloquent
*/
class WebSocketDialogMsgTodo extends AbstractModel
{
protected $appends = [
'msg_data',
];
function __construct(array $attributes = [])
{
parent::__construct($attributes);
$this->timestamps = false;
}
/**
* 消息详情
* @return array|mixed
*/
public function getMsgDataAttribute()
{
if (!isset($this->appendattrs['msgData'])) {
$this->appendattrs['msgData'] = WebSocketDialogMsg::select(['id', 'type', 'msg'])->whereId($this->msg_id)->first()?->cancelAppend();
}
return $this->appendattrs['msgData'];
}
}

View File

@@ -10,6 +10,9 @@ namespace App\Models;
* @property int|null $userid 会员ID
* @property string|null $top_at 置顶时间
* @property int|null $mark_unread 是否标记为未读0否1是
* @property int|null $silence 是否免打扰0否1是
* @property int|null $inviter 邀请人
* @property int|null $important 是否不可移出(项目、任务、部门人员)
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser newModelQuery()
@@ -18,7 +21,10 @@ namespace App\Models;
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereDialogId($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereImportant($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereInviter($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereMarkUnread($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereSilence($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereTopAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereUserid($value)

23
app/Models/clearHelper.php Executable file
View File

@@ -0,0 +1,23 @@
<?php
/**
* 清除模型class注释
*/
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
$path = dirname(__FILE__). '/';
$lists = scandir($path);
//
foreach ($lists AS $item) {
$fillPath = $path . $item;
if (!in_array($item, ['AbstractModel.php', 'clearHelper.php']) && str_ends_with($item, '.php')) {
$content = file_get_contents($fillPath);
preg_match("/\/\*\*([\s\S]*?)class\s*(.*?)\s*extends\s*AbstractModel/i", $content, $matchs);
if ($matchs[0]) {
$content = str_replace($matchs[0], 'class ' . $matchs[2] . ' extends AbstractModel', $content);
file_put_contents($fillPath, $content);
}
}
}
echo "Success \n";

View File

@@ -0,0 +1,193 @@
<?php
/**
* @Description :
*
* @Date : 2019-03-14 13:22
* @Author : hmy940118@gmail.com
*/
namespace App\Module\AgoraIO;
class AccessToken
{
const Privileges = array(
"kJoinChannel" => 1,
"kPublishAudioStream" => 2,
"kPublishVideoStream" => 3,
"kPublishDataStream" => 4,
"kPublishAudioCdn" => 5,
"kPublishVideoCdn" => 6,
"kRequestPublishAudioStream" => 7,
"kRequestPublishVideoStream" => 8,
"kRequestPublishDataStream" => 9,
"kInvitePublishAudioStream" => 10,
"kInvitePublishVideoStream" => 11,
"kInvitePublishDataStream" => 12,
"kAdministrateChannel" => 101
);
public $appID, $appCertificate, $channelName, $uid;
public $message;
/**
* AccessToken constructor.
* @throws \Exception
*/
public function __construct()
{
$this->message = new Message();
}
/**
* @param $uid
*/
public function setUid($uid)
{
if ($uid === 0) {
$this->uid = "";
} else {
$this->uid = $uid . '';
}
}
/**
* @param $name
* @param $str
* @return bool
*/
public function is_nonempty_string($name, $str)
{
if (is_string($str) && $str !== "") {
return true;
}
echo $name . " check failed, should be a non-empty string";
return false;
}
/**
* @param $appID
* @param $appCertificate
* @param $channelName
* @param $uid
* @return AccessToken|null
* @throws \Exception
*/
public static function init($appID, $appCertificate, $channelName, $uid)
{
$accessToken = new AccessToken();
if (!$accessToken->is_nonempty_string("appID", $appID) ||
!$accessToken->is_nonempty_string("appCertificate", $appCertificate) ||
!$accessToken->is_nonempty_string("channelName", $channelName)) {
return null;
}
$accessToken->appID = $appID;
$accessToken->appCertificate = $appCertificate;
$accessToken->channelName = $channelName;
$accessToken->setUid($uid);
$accessToken->message = new Message();
return $accessToken;
}
/**
* @param $token
* @param $appCertificate
* @param $channel
* @param $uid
* @return AccessToken|null
* @throws \Exception
*/
public static function initWithToken($token, $appCertificate, $channel, $uid)
{
$accessToken = new AccessToken();
if (!$accessToken->extract($token, $appCertificate, $channel, $uid)) {
return null;
}
return $accessToken;
}
/**
* @param $key
* @param $expireTimestamp
* @return $this
*/
public function addPrivilege($key, $expireTimestamp)
{
$this->message->privileges[$key] = $expireTimestamp;
return $this;
}
/**
* @param $token
* @param $appCertificate
* @param $channelName
* @param $uid
* @return bool
* @throws \Exception
*/
protected function extract($token, $appCertificate, $channelName, $uid)
{
$ver_len = 3;
$appid_len = 32;
$version = substr($token, 0, $ver_len);
if ($version !== "006") {
echo 'invalid version ' . $version;
return false;
}
if (!$this->is_nonempty_string("token", $token) ||
!$this->is_nonempty_string("appCertificate", $appCertificate) ||
!$this->is_nonempty_string("channelName", $channelName)) {
return false;
}
$appid = substr($token, $ver_len, $appid_len);
$content = (base64_decode(substr($token, $ver_len + $appid_len, strlen($token) - ($ver_len + $appid_len))));
$pos = 0;
$len = unpack("v", $content . substr($pos, 2))[1];
$pos += 2;
$sig = substr($content, $pos, $len);
$pos += $len;
$crc_channel = unpack("V", substr($content, $pos, 4))[1];
$pos += 4;
$crc_uid = unpack("V", substr($content, $pos, 4))[1];
$pos += 4;
$msgLen = unpack("v", substr($content, $pos, 2))[1];
$pos += 2;
$msg = substr($content, $pos, $msgLen);
$this->appID = $appid;
$message = new Message();
$message->unpackContent($msg);
$this->message = $message;
//non reversable values
$this->appCertificate = $appCertificate;
$this->channelName = $channelName;
$this->setUid($uid);
return true;
}
/**
* @return string
*/
public function build()
{
$msg = $this->message->packContent();
$val = array_merge(unpack("C*", $this->appID), unpack("C*", $this->channelName), unpack("C*", $this->uid), $msg);
$sig = hash_hmac('sha256', implode(array_map("chr", $val)), $this->appCertificate, true);
$crc_channel_name = crc32($this->channelName) & 0xffffffff;
$crc_uid = crc32($this->uid) & 0xffffffff;
$content = array_merge(unpack("C*", $this->packString($sig)), unpack("C*", pack("V", $crc_channel_name)), unpack("C*", pack("V", $crc_uid)), unpack("C*", pack("v", count($msg))), $msg);
$version = "006";
$ret = $version . $this->appID . base64_encode(implode(array_map("chr", $content)));
return $ret;
}
/**
* @param $value
* @return string
*/
public function packString($value)
{
return pack("v", strlen($value)) . $value;
}
}

View File

@@ -0,0 +1,122 @@
<?php
/**
* @Description :
*
* @Date : 2019-03-14 13:20
* @Author : hmy940118@gmail.com
*/
namespace App\Module\AgoraIO;
class AgoraTokenGenerator
{
const AttendeePrivileges = array(
AccessToken::Privileges["kJoinChannel"] => 0,
AccessToken::Privileges["kPublishAudioStream"] => 0,
AccessToken::Privileges["kPublishVideoStream"] => 0,
AccessToken::Privileges["kPublishDataStream"] => 0
);
const PublisherPrivileges = array(
AccessToken::Privileges["kJoinChannel"] => 0,
AccessToken::Privileges["kPublishAudioStream"] => 0,
AccessToken::Privileges["kPublishVideoStream"] => 0,
AccessToken::Privileges["kPublishDataStream"] => 0,
AccessToken::Privileges["kPublishAudioCdn"] => 0,
AccessToken::Privileges["kPublishVideoCdn"] => 0,
AccessToken::Privileges["kInvitePublishAudioStream"] => 0,
AccessToken::Privileges["kInvitePublishVideoStream"] => 0,
AccessToken::Privileges["kInvitePublishDataStream"] => 0
);
const SubscriberPrivileges = array(
AccessToken::Privileges["kJoinChannel"] => 0,
AccessToken::Privileges["kRequestPublishAudioStream"] => 0,
AccessToken::Privileges["kRequestPublishVideoStream"] => 0,
AccessToken::Privileges["kRequestPublishDataStream"] => 0
);
const AdminPrivileges = array(
AccessToken::Privileges["kJoinChannel"] => 0,
AccessToken::Privileges["kPublishAudioStream"] => 0,
AccessToken::Privileges["kPublishVideoStream"] => 0,
AccessToken::Privileges["kPublishDataStream"] => 0,
AccessToken::Privileges["kAdministrateChannel"] => 0
);
const Role = array(
"kRoleAttendee" => 0, // for communication
"kRolePublisher" => 1, // for live broadcast
"kRoleSubscriber" => 2, // for live broadcast
"kRoleAdmin" => 101
);
const RolePrivileges = array(
self::Role["kRoleAttendee"] => self::AttendeePrivileges,
self::Role["kRolePublisher"] => self::PublisherPrivileges,
self::Role["kRoleSubscriber"] => self::SubscriberPrivileges,
self::Role["kRoleAdmin"] => self::AdminPrivileges
);
public $token;
/**
* AgoraTokenGenerator constructor.
* @param $appID
* @param $appCertificate
* @param $channelName
* @param $uid
* @throws \Exception
*/
public function __construct($appID, $appCertificate, $channelName, $uid){
$this->token = new AccessToken();
$this->token->appID = $appID;
$this->token->appCertificate = $appCertificate;
$this->token->channelName = $channelName;
$this->token->setUid($uid);
}
/**
* @param $token
* @param $appCertificate
* @param $channel
* @param $uid
* @throws \Exception
*/
public function initWithToken($token, $appCertificate, $channel, $uid){
$this->token = AccessToken::initWithToken($token, $appCertificate, $channel, $uid);
}
/**
* @param $role
*/
public function initPrivilege($role){
$p = self::RolePrivileges[$role];
foreach($p as $key => $value){
$this->setPrivilege($key, $value);
}
}
/**
* @param $privilege
* @param $expireTimestamp
*/
public function setPrivilege($privilege, $expireTimestamp){
$this->token->addPrivilege($privilege, $expireTimestamp);
}
/**
* @param $privilege
*/
public function removePrivilege($privilege){
unset($this->token->message->privileges[$privilege]);
}
/**
* @return string
*/
public function buildToken(){
return $this->token->build();
}
}

View File

@@ -0,0 +1,70 @@
<?php
/**
* @Description :
*
* @Date : 2019-03-14 13:27
* @Author : hmy940118@gmail.com
*/
namespace App\Module\AgoraIO;
class Message
{
public $salt;
public $ts;
public $privileges;
/**
* Message constructor.
* @throws \Exception
*/
public function __construct()
{
$this->salt = rand(0, 100000);
$date = new \DateTime("now", new \DateTimeZone('UTC'));
$this->ts = $date->getTimestamp() + 168 * 3600; // 7天时间
$this->privileges = array();
}
/**
* @return array
*/
public function packContent()
{
$buffer = unpack("C*", pack("V", $this->salt));
$buffer = array_merge($buffer, unpack("C*", pack("V", $this->ts)));
$buffer = array_merge($buffer, unpack("C*", pack("v", sizeof($this->privileges))));
foreach ($this->privileges as $key => $value) {
$buffer = array_merge($buffer, unpack("C*", pack("v", $key)));
$buffer = array_merge($buffer, unpack("C*", pack("V", $value)));
}
return $buffer;
}
/**
* @param $msg
*/
public function unpackContent($msg)
{
$pos = 0;
$salt = unpack("V", substr($msg, $pos, 4))[1];
$pos += 4;
$ts = unpack("V", substr($msg, $pos, 4))[1];
$pos += 4;
$size = unpack("v", substr($msg, $pos, 2))[1];
$pos += 2;
$privileges = array();
for ($i = 0; $i < $size; $i++) {
$key = unpack("v", substr($msg, $pos, 2));
$pos += 2;
$value = unpack("V", substr($msg, $pos, 4));
$pos += 4;
$privileges[$key[1]] = $value[1];
}
$this->salt = $salt;
$this->ts = $ts;
$this->privileges = $privileges;
}
}

View File

@@ -6,9 +6,9 @@ use App\Exceptions\ApiException;
use App\Models\Setting;
use App\Models\Tmp;
use Cache;
use Exception;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Config;
use Overtrue\Pinyin\Pinyin;
use Redirect;
use Request;
use Storage;
@@ -106,11 +106,21 @@ class Base
*/
public static function checkClientVersion($min)
{
if (version_compare(Base::getClientVersion(), $min, '<')) {
throw new ApiException('当前版本 (v' . Base::getClientVersion() . ') 过低');
if (!self::judgeClientVersion($min)) {
throw new ApiException('当前版本 (v' . Base::getClientVersion() . ') 过低,最低版本要求 (v' . $min . ')。');
}
}
/**
* 判断客户端版本
* @param $min
* @return bool
*/
public static function judgeClientVersion($min)
{
return !version_compare(Base::getClientVersion(), $min, '<');
}
/**
* 判断是否域名格式
* @param $domain
@@ -285,7 +295,7 @@ class Base
{
try {
Storage::makeDirectory($path);
} catch (Exception $e) {
} catch (\Throwable $e) {
}
if (!file_exists($path)) {
self::makeDir(dirname($path));
@@ -486,7 +496,7 @@ class Base
try {
$array = json_decode($string, true);
return is_array($array) ? $array : [];
} catch (Exception $e) {
} catch (\Throwable) {
return [];
}
}
@@ -504,7 +514,7 @@ class Base
}
try {
return json_encode($array, $options);
} catch (Exception $e) {
} catch (\Throwable) {
return '';
}
}
@@ -873,36 +883,61 @@ class Base
* 打散字符串,只留为数字的项
* @param $delimiter
* @param $string
* @param bool $reInt 是否格式化值
* @return array
*/
public static function explodeInt($delimiter, $string = null)
public static function explodeInt($delimiter, $string = null, $reInt = true)
{
if ($string == null) {
$string = $delimiter;
$delimiter = ',';
}
$array = is_array($string) ? $string : explode($delimiter, $string);
return self::arrayRetainInt($array);
return self::arrayRetainInt($array, $reInt);
}
/**
* 数组只保留数字的
* @param $array
* @param bool $int 是否格式化值
* @param bool $reInt 是否格式化值
* @return array
*/
public static function arrayRetainInt($array, $int = false)
public static function arrayRetainInt($array, $reInt = false)
{
if (!is_array($array)) {
return $array;
}
foreach ($array as $k => $v) {
if (!is_numeric($v)) {
unset($array[$k]);
} elseif ($int === true) {
} elseif ($reInt === true) {
$array[$k] = intval($v);
}
}
return array_values($array);
}
/**
* 数组拼接字符串(前后也加上)
* @param $glue
* @param $pieces
* @param $around
* @return string
*/
public static function arrayImplode($glue = "", $pieces = null, $around = true)
{
if ($pieces == null) {
$pieces = $glue;
$glue = ',';
}
$pieces = array_values(array_filter(array_unique($pieces)));
$string = implode($glue, $pieces);
if ($around && $string) {
$string = ",{$string},";
}
return $string;
}
/**
* 判断是否二维数组
* @param $array
@@ -1011,7 +1046,21 @@ class Base
*/
public static function isNumber($str)
{
if (preg_match("/^\d*$/", $str)) {
if (preg_match("/^\d+$/", $str)) {
return true;
} else {
return false;
}
}
/**
* 正则判断是否MAC地址
* @param $str
* @return bool
*/
public static function isMac($str)
{
if (preg_match("/^[A-Fa-f\d]{2}:[A-Fa-f\d]{2}:[A-Fa-f\d]{2}:[A-Fa-f\d]{2}:[A-Fa-f\d]{2}:[A-Fa-f\d]{2}$/", $str)) {
return true;
} else {
return false;
@@ -1810,7 +1859,7 @@ class Base
$onlineip = '0,0,0,0';
}
preg_match("/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/", $onlineip, $match);
$_A["__static_ip"] = $match[0] ?: 'unknown';
$_A["__static_ip"] = $match ? ($match[0] ?: 'unknown') : '';
}
return $_A["__static_ip"];
}
@@ -2174,9 +2223,42 @@ class Base
return Min(Max(Base::nullShow(Request::input($inputName), $default), 1), $max);
}
/**
* base64语音保存
* @param array $param [ base64=带前缀的base64, path=>文件路径 ]
* @return array [name=>文件名, size=>文件大小(单位KB),file=>绝对地址, path=>相对地址, url=>全路径地址, ext=>文件后缀名]
*/
public static function record64save($param)
{
$base64 = $param['base64'];
if (preg_match('/^(data:\s*audio\/(\w+);base64,)/', $base64, $res)) {
$extension = $res[2];
if (!in_array($extension, ['mp3', 'wav'])) {
return Base::retError('语音格式错误');
}
$fileName = 'record_' . md5($base64) . '.' . $extension;
$fileDir = $param['path'];
$filePath = public_path($fileDir);
Base::makeDir($filePath);
if (file_put_contents($filePath . $fileName, base64_decode(str_replace($res[1], '', $base64)))) {
$fileSize = filesize($filePath . $fileName);
$array = [
"name" => $fileName, //原文件名
"size" => Base::twoFloat($fileSize / 1024, true), //大小KB
"file" => $filePath . $fileName, //文件的完整路径 "D:\www....KzZ.jpg"
"path" => $fileDir . $fileName, //相对路径 "uploads/pic....KzZ.jpg"
"url" => Base::fillUrl($fileDir . $fileName), //完整的URL "https://.....hhsKzZ.jpg"
"ext" => $extension, //文件后缀名
];
return Base::retSuccess('success', $array);
}
}
return Base::retError('语音保存失败');
}
/**
* image64图片保存
* @param array $param [ image64=带前缀的base64, path=>文件路径, fileName=>文件名称, scale=>[压缩原图宽,高, 压缩方式] ]
* @param array $param [ image64=带前缀的base64, path=>文件路径, fileName=>文件名称, scale=>[压缩原图宽,高, 压缩方式], autoThumb=>false不要自动生成缩略图 ]
* @return array [name=>文件名, size=>文件大小(单位KB),file=>绝对地址, path=>相对地址, url=>全路径地址, ext=>文件后缀名]
*/
public static function image64save($param)
@@ -2196,7 +2278,7 @@ class Base
if ($width > 0 || $height > 0) {
$scaleName = "_{WIDTH}x{HEIGHT}";
if (isset($param['scale'][2])) {
$scaleName .= $param['scale'][2];
$scaleName .= "_c{$param['scale'][2]}";
}
}
}
@@ -2257,8 +2339,13 @@ class Base
}
//生成缩略图
$array['thumb'] = $array['path'];
if (Base::imgThumb($array['file'], $array['file'] . "_thumb.jpg", 180, 0)) {
$array['thumb'] .= "_thumb.jpg";
if ($extension === 'gif' && !isset($param['autoThumb'])) {
$param['autoThumb'] = false;
}
if ($param['autoThumb'] !== false) {
if (Base::imgThumb($array['file'], $array['file'] . "_thumb.jpg", 320, 0)) {
$array['thumb'] .= "_thumb.jpg";
}
}
$array['thumb'] = Base::fillUrl($array['thumb']);
return Base::retSuccess('success', $array);
@@ -2269,12 +2356,13 @@ class Base
/**
* 上传文件
* @param array $param [ type=[文件类型], file=>Request::file, path=>文件路径, fileName=>文件名称, scale=>[压缩原图宽,高, 压缩方式], size=>限制大小KB, autoThumb=>false不要自动生成缩略图 ]
* @param array $param [ type=[文件类型], file=>Request::file, path=>文件路径, fileName=>文件名称, scale=>[压缩原图宽,高, 压缩方式], size=>限制大小KB, autoThumb=>false不要自动生成缩略图, chmod=>权限(默认0644) ]
* @return array [name=>原文件名, size=>文件大小(单位KB),file=>绝对地址, path=>相对地址, url=>全路径地址, ext=>文件后缀名]
*/
public static function upload($param)
{
$file = $param['file'];
$chmod = $param['chmod'] ?: 0644;
if (empty($file)) {
return Base::retError("您没有选择要上传的文件");
}
@@ -2319,30 +2407,7 @@ class Base
$type = ['yml', 'yaml', 'dmg', 'pkg', 'blockmap', 'zip', 'exe', 'msi'];
break;
case 'more':
$type = [
'text', 'md', 'markdown',
'drawio',
'mind',
'docx', 'wps', 'doc', 'xls', 'xlsx', 'ppt', 'pptx',
'jpg', 'jpeg', 'png', 'gif', 'bmp', 'ico', 'raw',
'rar', 'zip', 'jar', '7-zip', 'tar', 'gzip', '7z',
'tif', 'tiff',
'dwg', 'dxf',
'ofd',
'pdf',
'txt',
'htaccess', 'htgroups', 'htpasswd', 'conf', 'bat', 'cmd', 'cpp', 'c', 'cc', 'cxx', 'h', 'hh', 'hpp', 'ino', 'cs', 'css',
'dockerfile', 'go', 'golang', 'html', 'htm', 'xhtml', 'vue', 'we', 'wpy', 'java', 'js', 'jsm', 'jsx', 'json', 'jsp', 'less', 'lua', 'makefile', 'gnumakefile',
'ocamlmakefile', 'make', 'mysql', 'nginx', 'ini', 'cfg', 'prefs', 'm', 'mm', 'pl', 'pm', 'p6', 'pl6', 'pm6', 'pgsql', 'php',
'inc', 'phtml', 'shtml', 'php3', 'php4', 'php5', 'phps', 'phpt', 'aw', 'ctp', 'module', 'ps1', 'py', 'r', 'rb', 'ru', 'gemspec', 'rake', 'guardfile', 'rakefile',
'gemfile', 'rs', 'sass', 'scss', 'sh', 'bash', 'bashrc', 'sql', 'sqlserver', 'swift', 'ts', 'typescript', 'str', 'vbs', 'vb', 'v', 'vh', 'sv', 'svh', 'xml',
'rdf', 'rss', 'wsdl', 'xslt', 'atom', 'mathml', 'mml', 'xul', 'xbl', 'xaml', 'yaml', 'yml',
'asp', 'properties', 'gitignore', 'log', 'bas', 'prg', 'python', 'ftl', 'aspx',
'mp3', 'wav', 'mp4', 'flv',
'avi', 'mov', 'wmv', 'mkv', '3gp', 'rm',
'xmind',
'rp',
];
$type = []; // 不限制上传文件类型
break;
default:
return Base::retError('错误的类型参数');
@@ -2356,7 +2421,7 @@ class Base
if ($param['size'] > 0 && $fileSize > $param['size'] * 1024) {
return Base::retError('文件大小超限,最大限制:' . $param['size'] . 'KB');
}
} catch (Exception $e) {
} catch (\Throwable) {
$fileSize = 0;
}
$scaleName = "";
@@ -2370,30 +2435,36 @@ class Base
if ($width > 0 || $height > 0) {
$scaleName = "_{WIDTH}x{HEIGHT}";
if (isset($param['scale'][2])) {
$scaleName .= $param['scale'][2];
$scaleName .= "_c{$param['scale'][2]}";
}
}
}
$fileName = md5_file($file) . '.' . $extension;
$scaleName = md5_file($file) . $scaleName . '.' . $extension;
$fileName = md5_file($file);
$scaleName = md5_file($file) . $scaleName;
if ($extension) {
$fileName = $fileName . '.' . $extension;
$scaleName = $scaleName . '.' . $extension;
}
}
//
$file->move(public_path($param['path']), $fileName);
//
$path = $param['path'] . $fileName;
$array = [
"name" => $file->getClientOriginalName(), //原文件名
"size" => Base::twoFloat($fileSize / 1024, true), //大小KB
"file" => public_path($param['path'] . $fileName), //文件的完整路径 "D:\www....KzZ.jpg"
"path" => $param['path'] . $fileName, //相对路径 "uploads/pic....KzZ.jpg"
"url" => Base::fillUrl($param['path'] . $fileName), //完整的URL "https://.....hhsKzZ.jpg"
"thumb" => '', //缩略图(预览图) "https://.....hhsKzZ.jpg_thumb.jpg"
"width" => -1, //图片宽度
"height" => -1, //图片高度
"ext" => $extension, //文件后缀名
"name" => $file->getClientOriginalName(), //原文件名
"size" => Base::twoFloat($fileSize / 1024, true), //大小KB
"file" => public_path($path), //文件的完整路径 "D:\www....KzZ.jpg"
"path" => $path, //相对路径 "uploads/pic....KzZ.jpg"
"url" => Base::fillUrl($path), //完整的URL "https://.....hhsKzZ.jpg"
"thumb" => '', //缩略图(预览图) "https://.....hhsKzZ.jpg_thumb.jpg"
"width" => -1, //图片宽度
"height" => -1, //图片高度
"ext" => $extension, //文件后缀名
];
if (!is_file($array['file'])) {
return Base::retError('上传失败');
}
@chmod($array['file'], $chmod);
//iOS照片颠倒处理
if (in_array($extension, ['jpg', 'jpeg']) && function_exists('exif_read_data')) {
$data = imagecreatefromstring(file_get_contents($array['file']));
@@ -2450,7 +2521,9 @@ class Base
}
//生成缩略图
$array['thumb'] = $array['path'];
if ($param['autoThumb'] === "false") $param['autoThumb'] = false;
if ($extension === 'gif' && !isset($param['autoThumb'])) {
$param['autoThumb'] = false;
}
if ($param['autoThumb'] !== false) {
if (Base::imgThumb($array['file'], $array['file'] . "_thumb.jpg", 320, 0)) {
$array['thumb'] .= "_thumb.jpg";
@@ -2613,7 +2686,7 @@ class Base
try {
$white = imagecolorallocate($dst, 255, 255, 255);
imagefill($dst, 0, 0, $white);
} catch (Exception $e) {
} catch (\Throwable) {
}
if (function_exists('imagecopyresampled')) {
@@ -2798,36 +2871,34 @@ class Base
if (empty($str)) {
return '';
}
$fchar = ord($str[0]);
if ($fchar >= ord('A') && $fchar <= ord('z')) return strtoupper($str[0]);
$s1 = iconv('UTF-8', 'gb2312', $str);
$s2 = iconv('gb2312', 'UTF-8', $s1);
$s = $s2 == $str ? $s1 : $str;
$asc = ord($s[0]) * 256 + ord($s[1]) - 65536;
if ($asc >= -20319 && $asc <= -20284) return 'A';
if ($asc >= -20283 && $asc <= -19776) return 'B';
if ($asc >= -19775 && $asc <= -19219) return 'C';
if ($asc >= -19218 && $asc <= -18711) return 'D';
if ($asc >= -18710 && $asc <= -18527) return 'E';
if ($asc >= -18526 && $asc <= -18240) return 'F';
if ($asc >= -18239 && $asc <= -17923) return 'G';
if ($asc >= -17922 && $asc <= -17418) return 'H';
if ($asc >= -17417 && $asc <= -16475) return 'J';
if ($asc >= -16474 && $asc <= -16213) return 'K';
if ($asc >= -16212 && $asc <= -15641) return 'L';
if ($asc >= -15640 && $asc <= -15166) return 'M';
if ($asc >= -15165 && $asc <= -14923) return 'N';
if ($asc >= -14922 && $asc <= -14915) return 'O';
if ($asc >= -14914 && $asc <= -14631) return 'P';
if ($asc >= -14630 && $asc <= -14150) return 'Q';
if ($asc >= -14149 && $asc <= -14091) return 'R';
if ($asc >= -14090 && $asc <= -13319) return 'S';
if ($asc >= -13318 && $asc <= -12839) return 'T';
if ($asc >= -12838 && $asc <= -12557) return 'W';
if ($asc >= -12556 && $asc <= -11848) return 'X';
if ($asc >= -11847 && $asc <= -11056) return 'Y';
if ($asc >= -11055 && $asc <= -10247) return 'Z';
return '#';
$first = mb_substr($str, 0, 1);
if (preg_match("/^\d$/", $first)) {
return '#';
}
if (!preg_match("/^[a-zA-Z]$/", $first)) {
$pinyin = new Pinyin();
$first = $pinyin->abbr($first, '', PINYIN_NAME);
}
return $first ? strtoupper($first) : '#';
}
/**
* 中文转拼音
* @param $str
* @return string
*/
public static function cn2pinyin($str)
{
if (empty($str)) {
return '';
}
if (!preg_match("/^[a-zA-Z0-9_.]+$/", $str)) {
$str = Cache::rememberForever("cn2pinyin:" . md5($str), function() use ($str) {
$pinyin = new Pinyin();
return $pinyin->permalink($str, '');
});
}
return $str;
}
/**

View File

@@ -14,11 +14,12 @@ use PhpOffice\PhpSpreadsheet\Writer\Exception;
class BillExport implements WithHeadings, WithEvents, FromCollection, WithTitle, WithStrictNullComparison
{
public $title;
public $headings = [];
public $data = [];
public $typeLists = [];
public $typeNumber = 0;
protected $title;
protected $headings = [];
protected $data = [];
protected $typeLists = [];
protected $typeNumber = 0;
protected $styles = [];
public function __construct($title, array $data)
{
@@ -57,15 +58,19 @@ class BillExport implements WithHeadings, WithEvents, FromCollection, WithTitle,
return $this;
}
public function setStyles(array $styles)
{
$this->styles = $styles;
return $this;
}
public function store($fileName = '') {
if (empty($fileName)) {
$fileName = date("YmdHis") . '.xls';
}
try {
return Excel::store($this, $fileName);
} catch (Exception $e) {
return "导出错误:" . $e->getMessage();
} catch (\PhpOffice\PhpSpreadsheet\Exception $e) {
} catch (Exception|\PhpOffice\PhpSpreadsheet\Exception $e) {
return "导出错误:" . $e->getMessage();
}
}
@@ -76,9 +81,7 @@ class BillExport implements WithHeadings, WithEvents, FromCollection, WithTitle,
}
try {
return Excel::download($this, $fileName);
} catch (Exception $e) {
return "导出错误:" . $e->getMessage();
} catch (\PhpOffice\PhpSpreadsheet\Exception $e) {
} catch (Exception|\PhpOffice\PhpSpreadsheet\Exception $e) {
return "导出错误:" . $e->getMessage();
}
}
@@ -118,6 +121,11 @@ class BillExport implements WithHeadings, WithEvents, FromCollection, WithTitle,
{
return [
AfterSheet::Class => function (AfterSheet $event) {
if ($this->styles) {
foreach ($this->styles as $cell => $style) {
$event->sheet->getDelegate()->getStyle($cell)->applyFromArray($style);
}
}
$count = count($this->data);
foreach ($this->typeLists AS $cell => $typeList) {
if ($cell && $typeList) {

View File

@@ -0,0 +1,24 @@
<?php
namespace App\Module;
use Maatwebsite\Excel\Concerns\Exportable;
use Maatwebsite\Excel\Concerns\WithMultipleSheets;
class BillMultipleExport implements WithMultipleSheets
{
use Exportable;
protected $data = [];
public function __construct(array $data)
{
$this->data = $data;
}
public function sheets(): array
{
return $this->data;
}
}

View File

@@ -2,8 +2,6 @@
namespace App\Module;
use Exception;
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
class Ihttp
@@ -88,7 +86,7 @@ class Ihttp
return Base::retError($error);
} else {
if ($isGb2312) {
try { $data = iconv('GB2312', 'UTF-8', $data); }catch (Exception $e) { }
try { $data = iconv('GB2312', 'UTF-8', $data); }catch (\Throwable) { }
}
$response = self::ihttp_response_parse($data);
Base::addLog([
@@ -148,7 +146,7 @@ class Ihttp
$content .= fgets($fp, 512);
fclose($fp);
if ($isGb2312) {
try { $content = iconv('GB2312', 'UTF-8', $content); }catch (Exception $e) { }
try { $content = iconv('GB2312', 'UTF-8', $content); }catch (\Throwable) { }
}
$response = self::ihttp_response_parse($content, true);
Base::addLog([

390
app/Module/RandomColor.php Normal file
View File

@@ -0,0 +1,390 @@
<?php
/**
* RandomColor 1.0.5
*
* PHP port of David Merfield JavaScript randomColor
* https://github.com/davidmerfield/randomColor
*
*
* The MIT License (MIT)
*
* Copyright (c) 2014-2022 Damien "Mistic" Sorel
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
namespace App\Module;
class RandomColor
{
static public $dictionary = null;
private function __construct() {}
static public function one($options = array())
{
$h = self::_pickHue($options);
$s = self::_pickSaturation($h, $options);
$v = self::_pickBrightness($h, $s, $options);
return self::format(compact('h','s','v'), @$options['format']);
}
static public function many($count, $options = array())
{
$colors = array();
for ($i = 0; $i < $count; $i++)
{
$colors[] = self::one($options);
}
return $colors;
}
static public function format($hsv, $format='hex')
{
switch ($format)
{
case 'hsv':
return $hsv;
case 'hsl':
return self::hsv2hsl($hsv);
case 'hslCss':
$hsl = self::hsv2hsl($hsv);
return 'hsl(' . $hsl['h'] . ',' . $hsl['s'] . '%,' . $hsl['l'] . '%)';
case 'rgb':
return self::hsv2rgb($hsv);
case 'rgbCss':
return 'rgb(' . implode(',', self::hsv2rgb($hsv)) . ')';
case 'hex':
default:
return self::hsv2hex($hsv);
}
}
static private function _pickHue($options)
{
$range = self::_getHueRange($options);
if (empty($range))
{
return 0;
}
$hue = self::_rand($range, $options);
// Instead of storing red as two separate ranges,
// we group them, using negative numbers
if ($hue < 0)
{
$hue = 360 + $hue;
}
return $hue;
}
static private function _pickSaturation($h, $options)
{
if (@$options['hue'] === 'monochrome')
{
return 0;
}
if (@$options['luminosity'] === 'random')
{
return self::_rand(array(0, 100), $options);
}
$colorInfo = self::_getColorInfo($h);
$range = $colorInfo['s'];
switch (@$options['luminosity'])
{
case 'bright':
$range[0] = 55;
break;
case 'dark':
$range[0] = $range[1] - 10;
break;
case 'light':
$range[1] = 55;
break;
}
return self::_rand($range, $options);
}
static private function _pickBrightness($h, $s, $options)
{
if (@$options['luminosity'] === 'random')
{
$range = array(0, 100);
}
else
{
$range = array(
self::_getMinimumBrightness($h, $s),
100
);
switch (@$options['luminosity'])
{
case 'dark':
$range[1] = $range[0] + 20;
break;
case 'light':
$range[0] = round(($range[1] + $range[0]) / 2);
break;
}
}
return self::_rand($range, $options);
}
static private function _getHueRange($options)
{
$ranges = array();
if (isset($options['hue']))
{
if (!is_array($options['hue']))
{
$options['hue'] = array($options['hue']);
}
foreach ($options['hue'] as $hue)
{
if ($hue === 'random')
{
$ranges[] = array(0, 360);
}
else if (isset(self::$dictionary[$hue]))
{
$ranges[] = self::$dictionary[$hue]['h'];
}
else if (is_numeric($hue))
{
$hue = intval($hue);
if ($hue <= 360 && $hue >= 0)
{
$ranges[] = array($hue, $hue);
}
}
}
}
if (($l = count($ranges)) === 0)
{
return array(0, 360);
}
else if ($l === 1)
{
return $ranges[0];
}
else
{
return $ranges[self::_rand(array(0, $l-1), $options)];
}
}
static private function _getMinimumBrightness($h, $s)
{
$colorInfo = self::_getColorInfo($h);
$bounds = $colorInfo['bounds'];
for ($i = 0, $l = count($bounds); $i < $l - 1; $i++)
{
$s1 = $bounds[$i][0];
$v1 = $bounds[$i][1];
$s2 = $bounds[$i+1][0];
$v2 = $bounds[$i+1][1];
if ($s >= $s1 && $s <= $s2)
{
$m = ($v2 - $v1) / ($s2 - $s1);
$b = $v1 - $m * $s1;
return round($m * $s + $b);
}
}
return 0;
}
static private function _getColorInfo($h)
{
// Maps red colors to make picking hue easier
if ($h >= 334 && $h <= 360)
{
$h-= 360;
}
foreach (self::$dictionary as $color)
{
if ($color['h'] !== null && $h >= $color['h'][0] && $h <= $color['h'][1])
{
return $color;
}
}
}
static private function _rand($bounds, $options)
{
if (isset($options['prng']))
{
return $options['prng']($bounds[0], $bounds[1]);
}
else
{
return mt_rand($bounds[0], $bounds[1]);
}
}
static public function hsv2hex($hsv)
{
$rgb = self::hsv2rgb($hsv);
$hex = '#';
foreach ($rgb as $c)
{
$hex.= str_pad(dechex($c), 2, '0', STR_PAD_LEFT);
}
return $hex;
}
static public function hsv2hsl($hsv)
{
extract($hsv);
$s/= 100;
$v/= 100;
$k = (2-$s)*$v;
return array(
'h' => $h,
's' => round($s*$v / ($k < 1 ? $k : 2-$k), 4) * 100,
'l' => $k/2 * 100,
);
}
static public function hsv2rgb($hsv)
{
extract($hsv);
$h/= 360;
$s/= 100;
$v/= 100;
$i = floor($h * 6);
$f = $h * 6 - $i;
$m = $v * (1 - $s);
$n = $v * (1 - $s * $f);
$k = $v * (1 - $s * (1 - $f));
$r = 1;
$g = 1;
$b = 1;
switch ($i)
{
case 0:
list($r,$g,$b) = array($v,$k,$m);
break;
case 1:
list($r,$g,$b) = array($n,$v,$m);
break;
case 2:
list($r,$g,$b) = array($m,$v,$k);
break;
case 3:
list($r,$g,$b) = array($m,$n,$v);
break;
case 4:
list($r,$g,$b) = array($k,$m,$v);
break;
case 5:
case 6:
list($r,$g,$b) = array($v,$m,$n);
break;
}
return array(
'r' => floor($r*255),
'g' => floor($g*255),
'b' => floor($b*255),
);
}
}
/*
* h=hueRange
* s=saturationRange : bounds[0][0] ; bounds[-][0]
*/
RandomColor::$dictionary = array(
'monochrome' => array(
'bounds' => array(array(0,0), array(100,0)),
'h' => NULL,
's' => array(0,100)
),
'red' => array(
'bounds' => array(array(20,100),array(30,92),array(40,89),array(50,85),array(60,78),array(70,70),array(80,60),array(90,55),array(100,50)),
'h' => array(-26,18),
's' => array(20,100)
),
'orange' => array(
'bounds' => array(array(20,100),array(30,93),array(40,88),array(50,86),array(60,85),array(70,70),array(100,70)),
'h' => array(19,46),
's' => array(20,100)
),
'yellow' => array(
'bounds' => array(array(25,100),array(40,94),array(50,89),array(60,86),array(70,84),array(80,82),array(90,80),array(100,75)),
'h' => array(47,62),
's' => array(25,100)
),
'green' => array(
'bounds' => array(array(30,100),array(40,90),array(50,85),array(60,81),array(70,74),array(80,64),array(90,50),array(100,40)),
'h' => array(63,178),
's' => array(30,100)
),
'blue' => array(
'bounds' => array(array(20,100),array(30,86),array(40,80),array(50,74),array(60,60),array(70,52),array(80,44),array(90,39),array(100,35)),
'h' => array(179,257),
's' => array(20,100)
),
'purple' => array(
'bounds' => array(array(20,100),array(30,87),array(40,79),array(50,70),array(60,65),array(70,59),array(80,52),array(90,45),array(100,42)),
'h' => array(258,282),
's' => array(20,100)
),
'pink' => array(
'bounds' => array(array(20,100),array(30,90),array(40,86),array(60,84),array(80,80),array(90,75),array(100,73)),
'h' => array(283,334),
's' => array(20,100)
)
);

View File

@@ -69,7 +69,7 @@ class WebSocketService implements WebSocketHandlerInterface
$server->push($fd, Base::array2json([
'type' => 'error',
'data' => [
'error' => '员不存在'
'error' => '员不存在'
],
]));
$server->close($fd);
@@ -211,6 +211,12 @@ class WebSocketService implements WebSocketHandlerInterface
/** @var WebSocket $item */
foreach ($list as $item) {
$item->delete();
if ($item->userid) {
// 离线时更新会员最后在线时间
User::whereUserid($item->userid)->update([
'line_at' => Carbon::now()
]);
}
if ($item->path && str_starts_with($item->path, "/single/file/")) {
$array[$item->path] = $item->path;
}

View File

@@ -1,6 +1,9 @@
<?php
namespace App\Tasks;
use App\Models\TaskWorker;
use App\Module\Base;
use Carbon\Carbon;
use Hhxsv5\LaravelS\Swoole\Task\Task;
/**
@@ -9,27 +12,18 @@ use Hhxsv5\LaravelS\Swoole\Task\Task;
*/
abstract class AbstractTask extends Task
{
protected $newTask = [];
protected int $twid = 0;
/**
* 添加完成后执行的任务
* @param $task
*/
final protected function addTask($task)
public function __construct(...$params)
{
$this->newTask[] = $task;
}
/**
* 包装执行过程
*/
final public function handle()
{
try {
$this->start();
} catch (\Exception $e) {
$this->info($e);
$this->failed($e);
$row = TaskWorker::createInstance([
'args' => [
'params' => $params,
'class' => get_class($this)
],
]);
if ($row->save()) {
$this->twid = $row->id;
}
}
@@ -41,31 +35,53 @@ abstract class AbstractTask extends Task
/**
* 任务完成事件
*/
public function finish()
abstract public function end();
/**
* 重写执行过程
*/
final public function handle()
{
foreach ($this->newTask AS $task) {
Task::deliver($task);
TaskWorker::whereId($this->twid)->update(['start_at' => Carbon::now()]);
//
try {
$this->start();
} catch (\Throwable $e) {
$this->failed("start", $e);
}
}
/**
* 重写完成事件
*/
final public function finish()
{
TaskWorker::whereId($this->twid)->update(['end_at' => Carbon::now()]);
//
try {
$this->end();
TaskWorker::whereId($this->twid)->delete();
} catch (\Throwable $e) {
$this->failed("end", $e);
}
}
/**
* 任务失败事件
* @param $e
* @param string $type
* @param \Throwable $e
*/
public function failed($e)
public function failed(string $type, \Throwable $e)
{
info($type);
info($e);
//
}
/**
* 添加日志
* @param $var
*/
private function info($var)
{
if (!config('app.debug') || defined('DO_NOT_ADD_LOGS')) {
return;
}
info($var);
TaskWorker::whereId($this->twid)->update(['error' => Base::array2json([
'time' => Carbon::now(),
'type' => $type,
'code' => $e->getCode(),
'file' => $e->getFile(),
'message' => $e->getMessage(),
])]);
}
}

52
app/Tasks/AppPushTask.php Normal file
View File

@@ -0,0 +1,52 @@
<?php
namespace App\Tasks;
use App\Models\ProjectTask;
use App\Module\Base;
use Carbon\Carbon;
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
class AppPushTask extends AbstractTask
{
public function __construct()
{
parent::__construct();
}
public function start()
{
$setting = Base::setting('appPushSetting');
if ($setting['push'] !== 'open') {
return;
}
ProjectTask::whereNull("complete_at")
->whereNull("archived_at")
->whereBetween("end_at", [
Carbon::now()->addMinutes(60),
Carbon::now()->addMinutes(60 + 10)
])->chunkById(100, function ($tasks) {
/** @var ProjectTask $task */
foreach ($tasks as $task) {
$task->taskPush(null, 1);
}
});
ProjectTask::whereNull("complete_at")
->whereNull("archived_at")
->whereBetween("end_at", [
Carbon::now()->subMinutes(60 + 10),
Carbon::now()->subMinutes(60)
])->chunkById(100, function ($tasks) {
/** @var ProjectTask $task */
foreach ($tasks as $task) {
$task->taskPush(null, 2);
}
});
}
public function end()
{
}
}

View File

@@ -18,7 +18,7 @@ class AutoArchivedTask extends AbstractTask
public function __construct()
{
//
parent::__construct();
}
public function start()
@@ -42,4 +42,9 @@ class AutoArchivedTask extends AbstractTask
}
}
}
public function end()
{
}
}

View File

@@ -0,0 +1,272 @@
<?php
namespace App\Tasks;
use App\Models\User;
use App\Models\UserBot;
use App\Models\WebSocketDialog;
use App\Models\WebSocketDialogMsg;
use App\Module\Base;
use Carbon\Carbon;
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
/**
* 推送会话消息
* Class BotReceiveMsgTask
* @package App\Tasks
*/
class BotReceiveMsgTask extends AbstractTask
{
protected $userid;
protected $msgId;
public function __construct($userid, $msgId)
{
parent::__construct(...func_get_args());
$this->userid = $userid;
$this->msgId = $msgId;
}
public function start()
{
$botUser = User::whereUserid($this->userid)->whereBot(1)->first();
if (empty($botUser)) {
return;
}
$msg = WebSocketDialogMsg::find($this->msgId);
if (empty($msg)) {
return;
}
$msg->readSuccess($botUser->userid);
//
$dialog = WebSocketDialog::find($msg->dialog_id);
if (empty($dialog)) {
return;
}
if ($dialog->type !== 'user') {
return;
}
if ($botUser->email === 'bot-manager@bot.system') {
$this->botManagerReceive($msg);
}
}
public function end()
{
}
/**
* 机器人管理处理消息
* @param WebSocketDialogMsg $msg
* @return void
*/
private function botManagerReceive(WebSocketDialogMsg $msg)
{
if ($msg->type === 'text') {
$text = trim(strip_tags($msg->msg['text']));
if (empty($text)) {
return;
}
$array = Base::newTrim(explode(" ", "{$text} "));
$type = $array[0];
$data = [];
$notice = "";
switch ($type) {
/**
* 列表
*/
case '/list':
$data = User::select(['users.*'])
->join('user_bots', 'users.userid', '=', 'user_bots.bot_id')
->where('users.bot', 1)
->where('user_bots.userid', $msg->userid)
->take(50)
->orderByDesc('id')
->get();
if ($data->isEmpty()) {
$type = "notice";
$notice = "您没有创建机器人。";
}
break;
/**
* 创建
*/
case '/newbot':
if (User::select(['users.*'])
->join('user_bots', 'users.userid', '=', 'user_bots.bot_id')
->where('users.bot', 1)
->where('user_bots.userid', $msg->userid)
->count() >= 50) {
$type = "notice";
$notice = "超过最大创建数量。";
break;
}
if (strlen($array[1]) < 2 || strlen($array[1]) > 20) {
$type = "notice";
$notice = "机器人名称由2-20个字符组成。";
break;
}
$data = User::botGetOrCreate("user-" . Base::generatePassword(), [
'nickname' => $array[1]
], $msg->userid);
if (empty($data)) {
$type = "notice";
$notice = "创建失败。";
break;
}
$dialog = WebSocketDialog::checkUserDialog($data->userid, $msg->userid);
if ($dialog) {
$text = "你好,我是你的机器人:{$data->nickname}, 我的机器人ID是{$data->userid}";
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'text', ['text' => $text], $data->userid); // todo 未能在任务end事件来发送任务
}
break;
/**
* 修改名字
*/
case '/setname':
if (strlen($array[2]) < 2 || strlen($array[2]) > 20) {
$type = "notice";
$notice = "机器人名称由2-20个字符组成。";
break;
}
$data = $this->botManagerOne($array[1], $msg->userid);
if ($data) {
$data->nickname = $array[2];
$data->az = Base::getFirstCharter($array[2]);
$data->pinyin = Base::cn2pinyin($array[2]);
$data->save();
} else {
$type = "notice";
$notice = "机器人不存在。";
}
break;
/**
* 删除
*/
case '/deletebot':
$data = $this->botManagerOne($array[1], $msg->userid);
if ($data) {
$data->deleteUser('delete bot');
} else {
$type = "notice";
$notice = "机器人不存在。";
}
break;
/**
* 获取Token
*/
case '/token':
$data = $this->botManagerOne($array[1], $msg->userid);
if ($data) {
User::token($data);
} else {
$type = "notice";
$notice = "机器人不存在。";
}
break;
/**
* 更新Token
*/
case '/revoke':
$data = $this->botManagerOne($array[1], $msg->userid);
if ($data) {
$data->encrypt = Base::generatePassword(6);
$data->password = Base::md52(Base::generatePassword(32), $data->encrypt);
$data->save();
} else {
$type = "notice";
$notice = "机器人不存在。";
}
break;
/**
* 设置自动清理消息时间
*/
case '/clearday':
$data = $this->botManagerOne($array[1], $msg->userid);
if ($data) {
$userBot = UserBot::whereBotId($array[1])->whereUserid($msg->userid)->first();
if ($userBot) {
$userBot->clear_day = min(intval($array[2]) ?: 30, 999);
$userBot->clear_at = Carbon::now()->addDays($userBot->clear_day);
$userBot->save();
}
$data->clear_day = $userBot->clear_day;
$data->clear_at = $userBot->clear_at; // 这两个参数只是作为输出,所以不保存
} else {
$type = "notice";
$notice = "机器人不存在。";
}
break;
/**
* 会话搜索
*/
case '/dialog':
$data = $this->botManagerOne($array[1], $msg->userid);
if ($data) {
$list = WebSocketDialog::select(['web_socket_dialogs.*', 'u.top_at', 'u.mark_unread', 'u.silence'])
->join('web_socket_dialog_users as u', 'web_socket_dialogs.id', '=', 'u.dialog_id')
->where('web_socket_dialogs.name', 'LIKE', "%{$array[2]}%")
->where('u.userid', $data->userid)
->orderByDesc('u.top_at')
->orderByDesc('web_socket_dialogs.last_at')
->take(20)
->get();
if ($list->isEmpty()) {
$type = "notice";
$notice = "没有搜索到相关会话。";
} else {
$list->transform(function (WebSocketDialog $item) use ($data) {
return $item->formatData($data->userid);
});
$data->list = $list;
}
} else {
$type = "notice";
$notice = "机器人不存在。";
}
break;
}
//
$text = view('push.bot', [
'type' => $type,
'data' => $data,
'notice' => $notice,
'version' => Base::getVersion()
])->render();
$text = preg_replace("/^\x20+/", "", $text);
$text = preg_replace("/\n\x20+/", "\n", $text);
WebSocketDialogMsg::sendMsg(null, $msg->dialog_id, 'text', ['text' => $text], $this->userid, false, false, true); // todo 未能在任务end事件来发送任务
}
}
/**
* @param $botId
* @param $userid
* @return User
*/
private function botManagerOne($botId, $userid)
{
$botId = intval($botId);
$userid = intval($userid);
if ($botId > 0) {
return User::select(['users.*'])
->join('user_bots', 'users.userid', '=', 'user_bots.bot_id')
->where('users.bot', 1)
->where('user_bots.bot_id', $botId)
->where('user_bots.userid', $userid)
->first();
}
return null;
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace App\Tasks;
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
use App\Models\UserBot;
use App\Models\WebSocketDialogMsg;
use Carbon\Carbon;
/**
* 删除机器人消息
*/
class DeleteBotMsgTask extends AbstractTask
{
public function __construct()
{
parent::__construct();
}
public function start()
{
$bots = UserBot::where('clear_at', '<=', Carbon::now())->take(100)->get();
foreach ($bots as $bot) {
WebSocketDialogMsg::whereUserid($bot->bot_id)
->where('created_at', '<=', Carbon::now()->subDays($bot->clear_day))
->orderBy('id')
->chunk(1000, function ($msgs) {
$ids = $msgs->pluck('id')->toArray();
if ($ids) {
WebSocketDialogMsg::deleteMsgs($ids);
}
});
$bot->clear_at = Carbon::now()->addDays($bot->clear_day);
$bot->save();
}
}
public function end()
{
}
}

View File

@@ -2,6 +2,7 @@
namespace App\Tasks;
use App\Models\TaskWorker;
use App\Models\Tmp;
use App\Models\WebSocketTmpMsg;
use Carbon\Carbon;
@@ -18,6 +19,7 @@ class DeleteTmpTask extends AbstractTask
public function __construct(string $data, int $hours)
{
parent::__construct(...func_get_args());
$this->data = $data;
$this->hours = $hours;
}
@@ -54,6 +56,23 @@ class DeleteTmpTask extends AbstractTask
});
}
break;
/**
* 表pre_task_worker
*/
case 'task_worker':
{
TaskWorker::onlyTrashed()
->where('deleted_at', '<', Carbon::now()->subHours($this->hours)->toDateTimeString())
->orderBy('id')
->forceDelete();
}
break;
}
}
public function end()
{
}
}

View File

@@ -0,0 +1,170 @@
<?php
namespace App\Tasks;
use App\Models\Setting;
use App\Models\User;
use App\Models\WebSocketDialogMsg;
use App\Models\WebSocketDialogMsgRead;
use App\Module\Base;
use Carbon\Carbon;
use Guanguans\Notify\Factory;
use Guanguans\Notify\Messages\EmailMessage;
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
class EmailNoticeTask extends AbstractTask
{
public function __construct()
{
parent::__construct();
}
public function start()
{
$setting = Base::setting('emailSetting');
// 消息通知
if ($setting['notice_msg'] === 'open') {
$userMinute = intval($setting['msg_unread_user_minute']);
$groupMinute = intval($setting['msg_unread_group_minute']);
\DB::statement("SET SQL_MODE=''");
$builder = WebSocketDialogMsg::select(['web_socket_dialog_msgs.*', 'r.id as r_id', 'r.userid as r_userid'])
->join('web_socket_dialog_msg_reads as r', 'web_socket_dialog_msgs.id', '=', 'r.msg_id')
->whereNull("r.read_at")
->where("r.silence", 0)
->where("r.email", 0);
if ($userMinute > -1) {
$builder->clone()
->where("web_socket_dialog_msgs.dialog_type", "user")
->whereIn("web_socket_dialog_msgs.type", ["text", "file", "record", "meeting"])
->whereBetween("web_socket_dialog_msgs.created_at", [
Carbon::now()->subMinutes($userMinute + 10),
Carbon::now()->subMinutes($userMinute)
])
->groupBy('r_userid')
->chunkById(100, function ($rows) {
$this->unreadMsgEmail($rows, "user");
});
}
if ($groupMinute > -1) {
$builder->clone()
->where("web_socket_dialog_msgs.dialog_type", "group")
->whereIn("web_socket_dialog_msgs.type", ["text", "file", "record", "meeting"])
->whereBetween("web_socket_dialog_msgs.created_at", [
Carbon::now()->subMinutes($groupMinute + 10),
Carbon::now()->subMinutes($groupMinute)
])
->groupBy('r_userid')
->chunkById(100, function ($rows) {
$this->unreadMsgEmail($rows, "group");
});
}
}
}
public function end()
{
}
/**
* 未读消息通知
* @param $rows
* @param $dialogType
* @return void
*/
private function unreadMsgEmail($rows, $dialogType)
{
$array = $rows->groupBy('r_userid');
foreach ($array as $userid => $data) {
$data = WebSocketDialogMsg::select(['web_socket_dialog_msgs.*', 'r.id as r_id', 'r.userid as r_userid'])
->join('web_socket_dialog_msg_reads as r', 'web_socket_dialog_msgs.id', '=', 'r.msg_id')
->whereNull("r.read_at")
->where("r.silence", 0)
->where("r.email", 0)
->where("r.userid", $userid)
->where("web_socket_dialog_msgs.dialog_type", $dialogType)
->whereIn("web_socket_dialog_msgs.type", ["text", "file", "record", "meeting"])
->take(100)
->get();
if (empty($data)) {
continue;
}
$user = User::whereNull('disable_at')->find($userid);
if (empty($user)) {
continue;
}
if (!Base::isEmail($user->email)) {
continue;
}
$setting = Base::setting('emailSetting');
$msgType = $dialogType === "group" ? "群聊" : "成员";
$subject = null;
$content = view('email.unread', [
'type' => 'head',
'nickname' => $user->nickname,
'msgType' => $msgType,
'count' => count($data),
])->render();
$lists = $data->groupBy('dialog_id');
/** @var WebSocketDialogMsg[] $items */
foreach ($lists as $items) {
$dialogId = 0;
$dialogName = null;
foreach ($items as $item) {
$item->cancelAppend();
$item->userInfo = User::userid2basic($item->userid);
$item->preview = $item->previewMsg(true);
$item->preview = str_replace('<p>', '<p style="margin:0;padding:0">', $item->preview);
if (empty($dialogId)) {
$dialogId = $item->dialog_id;
}
if ($dialogName === null) {
if ($dialogType === "user" && $item->userInfo) {
if ($item->userInfo->profession) {
$dialogName = $item->userInfo->nickname . " ({$item->userInfo->profession})";
} else {
$dialogName = $item->userInfo->nickname;
}
} else {
$dialogName = $item->webSocketDialog?->getGroupName();
}
}
}
if ($subject === null) {
$count = count($lists);
if ($count > 1) {
$subject = "来自{$count}{$msgType}未读消息提醒";
} else {
$subject = "来自{$dialogName}未读消息提醒";
}
}
$content .= view('email.unread', [
'type' => 'content',
'dialogUrl' => config("app.url") . "/manage/messenger?dialog_id={$dialogId}",
'dialogName' => $dialogName,
'unread' => count($items),
'items' => $items,
])->render();
$content = str_replace("{{RemoteURL}}", config("app.url") . "/", $content);
}
try {
Setting::validateAddr($user->email, function($to) use ($content, $subject, $setting) {
Factory::mailer()
->setDsn("smtp://{$setting['account']}:{$setting['password']}@{$setting['smtp_server']}:{$setting['port']}?verify_peer=0")
->setMessage(EmailMessage::create()
->from(env('APP_NAME', 'Task') . " <{$setting['account']}>")
->to($to)
->subject($subject)
->html($content))
->send();
});
} catch (\Throwable $e) {
info("unreadMsgEmail: " . $e->getMessage());
}
WebSocketDialogMsgRead::whereIn('id', $data->pluck('r_id'))->update([
'email' => 1
]);
}
}
}

View File

@@ -18,6 +18,7 @@ class IhttpTask extends AbstractTask
protected $extra;
protected $apiWebsocket;
protected $apiUserid;
protected $endPush = [];
/**
* IhttpTask constructor.
@@ -27,6 +28,7 @@ class IhttpTask extends AbstractTask
*/
public function __construct($url, $post = [], $extra = [])
{
parent::__construct(...func_get_args());
$this->url = $url;
$this->post = $post;
$this->extra = $extra;
@@ -53,7 +55,7 @@ class IhttpTask extends AbstractTask
$res = Ihttp::ihttp_request($this->url, $this->post, $this->extra);
if ($this->apiWebsocket && $this->apiUserid) {
$data = Base::isSuccess($res) ? Base::json2array($res['data']) : $res;
PushTask::push([
$this->endPush[] = [
'userid' => $this->apiUserid,
'msg' => [
'type' => 'apiWebsocket',
@@ -61,7 +63,13 @@ class IhttpTask extends AbstractTask
'apiSuccess' => Base::isSuccess($res),
'data' => $data,
]
]);
];
}
}
public function end()
{
PushTask::push($this->endPush);
}
}

View File

@@ -0,0 +1,71 @@
<?php
namespace App\Tasks;
use App\Module\Base;
use App\Module\Ihttp;
use Cache;
use Carbon\Carbon;
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
/**
* 获取笑话、心灵鸡汤
*
* 在.env添加笑话 JUKE_KEY_JOKE
* 在.env添加鸡汤 JUKE_KEY_SOUP
*
* 每日小时采集1次
*/
class JokeSoupTask extends AbstractTask
{
public function __construct()
{
parent::__construct();
}
public function start()
{
// 判断每小时执行一次
if (Cache::get("JokeSoupTask:YmdH") == date("YmdH")) {
return;
}
Cache::put("JokeSoupTask:YmdH", date("YmdH"), Carbon::now()->addDay());
//
$jokeKey = env("JUKE_KEY_JOKE");
if ($jokeKey) {
$array = Base::json2array(Cache::get("JokeSoupTask:jokes"));
$res = Ihttp::ihttp_get("http://v.juhe.cn/joke/randJoke.php?key=" . $jokeKey);
if (Base::isSuccess($res)) {
$data = Base::json2array($res['data']);
if ($data['reason'] === 'success') {
foreach ($data['result'] as $item) {
if ($text = trim($item['content'])) {
$array[] = $text;
}
}
}
}
Cache::forever("JokeSoupTask:jokes", Base::array2json(array_slice($array, -100)));
}
//
$soupKey = env("JUKE_KEY_SOUP");
if ($soupKey) {
$array = Base::json2array(Cache::get("JokeSoupTask:soups"));
$res = Ihttp::ihttp_get("https://apis.juhe.cn/fapig/soup/query?key=" . $soupKey);
if (Base::isSuccess($res)) {
$data = Base::json2array($res['data']);
if ($data['reason'] === 'success' && $text = trim($data['result']['text'])) {
$array[] = $text;
}
}
Cache::forever("JokeSoupTask:soups", Base::array2json(array_slice($array, -24)));
}
}
public function end()
{
}
}

View File

@@ -16,6 +16,7 @@ class LineTask extends AbstractTask
{
protected $userid;
protected $online;
protected $endPush = [];
/**
* LineTask constructor.
@@ -24,6 +25,7 @@ class LineTask extends AbstractTask
*/
public function __construct($userid, bool $online)
{
parent::__construct(...func_get_args());
$this->userid = $userid;
$this->online = $online;
}
@@ -36,7 +38,7 @@ class LineTask extends AbstractTask
$fd[] = $ws->fd;
}
if ($fd) {
PushTask::push([
$this->endPush[] = [
'fd' => $fd,
'msg' => [
'type' => 'line',
@@ -45,8 +47,13 @@ class LineTask extends AbstractTask
'online' => $this->online,
],
]
]);
];
}
});
}
public function end()
{
PushTask::push($this->endPush);
}
}

84
app/Tasks/LoopTask.php Normal file
View File

@@ -0,0 +1,84 @@
<?php
namespace App\Tasks;
use App\Models\ProjectFlow;
use App\Models\ProjectFlowItem;
use App\Models\ProjectTask;
use App\Models\ProjectTaskUser;
use Carbon\Carbon;
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
/**
* 任务重复周期
*/
class LoopTask extends AbstractTask
{
public function __construct()
{
parent::__construct();
}
public function start()
{
ProjectTask::whereBetween('loop_at', [
Carbon::now()->subMinutes(10),
Carbon::now()
])->chunkById(100, function ($list) {
/** @var ProjectTask $item */
foreach ($list as $item) {
try {
$task = $item->copyTask();
// 工作流
$projectFlow = ProjectFlow::whereProjectId($task->project_id)->orderByDesc('id')->first();
if ($projectFlow) {
$projectFlowItem = ProjectFlowItem::whereFlowId($projectFlow->id)->orderBy('sort')->get();
// 赋一个开始状态
foreach ($projectFlowItem as $flowItem) {
if ($flowItem->status == 'start') {
$task->flow_item_id = $flowItem->id;
$task->flow_item_name = $flowItem->status . "|" . $flowItem->name;
if ($flowItem->userids) {
$userids = array_values(array_unique($flowItem->userids));
foreach ($userids as $uid) {
ProjectTaskUser::updateInsert([
'task_id' => $task->id,
'userid' => $uid,
], [
'project_id' => $task->project_id,
'task_pid' => $task->id,
'owner' => 1,
]);
}
}
break;
}
}
}
// 新任务时间、周期
if ($task->start_at) {
$diffSecond = Carbon::parse($task->start_at)->diffInSeconds(Carbon::parse($task->end_at), true);
$task->start_at = Carbon::parse($task->loop_at);
$task->end_at = $task->start_at->clone()->addSeconds($diffSecond);
}
$task->refreshLoop(true);
$task->addLog("创建任务来自周期任务ID{$item->id}", [], $task->userid);
// 清空旧周期
$item->loop = '';
$item->loop_at = null;
$item->save();
$item->addLog("已创建新的周期任务ID{$task->id},此任务关闭周期", [], $task->userid);
} catch (\Throwable $e) {
$item->addLog("生成重复任务失败:" . $e->getMessage(), [], $item->userid);
}
}
});
}
public function end()
{
}
}

View File

@@ -1,116 +0,0 @@
<?php
namespace App\Tasks;
use App\Models\ProjectTask;
use App\Models\ProjectTaskMailLog;
use App\Models\User;
use App\Module\Base;
use Carbon\Carbon;
use Guanguans\Notify\Factory;
use Guanguans\Notify\Messages\EmailMessage;
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
class OverdueRemindEmailTask extends AbstractTask
{
public function __construct()
{
//
}
public function start()
{
$setting = Base::setting('emailSetting');
if ($setting['notice'] === 'open') {
$hours = floatval($setting['task_remind_hours']);
$hours2 = floatval($setting['task_remind_hours2']);
if ($hours > 0) {
ProjectTask::whereNull('complete_at')
->whereNull('archived_at')
->whereBetween("end_at", [
Carbon::now()->addMinutes($hours * 60),
Carbon::now()->addMinutes($hours * 60 + 10)
])->chunkById(100, function ($tasks) {
/** @var ProjectTask $task */
foreach ($tasks as $task) {
$this->overdueBeforeAfterEmail($task, true);
}
});
}
if ($hours2 > 0) {
ProjectTask::whereNull('complete_at')
->whereNull('archived_at')
->whereBetween("end_at", [
Carbon::now()->subMinutes($hours2 * 60 + 10),
Carbon::now()->subMinutes($hours2 * 60)
])->chunkById(100, function ($tasks) {
/** @var ProjectTask $task */
foreach ($tasks as $task) {
$this->overdueBeforeAfterEmail($task, false);
}
});
}
}
}
/**
* 过期前、超期后提醒
* @param ProjectTask $task
* @param $isBefore
* @return void
*/
private function overdueBeforeAfterEmail(ProjectTask $task, $isBefore)
{
$userids = $task->taskUser->where('owner', 1)->pluck('userid')->toArray();
if (empty($userids)) {
return;
}
$users = User::whereIn('userid', $userids)->get();
if (empty($users)) {
return;
}
$setting = Base::setting('emailSetting');
$hours = floatval($setting['task_remind_hours']);
$hours2 = floatval($setting['task_remind_hours2']);
/** @var User $user */
foreach ($users as $user) {
$data = [
'type' => $isBefore ? 1 : 2,
'userid' => $user->userid,
'task_id' => $task->id,
];
$emailLog = ProjectTaskMailLog::where($data)->first();
if ($emailLog) {
continue;
}
try {
if (!Base::isEmail($user->email)) {
throw new \Exception("User email '{$user->email}' address error");
}
if ($isBefore) {
$subject = env('APP_NAME') . " 任务提醒";
$content = "<p>{$user->nickname} 您好:</p><p>您有一个任务【{$task->name}】还有{$hours}小时即将超时,请及时处理。</p>";
} else {
$subject = env('APP_NAME') . " 任务过期提醒";
$content = "<p>{$user->nickname} 您好:</p><p>您的任务【{$task->name}】已经超时{$hours2}小时,请及时处理。</p>";
}
Factory::mailer()
->setDsn("smtp://{$setting['account']}:{$setting['password']}@{$setting['smtp_server']}:{$setting['port']}?verify_peer=0")
->setMessage(EmailMessage::create()
->from(env('APP_NAME', 'Task') . " <{$setting['account']}>")
->to($user->email)
->subject($subject)
->html($content))
->send();
$data['is_send'] = 1;
} catch (\Exception $e) {
$data['send_error'] = $e->getMessage();
}
$data['email'] = $user->email;
ProjectTaskMailLog::createInstance($data)->save();
}
}
}

View File

@@ -19,6 +19,7 @@ class PushTask extends AbstractTask
{
protected $params;
protected $retryOffline = true;
protected $endPush = [];
/**
* PushTask constructor.
@@ -26,6 +27,7 @@ class PushTask extends AbstractTask
*/
public function __construct($params = [], $retryOffline = true)
{
parent::__construct(...func_get_args());
$this->params = $params;
$this->retryOffline = $retryOffline;
}
@@ -42,12 +44,41 @@ class PushTask extends AbstractTask
}
// 根据会员ID推送离线时收到的消息
elseif (Base::leftExists($this->params, "RETRY::")) {
self::sendTmpMsgForUserid(intval(Base::leftDelete($this->params, "RETRY::")));
$this->sendTmpMsgForUserid(intval(Base::leftDelete($this->params, "RETRY::")));
}
}
is_array($this->params) && self::push($this->params, $this->retryOffline);
}
public function end()
{
self::push($this->endPush);
}
/**
* 根据会员ID推送离线时收到的消息
* @param $userid
*/
private function sendTmpMsgForUserid($userid)
{
if (empty($userid)) {
return;
}
WebSocketTmpMsg::whereCreateId($userid)
->whereSend(0)
->where('created_at', '>', Carbon::now()->subMinute()) // 1分钟内添加的数据
->orderBy('id')
->chunk(100, function($list) use ($userid) {
foreach ($list as $item) {
$this->endPush[] = [
'tmpMsgId' => $item->id,
'userid' => $userid,
'msg' => Base::json2array($item->msg),
];
}
});
}
/**
* 记录离线消息,等上线后重新发送
* @param array $userFail
@@ -71,30 +102,6 @@ class PushTask extends AbstractTask
}
}
/**
* 根据会员ID推送离线时收到的消息
* @param $userid
*/
private static function sendTmpMsgForUserid($userid)
{
if (empty($userid)) {
return;
}
WebSocketTmpMsg::whereCreateId($userid)
->whereSend(0)
->where('created_at', '>', Carbon::now()->subMinute()) // 1分钟内添加的数据
->orderBy('id')
->chunk(100, function($list) use ($userid) {
foreach ($list as $item) {
self::push([
'tmpMsgId' => $item->id,
'userid' => $userid,
'msg' => Base::json2array($item->msg),
]);
}
});
}
/**
* 推送消息
* @param array $lists 消息列表
@@ -172,7 +179,7 @@ class PushTask extends AbstractTask
try {
$swoole->push($fid, Base::array2json($msg));
$tmpMsgId > 0 && WebSocketTmpMsg::whereId($tmpMsgId)->update(['send' => 1]);
} catch (\Exception $e) {
} catch (\Throwable) {
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace App\Tasks;
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
use App\Models\UmengAlias;
use App\Module\Base;
/**
* 推送友盟消息
*/
class PushUmengMsg extends AbstractTask
{
protected $userid = 0;
protected $array = [];
/**
* @param array|int $userid
* @param array $array
*/
public function __construct($userid, $array = [])
{
parent::__construct(...func_get_args());
$this->userid = $userid;
$this->array = is_array($array) ? $array : [];
}
public function start()
{
if (empty($this->userid) || empty($this->array)) {
return;
}
$setting = Base::setting('appPushSetting');
if ($setting['push'] !== 'open') {
return;
}
UmengAlias::pushMsgToUserid($this->userid, $this->array);
}
public function end()
{
}
}

View File

@@ -8,11 +8,13 @@ use App\Models\User;
use App\Models\WebSocketDialog;
use App\Models\WebSocketDialogMsg;
use App\Models\WebSocketDialogMsgRead;
use App\Module\Base;
use Hhxsv5\LaravelS\Swoole\Task\Task;
use Request;
/**
* 推送话消息
* 推送话消息
* Class WebSocketDialogMsgTask
* @package App\Tasks
*/
@@ -20,15 +22,45 @@ class WebSocketDialogMsgTask extends AbstractTask
{
protected $id;
protected $ignoreFd;
protected $msgNotExistRetry = false; // 推送失败后重试
protected $silence = false; // 静默推送前端不通知、App不推送如果会话设置了免打扰则强制静默
protected $endPush = [];
protected $endArray = [];
/**
* WebSocketDialogMsgTask constructor.
* @param int $id 消息ID
* @param mixed $ignoreFd
*/
public function __construct($id)
public function __construct($id, $ignoreFd = null)
{
parent::__construct(...func_get_args());
$this->id = $id;
$this->ignoreFd = Request::header('fd');
$this->ignoreFd = $ignoreFd === null ? Request::header('fd') : $ignoreFd;
}
/**
* @param $ignoreFd
*/
public function setIgnoreFd($ignoreFd)
{
$this->ignoreFd = $ignoreFd;
}
/**
* @param bool $msgNotExistRetry
*/
public function setMsgNotExistRetry(bool $msgNotExistRetry): void
{
$this->msgNotExistRetry = $msgNotExistRetry;
}
/**
* @param bool $silence
*/
public function setSilence(bool $silence): void
{
$this->silence = $silence;
}
public function start()
@@ -37,48 +69,113 @@ class WebSocketDialogMsgTask extends AbstractTask
$_A = [
'__fill_url_remote_url' => true,
];
//
$msg = WebSocketDialogMsg::find($this->id);
if (empty($msg)) {
if ($this->msgNotExistRetry) {
$task = new WebSocketDialogMsgTask($this->id, $this->ignoreFd || '');
$task->delay(1);
$this->endArray[] = $task;
}
return;
}
$dialog = WebSocketDialog::find($msg->dialog_id);
if (empty($dialog)) {
return;
}
$silences = $dialog->dialogUser->pluck('silence', 'userid')->toArray();
$userids = array_keys($silences);
// 推送目标①:群成
$array = [];
$userids = $dialog->dialogUser->pluck('userid')->toArray();
foreach ($userids AS $userid) {
if ($userid == $msg->userid) {
continue;
// 提及会
$mentions = [];
if ($msg->type === 'text') {
preg_match_all("/<span class=\"mention user\" data-id=\"(\d+)\">/", $msg->msg['text'], $matchs);
if ($matchs) {
$mentions = array_values(array_filter(array_unique($matchs[1])));
}
}
// 将会话以外的成员加入会话内
$diffids = array_values(array_diff($mentions, $userids));
if ($diffids) {
// 仅(群聊)且(是群主或没有群主)才可以@成员以外的人
if ($dialog->type === 'group' && in_array($dialog->owner_id, [0, $msg->userid])) {
$dialog->joinGroup($diffids, $msg->userid);
$dialog->pushMsg("groupJoin", null, $diffids);
$userids = array_values(array_unique(array_merge($mentions, $userids)));
}
}
// 推送目标①:会话成员/群成员
$array = [];
foreach ($userids AS $userid) {
$silence = $this->silence || $silences[$userid];
if ($userid == $msg->userid) {
$array[$userid] = [
'userid' => $userid,
'silence' => $silence,
'mention' => false,
];
} else {
$mention = array_intersect([0, $userid], $mentions) ? 1 : 0;
WebSocketDialogMsgRead::createInstance([
'dialog_id' => $msg->dialog_id,
'msg_id' => $msg->id,
'userid' => $userid,
'mention' => $mention,
'silence' => $silence,
])->saveOrIgnore();
$array[$userid] = [
'userid' => $userid,
'silence' => $silence,
'mention' => $mention,
];
// 机器人收到消处理
$botUser = User::whereUserid($userid)->whereBot(1)->first();
if ($botUser) {
$this->endArray[] = new BotReceiveMsgTask($botUser->userid, $msg->id);
}
}
$mention = preg_match("/<span class=\"mention user\" data-id=\"[0|{$userid}]\">/", $msg->type === 'text' ? $msg->msg['text'] : '');
WebSocketDialogMsgRead::createInstance([
'dialog_id' => $msg->dialog_id,
'msg_id' => $msg->id,
'userid' => $userid,
'mention' => $mention,
])->saveOrIgnore();
$array[$userid] = $mention;
}
// 更新已发送数量
$msg->send = WebSocketDialogMsgRead::whereMsgId($msg->id)->count();
$msg->save();
// 开始推送消息
foreach ($array as $userid => $mention) {
PushTask::push([
'userid' => $userid,
$umengUserid = [];
foreach ($array as $item) {
$this->endPush[] = [
'userid' => $item['userid'],
'ignoreFd' => $this->ignoreFd,
'msg' => [
'type' => 'dialog',
'mode' => 'add',
'silence' => $item['silence'] ? 1 : 0,
'data' => array_merge($msg->toArray(), [
'mention' => $mention,
'mention' => $item['mention'],
]),
]
]);
];
if ($item['userid'] != $msg->userid && !$item['silence'] && !$this->silence) {
$umengUserid[] = $item['userid'];
}
}
// umeng推送app
if ($umengUserid) {
$setting = Base::setting('appPushSetting');
if ($setting['push'] === 'open') {
$umengTitle = User::userid2nickname($msg->userid);
if ($dialog->type == 'group') {
$umengTitle = "{$dialog->getGroupName()} ($umengTitle)";
}
$this->endArray[] = new PushUmengMsg($umengUserid, [
'title' => $umengTitle,
'body' => $msg->previewMsg(),
'description' => "MID:{$msg->id}",
'seconds' => 3600,
'badge' => 1,
]);
}
}
// 推送目标②:正在打开这个任务会话的会员
@@ -86,23 +183,32 @@ class WebSocketDialogMsgTask extends AbstractTask
$list = User::whereTaskDialogId($dialog->id)->pluck('userid')->toArray();
if ($list) {
$array = [];
foreach ($list as $uid) {
if (!in_array($uid, $userids)) {
$array[] = $uid;
foreach ($list as $item) {
if (!in_array($item, $userids)) {
$array[] = $item;
}
}
if ($array) {
PushTask::push([
$this->endPush[] = [
'userid' => $array,
'ignoreFd' => $this->ignoreFd,
'msg' => [
'type' => 'dialog',
'mode' => 'chat',
'silence' => $this->silence ? 1 : 0,
'data' => $msg->toArray(),
]
]);
];
}
}
}
}
public function end()
{
foreach ($this->endArray as $task) {
Task::deliver($task);
}
PushTask::push($this->endPush);
}
}

60
cliff.toml Normal file
View File

@@ -0,0 +1,60 @@
# configuration file for git-cliff (0.1.0)
[changelog]
# changelog header
header = """
# Changelog\n
All notable changes to this project will be documented in this file.\n
"""
# template for the changelog body
# https://tera.netlify.app/docs/#introduction
body = """
{% if version %}\
## [{{ version | trim_start_matches(pat="v") }}]
{% else %}\
## [Unreleased]
{% endif %}\
{% for group, commits in commits | group_by(attribute="group") %}
### {{ group | upper_first }}
{% for commit in commits %}
- {% if commit.breaking %}[**breaking**] {% endif %}{{ commit.message | upper_first }}\
{% endfor %}
{% endfor %}\n
"""
# remove the leading and trailing whitespace from the template
trim = true
# changelog footer
footer = """
"""
[git]
# parse the commits based on https://www.conventionalcommits.org
conventional_commits = true
# filter out the commits that are not conventional
filter_unconventional = true
# regex for parsing and grouping commits
commit_parsers = [
{ message = "^feat", group = "Features"},
{ message = "^fix", group = "Bug Fixes"},
{ message = "^doc", group = "Documentation"},
{ message = "^perf", group = "Performance"},
{ message = "^pref", group = "Performance"},
{ message = "^refactor", group = "Refactor"},
{ message = "^style", group = "Styling"},
{ message = "^test", group = "Testing"},
{ message = "^chore\\(release\\): prepare for", skip = true},
{ message = "^chore", group = "Miscellaneous Tasks"},
{ body = ".*security", group = "Security"},
]
# filter out the commits that are not matched by commit parsers
filter_commits = true
# glob pattern for matching git tags
tag_pattern = "v[0-9]*"
# regex for skipping tags
skip_tags = "v0.1.0-beta.1"
# regex for ignoring tags
ignore_tags = ""
# sort the tags chronologically
date_order = false
# sort the commits inside sections by oldest/newest order
sort_commits = "oldest"

45
cmd
View File

@@ -84,6 +84,15 @@ docker_name() {
echo `$COMPOSE ps | awk '{print $1}' | grep "\-$1\-"`
}
mix_manifest() {
local file=$1
if [[ `uname` == 'Linux' ]]; then
sed -i '/\"\/uploads/d' ${cur_path}/$file/mix-manifest.json
else
docker run -it --rm -v ${cur_path}/$file:/public alpine sh -c "sed -i '/\"\/uploads/d' /public/mix-manifest.json"
fi
}
run_compile() {
local type=$1
check_node
@@ -96,6 +105,7 @@ run_compile() {
if [ "$type" = "prod" ]; then
rm -rf "./public/js/build"
npx mix --production
mix_manifest "public"
echo "$(rand_string 16)" > ./public/js/hash
else
npx mix watch --hot
@@ -125,6 +135,7 @@ run_electron() {
#
if [ "$argv" != "dev" ] && [ "$argv" != "--nobuild" ]; then
npx mix --production -- --env --electron
mix_manifest "electron/public"
fi
if [ "$argv" == "dev" ]; then
run_exec php "php bin/run --mode=$argv"
@@ -243,15 +254,6 @@ arg_get() {
echo $value
}
is_arm() {
local get_arch=`arch`
if [[ $get_arch =~ "aarch" ]] || [[ $get_arch =~ "arm" ]]; then
echo "yes"
else
echo "no"
fi
}
####################################################################################
####################################################################################
####################################################################################
@@ -264,11 +266,6 @@ fi
if [ $# -gt 0 ]; then
if [[ "$1" == "init" ]] || [[ "$1" == "install" ]]; then
shift 1
# 判断架构
if [[ "$(is_arm)" == "yes" ]] && [[ -z "$(arg_get force)" ]]; then
echo -e "${Error} ${RedBG}暂不支持arm架构强制安装请使用./cmd install --force${Font}"
exit 1
fi
# 初始化文件
if [[ -n "$(arg_get relock)" ]]; then
rm -rf node_modules
@@ -331,8 +328,8 @@ if [ $# -gt 0 ]; then
$COMPOSE up -d
elif [[ "$1" == "uninstall" ]]; then
shift 1
read -rp "确定要卸载(含:删除容器、数据库、日志)吗?(y/n): " uninstall
[[ -z ${uninstall} ]] && uninstall="N"
read -rp "确定要卸载(含:删除容器、数据库、日志)吗?(Y/n): " uninstall
[[ -z ${uninstall} ]] && uninstall="Y"
case $uninstall in
[yY][eE][sS] | [yY])
echo -e "${RedBG} 开始卸载... ${Font}"
@@ -358,6 +355,18 @@ if [ $# -gt 0 ]; then
$COMPOSE up -d
echo -e "${OK} ${GreenBG} 修改成功 ${Font}"
echo -e "地址: http://${GreenBG}127.0.0.1:$(env_get APP_PORT)${Font}"
elif [[ "$1" == "url" ]]; then
shift 1
env_set APP_URL "$1"
supervisorctl_restart php
echo -e "${OK} ${GreenBG} 修改成功 ${Font}"
elif [[ "$1" == "env" ]]; then
shift 1
if [ -n "$1" ]; then
env_set $1 "$2"
fi
supervisorctl_restart php
echo -e "${OK} ${GreenBG} 修改成功 ${Font}"
elif [[ "$1" == "repassword" ]]; then
shift 1
run_exec mariadb "sh /etc/mysql/repassword.sh \"$@\""
@@ -367,6 +376,9 @@ if [ $# -gt 0 ]; then
elif [[ "$1" == "prod" ]] || [[ "$1" == "production" ]]; then
shift 1
run_compile prod
elif [[ "$1" == "appbuild" ]] || [[ "$1" == "buildapp" ]]; then
shift 1
run_electron app $@
elif [[ "$1" == "electron" ]]; then
shift 1
run_electron $@
@@ -422,6 +434,7 @@ if [ $# -gt 0 ]; then
e="supervisorctl $@" && run_exec php "$e"
elif [[ "$1" == "models" ]]; then
shift 1
run_exec php "php app/Models/clearHelper.php"
run_exec php "php artisan ide-helper:models -W"
elif [[ "$1" == "test" ]]; then
shift 1

View File

@@ -14,17 +14,22 @@
"ext-json": "*",
"ext-libxml": "*",
"ext-simplexml": "*",
"directorytree/ldaprecord-laravel": "^2.7",
"fideloper/proxy": "^4.4.1",
"fruitcake/laravel-cors": "^2.0.4",
"guanguans/notify": "^1.20",
"guanguans/notify": "^1.21.1",
"guzzlehttp/guzzle": "^7.3.0",
"laravel/framework": "^v8.48.1",
"hedeqiang/umeng": "^2.1",
"laravel/framework": "^v8.83.27",
"laravel/tinker": "^v2.6.1",
"lasserafn/php-initial-avatar-generator": "^4.2",
"maatwebsite/excel": "^3.1.31",
"madnest/madzipper": "^v1.1.0",
"mews/captcha": "^3.2.6",
"orangehill/iseed": "^3.0.1",
"predis/predis": "^1.1.7"
"overtrue/pinyin": "^4.0",
"predis/predis": "^1.1.7",
"symfony/mailer": "^6.0"
},
"require-dev": {
"barryvdh/laravel-ide-helper": "^v2.10.0",

2423
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -300,9 +300,9 @@ return [
'log_level' => 4,
'log_file' => storage_path(sprintf('logs/swoole-%s.log', date('Y-m'))),
'document_root' => base_path('public'),
'buffer_output_size' => 512 * 1024 * 1024,
'socket_buffer_size' => 512 * 1024 * 1024,
'package_max_length' => 512 * 1024 * 1024,
'buffer_output_size' => 2 * 1024 * 1024,
'socket_buffer_size' => 8 * 1024 * 1024,
'package_max_length' => 1024 * 1024 * 1024, // 1GB
'reload_async' => true,
'max_wait_time' => 60,
'enable_reuse_port' => true,

73
config/ldap.php Normal file
View File

@@ -0,0 +1,73 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default LDAP Connection Name
|--------------------------------------------------------------------------
|
| Here you may specify which of the LDAP connections below you wish
| to use as your default connection for all LDAP operations. Of
| course you may add as many connections you'd like below.
|
*/
'default' => env('LDAP_CONNECTION', 'default'),
/*
|--------------------------------------------------------------------------
| LDAP Connections
|--------------------------------------------------------------------------
|
| Below you may configure each LDAP connection your application requires
| access to. Be sure to include a valid base DN - otherwise you may
| not receive any results when performing LDAP search operations.
|
*/
'connections' => [
'default' => [
'hosts' => [env('LDAP_HOST', '127.0.0.1')],
'username' => env('LDAP_USERNAME', 'cn=user,dc=local,dc=com'),
'password' => env('LDAP_PASSWORD', 'secret'),
'port' => env('LDAP_PORT', 389),
'base_dn' => env('LDAP_BASE_DN', 'dc=local,dc=com'),
'timeout' => env('LDAP_TIMEOUT', 5),
'use_ssl' => env('LDAP_SSL', false),
'use_tls' => env('LDAP_TLS', false),
],
],
/*
|--------------------------------------------------------------------------
| LDAP Logging
|--------------------------------------------------------------------------
|
| When LDAP logging is enabled, all LDAP search and authentication
| operations are logged using the default application logging
| driver. This can assist in debugging issues and more.
|
*/
'logging' => (bool) env('APP_DEBUG', false),
/*
|--------------------------------------------------------------------------
| LDAP Cache
|--------------------------------------------------------------------------
|
| LDAP caching enables the ability of caching search results using the
| query builder. This is great for running expensive operations that
| may take many seconds to complete, such as a pagination request.
|
*/
'cache' => [
'enabled' => env('LDAP_CACHE', false),
'driver' => env('CACHE_DRIVER', 'file'),
],
];

View File

@@ -20,7 +20,7 @@ class CreateWebSocketDialogMsgReadsTable extends Migration
$table->bigInteger('userid')->nullable()->default(0)->comment('发送会员ID');
$table->tinyInteger('after')->nullable()->default(0)->comment('在阅读之后才添加的记录');
$table->timestamp('read_at')->nullable()->comment('阅读时间');
$table->unique(['msg_id', 'userid'], 'IDEX_msg_id_userid');
$table->unique(['msg_id', 'userid']);
});
}

View File

@@ -23,7 +23,7 @@ class CreateReportsTable extends Migration
$table->enum("type", ["weekly", "daily"])->default("daily")->comment("汇报类型");
$table->unsignedBigInteger("userid")->default(0);
$table->longText("content")->nullable();
$table->index(["userid", "created_at"], "default");
$table->index(["userid", "created_at"]);
});
}

View File

@@ -22,7 +22,7 @@ class CreateReportReceivesTable extends Migration
$table->timestamp("receive_time")->nullable()->comment("接收时间");
$table->unsignedBigInteger("userid")->default(0)->comment("接收人");
$table->unsignedTinyInteger("read")->default(0)->comment("是否已读");
$table->index(["userid", "receive_time"], "default");
$table->index(["userid", "receive_time"]);
});
}

View File

@@ -18,7 +18,7 @@ class CreateProjectTaskMailLogsTable extends Migration
$table->bigInteger('userid')->nullable()->default(0)->comment('用户id');
$table->integer('task_id')->nullable()->default(0)->comment('任务id');
$table->string('email')->nullable()->default('')->comment('电子邮箱');
$table->tinyInteger('type')->nullable()->default(0)->comment('提醒类型:1第一次任务提醒2第二次任务超期提醒');
$table->tinyInteger('type')->nullable()->default(0)->comment('提醒类型:0 任务开始提醒1 距离到期提醒2到期超时提醒');
$table->tinyInteger('is_send')->nullable()->default(0)->comment('邮件发送是否成功0否1是');
$table->timestamps();
});

View File

@@ -25,7 +25,7 @@ class AddFilesPids extends Migration
\App\Models\File::where('pid', '>', 0)->chunkById(100, function ($lists) {
/** @var \App\Models\File $item */
foreach ($lists as $item) {
$item->saveBeforePids();
$item->saveBeforePP();
}
});
\App\Models\File::whereShare(0)->chunkById(100, function ($lists) {

View File

@@ -0,0 +1,45 @@
<?php
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
use App\Models\ProjectLog;
use App\Module\Base;
use Illuminate\Database\Migrations\Migration;
class ProjectLogsRecordUserid extends Migration
{
/**
* 处理日志格式
*
* @return void
*/
public function up()
{
ProjectLog::whereIn('detail', [
'删除任务负责人',
'删除子任务负责人',
'删除任务协助人员',
'删除子任务协助人员',
])->chunkById(100, function ($lists) {
/** @var ProjectLog $log */
foreach ($lists as $log) {
$record = $log->record;
if (is_string($record['userid']) && str_contains($record['userid'], ",")) {
$record['userid'] = Base::explodeInt($record['userid']);
$log->record = Base::array2json($record);
$log->save();
}
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}

View File

@@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddWebSocketDialogMsgsDialogType extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('web_socket_dialog_msgs', function (Blueprint $table) {
if (!Schema::hasColumn('web_socket_dialog_msgs', 'dialog_type')) {
$table->string('dialog_type', 50)->nullable()->default('')->after('dialog_id')->comment('对话类型');
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('web_socket_dialog_msgs', function (Blueprint $table) {
$table->dropColumn("dialog_type");
});
}
}

View File

@@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddWebSocketDialogMsgReadsEmail extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('web_socket_dialog_msg_reads', function (Blueprint $table) {
if (!Schema::hasColumn('web_socket_dialog_msg_reads', 'email')) {
$table->boolean('email')->default(0)->after('mention')->nullable()->comment('是否发了邮件');
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('web_socket_dialog_msg_reads', function (Blueprint $table) {
$table->dropColumn("email");
});
}
}

View File

@@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateUmengAliasTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('umeng_alias', function (Blueprint $table) {
$table->bigIncrements('id');
$table->bigInteger('userid')->nullable()->default(0)->comment('会员ID');
$table->string('alias', 50)->nullable()->default('')->comment('别名');
$table->string('platform', 50)->nullable()->default('')->comment('平台类型');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('umeng_alias');
}
}

View File

@@ -0,0 +1,48 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddProjectsPersonal extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
$isAdd = false;
Schema::table('projects', function (Blueprint $table) use (&$isAdd) {
if (!Schema::hasColumn('projects', 'personal')) {
$isAdd = true;
$table->boolean('personal')->default(0)->after('userid')->nullable()->comment('是否个人项目');
}
});
if ($isAdd) {
// 更新数据
\App\Models\Project::whereName('个人项目')->chunkById(100, function ($lists) {
/** @var \App\Models\Project $item */
foreach ($lists as $item) {
if ($item->desc == '注册时系统自动创建项目,你可以自由删除。') {
$item->personal = 1;
$item->save();
}
}
});
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('projects', function (Blueprint $table) {
$table->dropColumn("personal");
});
}
}

View File

@@ -0,0 +1,46 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddProjectsUserSimple extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
$isAdd = false;
Schema::table('projects', function (Blueprint $table) use (&$isAdd) {
if (!Schema::hasColumn('projects', 'user_simple')) {
$isAdd = true;
$table->string('user_simple', 255)->nullable()->default('')->after('personal')->comment('成员总数|1,2,3');
}
});
if ($isAdd) {
\App\Models\Project::chunkById(100, function ($lists) {
/** @var \App\Models\Project $item */
foreach ($lists as $item) {
$array = $item->projectUser->pluck('userid')->toArray();
$item->user_simple = count($array) . "|" . implode(",", array_slice($array, 0, 3));
$item->save();
}
});
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('projects', function (Blueprint $table) {
$table->dropColumn("user_simple");
});
}
}

View File

@@ -0,0 +1,37 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateMeetingsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('meetings', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('meetingid')->nullable()->default('')->unique()->comment('会议ID不是数字');
$table->string('name')->nullable()->default('')->comment('会议主题');
$table->string('channel')->nullable()->default('')->comment('频道');
$table->bigInteger('userid')->nullable()->default(0)->comment('创建人');
$table->timestamps();
$table->timestamp('end_at')->nullable();
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('meetings');
}
}

View File

@@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddWebSocketDialogMsgsEmoji extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('web_socket_dialog_msgs', function (Blueprint $table) {
if (!Schema::hasColumn('web_socket_dialog_msgs', 'emoji')) {
$table->longText('emoji')->after('msg')->nullable()->comment('emoji回复');
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('web_socket_dialog_msgs', function (Blueprint $table) {
$table->dropColumn("emoji");
});
}
}

View File

@@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class WebSocketDialogMsgsAddReplyId extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('web_socket_dialog_msgs', function (Blueprint $table) {
if (!Schema::hasColumn('web_socket_dialog_msgs', 'reply_id')) {
$table->bigInteger('reply_id')->nullable()->default(0)->after('send')->comment('回复ID');
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('web_socket_dialog_msgs', function (Blueprint $table) {
$table->dropColumn("reply_id");
});
}
}

View File

@@ -0,0 +1,50 @@
<?php
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddWebSocketDialogMsgsKey extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
$isAdd = false;
Schema::table('web_socket_dialog_msgs', function (Blueprint $table) use (&$isAdd) {
if (!Schema::hasColumn('web_socket_dialog_msgs', 'key')) {
$isAdd = true;
$table->text('key')->after('emoji')->nullable()->default('')->comment('搜索关键词');
}
});
if ($isAdd) {
\App\Models\WebSocketDialogMsg::chunkById(100, function ($lists) {
/** @var \App\Models\WebSocketDialogMsg $item */
foreach ($lists as $item) {
$key = $item->generateMsgKey();
if ($key) {
$item->key = $key;
$item->save();
}
}
});
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('web_socket_dialog_msgs', function (Blueprint $table) {
$table->dropColumn("key");
});
}
}

View File

@@ -0,0 +1,48 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddWebSocketDialogUsersInviterImportant extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
$isAdd = false;
Schema::table('web_socket_dialog_users', function (Blueprint $table) use (&$isAdd) {
if (!Schema::hasColumn('web_socket_dialog_users', 'important')) {
$isAdd = true;
$table->boolean('important')->default(0)->after('mark_unread')->nullable()->comment('是否不可移出(项目、任务、部门人员)');
$table->bigInteger('inviter')->nullable()->default(0)->after('mark_unread')->comment('邀请人');
}
});
if ($isAdd) {
\App\Models\WebSocketDialog::whereIn('group_type', ['project', 'task'])->chunkById(100, function ($lists) {
/** @var \App\Models\WebSocketDialog $item */
foreach ($lists as $item) {
\App\Models\WebSocketDialogUser::whereDialogId($item->id)->update([
'important' => 1,
]);
}
});
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('web_socket_dialog_users', function (Blueprint $table) {
$table->dropColumn("important");
$table->dropColumn("inviter");
});
}
}

View File

@@ -0,0 +1,45 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddWebUsersPinyin extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
$isAdd = false;
Schema::table('users', function (Blueprint $table) use (&$isAdd) {
if (!Schema::hasColumn('users', 'pinyin')) {
$isAdd = true;
$table->string('pinyin', 255)->nullable()->default('')->after('az')->comment('拼音(主要用于搜索)');
}
});
if ($isAdd) {
\App\Models\User::chunkById(100, function ($lists) {
/** @var \App\Models\User $item */
foreach ($lists as $item) {
$item->pinyin = \App\Module\Base::cn2pinyin($item->nickname);
$item->save();
}
});
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn("pinyin");
});
}
}

View File

@@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddProjectTasksLoopLoopAt extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('project_tasks', function (Blueprint $table) {
if (!Schema::hasColumn('project_tasks', 'loop')) {
$table->timestamp('loop_at')->nullable()->after('sort')->comment('下一次重复时间');
$table->string('loop', 20)->nullable()->default('')->after('sort')->comment('重复周期');
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('project_tasks', function (Blueprint $table) {
$table->dropColumn("loop_at");
$table->dropColumn("loop");
});
}
}

View File

@@ -0,0 +1,49 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddWebSocketDialogMsgsReplyNum extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
$isAdd = false;
Schema::table('web_socket_dialog_msgs', function (Blueprint $table) use (&$isAdd) {
if (!Schema::hasColumn('web_socket_dialog_msgs', 'reply_num')) {
$isAdd = true;
$table->bigInteger('reply_num')->nullable()->default(0)->after('send')->comment('有多少条回复');
}
});
if ($isAdd) {
\App\Models\WebSocketDialogMsg::select(['reply_id'])
->distinct()
->where('reply_id', '>', 0)
->chunk(100, function ($lists) {
/** @var \App\Models\WebSocketDialogMsg $item */
foreach ($lists as $item) {
\App\Models\WebSocketDialogMsg::whereId($item->reply_id)->update([
'reply_num' => \App\Models\WebSocketDialogMsg::whereReplyId($item->reply_id)->count()
]);
}
});
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('web_socket_dialog_msgs', function (Blueprint $table) {
$table->dropColumn("reply_num");
});
}
}

View File

@@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddWebSocketDialogMsgsTag extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('web_socket_dialog_msgs', function (Blueprint $table) {
if (!Schema::hasColumn('web_socket_dialog_msgs', 'tag')) {
$table->bigInteger('tag')->nullable()->default(0)->after('send')->comment('标注会员ID');
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('web_socket_dialog_msgs', function (Blueprint $table) {
$table->dropColumn("tag");
});
}
}

View File

@@ -0,0 +1,56 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddWebSocketDialogMsgsMtype extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
$isAdd = false;
Schema::table('web_socket_dialog_msgs', function (Blueprint $table) use (&$isAdd) {
if (!Schema::hasColumn('web_socket_dialog_msgs', 'mtype')) {
$isAdd = true;
$table->string('mtype', 20)->nullable()->default('')->after('type')->comment('消息类型(用于搜索)');
}
});
if ($isAdd) {
DB::table('web_socket_dialog_msgs')->update([
'mtype' => DB::raw('type')
]);
DB::table('web_socket_dialog_msgs')->where('type', 'text')->where('msg', 'LIKE', '%<img %')->update([
'mtype' => 'image'
]);
DB::table('web_socket_dialog_msgs')->where('type', 'file')->where('msg', 'LIKE', '%.jpg"%')->update([
'mtype' => 'image'
]);
DB::table('web_socket_dialog_msgs')->where('type', 'file')->where('msg', 'LIKE', '%.jpeg"%')->update([
'mtype' => 'image'
]);
DB::table('web_socket_dialog_msgs')->where('type', 'file')->where('msg', 'LIKE', '%.png"%')->update([
'mtype' => 'image'
]);
DB::table('web_socket_dialog_msgs')->where('type', 'file')->where('msg', 'LIKE', '%.gif"%')->update([
'mtype' => 'image'
]);
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('web_socket_dialog_msgs', function (Blueprint $table) {
$table->dropColumn("mtype");
});
}
}

View File

@@ -0,0 +1,47 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddWebSocketDialogMsgsLink extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
$isAdd = false;
Schema::table('web_socket_dialog_msgs', function (Blueprint $table) use (&$isAdd) {
if (!Schema::hasColumn('web_socket_dialog_msgs', 'link')) {
$isAdd = true;
$table->boolean('link')->default(0)->after('tag')->nullable()->comment('是否存在链接');
}
});
if ($isAdd) {
DB::table('web_socket_dialog_msgs')->where('type', 'text')->where('msg', 'LIKE', '%<a %')->update([
'link' => 1
]);
DB::table('web_socket_dialog_msgs')->where('type', 'text')->where('msg', 'LIKE', '%http:%')->update([
'link' => 1
]);
DB::table('web_socket_dialog_msgs')->where('type', 'text')->where('msg', 'LIKE', '%https:%')->update([
'link' => 1
]);
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('web_socket_dialog_msgs', function (Blueprint $table) {
$table->dropColumn("link");
});
}
}

View File

@@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class HandleWebSocketDialogMsgsEmoticon extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
DB::table('web_socket_dialog_msgs')->where('mtype', 'image')->where('msg', 'LIKE', '%"emoticon%')->update([
'mtype' => 'emoticon'
]);
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
}
}

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