Compare commits

...

607 Commits

Author SHA1 Message Date
kuaifan
96b14911f6 build 2023-03-06 09:59:20 +08:00
kuaifan
248039f59e build 2023-03-06 09:46:40 +08:00
kuaifan
aacd52eec7 perf: 优化表情回复 2023-03-06 09:35:00 +08:00
kuaifan
c699feef54 优化快捷消息 2023-03-06 09:28:15 +08:00
kuaifan
6cf7833ef7 no message 2023-03-06 08:50:31 +08:00
kuaifan
d57245d3f0 build 2023-03-06 00:15:57 +08:00
kuaifan
893465fc38 build 2023-03-05 23:52:15 +08:00
kuaifan
19b55ff74c no message 2023-03-05 23:01:17 +08:00
kuaifan
4c76b3e60c perf: 消息快捷发送菜单 2023-03-05 23:01:09 +08:00
kuaifan
c27a15fce2 perf: 优化消息类型分类 2023-03-05 23:00:35 +08:00
kuaifan
2322e9a1b6 perf: 文件列表支持隐藏共享文件 2023-03-05 19:47:15 +08:00
kuaifan
aa781a51df feat: 支持发送匿名消息 2023-03-05 18:40:23 +08:00
kuaifan
5d5b1000fe no message 2023-03-05 18:17:47 +08:00
kuaifan
c0fe4a2bcb build 2023-03-05 09:21:16 +08:00
kuaifan
5f1cb1492a no message 2023-03-05 09:17:37 +08:00
kuaifan
67d0e26972 no message 2023-03-05 01:23:02 +08:00
kuaifan
0dfeb4be4f build 2023-03-05 01:11:08 +08:00
kuaifan
3112cc55f3 no message 2023-03-05 01:09:42 +08:00
kuaifan
10a2120fb8 perf: 群组支持修改头像 2023-03-05 01:00:05 +08:00
kuaifan
61aa50f366 no message 2023-03-04 23:23:11 +08:00
kuaifan
eee9045701 build 2023-03-04 16:25:20 +08:00
kuaifan
5c6fc6fe1a perf: 优化阅读消息 2023-03-04 15:58:31 +08:00
kuaifan
883cd94a5e no message 2023-03-04 15:02:11 +08:00
kuaifan
1f7fca0c78 perf: 点击会话消息头像@ 2023-03-04 15:01:59 +08:00
kuaifan
abe953f234 no message 2023-03-04 14:25:20 +08:00
kuaifan
ba3eb7f27c perf: 通讯录显示部门负责人 2023-03-04 12:27:09 +08:00
kuaifan
20e236d429 perf: 加载更多消息safari兼容性 2023-03-04 11:12:55 +08:00
kuaifan
b5059c13a3 perf: 优化签到数据 2023-03-04 10:21:48 +08:00
kuaifan
0a84a8718e perf: 点击头像进入对话 2023-03-04 07:14:22 +08:00
kuaifan
b1f5cae663 perf: 优化开发执行脚本 2023-03-04 07:03:55 +08:00
kuaifan
112b70dcad no message 2023-03-04 06:36:42 +08:00
kuaifan
dca8eca669 fix: 思维导图快捷键保存 2023-03-04 06:36:31 +08:00
kuaifan
e338711e19 build 2023-03-03 23:23:23 +08:00
kuaifan
1913e58758 perf: 网络恢复后重新标记已读失败的信息 2023-03-03 23:20:13 +08:00
kuaifan
947e5ed130 优化消息loading 2023-03-03 23:19:37 +08:00
kuaifan
1b693d782b no message 2023-03-03 22:04:59 +08:00
kuaifan
d63e1a4045 build 2023-03-03 20:38:55 +08:00
kuaifan
5aa31d8068 perf: dialog loading 2023-03-03 20:34:36 +08:00
kuaifan
55519148f1 no message 2023-02-27 14:46:11 +08:00
kuaifan
ca4a0eb0d3 perf: 优化再次点击抖动 2023-02-27 10:51:12 +08:00
kuaifan
b64b1cd654 perf: 优化ipad表单显示 2023-02-27 10:41:56 +08:00
kuaifan
81817fbfaf perf: 工作包括周报模板添加下周拟定计划项 2023-02-27 09:49:56 +08:00
kuaifan
9a9d19e16c 取消 Content-Security-Policy 2023-02-27 01:44:26 +08:00
kuaifan
e500426f8f no message 2023-02-26 23:37:47 +08:00
kuaifan
8f37c2f949 build 2023-02-26 23:00:29 +08:00
kuaifan
16ae0bd323 perf: 优化文件分享链接 2023-02-26 22:26:22 +08:00
kuaifan
a21c3d640f no message 2023-02-26 22:00:07 +08:00
kuaifan
f8644e7e39 fix: app cross domain 2023-02-26 20:21:35 +08:00
kuaifan
50b0542e28 fix: 桌面端已知bug 2023-02-26 10:37:57 +08:00
kuaifan
23cf984123 build 2023-02-25 23:55:34 +08:00
kuaifan
48654ca5db no message 2023-02-25 23:18:20 +08:00
kuaifan
2b362659ef perf: 非工作日不推送签到提醒 2023-02-25 23:14:32 +08:00
kuaifan
ce76f2d817 fix: 从任务窗口发送聊天输入缓存的问题 2023-02-25 22:21:24 +08:00
kuaifan
9a12184f9b fix: 桌面端新窗口打开任务无法发起聊天的问题 2023-02-25 22:09:41 +08:00
kuaifan
bdbc394b4a perf: 设置免打扰后被@也推送通知 2023-02-25 20:53:48 +08:00
kuaifan
60a04aa4a8 perf: 任务完成通知流程状态 2023-02-25 20:28:16 +08:00
kuaifan
17f0d65b5c no message 2023-02-25 15:35:50 +08:00
kuaifan
2dd7250616 add autoprefixer 2023-02-25 15:30:20 +08:00
kuaifan
fe8450d48a perf: 优化会话列表数据加载 2023-02-25 15:01:05 +08:00
kuaifan
b9163af11a build 2023-02-24 18:50:03 +08:00
kuaifan
f9fdff54ac no message 2023-02-24 16:11:20 +08:00
kuaifan
0c9b553591 no message 2023-02-24 13:49:21 +08:00
kuaifan
18d57a41c4 build 2023-02-24 01:04:50 +08:00
kuaifan
914b19ea58 no message 2023-02-24 00:33:37 +08:00
kuaifan
3a74fc6d4e no message 2023-02-23 23:44:41 +08:00
kuaifan
1693d08d09 no message 2023-02-23 21:29:24 +08:00
kuaifan
af2e68a97d vite2 2023-02-23 18:05:06 +08:00
kuaifan
b83010acdf fix: ldap一处报错 2023-02-23 09:47:08 +08:00
kuaifan
5461071a87 build 2023-02-22 21:03:17 +08:00
kuaifan
d561d4f902 build 2023-02-22 21:01:04 +08:00
kuaifan
db5bcfb683 perf: 优化ws连接机制 2023-02-22 20:50:19 +08:00
kuaifan
50a6b91a7e perf: office、图表、文本国际化 2023-02-22 16:25:39 +08:00
kuaifan
e8541f80ba build 2023-02-22 12:44:16 +08:00
kuaifan
9f7cdc34b4 fix: 没有后缀名无法下载文件的问题 2023-02-22 12:41:36 +08:00
kuaifan
ce7037d317 fix: 打卡提醒失效 2023-02-22 12:26:40 +08:00
kuaifan
2b7ed69c21 perf: 优化移动端设置 2023-02-22 12:17:35 +08:00
kuaifan
9d5c5e0982 fix: 移动端在任务提醒打开任务无法聊天的问题 2023-02-22 11:48:00 +08:00
kuaifan
405e5aa03a perf: 再次点击消息图标闪动未读对话 2023-02-22 08:35:30 +08:00
kuaifan
68af686139 no message 2023-02-22 07:48:16 +08:00
kuaifan
7f5261ad93 build 2023-02-21 20:58:45 +08:00
kuaifan
71a9b8f6ce perf: 优化已读标记 2023-02-21 20:40:24 +08:00
阿胖
efd25bc787 perf: 移动端优化 2023-02-21 06:38:54 +00:00
阿胖
921ff4e5bf perf: 消息接口支持@邮箱 2023-02-21 06:38:31 +00:00
阿胖
6e51b7f300 perf: 支持gitpod 2023-02-21 06:37:37 +00:00
kuaifan
d9c036e359 build 2023-02-20 19:51:47 +08:00
kuaifan
3399ea73d0 优化聊天输入框粘贴 2023-02-20 19:45:41 +08:00
kuaifan
2fca82d7c5 fix: 编辑消息@丢失的问题 2023-02-20 18:54:44 +08:00
kuaifan
6ea3918a8b perf: 兼容ipad app样式 2023-02-20 17:42:10 +08:00
kuaifan
244c283556 no message 2023-02-20 17:14:41 +08:00
kuaifan
44f3c76569 build 2023-02-20 14:57:23 +08:00
kuaifan
111b5a777f perf: 添加上班签到提醒消息 2023-02-20 14:54:41 +08:00
kuaifan
071ddfad0f fix:修复已知bug 2023-02-20 14:53:41 +08:00
kuaifan
8a83015118 no message 2023-02-20 10:16:37 +08:00
kuaifan
1a5f472030 no message 2023-02-20 01:08:47 +08:00
kuaifan
a8f26f8ba6 build 2023-02-20 00:39:38 +08:00
kuaifan
64f032a352 update language 2023-02-20 00:22:35 +08:00
kuaifan
d3a50e7810 perf:完善临时帐号权限 2023-02-20 00:08:46 +08:00
kuaifan
5f0b858baf feat: 新增临时帐号功能 2023-02-19 23:20:19 +08:00
kuaifan
7c4c03bbd4 no message 2023-02-19 23:19:41 +08:00
kuaifan
79b5ca4db0 perf: 设待办快速选择人员 2023-02-19 17:51:15 +08:00
kuaifan
bf941b7ec4 perf: 优化消息数量显示 2023-02-19 17:39:18 +08:00
kuaifan
aa5da2479c perf: 优化阅读消息 2023-02-19 16:36:27 +08:00
kuaifan
87d7be254a no message 2023-02-19 15:18:57 +08:00
kuaifan
42e54d8a6e perf: 对话顶部提示 2023-02-19 08:49:32 +08:00
kuaifan
66f0bef271 no message 2023-02-19 00:14:53 +08:00
kuaifan
a76e187c3a no message 2023-02-19 00:07:16 +08:00
kuaifan
95431a7876 no message 2023-02-19 00:06:55 +08:00
kuaifan
ed53234dc7 no message 2023-02-18 09:05:14 +08:00
kuaifan
08e136bbca no message 2023-02-18 08:52:43 +08:00
kuaifan
a610731897 build 2023-02-18 07:43:36 +08:00
kuaifan
3a41c7e57b 优化国际化 2023-02-18 07:40:49 +08:00
kuaifan
8c1f408930 no message 2023-02-17 21:58:37 +08:00
kuaifan
771d63b3ee build 2023-02-17 18:51:26 +08:00
kuaifan
b271105d1a perf: 样式兼容 2023-02-17 18:51:12 +08:00
kuaifan
6c173674f0 no message 2023-02-17 18:12:06 +08:00
kuaifan
fedf8a3eda 客户端国际化 2023-02-17 17:39:31 +08:00
kuaifan
03170e3a9a build 2023-02-17 17:06:45 +08:00
kuaifan
16832c1a17 perf: 优化api国际化 2023-02-17 17:06:14 +08:00
kuaifan
fcdd5e851b build 2023-02-17 14:42:29 +08:00
kuaifan
1eb17dd003 perf: 优化国际化 2023-02-17 14:34:51 +08:00
kuaifan
a7e82f906a no message 2023-02-17 06:01:57 +08:00
kuaifan
150462354f perf: 优化ws重连规则 2023-02-17 06:01:48 +08:00
kuaifan
1bc614abd2 build 2023-02-16 14:28:51 +08:00
kuaifan
3cf6246f4e 修复已知错误 2023-02-16 14:25:38 +08:00
kuaifan
9265080fcf build 2023-02-16 09:56:25 +08:00
kuaifan
094b75c4f7 build 2023-02-15 20:35:56 +08:00
kuaifan
a1e8da140e perf: 优化翻译 2023-02-15 20:34:29 +08:00
kuaifan
84864801aa build 2023-02-15 16:14:32 +08:00
kuaifan
30b511891e perf: 优化更新日志生成 2023-02-15 16:11:46 +08:00
kuaifan
6a2c56da3e perf: 优化首页 2023-02-15 13:40:58 +08:00
kuaifan
be8cd95ae4 优化数据结构 2023-02-15 12:03:21 +08:00
kuaifan
d9562e6b33 build 2023-02-14 20:10:12 +08:00
kuaifan
84edd151bf fix: 移动端应用内通知标题溢出的问题 2023-02-14 19:55:50 +08:00
kuaifan
e1456b72fb 优化机器人 2023-02-14 15:45:25 +08:00
kuaifan
2307f36db0 优化机器人 2023-02-14 15:37:43 +08:00
kuaifan
9a8eae723f perf: 机器人支持webhook 2023-02-14 15:10:54 +08:00
kuaifan
19f806e429 no message 2023-02-14 10:58:31 +08:00
kuaifan
799d8330ba build 2023-02-14 10:52:15 +08:00
kuaifan
d1fb538e75 perf: 优化输入框功能提示 2023-02-14 10:47:53 +08:00
kuaifan
96e7913ae4 perf: 优化任务修改时间通知 2023-02-14 10:47:34 +08:00
kuaifan
8a41730b63 perf: 导出所有超期任务 2023-02-14 10:37:37 +08:00
kuaifan
5601261cbe 优化删除数据 2023-02-14 08:58:41 +08:00
kuaifan
ac123387fd perf: 优化删除数据 2023-02-14 07:45:07 +08:00
kuaifan
e9b07dacce build 2023-02-14 00:27:02 +08:00
kuaifan
7fd0ef4ac6 fix: LDAP Exception 2023-02-14 00:23:42 +08:00
kuaifan
f3e41f2ff4 完善扫码登录 2023-02-14 00:15:18 +08:00
kuaifan
49ac519a5e feat: 二维码登录 2023-02-13 19:58:42 +08:00
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
804 changed files with 62279 additions and 28435 deletions

View File

@@ -1,13 +1,14 @@
APP_NAME=DooTask
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_DEBUG=false
APP_SCHEME=auto
APP_URL=http://localhost
APP_ID=
APP_IPPR=
APP_PORT=2222
APP_DEV_PORT=
LOG_CHANNEL=stack
LOG_LEVEL=debug
@@ -53,6 +54,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,7 +8,6 @@
/vendor
/build
/tmp
/CHANGELOG.md
._*
.env
.idea

2
.gitmodules vendored
View File

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

13
.gitpod.yml Normal file
View File

@@ -0,0 +1,13 @@
# This configuration file was automatically generated by Gitpod.
# Please adjust to your needs (see https://www.gitpod.io/docs/config-gitpod-file)
# and commit this file to your remote git repository to share the goodness with others.
tasks:
- init: sudo ./cmd install
command: ./cmd dev
ports:
- port: 2222
visibility: public
- port: 22222
visibility: public

1895
CHANGELOG.md Normal file

File diff suppressed because it is too large Load Diff

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

@@ -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

@@ -45,11 +45,12 @@ class FileController extends AbstractController
$pid = intval($data['pid']);
//
$permission = 1000;
$userids = $user->isTemp() ? [$user->userid] : [0, $user->userid];
$builder = File::wherePid($pid);
if ($pid > 0) {
File::permissionFind($pid, 0, $permission);
$builder = File::wherePid($pid);
File::permissionFind($pid, $userids, 0, $permission);
} else {
$builder = File::whereUserid($user->userid);
$builder->whereUserid($user->userid);
}
//
$array = $builder->take(500)->get()->toArray();
@@ -66,7 +67,7 @@ class FileController extends AbstractController
}
$pid = $file->pid;
$temp = $file->toArray();
$temp['permission'] = $file->getPermission($user->userid);
$temp['permission'] = $file->getPermission($userids);
$array[] = $temp;
}
// 去除没有权限的文件
@@ -92,10 +93,7 @@ class FileController extends AbstractController
$list = File::select(["files.*", DB::raw("MAX({$pre}file_users.permission) as permission")])
->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);
})
->whereIn('file_users.userid', $userids)
->groupBy('files.id')
->take(100)
->get();
@@ -136,13 +134,18 @@ class FileController extends AbstractController
//
$permission = 0;
if (Base::isNumber($id)) {
User::auth();
$file = File::permissionFind(intval($id), 0, $permission);
$user = User::auth();
$file = File::permissionFind(intval($id), $user, 0, $permission);
} elseif ($id) {
$fileLink = FileLink::whereCode($id)->first();
$file = $fileLink?->file;
if (empty($file)) {
return Base::retError('链接不存在');
$msg = '文件链接不存在';
$data = File::code2IdName($id);
if ($data) {
$msg = "{$data->name}{$msg}";
}
return Base::retError($msg, $data);
}
} else {
return Base::retError('参数错误');
@@ -172,14 +175,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);
}
/**
@@ -215,9 +243,10 @@ class FileController extends AbstractController
//
if ($id > 0) {
// 修改
$file = File::permissionFind($id, 1);
$file = File::permissionFind($id, $user, 1);
//
$file->name = $name;
$file->handleDuplicateName();
$file->save();
$file->pushMsg('update', $file);
return Base::retSuccess('修改成功', $file);
@@ -257,7 +286,7 @@ class FileController extends AbstractController
if (File::wherePid($pid)->count() >= 300) {
return Base::retError('每个文件夹里最多只能创建300个文件或文件夹');
}
$row = File::permissionFind($pid, 1);
$row = File::permissionFind($pid, $user, 1);
$userid = $row->userid;
} else {
if (File::whereUserid($user->userid)->wherePid(0)->count() >= 300) {
@@ -274,7 +303,7 @@ class FileController extends AbstractController
'created_id' => $user->userid,
]);
$file->handleDuplicateName();
$file->saveBeforePids();
$file->saveBeforePP();
//
$data = File::find($file->id);
$data->pushMsg('add', $data);
@@ -302,7 +331,7 @@ class FileController extends AbstractController
//
$id = intval(Request::input('id'));
//
$row = File::permissionFind($id);
$row = File::permissionFind($id, $user);
//
$userid = $user->userid;
if ($row->pid > 0) {
@@ -328,7 +357,7 @@ class FileController extends AbstractController
$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;
@@ -359,7 +388,7 @@ class FileController extends AbstractController
*/
public function move()
{
User::auth();
$user = User::auth();
//
$ids = Request::input('ids');
$pid = intval(Request::input('pid'));
@@ -372,14 +401,14 @@ class FileController extends AbstractController
}
$toShareFile = false;
if ($pid > 0) {
$tmpFile = File::permissionFind($pid, 1);
$tmpFile = File::permissionFind($pid, $user, 1);
$toShareFile = $tmpFile->getShareInfo();
}
//
$files = [];
AbstractModel::transaction(function() use ($pid, $ids, $toShareFile, &$files) {
AbstractModel::transaction(function() use ($user, $pid, $ids, $toShareFile, &$files) {
foreach ($ids as $id) {
$file = File::permissionFind($id, 1000);
$file = File::permissionFind($id, $user, 1000);
//
if ($pid > 0) {
if ($toShareFile) {
@@ -389,6 +418,8 @@ class FileController extends AbstractController
if ($file->isSubShare()) {
throw new ApiException("{$file->name} 内含有共享文件,无法移动到另一个共享文件夹内");
}
$file->userid = $toShareFile->userid;
File::where('pids', 'LIKE', "%,{$file->id},%")->update(['userid' => $toShareFile->userid]);
}
//
$tmpId = $pid;
@@ -398,11 +429,14 @@ class FileController extends AbstractController
}
$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->handleDuplicateName();
$file->saveBeforePids();
$file->saveBeforePP();
$files[] = $file;
}
});
@@ -428,7 +462,7 @@ class FileController extends AbstractController
*/
public function remove()
{
User::auth();
$user = User::auth();
//
$ids = Request::input('ids');
//
@@ -440,9 +474,9 @@ class FileController extends AbstractController
}
//
$files = [];
AbstractModel::transaction(function() use ($ids, &$files) {
AbstractModel::transaction(function() use ($user, $ids, &$files) {
foreach ($ids as $id) {
$file = File::permissionFind($id, 1000);
$file = File::permissionFind($id, $user, 1000);
$file->deleteFile();
$files[] = $file;
}
@@ -483,13 +517,18 @@ class FileController extends AbstractController
$history_id = intval(Request::input('history_id'));
//
if (Base::isNumber($id)) {
User::auth();
$file = File::permissionFind(intval($id));
$user = User::auth();
$file = File::permissionFind(intval($id), $user);
} elseif ($id) {
$fileLink = FileLink::whereCode($id)->first();
$file = $fileLink?->file;
if (empty($file)) {
return Base::retError('链接不存在');
$msg = '文件链接不存在';
$data = File::code2IdName($id);
if ($data) {
$msg = "{$data->name}{$msg}";
}
return Base::retError($msg, $data);
}
} else {
return Base::retError('参数错误');
@@ -531,13 +570,12 @@ class FileController extends AbstractController
*/
public function content__save()
{
Base::checkClientVersion('0.13.68');
$user = User::auth();
//
$id = Base::getPostInt('id');
$content = Base::getPostValue('content');
//
$file = File::permissionFind($id, 1);
$file = File::permissionFind($id, $user, 1);
//
$text = '';
if ($file->type == 'document') {
@@ -630,7 +668,7 @@ class FileController extends AbstractController
$key = Request::input('key');
$url = Request::input('url');
//
$file = File::permissionFind($id, 1);
$file = File::permissionFind($id, $user, 1);
//
if ($status === 2) {
$parse = parse_url($url);
@@ -688,7 +726,7 @@ class FileController extends AbstractController
if (File::wherePid($pid)->count() >= 300) {
return Base::retError('每个文件夹里最多只能创建300个文件或文件夹');
}
$row = File::permissionFind($pid, 1);
$row = File::permissionFind($pid, $user, 1);
$userid = $row->userid;
} else {
if (File::whereUserid($user->userid)->wherePid(0)->count() >= 300) {
@@ -712,7 +750,7 @@ class FileController extends AbstractController
'created_id' => $user->userid,
]);
$dirRow->handleDuplicateName();
if ($dirRow->saveBeforePids()) {
if ($dirRow->saveBeforePP()) {
$addItem[] = File::find($dirRow->id);
}
}
@@ -782,7 +820,7 @@ class FileController extends AbstractController
// 开始创建
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 = [
@@ -834,9 +872,11 @@ class FileController extends AbstractController
*/
public function content__history()
{
$user = User::auth();
//
$id = Request::input('id');
//
$file = File::permissionFind(intval($id));
$file = File::permissionFind(intval($id), $user);
//
$data = FileContent::select(['id', 'size', 'userid', 'created_at'])
->whereFid($file->id)
@@ -867,7 +907,7 @@ class FileController extends AbstractController
$id = intval(Request::input('id'));
$history_id = intval(Request::input('history_id'));
//
$file = File::permissionFind($id);
$file = File::permissionFind($id, $user);
//
$history = FileContent::whereFid($file->id)->whereId($history_id)->first();
if (empty($history)) {
@@ -1031,7 +1071,7 @@ class FileController extends AbstractController
//
$id = intval(Request::input('id'));
//
$file = File::permissionFind($id);
$file = File::permissionFind($id, $user);
//
if ($file->userid == $user->userid) {
return Base::retError('不能退出自己共享的文件');
@@ -1069,29 +1109,9 @@ class FileController extends AbstractController
$id = intval(Request::input('id'));
$refresh = Request::input('refresh', 'no');
//
$file = File::permissionFind($id);
if ($file->type == 'folder') {
return Base::retError('文件夹暂不支持此功能');
}
$file = File::permissionFind($id, $user);
$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

@@ -20,6 +20,7 @@ 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;
@@ -44,6 +45,10 @@ class ProjectController extends AbstractController
* @apiName lists
*
* @apiParam {String} [all] 是否查看所有项目(限制管理员)
* @apiParam {String} [type] 项目类型
* - all全部默认
* - team团队项目
* - personal个人项目
* @apiParam {String} [archived] 归档状态
* - all全部
* - no未归档默认
@@ -53,6 +58,7 @@ class ProjectController extends AbstractController
* - yes取列表
* @apiParam {Object} [keys] 搜索条件
* - keys.name: 项目名称
* @apiParam {String} [deleted_at] 读取在这个时间之后删除的项目ID返回数据: deleted_data此参数仅第1页有效
*
* @apiParam {Number} [page] 当前页,默认:1
* @apiParam {Number} [pagesize] 每页显示数量,默认:50最大:100
@@ -96,6 +102,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');
//
@@ -110,6 +117,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') {
@@ -118,7 +131,7 @@ class ProjectController extends AbstractController
//
$keys = Request::input('keys');
if (is_array($keys)) {
$buildClone = $builder->clone();
$totalAll = $builder->clone()->count();
if ($keys['name']) {
$builder->where("projects.name", "like", "%{$keys['name']}%");
}
@@ -130,10 +143,16 @@ class ProjectController extends AbstractController
});
//
$data = $list->toArray();
if (isset($buildClone)) {
$data['total_all'] = $buildClone->count();
} else {
$data['total_all'] = $data['total'];
$data['total_all'] = $totalAll ?? $data['total'];
//
if ($list->currentPage() === 1 && Request::input('deleted_at')) {
$data['deleted_at'] = date("Y-m-d H:i:s");
$data['deleted_data'] = Project::authData()
->withTrashed()
->where('projects.deleted_at', '>=', Carbon::parse(Request::input('deleted_at')))
->orderByDesc('projects.deleted_at')
->take(100)
->pluck('id');
}
//
return Base::retSuccess('success', $data);
@@ -260,18 +279,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);
@@ -483,7 +506,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) {
@@ -962,8 +985,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('时间选择错误');
@@ -1052,7 +1075,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) ?: '-',
@@ -1072,6 +1109,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) {
@@ -1079,7 +1130,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 . '');
}
@@ -1109,11 +1161,119 @@ class ProjectController extends AbstractController
}
/**
* @api {get} api/project/task/down 20. 导出任务(限管理员)
* @api {get} api/project/task/exportoverdue 20. 导出超期任务(限管理员)
*
* @apiDescription 导出指定范围任务已完成、未完成、已归档返回下载地址需要token身份
* @apiVersion 1.0.0
* @apiGroup project
* @apiName task__exportoverdue
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function task__exportoverdue()
{
$user = User::auth('admin');
//
$headings = [];
$headings[] = '任务ID';
$headings[] = '父级任务ID';
$headings[] = '所属项目';
$headings[] = '任务标题';
$headings[] = '任务开始时间';
$headings[] = '任务结束时间';
$headings[] = '任务计划用时';
$headings[] = '超时时间';
$headings[] = '负责人';
$headings[] = '创建人';
$data = [];
//
ProjectTask::whereNull('complete_at')
->whereNotNull('end_at')
->where('end_at', '<=', Carbon::now())
->orderBy('end_at')
->chunk(100, function ($tasks) use (&$data) {
/** @var ProjectTask $task */
foreach ($tasks as $task) {
$taskStartTime = Carbon::parse($task->start_at ?: $task->created_at)->timestamp;
$totalTime = time() - $taskStartTime; //开发测试总用时
$planTime = '-';//任务计划用时
$overTime = '-';//超时时间
if ($task->end_at) {
$startTime = Carbon::parse($task->start_at)->timestamp;
$endTime = Carbon::parse($task->end_at)->timestamp;
$planTotalTime = $endTime - $startTime;
$residueTime = $planTotalTime - $totalTime;
if ($residueTime < 0) {
$overTime = Base::timeFormat(abs($residueTime));
}
$planTime = Base::timeDiff($startTime, $endTime);
}
$ownerIds = $task->taskUser->where('owner', 1)->pluck('userid')->toArray();
$ownerNames = [];
foreach ($ownerIds as $ownerId) {
$ownerNames[] = Base::filterEmoji(User::userid2nickname($ownerId)) . " (ID: {$ownerId})";
}
$data[] = [
$task->id,
$task->parent_id ?: '-',
Base::filterEmoji($task->project?->name) ?: '-',
Base::filterEmoji($task->name),
$task->start_at ?: '-',
$task->end_at ?: '-',
$planTime ?: '-',
$overTime,
implode("", $ownerNames),
Base::filterEmoji(User::userid2nickname($task->userid)) . " (ID: {$task->userid})",
];
}
});
if (empty($data)) {
return Base::retError('没有任何数据');
}
//
$sheets = [
BillExport::create()->setTitle("超期任务")->setHeadings($headings)->setData($data)->setStyles(["A1:J1" => ["font" => ["bold" => true]]])
];
//
$fileName = '超期任务_' . Base::time() . '.xls';
$filePath = "temp/task/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('task::export:userid', $user->userid);
return Base::retSuccess('success', [
'size' => Base::twoFloat(filesize($zipPath) / 1024, true),
'url' => Base::fillUrl('api/project/task/down?key=' . urlencode($base64)),
]);
} else {
return Base::retError('打包失败,请稍后再试...');
}
}
/**
* @api {get} api/project/task/down 21. 下载导出的任务
*
* @apiVersion 1.0.0
* @apiGroup project
* @apiName task__down
*
* @apiParam {String} key 通过export接口得到的下载钥匙
@@ -1132,11 +1292,11 @@ 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));
}
/**
* @api {get} api/project/task/one 21. 获取单个任务信息
* @api {get} api/project/task/one 22. 获取单个任务信息
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@@ -1170,7 +1330,7 @@ class ProjectController extends AbstractController
}
/**
* @api {get} api/project/task/content 22. 获取任务详细描述
* @api {get} api/project/task/content 23. 获取任务详细描述
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@@ -1198,7 +1358,7 @@ class ProjectController extends AbstractController
}
/**
* @api {get} api/project/task/files 23. 获取任务文件列表
* @api {get} api/project/task/files 24. 获取任务文件列表
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@@ -1223,7 +1383,7 @@ class ProjectController extends AbstractController
}
/**
* @api {get} api/project/task/filedelete 24. 删除任务文件
* @api {get} api/project/task/filedelete 25. 删除任务文件
*
* @apiDescription 需要token身份项目、任务负责人
* @apiVersion 1.0.0
@@ -1256,7 +1416,7 @@ class ProjectController extends AbstractController
}
/**
* @api {get} api/project/task/filedetail 25. 获取任务文件详情
* @api {get} api/project/task/filedetail 26. 获取任务文件详情
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@@ -1300,7 +1460,7 @@ class ProjectController extends AbstractController
}
/**
* @api {get} api/project/task/filedown 26. 下载任务文件
* @api {get} api/project/task/filedown 27. 下载任务文件
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@@ -1342,11 +1502,14 @@ class ProjectController extends AbstractController
]));
}
//
return Response::download(public_path($file->getRawOriginal('path')), $file->name);
$filePath = public_path($file->getRawOriginal('path'));
return Base::streamDownload(function() use ($filePath) {
echo file_get_contents($filePath);
}, $file->name);
}
/**
* @api {post} api/project/task/add 27. 添加任务
* @api {post} api/project/task/add 28. 添加任务
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@@ -1413,11 +1576,12 @@ class ProjectController extends AbstractController
$data['new_column'] = $newColumn;
}
$task->pushMsg('add', $data);
$task->taskPush(null, 0);
return Base::retSuccess('添加成功', $data);
}
/**
* @api {get} api/project/task/addsub 28. 添加子任务
* @api {get} api/project/task/addsub 29. 添加子任务
*
* @apiDescription 需要token身份项目、任务负责人
* @apiVersion 1.0.0
@@ -1457,7 +1621,7 @@ class ProjectController extends AbstractController
}
/**
* @api {post} api/project/task/update 29. 修改任务、子任务
* @api {post} api/project/task/update 30. 修改任务、子任务
*
* @apiDescription 需要token身份项目、任务负责人
* @apiVersion 1.0.0
@@ -1504,7 +1668,7 @@ class ProjectController extends AbstractController
}
/**
* @api {get} api/project/task/dialog 30. 创建/获取聊天室
* @api {get} api/project/task/dialog 31. 创建/获取聊天室
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@@ -1532,7 +1696,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();
@@ -1553,7 +1717,7 @@ class ProjectController extends AbstractController
}
/**
* @api {get} api/project/task/archived 31. 归档任务
* @api {get} api/project/task/archived 32. 归档任务
*
* @apiDescription 需要token身份项目、任务负责人
* @apiVersion 1.0.0
@@ -1595,7 +1759,7 @@ class ProjectController extends AbstractController
}
/**
* @api {get} api/project/task/remove 32. 删除任务
* @api {get} api/project/task/remove 33. 删除任务
*
* @apiDescription 需要token身份项目、任务负责人
* @apiVersion 1.0.0
@@ -1629,7 +1793,7 @@ class ProjectController extends AbstractController
}
/**
* @api {get} api/project/task/resetfromlog 33. 根据日志重置任务
* @api {get} api/project/task/resetfromlog 34. 根据日志重置任务
*
* @apiDescription 需要token身份项目、任务负责人
* @apiVersion 1.0.0
@@ -1688,7 +1852,7 @@ class ProjectController extends AbstractController
}
/**
* @api {get} api/project/task/flow 34. 任务工作流信息
* @api {get} api/project/task/flow 35. 任务工作流信息
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@@ -1770,7 +1934,7 @@ class ProjectController extends AbstractController
}
/**
* @api {get} api/project/flow/list 35. 工作流列表
* @api {get} api/project/flow/list 36. 工作流列表
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@@ -1796,7 +1960,7 @@ class ProjectController extends AbstractController
}
/**
* @api {post} api/project/flow/save 36. 保存工作流
* @api {post} api/project/flow/save 37. 保存工作流
*
* @apiDescription 需要token身份项目负责人
* @apiVersion 1.0.0
@@ -1830,7 +1994,7 @@ class ProjectController extends AbstractController
}
/**
* @api {get} api/project/flow/delete 37. 删除工作流
* @api {get} api/project/flow/delete 38. 删除工作流
*
* @apiDescription 需要token身份项目负责人
* @apiVersion 1.0.0
@@ -1862,7 +2026,7 @@ class ProjectController extends AbstractController
}
/**
* @api {get} api/project/log/lists 38. 获取项目、任务日志
* @api {get} api/project/log/lists 39. 获取项目、任务日志
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@@ -1901,11 +2065,12 @@ class ProjectController extends AbstractController
if ($task_id === 0) {
$log->projectTask?->cancelAppend();
}
$log->detail = Base::Lang($log->detail);
$log->time = [
'ymd' => date(date("Y", $timestamp) == date("Y", Base::time()) ? "m-d" : "Y-m-d", $timestamp),
'hi' => date("h:i", $timestamp) ,
'week' => "" . Base::getTimeWeek($timestamp),
'segment' => Base::getTimeDayeSegment($timestamp),
'week' => Base::Lang("" . Base::getTimeWeek($timestamp)),
'segment' => Base::Lang(Base::getTimeDayeSegment($timestamp)),
];
return $log;
});
@@ -1914,7 +2079,7 @@ class ProjectController extends AbstractController
}
/**
* @api {get} api/project/top 39. 项目置顶
* @api {get} api/project/top 40. 项目置顶
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0

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']) : ['09:00', '18:00'];
$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, $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>';
@@ -339,15 +340,21 @@ class ReportController extends AbstractController
} else {
$title = $user->nickname . "的日报[" . $start_time->format("Y/m/d") . "]";
}
// 生成内容
$content = '<h2>' . Base::Lang('已完成工作') . '</h2><ol>' .
$completeContent . '</ol><h2>' .
Base::Lang('未完成的工作') . '</h2><ol>' .
$unfinishedContent . '</ol>';
if ($type === Report::WEEKLY) {
$content .= "<h2>" . Base::Lang("下周拟定计划") . "[" . $start_time->addWeek()->format("m/d") . "-" . $end_time->addWeek()->format("m/d") . "]</h2><ol><li>&nbsp;</li></ol>";
}
$data = [
"time" => $start_time->toDateTimeString(),
"sign" => $sign,
"title" => $title,
"content" => $content,
"complete_task" => $complete_task,
"unfinished_task" => $unfinished_task,
"content" => '<h2>' . Base::Lang('已完成工作') . '</h2><ol>' .
$completeContent . '</ol><h2>' .
Base::Lang('未完成的工作') . '</h2><ol>' .
$unfinishedContent . '</ol>',
"title" => $title,
];
if ($one) {
$data['id'] = $one->id;
@@ -393,7 +400,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 +459,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 +483,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 +504,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,11 +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
@@ -26,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_identity', 'reg_invite', 'login_code', 'password_policy', 'project_invite', 'chat_information', 'anon_message', 'auto_archived', 'archived_day', 'all_group_mute', 'all_group_autoin', 'start_home', 'home_footer']
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
@@ -44,13 +59,17 @@ class SystemController extends AbstractController
foreach ($all AS $key => $value) {
if (!in_array($key, [
'reg',
'reg_identity',
'reg_invite',
'login_code',
'password_policy',
'project_invite',
'chat_nickname',
'chat_information',
'anon_message',
'auto_archived',
'archived_day',
'all_group_mute',
'all_group_autoin',
'start_home',
'home_footer'
])) {
@@ -78,12 +97,16 @@ class SystemController extends AbstractController
}
//
$setting['reg'] = $setting['reg'] ?: 'open';
$setting['reg_identity'] = $setting['reg_identity'] ?: 'normal';
$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['anon_message'] = $setting['anon_message'] ?: 'open';
$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('{}'));
@@ -98,20 +121,21 @@ class SystemController extends AbstractController
*
* @apiParam {String} type
* - get: 获取(默认)
* - save: 保存设置(参数:['smtp_server', 'port', 'account', 'password', 'reg_verify', 'notice', 'task_start_minute', 'task_remind_hours', 'task_remind_hours2', 'notice_msg', 'msg_unread_user_minute', 'msg_unread_group_minute']
* - 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, [
@@ -120,13 +144,10 @@ class SystemController extends AbstractController
'account',
'password',
'reg_verify',
'notice',
'task_start_minute',
'task_remind_hours',
'task_remind_hours2',
'notice_msg',
'msg_unread_user_minute',
'msg_unread_group_minute'
'msg_unread_group_minute',
'ignore_addr'
])) {
unset($all[$key]);
}
@@ -141,13 +162,14 @@ class SystemController extends AbstractController
$setting['account'] = $setting['account'] ?: '';
$setting['password'] = $setting['password'] ?: '';
$setting['reg_verify'] = $setting['reg_verify'] ?: 'close';
$setting['notice'] = $setting['notice'] ?: 'close';
$setting['task_start_minute'] = intval($setting['task_start_minute'] ?? -1);
$setting['task_remind_hours'] = floatval($setting['task_remind_hours'] ?? -1);
$setting['task_remind_hours2'] = floatval($setting['task_remind_hours2'] ?? -1);
$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('{}'));
}
@@ -196,7 +218,70 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/setting/apppush 04. 获取APP推送设置、保存APP推送设置(限管理员)
* @api {get} api/system/setting/checkin 04. 获取签到设置、保存签到设置(限管理员)
*
* @apiVersion 1.0.0
* @apiGroup system
* @apiName setting__checkin
*
* @apiParam {String} type
* - get: 获取(默认)
* - save: 保存设置(参数:['open', 'time', 'advance', 'delay', 'remindin', 'remindexceed', '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',
'remindin',
'remindexceed',
'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']) : ['09:00', '18:00'];
$setting['advance'] = intval($setting['advance']) ?: 120;
$setting['delay'] = intval($setting['delay']) ?: 120;
$setting['remindin'] = intval($setting['remindin']) ?: 5;
$setting['remindexceed'] = intval($setting['remindexceed']) ?: 10;
$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 05. 获取APP推送设置、保存APP推送设置限管理员
*
* @apiVersion 1.0.0
* @apiGroup system
@@ -225,7 +310,7 @@ class SystemController extends AbstractController
'ios_key',
'ios_secret',
'android_key',
'android_secret'
'android_secret',
])) {
unset($all[$key]);
}
@@ -241,7 +326,77 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/demo 05. 获取演示帐号
* @api {get} api/system/setting/thirdaccess 06. 第三方帐号(限管理员)
*
* @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 07. 获取演示帐号
*
* @apiVersion 1.0.0
* @apiGroup system
@@ -265,7 +420,7 @@ class SystemController extends AbstractController
}
/**
* @api {post} api/system/priority 06. 任务优先级
* @api {post} api/system/priority 08. 任务优先级
*
* @apiDescription 获取任务优先级、保存任务优先级
* @apiVersion 1.0.0
@@ -314,7 +469,7 @@ class SystemController extends AbstractController
}
/**
* @api {post} api/system/column/template 07. 创建项目模板
* @api {post} api/system/column/template 09. 创建项目模板
*
* @apiDescription 获取创建项目模板、保存创建项目模板
* @apiVersion 1.0.0
@@ -361,7 +516,7 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/get/info 08. 获取终端详细信息
* @api {get} api/system/get/info 10. 获取终端详细信息
*
* @apiVersion 1.0.0
* @apiGroup system
@@ -390,7 +545,7 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/get/ip 09. 获取IP地址
* @api {get} api/system/get/ip 11. 获取IP地址
*
* @apiVersion 1.0.0
* @apiGroup system
@@ -405,7 +560,7 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/get/cnip 10. 是否中国IP地址
* @api {get} api/system/get/cnip 12. 是否中国IP地址
*
* @apiVersion 1.0.0
* @apiGroup system
@@ -422,7 +577,7 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/get/ipgcj02 11. 获取IP地址经纬度
* @api {get} api/system/get/ipgcj02 13. 获取IP地址经纬度
*
* @apiVersion 1.0.0
* @apiGroup system
@@ -439,7 +594,7 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/get/ipinfo 12. 获取IP地址详细信息
* @api {get} api/system/get/ipinfo 14. 获取IP地址详细信息
*
* @apiVersion 1.0.0
* @apiGroup system
@@ -456,7 +611,7 @@ class SystemController extends AbstractController
}
/**
* @api {post} api/system/imgupload 13. 上传图片
* @api {post} api/system/imgupload 15. 上传图片
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@@ -516,7 +671,7 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/get/imgview 14. 浏览图片空间
* @api {get} api/system/get/imgview 16. 浏览图片空间
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@@ -612,7 +767,7 @@ class SystemController extends AbstractController
}
/**
* @api {post} api/system/fileupload 15. 上传文件
* @api {post} api/system/fileupload 17. 上传文件
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@@ -654,7 +809,39 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/get/starthome 16. 启动首页设置信息
* @api {get} api/system/get/showitem 18. 首页显示ITEM
*
* @apiDescription 用于判断首页是否显示pro、github、更新日志...
* @apiVersion 1.0.0
* @apiGroup system
* @apiName get__showitem
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function get__showitem()
{
$logPath = base_path('CHANGELOG.md');
$logContent = "";
$logVersion = "";
if (file_exists($logPath)) {
$logContent = file_get_contents($logPath);
preg_match("/## \[(.*?)\]/", $logContent, $matchs);
if ($matchs) {
$logVersion = $matchs[1] === "Unreleased" ? $matchs[1] : "v{$matchs[1]}";
}
}
return Base::retSuccess('success', [
'pro' => str_contains(Request::getHost(), "dootask.com") || str_contains(Request::getHost(), "127.0.0.1"),
'github' => env('GITHUB_URL') ?: false,
'updateLog' => $logContent ?: false,
'updateVer' => $logVersion,
]);
}
/**
* @api {get} api/system/get/starthome 19. 启动首页设置信息
*
* @apiDescription 用于判断注册是否需要启动首页
* @apiVersion 1.0.0
@@ -674,7 +861,7 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/email/check 17. 邮件发送测试(限管理员)
* @api {get} api/system/email/check 20. 邮件发送测试(限管理员)
*
* @apiDescription 测试配置邮箱是否能发送邮件
* @apiVersion 1.0.0
@@ -694,14 +881,18 @@ 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 (\Throwable $e) {
// 一般是请求超时
@@ -714,4 +905,226 @@ class SystemController extends AbstractController
}
}
}
/**
* @api {get} api/system/checkin/export 21. 导出签到数据(限管理员)
*
* @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 22. 下载导出的签到数据
*
* @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 23. 获取版本号
*
* @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

@@ -6,9 +6,13 @@ 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\CheckinRemindTask;
use App\Tasks\DeleteBotMsgTask;
use App\Tasks\DeleteTmpTask;
use App\Tasks\EmailNoticeTask;
use App\Tasks\JokeSoupTask;
use App\Tasks\LoopTask;
use Arr;
use Cache;
@@ -16,6 +20,7 @@ use Hhxsv5\LaravelS\Swoole\Task\Task;
use LasseRafn\InitialAvatarGenerator\InitialAvatar;
use Redirect;
use Request;
use Response;
/**
@@ -47,19 +52,22 @@ class IndexController extends InvokeController
*/
public function main()
{
$hash = 'no';
$path = public_path('js/hash');
$murl = url('manifest.txt');
if (file_exists($path)) {
$hash = trim(file_get_contents(public_path('js/hash')));
if (strlen($hash) > 16) {
$hash = 'long';
}
$hotFile = public_path('hot');
$manifestFile = public_path('manifest.json');
if (file_exists($hotFile)) {
$array = Base::json2array(file_get_contents($hotFile));
$style = null;
$script = preg_replace("/^(\/\/(.*?))(:\d+)?\//i", "$1:" . $array['APP_DEV_PORT'] . "/", asset_main("resources/assets/js/app.js"));
} else {
$array = Base::json2array(file_get_contents($manifestFile));
$style = asset_main($array['resources/assets/js/app.js']['css'][0]);
$script = asset_main($array['resources/assets/js/app.js']['file']);
}
return response()->view('main', [
'version' => Base::getVersion(),
'hash' => $hash
])->header('Link', "<{$murl}>; rel=\"prefetch\"");
'style' => $style,
'script' => $script,
])->header('Link', "<" . url('manifest.txt') . ">; rel=\"prefetch\"");
}
/**
@@ -75,28 +83,28 @@ class IndexController extends InvokeController
}
$array = [
"office/web-apps/apps/api/documents/api.js?hash=" . Base::getVersion(),
"office/7.1.1-23/web-apps/vendor/requirejs/require.js",
"office/7.1.1-23/web-apps/apps/api/documents/api.js",
"office/7.1.1-23/sdkjs/common/AllFonts.js",
"office/7.1.1-23/web-apps/vendor/xregexp/xregexp-all-min.js",
"office/7.1.1-23/web-apps/vendor/sockjs/sockjs.min.js",
"office/7.1.1-23/web-apps/vendor/jszip/jszip.min.js",
"office/7.1.1-23/web-apps/vendor/jszip-utils/jszip-utils.min.js",
"office/7.1.1-23/sdkjs/common/libfont/wasm/fonts.js",
"office/7.1.1-23/sdkjs/common/Charts/ChartStyles.js",
"office/7.1.1-23/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.1.1-23/web-apps/apps/presentationeditor/main/app.js",
"office/7.1.1-23/sdkjs/slide/sdk-all-min.js",
"office/7.1.1-23/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.1.1-23/web-apps/apps/documenteditor/main/app.js",
"office/7.1.1-23/sdkjs/word/sdk-all-min.js",
"office/7.1.1-23/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.1.1-23/web-apps/apps/spreadsheeteditor/main/app.js",
"office/7.1.1-23/sdkjs/cell/sdk-all-min.js",
"office/7.1.1-23/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);
@@ -106,25 +114,11 @@ 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 $array;
return Redirect::to(Base::fillUrl('api/system/version'), 301);
}
/**
@@ -133,7 +127,12 @@ class IndexController extends InvokeController
*/
public function avatar()
{
$name = Request::input('name', 'H');
$segment = Request::segment(2);
if ($segment && preg_match('/.*?\.png$/i', $segment)) {
$name = substr($segment, 0, -4);
} else {
$name = Request::input('name', 'H');
}
$size = Request::input('size', 128);
$color = Request::input('color');
$background = Request::input('background');
@@ -189,11 +188,20 @@ class IndexController extends InvokeController
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 DeleteBotMsgTask());
// 周期任务
Task::deliver(new LoopTask());
// 签到提醒
Task::deliver(new CheckinRemindTask());
// 获取笑话/心灵鸡汤
Task::deliver(new JokeSoupTask());
return "success";
}
@@ -243,6 +251,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]);
}
// 下载
@@ -251,7 +282,7 @@ 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);
}
}
}
@@ -299,7 +330,9 @@ class IndexController extends InvokeController
$userAgent = strtolower(Request::server('HTTP_USER_AGENT'));
if ($ext === 'pdf'
&& (str_contains($userAgent, 'electron') || str_contains($userAgent, 'chrome'))) {
return response()->download($file, $name, [], 'inline');
return Response::download($file, $name, [
'Content-Type' => 'application/pdf'
], 'inline');
}
//
if (in_array($ext, File::localExt)) {
@@ -391,9 +424,6 @@ class IndexController extends InvokeController
$list = array_merge(Base::readDir(app_path()), Base::readDir(resource_path()));
$array = [];
foreach ($list as $item) {
if (Base::rightExists($item, "language.all.js")) {
continue;
}
if (Base::rightExists($item, ".php") || Base::rightExists($item, ".vue") || Base::rightExists($item, ".js")) {
$content = file_get_contents($item);
preg_match_all("/(['\"])(.*?)[\u{4e00}-\u{9fa5}\u{FE30}-\u{FFA0}]+([\s\S]((?!\n).)*)\\1/u", $content, $matchs);

View File

@@ -42,6 +42,9 @@ class VerifyCsrfToken extends Middleware
// 聊天发文件
'api/dialog/msg/sendfile/',
// 聊天发匿名消息
'api/dialog/msg/sendanon/',
// 保存文件内容
'api/file/content/save/',
@@ -54,6 +57,12 @@ class VerifyCsrfToken extends Middleware
// 保存汇报
'api/report/store/',
// 签到设置
'api/users/checkin/save/',
// 签到上报
'api/public/checkin/report/',
// 发布桌面端
'desktop/publish/',
];

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

@@ -0,0 +1,249 @@
<?php
namespace App\Ldap;
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 $init = null;
/**
* 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 bool
*/
public static function initConfig()
{
if (is_bool(self::$init)) {
return self::$init;
}
//
$setting = Base::setting('thirdAccessSetting');
if ($setting['ldap_open'] !== 'open') {
return self::$init = false;
}
//
$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'],
]);
return self::$init = true;
} catch (ConfigurationException $e) {
info($e->getMessage());
return self::$init = false;
}
}
/**
* 获取
* @param $username
* @param $password
* @return Model|null
*/
public static function userFirst($username, $password): ?Model
{
if (!self::initConfig()) {
return null;
}
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)
{
if (!self::initConfig()) {
return null;
}
$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)
{
if ($user->isLdap()) {
return;
}
//
if (!self::initConfig()) {
return;
}
//
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;
}
if (!self::initConfig()) {
return;
}
try {
$row = self::static()
->where([
'cn' => $username,
])->first();
$row?->update($array);
} catch (\Exception $e) {
info("[LDAP] update fail: " . $e->getMessage());
}
}
/**
* 删除
* @param $username
* @return void
*/
public static function userDelete($username)
{
if (!self::initConfig()) {
return;
}
try {
$row = self::static()
->where([
'cn' => $username,
])->first();
$row?->delete();
} catch (\Exception $e) {
info("[LDAP] delete fail: " . $e->getMessage());
}
}
}

View File

@@ -21,6 +21,7 @@ use Illuminate\Support\Facades\DB;
* @method static \Illuminate\Database\Eloquent\Model|object|static|null cancelHidden()
* @method static \Illuminate\Database\Eloquent\Builder|static with($relations)
* @method static \Illuminate\Database\Query\Builder|static select($columns = [])
* @method static \Illuminate\Database\Query\Builder|static whereIn($column, $values, $boolean = 'and', $not = false)
* @method static \Illuminate\Database\Query\Builder|static whereNotIn($column, $values, $boolean = 'and')
* @mixin \Eloquent
*/

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)
@@ -94,21 +96,18 @@ class File extends AbstractModel
/**
* 是否有访问权限
* @param $userid
* @param array $userids
* @return int -1:没有权限0:访问权限1:读写权限1000:所有者或创建者
*/
public function getPermission($userid)
public function getPermission(array $userids)
{
if ($userid == $this->userid || $userid == $this->created_id) {
if (in_array($this->userid, $userids) || in_array($this->created_id, $userids)) {
// ① 自己的文件夹 或 自己创建的文件夹
return 1000;
}
$row = $this->getShareInfo();
if ($row) {
$fileUser = FileUser::whereFileId($row->id)->where(function ($query) use ($userid) {
$query->where('userid', 0);
$query->orWhere('userid', $userid);
})->orderByDesc('permission')->first();
$fileUser = FileUser::whereFileId($row->id)->whereIn('userid', $userids)->orderByDesc('permission')->first();
if ($fileUser) {
// ② 在指定共享成员内
return $fileUser->permission;
@@ -183,6 +182,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);
}
@@ -223,16 +223,25 @@ class File extends AbstractModel
}
/**
* 保存前更新pids
* 保存前更新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) {
@@ -241,6 +250,7 @@ class File extends AbstractModel
} else {
$this->pids = '';
}
$this->pshare = $pshare;
if (!$this->save()) {
return false;
}
@@ -249,7 +259,7 @@ class File extends AbstractModel
self::wherePid($this->id)->chunkById(100, function ($lists) {
/** @var self $item */
foreach ($lists as $item) {
$item->saveBeforePids();
$item->saveBeforePP();
}
});
}
@@ -277,6 +287,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
@@ -354,6 +387,23 @@ class File extends AbstractModel
return $array;
}
/**
* code获取文件ID、名称
* @param $code
* @return File
*/
public static function code2IdName($code) {
$arr = explode(",", base64_decode($code));
if (empty($arr)) {
return null;
}
$fileId = intval($arr[0]);
if (empty($fileId)) {
return null;
}
return File::select(['id', 'name'])->find($fileId);
}
/**
* 处理返回图片地址
@@ -375,19 +425,25 @@ class File extends AbstractModel
/**
* 获取文件并检测权限
* @param $id
* @param int $limit 要求权限: 0-访问权限、1-读写权限、1000-所有者或创建者
* @param $permission
* @param int $id
* @param User|array|int $user 要求权限的用户,如:[0, 1]
* @param int $limit 要求权限: 0-访问权限、1-读写权限、1000-所有者或创建者
* @param int $permission
* @return File
*/
public static function permissionFind($id, $limit = 0, &$permission = -1)
public static function permissionFind(int $id, $user, int $limit = 0, int &$permission = -1)
{
$file = File::find($id);
if (empty($file)) {
throw new ApiException('文件不存在或已被删除');
}
//
$permission = $file->getPermission(User::userid());
if ($user instanceof User) {
$userids = $user->isTemp() ? [$user->userid] : [0, $user->userid];
} else {
$userids = is_array($user) ? $user : [$user];
}
$permission = $file->getPermission($userids);
if ($permission < $limit) {
$msg = match ($limit) {
1000 => '仅限所有者或创建者操作',
@@ -494,7 +550,7 @@ class File extends AbstractModel
'created_id' => 0,
]);
$file->handleDuplicateName();
$file->saveBeforePids();
$file->saveBeforePP();
// 移交文件
self::whereUserid($originalUserid)->chunkById(100, function($list) use ($file, $newUserid) {
@@ -504,7 +560,7 @@ class File extends AbstractModel
$item->pid = $file->id;
}
$item->userid = $newUserid;
$item->saveBeforePids();
$item->saveBeforePP();
}
});

View File

@@ -5,7 +5,6 @@ namespace App\Models;
use App\Module\Base;
use Illuminate\Database\Eloquent\SoftDeletes;
use Response;
/**
* App\Models\FileContent
@@ -85,7 +84,7 @@ class FileContent extends AbstractModel
* @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)
{
@@ -93,9 +92,13 @@ class FileContent extends AbstractModel
$content = Base::json2array($content ?: []);
if (in_array($file->type, ['word', 'excel', 'ppt'])) {
if (empty($content)) {
return Response::download(public_path('assets/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 Base::streamDownload(function() use ($filePath) {
echo file_get_contents($filePath);
}, $name);
}
if (empty($content)) {
$content = match ($file->type) {
@@ -124,7 +127,9 @@ class FileContent extends AbstractModel
if ($download) {
$filePath = public_path($path);
if (isset($filePath)) {
return Response::download($filePath, $name);
return Base::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' => base64_encode("{$fileId},{$userid}," . Base::generatePassword()),
]);
$fileLink->save();
} else {
if ($refresh == 'yes') {
$fileLink->code = base64_encode("{$fileId},{$userid}," . Base::generatePassword());
$fileLink->save();
}
}
return [
'id' => $fileId,
'url' => Base::fillUrl('single/file/' . $fileLink->code),
'code' => $fileLink->code,
'num' => $fileLink->num
];
}
}

View File

@@ -12,7 +12,7 @@ namespace App\Models;
* @property int|null $userid 创建人
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property \Illuminate\Support\Carbon|null $end_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()

View File

@@ -281,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);
@@ -537,7 +538,7 @@ class Project extends AbstractModel
$column['project_id'] = $project->id;
ProjectColumn::createInstance($column)->save();
}
$dialog = WebSocketDialog::createGroup(null, $project->userid, 'project');
$dialog = WebSocketDialog::createGroup($project->name, $project->userid, 'project');
if (empty($dialog)) {
throw new ApiException('创建项目聊天室失败');
}

View File

@@ -531,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;
// 工作流
@@ -628,7 +628,7 @@ class ProjectTask extends AbstractModel
if ($this->complete_at) {
throw new ApiException('任务已完成');
}
$this->completeTask(Carbon::now());
$this->completeTask(Carbon::now(), isset($newFlowItem) ? $newFlowItem->name : null);
} else {
// 标记未完成
if (!$this->complete_at) {
@@ -649,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) {
@@ -676,11 +679,12 @@ 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()) {
@@ -699,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);
@@ -760,14 +764,13 @@ class ProjectTask extends AbstractModel
});
}
$newStringAt = $this->start_at ? ($this->start_at->toDateTimeString() . '~' . $this->end_at->toDateTimeString()) : '';
$this->addLog("修改{任务}时间", [
$newDesc = $desc ? "(备注:{$desc}" : "";
$this->addLog("修改{任务}时间" . $newDesc, [
'change' => [$oldStringAt, $newStringAt]
]);
// 修改计划时间需要重置任务邮件提醒日志
ProjectTaskMailLog::whereTaskId($this->id)->delete();
$this->taskPush(null, 3, $newDesc);
}
// 以下顶级任务可修改
// 以下顶级任务可修改
if ($this->parent_id === 0) {
// 重复周期
$loopAt = $this->loop_at;
@@ -1177,15 +1180,22 @@ class ProjectTask extends AbstractModel
/**
* 标记已完成、未完成
* @param Carbon|null $complete_at 完成时间
* @param String $complete_name 已完成名称(留空为:已完成)
* @return bool
*/
public function completeTask($complete_at)
public function completeTask($complete_at, $complete_name = null)
{
AbstractModel::transaction(function () use ($complete_at) {
AbstractModel::transaction(function () use ($complete_at, $complete_name) {
$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) {
@@ -1196,8 +1206,16 @@ class ProjectTask extends AbstractModel
if (!$this->hasOwner()) {
throw new ApiException('请先领取任务');
}
if (empty($complete_name)) {
$complete_name = '已完成';
}
$this->complete_at = $complete_at;
$this->addLog("标记{任务}已完成");
$this->addLog("标记{任务}{$complete_name}");
if ($addMsg) {
WebSocketDialogMsg::sendMsg(null, $this->dialog_id, 'notice', [
'notice' => "标记任务{$complete_name}"
], 0, true, true);
}
}
$this->save();
});
@@ -1268,6 +1286,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();
@@ -1354,28 +1373,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);
@@ -1383,6 +1436,62 @@ class ProjectTask extends AbstractModel
}
}
/**
* 任务提醒
* @param $userids
* @param int $type 0-新任务、1-即将超时、2-已超时、3-修改时间
* @param string $suffix 描述后缀
* @return void
*/
public function taskPush($userids, int $type, string $suffix = "")
{
if ($userids === null) {
$userids = $this->taskUser->pluck('userid')->toArray();
}
if (empty($userids)) {
return;
}
$owners = $this->taskUser->pluck('owner', 'userid')->toArray();
$receivers = User::whereIn('userid', $userids)->whereNull('disable_at')->get();
if (empty($receivers)) {
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 ($receivers as $receiver) {
$data = [
'type' => $type,
'userid' => $receiver->userid,
'task_id' => $this->id,
];
if (in_array($type, [1, 2]) && ProjectTaskPushLog::where($data)->exists()) {
continue;
}
//
$replace = $owners[$receiver->userid] ? "您负责的任务" : "您协助的任务";
$dialog = WebSocketDialog::checkUserDialog($botUser, $receiver->userid);
if ($dialog) {
ProjectTaskPushLog::createInstance($data)->save();
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'text', [
'text' => str_replace("您的任务", $replace, $text) . $suffix
], $botUser->userid);
}
}
}
/**
* 获取任务
* @param $task_id
@@ -1390,7 +1499,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;
}
/**

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 提醒类型0 任务开始提醒1 距离到期提醒2到期超时提醒
* @property int|null $is_send 邮件发送是否成功0否1是
* @property string|null $send_error 邮件发送错误详情
* @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

@@ -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;
}

View File

@@ -128,13 +128,11 @@ class UmengAlias extends AbstractModel
'type' => 'customizedcast',
'alias_type' => 'userid',
'alias' => $alias,
'mipush' => true,
'mi_activity' => 'app.eeui.umeng.activity.MfrMessageActivity',
'policy' => [
'expire_time' => Carbon::now()->addSeconds($seconds)->toDateTimeString(),
],
'channel_properties' => [
'channel_activity' => 'app.eeui.umeng.activity.MfrMessageActivity',
'huawei_channel_importance' => 'NORMAL'
],
]
]);
default:
@@ -150,7 +148,7 @@ class UmengAlias extends AbstractModel
*/
public static function pushMsgToUserid($userid, $array)
{
$builder = self::select(['id', 'platform', 'alias']);
$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)) {
@@ -158,11 +156,15 @@ class UmengAlias extends AbstractModel
}
$builder
->orderByDesc('id')
->chunkById(100, function ($rows) use ($array) {
$lists = $rows->groupBy('platform');
foreach ($lists as $platform => $list) {
$alias = $list->pluck('alias')->implode(',');
self::pushMsgToAlias($alias, $platform, $array);
->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,9 +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 头像
@@ -31,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)
@@ -38,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)
@@ -56,6 +61,7 @@ use Carbon\Carbon;
* @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)
@@ -69,20 +75,11 @@ class User extends AbstractModel
'updated_at',
];
protected $defaultAvatarMode = 'auto'; // auto自动生成system系统默认
// 默认头像类型:auto自动生成system系统默认
public static $defaultAvatarMode = 'auto';
/**
* 更新数据校验
* @param array $param
*/
public function updateInstance(array $param)
{
parent::updateInstance($param);
//
if (isset($param['line_at']) && $this->userid) {
Cache::put("User::online:" . $this->userid, time(), Carbon::now()->addSeconds(30));
}
}
// 基本信息的字段
public static $basicField = ['userid', 'email', 'nickname', 'profession', 'department', 'userimg', 'bot', 'az', 'pinyin', 'line_at', 'disable_at'];
/**
* 昵称
@@ -101,17 +98,7 @@ class User extends AbstractModel
*/
public function getUserimgAttribute($value)
{
if ($value && !str_contains($value, 'avatar/')) {
return Base::fillUrl($value);
}
if ($this->defaultAvatarMode === 'auto') {
// 自动生成头像
return url("avatar?name=" . urlencode($this->nickname));
} else {
// 系统默认头像
$name = ($this->userid - 1) % 21 + 1;
return url("images/avatar/default_{$name}.png");
}
return self::getAvatar($this->userid, $value, $this->email, $this->nickname);
}
/**
@@ -127,13 +114,47 @@ 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 "";
}
$key = "UserDepartment::" . md5(Cache::get("UserDepartment::rand") . '-' . implode(',' , $this->department));
$list = Cache::remember($key, now()->addMonth(), function() {
$list = UserDepartment::select(['id', 'owner_userid', 'name'])->whereIn('id', $this->department)->take(10)->get();
return $list->toArray();
});
$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 || Cache::get("User::online:" . $this->userid) === "on";
if ($online) {
return true;
}
@@ -141,9 +162,45 @@ class User extends AbstractModel
}
/**
* 判断是否管理员
* 返回是否LDAP用户
* @return bool
*/
public function isLdap()
{
return in_array('ldap', $this->identity);
}
/**
* 返回是否临时帐号
* @return bool
*/
public function isTemp()
{
return in_array('temp', $this->identity);
}
/**
* 返回是否禁用帐号(离职)
* @return bool
*/
public function isDisable()
{
return in_array('disable', $this->identity);
}
/**
* 返回是否管理员
* @return bool
*/
public function isAdmin()
{
return in_array('admin', $this->identity);
}
/**
* 判断是否管理员
*/
public function checkAdmin()
{
$this->identity('admin');
}
@@ -180,12 +237,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();
});
}
/** ***************************************************************************************** */
@@ -201,7 +278,7 @@ class User extends AbstractModel
*/
public static function reg($email, $password, $other = [])
{
//邮箱
// 邮箱
if (!Base::isEmail($email)) {
throw new ApiException('请输入正确的邮箱地址');
}
@@ -214,9 +291,9 @@ class User extends AbstractModel
}
throw new ApiException('邮箱地址已存在');
}
//密码
// 密码
self::passwordPolicy($password);
//开始注册
// 开始注册
$encrypt = Base::generatePassword(6);
$inArray = [
'encrypt' => $encrypt,
@@ -230,7 +307,21 @@ class User extends AbstractModel
$user = User::createInstance($inArray);
$user->az = Base::getFirstCharter($user->nickname);
$user->pinyin = Base::cn2pinyin($user->nickname);
$user->save();
if ($user->save()) {
$setting = Base::setting('system');
$reg_identity = $setting['reg_identity'] ?: 'normal';
$all_group_autoin = $setting['all_group_autoin'] ?: 'yes';
// 注册临时身份
if ($reg_identity === 'temp') {
$user->identity = Base::arrayImplode(array_merge(array_diff($user->identity, ['temp']), ['temp']));
$user->save();
}
// 加入全员群组
if ($all_group_autoin === 'yes') {
$dialog = WebSocketDialog::whereGroupType('all')->orderByDesc('id')->first();
$dialog?->joinGroup($user->userid, 0);
}
}
return $user->find($user->userid);
}
@@ -368,9 +459,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();
@@ -397,7 +491,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;
@@ -430,10 +525,10 @@ class User extends AbstractModel
if (isset($_A["__static_userid2basic_" . $userid])) {
return $_A["__static_userid2basic_" . $userid];
}
$fields = ['userid', 'email', 'nickname', 'profession', 'userimg', 'az', 'pinyin', 'line_at', 'disable_at'];
$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 ?: []);
}
@@ -474,6 +569,42 @@ 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);
}
// 机器人头像
switch ($email) {
case 'system-msg@bot.system':
return url("images/avatar/default_system.png");
case 'task-alert@bot.system':
return url("images/avatar/default_task.png");
case 'check-in@bot.system':
return url("images/avatar/default_checkin.png");
case 'anon-msg@bot.system':
return url("images/avatar/default_anon.png");
case '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
@@ -504,4 +635,61 @@ 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();
}
//
switch ($key) {
case 'system-msg':
$update['nickname'] = '系统消息';
break;
case 'task-alert':
$update['nickname'] = '任务提醒';
break;
case 'check-in':
$update['nickname'] = '签到打卡';
break;
case 'anon-msg':
$update['nickname'] = '匿名消息';
break;
case 'bot-manager':
$update['nickname'] = '机器人管理';
break;
}
}
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;
}
}

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

@@ -0,0 +1,34 @@
<?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 string|null $webhook_url 消息webhook地址
* @property int|null $webhook_num 消息webhook请求次数
* @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)
* @method static \Illuminate\Database\Eloquent\Builder|UserBot whereWebhookNum($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserBot whereWebhookUrl($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,34 +38,50 @@ 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();
@@ -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;
@@ -15,6 +17,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @property string|null $type 对话类型
* @property string|null $group_type 聊天室类型
* @property string|null $name 对话名称
* @property string|null $avatar 头像(群)
* @property string|null $last_at 最后消息时间
* @property int|null $owner_id 群主用户ID
* @property \Illuminate\Support\Carbon|null $created_at
@@ -26,6 +29,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog newQuery()
* @method static \Illuminate\Database\Query\Builder|WebSocketDialog onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog query()
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog whereAvatar($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog whereDeletedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog whereGroupType($value)
@@ -43,6 +47,16 @@ class WebSocketDialog extends AbstractModel
{
use SoftDeletes;
/**
* 头像地址
* @param $value
* @return string
*/
public function getAvatarAttribute($value)
{
return $value ? Base::fillUrl($value) : $value;
}
/**
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
@@ -54,10 +68,24 @@ class WebSocketDialog extends AbstractModel
/**
* 格式化对话
* @param int $userid 会员ID
* @param bool $hasData
* @return $this
*/
public function formatData($userid)
public function formatData($userid, $hasData = false)
{
$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;
};
//
$this->pinyin = Base::cn2pinyin($this->name);
$this->top_at = $this->top_at ?? $dialogUserFun('top_at');
$this->user_at = $this->user_at ?? $dialogUserFun('updated_at');
$this->user_ms = WebSocketDialogUser::userMs($this->user_at);
$this->quick_msgs = [];
//
if (isset($this->search_msg_id)) {
// 最后消息 (搜索预览消息)
$this->last_msg = WebSocketDialogMsg::whereDialogId($this->id)->find($this->search_msg_id);
@@ -66,23 +94,20 @@ class WebSocketDialog extends AbstractModel
// 最后消息
$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;
if ($this->unread > 0) {
$this->mention = $unreadBuilder->clone()->whereMention(1)->count();
$this->last_umid = intval($unreadBuilder->clone()->orderByDesc('msg_id')->value('msg_id'));
}
$this->mark_unread = $this->mark_unread ?? WebSocketDialogUser::whereDialogId($this->id)->whereUserid($userid)->value('mark_unread');
$this->generateUnread($userid, $hasData);
// 未读标记
$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->people = WebSocketDialogUser::whereDialogId($this->id)->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->bot = 0;
switch ($this->type) {
case "user":
$dialog_user = WebSocketDialogUser::whereDialogId($this->id)->where('userid', '!=', $userid)->first();
@@ -92,6 +117,15 @@ class WebSocketDialog extends AbstractModel
$basic = User::userid2basic($dialog_user->userid);
if ($basic) {
$this->name = $basic->nickname;
$this->bot = $basic->bot;
if ($basic->email === 'check-in@bot.system') {
$this->quick_msgs = [
[
'key' => 'checkin',
'label' => Base::Lang('我要签到')
]
];
}
} else {
$this->name = 'non-existent';
$this->dialog_delete = 1;
@@ -99,45 +133,108 @@ class WebSocketDialog extends AbstractModel
$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();
if ($this->group_info) {
$this->name = $this->group_info->name;
} else {
$this->name = '[Delete]';
$this->dialog_delete = 1;
}
} 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();
if ($this->group_info) {
$this->name = $this->group_info->name;
} else {
$this->name = '[Delete]';
$this->dialog_delete = 1;
}
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();
}
return $this;
}
/**
* 生成未读数据
* @param $userid
* @param $positionData
* @return $this
*/
public function generateUnread($userid, $positionData = true)
{
$builder = WebSocketDialogMsgRead::whereDialogId($this->id)->whereUserid($userid)->whereReadAt(null);
$this->unread = $builder->count();
$this->mention = 0;
if ($this->unread > 0) {
$this->mention = $builder->clone()->whereMention(1)->count();
}
if ($positionData) {
$array = [];
// @我的消息
if ($this->mention > 0
&& $mention_id = intval($builder->clone()->whereMention(1)->orderByDesc('msg_id')->value('msg_id'))) {
$array[] = [
'msg_id' => $mention_id,
'label' => Base::Lang('@我的消息'),
];
}
// 最早一条未读消息
if ($this->unread > 0
&& $first_id = intval($builder->clone()->orderBy('msg_id')->value('msg_id'))) {
$array[] = [
'msg_id' => $first_id,
'label' => '{UNREAD}'
];
}
//
$this->position_msgs = $array;
}
return $this;
}
/**
* 加入聊天室
* @param int|array $userid 加入的会员ID或会员ID组
* @param int $inviter 邀请人
* @param int|array $userid 加入的会员ID或会员ID组
* @param int $inviter 邀请人
* @param bool|null $important 重要人员(null不修改、bool修改)
* @return bool
*/
public function joinGroup($userid, $inviter)
public function joinGroup($userid, $inviter, $important = null)
{
AbstractModel::transaction(function () use ($inviter, $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,
], [
'inviter' => $inviter,
]);
], $updateData, [], $isInsert);
if ($isInsert) {
WebSocketDialogMsg::sendMsg(null, $this->id, 'notice', [
'notice' => User::userid2nickname($value) . " 已加入群组"
], $inviter, true, true);
}
}
}
});
@@ -150,32 +247,48 @@ class WebSocketDialog extends AbstractModel
/**
* 退出聊天室
* @param int|array $userid 加入的会员ID或会员ID组
* @param $type
* @param int|array $userid 退出的会员ID或会员ID组
* @param string $type exit|remove
* @param bool $checkDelete 是否检查删除
* @param bool $pushMsg 是否推送消息
*/
public function exitGroup($userid, $type = 'exit')
public function exitGroup($userid, $type = 'exit', $checkDelete = true, $pushMsg = true)
{
$typeDesc = $type === 'remove' ? '移出' : '退出';
AbstractModel::transaction(function () use ($typeDesc, $type, $userid) {
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 ($typeDesc, $type) {
$builder->chunkById(100, function($list) use ($pushMsg, $checkDelete, $typeDesc, $type) {
/** @var WebSocketDialogUser $item */
foreach ($list as $item) {
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);
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);
}
}
});
});
@@ -231,6 +344,26 @@ class WebSocketDialog extends AbstractModel
}
}
/**
* 检查禁言
* @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
@@ -240,10 +373,16 @@ class WebSocketDialog extends AbstractModel
if (!isset($this->appendattrs['groupName'])) {
$name = $this->name;
if ($this->type == "group") {
if ($this->group_type === 'project') {
$name = \DB::table('projects')->where('dialog_id', $this->id)->value('name');
} elseif ($this->group_type === 'task') {
$name = \DB::table('project_tasks')->where('dialog_id', $this->id)->value('name');
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;
@@ -280,6 +419,20 @@ 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
@@ -332,7 +485,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) {
@@ -340,7 +493,7 @@ class WebSocketDialog extends AbstractModel
WebSocketDialogUser::createInstance([
'dialog_id' => $dialog->id,
'userid' => $value,
'important' => $group_type != 'user'
'important' => !in_array($group_type, ['user', 'all'])
])->save();
}
}
@@ -350,40 +503,42 @@ class WebSocketDialog extends AbstractModel
/**
* 获取会员对话(没有自动创建)
* @param int $userid 会员ID
* @param int $userid2 另一个会员ID
* @param User $user 发起会话的会员
* @param int $receiver 另一个会员ID
* @return self|null
*/
public static function checkUserDialog($userid, $userid2)
public static function checkUserDialog($user, $receiver)
{
if ($userid == $userid2) {
$userid2 = 0;
if ($user->userid == $receiver) {
$receiver = 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')
->where('u1.userid', $userid)
->where('u2.userid', $userid2)
->where('u1.userid', $user->userid)
->where('u2.userid', $receiver)
->where('web_socket_dialogs.type', 'user')
->first();
if ($dialogUser) {
return $dialogUser;
}
return AbstractModel::transaction(function () use ($userid2, $userid) {
if ($receiver > 0 && $user->isTemp()) {
throw new ApiException('无法发起会话');
}
return AbstractModel::transaction(function () use ($receiver, $user) {
$dialog = self::createInstance([
'type' => 'user',
]);
$dialog->save();
WebSocketDialogUser::createInstance([
'dialog_id' => $dialog->id,
'userid' => $userid,
'userid' => $user->userid,
])->save();
WebSocketDialogUser::createInstance([
'dialog_id' => $dialog->id,
'userid' => $userid2,
'userid' => $receiver,
])->save();
return $dialog;
});
}
}

View File

@@ -2,11 +2,12 @@
namespace App\Models;
use App\Exceptions\ApiException;
use Carbon\Carbon;
use App\Models\User;
use App\Module\Base;
use App\Tasks\PushTask;
use App\Exceptions\ApiException;
use App\Tasks\WebSocketDialogMsgTask;
use Carbon\Carbon;
use Hhxsv5\LaravelS\Swoole\Task\Task;
use Illuminate\Database\Eloquent\SoftDeletes;
@@ -18,11 +19,16 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @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
@@ -42,11 +48,16 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @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)
@@ -244,28 +255,154 @@ class WebSocketDialogMsg extends AbstractModel
$dialog = WebSocketDialog::find($this->dialog_id);
$dialog?->pushMsg('update', $resData);
//
return Base::retSuccess('sucess', $resData);
return Base::retSuccess('success', $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 $userids
* @param int $sender 发送的会员ID
* @param array|int $dialogids
* @param array|int $userids
* @param User $user 发送的会员
* @return mixed
*/
public function forwardMsg($userids, $sender)
public function forwardMsg($dialogids, $userids, $user)
{
return AbstractModel::transaction(function() use ($sender, $userids) {
return AbstractModel::transaction(function() use ($dialogids, $user, $userids) {
$originalMsg = Base::json2array($this->getRawOriginal('msg'));
$msgs = [];
foreach ($userids as $userid) {
if (!User::whereUserid($userid)->exists()) {
continue;
$already = [];
if ($dialogids) {
if (!is_array($dialogids)) {
$dialogids = [$dialogids];
}
$dialog = WebSocketDialog::checkUserDialog($sender, $userid);
if ($dialog) {
$res = self::sendMsg($dialog->id, 0, $this->type, $this->getOriginal('msg'), $sender);
foreach ($dialogids as $dialogid) {
$res = self::sendMsg(null, $dialogid, $this->type, $originalMsg, $user->userid);
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($user, $userid);
if ($dialog && !in_array($dialog->id, $already)) {
$res = self::sendMsg(null, $dialog->id, $this->type, $originalMsg, $user->userid);
if (Base::isSuccess($res)) {
$msgs[] = $res['data'];
}
}
}
}
@@ -277,9 +414,34 @@ class WebSocketDialogMsg extends AbstractModel
/**
* 删除消息
* @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();
//
$dialogDatas = WebSocketDialog::whereIn('id', $dialogIds)->get();
foreach ($dialogDatas as $dialogData) {
$dialogData->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())) {
@@ -293,16 +455,13 @@ class WebSocketDialogMsg extends AbstractModel
self::whereId($this->reply_id)->decrement('reply_num');
}
//
$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();
}
//
$dialog = WebSocketDialog::find($this->dialog_id);
if ($dialog) {
$userids = $dialog->dialogUser->pluck('userid')->toArray();
$dialogData = $this->webSocketDialog;
if ($dialogData) {
foreach ($dialogData->dialogUser as $dialogUser) {
$dialogUser->updated_at = Carbon::now();
$dialogUser->save();
}
$userids = $dialogData->dialogUser->pluck('userid')->toArray();
PushTask::push([
'userid' => $userids,
'msg' => [
@@ -311,34 +470,51 @@ class WebSocketDialogMsg extends AbstractModel
'data' => [
'id' => $this->id,
'dialog_id' => $this->dialog_id,
'last_msg' => $last_msg,
'last_msg' => $dialogData->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)
public function previewMsg($preserveHtml = false, $data = null)
{
switch ($this->type) {
if ($data === null) {
$data = [
'type' => $this->type,
'msg' => $this->msg,
];
}
switch ($data['type']) {
case 'text':
return $this->previewTextMsg($this->msg['text'], $preserveHtml);
return $this->previewTextMsg($data['msg']['text'], $preserveHtml);
case 'record':
return "[语音]";
case 'meeting':
return "[会议] ${$this->msg['name']}";
return "[会议] ${$data['msg']['name']}";
case 'file':
if ($this->msg['type'] == 'img') {
if ($data['msg']['type'] == 'img') {
return "[图片]";
}
return "[文件] {$this->msg['name']}";
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 "[未知的消息]";
}
@@ -351,12 +527,29 @@ class WebSocketDialogMsg extends AbstractModel
public function generateMsgKey()
{
return match ($this->type) {
'text' => strip_tags($this->msg['text']),
'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
@@ -367,13 +560,13 @@ class WebSocketDialogMsg extends AbstractModel
{
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=\"emoticon\"[^>]*?>/", "[动画表情]", $text);
$text = preg_replace("/<img\s+class=\"browse\"[^>]*?>/", "[图片]", $text);
if ($preserveHtml) {
return $text;
} else {
return strip_tags($text);
if (!$preserveHtml) {
$text = strip_tags($text);
$text = str_replace(["&nbsp;", "&amp;", "&lt;", "&gt;"], [" ", "&", "<", ">"], $text);
}
return $text;
}
/**
@@ -385,18 +578,20 @@ 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|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));
if (Base::imgThumb(public_path($tmpPath), public_path($tmpPath) . "_thumb.jpg", 320, 0)) {
$tmpPath .= "_thumb.jpg";
$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]}:{$tmpPath}::]", $text);
$text = str_replace($matchs[0][$key], "[:IMAGE:browse:{$imageSize[0]}:{$imageSize[1]}:{$imagePath}::]", $text);
}
}
// 表情图片
@@ -404,50 +599,144 @@ class WebSocketDialogMsg extends AbstractModel
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("/<img[^>]*?src=([\"'])(.*?\.(png|jpg|jpeg|gif))\\1[^>]*?>/is", $text, $matchs);
foreach ($matchs[2] as $key => $str) {
$tmpPath = "uploads/chat/" . date("Ym") . "/" . $dialog_id . "/";
Base::makeDir(public_path($tmpPath));
$tmpPath .= md5s($str) . "." . $matchs[3][$key];
if (file_exists(public_path($tmpPath))) {
$imagesize = getimagesize(public_path($tmpPath));
if (Base::imgThumb(public_path($tmpPath), public_path($tmpPath) . "_thumb.jpg", 320, 0)) {
$tmpPath .= "_thumb.jpg";
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]}:{$tmpPath}::]", $text);
$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($tmpPath), $image)) {
$imagesize = getimagesize(public_path($tmpPath));
if (Base::imgThumb(public_path($tmpPath), public_path($tmpPath) . "_thumb.jpg", 320, 0)) {
$tmpPath .= "_thumb.jpg";
} 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]}:{$tmpPath}::]", $text);
$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, User::auth());
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]) >= 8) {
$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[2] as $key => $str) {
// 处理快捷消息
preg_match_all("/<span[^>]*?data-quick-key=([\"'])(.*?)\\1[^>]*?>(.*?)<\/span>/is", $text, $matchs);
foreach ($matchs[0] as $key => $str) {
$quickKey = $matchs[2][$key];
$quickLabel = $matchs[3][$key];
if ($quickKey && $quickLabel) {
$quickKey = str_replace(":", "", $quickKey);
$quickLabel = str_replace(":", "", $quickLabel);
$text = str_replace($str, "[:QUICK:{$quickKey}:{$quickLabel}:]", $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;
$text = str_replace($matchs[0][$key], "<a href=\"{$herf}\" target=\"_blank\">{$title}</a>", $text);
preg_match("/\/single\/file\/(.*?)$/i", strip_tags($title), $match);
if ($match && strlen($match[1]) >= 8) {
$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]) >= 8) {
$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> <a>');
@@ -455,44 +744,133 @@ class WebSocketDialogMsg extends AbstractModel
$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("/\[:QUICK:(.*?):(.*?):\]/i", "<span data-quick-key=\"$1\">$2</span>", $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 int $reply_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, $reply_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([
'dialog_id' => $dialog_id,
'reply_id' => $reply_id,
'userid' => $sender ?: User::userid(),
'type' => $type,
'msg' => $msg,
'read' => 0,
]);
if ($reply_id > 0) {
self::whereId($reply_id)->increment('reply_num');
}
AbstractModel::transaction(function () use ($dialogMsg) {
$dialog = WebSocketDialog::find($dialogMsg->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_type = $dialog->type;
if (str_contains($msg['text'], '<img ')) {
$mtype = str_contains($msg['text'], '"emoticon"') ? 'emoticon' : 'image';
}
preg_match_all("/@([A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,6})/i", $msg['text'], $matchs);
foreach($matchs[0] as $key => $item) {
$aiUser = User::whereEmail($matchs[1][$key])->whereDisableAt(null)->first();
if ($aiUser) {
$msg['text'] = str_replace($item, "<span class=\"mention user\" data-id=\"{$aiUser->userid}\">@{$aiUser->nickname}</span>", $msg['text']);
}
}
} 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();
WebSocketDialogUser::whereDialogId($dialog->id)->update(['updated_at' => $dialog->updated_at]);
});
//
$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,9 @@ 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 阅读时间
@@ -26,6 +27,7 @@ use Carbon\Carbon;
* @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

@@ -2,6 +2,8 @@
namespace App\Models;
use Carbon\Carbon;
/**
* App\Models\WebSocketDialogUser
*
@@ -10,10 +12,12 @@ 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 int|null $important 是否不可移出(项目、任务、部门人员)
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\Models\WebSocketDialog|null $webSocketDialog
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser query()
@@ -23,6 +27,7 @@ namespace App\Models;
* @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)
@@ -30,5 +35,24 @@ namespace App\Models;
*/
class WebSocketDialogUser extends AbstractModel
{
/**
* @return \Illuminate\Database\Eloquent\Relations\HasOne
*/
public function webSocketDialog(): \Illuminate\Database\Eloquent\Relations\HasOne
{
return $this->hasOne(WebSocketDialog::class, 'id', 'dialog_id');
}
/**
* 转时间戳加上现在的毫秒
* @param $time
* @return int
*/
public static function userMs($time = 0)
{
if (empty($time)) {
return Carbon::now()->getPreciseTimestamp(3);
}
return intval(Carbon::parse($time)->timestamp . substr(Carbon::now()->getPreciseTimestamp(3), -3));
}
}

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

@@ -11,6 +11,7 @@ use Illuminate\Support\Facades\Config;
use Overtrue\Pinyin\Pinyin;
use Redirect;
use Request;
use Response;
use Storage;
use Validator;
@@ -76,6 +77,16 @@ class Base
});
}
/**
* 如果header没有则通过input读取
* @param $key
* @return mixed|string
*/
public static function headerOrInput($key)
{
return Base::nullShow(Request::header($key), Request::input($key));
}
/**
* 获取版本号
* @return string
@@ -94,7 +105,7 @@ class Base
{
global $_A;
if (!isset($_A["__static_client_version"])) {
$_A["__static_client_version"] = Request::header('version') ?: '0.0.1';
$_A["__static_client_version"] = self::headerOrInput('version') ?: '0.0.1';
}
return $_A["__static_client_version"];
}
@@ -106,11 +117,21 @@ class Base
*/
public static function checkClientVersion($min)
{
if (version_compare(Base::getClientVersion(), $min, '<')) {
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
@@ -873,36 +894,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
@@ -1018,6 +1064,20 @@ class Base
}
}
/**
* 正则判断是否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;
}
}
/**
* 判断身份证是否正确
* @param $id
@@ -1252,6 +1312,27 @@ class Base
return $setting;
}
/**
* 时间转毫秒时间戳
* @param $time
* @return float|int
*/
public static function strtotimeM($time)
{
if (str_contains($time, '.')) {
list($t, $m) = explode(".", $time);
if (is_string($t)) {
$t = strtotime($t);
}
$time = $t . str_pad($m, 3, "0", STR_PAD_LEFT);
}
if (is_numeric($time)) {
return (int) str_pad($time, 13, "0");
} else {
return strtotime($time) * 1000;
}
}
/**
* 获取设置值
* @param $setname
@@ -1386,45 +1467,67 @@ class Base
*/
public static function Lang($val)
{
$repArray = [];
if (is_array($val)) {
if (self::strExists($val[0], '%') && count($val) > 1) {
$repArray = array_slice($val, 1);
}
$val = $val[0];
}
$data = self::langData();
if (isset($data[$val]) && $data[$val] !== null) {
$val = $data[$val];
if (isset($data[$val])) {
return $data[$val] ?: $val;
}
if ($repArray) {
foreach ($repArray as $item) {
$val = self::strReplaceLimit('%', $item, $val, 1);
}
}
return $val;
}
/**
* 加载语言数据
* @param bool $refresh
* @return array
*/
public static function langData($refresh = false)
{
global $_A;
if (!isset($_A["__static_langdata"]) || $refresh === true) {
$_A["__static_langdata"] = [];
$language = trim(Request::header('language'));
$langpath = resource_path('lang/' . $language . '/general.php');
if (file_exists($langpath)) {
$data = include $langpath;
if (is_array($data)) {
$_A["__static_langdata"] = $data;
foreach ($data as $key => $item) {
if (str_contains($key, "(*)")) {
$regex = str_replace("(*)", "~%~", $key);
$regex = preg_quote($regex);
$regex = str_replace("~%~", "(.*?)", $regex);
$regex = "/^" . $regex . "$/";
if (preg_match($regex, $val)) {
$r = $item ?: $key;
return preg_replace_callback($regex, function($m) use ($r) {
$i = 0;
foreach ($m as $v) {
if ($i > 0) {
$r = preg_replace("/\(\*\)/", $v, $r, 1);
}
$i++;
}
return $r;
}, $val);
}
}
}
return $_A["__static_langdata"];
//
$undefinedPath = base_path('language/undefined-api.txt');
if (self::$undefinedLang === null) {
self::$undefinedLang = [];
if (file_exists($undefinedPath)) {
self::$undefinedLang = explode("\n", file_get_contents($undefinedPath));
self::$undefinedLang = array_values(array_filter(array_unique(self::$undefinedLang)));
}
}
if (!in_array($val, self::$undefinedLang)) {
self::$undefinedLang[] = $val;
@file_put_contents(base_path('language/undefined-api.txt'), "$val\n", FILE_APPEND);
}
return $val;
}
private static $undefinedLang = null;
/**
* 加载语言数据
* @return array
*/
public static function langData()
{
global $_A;
$language = trim(self::headerOrInput('language'));
if (!isset($_A["__static_langdata_" . $language])) {
$_A["__static_langdata_" . $language] = [];
$langpath = resource_path('lang/' . $language . '.php');
if (file_exists($langpath)) {
$data = include $langpath;
if (is_array($data)) {
$_A["__static_langdata_" . $language] = $data;
}
}
}
return $_A["__static_langdata_" . $language];
}
/**
@@ -1434,10 +1537,8 @@ class Base
*/
public static function jsonEcho($param)
{
global $_GPC;
//
$json = json_encode($param);
$callback = $_GPC['callback'];
$callback = Request::input('callback');
if ($callback) {
return $callback . '(' . $json . ')';
} else {
@@ -1454,11 +1555,11 @@ class Base
*/
public static function retSuccess($msg, $data = [], $ret = 1)
{
return array(
return [
'ret' => $ret,
'msg' => self::Lang($msg),
'data' => $data
);
];
}
/**
@@ -1470,11 +1571,11 @@ class Base
*/
public static function retError($msg, $data = [], $ret = 0)
{
return array(
return [
'ret' => $ret,
'msg' => self::Lang($msg),
'data' => $data
);
];
}
/**
@@ -1810,7 +1911,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"];
}
@@ -2103,7 +2204,7 @@ class Base
{
global $_A;
if (!isset($_A["__static_token"])) {
$_A["__static_token"] = Base::nullShow(Request::header('token'), Request::input('token'));
$_A["__static_token"] = self::headerOrInput('token');
}
return $_A["__static_token"];
}
@@ -2307,12 +2408,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("您没有选择要上传的文件");
}
@@ -2357,30 +2459,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', 'svg',
'rar', 'zip', 'jar', '7-zip', 'tar', 'gzip', '7z', 'gz', 'apk', 'dmg',
'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', 'plist',
'mp3', 'wav', 'mp4', 'flv',
'avi', 'mov', 'wmv', 'mkv', '3gp', 'rm',
'xmind',
'rp',
];
$type = []; // 不限制上传文件类型
break;
default:
return Base::retError('错误的类型参数');
@@ -2412,26 +2491,32 @@ class Base
}
}
}
$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']));
@@ -2860,8 +2945,10 @@ class Base
return '';
}
if (!preg_match("/^[a-zA-Z0-9_.]+$/", $str)) {
$pinyin = new Pinyin();
$str = $pinyin->permalink($str, '');
$str = Cache::rememberForever("cn2pinyin:" . md5($str), function() use ($str) {
$pinyin = new Pinyin();
return $pinyin->permalink($str, '');
});
}
return $str;
}
@@ -3094,4 +3181,52 @@ class Base
throw new ApiException($validator->errors()->first());
}
}
/**
* 流下载,解决没有后缀无法下载的问题
* @param $callback
* @param $name
* @return mixed
*/
public static function streamDownload($callback, $name = null) {
if ($name && !str_contains($name, '.')) {
$name .= ".";
}
return Response::streamDownload($callback, $name);
}
/**
* 判断是否工作日
* @param string $Ymd 年月日20220102
* @return int
* 0: 工作日
* 1: 非工作日
* 2: 获取不到远程数据的非工作日(周六、日)
* 所以可以用>0来判断是否工作日
*/
public static function isHoliday($Ymd) {
$time = strtotime($Ymd . " 00:00:00");
$holidayKey = "holiday::" . date("Ym", $time);
$holidayData = Cache::remember($holidayKey, now()->addMonth(), function () use ($time) {
$apiMonth = date("Ym", $time);
$apiResult = Ihttp::ihttp_request("https://api.apihubs.cn/holiday/get?field=date&month={$apiMonth}&workday=2&size=31", [], [], 20);
if (Base::isError($apiResult)) {
info('[holiday] get error');
return [];
}
$apiResult = Base::json2array($apiResult['data']);
if ($apiResult['code'] !== 0) {
info('[holiday] result error');
return [];
}
return array_map(function ($item) {
return $item['date'];
}, $apiResult['data']['list']);
});
if (empty($holidayData)) {
Cache::forget($holidayKey);
return in_array(date("w", $time), [0, 6]) ? 2 : 0;
}
return in_array($Ymd, $holidayData) ? 1 : 0;
}
}

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

@@ -69,7 +69,7 @@ class WebSocketService implements WebSocketHandlerInterface
$server->push($fd, Base::array2json([
'type' => 'error',
'data' => [
'error' => '员不存在'
'error' => '员不存在'
],
]));
$server->close($fd);
@@ -191,13 +191,15 @@ class WebSocketService implements WebSocketHandlerInterface
*/
private function saveUser($fd, $userid)
{
Cache::put("User::fd:" . $fd, "on", Carbon::now()->addDay());
Cache::put("User::online:" . $userid, "on", Carbon::now()->addDay());
//
WebSocket::updateInsert([
'key' => md5($fd . '@' . $userid)
], [
'fd' => $fd,
'userid' => $userid,
]);
Cache::put("User::online:" . $userid, time(), Carbon::now()->addSeconds(30));
}
/**
@@ -206,6 +208,8 @@ class WebSocketService implements WebSocketHandlerInterface
*/
private function deleteUser($fd)
{
Cache::forget("User::fd:" . $fd);
//
$array = [];
WebSocket::whereFd($fd)->chunk(10, function($list) use (&$array) {
/** @var WebSocket $item */
@@ -216,6 +220,7 @@ class WebSocketService implements WebSocketHandlerInterface
User::whereUserid($item->userid)->update([
'line_at' => Carbon::now()
]);
Cache::forget("User::online:" . $item->userid);
}
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 (\Throwable $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,377 @@
<?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 App\Module\Ihttp;
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;
}
$this->botManagerReceive($msg, $botUser);
}
public function end()
{
}
/**
* 机器人管理处理消息
* @param WebSocketDialogMsg $msg
* @param User $botUser
* @return void
*/
private function botManagerReceive(WebSocketDialogMsg $msg, User $botUser)
{
if ($msg->type !== 'text') {
return;
}
$original = $msg->msg['text'];
$pureText = trim(strip_tags($original));
// 签到机器人
if ($botUser->email === 'check-in@bot.system') {
if (preg_match("/<span[^>]*?data-quick-key=([\"'])(.*?)\\1[^>]*?>(.*?)<\/span>/is", $original, $match)) {
if ($match[2] === 'checkin') {
$text = "暂未开放手动签到。";
WebSocketDialogMsg::sendMsg(null, $msg->dialog_id, 'text', ['text' => $text], $botUser->userid, false, false, true); // todo 未能在任务end事件来发送任务
return;
}
}
}
// 管理机器人
if (str_starts_with($pureText, '/')) {
if ($botUser->email === 'bot-manager@bot.system') {
$isManager = true;
} elseif (UserBot::whereBotId($botUser->userid)->whereUserid($msg->userid)->exists()) {
$isManager = false;
} else {
$text = "非常抱歉,我不是你的机器人,无法完成你的指令。";
WebSocketDialogMsg::sendMsg(null, $msg->dialog_id, 'text', ['text' => $text], $botUser->userid, false, false, true); // todo 未能在任务end事件来发送任务
return;
}
//
$array = Base::newTrim(explode(" ", "{$pureText} "));
$type = $array[0];
$data = [];
$notice = "";
if (!$isManager && in_array($type, ['/list', '/newbot'])) {
return; // 这些操作仅支持【机器人管理】机器人
}
switch ($type) {
/**
* 列表
*/
case '/list':
$data = User::select([
'users.*',
'user_bots.clear_day',
'user_bots.clear_at',
'user_bots.webhook_url',
'user_bots.webhook_num'
])
->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 '/info':
$botId = $isManager ? $array[1] : $botUser->userid;
$data = $this->botManagerOne($botId, $msg->userid);
if (!$data) {
$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, $msg->userid);
if ($dialog) {
$text = "<p>您好,我是机器人:{$data->nickname}我的机器人ID是{$data->userid}</p><p>你可以发送 <u><b>/help</b></u> 查看我支持什么命令。</p>";
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'text', ['text' => $text], $data->userid); // todo 未能在任务end事件来发送任务
}
break;
/**
* 修改名字
*/
case '/setname':
$botId = $isManager ? $array[1] : $botUser->userid;
$nameString = $isManager ? $array[2] : $array[1];
if (strlen($nameString) < 2 || strlen($nameString) > 20) {
$type = "notice";
$notice = "机器人名称由2-20个字符组成。";
break;
}
$data = $this->botManagerOne($botId, $msg->userid);
if ($data) {
$data->nickname = $nameString;
$data->az = Base::getFirstCharter($nameString);
$data->pinyin = Base::cn2pinyin($nameString);
$data->save();
} else {
$type = "notice";
$notice = "机器人不存在。";
}
break;
/**
* 删除
*/
case '/deletebot':
$botId = $isManager ? $array[1] : $botUser->userid;
$data = $this->botManagerOne($botId, $msg->userid);
if ($data) {
$data->deleteUser('delete bot');
} else {
$type = "notice";
$notice = "机器人不存在。";
}
break;
/**
* 获取Token
*/
case '/token':
$botId = $isManager ? $array[1] : $botUser->userid;
$data = $this->botManagerOne($botId, $msg->userid);
if ($data) {
User::token($data);
} else {
$type = "notice";
$notice = "机器人不存在。";
}
break;
/**
* 更新Token
*/
case '/revoke':
$botId = $isManager ? $array[1] : $botUser->userid;
$data = $this->botManagerOne($botId, $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':
$botId = $isManager ? $array[1] : $botUser->userid;
$clearDay = $isManager ? $array[2] : $array[1];
$data = $this->botManagerOne($botId, $msg->userid);
if ($data) {
$userBot = UserBot::whereBotId($botId)->whereUserid($msg->userid)->first();
if ($userBot) {
$userBot->clear_day = min(intval($clearDay) ?: 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;
/**
* 设置webhook
*/
case '/webhook':
$botId = $isManager ? $array[1] : $botUser->userid;
$webhookUrl = $isManager ? $array[2] : $array[1];
$data = $this->botManagerOne($botId, $msg->userid);
if (strlen($webhookUrl) > 255) {
$type = "notice";
$notice = "webhook地址最长仅支持255个字符。";
} elseif ($data) {
$userBot = UserBot::whereBotId($botId)->whereUserid($msg->userid)->first();
if ($userBot) {
$userBot->webhook_url = $webhookUrl ?: "";
$userBot->webhook_num = 0;
$userBot->save();
}
$data->webhook_url = $userBot->webhook_url ?: '-';
$data->webhook_num = $userBot->webhook_num; // 这两个参数只是作为输出,所以不保存
} else {
$type = "notice";
$notice = "机器人不存在。";
}
break;
/**
* 会话搜索
*/
case '/dialog':
$botId = $isManager ? $array[1] : $botUser->userid;
$nameKey = $isManager ? $array[2] : $array[1];
$data = $this->botManagerOne($botId, $msg->userid);
if ($data) {
$list = WebSocketDialog::select(['web_socket_dialogs.*', 'u.top_at', 'u.mark_unread', 'u.silence', 'u.updated_at as user_at'])
->join('web_socket_dialog_users as u', 'web_socket_dialogs.id', '=', 'u.dialog_id')
->where('web_socket_dialogs.name', 'LIKE', "%{$nameKey}%")
->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,
'manager' => $isManager,
'version' => Base::getVersion()
])->render();
if (!$isManager) {
$text = preg_replace("/\s*\{机器人ID\}/", "", $text);
}
$text = preg_replace("/^\x20+/", "", $text);
$text = preg_replace("/\n\x20+/", "\n", $text);
WebSocketDialogMsg::sendMsg(null, $msg->dialog_id, 'text', ['text' => $text], $botUser->userid, false, false, true); // todo 未能在任务end事件来发送任务
return;
}
// 推送Webhook
if ($pureText) {
$userBot = UserBot::whereBotId($botUser->userid)->first();
if ($userBot && preg_match("/^https*:\/\//", $userBot->webhook_url)) {
Ihttp::ihttp_post($userBot->webhook_url, [
'text' => $pureText,
'token' => User::token($botUser),
'dialog_id' => $msg->dialog_id,
'msg_id' => $msg->id,
'msg_uid' => $msg->userid,
'bot_uid' => $botUser->userid,
'version' => Base::getVersion(),
], 10);
$userBot->webhook_num++;
$userBot->save();
}
}
}
/**
* @param $botId
* @param $userid
* @return User
*/
private function botManagerOne($botId, $userid)
{
$botId = intval($botId);
$userid = intval($userid);
if ($botId > 0) {
return User::select([
'users.*',
'user_bots.clear_day',
'user_bots.clear_at',
'user_bots.webhook_url',
'user_bots.webhook_num'
])
->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,95 @@
<?php
namespace App\Tasks;
use App\Models\User;
use App\Models\UserCheckinRecord;
use App\Models\WebSocketDialog;
use App\Models\WebSocketDialogMsg;
use App\Module\Base;
use App\Module\Ihttp;
use Cache;
use Carbon\Carbon;
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
class CheckinRemindTask extends AbstractTask
{
public function __construct()
{
parent::__construct();
}
public function start()
{
$setting = Base::setting('checkinSetting');
if ($setting['open'] !== 'open') {
return;
}
// 判断非工作日
if (Base::isHoliday(date("Ymd")) > 0) {
return;
}
//
$times = $setting['time'] ? Base::json2array($setting['time']) : ['09:00', '18:00'];
$remindin = (intval($setting['remindin']) ?: 5) * 60;
$remindexceed = (intval($setting['remindexceed']) ?: 10) * 60;
//
$nowDate = date("Y-m-d");
$timeStart = strtotime("{$nowDate} {$times[0]}");
//
if ($remindin > 0) {
$timeRemindin = $timeStart - $remindin;
if ($timeRemindin <= Base::time() && Base::time() <= $timeStart) {
// 签到打卡提醒
$this->remind('in');
}
}
if ($remindexceed > 0) {
$timeRemindexceed = $timeStart + $remindexceed;
if ($timeRemindexceed <= Base::time() && Base::time() <= $timeRemindexceed + 300) {
// 签到缺卡提醒
$this->remind('exceed');
}
}
}
public function end()
{
}
private function remind($type)
{
if (Cache::get("CheckinRemindTask:remind-" . $type) == date("Ymd")) {
return;
}
Cache::put("CheckinRemindTask:remind-" . $type, date("Ymd"), Carbon::now()->addDay());
//
$botUser = User::botGetOrCreate('check-in');
if (!$botUser) {
return;
}
// 提醒对象3天内有签到的成员在职
User::whereBot(0)->whereNull('disable_at')->chunk(100, function ($users) use ($type, $botUser) {
/** @var User $user */
foreach ($users as $user) {
if (UserCheckinRecord::whereUserid($user->userid)->where('date', date("Y-m-d"))->exists()) {
continue; // 已打卡
}
if (!UserCheckinRecord::whereUserid($user->userid)->where('created_at', '>', Carbon::now()->subDays(3))->exists()) {
continue; // 3天内没有打卡
}
$dialog = WebSocketDialog::checkUserDialog($botUser, $user->userid);
if ($dialog) {
if ($type === 'exceed') {
$text = "<p><strong style='color:red'>缺卡提醒:</strong>上班时间到了,你还没有打卡哦~</p>";
} else {
$text = "<p><strong>打卡提醒:</strong>快到上班时间了,别忘了打卡哦~</p>";
}
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'text', ['text' => $text], $botUser->userid);
}
}
});
}
}

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

@@ -2,8 +2,7 @@
namespace App\Tasks;
use App\Models\ProjectTask;
use App\Models\ProjectTaskMailLog;
use App\Models\Setting;
use App\Models\User;
use App\Models\WebSocketDialogMsg;
use App\Models\WebSocketDialogMsgRead;
@@ -18,57 +17,12 @@ class EmailNoticeTask extends AbstractTask
{
public function __construct()
{
//
parent::__construct();
}
public function start()
{
$setting = Base::setting('emailSetting');
// 任务通知
if ($setting['notice'] === 'open') {
$start = intval($setting['task_start_minute']);
$hours = floatval($setting['task_remind_hours']);
$hours2 = floatval($setting['task_remind_hours2']);
if ($start > -1) {
ProjectTask::whereNull("complete_at")
->whereNull("archived_at")
->whereBetween("start_at", [
Carbon::now()->subMinutes($start + 10),
Carbon::now()->subMinutes($start)
])->chunkById(100, function ($tasks) {
/** @var ProjectTask $task */
foreach ($tasks as $task) {
$this->taskEmail($task, 0);
}
});
}
if ($hours > -1) {
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->taskEmail($task, 1);
}
});
}
if ($hours2 > -1) {
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->taskEmail($task, 2);
}
});
}
}
// 消息通知
if ($setting['notice_msg'] === 'open') {
$userMinute = intval($setting['msg_unread_user_minute']);
@@ -77,10 +31,12 @@ class EmailNoticeTask extends AbstractTask
$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)
@@ -93,6 +49,7 @@ class EmailNoticeTask extends AbstractTask
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)
@@ -105,66 +62,9 @@ class EmailNoticeTask extends AbstractTask
}
}
/**
* 任务过期前、超期后提醒
* @param ProjectTask $task
* @param int $type
* @return void
*/
private function taskEmail(ProjectTask $task, int $type)
public function end()
{
$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');
/** @var User $user */
foreach ($users as $user) {
$data = [
'type' => $type,
'userid' => $user->userid,
'task_id' => $task->id,
];
$emailLog = ProjectTaskMailLog::where($data)->exists();
if ($emailLog) {
continue;
}
try {
if (!Base::isEmail($user->email)) {
throw new \Exception("User email '{$user->email}' address error");
}
$subject = match ($type) {
1 => "任务提醒",
2 => "任务过期提醒",
default => "任务开始提醒",
};
$content = view('email.task', [
'type' => str_replace([0, 1, 2], ['start', 'before', 'after'], $type),
'user' => $user,
'task' => $task,
'setting' => $setting,
])->render();
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 (\Throwable $e) {
$data['send_error'] = $e->getMessage();
}
$data['email'] = $user->email;
ProjectTaskMailLog::createInstance($data)->save();
}
}
/**
@@ -180,15 +80,17 @@ class EmailNoticeTask extends AbstractTask
$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::find($userid);
$user = User::whereBot(0)->whereNull('disable_at')->find($userid);
if (empty($user)) {
continue;
}
@@ -247,14 +149,16 @@ class EmailNoticeTask extends AbstractTask
$content = str_replace("{{RemoteURL}}", config("app.url") . "/", $content);
}
try {
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();
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());
}

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);
}
}

View File

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

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 消息列表

View File

@@ -20,6 +20,7 @@ class PushUmengMsg extends AbstractTask
*/
public function __construct($userid, $array = [])
{
parent::__construct(...func_get_args());
$this->userid = $userid;
$this->array = is_array($array) ? $array : [];
}
@@ -35,4 +36,9 @@ class PushUmengMsg extends AbstractTask
}
UmengAlias::pushMsgToUserid($this->userid, $this->array);
}
public function end()
{
}
}

View File

@@ -8,6 +8,9 @@ use App\Models\User;
use App\Models\WebSocketDialog;
use App\Models\WebSocketDialogMsg;
use App\Models\WebSocketDialogMsgRead;
use App\Models\WebSocketDialogUser;
use App\Module\Base;
use Carbon\Carbon;
use Hhxsv5\LaravelS\Swoole\Task\Task;
use Request;
@@ -21,15 +24,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()
@@ -42,12 +75,24 @@ class WebSocketDialogMsgTask extends AbstractTask
//
$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;
}
$updateds = [];
$silences = [];
foreach ($dialog->dialogUser as $dialogUser) {
$updateds[$dialogUser->userid] = $dialogUser->updated_at;
$silences[$dialogUser->userid] = $dialogUser->silence;
}
$userids = array_keys($silences);
// 提及会员
$mentions = [];
@@ -59,7 +104,6 @@ class WebSocketDialogMsgTask extends AbstractTask
}
// 将会话以外的成员加入会话内
$userids = $dialog->dialogUser->pluck('userid')->toArray();
$diffids = array_values(array_diff($mentions, $userids));
if ($diffids) {
// 仅(群聊)且(是群主或没有群主)才可以@成员以外的人
@@ -73,77 +117,114 @@ class WebSocketDialogMsgTask extends AbstractTask
// 推送目标①:会话成员/群成员
$array = [];
foreach ($userids AS $userid) {
$silence = $this->silence || $silences[$userid];
$updated = $updateds[$userid] ?? $msg->created_at;
if ($userid == $msg->userid) {
$array[$userid] = false;
$array[$userid] = [
'userid' => $userid,
'mention' => false,
'silence' => $silence,
'updated' => $updated,
];
} else {
$mention = array_intersect([0, $userid], $mentions) ? 1 : 0;
$silence = $mention ? false : $silence;
WebSocketDialogMsgRead::createInstance([
'dialog_id' => $msg->dialog_id,
'msg_id' => $msg->id,
'userid' => $userid,
'mention' => $mention,
'silence' => $silence,
])->saveOrIgnore();
$array[$userid] = $mention;
$array[$userid] = [
'userid' => $userid,
'mention' => $mention,
'silence' => $silence,
'updated' => $updated,
];
// 机器人收到消处理
$botUser = User::whereUserid($userid)->whereBot(1)->first();
if ($botUser) {
$this->endArray[] = new BotReceiveMsgTask($botUser->userid, $msg->id);
}
}
}
// 更新已发送数量
$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'],
'user_at' => Carbon::parse($item['updated'])->toDateTimeString(),
'user_ms' => WebSocketDialogUser::userMs($item['updated']),
]),
]
]);
];
if ($item['userid'] != $msg->userid && !$item['silence'] && !$this->silence) {
$umengUserid[] = $item['userid'];
}
}
// umeng推送app
$umengUserid = $array;
if (isset($umengUserid[$msg->userid])) {
unset($umengUserid[$msg->userid]);
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,
]);
}
}
$umengUserid = array_keys($umengUserid);
$umengTitle = User::userid2nickname($msg->userid);
if ($dialog->type == 'group') {
$umengTitle = "{$dialog->getGroupName()} ($umengTitle)";
}
$umengMsg = new PushUmengMsg($umengUserid, [
'title' => $umengTitle,
'body' => $msg->previewMsg(),
'description' => "MID:{$msg->id}",
'seconds' => 3600,
'badge' => 1,
]);
Task::deliver($umengMsg);
// 推送目标②:正在打开这个任务会话的会员
if ($dialog->type == 'group' && $dialog->group_type == 'task') {
$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',
'data' => $msg->toArray(),
'silence' => $this->silence ? 1 : 0,
'data' => array_merge($msg->toArray(), [
'user_at' => Carbon::parse($msg->created_at)->toDateTimeString(),
'user_ms' => WebSocketDialogUser::userMs($msg->created_at),
]),
]
]);
];
}
}
}
}
public function end()
{
foreach ($this->endArray as $task) {
Task::deliver($task);
}
PushTask::push($this->endPush);
}
}

15
bin/auto Executable file
View File

@@ -0,0 +1,15 @@
#!/usr/bin/env bash
if [ ! -f ".env" ]; then
echo -e "配置文件不存在!"
exit 1
fi
debug=`cat .env | grep "^APP_DEBUG=" | awk -F '=' '{print $2}'`
if [ "$debug" = "true" ]; then
echo "[MODE] development"
./bin/inotify ./app
else
echo "[MODE] production"
php bin/laravels start -i
fi

89
bin/run
View File

@@ -1,89 +0,0 @@
#!/usr/bin/env php
<?php
/**
* Class runLoader
*/
class runLoader
{
public function randString($length = 16)
{
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$passwordstr = '';
$max = strlen($chars) - 1;
for ($i = 0; $i < $length; $i++) {
$passwordstr .= $chars[mt_rand(0, $max)];
}
return $passwordstr;
}
function getEnv(string $key)
{
if (empty($key) || !is_string($key)) {
return '';
}
$envPath = realpath(__DIR__ . '/../') . DIRECTORY_SEPARATOR . '.env';
if (!file_exists($envPath)) {
return false;
}
$envContent = file_get_contents($envPath);
preg_match_all("/^" . $key . "\s*=\s*(.*?)$/m", $envContent, $matchs);
return $matchs[1] ?: '';
}
function modifyEnv(array $data)
{
if (empty($data) || !is_array($data)) {
return false;
}
$envPath = realpath(__DIR__ . '/../') . DIRECTORY_SEPARATOR . '.env';
if (!file_exists($envPath)) {
return false;
}
$envContent = file_get_contents($envPath);
foreach ($data as $key => $val) {
$envContent = preg_replace("/^" . $key . "\s*=\s*(.*?)$/m", $key . "=" . $val, $envContent);
}
file_put_contents($envPath, $envContent);
return true;
}
function modifyMode($type)
{
$filePath = realpath(__DIR__ . '/../') . DIRECTORY_SEPARATOR . '/docker/php/php.conf';
if (!file_exists($filePath)) {
return false;
}
$envContent = file_get_contents($filePath);
$envContent = str_replace("#command=php bin/laravels start -i", "command=php bin/laravels start -i", $envContent);
$envContent = str_replace("#command=./bin/inotify ./app", "command=./bin/inotify ./app", $envContent);
if ($type == "dev") {
$envContent = str_replace("command=php bin/laravels start -i", "#command=php bin/laravels start -i", $envContent);
$this->modifyEnv([
'APP_DEBUG' => 'true'
]);
} else {
$envContent = str_replace("command=./bin/inotify ./app", "#command=./bin/inotify ./app", $envContent);
$this->modifyEnv([
'APP_DEBUG' => 'false'
]);
}
file_put_contents($filePath, $envContent);
return true;
}
}
$array = getopt('', ['port:', 'mode:']);
$loader = new runLoader();
if (isset($array['mode'])) {
$loader->modifyMode($array['mode']);
}
$data = [];
if (isset($array['port'])) {
$data['APP_PORT'] = $array['port'];
}
if ($data) {
$loader->modifyEnv($data);
}

94
cmd
View File

@@ -41,15 +41,31 @@ rand_string() {
fi
}
supervisorctl_restart() {
local RES=`run_exec php "supervisorctl update $1"`
restart_php() {
local RES=`run_exec php "supervisorctl update php"`
if [ -z "$RES" ]; then
run_exec php "supervisorctl restart $1"
RES=`run_exec php "supervisorctl restart php"`
fi
local IN=`echo $RES | grep "ERROR"`
if [[ "$IN" != "" ]]; then
$COMPOSE stop php
$COMPOSE start php
else
echo -e "$RES"
fi
}
switch_debug() {
local debug="false"
if [[ "$1" == "true" ]] || [[ "$1" == "dev" ]] || [[ "$1" == "open" ]]; then
debug="true"
fi
if [[ "$(env_get APP_DEBUG)" != "$debug" ]]; then
env_set APP_DEBUG "$debug"
restart_php
fi
}
check_docker() {
docker --version &> /dev/null
if [ $? -ne 0 ]; then
@@ -90,15 +106,17 @@ run_compile() {
if [ ! -d "./node_modules" ]; then
npm install
fi
run_exec php "php bin/run --mode=$type"
supervisorctl_restart php
if [ "$type" = "dev" ]; then
echo "<script>window.location.href=window.location.href.replace(/:\d+/, ':' + $(env_get APP_PORT))</script>" > ./index.html
env_set APP_DEV_PORT $(rand 20001 30000)
fi
switch_debug "$type"
#
if [ "$type" = "prod" ]; then
rm -rf "./public/js/build"
npx mix --production
echo "$(rand_string 16)" > ./public/js/hash
npx vite build -- fromcmd
else
npx mix watch --hot
npx vite -- fromcmd
fi
}
@@ -117,18 +135,16 @@ run_electron() {
if [ -d "./electron/dist" ]; then
rm -rf "./electron/dist"
fi
if [ -d "./electron/public" ] && [ "$argv" != "--nobuild" ]; then
if [ -d "./electron/public" ]; then
rm -rf "./electron/public"
fi
mkdir -p ./electron/public
cp ./electron/index.html ./electron/public/index.html
#
if [ "$argv" != "dev" ] && [ "$argv" != "--nobuild" ]; then
npx mix --production -- --env --electron
fi
if [ "$argv" == "dev" ]; then
run_exec php "php bin/run --mode=$argv"
supervisorctl_restart php
switch_debug "$argv"
else
mkdir -p ./electron/public
cp ./electron/index.html ./electron/public/index.html
npx vite build -- fromcmd electronBuild
fi
node ./electron/build.js $argv
}
@@ -243,15 +259,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 +271,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
@@ -295,7 +297,7 @@ if [ $# -gt 0 ]; then
exit 1
fi
[[ -z "$(env_get APP_KEY)" ]] && run_exec php "php artisan key:generate"
run_exec php "php bin/run --mode=prod"
switch_debug "false"
# 检查数据库
remaining=20
while [ ! -f "${cur_path}/docker/mysql/data/$(env_get DB_DATABASE)/db.opt" ]; do
@@ -315,7 +317,7 @@ if [ $# -gt 0 ]; then
# 设置初始化密码
res=`run_exec mariadb "sh /etc/mysql/repassword.sh"`
$COMPOSE up -d
supervisorctl_restart php
restart_php
echo -e "${OK} ${GreenBG} 安装完成 ${Font}"
echo -e "地址: http://${GreenBG}127.0.0.1:$(env_get APP_PORT)${Font}"
echo -e "$res"
@@ -327,12 +329,12 @@ if [ $# -gt 0 ]; then
git pull
run_exec php "composer update"
run_exec php "php artisan migrate"
supervisorctl_restart php
restart_php
$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}"
@@ -361,22 +363,22 @@ if [ $# -gt 0 ]; then
elif [[ "$1" == "url" ]]; then
shift 1
env_set APP_URL "$1"
supervisorctl_restart php
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
restart_php
echo -e "${OK} ${GreenBG} 修改成功 ${Font}"
elif [[ "$1" == "repassword" ]]; then
shift 1
run_exec mariadb "sh /etc/mysql/repassword.sh \"$@\""
elif [[ "$1" == "dev" ]] || [[ "$1" == "development" ]]; then
elif [[ "$1" == "serve" ]] || [[ "$1" == "dev" ]] || [[ "$1" == "development" ]]; then
shift 1
run_compile dev
elif [[ "$1" == "prod" ]] || [[ "$1" == "production" ]]; then
elif [[ "$1" == "build" ]] || [[ "$1" == "prod" ]] || [[ "$1" == "production" ]]; then
shift 1
run_compile prod
elif [[ "$1" == "appbuild" ]] || [[ "$1" == "buildapp" ]]; then
@@ -391,12 +393,8 @@ if [ $# -gt 0 ]; then
docker run -it --rm -v ${cur_path}:/home/node/apidoc kuaifan/apidoc -i app/Http/Controllers/Api -o public/docs
elif [[ "$1" == "debug" ]]; then
shift 1
if [[ "$@" == "close" ]]; then
env_set APP_DEBUG "false"
else
env_set APP_DEBUG "true"
fi
supervisorctl_restart php
switch_debug "$@"
echo "success"
elif [[ "$1" == "https" ]]; then
shift 1
if [[ "$@" == "auto" ]]; then
@@ -404,7 +402,7 @@ if [ $# -gt 0 ]; then
else
env_set APP_SCHEME "true"
fi
supervisorctl_restart php
restart_php
elif [[ "$1" == "artisan" ]]; then
shift 1
e="php artisan $@" && run_exec php "$e"
@@ -437,7 +435,11 @@ 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" == "translate" ]]; then
shift 1
run_exec php "cd /var/www/language && php translate.php"
elif [[ "$1" == "test" ]]; then
shift 1
e="./vendor/bin/phpunit $@" && run_exec php "$e"

View File

@@ -14,12 +14,13 @@
"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.21.1",
"guzzlehttp/guzzle": "^7.3.0",
"hedeqiang/umeng": "^2.1",
"laravel/framework": "^v8.48.1",
"laravel/framework": "^v8.83.27",
"laravel/tinker": "^v2.6.1",
"lasserafn/php-initial-avatar-generator": "^4.2",
"maatwebsite/excel": "^3.1.31",

1605
composer.lock generated

File diff suppressed because it is too large Load Diff

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

@@ -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

@@ -25,7 +25,7 @@ class ProjectLogsRecordUserid extends Migration
foreach ($lists as $log) {
$record = $log->record;
if (is_string($record['userid']) && str_contains($record['userid'], ",")) {
$record['userid'] = Base::explodeInt(',', $record['userid']);
$record['userid'] = Base::explodeInt($record['userid']);
$log->record = Base::array2json($record);
$log->save();
}

View File

@@ -17,7 +17,7 @@ class AddWebSocketDialogUsersInviterImportant extends Migration
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->boolean('important')->default(0)->after('mark_unread')->nullable()->comment('是否不可移出(项目、任务、部门人员)');
$table->bigInteger('inviter')->nullable()->default(0)->after('mark_unread')->comment('邀请人');
}
});

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()
{
}
}

View File

@@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddWebSocketDialogMsgsModify 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', 'modify')) {
$table->boolean('modify')->default(0)->after('link')->nullable()->comment('是否编辑');
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('web_socket_dialog_msgs', function (Blueprint $table) {
$table->dropColumn("modify");
});
}
}

View File

@@ -0,0 +1,31 @@
<?php
use App\Models\User;
use App\Models\WebSocketDialog;
use Illuminate\Database\Migrations\Migration;
class GenerateWebSocketDialogsAllGroup extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
if (WebSocketDialog::count() > 0 && !WebSocketDialog::whereGroupType('all')->exists()) {
$userids = User::whereNull('disable_at')->pluck('userid')->toArray();
WebSocketDialog::createGroup("全体成员 All members", $userids, 'all');
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
}
}

View File

@@ -0,0 +1,46 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
class UpdateWebSocketDialogsGroupName extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('web_socket_dialogs', function (Blueprint $table) {
$table->string('name', 255)->nullable()->default('')->comment('对话名称')->change();
});
//
\App\Models\WebSocketDialog::whereGroupType('project')
->chunk(100, function ($lists) {
/** @var \App\Models\WebSocketDialog $item */
foreach ($lists as $item) {
$item->name = \App\Models\Project::whereDialogId($item->id)->first()?->name;
$item->save();
}
});
\App\Models\WebSocketDialog::whereGroupType('task')
->chunk(100, function ($lists) {
/** @var \App\Models\WebSocketDialog $item */
foreach ($lists as $item) {
$item->name = \App\Models\ProjectTask::whereDialogId($item->id)->first()?->name;
$item->save();
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
}
}

View File

@@ -0,0 +1,39 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddWebSocketDialogMsgsTodo 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', 'todo')) {
$table->bigInteger('todo')->nullable()->default(0)->after('tag')->comment('设为待办会员ID');
}
});
Schema::table('web_socket_dialog_msg_reads', function (Blueprint $table) {
if (Schema::hasColumn('web_socket_dialog_msg_reads', 'userid')) {
$table->bigInteger('userid')->nullable()->default(0)->comment('接收会员ID')->change();
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('web_socket_dialog_msgs', function (Blueprint $table) {
$table->dropColumn("todo");
});
}
}

View File

@@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateWebSocketDialogMsgTodosTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('web_socket_dialog_msg_todos', function (Blueprint $table) {
$table->bigIncrements('id');
$table->bigInteger('dialog_id')->nullable()->default(0)->comment('对话ID');
$table->bigInteger('msg_id')->nullable()->default(0)->comment('消息ID');
$table->bigInteger('userid')->nullable()->default(0)->comment('接收会员ID');
$table->timestamp('done_at')->nullable()->comment('完成时间');
$table->index(['dialog_id', 'userid', 'done_at']);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('web_socket_dialog_msg_todos');
}
}

View File

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

View File

@@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddTypeToUserEmailVerificationsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('user_email_verifications', function (Blueprint $table) {
if (!Schema::hasColumn('user_email_verifications', 'type')) {
$table->tinyInteger('type')->nullable()->default(1)->after('status')->comment('邮件类型1-邮箱认证2-修改邮箱');
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('user_email_verifications', function (Blueprint $table) {
$table->dropColumn("type");
});
}
}

View File

@@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateUserDeletesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('user_deletes', function (Blueprint $table) {
$table->bigIncrements('id');
$table->bigInteger('userid')->nullable()->default(0)->comment('用户id');
$table->string('email', 100)->nullable()->default('')->comment('邮箱帐号');
$table->text('reason')->nullable()->comment('注销原因');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('user_deletes');
}
}

View File

@@ -0,0 +1,46 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddFilesPshare extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
$isAdd = false;
Schema::table('files', function (Blueprint $table) use (&$isAdd) {
if (!Schema::hasColumn('files', 'pshare')) {
$isAdd = true;
$table->bigInteger('pshare')->nullable()->default(0)->after('share')->comment('所属分享ID');
}
});
if ($isAdd) {
\App\Models\File::whereShare(1)->chunkById(100, function ($lists) {
/** @var \App\Models\File $item */
foreach ($lists as $item) {
\App\Models\File::where("pids", "like", "%,{$item->id},%")->update(['pshare' => $item->id]);
$item->pshare = $item->id;
$item->save();
}
});
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('files', function (Blueprint $table) {
$table->dropColumn("pshare");
});
}
}

View File

@@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddUserDeletesOperator extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('user_deletes', function (Blueprint $table) {
if (!Schema::hasColumn('user_deletes', 'operator')) {
$table->bigInteger('operator')->nullable()->default(0)->after('id')->comment('操作人员');
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('user_deletes', function (Blueprint $table) {
$table->dropColumn("operator");
});
}
}

View File

@@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddUserDeletesCache extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('user_deletes', function (Blueprint $table) {
if (!Schema::hasColumn('user_deletes', 'cache')) {
$table->text('cache')->after('reason')->nullable()->default('')->comment('会员资料缓存');
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('user_deletes', function (Blueprint $table) {
$table->dropColumn("cache");
});
}
}

View File

@@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateProjectTaskPushLogsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('project_task_push_logs', function (Blueprint $table) {
$table->id();
$table->bigInteger('userid')->nullable()->default(0)->comment('用户id');
$table->integer('task_id')->nullable()->default(0)->comment('任务id');
$table->tinyInteger('type')->nullable()->default(0)->comment('提醒类型0 任务开始提醒1 距离到期提醒2到期超时提醒');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('project_task_push_logs');
}
}

View File

@@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateTaskWorkersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('task_workers', function (Blueprint $table) {
$table->bigIncrements('id');
$table->longText('args')->nullable();
$table->longText('error')->nullable();
$table->timestamp('start_at')->nullable()->comment('开始时间');
$table->timestamp('end_at')->nullable()->comment('结束时间');
$table->timestamps();
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('task_workers');
}
}

View File

@@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateUserDepartmentsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('user_departments', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name', 100)->nullable()->default('')->comment('部门名称');
$table->bigInteger('dialog_id')->nullable()->default(0)->comment('聊天会话ID');
$table->bigInteger('parent_id')->nullable()->default(0)->comment('上级部门');
$table->bigInteger('owner_userid')->nullable()->default(0)->comment('部门负责人');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('user_departments');
}
}

View File

@@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddUsersDepartment extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function (Blueprint $table) {
if (!Schema::hasColumn('users', 'department')) {
$table->string('department', 255)->nullable()->default('')->after('identity')->comment('所属部门');
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn("department");
});
}
}

View File

@@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddProjectTaskPushLogsDeletedAt extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('project_task_push_logs', function (Blueprint $table) {
if (!Schema::hasColumn('project_task_push_logs', 'deleted_at')) {
$table->softDeletes();
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('project_task_push_logs', function (Blueprint $table) {
$table->dropSoftDeletes();
});
}
}

View File

@@ -0,0 +1,37 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateUserCheckinMacsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
if (Schema::hasTable('user_checkin_macs'))
return;
Schema::create('user_checkin_macs', function (Blueprint $table) {
$table->bigIncrements('id');
$table->bigInteger('userid')->nullable()->default(0)->comment('会员id');
$table->string('mac', 100)->nullable()->default('')->comment('MAC地址');
$table->string('remark', 100)->nullable()->default('')->comment('备注');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('user_checkin_macs');
}
}

View File

@@ -0,0 +1,37 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateUserCheckinRecordsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
if (Schema::hasTable('user_checkin_records'))
return;
Schema::create('user_checkin_records', function (Blueprint $table) {
$table->bigIncrements('id');
$table->bigInteger('userid')->nullable()->default(0)->comment('会员id');
$table->string('mac', 100)->nullable()->default('')->comment('MAC地址');
$table->bigInteger('time')->nullable()->default(0)->comment('上报的时间戳');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('user_checkin_records');
}
}

View File

@@ -0,0 +1,74 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddDateTimesUserCheckinRecords extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
$isAdd = false;
Schema::table('user_checkin_records', function (Blueprint $table) use (&$isAdd) {
if (!Schema::hasColumn('user_checkin_records', 'date')) {
$isAdd = true;
$table->string('date', 20)->nullable()->default('')->after('mac')->comment('签到日期');
$table->text('times')->nullable()->after('date')->comment('签到时间');
$table->renameColumn('time', 'report_time');
}
});
if ($isAdd) {
$userids = \App\Models\UserCheckinRecord::select('userid')->distinct()->get()->pluck('userid');
foreach ($userids as $userid) {
$list = \App\Models\UserCheckinRecord::whereUserid($userid)->orderBy('created_at')->get();
$ids = [];
$date = "";
$array = [];
foreach ($list as $item) {
$ids[] = $item->id;
$created_at = \Carbon\Carbon::parse($item->created_at);
if ($created_at->toDateString() != $date) {
$date = $created_at->toDateString();
if ($array) {
$record = \App\Models\UserCheckinRecord::createInstance($array);
$record->save();
}
$array = [
'userid' => $item->userid,
'mac' => $item->mac,
'date' => $date,
'times' => [],
'report_time' => $item->report_time,
'created_at' => $item->created_at,
];
}
if ($array) {
$array['times'][] = $created_at->toTimeString();
}
}
if ($array) {
\App\Models\UserCheckinRecord::whereIn('id', $ids)->delete();
}
if ($array) {
$record = \App\Models\UserCheckinRecord::createInstance($array);
$record->save();
}
}
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
// ... 退回去意义不大
}
}

View File

@@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateUserBotsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
if (Schema::hasTable('user_bots'))
return;
Schema::create('user_bots', function (Blueprint $table) {
$table->bigIncrements('id');
$table->bigInteger('userid')->nullable()->default(0)->comment('所属人ID');
$table->bigInteger('bot_id')->nullable()->default(0)->comment('机器人ID');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('user_bots');
}
}

View File

@@ -0,0 +1,40 @@
<?php
use App\Models\User;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddUsersBot extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
$isAdd = false;
Schema::table('users', function (Blueprint $table) use (&$isAdd) {
if (!Schema::hasColumn('users', 'bot')) {
$isAdd = true;
$table->tinyInteger('bot')->nullable()->default(0)->after('email_verity')->comment('是否机器人');
}
});
if ($isAdd && User::count() > 0) {
User::botGetOrCreate('bot-manager');
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn("bot");
});
}
}

View File

@@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddWebSocketDialogUsersAddSilence extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('web_socket_dialog_users', function (Blueprint $table) {
if (!Schema::hasColumn('web_socket_dialog_users', 'silence')) {
$table->boolean('silence')->default(0)->nullable()->after('mark_unread')->comment('是否免打扰0否1是');
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('web_socket_dialog_users', function (Blueprint $table) {
$table->dropColumn("silence");
});
}
}

View File

@@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddWebSocketDialogMsgReadsAddSilence 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', 'silence')) {
$table->boolean('silence')->default(0)->nullable()->after('mention')->comment('是否免打扰0否1是');
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('web_socket_dialog_msg_reads', function (Blueprint $table) {
$table->dropColumn("silence");
});
}
}

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