Compare commits
316 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0a0f34ff4 | ||
|
|
e983677e57 | ||
|
|
a813809fc6 | ||
|
|
f28b99b516 | ||
|
|
bab37530e4 | ||
|
|
fb24c63e7f | ||
|
|
65b8e2270e | ||
|
|
bfb2db8a3f | ||
|
|
fe8deb98a2 | ||
|
|
cee2458370 | ||
|
|
764bf6dd55 | ||
|
|
88aee1e3bf | ||
|
|
bcc7d6d35c | ||
|
|
f3f0dec87b | ||
|
|
bf4c4df939 | ||
|
|
51efb07c17 | ||
|
|
20e13ee9eb | ||
|
|
b1b4ef926f | ||
|
|
43e6b4dc2f | ||
|
|
906d87f43f | ||
|
|
ec8d48292e | ||
|
|
1882a7baba | ||
|
|
d4cccbeb09 | ||
|
|
1f187ba8fb | ||
|
|
9c4ff466a4 | ||
|
|
e5c3cf6adb | ||
|
|
02fd214b33 | ||
|
|
1ddb88a3a6 | ||
|
|
6edd4451c5 | ||
|
|
1e83c7442a | ||
|
|
91533c5cac | ||
|
|
a2ee1135dd | ||
|
|
0ec255ed60 | ||
|
|
a19d11061f | ||
|
|
a730c95492 | ||
|
|
89a50fd389 | ||
|
|
82a340d576 | ||
|
|
c952da669c | ||
|
|
24cec7016a | ||
|
|
2c407bea78 | ||
|
|
fba98db7cb | ||
|
|
00eb8f7b01 | ||
|
|
1055daa0e3 | ||
|
|
928145214d | ||
|
|
56e52a7dfd | ||
|
|
479d3e3f39 | ||
|
|
ad3e773f27 | ||
|
|
42c77db1d4 | ||
|
|
11ea2d3697 | ||
|
|
d0b54ab27c | ||
|
|
5b9b6ed966 | ||
|
|
fc192891b7 | ||
|
|
14f54e9df4 | ||
|
|
07a290dbf9 | ||
|
|
694f9a37a5 | ||
|
|
13e58c63f4 | ||
|
|
67c79bf565 | ||
|
|
428b72ef3d | ||
|
|
b78d92b387 | ||
|
|
a09f2038ee | ||
|
|
fbb74e09e8 | ||
|
|
ca1028921a | ||
|
|
0fd37e4c05 | ||
|
|
f3d9e3376e | ||
|
|
9296008ecc | ||
|
|
ee7a1bd99c | ||
|
|
21eab03684 | ||
|
|
da066e40ce | ||
|
|
a219b7b6ee | ||
|
|
85c4ed6399 | ||
|
|
fa42194d15 | ||
|
|
e574e728d4 | ||
|
|
2ca35e4458 | ||
|
|
99027858d9 | ||
|
|
e7fcb47e81 | ||
|
|
02d6dcd592 | ||
|
|
6e0a575da9 | ||
|
|
93387c289e | ||
|
|
1227a05e2d | ||
|
|
9f00047fdd | ||
|
|
9bc3e56c79 | ||
|
|
508aaef303 | ||
|
|
efd44a5da1 | ||
|
|
0c70613865 | ||
|
|
6fda0bd548 | ||
|
|
77224c3726 | ||
|
|
f25d72e4f5 | ||
|
|
34603ff96e | ||
|
|
812232b945 | ||
|
|
bd7228a378 | ||
|
|
ab61715973 | ||
|
|
095f461cfd | ||
|
|
047771e6f8 | ||
|
|
e2cec420fa | ||
|
|
35e55b8677 | ||
|
|
1b0ec71d93 | ||
|
|
c6c735bbe8 | ||
|
|
d5bc7d4051 | ||
|
|
74405f1a2a | ||
|
|
016bc41180 | ||
|
|
e5df3e6746 | ||
|
|
13fb884387 | ||
|
|
3b9c9872ca | ||
|
|
2fc329a403 | ||
|
|
8ca1ef3b50 | ||
|
|
f7dd9f852f | ||
|
|
4a9ed730c6 | ||
|
|
a023c0b8bf | ||
|
|
ff38be3187 | ||
|
|
9ffb2de2c8 | ||
|
|
dcd87f86f1 | ||
|
|
d149c16713 | ||
|
|
1d99022ca3 | ||
|
|
bc85da49e3 | ||
|
|
18e1240775 | ||
|
|
e149e276d5 | ||
|
|
02654c8327 | ||
|
|
dace1dd1f3 | ||
|
|
c46fd080df | ||
|
|
ef2230a331 | ||
|
|
2ecd0584aa | ||
|
|
65c398880b | ||
|
|
5962a593da | ||
|
|
67baddf7a8 | ||
|
|
ceb4fc8292 | ||
|
|
c51135a4cc | ||
|
|
b2b4f593ce | ||
|
|
a95504bbf1 | ||
|
|
6ed0e14fe0 | ||
|
|
257e69268b | ||
|
|
7e951196bf | ||
|
|
501872e8d2 | ||
|
|
87e46ec5a5 | ||
|
|
ebe953cf63 | ||
|
|
cbfcdbf836 | ||
|
|
bd15915648 | ||
|
|
312acdab51 | ||
|
|
4ba9cc88dd | ||
|
|
239013a2cb | ||
|
|
85412ea4b7 | ||
|
|
cfda858d87 | ||
|
|
df8fdd56ba | ||
|
|
698d03f77e | ||
|
|
3e15a3341c | ||
|
|
d8a25e75d7 | ||
|
|
42f69124aa | ||
|
|
621726ab3b | ||
|
|
cce7523f45 | ||
|
|
5e6a62376a | ||
|
|
b03fb9f1de | ||
|
|
1a7591314f | ||
|
|
b8852f821c | ||
|
|
6ebca3befa | ||
|
|
8db34c6ee6 | ||
|
|
d799c06017 | ||
|
|
50a7950ccd | ||
|
|
a393dec0a0 | ||
|
|
423aad4179 | ||
|
|
80d10051cf | ||
|
|
b1776c82ad | ||
|
|
36f313380e | ||
|
|
7df9c37850 | ||
|
|
9001c51bea | ||
|
|
99757fc947 | ||
|
|
8e9ff1116a | ||
|
|
f1df4e07d2 | ||
|
|
3e3799074a | ||
|
|
ae0ee590e4 | ||
|
|
988a9b0606 | ||
|
|
7a457e4364 | ||
|
|
2edbe4fb3f | ||
|
|
8eaff830ad | ||
|
|
7fdc7a47e3 | ||
|
|
0e821d1c84 | ||
|
|
c23de08cf5 | ||
|
|
a6acb7ea0d | ||
|
|
0c64cf0546 | ||
|
|
a4a9ab8d2d | ||
|
|
19a1ae9bec | ||
|
|
36cb8290f4 | ||
|
|
244991e8e8 | ||
|
|
d6a7c19cbf | ||
|
|
64906a827d | ||
|
|
da53306a2c | ||
|
|
48515d7caf | ||
|
|
1f6ef62499 | ||
|
|
6b4b88aba7 | ||
|
|
fadff146b4 | ||
|
|
01feacfe54 | ||
|
|
d6ddc5ff88 | ||
|
|
287b6b396d | ||
|
|
b976f294f9 | ||
|
|
dce48bd0cb | ||
|
|
ab84235890 | ||
|
|
7445ac3a39 | ||
|
|
f9ceb3e2d8 | ||
|
|
8bb7b60055 | ||
|
|
190211a467 | ||
|
|
8a6868e811 | ||
|
|
6aa868c8d8 | ||
|
|
4dfa1c8efc | ||
|
|
e2e7bc8d72 | ||
|
|
a97d78bbf4 | ||
|
|
22dbd288df | ||
|
|
4685cdcd3c | ||
|
|
f792b3d983 | ||
|
|
adc94cef90 | ||
|
|
e639cfbc2f | ||
|
|
e520cd9020 | ||
|
|
daf8d15f45 | ||
|
|
0e473ceacc | ||
|
|
873bd0ed88 | ||
|
|
58b7853d63 | ||
|
|
2284788366 | ||
|
|
d1766e52b6 | ||
|
|
fdd5e36d19 | ||
|
|
4fe4dc8c6e | ||
|
|
a3202cbead | ||
|
|
e8b03ae565 | ||
|
|
829e3982d2 | ||
|
|
07c5f586b0 | ||
|
|
2ebaeb3453 | ||
|
|
5660be12f6 | ||
|
|
3cd00e1343 | ||
|
|
f983146501 | ||
|
|
6cf64ce538 | ||
|
|
47a7876505 | ||
|
|
3f5ac55753 | ||
|
|
a33d95f2c1 | ||
|
|
1128db184e | ||
|
|
153fd6c569 | ||
|
|
c9d002c1cd | ||
|
|
e0a108eb2e | ||
|
|
ae587950b9 | ||
|
|
e956a03098 | ||
|
|
1702aab538 | ||
|
|
3c67b49d08 | ||
|
|
d58246b255 | ||
|
|
814a488801 | ||
|
|
e029b39eb9 | ||
|
|
a8361299c7 | ||
|
|
e3f5fb323a | ||
|
|
be262c3a69 | ||
|
|
a4525d4519 | ||
|
|
4f6034457f | ||
|
|
5413457b6b | ||
|
|
977cf61b50 | ||
|
|
8c8c5b04d5 | ||
|
|
620465d62a | ||
|
|
a80e0d4c45 | ||
|
|
0ab6e6ca8d | ||
|
|
dcd41b4be2 | ||
|
|
33cd9358c0 | ||
|
|
51a3ad25d1 | ||
|
|
f586938fe9 | ||
|
|
912d229bdd | ||
|
|
a93345afbd | ||
|
|
a7bd0e0dac | ||
|
|
e2fd37fe24 | ||
|
|
6e6397fc91 | ||
|
|
45c20dbed9 | ||
|
|
594c19da03 | ||
|
|
9251ccbb12 | ||
|
|
34305a1285 | ||
|
|
ccc60dfd77 | ||
|
|
b7da689955 | ||
|
|
0598a36b19 | ||
|
|
947e106f19 | ||
|
|
81957c9396 | ||
|
|
d54c86cec9 | ||
|
|
c17eca28fa | ||
|
|
9a69f3b019 | ||
|
|
c39fc80c02 | ||
|
|
b0eead121a | ||
|
|
511b98ab5b | ||
|
|
a69b01ecf5 | ||
|
|
a967493d77 | ||
|
|
050c9702d8 | ||
|
|
0d23b973de | ||
|
|
fc3170369b | ||
|
|
647f7fdc7d | ||
|
|
8c3cd379a2 | ||
|
|
cf9051412a | ||
|
|
6db0ff5647 | ||
|
|
9ce127df86 | ||
|
|
20eec62fde | ||
|
|
effc8ce43f | ||
|
|
ced25e0cd2 | ||
|
|
72c70fe494 | ||
|
|
dc062a44e1 | ||
|
|
dff22272b5 | ||
|
|
a0a1e03b53 | ||
|
|
3915c065fe | ||
|
|
dc71a779e0 | ||
|
|
b56ba93634 | ||
|
|
2926472f7d | ||
|
|
a253d42f10 | ||
|
|
700d566255 | ||
|
|
fbbace90aa | ||
|
|
6b5f7e780c | ||
|
|
79d4932bee | ||
|
|
e8af0f2ea6 | ||
|
|
f1ecf33ce7 | ||
|
|
18eaf56ff9 | ||
|
|
75f15ccc96 | ||
|
|
3f17e91f72 | ||
|
|
ee6eddf308 | ||
|
|
da84f15e9f | ||
|
|
62f4d43bd9 | ||
|
|
376120b6d0 | ||
|
|
ff872c7dce | ||
|
|
a834481d32 | ||
|
|
4c5d3bd43e | ||
|
|
b737b841f5 | ||
|
|
0c5500edd4 | ||
|
|
990a40e4e4 |
55
.github/workflows/publish-android.yml
vendored
55
.github/workflows/publish-android.yml
vendored
@@ -1,55 +0,0 @@
|
||||
name: Publish Android
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
Build:
|
||||
name: Build Android
|
||||
runs-on: ubuntu-latest
|
||||
environment: build
|
||||
|
||||
if: startsWith(github.event.ref, 'refs/tags/v')
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Use Node.js 20.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 20.x
|
||||
|
||||
- name: Build Js
|
||||
run: |
|
||||
git submodule init
|
||||
git submodule update --remote "resources/mobile"
|
||||
./cmd appbuild publish
|
||||
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: '11'
|
||||
|
||||
- name: Build Android
|
||||
run: |
|
||||
cd resources/mobile/platforms/android/eeuiApp
|
||||
chmod +x ./gradlew
|
||||
./gradlew assembleRelease --quiet
|
||||
|
||||
- name: Upload File
|
||||
env:
|
||||
DP_KEY: ${{ secrets.DP_KEY }}
|
||||
run: |
|
||||
node ./electron/build.js android-upload
|
||||
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
with:
|
||||
files: |
|
||||
resources/mobile/platforms/android/eeuiApp/app/build/outputs/apk/release/*.apk
|
||||
35
.github/workflows/publish-desktop-mac.yml
vendored
35
.github/workflows/publish-desktop-mac.yml
vendored
@@ -1,35 +0,0 @@
|
||||
name: Publish Mac
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
Build:
|
||||
name: Build Mac
|
||||
runs-on: macos-12
|
||||
environment: build
|
||||
|
||||
if: startsWith(github.event.ref, 'refs/tags/v')
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Use Node.js 20.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 20.x
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
APPLEID: ${{ secrets.APPLEID }}
|
||||
APPLEIDPASS: ${{ secrets.APPLEIDPASS }}
|
||||
CSC_LINK: ${{ secrets.CSC_LINK }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
||||
DP_KEY: ${{ secrets.DP_KEY }}
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
GH_REPOSITORY: ${{ github.repository }}
|
||||
run: |
|
||||
./cmd electron mac
|
||||
|
||||
31
.github/workflows/publish-desktop-win.yml
vendored
31
.github/workflows/publish-desktop-win.yml
vendored
@@ -1,31 +0,0 @@
|
||||
name: Publish Win
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
Build:
|
||||
name: Build Windows
|
||||
runs-on: windows-latest
|
||||
environment: build
|
||||
|
||||
if: startsWith(github.event.ref, 'refs/tags/v')
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Use Node.js 20.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 20.x
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
DP_KEY: ${{ secrets.DP_KEY }}
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
GH_REPOSITORY: ${{ github.repository }}
|
||||
shell: bash
|
||||
run: |
|
||||
./cmd electron win
|
||||
261
.github/workflows/publish.yml
vendored
Normal file
261
.github/workflows/publish.yml
vendored
Normal file
@@ -0,0 +1,261 @@
|
||||
name: "Publish"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "pro"
|
||||
- "dev"
|
||||
|
||||
jobs:
|
||||
check-version:
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
should_release: ${{ steps.check-tag.outputs.should_release }}
|
||||
version: ${{ steps.get-version.outputs.version }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Get version from package.json
|
||||
id: get-version
|
||||
run: |
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Check if tag exists
|
||||
id: check-tag
|
||||
run: |
|
||||
VERSION=${{ steps.get-version.outputs.version }}
|
||||
if git ls-remote --tags origin | grep -q "refs/tags/v${VERSION}$"; then
|
||||
echo "This version v${VERSION} has been released"
|
||||
echo "should_release=false" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "Version v${VERSION} has not been released, continue building"
|
||||
echo "should_release=true" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
create-release:
|
||||
needs: check-version
|
||||
if: needs.check-version.outputs.should_release == 'true'
|
||||
permissions:
|
||||
contents: write
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
release_id: ${{ steps.create-release.outputs.result }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Create Release
|
||||
id: create-release
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
// 获取最新的 tag
|
||||
const { data: tags } = await github.rest.repos.listTags({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
per_page: 1
|
||||
});
|
||||
|
||||
// 获取提交日志
|
||||
let changelog = '';
|
||||
if (tags.length > 0) {
|
||||
const { data: commits } = await github.rest.repos.compareCommits({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
base: tags[0].name,
|
||||
head: 'HEAD'
|
||||
});
|
||||
|
||||
// 按类型分组提交
|
||||
const groups = {
|
||||
'feat:': { title: '## Features', commits: new Set() },
|
||||
'fix:': { title: '## Bug Fixes', commits: new Set() },
|
||||
'perf:': { title: '## Performance Improvements', commits: new Set() }
|
||||
};
|
||||
|
||||
// 分类收集提交,使用 Set 去重
|
||||
commits.commits.forEach(commit => {
|
||||
const message = commit.commit.message.split('\n')[0].trim();
|
||||
for (const [prefix, group] of Object.entries(groups)) {
|
||||
if (message.startsWith(prefix)) {
|
||||
// 移除前缀后添加到对应分组
|
||||
const cleanMessage = message.slice(prefix.length).trim();
|
||||
group.commits.add(cleanMessage); // 使用 Set.add 自动去重
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 生成更新日志
|
||||
const sections = [];
|
||||
for (const group of Object.values(groups)) {
|
||||
if (group.commits.size > 0) {
|
||||
sections.push(`${group.title}\n\n${Array.from(group.commits).map(msg => `- ${msg}`).join('\n')}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (sections.length > 0) {
|
||||
changelog = '# Changelog\n\n' + sections.join('\n\n');
|
||||
}
|
||||
}
|
||||
|
||||
// 创建 release
|
||||
const { data } = await github.rest.repos.createRelease({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
tag_name: `v${{ needs.check-version.outputs.version }}`,
|
||||
name: `${{ needs.check-version.outputs.version }}`,
|
||||
body: changelog || 'No significant changes in this release.',
|
||||
draft: true,
|
||||
prerelease: false
|
||||
})
|
||||
return data.id
|
||||
|
||||
build-client:
|
||||
needs: [ check-version, create-release ]
|
||||
if: needs.check-version.outputs.should_release == 'true'
|
||||
permissions:
|
||||
contents: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- platform: "macos-latest"
|
||||
build_type: "mac"
|
||||
- platform: "ubuntu-latest"
|
||||
build_type: "android"
|
||||
- platform: "windows-latest"
|
||||
build_type: "windows"
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
environment: build
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Use Node.js 20.x
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20.x
|
||||
|
||||
# Android 构建步骤
|
||||
- name: (Android) Build Js
|
||||
if: matrix.build_type == 'android'
|
||||
uses: nick-fields/retry@v2
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: |
|
||||
git submodule init
|
||||
git submodule update --remote "resources/mobile"
|
||||
./cmd appbuild publish
|
||||
|
||||
- name: (Android) Setup JDK 11
|
||||
if: matrix.build_type == 'android'
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: "zulu"
|
||||
java-version: "11"
|
||||
|
||||
- name: (Android) Build App
|
||||
if: matrix.build_type == 'android'
|
||||
uses: nick-fields/retry@v2
|
||||
with:
|
||||
timeout_minutes: 20
|
||||
max_attempts: 5
|
||||
command: |
|
||||
cd resources/mobile/platforms/android/eeuiApp
|
||||
chmod +x ./gradlew
|
||||
./gradlew assembleRelease --quiet
|
||||
|
||||
- name: (Android) Upload File
|
||||
if: matrix.build_type == 'android'
|
||||
env:
|
||||
PUBLISH_KEY: ${{ secrets.PUBLISH_KEY }}
|
||||
run: |
|
||||
node ./electron/build.js android-upload
|
||||
|
||||
- name: (Android) Upload Release
|
||||
if: matrix.build_type == 'android'
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
RELEASE_ID: ${{ needs.create-release.outputs.release_id }}
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const globby = require('globby');
|
||||
|
||||
// 查找 APK 文件
|
||||
const files = await globby('resources/mobile/platforms/android/eeuiApp/app/build/outputs/apk/release/*.apk');
|
||||
|
||||
for (const file of files) {
|
||||
const data = await fs.promises.readFile(file);
|
||||
|
||||
await github.rest.repos.uploadReleaseAsset({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
release_id: process.env.RELEASE_ID,
|
||||
name: path.basename(file),
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
# Mac 构建步骤
|
||||
- name: (Mac) Build Client
|
||||
if: matrix.build_type == 'mac'
|
||||
env:
|
||||
APPLEID: ${{ secrets.APPLEID }}
|
||||
APPLEIDPASS: ${{ secrets.APPLEIDPASS }}
|
||||
CSC_LINK: ${{ secrets.CSC_LINK }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
||||
PUBLISH_KEY: ${{ secrets.PUBLISH_KEY }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
run: |
|
||||
./cmd electron mac
|
||||
|
||||
# Windows 构建步骤
|
||||
- name: (Windows) Build Client
|
||||
if: matrix.build_type == 'windows'
|
||||
env:
|
||||
PUBLISH_KEY: ${{ secrets.PUBLISH_KEY }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
shell: bash
|
||||
run: |
|
||||
./cmd electron win
|
||||
|
||||
publish-release:
|
||||
needs: [ check-version, create-release, build-client ]
|
||||
if: needs.check-version.outputs.should_release == 'true' && github.ref == 'refs/heads/pro'
|
||||
permissions:
|
||||
contents: write
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Publish Release
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
RELEASE_ID: ${{ needs.create-release.outputs.release_id }}
|
||||
with:
|
||||
script: |
|
||||
github.rest.repos.updateRelease({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
release_id: process.env.RELEASE_ID,
|
||||
draft: false,
|
||||
prerelease: false
|
||||
})
|
||||
|
||||
- name: Publish Official
|
||||
env:
|
||||
PUBLISH_KEY: ${{ secrets.PUBLISH_KEY }}
|
||||
run: |
|
||||
node ./electron/build.js published
|
||||
127
.prefetch
127
.prefetch
@@ -1,27 +1,110 @@
|
||||
office/web-apps/apps/api/documents/api.js?hash={version}
|
||||
office/7.5.1-23/sdkjs/cell/css/main.css
|
||||
office/7.5.1-23/web-apps/apps/spreadsheeteditor/main/resources/css/app.css
|
||||
office/7.5.1-23/web-apps/vendor/requirejs/require.js
|
||||
office/7.5.1-23/web-apps/apps/spreadsheeteditor/main/app.js
|
||||
office/7.5.1-23/sdkjs/common/AllFonts.js
|
||||
office/7.5.1-23/web-apps/vendor/xregexp/xregexp-all-min.js
|
||||
office/7.5.1-23/web-apps/vendor/socketio/socket.io.min.js
|
||||
office/7.5.1-23/sdkjs/cell/sdk-all-min.js
|
||||
office/7.5.1-23/sdkjs/cell/sdk-all.js
|
||||
office/7.5.1-23/sdkjs/common/libfont/engine/fonts.js
|
||||
office/7.5.1-23/sdkjs/common/Charts/ChartStyles.js
|
||||
office/7.5.1-23/sdkjs/common/libfont/engine/fonts.wasm
|
||||
office/7.5.1-23/web-apps/apps/presentationeditor/main/resources/css/app.css
|
||||
office/7.5.1-23/web-apps/apps/presentationeditor/main/app.js
|
||||
office/7.5.1-23/sdkjs/slide/sdk-all-min.js
|
||||
office/7.5.1-23/sdkjs/slide/sdk-all.js
|
||||
office/7.5.1-23/sdkjs/slide/themes//themes.js
|
||||
office/7.5.1-23/web-apps/apps/documenteditor/main/resources/css/app.css
|
||||
office/7.5.1-23/web-apps/apps/documenteditor/main/app.js
|
||||
office/7.5.1-23/sdkjs/word/sdk-all-min.js
|
||||
office/7.5.1-23/sdkjs/word/sdk-all.js
|
||||
|
||||
office/{path}/fonts/000
|
||||
office/{path}/fonts/020
|
||||
office/{path}/fonts/020
|
||||
office/{path}/fonts/020
|
||||
office/{path}/fonts/022
|
||||
office/{path}/fonts/022
|
||||
office/{path}/fonts/022
|
||||
office/{path}/fonts/023
|
||||
office/{path}/fonts/023
|
||||
office/{path}/fonts/023
|
||||
office/{path}/fonts/024
|
||||
office/{path}/fonts/024
|
||||
office/{path}/fonts/024
|
||||
office/{path}/fonts/027
|
||||
office/{path}/fonts/027
|
||||
office/{path}/fonts/028
|
||||
office/{path}/fonts/028
|
||||
office/{path}/fonts/029
|
||||
office/{path}/fonts/029
|
||||
office/{path}/fonts/030
|
||||
office/{path}/fonts/030
|
||||
office/{path}/fonts/036
|
||||
office/{path}/fonts/036
|
||||
office/{path}/fonts/037
|
||||
office/{path}/fonts/037
|
||||
office/{path}/fonts/038
|
||||
office/{path}/fonts/038
|
||||
office/{path}/fonts/039
|
||||
office/{path}/fonts/039
|
||||
office/{path}/fonts/058
|
||||
office/{path}/fonts/058
|
||||
office/{path}/fonts/058
|
||||
office/{path}/fonts/059
|
||||
office/{path}/fonts/059
|
||||
office/{path}/fonts/059
|
||||
office/{path}/fonts/060
|
||||
office/{path}/fonts/060
|
||||
office/{path}/fonts/060
|
||||
office/{path}/fonts/061
|
||||
office/{path}/fonts/061
|
||||
office/{path}/fonts/061
|
||||
office/{path}/fonts/063
|
||||
office/{path}/fonts/065
|
||||
office/{path}/fonts/066
|
||||
office/{path}/fonts/081
|
||||
office/{path}/fonts/081
|
||||
office/{path}/fonts/081
|
||||
office/{path}/fonts/138
|
||||
office/{path}/fonts/184
|
||||
office/{path}/fonts/184
|
||||
office/{path}/sdkjs/cell/sdk-all-min.js
|
||||
office/{path}/sdkjs/cell/sdk-all.js
|
||||
office/{path}/sdkjs/common/AllFonts.js
|
||||
office/{path}/sdkjs/common/AllFonts.js
|
||||
office/{path}/sdkjs/common/AllFonts.js
|
||||
office/{path}/sdkjs/common/Charts/ChartStyles.js
|
||||
office/{path}/sdkjs/common/Charts/ChartStyles.js
|
||||
office/{path}/sdkjs/common/Charts/ChartStyles.js
|
||||
office/{path}/sdkjs/common/Images/fonts_thumbnail_ea@2x.png.bin
|
||||
office/{path}/sdkjs/common/Images/fonts_thumbnail_ea@2x.png.bin
|
||||
office/{path}/sdkjs/common/Images/fonts_thumbnail_ea@2x.png.bin
|
||||
office/{path}/sdkjs/common/libfont/engine/fonts.js
|
||||
office/{path}/sdkjs/common/libfont/engine/fonts.js
|
||||
office/{path}/sdkjs/common/libfont/engine/fonts.js
|
||||
office/{path}/sdkjs/common/libfont/engine/fonts.wasm
|
||||
office/{path}/sdkjs/common/libfont/engine/fonts.wasm
|
||||
office/{path}/sdkjs/common/libfont/engine/fonts.wasm
|
||||
office/{path}/sdkjs/slide/sdk-all-min.js
|
||||
office/{path}/sdkjs/slide/sdk-all.js
|
||||
office/{path}/sdkjs/word/sdk-all-min.js
|
||||
office/{path}/sdkjs/word/sdk-all.js
|
||||
office/{path}/web-apps/apps/documenteditor/main/app.js
|
||||
office/{path}/web-apps/apps/documenteditor/main/code.js
|
||||
office/{path}/web-apps/apps/documenteditor/main/locale/zh.json
|
||||
office/{path}/web-apps/apps/documenteditor/main/resources/css/app.css
|
||||
office/{path}/web-apps/apps/documenteditor/main/resources/img/iconssmall@2.5x.svg
|
||||
office/{path}/web-apps/apps/presentationeditor/main/app.js
|
||||
office/{path}/web-apps/apps/presentationeditor/main/code.js
|
||||
office/{path}/web-apps/apps/presentationeditor/main/locale/zh.json
|
||||
office/{path}/web-apps/apps/presentationeditor/main/resources/css/app.css
|
||||
office/{path}/web-apps/apps/presentationeditor/main/resources/img/iconsbig@2.5x.svg
|
||||
office/{path}/web-apps/apps/presentationeditor/main/resources/img/iconsbig@2x.png
|
||||
office/{path}/web-apps/apps/presentationeditor/main/resources/img/iconssmall@2.5x.svg
|
||||
office/{path}/web-apps/apps/spreadsheeteditor/main/app.js
|
||||
office/{path}/web-apps/apps/spreadsheeteditor/main/code.js
|
||||
office/{path}/web-apps/apps/spreadsheeteditor/main/locale/zh.json
|
||||
office/{path}/web-apps/apps/spreadsheeteditor/main/resources/css/app.css
|
||||
office/{path}/web-apps/apps/spreadsheeteditor/main/resources/formula-lang/zh_desc.json
|
||||
office/{path}/web-apps/apps/spreadsheeteditor/main/resources/img/iconssmall@2.5x.svg
|
||||
office/{path}/web-apps/apps/spreadsheeteditor/main/resources/img/iconssmall@2x.png
|
||||
office/{path}/web-apps/vendor/xregexp/xregexp-all-min.js
|
||||
office/{path}/web-apps/vendor/xregexp/xregexp-all-min.js
|
||||
office/{path}/web-apps/vendor/xregexp/xregexp-all-min.js
|
||||
|
||||
drawio/webapp/js/app.min.js
|
||||
drawio/webapp/js/stencils.min.js
|
||||
drawio/webapp/js/extensions.min.js
|
||||
drawio/webapp/js/shapes-14-6-5.min.js
|
||||
drawio/webapp/js/stencils.min.js
|
||||
drawio/webapp/math/es5/core.js
|
||||
drawio/webapp/math/es5/input/asciimath.js
|
||||
drawio/webapp/math/es5/input/tex.js
|
||||
drawio/webapp/math/es5/output/svg.js
|
||||
drawio/webapp/math/es5/output/svg/fonts/tex.js
|
||||
drawio/webapp/styles/grapheditor.css
|
||||
|
||||
minder/css/chunk-vendors.fe9c56c6.css
|
||||
minder/js/app.aa385de3.js
|
||||
minder/js/chunk-vendors.cc7455b8.js
|
||||
338
CHANGELOG.md
338
CHANGELOG.md
@@ -2,6 +2,338 @@
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [0.40.78]
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- 修复退出群组不完全的问题
|
||||
- 修复退出还能收到推送的情况
|
||||
- 修复账号被禁用之后还能收到推送和邮件
|
||||
- 任务首次聊天发表情失败的情况
|
||||
- AI聊天缺少最后一句话的情况
|
||||
- 文件打包下载
|
||||
|
||||
### Performance
|
||||
|
||||
- 优化会话成员列表查询
|
||||
- 重复添加任务的情况
|
||||
- 重复添加任务列表的情况
|
||||
- 优化消息样式
|
||||
- 优化websocket消息
|
||||
- 优化快捷选择
|
||||
- 延期任务支持快选时间
|
||||
- 优化消息阅读机制
|
||||
- 新增文件打包下载权限设置
|
||||
- 升级electron框架
|
||||
- 优化深色主题
|
||||
- 优化表情包资源
|
||||
- 优化客户端子窗口
|
||||
- 优化项目列表
|
||||
- 优化录制语音消息
|
||||
- 优化任务内容
|
||||
- 优化任务提交添加继续
|
||||
- 移动端审批窗口点击人员头像直接进入会话
|
||||
- 新增系统别名设置
|
||||
|
||||
## [0.40.40]
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- 任务内容保存后图片消失的情况
|
||||
- 修复上传超大尺寸图片
|
||||
|
||||
### Performance
|
||||
|
||||
- 优化窗口加载速度
|
||||
- 优化国际化
|
||||
- 优化图片上传
|
||||
- 优化用户在线状态
|
||||
- 优化小屏幕登录页
|
||||
- 优化本地资源
|
||||
- 优化iOS上传图片颠倒的问题
|
||||
- 优化桌面端通知图标
|
||||
- 优化资源预取
|
||||
- 优化emoji表情回复的判断
|
||||
- 更新office组件
|
||||
- 优化审批功能
|
||||
- 优化客户端升级
|
||||
- 优化客户端
|
||||
- 优化签到错误提示
|
||||
- 优化图片选择器
|
||||
- 优化邮件通知
|
||||
- 修复iOS下载中文名乱码的问题
|
||||
|
||||
## [0.39.97]
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- IOS16-无法打开定位签到的问题
|
||||
|
||||
### Performance
|
||||
|
||||
- 优化初始化数据
|
||||
- 优化一处定位签到的问题
|
||||
- 升级海豚表情包
|
||||
- 优化新窗口链接打开逻辑
|
||||
- 添加会议机器人快捷菜单
|
||||
|
||||
## [0.39.88]
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- 翻译聊天内容参数错误
|
||||
|
||||
### Performance
|
||||
|
||||
- 优化使用默认浏览器打开链接
|
||||
- 添加定位签到
|
||||
- 优化打开会议
|
||||
- 优化打开会话逻辑
|
||||
|
||||
## [0.39.73]
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- 无法打开项目的情况
|
||||
- 搜索特殊字符报错的情况
|
||||
|
||||
### Features
|
||||
|
||||
- 添加定位签到
|
||||
|
||||
### Performance
|
||||
|
||||
- 优化从审批点击头像发起会话
|
||||
|
||||
## [0.39.66]
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- 修复会话内加载待办为空的情况
|
||||
|
||||
### Performance
|
||||
|
||||
- 图片浏览
|
||||
- 优化会话搜索
|
||||
- 优化国际化语言
|
||||
- 优化消息已读逻辑
|
||||
- 优化app新版本提示
|
||||
- 优化文字头像
|
||||
- 优化修改任务load
|
||||
|
||||
## [0.39.52]
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- 无法清理数据缓存的情况
|
||||
|
||||
### Performance
|
||||
|
||||
- 优化预览消息
|
||||
- 优化移动端输入法换行
|
||||
- 审批消息预览图片
|
||||
- 删除冗余字段
|
||||
- 优化索引
|
||||
- 优化国际化语言
|
||||
- 优化会话查询
|
||||
|
||||
## [0.39.39]
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- 目录拼错的情况
|
||||
- 设置子任务时间主任务出现1970的情况
|
||||
- 消息溢出的情况
|
||||
|
||||
### Features
|
||||
|
||||
- 消息翻译支持切换语言
|
||||
|
||||
### Performance
|
||||
|
||||
- 优化国际化语言
|
||||
- 审批支持点击头像进入私聊
|
||||
- 优化删除临时文件
|
||||
- 优化缩略图
|
||||
|
||||
## [0.39.21]
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- 无法设置修改子任务时间的情况
|
||||
- 判断广告页逻辑错误
|
||||
- 下载页控制台报错处理
|
||||
|
||||
### Performance
|
||||
|
||||
- 优化长按消息菜单显示逻辑
|
||||
- 优化会话全屏输入功能菜单固定下方
|
||||
- 优化聊天输入时页面乱滚动的情况
|
||||
- 优化导出统计国际化
|
||||
- 支持会员选择窗标题省略号点击查看全标题
|
||||
- 任务内容加载太久显示load
|
||||
- 任务日志显示子任务关联
|
||||
- 审批评论图片浏览可滑动连续查看
|
||||
- 审批评论优化显示缩略图
|
||||
- 任务变化通知加上任务标题
|
||||
- 新任务提醒区分协助还是负责
|
||||
- 优化审批通知标题
|
||||
- 优化推送预览
|
||||
- 优化md标题样式
|
||||
- 新增查看更新日志
|
||||
|
||||
### Styling
|
||||
|
||||
- 推广页样式调整
|
||||
|
||||
## [0.38.94]
|
||||
|
||||
### Performance
|
||||
|
||||
- 优化国际化、优化显示
|
||||
|
||||
## [0.38.91]
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- 周报默认内容已完成工作负责人不显示的情况
|
||||
- 无法清除计划时间
|
||||
- 选择时间起始不正确的问题
|
||||
- 修复iOS日历无法正常显示的情况
|
||||
- 签到信息预览错误
|
||||
|
||||
### Performance
|
||||
|
||||
- 优化人脸签到设置
|
||||
- 优化消息搜索速度
|
||||
- 优化显示
|
||||
- 优化图片压缩
|
||||
- 优化cmd命令
|
||||
|
||||
## [0.38.73]
|
||||
|
||||
### Performance
|
||||
|
||||
- 优化显示效果
|
||||
|
||||
## [0.38.70]
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- 推送失败的情况
|
||||
- 导出签到数据快速选择时间
|
||||
- 搜索区域无法回车搜索的问题
|
||||
- 未领任务提醒机器人无须加入项目
|
||||
- 日历中总是显示时间相差一个月
|
||||
|
||||
### Documentation
|
||||
|
||||
- 更新docker-compose
|
||||
|
||||
### Features
|
||||
|
||||
- 支持人脸打卡设备
|
||||
|
||||
### Performance
|
||||
|
||||
- 升级onlyoffice
|
||||
- 优化人脸签到功能
|
||||
- 优化加载通讯录数量
|
||||
- 优化继续添加任务数据处理
|
||||
- 优化翻译
|
||||
- 更新gpt的一些模型
|
||||
- 优化消息组件
|
||||
- 优化后端翻译
|
||||
- 优化创建任务提示时间冲突的逻辑
|
||||
- 人脸打卡配置
|
||||
- 签到设置保存
|
||||
- 签到设备显示
|
||||
- 打卡标签页
|
||||
|
||||
## [0.38.27]
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- 修复表情回应一处报错
|
||||
- 任务到期时间不变颜色
|
||||
- 聊天提及内容错位的情况
|
||||
- 首次修改任务时间不提示时间冲突的问题
|
||||
- 添加任务时不设置时间无须提示任务冲突
|
||||
- 负责人修改后不显示在仪表盘的情况
|
||||
- 添加任务选择今天时间无效的情况
|
||||
- 修复url-token登录异常问题
|
||||
|
||||
### Features
|
||||
|
||||
- 官网侧边导航按钮新增谷歌分析事件追踪
|
||||
|
||||
### Performance
|
||||
|
||||
- 优化翻译
|
||||
- 优化时间组件
|
||||
- 优化日历样式
|
||||
- 手机端消息菜单居中
|
||||
- 优化数据库外部访问方式
|
||||
- 优化消息选择文本
|
||||
- 优化表情搜索
|
||||
- 任务描述再次点击隐藏菜单
|
||||
- 优化工作包括模板
|
||||
- 仪表盘任务列表支持折叠
|
||||
- 新增修改任务时间权限
|
||||
- 优化子任务读取失败
|
||||
|
||||
## [0.37.98]
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- 修复任务可见性为非项目人员时项目负责人不可见的bug
|
||||
- 修改谷歌分析以及谷歌推广文件命名
|
||||
- 修改谷歌分析代码
|
||||
- 删除打印
|
||||
- 修改关于我们页面公司介绍文案
|
||||
|
||||
### Features
|
||||
|
||||
- 网页右下角导航改为点击显示以及手机端点击拨打电话直接拨号
|
||||
- 更改审批版本
|
||||
- 官网新增谷歌分析代码
|
||||
- 页面新增谷歌分析
|
||||
|
||||
### Performance
|
||||
|
||||
- 1.优化审批流程-审批人审核过后自动通过 2. 优化审批评论图片可以左右滑动查看
|
||||
|
||||
## [0.37.76]
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- 无法下载大文件
|
||||
- 修改关于我们页面公司介绍文案
|
||||
|
||||
### Performance
|
||||
|
||||
- 优化聊天视频预览
|
||||
- 优化打包下载
|
||||
- 支持上传mov、webm视频
|
||||
|
||||
## [0.37.71]
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- 切换对话之后无法通过右键@
|
||||
|
||||
### Performance
|
||||
|
||||
- 通讯录菜单添加会议
|
||||
- 优化文件里预览图片
|
||||
- 优化消息描述
|
||||
|
||||
## [0.37.65]
|
||||
|
||||
### Features
|
||||
|
||||
- 操作人员离职对okr的移交处理
|
||||
|
||||
## [0.37.62]
|
||||
|
||||
### Bug Fixes
|
||||
@@ -517,6 +849,12 @@ All notable changes to this project will be documented in this file.
|
||||
|
||||
- 优化任务修改
|
||||
|
||||
## [0.33.37]
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- 更新导致的小问题
|
||||
|
||||
## [0.33.34]
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -88,7 +88,7 @@ cd dootask
|
||||
./cmd redis "your command" # To run a redis command
|
||||
./cmd composer "your command" # To run a composer command
|
||||
./cmd supervisorctl "your command" # To run a supervisorctl command
|
||||
./cmd mysql "your command" # To run a mysql command (backup: Backup database, recovery: Restore database)
|
||||
./cmd mysql "your command" # To run a mysql command (backup: Backup database, recovery: Restore database, open: Open database external port access, close: Close database external port access)
|
||||
```
|
||||
|
||||
### SSL configuration
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
# 客户端说明
|
||||
|
||||
## 1、App客户端
|
||||
|
||||
#### 1.1、说明
|
||||
|
||||
目录 `resources/mobile`,使用`eeui.app`框架,遵从eeui的开发文档进行打包开发app
|
||||
|
||||
#### 1.2、编译App
|
||||
|
||||
1. 在项目目录执行 `./cmd appbuild [build|setting]` 编译
|
||||
2. 进入 `resources/mobile` eeui框架内打包Android或iOS应用
|
||||
|
||||
|
||||
## 2、PC/Mac客户端
|
||||
|
||||
#### 2.1、说明
|
||||
|
||||
目录 `electron`,使用`electron`框架,遵从electron的开发文档进行打包客户端
|
||||
|
||||
#### 2.2、编译客户端
|
||||
|
||||
在项目目录执行 `./cmd electron [dev]` 根据提示编译
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ cd dootask
|
||||
./cmd redis "your command" # 运行 redis 命令
|
||||
./cmd composer "your command" # 运行 composer 命令
|
||||
./cmd supervisorctl "your command" # 运行 supervisorctl 命令
|
||||
./cmd mysql "your command" # 运行 mysql 命令 (backup: 备份数据库,recovery: 还原数据库)
|
||||
./cmd mysql "your command" # 运行 mysql 命令 (backup: 备份数据库,recovery: 还原数据库,open: 开启数据库外部端口访问,close: 关闭数据库外部端口访问)
|
||||
```
|
||||
|
||||
### SSL 配置
|
||||
|
||||
@@ -1,26 +1,31 @@
|
||||
# 发布说明
|
||||
# 发布
|
||||
|
||||
## 发布前
|
||||
## 准备工作
|
||||
|
||||
1. 添加环境变量 `APPLEID`、`APPLEIDPASS` 用于公证
|
||||
2. 添加环境变量 `CSC_LINK`、`CSC_KEY_PASSWORD` 用于签名
|
||||
3. 添加环境变量 `GH_TOKEN`、`GH_REPOSITORY` 用于发布到GitHub
|
||||
4. 添加环境变量 `DP_KEY` 用于发布到私有服务器
|
||||
3. 添加环境变量 `GITHUB_TOKEN`、`GITHUB_REPOSITORY` 用于发布到GitHub(GitHub Actions 发布不需要)
|
||||
4. 添加环境变量 `PUBLISH_KEY` 用于发布到私有服务器
|
||||
|
||||
## 通过 GitHub Actions 发布
|
||||
## 发布版本
|
||||
|
||||
1. 执行 `npm run version` 生成版本
|
||||
2. 执行 `npm run build` 编译前端
|
||||
3. 执行 `git commit` 提交并推送
|
||||
4. 添加并推送标签
|
||||
```shell
|
||||
npm run translate # 翻译(可选)
|
||||
npm run version # 生成版本
|
||||
npm run build # 编译前端
|
||||
```
|
||||
|
||||
## 本地发布
|
||||
说明:
|
||||
|
||||
1. 执行 `npm run version` 生成版本
|
||||
2. 执行 `npm run build` 编译前端
|
||||
3. 执行 `./cmd electron` 相关操作
|
||||
- 执行 `npm run build` 作用是生成网页端;
|
||||
- 客户端 (Windows、Mac、Android) 会通过 GitHub Actions 自动生成并发布;所以,如果要自动发布只需要提交git并推送即可;
|
||||
- 如果想手动生成客户端执行 `./cmd electron` 根据提示选择操作。
|
||||
|
||||
## 编译App
|
||||
|
||||
1. 执行 `./cmd appbuild [setting]` 编译
|
||||
2. 进入 `resources/mobile` eeui框架内打包Android或iOS应用
|
||||
## 编译 App
|
||||
|
||||
```shell
|
||||
./cmd appbuild publish # 编译生成App需要的资源
|
||||
```
|
||||
|
||||
编译完后进入 `resources/mobile` EEUI框架目录内打包 Android 或 iOS 应用(Android 以实现 GitHub Actions 自动发布)
|
||||
|
||||
323
_ide_helper.php
323
_ide_helper.php
@@ -16045,7 +16045,7 @@
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @see \Maatwebsite\Excel\Mixins\DownloadCollection::downloadExcel()
|
||||
* @see \Maatwebsite\Excel\Mixins\DownloadCollectionMixin::downloadExcel()
|
||||
* @param string $fileName
|
||||
* @param string|null $writerType
|
||||
* @param mixed $withHeadings
|
||||
@@ -16059,7 +16059,7 @@
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @see \Maatwebsite\Excel\Mixins\StoreCollection::storeExcel()
|
||||
* @see \Maatwebsite\Excel\Mixins\StoreCollectionMixin::storeExcel()
|
||||
* @param string $filePath
|
||||
* @param string|null $disk
|
||||
* @param string|null $writerType
|
||||
@@ -16439,6 +16439,247 @@
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace Laravolt\Avatar {
|
||||
/**
|
||||
*
|
||||
*
|
||||
*/
|
||||
class Facade {
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
public static function setGenerator($generator)
|
||||
{
|
||||
/** @var \Laravolt\Avatar\Avatar $instance */
|
||||
return $instance->setGenerator($generator);
|
||||
}
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
public static function create($name)
|
||||
{
|
||||
/** @var \Laravolt\Avatar\Avatar $instance */
|
||||
return $instance->create($name);
|
||||
}
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
public static function applyTheme($config)
|
||||
{
|
||||
/** @var \Laravolt\Avatar\Avatar $instance */
|
||||
return $instance->applyTheme($config);
|
||||
}
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
public static function addTheme($name, $config)
|
||||
{
|
||||
/** @var \Laravolt\Avatar\Avatar $instance */
|
||||
return $instance->addTheme($name, $config);
|
||||
}
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
public static function toBase64()
|
||||
{
|
||||
/** @var \Laravolt\Avatar\Avatar $instance */
|
||||
return $instance->toBase64();
|
||||
}
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
public static function save($path, $quality = 90)
|
||||
{
|
||||
/** @var \Laravolt\Avatar\Avatar $instance */
|
||||
return $instance->save($path, $quality);
|
||||
}
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
public static function toSvg()
|
||||
{
|
||||
/** @var \Laravolt\Avatar\Avatar $instance */
|
||||
return $instance->toSvg();
|
||||
}
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
public static function toGravatar($param = null)
|
||||
{
|
||||
/** @var \Laravolt\Avatar\Avatar $instance */
|
||||
return $instance->toGravatar($param);
|
||||
}
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
public static function getInitial()
|
||||
{
|
||||
/** @var \Laravolt\Avatar\Avatar $instance */
|
||||
return $instance->getInitial();
|
||||
}
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
public static function getImageObject()
|
||||
{
|
||||
/** @var \Laravolt\Avatar\Avatar $instance */
|
||||
return $instance->getImageObject();
|
||||
}
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
public static function buildAvatar()
|
||||
{
|
||||
/** @var \Laravolt\Avatar\Avatar $instance */
|
||||
return $instance->buildAvatar();
|
||||
}
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
public static function getAttribute($key)
|
||||
{
|
||||
/** @var \Laravolt\Avatar\Avatar $instance */
|
||||
return $instance->getAttribute($key);
|
||||
}
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
public static function setTheme($theme)
|
||||
{
|
||||
/** @var \Laravolt\Avatar\Avatar $instance */
|
||||
return $instance->setTheme($theme);
|
||||
}
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
public static function setBackground($hex)
|
||||
{
|
||||
/** @var \Laravolt\Avatar\Avatar $instance */
|
||||
return $instance->setBackground($hex);
|
||||
}
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
public static function setForeground($hex)
|
||||
{
|
||||
/** @var \Laravolt\Avatar\Avatar $instance */
|
||||
return $instance->setForeground($hex);
|
||||
}
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
public static function setDimension($width, $height = null)
|
||||
{
|
||||
/** @var \Laravolt\Avatar\Avatar $instance */
|
||||
return $instance->setDimension($width, $height);
|
||||
}
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
public static function setFontSize($size)
|
||||
{
|
||||
/** @var \Laravolt\Avatar\Avatar $instance */
|
||||
return $instance->setFontSize($size);
|
||||
}
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
public static function setFontFamily($font)
|
||||
{
|
||||
/** @var \Laravolt\Avatar\Avatar $instance */
|
||||
return $instance->setFontFamily($font);
|
||||
}
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
public static function setBorder($size, $color, $radius = 0)
|
||||
{
|
||||
/** @var \Laravolt\Avatar\Avatar $instance */
|
||||
return $instance->setBorder($size, $color, $radius);
|
||||
}
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
public static function setBorderRadius($radius)
|
||||
{
|
||||
/** @var \Laravolt\Avatar\Avatar $instance */
|
||||
return $instance->setBorderRadius($radius);
|
||||
}
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
public static function setShape($shape)
|
||||
{
|
||||
/** @var \Laravolt\Avatar\Avatar $instance */
|
||||
return $instance->setShape($shape);
|
||||
}
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
public static function setChars($chars)
|
||||
{
|
||||
/** @var \Laravolt\Avatar\Avatar $instance */
|
||||
return $instance->setChars($chars);
|
||||
}
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
public static function setFont($font)
|
||||
{
|
||||
/** @var \Laravolt\Avatar\Avatar $instance */
|
||||
return $instance->setFont($font);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace Maatwebsite\Excel\Facades {
|
||||
@@ -16467,9 +16708,10 @@
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param string|null $disk Fallback for usage with named properties
|
||||
* @param object $export
|
||||
* @param string $filePath
|
||||
* @param string|null $disk
|
||||
* @param string|null $diskName
|
||||
* @param string $writerType
|
||||
* @param mixed $diskOptions
|
||||
* @return bool
|
||||
@@ -16477,10 +16719,10 @@
|
||||
* @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
|
||||
* @static
|
||||
*/
|
||||
public static function store($export, $filePath, $diskName = null, $writerType = null, $diskOptions = [])
|
||||
public static function store($export, $filePath, $diskName = null, $writerType = null, $diskOptions = [], $disk = null)
|
||||
{
|
||||
/** @var \Maatwebsite\Excel\Excel $instance */
|
||||
return $instance->store($export, $filePath, $diskName, $writerType, $diskOptions);
|
||||
return $instance->store($export, $filePath, $diskName, $writerType, $diskOptions, $disk);
|
||||
}
|
||||
/**
|
||||
*
|
||||
@@ -16698,7 +16940,7 @@
|
||||
* @param $pathToFile string The file to open
|
||||
* @param \Madnest\Madzipper\Repositories\RepositoryInterface|string $type The type of the archive, defaults to zip, possible are zip, phar
|
||||
* @throws \RuntimeException
|
||||
* @throws \Exception
|
||||
* @throws Exception
|
||||
* @throws \InvalidArgumentException
|
||||
* @return \Madnest\Madzipper\Madzipper Madzipper instance
|
||||
* @static
|
||||
@@ -16712,7 +16954,7 @@
|
||||
* Create a new zip archive or open an existing one.
|
||||
*
|
||||
* @param string $pathToFile
|
||||
* @throws \Exception
|
||||
* @throws Exception
|
||||
* @return self
|
||||
* @static
|
||||
*/
|
||||
@@ -16725,7 +16967,7 @@
|
||||
* Create a new phar file or open one.
|
||||
*
|
||||
* @param string $pathToFile
|
||||
* @throws \Exception
|
||||
* @throws Exception
|
||||
* @return self
|
||||
* @static
|
||||
*/
|
||||
@@ -16738,7 +16980,7 @@
|
||||
* Create a new rar file or open one.
|
||||
*
|
||||
* @param string $pathToFile
|
||||
* @throws \Exception
|
||||
* @throws Exception
|
||||
* @return self
|
||||
* @static
|
||||
*/
|
||||
@@ -16755,7 +16997,7 @@
|
||||
* @param $path string The path to extract to
|
||||
* @param array $files An array of files
|
||||
* @param int $methodFlags The Method the files should be treated
|
||||
* @throws \Exception
|
||||
* @throws Exception
|
||||
* @return void
|
||||
* @static
|
||||
*/
|
||||
@@ -16782,7 +17024,7 @@
|
||||
* Gets the content of a single file if available.
|
||||
*
|
||||
* @param $filePath string The full path (including all folders) of the file in the zip
|
||||
* @throws \Exception
|
||||
* @throws Exception
|
||||
* @return mixed returns the content or throws an exception
|
||||
* @static
|
||||
*/
|
||||
@@ -18774,6 +19016,64 @@ namespace {
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @see \Maatwebsite\Excel\Mixins\DownloadQueryMacro::__invoke()
|
||||
* @param string $fileName
|
||||
* @param string|null $writerType
|
||||
* @param mixed $withHeadings
|
||||
* @static
|
||||
*/
|
||||
public static function downloadExcel($fileName, $writerType = null, $withHeadings = false)
|
||||
{
|
||||
return \Illuminate\Database\Eloquent\Builder::downloadExcel($fileName, $writerType, $withHeadings);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @see \Maatwebsite\Excel\Mixins\StoreQueryMacro::__invoke()
|
||||
* @param string $filePath
|
||||
* @param string|null $disk
|
||||
* @param string|null $writerType
|
||||
* @param mixed $withHeadings
|
||||
* @static
|
||||
*/
|
||||
public static function storeExcel($filePath, $disk = null, $writerType = null, $withHeadings = false)
|
||||
{
|
||||
return \Illuminate\Database\Eloquent\Builder::storeExcel($filePath, $disk, $writerType, $withHeadings);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @see \Maatwebsite\Excel\Mixins\ImportMacro::__invoke()
|
||||
* @param string $filename
|
||||
* @param string|null $disk
|
||||
* @param string|null $readerType
|
||||
* @static
|
||||
*/
|
||||
public static function import($filename, $disk = null, $readerType = null)
|
||||
{
|
||||
return \Illuminate\Database\Eloquent\Builder::import($filename, $disk, $readerType);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @see \Maatwebsite\Excel\Mixins\ImportAsMacro::__invoke()
|
||||
* @param string $filename
|
||||
* @param callable $mapping
|
||||
* @param string|null $disk
|
||||
* @param string|null $readerType
|
||||
* @static
|
||||
*/
|
||||
public static function importAs($filename, $mapping, $disk = null, $readerType = null)
|
||||
{
|
||||
return \Illuminate\Database\Eloquent\Builder::importAs($filename, $mapping, $disk, $readerType);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @see \App\Providers\AppServiceProvider::boot()
|
||||
* @static
|
||||
*/
|
||||
@@ -20814,6 +21114,7 @@ namespace {
|
||||
class View extends \Illuminate\Support\Facades\View {}
|
||||
class Flare extends \Facade\Ignition\Facades\Flare {}
|
||||
class Image extends \Intervention\Image\Facades\Image {}
|
||||
class Avatar extends \Laravolt\Avatar\Facade {}
|
||||
class Excel extends \Maatwebsite\Excel\Facades\Excel {}
|
||||
class Madzipper extends \Madnest\Madzipper\Facades\Madzipper {}
|
||||
class Captcha extends \Mews\Captcha\Facades\Captcha {}
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use Hhxsv5\LaravelS\Swoole\Events\ServerStartInterface;
|
||||
use Swoole\Http\Server;
|
||||
|
||||
class ServerStartEvent implements ServerStartInterface
|
||||
{
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public function handle(Server $server)
|
||||
{
|
||||
$server->startMsecTime = $this->msecTime();
|
||||
}
|
||||
|
||||
private function msecTime()
|
||||
{
|
||||
list($msec, $sec) = explode(' ', microtime());
|
||||
$time = explode(".", $sec . ($msec * 1000));
|
||||
return $time[0];
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace App\Events;
|
||||
|
||||
use App\Models\WebSocket;
|
||||
use Cache;
|
||||
use Hhxsv5\LaravelS\Swoole\Events\WorkerStartInterface;
|
||||
use Swoole\Http\Server;
|
||||
|
||||
@@ -16,9 +15,15 @@ class WorkerStartEvent implements WorkerStartInterface
|
||||
|
||||
public function handle(Server $server, $workerId)
|
||||
{
|
||||
if (isset($server->startMsecTime) && Cache::get("swooleServerStartMsecTime") != $server->startMsecTime) {
|
||||
Cache::forever("swooleServerStartMsecTime", $server->startMsecTime);
|
||||
WebSocket::query()->delete();
|
||||
// 仅在Worker进程启动时执行一次初始化代码
|
||||
$initTable = app('swoole')->initFlagTable;
|
||||
if ($initTable->incr('init_flag', 'value') === 1) {
|
||||
$this->handleFirstWorkerTasks();
|
||||
}
|
||||
}
|
||||
|
||||
private function handleFirstWorkerTasks()
|
||||
{
|
||||
WebSocket::query()->delete();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
namespace App\Exceptions;
|
||||
|
||||
use App\Module\Base;
|
||||
use App\Module\Image;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Throwable;
|
||||
|
||||
class Handler extends ExceptionHandler
|
||||
@@ -51,6 +53,11 @@ class Handler extends ExceptionHandler
|
||||
*/
|
||||
public function render($request, Throwable $e)
|
||||
{
|
||||
if ($e instanceof NotFoundHttpException) {
|
||||
if ($result = $this->ImagePathHandler($request)) {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
if ($e instanceof ApiException) {
|
||||
return response()->json(Base::retError($e->getMessage(), $e->getData(), $e->getCode()));
|
||||
} elseif ($e instanceof ModelNotFoundException) {
|
||||
@@ -78,4 +85,144 @@ class Handler extends ExceptionHandler
|
||||
parent::report($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 图片路径处理
|
||||
* @param $request
|
||||
* @return \Illuminate\Http\JsonResponse|\Symfony\Component\HttpFoundation\BinaryFileResponse|null
|
||||
*/
|
||||
private function ImagePathHandler($request)
|
||||
{
|
||||
$path = $request->path();
|
||||
|
||||
// 处理图片
|
||||
$patternCrop = '/^(uploads\/.*\.(png|jpg|jpeg))\/crop\/([^\/]+)$/';
|
||||
$patternThumb = '/^(uploads\/.*)_thumb\.(png|jpg|jpeg)$/';
|
||||
$matchesCrop = null;
|
||||
$matchesThumb = null;
|
||||
if (preg_match($patternCrop, $path, $matchesCrop) || preg_match($patternThumb, $path, $matchesThumb)) {
|
||||
// 获取参数
|
||||
if ($matchesCrop) {
|
||||
$file = $matchesCrop[1];
|
||||
$ext = $matchesCrop[2];
|
||||
$rules = preg_replace('/\s+/', '', $matchesCrop[3]);
|
||||
$rules = str_replace(['=', '&'], [':', ','], $rules);
|
||||
$rules = explode(',', $rules);
|
||||
} elseif ($matchesThumb) {
|
||||
$file = $matchesThumb[1];
|
||||
$ext = $matchesThumb[2];
|
||||
$rules = ['percentage:320x0'];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
if (empty($rules)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 提取年月
|
||||
$Ym = date("Ym");
|
||||
if (preg_match('/\/(\d{6})\//', $file, $ms)) {
|
||||
$Ym = $ms[1];
|
||||
}
|
||||
|
||||
// 文件存在直接返回
|
||||
$dirName = str_replace(['/', '.'], '_', $file);
|
||||
$fileName = str_replace([':', ','], ['-', '_'], implode(',', $rules)) . '.' . $ext;
|
||||
$savePath = public_path('uploads/tmp/crop/' . $Ym . '/' . $dirName . '/' . $fileName);
|
||||
if (file_exists($savePath)) {
|
||||
// 设置头部声明图片缓存
|
||||
return response()->file($savePath, [
|
||||
'Pragma' => 'public',
|
||||
'Cache-Control' => 'max-age=1814400',
|
||||
'Expires' => gmdate('D, d M Y H:i:s', time() + 1814400) . ' GMT',
|
||||
'Last-Modified' => gmdate('D, d M Y H:i:s', filemtime($savePath)) . ' GMT',
|
||||
'ETag' => md5_file($savePath)
|
||||
]);
|
||||
}
|
||||
|
||||
// 文件不存在处理
|
||||
$sourcePath = public_path($file);
|
||||
if (!file_exists($sourcePath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 判断删除多余文件
|
||||
$saveDir = dirname($savePath);
|
||||
if (is_dir($saveDir)) {
|
||||
$items = glob($saveDir . '/*');
|
||||
if (count($items) > 5) {
|
||||
usort($items, function ($a, $b) {
|
||||
return filemtime($b) - filemtime($a);
|
||||
});
|
||||
$itemsToDelete = array_slice($items, 5);
|
||||
foreach ($itemsToDelete as $item) {
|
||||
if (is_file($item)) {
|
||||
unlink($item);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Base::makeDir($saveDir);
|
||||
}
|
||||
|
||||
// 处理图片
|
||||
try {
|
||||
$handle = 0;
|
||||
$image = new Image($sourcePath);
|
||||
foreach ($rules as $rule) {
|
||||
if (!str_contains($rule, ':')) {
|
||||
continue;
|
||||
}
|
||||
[$type, $value] = explode(':', $rule);
|
||||
if (!in_array($type, ['ratio', 'size', 'percentage', 'cover', 'contain'])) {
|
||||
continue;
|
||||
}
|
||||
switch ($type) {
|
||||
// 按比例裁剪
|
||||
case 'ratio':
|
||||
if (is_numeric($value)) {
|
||||
$image->ratioCrop($value);
|
||||
$handle++;
|
||||
}
|
||||
break;
|
||||
|
||||
// 按尺寸缩放
|
||||
case 'size':
|
||||
$size = Base::newIntval(explode('x', $value));
|
||||
if (count($size) === 2) {
|
||||
$image->resize($size[0], $size[1]);
|
||||
$handle++;
|
||||
}
|
||||
break;
|
||||
|
||||
// 按尺寸缩放
|
||||
case 'percentage':
|
||||
case 'cover':
|
||||
case 'contain':
|
||||
$size = Base::newIntval(explode('x', $value));
|
||||
if (count($size) === 2) {
|
||||
$image->thumb($size[0], $size[1], $type);
|
||||
$handle++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($handle > 0) {
|
||||
$image->saveTo($savePath);
|
||||
Image::compressImage($savePath, 80);
|
||||
return response()->file($savePath, [
|
||||
'Pragma' => 'public',
|
||||
'Cache-Control' => 'max-age=1814400',
|
||||
'Expires' => gmdate('D, d M Y H:i:s', time() + 1814400) . ' GMT',
|
||||
'Last-Modified' => gmdate('D, d M Y H:i:s', filemtime($savePath)) . ' GMT',
|
||||
'ETag' => md5_file($savePath)
|
||||
]);
|
||||
} else {
|
||||
$image->destroy();
|
||||
}
|
||||
} catch (\ImagickException) { }
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
if (!function_exists('asset_main')) {
|
||||
function asset_main($path, $secure = null)
|
||||
{
|
||||
return preg_replace("/^https*:\/\//", "//", app('url')->asset($path, $secure));
|
||||
return preg_replace("/^https?:\/\//", "//", app('url')->asset($path, $secure));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ use Madzipper;
|
||||
use Carbon\Carbon;
|
||||
use App\Models\User;
|
||||
use App\Module\Base;
|
||||
use App\Module\Doo;
|
||||
use App\Module\Timer;
|
||||
use App\Module\Ihttp;
|
||||
use App\Tasks\PushTask;
|
||||
use App\Module\BillExport;
|
||||
@@ -37,8 +39,9 @@ class ApproveController extends AbstractController
|
||||
/**
|
||||
* @api {get} api/approve/verifyToken 01. 验证APi登录
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup users
|
||||
* @apiGroup approve
|
||||
* @apiName verifyToken
|
||||
*
|
||||
* @apiSuccess {String} version
|
||||
@@ -262,7 +265,7 @@ class ApproveController extends AbstractController
|
||||
$this->approveMsg('approve_reviewer', $dialog, $botUser, $val, $process, $pass);
|
||||
}
|
||||
// 发起人
|
||||
if ($process['is_finished'] == true) {
|
||||
if ($process['is_finished']) {
|
||||
$dialog = WebSocketDialog::checkUserDialog($botUser, $process['start_user_id']);
|
||||
if (!empty($dialog)) {
|
||||
$this->approveMsg('approve_submitter', $dialog, $botUser, ['userid' => $data['userid']], $process, $pass);
|
||||
@@ -284,7 +287,7 @@ class ApproveController extends AbstractController
|
||||
}
|
||||
|
||||
// 抄送人
|
||||
$notifier = $this->handleProcessNode($process, $task['step']);
|
||||
$notifier = $this->handleProcessNode($process);
|
||||
if ($notifier && $pass == 'pass') {
|
||||
foreach ($notifier as $val) {
|
||||
$dialog = WebSocketDialog::checkUserDialog($botUser, $val['target_id']);
|
||||
@@ -756,7 +759,7 @@ class ApproveController extends AbstractController
|
||||
if (empty($name) || empty($date)) {
|
||||
return Base::retError('参数错误');
|
||||
}
|
||||
if (!(is_array($date) && Base::isDate($date[0]) && Base::isDate($date[1]))) {
|
||||
if (!(is_array($date) && Timer::isDate($date[0]) && Timer::isDate($date[1]))) {
|
||||
return Base::retError('日期选择错误');
|
||||
}
|
||||
if (Carbon::parse($date[1])->timestamp - Carbon::parse($date[0])->timestamp > 35 * 86400) {
|
||||
@@ -772,32 +775,31 @@ class ApproveController extends AbstractController
|
||||
$res = Base::arrayKeyToUnderline($process['data']);
|
||||
//
|
||||
$headings = [];
|
||||
$headings[] = '申请编号';
|
||||
$headings[] = '标题';
|
||||
$headings[] = '申请状态';
|
||||
$headings[] = '发起时间';
|
||||
$headings[] = '完成时间';
|
||||
$headings[] = '发起人工号';
|
||||
$headings[] = '发起人User ID';
|
||||
$headings[] = '发起人姓名';
|
||||
$headings[] = '发起人部门';
|
||||
$headings[] = '发起人部门ID';
|
||||
$headings[] = '部门负责人';
|
||||
$headings[] = '历史审批人';
|
||||
$headings[] = '历史办理人';
|
||||
$headings[] = '审批记录';
|
||||
$headings[] = '当前处理人';
|
||||
$headings[] = '审批节点';
|
||||
$headings[] = '审批人数';
|
||||
$headings[] = '审批耗时';
|
||||
$headings[] = '假期类型';
|
||||
$headings[] = '开始时间';
|
||||
$headings[] = '结束时间';
|
||||
$headings[] = '时长';
|
||||
$headings[] = '请假事由';
|
||||
$headings[] = '请假单位';
|
||||
$headings[] = Doo::translate('申请编号');
|
||||
$headings[] = Doo::translate('标题');
|
||||
$headings[] = Doo::translate('申请状态');
|
||||
$headings[] = Doo::translate('发起时间');
|
||||
$headings[] = Doo::translate('完成时间');
|
||||
$headings[] = Doo::translate('发起人工号');
|
||||
$headings[] = Doo::translate('发起人User ID');
|
||||
$headings[] = Doo::translate('发起人姓名');
|
||||
$headings[] = Doo::translate('发起人部门');
|
||||
$headings[] = Doo::translate('发起人部门ID');
|
||||
$headings[] = Doo::translate('部门负责人');
|
||||
$headings[] = Doo::translate('历史审批人');
|
||||
$headings[] = Doo::translate('历史办理人');
|
||||
$headings[] = Doo::translate('审批记录');
|
||||
$headings[] = Doo::translate('当前处理人');
|
||||
$headings[] = Doo::translate('审批节点');
|
||||
$headings[] = Doo::translate('审批人数');
|
||||
$headings[] = Doo::translate('审批耗时');
|
||||
$headings[] = Doo::translate('假期类型');
|
||||
$headings[] = Doo::translate('开始时间');
|
||||
$headings[] = Doo::translate('结束时间');
|
||||
$headings[] = Doo::translate('时长');
|
||||
$headings[] = Doo::translate('请假事由');
|
||||
$headings[] = Doo::translate('请假单位');
|
||||
//
|
||||
$sheets = [];
|
||||
$datas = [];
|
||||
foreach ($res as $val) {
|
||||
//
|
||||
@@ -816,12 +818,12 @@ class ApproveController extends AbstractController
|
||||
// 计算审批耗时
|
||||
$startTime = Carbon::parse($val['start_time'])->timestamp;
|
||||
$endTime = $val['end_time'] ? Carbon::parse($val['end_time'])->timestamp : time();
|
||||
$approval_time = Base::timeDiff($startTime, $endTime); // 审批耗时
|
||||
$approval_time = Doo::translate(Timer::timeDiff($startTime, $endTime)); // 审批耗时
|
||||
// 计算时长
|
||||
$varStartTime = Carbon::parse($val['var']['start_time']);
|
||||
$varEndTime = Carbon::parse($val['var']['end_time']);
|
||||
$duration = $varEndTime->floatDiffInHours($varStartTime);
|
||||
$duration_unit = '小时'; // 时长单位
|
||||
$duration_unit = Doo::translate('小时'); // 时长单位
|
||||
$datas[] = [
|
||||
$val['id'], // 申请编号
|
||||
$val['proc_def_name'], // 标题
|
||||
@@ -853,13 +855,13 @@ class ApproveController extends AbstractController
|
||||
return Base::retError('没有任何数据');
|
||||
}
|
||||
//
|
||||
$title = "Sheet1";
|
||||
$title = Doo::translate("审批记录");
|
||||
$sheets = [
|
||||
BillExport::create()->setTitle($title)->setHeadings($headings)->setData($datas)->setStyles(["A1:Y1" => ["font" => ["bold" => true]]])
|
||||
];
|
||||
//
|
||||
$fileName = '审批记录_' . Base::time() . '.xlsx';
|
||||
$filePath = "temp/approve/export/" . date("Ym", Base::time());
|
||||
$fileName = $title . '_' . Timer::time() . '.xlsx';
|
||||
$filePath = "temp/approve/export/" . date("Ym", Timer::time());
|
||||
$export = new BillMultipleExport($sheets);
|
||||
$res = $export->store($filePath . "/" . $fileName);
|
||||
if ($res != 1) {
|
||||
@@ -899,14 +901,14 @@ class ApproveController extends AbstractController
|
||||
3 => '拒绝',
|
||||
4 => '撤回'
|
||||
);
|
||||
return isset($state_map[$state]) ? $state_map[$state] : '';
|
||||
return $state_map[$state] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/approve/down 19. 下载导出的审批数据
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
* @apiGroup approve
|
||||
* @apiName down
|
||||
*
|
||||
* @apiParam {String} key 通过export接口得到的下载钥匙
|
||||
@@ -966,7 +968,6 @@ class ApproveController extends AbstractController
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
// 审批机器人消息
|
||||
public function approveMsg($type, $dialog, $botUser, $toUser, $process, $action = null)
|
||||
{
|
||||
@@ -978,51 +979,71 @@ class ApproveController extends AbstractController
|
||||
'department' => $process['department'],
|
||||
'type' => $process['var']['type'],
|
||||
'start_time' => $process['var']['start_time'],
|
||||
'start_day_of_week' => '周' . Base::getTimeWeek(Carbon::parse($process['var']['start_time'])->timestamp),
|
||||
'start_day_of_week' => '周' . Timer::getWeek(Carbon::parse($process['var']['start_time'])->timestamp),
|
||||
'end_time' => $process['var']['end_time'],
|
||||
'end_day_of_week' => '周' . Base::getTimeWeek(Carbon::parse($process['var']['end_time'])->timestamp),
|
||||
'end_day_of_week' => '周' . Timer::getWeek(Carbon::parse($process['var']['end_time'])->timestamp),
|
||||
'description' => $process['var']['description'],
|
||||
'comment_nickname' => $process['comment_user_id'] ? User::userid2nickname($process['comment_user_id']) : '',
|
||||
'comment_content' => $process['comment_contents']['content'] ?? '',
|
||||
'comment_pictures' => $process['comment_contents']['pictures'] ?? []
|
||||
];
|
||||
$text = view('push.bot', ['type' => $type, 'action' => $action, 'is_finished' => $process['is_finished'], 'data' => (object)$data])->render();
|
||||
$text = preg_replace("/^\x20+/", "", $text);
|
||||
$text = preg_replace("/\n\x20+/", "\n", $text);
|
||||
$msg_action = null;
|
||||
$thumb = null;
|
||||
if ($type === 'approve_reviewer') {
|
||||
$thumb = $process['var']['other'];
|
||||
} elseif ($type === 'approve_comment_notifier') {
|
||||
$thumb = $data['comment_pictures'] ? $data['comment_pictures'][0] : null;
|
||||
}
|
||||
if ($thumb && file_exists(public_path($thumb))) {
|
||||
$imageSize = getimagesize(public_path($thumb));
|
||||
$data['thumb'] = [
|
||||
'url' => $thumb,
|
||||
'width' => $imageSize[0],
|
||||
'height' => $imageSize[1]
|
||||
];
|
||||
}
|
||||
$msgAction = null;
|
||||
$msgData = [
|
||||
'type' => $type,
|
||||
'action' => $action,
|
||||
'is_finished' => $process['is_finished'],
|
||||
'data' => $data
|
||||
];
|
||||
$msgData['title'] = match ($type) {
|
||||
'approve_reviewer' => $data['nickname'] . " 提交的「{$data['proc_def_name']}」待你审批",
|
||||
'approve_notifier' => "抄送 {$data['nickname']} 提交的「{$data['proc_def_name']}」记录",
|
||||
'approve_comment_notifier' => $data['comment_nickname'] . " 评论了 {$data['nickname']} 的「{$data['proc_def_name']}」审批",
|
||||
'approve_submitter' => $action == 'pass' ? "您发起的「{$data['proc_def_name']}」已通过" : "您发起的「{$data['proc_def_name']}」被 {$data['nickname']} 拒绝",
|
||||
default => '不支持的指令',
|
||||
};
|
||||
if ($action == 'withdraw' || $action == 'pass' || $action == 'refuse') {
|
||||
// 任务完成,给发起人发送消息
|
||||
if ($type == 'approve_submitter' && $action != 'withdraw') {
|
||||
return WebSocketDialogMsg::sendMsg($msg_action, $dialog->id, 'text', ['text' => $text, 'approve_type' => $type], $botUser->userid, false, false, true);
|
||||
return WebSocketDialogMsg::sendMsg($msgAction, $dialog->id, 'template', $msgData, $botUser->userid, false, false, true);
|
||||
}
|
||||
// 查找最后一条消息msg_id
|
||||
$msg_action = 'change-' . $toUser['msg_id'];
|
||||
$msgAction = 'change-' . $toUser['msg_id'];
|
||||
}
|
||||
//
|
||||
try {
|
||||
$msg = WebSocketDialogMsg::sendMsg($msg_action, $dialog->id, 'text', ['text' => $text, 'approve_type' => $type], $process['start_user_id'], false, false, true);
|
||||
// 关联信息
|
||||
if ($action == 'start') {
|
||||
$proc_msg = new ApproveProcMsg();
|
||||
$proc_msg->proc_inst_id = $process['id'];
|
||||
$proc_msg->msg_id = $msg['data']->id;
|
||||
$proc_msg->userid = $toUser['userid'];
|
||||
$proc_msg->save();
|
||||
}
|
||||
// 更新工作报告 未读数量
|
||||
if ($type == 'approve_reviewer' && $toUser['userid']) {
|
||||
$params = [
|
||||
'userid' => [$toUser['userid'], User::auth()->userid()],
|
||||
'msg' => [
|
||||
'type' => 'approve',
|
||||
'action' => 'unread',
|
||||
'userid' => $toUser['userid'],
|
||||
]
|
||||
];
|
||||
Task::deliver(new PushTask($params, false));
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
//throw $th;
|
||||
$msg = WebSocketDialogMsg::sendMsg($msgAction, $dialog->id, 'template', $msgData, $process['start_user_id'], false, false, true);
|
||||
// 关联信息
|
||||
if ($action == 'start') {
|
||||
$proc_msg = new ApproveProcMsg();
|
||||
$proc_msg->proc_inst_id = $process['id'];
|
||||
$proc_msg->msg_id = $msg['data']->id;
|
||||
$proc_msg->userid = $toUser['userid'];
|
||||
$proc_msg->save();
|
||||
}
|
||||
// 更新审批 未读数量
|
||||
if ($type == 'approve_reviewer' && $toUser['userid']) {
|
||||
$params = [
|
||||
'userid' => [$toUser['userid'], User::userid()],
|
||||
'msg' => [
|
||||
'type' => 'approve',
|
||||
'action' => 'unread',
|
||||
'userid' => $toUser['userid'],
|
||||
]
|
||||
];
|
||||
Task::deliver(new PushTask($params, false));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -1057,8 +1078,9 @@ class ApproveController extends AbstractController
|
||||
}
|
||||
}
|
||||
// 全局评论
|
||||
unset($res['global_comment']);
|
||||
if (isset($res['global_comments'])) {
|
||||
foreach ($res['global_comments'] as $k => &$globalComment) {
|
||||
foreach ($res['global_comments'] as $k => $globalComment) {
|
||||
$info = User::whereUserid($globalComment['user_id'])->first();
|
||||
if (!$info) {
|
||||
continue;
|
||||
@@ -1066,6 +1088,8 @@ class ApproveController extends AbstractController
|
||||
$res['global_comments'][$k]['userimg'] = User::getAvatar($info->userid, $info->userimg, $info->email, $info->nickname);
|
||||
$res['global_comments'][$k]['nickname'] = $info->nickname;
|
||||
}
|
||||
} else {
|
||||
$res['global_comments'] = [];
|
||||
}
|
||||
$info = User::whereUserid($res['start_user_id'])->first();
|
||||
$res['userimg'] = $info ? User::getAvatar($info->userid, $info->userimg, $info->email, $info->nickname) : '';
|
||||
@@ -1111,7 +1135,7 @@ class ApproveController extends AbstractController
|
||||
* @api {get} api/approve/user/status 20. 获取用户审批状态
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
* @apiGroup approve
|
||||
* @apiName user__status
|
||||
*
|
||||
* @apiParam {String} userid
|
||||
@@ -1126,7 +1150,7 @@ class ApproveController extends AbstractController
|
||||
$ret = Ihttp::ihttp_get($this->flow_url . '/api/v1/workflow/process/getUserApprovalStatus?' . http_build_query($data));
|
||||
$procdef = json_decode($ret['ret'] == 1 ? $ret['data'] : '{}', true);
|
||||
if (isset($procdef['status']) && $procdef['status'] == 200) {
|
||||
return Base::retSuccess('success', isset($procdef['data']["proc_def_name"]) ? $procdef['data']["proc_def_name"] : '');
|
||||
return Base::retSuccess('success', $procdef['data']["proc_def_name"] ?? '');
|
||||
}
|
||||
return Base::retSuccess('success', '');
|
||||
}
|
||||
|
||||
@@ -113,8 +113,11 @@ class ComplaintController extends AbstractController
|
||||
->each(function ($adminUser) use ($reason, $botUser) {
|
||||
$dialog = WebSocketDialog::checkUserDialog($botUser, $adminUser->userid);
|
||||
if ($dialog) {
|
||||
$text = "<p>收到新的举报信息:{$reason} (请前往应用查看详情)</p>";
|
||||
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'text', ['text' => $text], $botUser->userid); // todo 未能在任务end事件来发送任务
|
||||
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'template', [
|
||||
'type' => 'content',
|
||||
'title' => '收到新的举报信息',
|
||||
'content' => "收到新的举报信息:{$reason} (请前往应用查看详情)"
|
||||
], $botUser->userid);
|
||||
}
|
||||
});
|
||||
//
|
||||
|
||||
@@ -2,15 +2,16 @@
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Tasks\PushTask;
|
||||
use DB;
|
||||
use Hhxsv5\LaravelS\Swoole\Task\Task;
|
||||
use Request;
|
||||
use Redirect;
|
||||
use Carbon\Carbon;
|
||||
use App\Tasks\PushTask;
|
||||
use App\Module\Doo;
|
||||
use App\Models\File;
|
||||
use App\Models\User;
|
||||
use App\Module\Base;
|
||||
use App\Module\Timer;
|
||||
use App\Module\Extranet;
|
||||
use App\Module\TimeRange;
|
||||
use App\Models\FileContent;
|
||||
@@ -20,6 +21,8 @@ use App\Models\WebSocketDialogMsg;
|
||||
use App\Models\WebSocketDialogUser;
|
||||
use App\Models\WebSocketDialogMsgRead;
|
||||
use App\Models\WebSocketDialogMsgTodo;
|
||||
use App\Models\WebSocketDialogMsgTranslate;
|
||||
use Hhxsv5\LaravelS\Swoole\Task\Task;
|
||||
|
||||
/**
|
||||
* @apiDefine dialog
|
||||
@@ -82,13 +85,7 @@ class DialogController extends AbstractController
|
||||
$unreadAt = Request::input('unread_at');
|
||||
$todoAt = Request::input('todo_at');
|
||||
//
|
||||
$unreadAt = Base::isNumber($unreadAt) ? intval($unreadAt) : trim($unreadAt);
|
||||
$unreadAt = Carbon::parse($unreadAt)->setTimezone(config('app.timezone'));
|
||||
//
|
||||
$todoAt = Base::isNumber($todoAt) ? intval($todoAt) : trim($todoAt);
|
||||
$todoAt = Carbon::parse($todoAt)->setTimezone(config('app.timezone'));
|
||||
//
|
||||
$data = WebSocketDialog::getDialogBeyond($user->userid, $unreadAt, $todoAt);
|
||||
$data = WebSocketDialog::getDialogBeyond($user->userid, Base::newCarbon($unreadAt), Base::newCarbon($todoAt));
|
||||
//
|
||||
return Base::retSuccess('success', $data);
|
||||
}
|
||||
@@ -116,18 +113,20 @@ class DialogController extends AbstractController
|
||||
return Base::retError('请输入搜索关键词');
|
||||
}
|
||||
// 搜索会话
|
||||
$dialogs = WebSocketDialog::select(['web_socket_dialogs.*', 'u.top_at', 'u.last_at', 'u.mark_unread', 'u.silence', 'u.hide', 'u.color', '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', "%{$key}%")
|
||||
$list = DB::table('web_socket_dialog_users as u')
|
||||
->select(['d.*', 'u.top_at', 'u.last_at', 'u.mark_unread', 'u.silence', 'u.hide', 'u.color', 'u.updated_at as user_at'])
|
||||
->join('web_socket_dialogs as d', 'u.dialog_id', '=', 'd.id')
|
||||
->where('u.userid', $user->userid)
|
||||
->where('d.name', 'LIKE', "%{$key}%")
|
||||
->whereNull('d.deleted_at')
|
||||
->orderByDesc('u.top_at')
|
||||
->orderByDesc('u.last_at')
|
||||
->take(20)
|
||||
->get();
|
||||
$dialogs->transform(function (WebSocketDialog $item) use ($user) {
|
||||
return $item->formatData($user->userid);
|
||||
});
|
||||
$list = $dialogs->toArray();
|
||||
->get()
|
||||
->map(function($item) use ($user) {
|
||||
return WebSocketDialog::synthesizeData($item, $user->userid);
|
||||
})
|
||||
->all();
|
||||
// 搜索联系人
|
||||
if (count($list) < 20 && Base::judgeClientVersion("0.21.60")) {
|
||||
$users = User::select(User::$basicField)
|
||||
@@ -140,31 +139,53 @@ class DialogController extends AbstractController
|
||||
})->orderBy('userid')
|
||||
->take(20 - count($list))
|
||||
->get();
|
||||
$users->transform(function (User $item) {
|
||||
$users->transform(function (User $item) use ($user) {
|
||||
$id = 'u:' . $item->userid;
|
||||
$lastAt = null;
|
||||
$lastMsg = null;
|
||||
$dialog = WebSocketDialog::getUserDialog($user->userid, $item->userid, now()->addDay());
|
||||
if ($dialog) {
|
||||
$id = $dialog->id;
|
||||
$row = WebSocketDialogMsg::whereDialogId($dialog->id)->orderByDesc('id')->first();
|
||||
if ($row) {
|
||||
$lastAt = Carbon::parse($row->created_at)->toDateTimeString();
|
||||
$lastMsg = WebSocketDialog::lastMsgFormat($row->toArray());
|
||||
}
|
||||
}
|
||||
return [
|
||||
'id' => 'u:' . $item->userid,
|
||||
'id' => $id,
|
||||
'type' => 'user',
|
||||
'name' => $item->nickname,
|
||||
'dialog_user' => $item,
|
||||
'last_msg' => null,
|
||||
'last_at' => $lastAt,
|
||||
'last_msg' => $lastMsg,
|
||||
];
|
||||
});
|
||||
$list = array_merge($list, $users->toArray());
|
||||
}
|
||||
// 搜索消息会话
|
||||
if (count($list) < 20) {
|
||||
$msgs = WebSocketDialog::select(['web_socket_dialogs.*', 'u.top_at', 'u.last_at', 'u.mark_unread', 'u.silence', 'u.hide', 'u.color', 'u.updated_at as user_at', 'm.id as search_msg_id'])
|
||||
->join('web_socket_dialog_users as u', 'web_socket_dialogs.id', '=', 'u.dialog_id')
|
||||
->join('web_socket_dialog_msgs as m', 'web_socket_dialogs.id', '=', 'm.dialog_id')
|
||||
$prefix = DB::getTablePrefix();
|
||||
if (preg_match('/[+\-><()~*"@]/', $key)) {
|
||||
$against = "\"{$key}\"";
|
||||
} else {
|
||||
$against = "*{$key}*";
|
||||
}
|
||||
$msgs = DB::table('web_socket_dialog_users as u')
|
||||
->select(['d.*', 'u.top_at', 'u.last_at', 'u.mark_unread', 'u.silence', 'u.hide', 'u.color', 'u.updated_at as user_at', 'm.id as search_msg_id'])
|
||||
->join('web_socket_dialogs as d', 'u.dialog_id', '=', 'd.id')
|
||||
->join('web_socket_dialog_msgs as m', 'm.dialog_id', '=', 'd.id')
|
||||
->where('u.userid', $user->userid)
|
||||
->where('m.key', 'LIKE', "%{$key}%")
|
||||
->whereNull('d.deleted_at')
|
||||
->whereRaw("MATCH({$prefix}m.key) AGAINST('{$against}' IN BOOLEAN MODE)")
|
||||
->orderByDesc('m.id')
|
||||
->take(20 - count($list))
|
||||
->get();
|
||||
$msgs->transform(function (WebSocketDialog $item) use ($user) {
|
||||
return $item->formatData($user->userid);
|
||||
});
|
||||
$list = array_merge($list, $msgs->toArray());
|
||||
->get()
|
||||
->map(function($item) use ($user) {
|
||||
return WebSocketDialog::synthesizeData($item, $user->userid);
|
||||
})
|
||||
->all();
|
||||
$list = array_merge($list, $msgs);
|
||||
}
|
||||
//
|
||||
return Base::retSuccess('success', $list);
|
||||
@@ -186,19 +207,22 @@ class DialogController extends AbstractController
|
||||
{
|
||||
$user = User::auth();
|
||||
// 搜索会话
|
||||
$msgs = WebSocketDialog::select(['web_socket_dialogs.*', 'u.top_at', 'u.last_at', 'u.mark_unread', 'u.silence', 'u.hide', 'u.color', 'u.updated_at as user_at', 'm.id as search_msg_id'])
|
||||
->join('web_socket_dialog_users as u', 'web_socket_dialogs.id', '=', 'u.dialog_id')
|
||||
->join('web_socket_dialog_msgs as m', 'web_socket_dialogs.id', '=', 'm.dialog_id')
|
||||
$msgs = DB::table('web_socket_dialog_users as u')
|
||||
->select(['d.*', 'u.top_at', 'u.last_at', 'u.mark_unread', 'u.silence', 'u.hide', 'u.color', 'u.updated_at as user_at', 'm.id as search_msg_id'])
|
||||
->join('web_socket_dialogs as d', 'u.dialog_id', '=', 'd.id')
|
||||
->join('web_socket_dialog_msgs as m', 'm.dialog_id', '=', 'd.id')
|
||||
->where('u.userid', $user->userid)
|
||||
->whereNull('d.deleted_at')
|
||||
->where('m.tag', '>', 0)
|
||||
->orderByDesc('m.id')
|
||||
->take(50)
|
||||
->get();
|
||||
$msgs->transform(function (WebSocketDialog $item) use ($user) {
|
||||
return $item->formatData($user->userid);
|
||||
});
|
||||
->get()
|
||||
->map(function($item) use ($user) {
|
||||
return WebSocketDialog::synthesizeData($item, $user->userid);
|
||||
})
|
||||
->all();
|
||||
//
|
||||
return Base::retSuccess('success', $msgs->toArray());
|
||||
return Base::retSuccess('success', $msgs);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -221,16 +245,14 @@ class DialogController extends AbstractController
|
||||
//
|
||||
$dialog_id = intval(Request::input('dialog_id'));
|
||||
//
|
||||
$item = WebSocketDialog::select(['web_socket_dialogs.*', 'u.top_at', 'u.last_at', 'u.mark_unread', 'u.silence', 'u.hide', 'u.color', 'u.updated_at as user_at'])
|
||||
->join('web_socket_dialog_users as u', 'web_socket_dialogs.id', '=', 'u.dialog_id')
|
||||
->where('web_socket_dialogs.id', $dialog_id)
|
||||
$item = DB::table('web_socket_dialog_users as u')
|
||||
->select(['d.*', 'u.top_at', 'u.last_at', 'u.mark_unread', 'u.silence', 'u.hide', 'u.color', 'u.updated_at as user_at'])
|
||||
->join('web_socket_dialogs as d', 'u.dialog_id', '=', 'd.id')
|
||||
->where('u.userid', $user->userid)
|
||||
->where('d.id', $dialog_id)
|
||||
->whereNull('d.deleted_at')
|
||||
->first();
|
||||
if (empty($item)) {
|
||||
WebSocketDialogMsgRead::forceRead($dialog_id, $user->userid);
|
||||
return Base::retError('会话不存在或已被删除', ['dialog_id' => $dialog_id], -4003);
|
||||
}
|
||||
return Base::retSuccess('success', $item->formatData($user->userid));
|
||||
return Base::retSuccess('success', WebSocketDialog::synthesizeData($item, $user->userid));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -258,21 +280,15 @@ class DialogController extends AbstractController
|
||||
$dialog = WebSocketDialog::checkDialog($dialog_id);
|
||||
//
|
||||
if ($getuser === 1) {
|
||||
$data = $dialog->dialogUser->toArray();
|
||||
$array = [];
|
||||
foreach ($data as $item) {
|
||||
$res = User::userid2basic($item['userid']);
|
||||
if ($res) {
|
||||
$array[] = array_merge($item, $res->toArray());
|
||||
}
|
||||
}
|
||||
$array = array_filter($array, function ($item) {
|
||||
$data = $dialog->dialogUserBuilder()->get();
|
||||
$array = array_filter($data->toArray(), function ($item) {
|
||||
return $item['userid'] > 0;
|
||||
});
|
||||
} else {
|
||||
$data = WebSocketDialogUser::select(['web_socket_dialog_users.*', 'users.bot'])
|
||||
->join('users', 'web_socket_dialog_users.userid', '=', 'users.userid')
|
||||
->where('web_socket_dialog_users.dialog_id', $dialog_id)
|
||||
->whereNull('users.disable_at')
|
||||
->orderBy('web_socket_dialog_users.id')
|
||||
->get();
|
||||
$array = $data->toArray();
|
||||
@@ -399,12 +415,11 @@ class DialogController extends AbstractController
|
||||
if ($dialog->type !== 'user') {
|
||||
return Base::retError("会话类型错误");
|
||||
}
|
||||
$dialogUser = $dialog->dialogUser->where('userid', '!=', $user->userid)->first();
|
||||
$dialogUser = $dialog->dialogUserBuilder(['tel'])->where('users.userid', '!=', $user->userid)->first();
|
||||
if (empty($dialogUser)) {
|
||||
return Base::retError("会话对象不存在");
|
||||
}
|
||||
$callUser = User::find($dialogUser->userid);
|
||||
if (empty($callUser) || empty($callUser->tel)) {
|
||||
if (empty($dialogUser->tel)) {
|
||||
return Base::retError("对方未设置联系电话");
|
||||
}
|
||||
if ($user->isTemp()) {
|
||||
@@ -413,14 +428,14 @@ class DialogController extends AbstractController
|
||||
//
|
||||
$add = null;
|
||||
$res = WebSocketDialogMsg::sendMsg(null, $dialog->id, 'notice', [
|
||||
'notice' => $user->nickname . " 查看了 " . $callUser->nickname . " 的联系电话"
|
||||
'notice' => $user->nickname . " 查看了 " . $dialogUser->nickname . " 的联系电话"
|
||||
]);
|
||||
if (Base::isSuccess($res)) {
|
||||
$add = $res['data'];
|
||||
}
|
||||
//
|
||||
return Base::retSuccess("success", [
|
||||
'tel' => $callUser->tel,
|
||||
'tel' => $dialogUser->tel,
|
||||
'add' => $add ?: null
|
||||
]);
|
||||
}
|
||||
@@ -452,7 +467,7 @@ class DialogController extends AbstractController
|
||||
if (empty($dialog)) {
|
||||
return Base::retError('打开会话失败');
|
||||
}
|
||||
$data = WebSocketDialog::find($dialog->id)?->formatData($user->userid);
|
||||
$data = WebSocketDialog::synthesizeData($dialog->id, $user->userid);
|
||||
return Base::retSuccess('success', $data);
|
||||
}
|
||||
|
||||
@@ -570,7 +585,7 @@ class DialogController extends AbstractController
|
||||
->value('id'));
|
||||
}
|
||||
$data['list'] = $list;
|
||||
$data['time'] = Base::time();
|
||||
$data['time'] = Timer::time();
|
||||
// 记录当前打开的任务对话
|
||||
if ($dialog->type == 'group' && $dialog->group_type == 'task') {
|
||||
$user->task_dialog_id = $dialog->id;
|
||||
@@ -584,8 +599,8 @@ class DialogController extends AbstractController
|
||||
}
|
||||
//
|
||||
if ($reDialog) {
|
||||
$data['dialog'] = $dialog->formatData($user->userid, true);
|
||||
$data['todo'] = $data['dialog']->todo_num > 0 ? WebSocketDialogMsgTodo::whereDialogId($dialog->id)->whereUserid($user->userid)->whereDoneAt(null)->orderByDesc('id')->take(50)->get() : [];
|
||||
$data['dialog'] = WebSocketDialog::synthesizeData($dialog, $user->userid, true);
|
||||
$data['todo'] = $data['dialog']['todo_num'] > 0 ? WebSocketDialogMsgTodo::whereDialogId($dialog->id)->whereUserid($user->userid)->whereDoneAt(null)->orderByDesc('id')->take(50)->get() : [];
|
||||
$data['top'] = $dialog->top_msg_id ? WebSocketDialogMsg::whereId($dialog->top_msg_id)->first() : null;
|
||||
}
|
||||
return Base::retSuccess('success', $data);
|
||||
@@ -764,7 +779,7 @@ class DialogController extends AbstractController
|
||||
*
|
||||
* @apiParam {Object} id 消息ID(组)
|
||||
* - 1、多个ID用逗号分隔,如:1,2,3
|
||||
* - 2、另一种格式:{"id": "[会话ID]"},如:{"2": 0, "3": 10}
|
||||
* - 2、另一种格式:{"id": "会话ID|0"},如:{"2": 0, "3": 10}
|
||||
* -- 会话ID:标记id之后的消息已读
|
||||
* -- 其他:标记已读
|
||||
*
|
||||
@@ -811,13 +826,13 @@ class DialogController extends AbstractController
|
||||
$dialogUser->updated_at = Carbon::now();
|
||||
$dialogUser->save();
|
||||
//
|
||||
$dialogUser->webSocketDialog->generateUnread($user->userid);
|
||||
$unreadData = WebSocketDialog::generateUnread($dialogUser->dialog_id, $user->userid);
|
||||
$data[] = [
|
||||
'id' => $dialogUser->webSocketDialog->id,
|
||||
'unread' => $dialogUser->webSocketDialog->unread,
|
||||
'unread_one' => $dialogUser->webSocketDialog->unread_one,
|
||||
'mention' => $dialogUser->webSocketDialog->mention,
|
||||
'mention_ids' => $dialogUser->webSocketDialog->mention_ids,
|
||||
'id' => $dialogUser->dialog_id,
|
||||
'unread' => $unreadData['unread'],
|
||||
'unread_one' => $unreadData['unread_one'],
|
||||
'mention' => $unreadData['mention'],
|
||||
'mention_ids' => $unreadData['mention_ids'],
|
||||
'user_at' => Carbon::parse($dialogUser->updated_at)->toDateTimeString('millisecond'),
|
||||
'user_ms' => Carbon::parse($dialogUser->updated_at)->valueOf()
|
||||
];
|
||||
@@ -855,14 +870,14 @@ class DialogController extends AbstractController
|
||||
if (empty($dialogUser?->webSocketDialog)) {
|
||||
return Base::retError('会话不存在');
|
||||
}
|
||||
$dialogUser->webSocketDialog->generateUnread($dialogUser->userid);
|
||||
$unreadData = WebSocketDialog::generateUnread($dialog_id, $dialogUser->userid);
|
||||
//
|
||||
return Base::retSuccess('success', [
|
||||
'id' => $dialogUser->webSocketDialog->id,
|
||||
'unread' => $dialogUser->webSocketDialog->unread,
|
||||
'unread_one' => $dialogUser->webSocketDialog->unread_one,
|
||||
'mention' => $dialogUser->webSocketDialog->mention,
|
||||
'mention_ids' => $dialogUser->webSocketDialog->mention_ids,
|
||||
'id' => $dialog_id,
|
||||
'unread' => $unreadData['unread'],
|
||||
'unread_one' => $unreadData['unread_one'],
|
||||
'mention' => $unreadData['mention'],
|
||||
'mention_ids' => $unreadData['mention_ids'],
|
||||
'user_at' => Carbon::parse($dialogUser->updated_at)->toDateTimeString('millisecond'),
|
||||
'user_ms' => Carbon::parse($dialogUser->updated_at)->valueOf()
|
||||
]);
|
||||
@@ -983,6 +998,7 @@ class DialogController extends AbstractController
|
||||
*
|
||||
* @apiParam {Number} dialog_id 对话ID
|
||||
* @apiParam {String} text 消息内容
|
||||
* @apiParam {String} [key] 搜索关键词 (不设置根据内容自动生成)
|
||||
* @apiParam {String} [text_type] 消息类型
|
||||
* - html: HTML(默认)
|
||||
* - md: MARKDOWN
|
||||
@@ -1010,6 +1026,7 @@ class DialogController extends AbstractController
|
||||
$update_mark = !($user->bot && in_array(strtolower(trim(Request::input('update_mark'))), ['no', 'false', '0']));
|
||||
$reply_id = intval(Request::input('reply_id'));
|
||||
$text = trim(Request::input('text'));
|
||||
$key = trim(Request::input('key'));
|
||||
$text_type = strtolower(trim(Request::input('text_type')));
|
||||
$silence = in_array(strtolower(trim(Request::input('silence'))), ['yes', 'true', '1']);
|
||||
$markdown = in_array($text_type, ['md', 'markdown']);
|
||||
@@ -1062,13 +1079,16 @@ class DialogController extends AbstractController
|
||||
'height' => -1,
|
||||
'ext' => $ext,
|
||||
];
|
||||
$result = WebSocketDialogMsg::sendMsg($action, $dialog_id, 'file', $fileData, $user->userid, false, false, $silence);
|
||||
if (empty($key)) {
|
||||
$key = mb_substr(strip_tags($text), 0, 200);
|
||||
}
|
||||
$result = WebSocketDialogMsg::sendMsg($action, $dialog_id, 'file', $fileData, $user->userid, false, false, $silence, $key);
|
||||
} else {
|
||||
$msgData = ['text' => $text];
|
||||
if ($markdown) {
|
||||
$msgData['type'] = 'md';
|
||||
}
|
||||
$result = WebSocketDialogMsg::sendMsg($action, $dialog_id, 'text', $msgData, $user->userid, false, false, $silence);
|
||||
$result = WebSocketDialogMsg::sendMsg($action, $dialog_id, 'text', $msgData, $user->userid, false, false, $silence, $key);
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
@@ -1330,11 +1350,74 @@ class DialogController extends AbstractController
|
||||
if (empty($dialog)) {
|
||||
return Base::retError('匿名机器人会话不存在');
|
||||
}
|
||||
return WebSocketDialogMsg::sendMsg(null, $dialog->id, 'text', ['text' => "<p>{$text}</p>"], $botUser->userid, true);
|
||||
return WebSocketDialogMsg::sendMsg(null, $dialog->id, 'template', [
|
||||
'type' => 'content',
|
||||
'content' => $text,
|
||||
], $botUser->userid, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/dialog/msg/readlist 27. 获取消息阅读情况
|
||||
* @api {post} api/dialog/msg/sendlocation 27. 发送位置消息
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup dialog
|
||||
* @apiName msg__sendlocation
|
||||
*
|
||||
* @apiParam {Number} dialog_id 对话ID
|
||||
* @apiParam {String} type 位置类型
|
||||
* - bd: 百度地图
|
||||
* @apiParam {Number} lng 经度
|
||||
* @apiParam {Number} lat 纬度
|
||||
* @apiParam {String} title 位置名称
|
||||
* @apiParam {Number} [distance] 距离(米)
|
||||
* @apiParam {String} [address] 位置地址
|
||||
* @apiParam {String} [thumb] 预览图片(url)
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function msg__sendlocation()
|
||||
{
|
||||
$user = User::auth();
|
||||
//
|
||||
$dialog_id = intval(Request::input('dialog_id'));
|
||||
$type = strtolower(trim(Request::input('type')));
|
||||
$lng = floatval(Request::input('lng'));
|
||||
$lat = floatval(Request::input('lat'));
|
||||
$title = trim(Request::input('title'));
|
||||
$distance = intval(Request::input('distance'));
|
||||
$address = trim(Request::input('address'));
|
||||
$thumb = trim(Request::input('thumb'));
|
||||
//
|
||||
if (empty($lng) || $lng < -180 || $lng > 180
|
||||
|| empty($lat) || $lat < -90 || $lat > 90) {
|
||||
return Base::retError('经纬度错误');
|
||||
}
|
||||
if (empty($title)) {
|
||||
return Base::retError('位置名称不能为空');
|
||||
}
|
||||
//
|
||||
WebSocketDialog::checkDialog($dialog_id);
|
||||
//
|
||||
if ($type == 'bd') {
|
||||
$msgData = [
|
||||
'type' => $type,
|
||||
'lng' => $lng,
|
||||
'lat' => $lat,
|
||||
'title' => $title,
|
||||
'distance' => $distance,
|
||||
'address' => $address,
|
||||
'thumb' => $thumb,
|
||||
];
|
||||
return WebSocketDialogMsg::sendMsg(null, $dialog_id, 'location', $msgData, $user->userid);
|
||||
}
|
||||
return Base::retError('位置类型错误');
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/dialog/msg/readlist 28. 获取消息阅读情况
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
@@ -1363,7 +1446,7 @@ class DialogController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/dialog/msg/detail 28. 消息详情
|
||||
* @api {get} api/dialog/msg/detail 29. 消息详情
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
@@ -1411,7 +1494,7 @@ class DialogController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/dialog/msg/download 29. 文件下载
|
||||
* @api {get} api/dialog/msg/download 30. 文件下载
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
@@ -1448,11 +1531,11 @@ class DialogController extends AbstractController
|
||||
}
|
||||
//
|
||||
$filePath = public_path($array['path']);
|
||||
return Base::streamDownload($filePath, $array['name']);
|
||||
return Base::BinaryFileResponse($filePath, $array['name']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/dialog/msg/withdraw 30. 聊天消息撤回
|
||||
* @api {get} api/dialog/msg/withdraw 31. 聊天消息撤回
|
||||
*
|
||||
* @apiDescription 消息撤回限制24小时内,需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
@@ -1478,7 +1561,7 @@ class DialogController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/dialog/msg/voice2text 31. 语音消息转文字
|
||||
* @api {get} api/dialog/msg/voice2text 32. 语音消息转文字
|
||||
*
|
||||
* @apiDescription 将语音消息转文字,需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
@@ -1530,7 +1613,66 @@ class DialogController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/dialog/msg/mark 32. 消息标记操作
|
||||
* @api {get} api/dialog/msg/translation 33. 翻译消息
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup dialog
|
||||
* @apiName msg__translation
|
||||
*
|
||||
* @apiParam {Number} msg_id 消息ID
|
||||
* @apiParam {String} [language] 目标语言,默认当前语言
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function msg__translation()
|
||||
{
|
||||
User::auth();
|
||||
//
|
||||
$msg_id = intval(Request::input("msg_id"));
|
||||
$language = Base::inputOrHeader('language');
|
||||
$targetLanguage = Doo::getLanguages($language);
|
||||
//
|
||||
if (empty($targetLanguage)) {
|
||||
return Base::retError("参数错误");
|
||||
}
|
||||
$msg = WebSocketDialogMsg::whereId($msg_id)->first();
|
||||
if (empty($msg)) {
|
||||
return Base::retError("消息不存在或已被删除");
|
||||
}
|
||||
if (!in_array($msg->type, ['text', 'record'])) {
|
||||
return Base::retError("此消息不支持翻译");
|
||||
}
|
||||
WebSocketDialog::checkDialog($msg->dialog_id);
|
||||
//
|
||||
$row = WebSocketDialogMsgTranslate::whereMsgId($msg_id)->whereLanguage($language)->first();
|
||||
if ($row) {
|
||||
return Base::retSuccess("success", $row->only(['msg_id', 'language', 'content']));
|
||||
}
|
||||
//
|
||||
$msgData = Base::json2array($msg->getRawOriginal('msg'));
|
||||
if (empty($msgData['text'])) {
|
||||
return Base::retError("消息内容为空");
|
||||
}
|
||||
$res = Extranet::openAItranslations($msgData['text'], $targetLanguage);
|
||||
if (Base::isError($res)) {
|
||||
return $res;
|
||||
}
|
||||
$row = WebSocketDialogMsgTranslate::createInstance([
|
||||
'dialog_id' => $msg->dialog_id,
|
||||
'msg_id' => $msg_id,
|
||||
'language' => $language,
|
||||
'content' => $res['data'],
|
||||
]);
|
||||
$row->save();
|
||||
//
|
||||
return Base::retSuccess("success", $row->only(['msg_id', 'language', 'content']));
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/dialog/msg/mark 34. 消息标记操作
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
@@ -1580,13 +1722,13 @@ class DialogController extends AbstractController
|
||||
}
|
||||
$dialogUser->mark_unread = $type == 'unread' ? 1 : 0;
|
||||
$dialogUser->save();
|
||||
$dialogUser->webSocketDialog->generateUnread($user->userid);
|
||||
$unreadData = WebSocketDialog::generateUnread($dialog_id, $user->userid);
|
||||
return Base::retSuccess("success", [
|
||||
'id' => $dialogUser->webSocketDialog->id,
|
||||
'unread' => $dialogUser->webSocketDialog->unread,
|
||||
'unread_one' => $dialogUser->webSocketDialog->unread_one,
|
||||
'mention' => $dialogUser->webSocketDialog->mention,
|
||||
'mention_ids' => $dialogUser->webSocketDialog->mention_ids,
|
||||
'id' => $dialog_id,
|
||||
'unread' => $unreadData['unread'],
|
||||
'unread_one' => $unreadData['unread_one'],
|
||||
'mention' => $unreadData['mention'],
|
||||
'mention_ids' => $unreadData['mention_ids'],
|
||||
'user_at' => Carbon::parse($dialogUser->updated_at)->toDateTimeString('millisecond'),
|
||||
'user_ms' => Carbon::parse($dialogUser->updated_at)->valueOf(),
|
||||
'mark_unread' => $dialogUser->mark_unread,
|
||||
@@ -1594,7 +1736,7 @@ class DialogController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/dialog/msg/silence 33. 消息免打扰
|
||||
* @api {get} api/dialog/msg/silence 35. 消息免打扰
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
@@ -1657,7 +1799,7 @@ class DialogController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/dialog/msg/forward 34. 转发消息给
|
||||
* @api {get} api/dialog/msg/forward 36. 转发消息给
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
@@ -1698,7 +1840,7 @@ class DialogController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/dialog/msg/emoji 35. emoji回复
|
||||
* @api {get} api/dialog/msg/emoji 37. emoji回复
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
@@ -1719,7 +1861,7 @@ class DialogController extends AbstractController
|
||||
$msg_id = intval(Request::input("msg_id"));
|
||||
$symbol = Request::input("symbol");
|
||||
//
|
||||
if (!preg_match("/^[\u{d800}-\u{dbff}]|[\u{dc00}-\u{dfff}]$/", $symbol)) {
|
||||
if (!preg_match('/[\x{1F300}-\x{1F9FF}]|[\x{1F000}-\x{1F02F}]|[\x{1F0A0}-\x{1F0FF}]|[\x{1F100}-\x{1F64F}]|[\x{1F680}-\x{1F6FF}]|[\x{2600}-\x{26FF}]|[\x{2700}-\x{27BF}]/u', $symbol)) {
|
||||
return Base::retError("参数错误");
|
||||
}
|
||||
//
|
||||
@@ -1733,7 +1875,7 @@ class DialogController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/dialog/msg/tag 36. 标注/取消标注
|
||||
* @api {get} api/dialog/msg/tag 38. 标注/取消标注
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
@@ -1762,7 +1904,7 @@ class DialogController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/dialog/msg/todo 37. 设待办/取消待办
|
||||
* @api {get} api/dialog/msg/todo 39. 设待办/取消待办
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
@@ -1805,7 +1947,7 @@ class DialogController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/dialog/msg/todolist 38. 获取消息待办情况
|
||||
* @api {get} api/dialog/msg/todolist 40. 获取消息待办情况
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
@@ -1835,7 +1977,7 @@ class DialogController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/dialog/msg/done 39. 完成待办
|
||||
* @api {get} api/dialog/msg/done 41. 完成待办
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
@@ -1888,7 +2030,7 @@ class DialogController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/dialog/msg/color 40. 设置颜色
|
||||
* @api {get} api/dialog/msg/color 42. 设置颜色
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
@@ -1929,7 +2071,7 @@ class DialogController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/dialog/group/add 41. 新增群组
|
||||
* @api {get} api/dialog/group/add 43. 新增群组
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
@@ -1984,14 +2126,14 @@ class DialogController extends AbstractController
|
||||
$dialog->avatar = $avatar;
|
||||
$dialog->save();
|
||||
}
|
||||
$data = WebSocketDialog::find($dialog->id)?->formatData($user->userid);
|
||||
$data = WebSocketDialog::synthesizeData($dialog, $user->userid);
|
||||
$userids = array_values(array_diff($userids, [$user->userid]));
|
||||
$dialog->pushMsg("groupAdd", null, $userids);
|
||||
return Base::retSuccess('创建成功', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/dialog/group/edit 42. 修改群组
|
||||
* @api {get} api/dialog/group/edit 44. 修改群组
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
@@ -2053,7 +2195,7 @@ class DialogController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/dialog/group/adduser 43. 添加群成员
|
||||
* @api {get} api/dialog/group/adduser 45. 添加群成员
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* - 有群主时:只有群主可以邀请
|
||||
@@ -2089,7 +2231,7 @@ class DialogController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/dialog/group/deluser 44. 移出(退出)群成员
|
||||
* @api {get} api/dialog/group/deluser 46. 移出(退出)群成员
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* - 只有群主、邀请人可以踢人
|
||||
@@ -2133,7 +2275,7 @@ class DialogController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/dialog/group/transfer 45. 转让群组
|
||||
* @api {get} api/dialog/group/transfer 47. 转让群组
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* - 只有群主且是个人类型群可以解散
|
||||
@@ -2182,7 +2324,7 @@ class DialogController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/dialog/group/disband 46. 解散群组
|
||||
* @api {get} api/dialog/group/disband 48. 解散群组
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* - 只有群主且是个人类型群可以解散
|
||||
@@ -2210,7 +2352,7 @@ class DialogController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/dialog/group/searchuser 47. 搜索个人群(仅限管理员)
|
||||
* @api {get} api/dialog/group/searchuser 49. 搜索个人群(仅限管理员)
|
||||
*
|
||||
* @apiDescription 需要token身份,用于创建部门搜索个人群组
|
||||
* @apiVersion 1.0.0
|
||||
@@ -2239,7 +2381,7 @@ class DialogController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} api/dialog/okr/add 48. 创建OKR评论会话
|
||||
* @api {post} api/dialog/okr/add 50. 创建OKR评论会话
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
@@ -2278,7 +2420,7 @@ class DialogController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} api/dialog/okr/push 49. 推送OKR相关信息
|
||||
* @api {post} api/dialog/okr/push 51. 推送OKR相关信息
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
@@ -2314,7 +2456,7 @@ class DialogController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} api/dialog/msg/wordchain 50. 发送接龙消息
|
||||
* @api {post} api/dialog/msg/wordchain 52. 发送接龙消息
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
@@ -2400,7 +2542,7 @@ class DialogController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} api/dialog/msg/vote 51. 发起投票
|
||||
* @api {post} api/dialog/msg/vote 53. 发起投票
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
@@ -2516,7 +2658,7 @@ class DialogController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/dialog/msg/top 52. 置顶/取消置顶
|
||||
* @api {get} api/dialog/msg/top 54. 置顶/取消置顶
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
@@ -2576,7 +2718,7 @@ class DialogController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/dialog/msg/topinfo 53. 获取置顶消息
|
||||
* @api {get} api/dialog/msg/topinfo 55. 获取置顶消息
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
@@ -2601,4 +2743,28 @@ class DialogController extends AbstractController
|
||||
//
|
||||
return Base::retSuccess('success', $topMsg);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/dialog/sticker/search 56. 搜索在线表情
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup dialog
|
||||
* @apiName sticker__search
|
||||
*
|
||||
* @apiParam {String} key 关键词
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function sticker__search()
|
||||
{
|
||||
User::auth();
|
||||
//
|
||||
$key = trim(Request::input('key'));
|
||||
return Base::retSuccess('success', [
|
||||
'list' => Extranet::sticker($key)
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ use App\Models\FileLink;
|
||||
use App\Models\FileUser;
|
||||
use App\Models\User;
|
||||
use App\Module\Base;
|
||||
use App\Module\Timer;
|
||||
use App\Module\Ihttp;
|
||||
use Response;
|
||||
use Session;
|
||||
@@ -552,14 +553,14 @@ class FileController extends AbstractController
|
||||
$tmpPath = "uploads/file/document/" . date("Ym") . "/" . $id . "/attached/";
|
||||
Base::makeDir(public_path($tmpPath));
|
||||
$tmpPath .= md5($text) . "." . $matchs[1][$key];
|
||||
if (Base::saveContentImage(public_path($tmpPath), base64_decode($text))) {
|
||||
if (Base::saveContentImage(public_path($tmpPath), base64_decode($text), 90)) {
|
||||
$paramet = getimagesize(public_path($tmpPath));
|
||||
$data['content'] = str_replace($matchs[0][$key], '<img src="' . Base::fillUrl($tmpPath) . '" original-width="' . $paramet[0] . '" original-height="' . $paramet[1] . '"', $data['content']);
|
||||
$isRep = true;
|
||||
}
|
||||
}
|
||||
$text = strip_tags($data['content']);
|
||||
if ($isRep == true) {
|
||||
if ($isRep) {
|
||||
$content = Base::array2json($data);
|
||||
}
|
||||
}
|
||||
@@ -1013,15 +1014,37 @@ class FileController extends AbstractController
|
||||
}
|
||||
|
||||
$user = User::auth();
|
||||
if ($user->isTemp()) {
|
||||
return Base::retError('无法打包下载');
|
||||
}
|
||||
$setting = Base::setting('fileSetting');
|
||||
switch ($setting['permission_pack_type']) {
|
||||
case 'admin':
|
||||
if (!$user->isAdmin()) {
|
||||
return Base::retError('此功能仅管理员可用');
|
||||
}
|
||||
break;
|
||||
case 'appointAllow':
|
||||
if (!in_array($user->userid, $setting['permission_pack_userids'])) {
|
||||
return Base::retError('此功能仅指定用户可用');
|
||||
}
|
||||
break;
|
||||
case 'appointProhibit':
|
||||
if (in_array($user->userid, $setting['permission_pack_userids'])) {
|
||||
return Base::retError('此功能已禁止使用');
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
$ids = Request::input('ids');
|
||||
$fileName = Request::input('name');
|
||||
$fileName = preg_replace("/[\/\\\:\*\?\"\<\>\|]/", "", $fileName);
|
||||
if (empty($fileName)) {
|
||||
$fileName = 'Package_' . $user->userid;
|
||||
}
|
||||
$fileName .= '_' . Base::time() . '.zip';
|
||||
$fileName .= '_' . Timer::time() . '.zip';
|
||||
|
||||
$filePath = "temp/file/pack/" . date("Ym", Base::time());
|
||||
$filePath = "temp/file/pack/" . date("Ym", Timer::time());
|
||||
$zipFile = "app/" . $filePath . "/" . $fileName;
|
||||
$zipPath = storage_path($zipFile);
|
||||
|
||||
@@ -1089,14 +1112,13 @@ class FileController extends AbstractController
|
||||
]);
|
||||
}
|
||||
//
|
||||
$text = "<b>文件下载打包已完成。</b>";
|
||||
$text .= "\n\n";
|
||||
$text .= "文件名:{$fileName}";
|
||||
$text .= "\n";
|
||||
$text .= "文件大小:".Base::twoFloat(filesize($zipPath) / 1024, true)."KB";
|
||||
$text .= "\n";
|
||||
$text .= '<a href="' . $fileUrl . '" target="_blank"><button type="button" class="ivu-btn ivu-btn-warning" style="margin-top: 10px;"><span>立即下载</span></button></a>';
|
||||
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'text', ['text' => $text], $botUser->userid, false, false, true);
|
||||
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'template', [
|
||||
'type' => 'file_download',
|
||||
'title' => '文件下载打包已完成',
|
||||
'name' => $fileName,
|
||||
'size' => filesize($zipPath),
|
||||
'url' => $fileUrl,
|
||||
], $botUser->userid, false, false, true);
|
||||
});
|
||||
return Base::retSuccess('success', [
|
||||
'name' => $fileName,
|
||||
|
||||
@@ -12,6 +12,7 @@ use App\Module\Doo;
|
||||
use App\Models\File;
|
||||
use App\Models\User;
|
||||
use App\Module\Base;
|
||||
use App\Module\Timer;
|
||||
use Swoole\Coroutine;
|
||||
use App\Models\Deleted;
|
||||
use App\Models\Project;
|
||||
@@ -323,7 +324,7 @@ class ProjectController extends AbstractController
|
||||
}
|
||||
$project->save();
|
||||
});
|
||||
$project->pushMsg('update', $project);
|
||||
$project->pushMsg('update');
|
||||
//
|
||||
return Base::retSuccess('修改成功', $project);
|
||||
}
|
||||
@@ -1003,7 +1004,7 @@ class ProjectController extends AbstractController
|
||||
}
|
||||
//
|
||||
if (is_array($time)) {
|
||||
if (Base::isDateOrTime($time[0]) && Base::isDateOrTime($time[1])) {
|
||||
if (Timer::isDateOrTime($time[0]) && Timer::isDateOrTime($time[1])) {
|
||||
$builder->betweenTime(Carbon::parse($time[0])->startOfDay(), Carbon::parse($time[1])->endOfDay());
|
||||
}
|
||||
}
|
||||
@@ -1111,7 +1112,7 @@ class ProjectController extends AbstractController
|
||||
/**
|
||||
* @api {get} api/project/task/easylists 20. 任务列表-简单的
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiDescription 需要token身份,主要用于判断是否有时间冲突的任务
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup project
|
||||
* @apiName task__easylists
|
||||
@@ -1130,23 +1131,24 @@ class ProjectController extends AbstractController
|
||||
//
|
||||
$taskid = trim(Request::input('taskid'));
|
||||
$userid = Request::input('userid');
|
||||
$timerange = Request::input('timerange');
|
||||
$timerange = TimeRange::parse(Request::input('timerange'));
|
||||
//
|
||||
$list = ProjectTask::with(['taskUser'])
|
||||
->select('projects.name as project_name', 'project_tasks.id', 'project_tasks.name', 'project_tasks.start_at', 'project_tasks.end_at')
|
||||
->select([
|
||||
'projects.name as project_name',
|
||||
'project_tasks.id',
|
||||
'project_tasks.name',
|
||||
'project_tasks.start_at',
|
||||
'project_tasks.end_at'
|
||||
])
|
||||
->join('projects','project_tasks.project_id','=','projects.id')
|
||||
->leftJoin('project_task_users', function ($query) {
|
||||
$query->on('project_tasks.id', '=', 'project_task_users.task_id')->where('project_task_users.owner', '=', 1);
|
||||
})
|
||||
->whereIn('project_task_users.userid', is_array($userid) ? $userid : explode(',', $userid) )
|
||||
->when(!empty($timerange), function ($query) use ($timerange) {
|
||||
if (!is_array($timerange)) {
|
||||
$timerange = explode(',', $timerange);
|
||||
}
|
||||
if (Base::isDateOrTime($timerange[0]) && Base::isDateOrTime($timerange[1])) {
|
||||
$query->where('project_tasks.start_at', '<=', Carbon::parse($timerange[1])->endOfDay());
|
||||
$query->where('project_tasks.end_at', '>=', Carbon::parse($timerange[0])->startOfDay());
|
||||
}
|
||||
->when($timerange->isExist(), function ($query) use ($timerange) {
|
||||
$query->where('project_tasks.start_at', '<=', $timerange->lastTime()->endOfDay());
|
||||
$query->where('project_tasks.end_at', '>=', $timerange->firstTime()->startOfDay());
|
||||
})
|
||||
->when(!empty($taskid), function ($query) use ($taskid) {
|
||||
$query->where('project_tasks.id', "!=", $taskid);
|
||||
@@ -1195,7 +1197,7 @@ class ProjectController extends AbstractController
|
||||
if (count($userid) > 100) {
|
||||
return Base::retError('导出成员限制最多100个');
|
||||
}
|
||||
if (!(is_array($time) && Base::isDateOrTime($time[0]) && Base::isDateOrTime($time[1]))) {
|
||||
if (!(is_array($time) && Timer::isDateOrTime($time[0]) && Timer::isDateOrTime($time[1]))) {
|
||||
return Base::retError('时间选择错误');
|
||||
}
|
||||
if (Carbon::parse($time[1])->timestamp - Carbon::parse($time[0])->timestamp > 90 * 86400) {
|
||||
@@ -1210,26 +1212,29 @@ class ProjectController extends AbstractController
|
||||
go(function () use ($user, $userid, $time, $type, $botUser, $dialog) {
|
||||
Coroutine::sleep(0.1);
|
||||
$headings = [];
|
||||
$headings[] = '任务ID';
|
||||
$headings[] = '父级任务ID';
|
||||
$headings[] = '所属项目';
|
||||
$headings[] = '任务标题';
|
||||
$headings[] = '任务开始时间';
|
||||
$headings[] = '任务结束时间';
|
||||
$headings[] = '完成时间';
|
||||
$headings[] = '归档时间';
|
||||
$headings[] = '任务计划用时';
|
||||
$headings[] = '实际完成用时';
|
||||
$headings[] = '超时时间';
|
||||
$headings[] = '开发用时';
|
||||
$headings[] = '验收/测试用时';
|
||||
$headings[] = '负责人';
|
||||
$headings[] = '创建人';
|
||||
$headings[] = '状态';
|
||||
$headings[] = Doo::translate('任务ID');
|
||||
$headings[] = Doo::translate('父级任务ID');
|
||||
$headings[] = Doo::translate('所属项目');
|
||||
$headings[] = Doo::translate('任务标题');
|
||||
$headings[] = Doo::translate('任务开始时间');
|
||||
$headings[] = Doo::translate('任务结束时间');
|
||||
$headings[] = Doo::translate('完成时间');
|
||||
$headings[] = Doo::translate('归档时间');
|
||||
$headings[] = Doo::translate('任务计划用时');
|
||||
$headings[] = Doo::translate('实际完成用时');
|
||||
$headings[] = Doo::translate('超时时间');
|
||||
$headings[] = Doo::translate('开发用时');
|
||||
$headings[] = Doo::translate('验收/测试用时');
|
||||
$headings[] = Doo::translate('负责人');
|
||||
$headings[] = Doo::translate('创建人');
|
||||
$headings[] = Doo::translate('状态');
|
||||
$datas = [];
|
||||
//
|
||||
$text = '<b>导出任务统计已完成。</b>';
|
||||
$text .= "\n\n";
|
||||
$content = [];
|
||||
$content[] = [
|
||||
'content' => '导出任务统计已完成',
|
||||
'style' => 'font-weight: bold;padding-bottom: 4px;',
|
||||
];
|
||||
//
|
||||
$builder = ProjectTask::select(['project_tasks.*', 'project_task_users.userid as ownerid'])
|
||||
->join('project_task_users', 'project_tasks.id', '=', 'project_task_users.task_id')
|
||||
@@ -1240,20 +1245,21 @@ class ProjectController extends AbstractController
|
||||
/** @var ProjectTask $task */
|
||||
foreach ($tasks as $task) {
|
||||
$flowChanges = ProjectTaskFlowChange::whereTaskId($task->id)->get();
|
||||
$testTime = 0;//验收/测试时间
|
||||
$testTime = 0; // 测试时间
|
||||
$taskStartTime = $task->start_at ? Carbon::parse($task->start_at)->timestamp : Carbon::parse($task->created_at)->timestamp;
|
||||
$taskCompleteTime = $task->complete_at ? Carbon::parse($task->complete_at)->timestamp : time();
|
||||
$totalTime = $taskCompleteTime - $taskStartTime; //开发测试总用时
|
||||
$totalTime = $taskCompleteTime - $taskStartTime; // 任务总用时
|
||||
foreach ($flowChanges as $change) {
|
||||
if (!str_contains($change->before_flow_item_name, 'end')) {
|
||||
$upOne = ProjectTaskFlowChange::where('id', '<', $change->id)->whereTaskId($task->id)->orderByDesc('id')->first();
|
||||
if ($upOne) {
|
||||
if (str_contains($change->before_flow_item_name, 'test') || str_contains($change->before_flow_item_name, '测试') || strpos($change->before_flow_item_name, '验收') !== false) {
|
||||
$testCtime = Carbon::parse($change->created_at)->timestamp;
|
||||
$tTime = Carbon::parse($upOne->created_at)->timestamp;
|
||||
$tMinusNum = $testCtime - $tTime;
|
||||
$testTime += $tMinusNum;
|
||||
}
|
||||
if (str_starts_with($change->before_flow_item_name, 'end')) {
|
||||
continue;
|
||||
}
|
||||
$upOne = ProjectTaskFlowChange::where('id', '<', $change->id)->whereTaskId($task->id)->orderByDesc('id')->first();
|
||||
if ($upOne) {
|
||||
if (str_starts_with($change->before_flow_item_name, 'test')) {
|
||||
$testCtime = Carbon::parse($change->created_at)->timestamp;
|
||||
$tTime = Carbon::parse($upOne->created_at)->timestamp;
|
||||
$tMinusNum = $testCtime - $tTime;
|
||||
$testTime += $tMinusNum;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1261,27 +1267,27 @@ class ProjectController extends AbstractController
|
||||
$lastChange = ProjectTaskFlowChange::whereTaskId($task->id)->orderByDesc('id')->first();
|
||||
$nowTime = time();
|
||||
$unFinishTime = $nowTime - Carbon::parse($lastChange->created_at)->timestamp;
|
||||
if (str_contains($lastChange->after_flow_item_name, 'test') || str_contains($lastChange->after_flow_item_name, '测试') || strpos($lastChange->after_flow_item_name, '验收') !== false) {
|
||||
if (str_starts_with($lastChange->after_flow_item_name, 'test')) {
|
||||
$testTime += $unFinishTime;
|
||||
}
|
||||
}
|
||||
$developTime = $totalTime - $testTime;//开发时间
|
||||
$planTime = '-';//任务计划用时
|
||||
$overTime = '-';//超时时间
|
||||
$developTime = $totalTime - $testTime; // 开发时间
|
||||
$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));
|
||||
$overTime = Doo::translate(Timer::timeFormat(abs($residueTime)));
|
||||
}
|
||||
$planTime = Base::timeDiff($startTime, $endTime);
|
||||
$planTime = Doo::translate(Timer::timeDiff($startTime, $endTime));
|
||||
}
|
||||
$actualTime = $task->complete_at ? $totalTime : 0;//实际完成用时
|
||||
$actualTime = $task->complete_at ? $totalTime : 0; // 实际完成用时
|
||||
$statusText = '未完成';
|
||||
if ($task->flow_item_name) {
|
||||
if (str_contains($task->flow_item_name, '已取消')) {
|
||||
if (str_starts_with($task->flow_item_name, 'end')) {
|
||||
if (preg_match('/已取消|Cancelled|취소됨|キャンセル済み|Abgebrochen|Annulé|Dibatalkan|Отменено/', $task->flow_item_name)) {
|
||||
$statusText = '已取消';
|
||||
$actualTime = 0;
|
||||
$testTime = 0;
|
||||
@@ -1316,20 +1322,27 @@ class ProjectController extends AbstractController
|
||||
$task->end_at ?: '-',
|
||||
$task->complete_at ?: '-',
|
||||
$task->archived_at ?: '-',
|
||||
$planTime ?: '-',
|
||||
$actualTime ? Base::timeFormat($actualTime) : '-',
|
||||
$planTime,
|
||||
$actualTime ? Doo::translate(Timer::timeFormat($actualTime)) : '-',
|
||||
$overTime,
|
||||
$developTime > 0 ? Base::timeFormat($developTime) : '-',
|
||||
$testTime > 0 ? Base::timeFormat($testTime) : '-',
|
||||
$developTime > 0 ? Doo::translate(Timer::timeFormat($developTime)) : '-',
|
||||
$testTime > 0 ? Doo::translate(Timer::timeFormat($testTime)) : '-',
|
||||
Base::filterEmoji(User::userid2nickname($task->ownerid)) . " (ID: {$task->ownerid})",
|
||||
Base::filterEmoji(User::userid2nickname($task->userid)) . " (ID: {$task->userid})",
|
||||
$statusText
|
||||
Doo::translate($statusText),
|
||||
];
|
||||
}
|
||||
});
|
||||
if (empty($datas)) {
|
||||
$text .= '没有任何数据';
|
||||
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'text', ['text' => $text], $botUser->userid, false, false, true);
|
||||
$content[] = [
|
||||
'content' => '没有任何数据',
|
||||
'style' => 'color: #ff0000;',
|
||||
];
|
||||
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'template', [
|
||||
'type' => 'content',
|
||||
'title' => $content[0]['content'],
|
||||
'content' => $content,
|
||||
], $botUser->userid, false, false, true);
|
||||
return;
|
||||
}
|
||||
//
|
||||
@@ -1346,15 +1359,24 @@ class ProjectController extends AbstractController
|
||||
//
|
||||
$fileName = User::userid2nickname($userid[0]) ?: $userid[0];
|
||||
if (count($userid) > 1) {
|
||||
$fileName .= '等' . count($userid) . '位成员任务统计';
|
||||
$fileName .= '等' . count($userid) . '位成员的任务统计';
|
||||
} else {
|
||||
$fileName .= '的任务统计';
|
||||
}
|
||||
$fileName .= '_' . Base::time() . '.xls';
|
||||
$filePath = "temp/task/export/" . date("Ym", Base::time());
|
||||
$fileName = Doo::translate($fileName) . '_' . Timer::time() . '.xls';
|
||||
$filePath = "temp/task/export/" . date("Ym", Timer::time());
|
||||
$export = new BillMultipleExport($sheets);
|
||||
$res = $export->store($filePath . "/" . $fileName);
|
||||
if ($res != 1) {
|
||||
$text .= "导出失败,{$fileName}!";
|
||||
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'text', ['text' => $text], $botUser->userid, false, false, true);
|
||||
$content[] = [
|
||||
'content' => "导出失败,{$fileName}!",
|
||||
'style' => 'color: #ff0000;',
|
||||
];
|
||||
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'template', [
|
||||
'type' => 'content',
|
||||
'title' => $content[0]['content'],
|
||||
'content' => $content,
|
||||
], $botUser->userid, false, false, true);
|
||||
return;
|
||||
}
|
||||
//
|
||||
@@ -1375,17 +1397,26 @@ class ProjectController extends AbstractController
|
||||
]));
|
||||
$fileUrl = Base::fillUrl('api/project/task/down?key=' . urlencode($base64));
|
||||
Session::put('task::export:userid', $user->userid);
|
||||
$text .= "文件名:{$fileName}";
|
||||
$text .= "\n";
|
||||
$text .= "文件大小:" . Base::twoFloat(filesize($zipPath) / 1024, true) . "KB";
|
||||
$text .= "\n";
|
||||
$text .= '<a href="' . $fileUrl . '" target="_blank"><button type="button" class="ivu-btn ivu-btn-warning" style="margin-top: 10px;"><span>立即下载</span></button></a>';
|
||||
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'template', [
|
||||
'type' => 'file_download',
|
||||
'title' => '导出任务统计已完成',
|
||||
'name' => $fileName,
|
||||
'size' => filesize($zipPath),
|
||||
'url' => $fileUrl,
|
||||
], $botUser->userid, false, false, true);
|
||||
} else {
|
||||
$text .= '打包失败,请稍后再试...';
|
||||
$content[] = [
|
||||
'content' => "打包失败,请稍后再试...",
|
||||
'style' => 'color: #ff0000;',
|
||||
];
|
||||
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'template', [
|
||||
'type' => 'content',
|
||||
'title' => $content[0]['content'],
|
||||
'content' => $content,
|
||||
], $botUser->userid, false, false, true);
|
||||
}
|
||||
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'text', ['text' => $text], $botUser->userid, false, false, true);
|
||||
});
|
||||
return Base::retSuccess('success', ['msg' => '正在打包,请留意系统消息。']);
|
||||
return Base::retSuccess('success');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1405,16 +1436,16 @@ class ProjectController extends AbstractController
|
||||
$user = User::auth('admin');
|
||||
//
|
||||
$headings = [];
|
||||
$headings[] = '任务ID';
|
||||
$headings[] = '父级任务ID';
|
||||
$headings[] = '所属项目';
|
||||
$headings[] = '任务标题';
|
||||
$headings[] = '任务开始时间';
|
||||
$headings[] = '任务结束时间';
|
||||
$headings[] = '任务计划用时';
|
||||
$headings[] = '超时时间';
|
||||
$headings[] = '负责人';
|
||||
$headings[] = '创建人';
|
||||
$headings[] = Doo::translate('任务ID');
|
||||
$headings[] = Doo::translate('父级任务ID');
|
||||
$headings[] = Doo::translate('所属项目');
|
||||
$headings[] = Doo::translate('任务标题');
|
||||
$headings[] = Doo::translate('任务开始时间');
|
||||
$headings[] = Doo::translate('任务结束时间');
|
||||
$headings[] = Doo::translate('任务计划用时');
|
||||
$headings[] = Doo::translate('超时时间');
|
||||
$headings[] = Doo::translate('负责人');
|
||||
$headings[] = Doo::translate('创建人');
|
||||
$data = [];
|
||||
//
|
||||
ProjectTask::whereNull('complete_at')
|
||||
@@ -1434,9 +1465,9 @@ class ProjectController extends AbstractController
|
||||
$planTotalTime = $endTime - $startTime;
|
||||
$residueTime = $planTotalTime - $totalTime;
|
||||
if ($residueTime < 0) {
|
||||
$overTime = Base::timeFormat(abs($residueTime));
|
||||
$overTime = Doo::translate(Timer::timeFormat(abs($residueTime)));
|
||||
}
|
||||
$planTime = Base::timeDiff($startTime, $endTime);
|
||||
$planTime = Doo::translate(Timer::timeDiff($startTime, $endTime));
|
||||
}
|
||||
$ownerIds = $task->taskUser->where('owner', 1)->pluck('userid')->toArray();
|
||||
$ownerNames = [];
|
||||
@@ -1450,7 +1481,7 @@ class ProjectController extends AbstractController
|
||||
Base::filterEmoji($task->name),
|
||||
$task->start_at ?: '-',
|
||||
$task->end_at ?: '-',
|
||||
$planTime ?: '-',
|
||||
$planTime,
|
||||
$overTime,
|
||||
implode("、", $ownerNames),
|
||||
Base::filterEmoji(User::userid2nickname($task->userid)) . " (ID: {$task->userid})",
|
||||
@@ -1461,12 +1492,13 @@ class ProjectController extends AbstractController
|
||||
return Base::retError('没有任何数据');
|
||||
}
|
||||
//
|
||||
$title = Doo::translate('超期任务');
|
||||
$sheets = [
|
||||
BillExport::create()->setTitle("超期任务")->setHeadings($headings)->setData($data)->setStyles(["A1:J1" => ["font" => ["bold" => true]]])
|
||||
BillExport::create()->setTitle($title)->setHeadings($headings)->setData($data)->setStyles(["A1:J1" => ["font" => ["bold" => true]]])
|
||||
];
|
||||
//
|
||||
$fileName = '超期任务_' . Base::time() . '.xls';
|
||||
$filePath = "temp/task/export/" . date("Ym", Base::time());
|
||||
$fileName = $title . '_' . Timer::time() . '.xls';
|
||||
$filePath = "temp/task/export/" . date("Ym", Timer::time());
|
||||
$export = new BillMultipleExport($sheets);
|
||||
$res = $export->store($filePath . "/" . $fileName);
|
||||
if ($res != 1) {
|
||||
@@ -1692,7 +1724,7 @@ class ProjectController extends AbstractController
|
||||
//
|
||||
$task = ProjectTask::userTask($file->task_id);
|
||||
//
|
||||
ProjectPermission::userTaskPermission(Project::userProject($task->project_id), ProjectPermission::TASK_UPDATE, $task);
|
||||
ProjectPermission::userTaskPermission(Project::userProject($task->project_id), ProjectPermission::TASK_REMOVE, $task);
|
||||
//
|
||||
$task->pushMsg('filedelete', $file);
|
||||
$file->delete();
|
||||
@@ -1788,7 +1820,7 @@ class ProjectController extends AbstractController
|
||||
}
|
||||
//
|
||||
$filePath = public_path($file->getRawOriginal('path'));
|
||||
return Base::streamDownload($filePath, $file->name);
|
||||
return Base::BinaryFileResponse($filePath, $file->name);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1964,11 +1996,13 @@ class ProjectController extends AbstractController
|
||||
$task = ProjectTask::userTask($task_id);
|
||||
//
|
||||
$project = Project::userProject($task->project_id);
|
||||
if (Arr::exists($param, 'flow_item_id')) {
|
||||
ProjectPermission::userTaskPermission($project, ProjectPermission::TASK_STATUS, $task);
|
||||
}else{
|
||||
ProjectPermission::userTaskPermission($project, ProjectPermission::TASK_UPDATE, $task);
|
||||
$permissionKey = ProjectPermission::TASK_UPDATE;
|
||||
if (Arr::exists($param, 'times')) {
|
||||
$permissionKey = ProjectPermission::TASK_TIME;
|
||||
} else if (Arr::exists($param, 'flow_item_id')) {
|
||||
$permissionKey = ProjectPermission::TASK_STATUS;
|
||||
}
|
||||
ProjectPermission::userTaskPermission($project, $permissionKey, $task);
|
||||
//
|
||||
$taskUser = ProjectTaskUser::select(['userid', 'owner'])->whereTaskId($task_id)->get();
|
||||
$owners = $taskUser->where('owner', 1)->pluck('userid')->toArray();
|
||||
@@ -2072,7 +2106,7 @@ class ProjectController extends AbstractController
|
||||
});
|
||||
//
|
||||
$task->pushMsg('dialog');
|
||||
$dialogData = WebSocketDialog::find($task->dialog_id)?->formatData($user->userid);
|
||||
$dialogData = WebSocketDialog::synthesizeData($task->dialog_id, $user->userid);
|
||||
return Base::retSuccess('success', [
|
||||
'id' => $task->id,
|
||||
'dialog_id' => $task->dialog_id,
|
||||
@@ -2508,10 +2542,10 @@ class ProjectController extends AbstractController
|
||||
}
|
||||
$log->detail = Doo::translate($log->detail);
|
||||
$log->time = [
|
||||
'ymd' => date(date("Y", $timestamp) == date("Y", Base::time()) ? "m-d" : "Y-m-d", $timestamp),
|
||||
'ymd' => date(date("Y", $timestamp) == date("Y", Timer::time()) ? "m-d" : "Y-m-d", $timestamp),
|
||||
'hi' => date("h:i", $timestamp) ,
|
||||
'week' => Doo::translate("周" . Base::getTimeWeek($timestamp)),
|
||||
'segment' => Doo::translate(Base::getTimeDayeSegment($timestamp)),
|
||||
'week' => Doo::translate("周" . Timer::getWeek($timestamp)),
|
||||
'segment' => Doo::translate(Timer::getDayeSegment($timestamp)),
|
||||
];
|
||||
$record = Base::json2array($log->record);
|
||||
if (is_array($record['change'])) {
|
||||
@@ -2558,6 +2592,10 @@ class ProjectController extends AbstractController
|
||||
}
|
||||
$projectUser->top_at = $projectUser->top_at ? null : Carbon::now();
|
||||
$projectUser->save();
|
||||
if ($projectUser->project) {
|
||||
$projectUser->project->updated_at = Carbon::now();
|
||||
$projectUser->project->save();
|
||||
}
|
||||
return Base::retSuccess("success", [
|
||||
'id' => $projectUser->project_id,
|
||||
'top_at' => $projectUser->top_at?->toDateTimeString(),
|
||||
@@ -2625,8 +2663,9 @@ class ProjectController extends AbstractController
|
||||
ProjectPermission::TASK_LIST_SORT,
|
||||
ProjectPermission::TASK_ADD,
|
||||
ProjectPermission::TASK_UPDATE,
|
||||
ProjectPermission::TASK_REMOVE,
|
||||
ProjectPermission::TASK_TIME,
|
||||
ProjectPermission::TASK_STATUS,
|
||||
ProjectPermission::TASK_REMOVE,
|
||||
ProjectPermission::TASK_ARCHIVED,
|
||||
ProjectPermission::TASK_MOVE,
|
||||
]);
|
||||
|
||||
@@ -65,7 +65,9 @@ class PublicController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* {post} 签到 - 路由器(openwrt)上报
|
||||
* {post} 签到 - 上报
|
||||
* - 1、路由器(openwrt)签到上报
|
||||
* - 2、考勤机签到上报
|
||||
*
|
||||
* @apiParam {String} key
|
||||
* @apiParam {String} mac 使用逗号分割多个
|
||||
@@ -78,20 +80,30 @@ class PublicController extends AbstractController
|
||||
$key = trim(Request::input('key'));
|
||||
$mac = trim(Request::input('mac'));
|
||||
$time = intval(Request::input('time'));
|
||||
$type = trim(Request::input('type'));
|
||||
//
|
||||
$setting = Base::setting('checkinSetting');
|
||||
if ($setting['open'] !== 'open') {
|
||||
return 'function off';
|
||||
}
|
||||
if (!in_array('auto', $setting['modes'])) {
|
||||
return 'mode off';
|
||||
}
|
||||
if ($key != $setting['key']) {
|
||||
return 'key error';
|
||||
}
|
||||
if ($error = UserBot::checkinBotCheckin($mac, $time)) {
|
||||
return $error;
|
||||
$alreadyTip = false;
|
||||
if ($type === 'face') {
|
||||
if (!in_array('face', $setting['modes'])) {
|
||||
return 'mode off';
|
||||
}
|
||||
if ($key != $setting['face_key']) {
|
||||
return 'key error';
|
||||
}
|
||||
$alreadyTip = $setting['face_retip'] === 'open';
|
||||
} else {
|
||||
if (!in_array('auto', $setting['modes'])) {
|
||||
return 'mode off';
|
||||
}
|
||||
if ($key != $setting['key']) {
|
||||
return 'key error';
|
||||
}
|
||||
}
|
||||
UserBot::checkinBotCheckin($mac, $time, $alreadyTip);
|
||||
return 'success';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,8 +53,8 @@ class ReportController extends AbstractController
|
||||
$builder->whereType($keys['type']);
|
||||
}
|
||||
if (is_array($keys['created_at'])) {
|
||||
if ($keys['created_at'][0] > 0) $builder->where('created_at', '>=', date('Y-m-d H:i:s', Base::dayTimeF($keys['created_at'][0])));
|
||||
if ($keys['created_at'][1] > 0) $builder->where('created_at', '<=', date('Y-m-d H:i:s', Base::dayTimeE($keys['created_at'][1])));
|
||||
if ($keys['created_at'][0] > 0) $builder->where('created_at', '>=', Base::newCarbon($keys['created_at'][0])->startOfDay());
|
||||
if ($keys['created_at'][1] > 0) $builder->where('created_at', '<=', Base::newCarbon($keys['created_at'][1])->endOfDay());
|
||||
}
|
||||
}
|
||||
$list = $builder->orderByDesc('created_at')->paginate(Base::getPaginate(50, 20));
|
||||
@@ -99,14 +99,14 @@ class ReportController extends AbstractController
|
||||
$builder->whereType($keys['type']);
|
||||
}
|
||||
if (is_array($keys['created_at'])) {
|
||||
if ($keys['created_at'][0] > 0) $builder->where('created_at', '>=', date('Y-m-d H:i:s', Base::dayTimeF($keys['created_at'][0])));
|
||||
if ($keys['created_at'][1] > 0) $builder->where('created_at', '<=', date('Y-m-d H:i:s', Base::dayTimeE($keys['created_at'][1])));
|
||||
if ($keys['created_at'][0] > 0) $builder->where('created_at', '>=', Base::newCarbon($keys['created_at'][0])->startOfDay());
|
||||
if ($keys['created_at'][1] > 0) $builder->where('created_at', '<=', Base::newCarbon($keys['created_at'][1])->endOfDay());
|
||||
}
|
||||
}
|
||||
$list = $builder->orderByDesc('created_at')->paginate(Base::getPaginate(50, 20));
|
||||
if ($list->items()) {
|
||||
foreach ($list->items() as $item) {
|
||||
$item->receive_time = ReportReceive::query()->whereRid($item["id"])->whereUserid($user->userid)->value("receive_time");
|
||||
$item->receive_at = ReportReceive::query()->whereRid($item["id"])->whereUserid($user->userid)->value("receive_at");
|
||||
}
|
||||
}
|
||||
return Base::retSuccess('success', $list);
|
||||
@@ -174,7 +174,7 @@ class ReportController extends AbstractController
|
||||
|
||||
foreach ($input["receive"] as $userid) {
|
||||
$input["receive_content"][] = [
|
||||
"receive_time" => Carbon::now()->toDateTimeString(),
|
||||
"receive_at" => Carbon::now()->toDateTimeString(),
|
||||
"userid" => $userid,
|
||||
"read" => 0,
|
||||
];
|
||||
@@ -259,6 +259,7 @@ class ReportController extends AbstractController
|
||||
$offset = abs(intval(Request::input("offset", 0)));
|
||||
$id = intval(Request::input("offset", 0));
|
||||
$now_dt = trim(Request::input("date")) ? Carbon::parse(Request::input("date")) : Carbon::now();
|
||||
|
||||
// 获取开始时间
|
||||
if ($type === Report::DAILY) {
|
||||
$start_time = Carbon::today();
|
||||
@@ -280,9 +281,11 @@ class ReportController extends AbstractController
|
||||
$start_time->startOfWeek();
|
||||
$end_time = Carbon::instance($start_time)->endOfWeek();
|
||||
}
|
||||
|
||||
// 生成唯一标识
|
||||
$sign = Report::generateSign($type, 0, Carbon::instance($start_time));
|
||||
$one = Report::whereSign($sign)->whereType($type)->first();
|
||||
|
||||
// 如果已经提交了相关汇报
|
||||
if ($one && $id > 0) {
|
||||
return Base::retSuccess('success', [
|
||||
@@ -293,8 +296,16 @@ class ReportController extends AbstractController
|
||||
]);
|
||||
}
|
||||
|
||||
// 表格头部
|
||||
$labels = [
|
||||
Doo::translate('项目'),
|
||||
Doo::translate('任务'),
|
||||
Doo::translate('负责人'),
|
||||
Doo::translate('备注'),
|
||||
];
|
||||
|
||||
// 已完成的任务
|
||||
$completeContent = "";
|
||||
$completeDatas = [];
|
||||
$complete_task = ProjectTask::query()
|
||||
->whereNotNull("complete_at")
|
||||
->whereBetween("complete_at", [$start_time->toDateTimeString(), $end_time->toDateTimeString()])
|
||||
@@ -306,15 +317,20 @@ class ReportController extends AbstractController
|
||||
if ($complete_task->isNotEmpty()) {
|
||||
foreach ($complete_task as $task) {
|
||||
$complete_at = Carbon::parse($task->complete_at);
|
||||
$pre = $type == Report::WEEKLY ? ('<span>[' . Doo::translate('周' . ['日', '一', '二', '三', '四', '五', '六'][$complete_at->dayOfWeek]) . ']</span> ') : '';
|
||||
$completeContent .= "<li>{$pre}[{$task->project->name}] {$task->name}</li>";
|
||||
$remark = $type == Report::WEEKLY ? ('<div style="text-align:center">[' . Doo::translate('周' . ['日', '一', '二', '三', '四', '五', '六'][$complete_at->dayOfWeek]) . ']</div>') : ' ';
|
||||
$completeDatas[] = [
|
||||
$task->project->name,
|
||||
$task->name,
|
||||
$task->taskUser->where("owner", 1)->map(function ($item) {
|
||||
return User::userid2nickname($item->userid);
|
||||
})->implode(", "),
|
||||
$remark,
|
||||
];
|
||||
}
|
||||
} else {
|
||||
$completeContent = '<li> </li>';
|
||||
}
|
||||
|
||||
// 未完成的任务
|
||||
$unfinishedContent = "";
|
||||
$unfinishedDatas = [];
|
||||
$unfinished_task = ProjectTask::query()
|
||||
->join("projects", "projects.id", "=", "project_tasks.project_id")
|
||||
->whereNull("projects.archived_at")
|
||||
@@ -330,12 +346,18 @@ class ReportController extends AbstractController
|
||||
if ($unfinished_task->isNotEmpty()) {
|
||||
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;">[' . Doo::translate('超期') . ']</span> ' : '';
|
||||
$unfinishedContent .= "<li>{$pre}[{$task->project->name}] {$task->name}</li>";
|
||||
$remark = (!empty($end_at) && $end_at->lt($now_dt)) ? '<div style="color:#ff0000;text-align:center">[' . Doo::translate('超期') . ']</div>' : ' ';
|
||||
$unfinishedDatas[] = [
|
||||
$task->project->name,
|
||||
$task->name,
|
||||
$task->taskUser->where("owner", 1)->map(function ($item) {
|
||||
return User::userid2nickname($item->userid);
|
||||
})->implode(", "),
|
||||
$remark,
|
||||
];
|
||||
}
|
||||
} else {
|
||||
$unfinishedContent = '<li> </li>';
|
||||
}
|
||||
|
||||
// 生成标题
|
||||
if ($type === Report::WEEKLY) {
|
||||
$title = $user->nickname . "的周报[" . $start_time->format("m/d") . "-" . $end_time->format("m/d") . "]";
|
||||
@@ -343,22 +365,43 @@ class ReportController extends AbstractController
|
||||
} else {
|
||||
$title = $user->nickname . "的日报[" . $start_time->format("Y/m/d") . "]";
|
||||
}
|
||||
$title = Doo::translate($title);
|
||||
|
||||
// 生成内容
|
||||
$content = '<h2>' . Doo::translate('已完成工作') . '</h2><ol>' .
|
||||
$completeContent . '</ol><h2>' .
|
||||
Doo::translate('未完成的工作') . '</h2><ol>' .
|
||||
$unfinishedContent . '</ol>';
|
||||
$contents = [];
|
||||
$contents[] = '<h2>' . Doo::translate('已完成工作') . '</h2>';
|
||||
$contents[] = view('report', [
|
||||
'labels' => $labels,
|
||||
'datas' => $completeDatas,
|
||||
])->render();
|
||||
|
||||
$contents[] = '<p> </p>';
|
||||
$contents[] = '<h2>' . Doo::translate('未完成的工作') . '</h2>';
|
||||
$contents[] = view('report', [
|
||||
'labels' => $labels,
|
||||
'datas' => $unfinishedDatas,
|
||||
])->render();
|
||||
|
||||
if ($type === Report::WEEKLY) {
|
||||
$content .= "<h2>" . Doo::translate("下周拟定计划") . "[" . $start_time->addWeek()->format("m/d") . "-" . $end_time->addWeek()->format("m/d") . "]</h2><ol><li> </li></ol>";
|
||||
$contents[] = '<p> </p>';
|
||||
$contents[] = "<h2>" . Doo::translate("下周拟定计划") . "[" . $start_time->addWeek()->format("m/d") . "-" . $end_time->addWeek()->format("m/d") . "]</h2>";
|
||||
$contents[] = view('report', [
|
||||
'labels' => [
|
||||
Doo::translate('计划描述'),
|
||||
Doo::translate('计划时间'),
|
||||
Doo::translate('负责人'),
|
||||
],
|
||||
'datas' => [],
|
||||
])->render();
|
||||
}
|
||||
|
||||
$data = [
|
||||
"time" => $start_time->toDateTimeString(),
|
||||
"sign" => $sign,
|
||||
"title" => $title,
|
||||
"content" => $content,
|
||||
"complete_task" => $complete_task,
|
||||
"unfinished_task" => $unfinished_task,
|
||||
"content" => implode("", $contents),
|
||||
];
|
||||
|
||||
if ($one) {
|
||||
$data['id'] = $one->id;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ use Carbon\Carbon;
|
||||
use App\Module\Doo;
|
||||
use App\Models\User;
|
||||
use App\Module\Base;
|
||||
use App\Module\Timer;
|
||||
use App\Models\Setting;
|
||||
use App\Module\Extranet;
|
||||
use LdapRecord\Container;
|
||||
@@ -40,7 +41,7 @@ class SystemController extends AbstractController
|
||||
* @apiParam {String} type
|
||||
* - get: 获取(默认)
|
||||
* - all: 获取所有(需要管理员权限)
|
||||
* - save: 保存设置(参数:['reg', 'reg_identity', 'reg_invite', 'temp_account_alias', 'login_code', 'password_policy', 'project_invite', 'chat_information', 'anon_message', 'voice2text', 'e2e_message', 'auto_archived', 'archived_day', 'task_visible', 'task_default_time', 'all_group_mute', 'all_group_autoin', 'user_private_chat_mute', 'user_group_chat_mute', 'image_compress', 'image_save_local', 'start_home'])
|
||||
* - save: 保存设置(参数:['reg', 'reg_identity', 'reg_invite', 'temp_account_alias', 'login_code', 'password_policy', 'project_invite', 'chat_information', 'anon_message', 'voice2text', 'translation', 'e2e_message', 'auto_archived', 'archived_day', 'task_visible', 'task_default_time', 'all_group_mute', 'all_group_autoin', 'user_private_chat_mute', 'user_group_chat_mute', 'system_alias', 'image_compress', 'image_save_local', 'start_home'])
|
||||
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
@@ -67,6 +68,7 @@ class SystemController extends AbstractController
|
||||
'chat_information',
|
||||
'anon_message',
|
||||
'voice2text',
|
||||
'translation',
|
||||
'e2e_message',
|
||||
'auto_archived',
|
||||
'archived_day',
|
||||
@@ -76,6 +78,7 @@ class SystemController extends AbstractController
|
||||
'all_group_autoin',
|
||||
'user_private_chat_mute',
|
||||
'user_group_chat_mute',
|
||||
'system_alias',
|
||||
'image_compress',
|
||||
'image_save_local',
|
||||
'start_home',
|
||||
@@ -97,6 +100,12 @@ class SystemController extends AbstractController
|
||||
if ($all['voice2text'] == 'open' && empty(Base::settingFind('aibotSetting', 'openai_key'))) {
|
||||
return Base::retError('开启语音转文字功能需要在应用中开启 ChatGPT AI 机器人。');
|
||||
}
|
||||
if ($all['translation'] == 'open' && empty(Base::settingFind('aibotSetting', 'openai_key'))) {
|
||||
return Base::retError('开启翻译功能需要在应用中开启 ChatGPT AI 机器人。');
|
||||
}
|
||||
if ($all['system_alias'] == env('APP_NAME')) {
|
||||
$all['system_alias'] = '';
|
||||
}
|
||||
$setting = Base::setting('system', Base::newTrim($all));
|
||||
} else {
|
||||
$setting = Base::setting('system');
|
||||
@@ -118,11 +127,11 @@ class SystemController extends AbstractController
|
||||
$setting['chat_information'] = $setting['chat_information'] ?: 'optional';
|
||||
$setting['anon_message'] = $setting['anon_message'] ?: 'open';
|
||||
$setting['voice2text'] = $setting['voice2text'] ?: 'close';
|
||||
$setting['translation'] = $setting['translation'] ?: 'close';
|
||||
$setting['e2e_message'] = $setting['e2e_message'] ?: 'close';
|
||||
$setting['auto_archived'] = $setting['auto_archived'] ?: 'close';
|
||||
$setting['archived_day'] = floatval($setting['archived_day']) ?: 7;
|
||||
$setting['task_visible'] = $setting['task_visible'] ?: 'close';
|
||||
$setting['task_default_time'] = $setting['task_default_time'] ? Base::json2array($setting['task_default_time']) : ['09:00', '18:00'];
|
||||
$setting['all_group_mute'] = $setting['all_group_mute'] ?: 'open';
|
||||
$setting['all_group_autoin'] = $setting['all_group_autoin'] ?: 'yes';
|
||||
$setting['user_private_chat_mute'] = $setting['user_private_chat_mute'] ?: 'open';
|
||||
@@ -133,6 +142,8 @@ class SystemController extends AbstractController
|
||||
$setting['file_upload_limit'] = $setting['file_upload_limit'] ?: '';
|
||||
$setting['unclaimed_task_reminder'] = $setting['unclaimed_task_reminder'] ?: 'close';
|
||||
$setting['unclaimed_task_reminder_time'] = $setting['unclaimed_task_reminder_time'] ?: '';
|
||||
$setting['server_closeai'] = env("SERVER_CLOSEAI") ?: 'open';
|
||||
$setting['server_timezone'] = config('app.timezone');
|
||||
$setting['server_version'] = Base::getVersion();
|
||||
//
|
||||
return Base::retSuccess('success', $setting ?: json_decode('{}'));
|
||||
@@ -173,11 +184,18 @@ class SystemController extends AbstractController
|
||||
'notice_msg',
|
||||
'msg_unread_user_minute',
|
||||
'msg_unread_group_minute',
|
||||
'msg_unread_time_ranges',
|
||||
'ignore_addr'
|
||||
])) {
|
||||
unset($all[$key]);
|
||||
}
|
||||
}
|
||||
$ranges = array_map(function ($item) {
|
||||
return !is_array($item) ? explode(',', $item) : $item;
|
||||
}, is_array($all['msg_unread_time_ranges']) ? $all['msg_unread_time_ranges'] : []);
|
||||
$all['msg_unread_time_ranges'] = array_values(array_filter($ranges, function ($item) {
|
||||
return count($item) == 2 && Timer::isTime($item[0]) && Timer::isTime($item[1]);
|
||||
}));
|
||||
$setting = Base::setting('emailSetting', Base::newTrim($all));
|
||||
} else {
|
||||
$setting = Base::setting('emailSetting');
|
||||
@@ -191,6 +209,7 @@ class SystemController extends AbstractController
|
||||
$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['msg_unread_time_ranges'] = is_array($setting['msg_unread_time_ranges']) ? $setting['msg_unread_time_ranges'] : [[]];
|
||||
$setting['ignore_addr'] = $setting['ignore_addr'] ?: '';
|
||||
//
|
||||
if ($type != 'save' && !in_array('admin', $user->identity)) {
|
||||
@@ -305,41 +324,45 @@ class SystemController extends AbstractController
|
||||
}
|
||||
$backup = $setting;
|
||||
$setting = Base::setting('aibotSetting', Base::newTrim($all));
|
||||
$tempMsg = [
|
||||
'type' => 'content',
|
||||
'content' => '设置成功'
|
||||
];
|
||||
//
|
||||
if ($backup['openai_key'] != $setting['openai_key']) {
|
||||
$botUser = User::botGetOrCreate('ai-openai');
|
||||
if ($botUser && $dialog = WebSocketDialog::checkUserDialog($botUser, $user->userid)) {
|
||||
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'text', ['text' => "设置成功"], $botUser->userid, true, false, true);
|
||||
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'template', $tempMsg, $botUser->userid, true, false, true);
|
||||
}
|
||||
}
|
||||
if ($backup['claude_token'] != $setting['claude_token']) {
|
||||
$botUser = User::botGetOrCreate('ai-claude');
|
||||
if ($botUser && $dialog = WebSocketDialog::checkUserDialog($botUser, $user->userid)) {
|
||||
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'text', ['text' => "设置成功"], $botUser->userid, true, false, true);
|
||||
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'template', $tempMsg, $botUser->userid, true, false, true);
|
||||
}
|
||||
}
|
||||
if ($backup['wenxin_key'] != $setting['wenxin_key']) {
|
||||
$botUser = User::botGetOrCreate('ai-wenxin');
|
||||
if ($botUser && $dialog = WebSocketDialog::checkUserDialog($botUser, $user->userid)) {
|
||||
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'text', ['text' => "设置成功"], $botUser->userid, true, false, true);
|
||||
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'template', $tempMsg, $botUser->userid, true, false, true);
|
||||
}
|
||||
}
|
||||
if ($backup['qianwen_key'] != $setting['qianwen_key']) {
|
||||
$botUser = User::botGetOrCreate('ai-qianwen');
|
||||
if ($botUser && $dialog = WebSocketDialog::checkUserDialog($botUser, $user->userid)) {
|
||||
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'text', ['text' => "设置成功"], $botUser->userid, true, false, true);
|
||||
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'template', $tempMsg, $botUser->userid, true, false, true);
|
||||
}
|
||||
}
|
||||
if ($backup['gemini_key'] != $setting['gemini_key']) {
|
||||
$botUser = User::botGetOrCreate('ai-gemini');
|
||||
if ($botUser && $dialog = WebSocketDialog::checkUserDialog($botUser, $user->userid)) {
|
||||
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'text', ['text' => "设置成功"], $botUser->userid, true, false, true);
|
||||
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'template', $tempMsg, $botUser->userid, true, false, true);
|
||||
}
|
||||
}
|
||||
if ($backup['zhipu_key'] != $setting['zhipu_key']) {
|
||||
$botUser = User::botGetOrCreate('ai-zhipu');
|
||||
if ($botUser && $dialog = WebSocketDialog::checkUserDialog($botUser, $user->userid)) {
|
||||
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'text', ['text' => "设置成功"], $botUser->userid, true, false, true);
|
||||
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'template', $tempMsg, $botUser->userid, true, false, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -393,6 +416,13 @@ class SystemController extends AbstractController
|
||||
'remindin',
|
||||
'remindexceed',
|
||||
'edit',
|
||||
'face_upload',
|
||||
'face_remark',
|
||||
'face_retip',
|
||||
'locat_remark',
|
||||
'locat_bd_lbs_key',
|
||||
'locat_bd_lbs_point', // 格式:{"lng":116.404, "lat":39.915, "radius":500}
|
||||
'manual_remark',
|
||||
'modes',
|
||||
'key',
|
||||
])) {
|
||||
@@ -401,8 +431,28 @@ class SystemController extends AbstractController
|
||||
}
|
||||
if ($all['open'] === 'close') {
|
||||
$all['key'] = md5(Base::generatePassword(32));
|
||||
$all['face_key'] = md5(Base::generatePassword(32));
|
||||
} else {
|
||||
$botUser = User::botGetOrCreate('check-in');
|
||||
if (!$botUser) {
|
||||
return Base::retError('创建签到机器人失败');
|
||||
}
|
||||
if (in_array('locat', $all['modes'])) {
|
||||
if (empty($all['locat_bd_lbs_key'])) {
|
||||
return Base::retError('请填写百度地图AK');
|
||||
}
|
||||
if (!is_array($all['locat_bd_lbs_point'])) {
|
||||
return Base::retError('请选择允许签到位置');
|
||||
}
|
||||
$all['locat_bd_lbs_point']['radius'] = intval($all['locat_bd_lbs_point']['radius']);
|
||||
if (empty($all['locat_bd_lbs_point']['lng']) || empty($all['locat_bd_lbs_point']['lat']) || empty($all['locat_bd_lbs_point']['radius'])) {
|
||||
return Base::retError('请选择有效的签到位置');
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($all['modes']) {
|
||||
$all['modes'] = array_intersect($all['modes'], ['auto', 'manual', 'locat', 'face']);
|
||||
}
|
||||
$all['modes'] = array_intersect($all['modes'], ['auto', 'manual', 'location']);
|
||||
$setting = Base::setting('checkinSetting', Base::newTrim($all));
|
||||
} else {
|
||||
$setting = Base::setting('checkinSetting');
|
||||
@@ -412,8 +462,18 @@ class SystemController extends AbstractController
|
||||
$setting['key'] = md5(Base::generatePassword(32));
|
||||
Base::setting('checkinSetting', $setting);
|
||||
}
|
||||
if (empty($setting['face_key'])) {
|
||||
$setting['face_key'] = md5(Base::generatePassword(32));
|
||||
Base::setting('checkinSetting', $setting);
|
||||
}
|
||||
//
|
||||
$setting['open'] = $setting['open'] ?: 'close';
|
||||
$setting['face_upload'] = $setting['face_upload'] ?: 'close';
|
||||
$setting['face_remark'] = $setting['face_remark'] ?: Doo::translate('考勤机');
|
||||
$setting['face_retip'] = $setting['face_retip'] ?: 'open';
|
||||
$setting['locat_remark'] = $setting['locat_remark'] ?: Doo::translate('定位签到');
|
||||
$setting['locat_bd_lbs_point'] = is_array($setting['locat_bd_lbs_point']) ? $setting['locat_bd_lbs_point'] : ['radius' => 500];
|
||||
$setting['manual_remark'] = $setting['manual_remark'] ?: Doo::translate('手动签到');
|
||||
$setting['time'] = $setting['time'] ? Base::json2array($setting['time']) : ['09:00', '18:00'];
|
||||
$setting['advance'] = intval($setting['advance']) ?: 120;
|
||||
$setting['delay'] = intval($setting['delay']) ?: 120;
|
||||
@@ -545,7 +605,47 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/demo 08. 获取演示帐号
|
||||
* @api {get} api/system/setting/file 08. 文件设置(限管理员)
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
* @apiName setting__file
|
||||
*
|
||||
* @apiParam {String} type
|
||||
* - get: 获取(默认)
|
||||
* - save: 保存设置(参数:['permission_pack_type', 'permission_pack_userids'])
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function setting__file()
|
||||
{
|
||||
User::auth('admin');
|
||||
//
|
||||
$type = trim(Request::input('type'));
|
||||
if ($type == 'save') {
|
||||
if (env("SYSTEM_SETTING") == 'disabled') {
|
||||
return Base::retError('当前环境禁止修改');
|
||||
}
|
||||
$all = Base::newTrim(Request::input());
|
||||
foreach ($all as $key => $value) {
|
||||
if (!in_array($key, [
|
||||
'permission_pack_type',
|
||||
'permission_pack_userids'
|
||||
])) {
|
||||
unset($all[$key]);
|
||||
}
|
||||
}
|
||||
$setting = Base::setting('fileSetting', Base::newTrim($all));
|
||||
} else {
|
||||
$setting = Base::setting('fileSetting');
|
||||
}
|
||||
//
|
||||
return Base::retSuccess('success', $setting ?: json_decode('{}'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/demo 09. 获取演示帐号
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
@@ -569,7 +669,7 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} api/system/priority 09. 任务优先级
|
||||
* @api {post} api/system/priority 10. 任务优先级
|
||||
*
|
||||
* @apiDescription 获取任务优先级、保存任务优先级
|
||||
* @apiVersion 1.0.0
|
||||
@@ -618,7 +718,7 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} api/system/column/template 10. 创建项目模板
|
||||
* @api {post} api/system/column/template 11. 创建项目模板
|
||||
*
|
||||
* @apiDescription 获取创建项目模板、保存创建项目模板
|
||||
* @apiVersion 1.0.0
|
||||
@@ -665,7 +765,7 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} api/system/license 11. License
|
||||
* @api {post} api/system/license 12. License
|
||||
*
|
||||
* @apiDescription 获取License信息、保存License(限管理员)
|
||||
* @apiVersion 1.0.0
|
||||
@@ -720,7 +820,7 @@ class SystemController extends AbstractController
|
||||
if ($data['info']['people'] > 0 && $data['user_count'] > $data['info']['people']) {
|
||||
$data['error'][] = '终端用户数超过License限制';
|
||||
}
|
||||
if ($data['info']['expired_at'] && strtotime($data['info']['expired_at']) <= Base::time()) {
|
||||
if ($data['info']['expired_at'] && strtotime($data['info']['expired_at']) <= Timer::time()) {
|
||||
$data['error'][] = '终端License已过期';
|
||||
}
|
||||
//
|
||||
@@ -728,7 +828,7 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/get/info 12. 获取终端详细信息
|
||||
* @api {get} api/system/get/info 13. 获取终端详细信息
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
@@ -757,7 +857,7 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/get/ip 13. 获取IP地址
|
||||
* @api {get} api/system/get/ip 14. 获取IP地址
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
@@ -772,7 +872,7 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/get/cnip 14. 是否中国IP地址
|
||||
* @api {get} api/system/get/cnip 15. 是否中国IP地址
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
@@ -789,7 +889,7 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/get/ipgcj02 15. 获取IP地址经纬度
|
||||
* @api {get} api/system/get/ipgcj02 16. 获取IP地址经纬度
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
@@ -806,7 +906,7 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/get/ipinfo 16. 获取IP地址详细信息
|
||||
* @api {get} api/system/get/ipinfo 17. 获取IP地址详细信息
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
@@ -823,7 +923,7 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} api/system/imgupload 17. 上传图片
|
||||
* @api {post} api/system/imgupload 18. 上传图片
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
@@ -835,10 +935,10 @@ class SystemController extends AbstractController
|
||||
* @apiParam {String} filename post-文件名
|
||||
* @apiParam {Number} [width] 压缩图片宽(默认0)
|
||||
* @apiParam {Number} [height] 压缩图片高(默认0)
|
||||
* @apiParam {String} [whcut] 压缩方式
|
||||
* - 1:裁切(默认,宽、高非0有效)
|
||||
* - 0:缩放
|
||||
* - -1或'auto':保持等比裁切
|
||||
* @apiParam {String} [whcut] 压缩方式(等比缩放)
|
||||
* - cover:完全覆盖容器,可能图片部分不可见(width、height必须大于0)
|
||||
* - contain:完全装入容器,可能容器部分显示空白(width、height必须大于0)
|
||||
* - percentage:完全装入容器,可能容器有一边尺寸不足(默认,假如:width=200、height=0,则宽度最大不超过200、高度自动)
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
@@ -851,11 +951,15 @@ class SystemController extends AbstractController
|
||||
}
|
||||
$width = intval(Request::input('width'));
|
||||
$height = intval(Request::input('height'));
|
||||
$whcut = intval(Request::input('whcut', 1));
|
||||
$scale = [2160, 4160, -1];
|
||||
if ($width > 0 || $height > 0) {
|
||||
$scale = [$width, $height, $whcut];
|
||||
}
|
||||
$whcut = Request::input('whcut');
|
||||
$whcut = match (strval($whcut)) {
|
||||
'1' => 'cover',
|
||||
'0' => 'contain',
|
||||
'cover',
|
||||
'contain' => $whcut,
|
||||
default => 'percentage',
|
||||
};
|
||||
$scale = [$width ?: 2160, $height ?: 4160, $whcut];
|
||||
$path = "uploads/user/picture/" . User::userid() . "/" . date("Ym") . "/";
|
||||
$image64 = trim(Request::input('image64'));
|
||||
$fileName = trim(Request::input('filename'));
|
||||
@@ -864,7 +968,8 @@ class SystemController extends AbstractController
|
||||
"image64" => $image64,
|
||||
"path" => $path,
|
||||
"fileName" => $fileName,
|
||||
"scale" => $scale
|
||||
"scale" => $scale,
|
||||
"quality" => 85
|
||||
]);
|
||||
} else {
|
||||
$data = Base::upload([
|
||||
@@ -872,7 +977,8 @@ class SystemController extends AbstractController
|
||||
"type" => 'image',
|
||||
"path" => $path,
|
||||
"fileName" => $fileName,
|
||||
"scale" => $scale
|
||||
"scale" => $scale,
|
||||
"quality" => 100
|
||||
]);
|
||||
}
|
||||
if (Base::isError($data)) {
|
||||
@@ -883,7 +989,7 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/get/imgview 18. 浏览图片空间
|
||||
* @api {get} api/system/get/imgview 19. 浏览图片空间
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
@@ -951,7 +1057,7 @@ class SystemController extends AbstractController
|
||||
];
|
||||
//
|
||||
$extension = pathinfo($dirPath . $filename, PATHINFO_EXTENSION);
|
||||
if (in_array($extension, array('gif', 'jpg', 'jpeg', 'webp', 'png', 'bmp'))) {
|
||||
if (in_array($extension, ['png', 'jpg', 'jpeg', 'gif', 'webp', 'bmp'])) {
|
||||
if ($extension = Base::getThumbExt($dirPath . $filename)) {
|
||||
$array['thumb'] .= "_thumb.{$extension}";
|
||||
} else {
|
||||
@@ -980,7 +1086,7 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} api/system/fileupload 19. 上传文件
|
||||
* @api {post} api/system/fileupload 20. 上传文件
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
@@ -1008,6 +1114,7 @@ class SystemController extends AbstractController
|
||||
"image64" => $image64,
|
||||
"path" => $path,
|
||||
"fileName" => $fileName,
|
||||
"quality" => 85
|
||||
]);
|
||||
} else {
|
||||
$data = Base::upload([
|
||||
@@ -1015,6 +1122,7 @@ class SystemController extends AbstractController
|
||||
"type" => 'file',
|
||||
"path" => $path,
|
||||
"fileName" => $fileName,
|
||||
"quality" => 100
|
||||
]);
|
||||
}
|
||||
//
|
||||
@@ -1022,36 +1130,50 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/get/updatelog 20. 获取更新日志
|
||||
* @api {get} api/system/get/updatelog 21. 获取更新日志
|
||||
*
|
||||
* @apiDescription 获取更新日志
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
* @apiName get__updatelog
|
||||
*
|
||||
* @apiParam {Number} [take] 获取数量:10-100(留空默认:50)
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function get__updatelog()
|
||||
{
|
||||
$take = min(100, max(10, intval(Request::input('take', 50))));
|
||||
$logPath = base_path('CHANGELOG.md');
|
||||
$logContent = "";
|
||||
$logVersion = "";
|
||||
$logContent = "";
|
||||
$logResults = [];
|
||||
if (file_exists($logPath)) {
|
||||
$logContent = file_get_contents($logPath);
|
||||
preg_match("/## \[(.*?)\]/", $logContent, $matchs);
|
||||
if ($matchs) {
|
||||
$logVersion = $matchs[1] === "Unreleased" ? $matchs[1] : "v{$matchs[1]}";
|
||||
$content = file_get_contents($logPath);
|
||||
$sections = preg_split("/## \[(.*?)\]/", $content, -1, PREG_SPLIT_DELIM_CAPTURE);
|
||||
for ($i = 1; $i < count($sections) && count($logResults) < $take; $i += 2) {
|
||||
$logResults[] = [
|
||||
'title' => $sections[$i],
|
||||
'content' => $sections[$i + 1]
|
||||
];
|
||||
}
|
||||
}
|
||||
if ($logResults) {
|
||||
$logVersion = $logResults[0]['title'];
|
||||
$logContent = implode("\n", array_map(function($item) {
|
||||
return "## [{$item['title']}]" . $item['content'];
|
||||
}, $logResults));
|
||||
}
|
||||
return Base::retSuccess('success', [
|
||||
'updateLog' => $logContent ?: false,
|
||||
'logVersion' => $logVersion,
|
||||
'updateLog' => $logContent,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/email/check 21. 邮件发送测试(限管理员)
|
||||
* @api {get} api/system/email/check 22. 邮件发送测试(限管理员)
|
||||
*
|
||||
* @apiDescription 测试配置邮箱是否能发送邮件
|
||||
* @apiVersion 1.0.0
|
||||
@@ -1075,10 +1197,10 @@ class SystemController extends AbstractController
|
||||
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']}>")
|
||||
->from(Base::settingFind('system', 'system_alias', 'Task') . " <{$all['account']}>")
|
||||
->to($to)
|
||||
->subject('Mail sending test')
|
||||
->html('<p>收到此电子邮件意味着您的邮箱配置正确。</p><p>Receiving this email means that your mailbox is configured correctly.</p>'))
|
||||
->html('<p>' . Doo::translate('收到此电子邮件意味着您的邮箱配置正确。') . '</p>'))
|
||||
->send();
|
||||
}, function () {
|
||||
throw new \Exception("收件人地址错误或已被忽略");
|
||||
@@ -1087,7 +1209,7 @@ class SystemController extends AbstractController
|
||||
} catch (\Throwable $e) {
|
||||
// 一般是请求超时
|
||||
if (str_contains($e->getMessage(), "Timed Out")) {
|
||||
return Base::retError("language.TimedOut");
|
||||
return Base::retError("邮件发送超时,请检查邮箱配置是否正确");
|
||||
} elseif ($e->getCode() === 550) {
|
||||
return Base::retError('邮件内容被拒绝,请检查邮箱是否开启接收功能');
|
||||
} else {
|
||||
@@ -1097,7 +1219,7 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/checkin/export 22. 导出签到数据(限管理员)
|
||||
* @api {get} api/system/checkin/export 23. 导出签到数据(限管理员)
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
@@ -1130,13 +1252,13 @@ class SystemController extends AbstractController
|
||||
if (count($userid) > 100) {
|
||||
return Base::retError('导出成员限制最多100个');
|
||||
}
|
||||
if (!(is_array($date) && Base::isDate($date[0]) && Base::isDate($date[1]))) {
|
||||
if (!(is_array($date) && Timer::isDate($date[0]) && Timer::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]))) {
|
||||
if (!(is_array($time) && Timer::isTime($time[0]) && Timer::isTime($time[1]))) {
|
||||
return Base::retError('时间选择错误');
|
||||
}
|
||||
//
|
||||
@@ -1144,14 +1266,14 @@ class SystemController extends AbstractController
|
||||
$secondEnd = strtotime("2000-01-01 {$time[1]}") - strtotime("2000-01-01 00:00:00");
|
||||
//
|
||||
$headings = [];
|
||||
$headings[] = '签到人';
|
||||
$headings[] = '签到日期';
|
||||
$headings[] = '班次时间';
|
||||
$headings[] = '首次签到时间';
|
||||
$headings[] = '首次签到结果';
|
||||
$headings[] = '最后签到时间';
|
||||
$headings[] = '最后签到结果';
|
||||
$headings[] = '参数数据';
|
||||
$headings[] = Doo::translate('签到人');
|
||||
$headings[] = Doo::translate('签到日期');
|
||||
$headings[] = Doo::translate('班次时间');
|
||||
$headings[] = Doo::translate('首次签到时间');
|
||||
$headings[] = Doo::translate('首次签到结果');
|
||||
$headings[] = Doo::translate('最后签到时间');
|
||||
$headings[] = Doo::translate('最后签到结果');
|
||||
$headings[] = Doo::translate('参数数据');
|
||||
//
|
||||
$sheets = [];
|
||||
$startD = Carbon::parse($date[0])->startOfDay();
|
||||
@@ -1178,28 +1300,28 @@ class SystemController extends AbstractController
|
||||
$lastRecord = $sameCollect?->whereBetween("datetime", $lastBetween)->last();
|
||||
$firstTimestamp = $firstRecord['timestamp'] ?: 0;
|
||||
$lastTimestamp = $lastRecord['timestamp'] ?: 0;
|
||||
if (Base::time() < $startT + $secondStart) {
|
||||
if (Timer::time() < $startT + $secondStart) {
|
||||
$firstResult = "-";
|
||||
} else {
|
||||
$firstResult = "正常";
|
||||
$firstResult = Doo::translate("正常");
|
||||
if (empty($firstTimestamp)) {
|
||||
$firstResult = "缺卡";
|
||||
$firstResult = Doo::translate("缺卡");
|
||||
$styles["E{$index}"] = ["font" => ["color" => ["rgb" => "ff0000"]]];
|
||||
} elseif ($firstTimestamp > $startT + $secondStart) {
|
||||
$firstResult = "迟到";
|
||||
$firstResult = Doo::translate("迟到");
|
||||
$styles["E{$index}"] = ["font" => ["color" => ["rgb" => "436FF6"]]];
|
||||
}
|
||||
}
|
||||
if (Base::time() < $startT + $secondEnd) {
|
||||
if (Timer::time() < $startT + $secondEnd) {
|
||||
$lastResult = "-";
|
||||
$lastTimestamp = 0;
|
||||
} else {
|
||||
$lastResult = "正常";
|
||||
$lastResult = Doo::translate("正常");
|
||||
if (empty($lastTimestamp) || $lastTimestamp === $firstTimestamp) {
|
||||
$lastResult = "缺卡";
|
||||
$lastResult = Doo::translate("缺卡");
|
||||
$styles["G{$index}"] = ["font" => ["color" => ["rgb" => "ff0000"]]];
|
||||
} elseif ($lastTimestamp < $startT + $secondEnd) {
|
||||
$lastResult = "早退";
|
||||
$lastResult = Doo::translate("早退");
|
||||
$styles["G{$index}"] = ["font" => ["color" => ["rgb" => "436FF6"]]];
|
||||
}
|
||||
}
|
||||
@@ -1229,10 +1351,12 @@ class SystemController extends AbstractController
|
||||
//
|
||||
$fileName = $users[0]->nickname;
|
||||
if (count($users) > 1) {
|
||||
$fileName .= "等" . count($userid) . "位成员";
|
||||
$fileName .= "等" . count($userid) . "位成员的签到记录";
|
||||
} else {
|
||||
$fileName .= '的签到记录';
|
||||
}
|
||||
$fileName .= '签到记录_' . Base::time() . '.xlsx';
|
||||
$filePath = "temp/checkin/export/" . date("Ym", Base::time());
|
||||
$fileName = Doo::translate($fileName) . '_' . Timer::time() . '.xlsx';
|
||||
$filePath = "temp/checkin/export/" . date("Ym", Timer::time());
|
||||
$export = new BillMultipleExport($sheets);
|
||||
$res = $export->store($filePath . "/" . $fileName);
|
||||
if ($res != 1) {
|
||||
@@ -1264,7 +1388,7 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/checkin/down 23. 下载导出的签到数据
|
||||
* @api {get} api/system/checkin/down 24. 下载导出的签到数据
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
@@ -1290,7 +1414,7 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/version 24. 获取版本号
|
||||
* @api {get} api/system/version 25. 获取版本号
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
@@ -1327,7 +1451,7 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/prefetch 25. 预加载的资源
|
||||
* @api {get} api/system/prefetch 26. 预加载的资源
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
@@ -1350,9 +1474,9 @@ class SystemController extends AbstractController
|
||||
|
||||
if ($isMain || $isApp) {
|
||||
$path = 'js/build/';
|
||||
$list = Base::readDir(public_path($path), false);
|
||||
$list = Base::recursiveFiles(public_path($path), false);
|
||||
foreach ($list as $item) {
|
||||
if (is_file($item) && filesize($item) > 50 * 1024) {
|
||||
if (is_file($item) && filesize($item) > 50 * 1024) { // 50KB
|
||||
$array[] = $path . basename($item);
|
||||
}
|
||||
}
|
||||
@@ -1365,6 +1489,27 @@ class SystemController extends AbstractController
|
||||
$items = explode("\n", $content);
|
||||
$array = array_merge($array, $items);
|
||||
}
|
||||
// 添加office资源
|
||||
$officePath = '';
|
||||
$officeApi = 'http://' . env('APP_IPPR') . '.6/web-apps/apps/api/documents/api.js';
|
||||
$content = @file_get_contents($officeApi);
|
||||
if ($content) {
|
||||
if (preg_match("/const\s+ver\s*=\s*'\/*([^']+)'/", $content, $matches)) {
|
||||
$officePath = $matches[1];
|
||||
}
|
||||
}
|
||||
if ($officePath) {
|
||||
$array = array_map(function($item) use ($officePath) {
|
||||
if (str_starts_with($item, 'office/{path}/')) {
|
||||
return preg_replace("/office\/{path}\//", '/office/' . $officePath . '/', $item);
|
||||
}
|
||||
return $item;
|
||||
}, $array);
|
||||
} else {
|
||||
$array = array_filter($array, function($item) {
|
||||
return !str_starts_with($item, 'office/{path}/');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return array_map(function($item) use ($version) {
|
||||
|
||||
@@ -11,6 +11,7 @@ use App\Module\Doo;
|
||||
use App\Models\File;
|
||||
use App\Models\User;
|
||||
use App\Module\Base;
|
||||
use App\Module\Timer;
|
||||
use App\Ldap\LdapUser;
|
||||
use App\Models\Meeting;
|
||||
use App\Models\Project;
|
||||
@@ -20,6 +21,7 @@ use App\Models\UmengAlias;
|
||||
use App\Models\UserDelete;
|
||||
use App\Models\UserTransfer;
|
||||
use App\Models\AbstractModel;
|
||||
use App\Models\UserCheckinFace;
|
||||
use App\Models\UserCheckinMac;
|
||||
use App\Models\UserDepartment;
|
||||
use App\Models\WebSocketDialog;
|
||||
@@ -40,7 +42,6 @@ class UsersController extends AbstractController
|
||||
/**
|
||||
* @api {get} api/users/login 01. 登录、注册
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup users
|
||||
* @apiName login
|
||||
@@ -129,7 +130,7 @@ class UsersController extends AbstractController
|
||||
return $retError('帐号或密码错误');
|
||||
}
|
||||
//
|
||||
if (in_array('disable', $user->identity)) {
|
||||
if ($user->isDisable()) {
|
||||
return $retError('帐号已停用...');
|
||||
}
|
||||
Cache::forget("code::" . $email);
|
||||
@@ -330,8 +331,7 @@ class UsersController extends AbstractController
|
||||
$data = $user->toArray();
|
||||
$data['nickname_original'] = $user->getRawOriginal('nickname');
|
||||
$data['department_name'] = $user->getDepartmentName();
|
||||
// 适用默认部门下第1级负责人才能添加部门OKR
|
||||
$data['department_owner'] = UserDepartment::where('parent_id',0)->where('owner_userid', $user->userid())->exists();
|
||||
$data['department_owner'] = UserDepartment::where('parent_id',0)->where('owner_userid', $user->userid)->exists(); // 适用默认部门下第1级负责人才能添加部门OKR
|
||||
return Base::retSuccess('success', $data);
|
||||
}
|
||||
|
||||
@@ -347,6 +347,7 @@ class UsersController extends AbstractController
|
||||
* @apiParam {String} [tel] 电话
|
||||
* @apiParam {String} [nickname] 昵称
|
||||
* @apiParam {String} [profession] 职位/职称
|
||||
* @apiParam {String} [lang] 语言(比如:zh/en)
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
@@ -409,6 +410,15 @@ class UsersController extends AbstractController
|
||||
$upLdap['employeeType'] = $profession;
|
||||
}
|
||||
}
|
||||
// 语言
|
||||
if (Arr::exists($data, 'lang')) {
|
||||
$lang = trim(Request::input('lang'));
|
||||
if (!Doo::checkLanguage($lang)) {
|
||||
return Base::retError('语言错误');
|
||||
} else {
|
||||
$user->lang = $lang;
|
||||
}
|
||||
}
|
||||
//
|
||||
$user->save();
|
||||
User::generateToken($user);
|
||||
@@ -670,9 +680,13 @@ class UsersController extends AbstractController
|
||||
* - all: 全部
|
||||
* - 其他值: 非机器人(默认)
|
||||
* - keys.department 部门ID(0表示默认部门,不赋值获取所有部门)
|
||||
* - keys.checkin_mac 签到mac地址(get_checkin_mac=1时有效)
|
||||
* - keys.checkin_face 人脸图片(get_checkin_data=1时有效)
|
||||
* - yes: 仅有人脸图片
|
||||
* - no: 无人脸图片
|
||||
* - all: 全部
|
||||
* - keys.checkin_mac 签到mac地址(get_checkin_data=1时有效)
|
||||
*
|
||||
* @apiParam {Number} [get_checkin_mac] 获取签到mac地址
|
||||
* @apiParam {Number} [get_checkin_data] 获取签到mac地址
|
||||
* - 0: 不获取(默认)
|
||||
* - 1: 获取
|
||||
* @apiParam {Number} [page] 当前页,默认:1
|
||||
@@ -689,7 +703,7 @@ class UsersController extends AbstractController
|
||||
$builder = User::select(['*', 'nickname as nickname_original']);
|
||||
//
|
||||
$keys = Request::input('keys');
|
||||
$getCheckinMac = intval(Request::input('get_checkin_mac')) === 1;
|
||||
$getCheckinData = intval(Request::input('get_checkin_data')) === 1;
|
||||
if (is_array($keys)) {
|
||||
if ($keys['key']) {
|
||||
if (str_contains($keys['key'], "@")) {
|
||||
@@ -757,10 +771,17 @@ class UsersController extends AbstractController
|
||||
$builder->orderBy("is_principal","desc");
|
||||
}
|
||||
}
|
||||
if ($getCheckinMac && isset($keys['checkin_mac'])) {
|
||||
$builder->whereIn('userid', function ($query) use ($keys) {
|
||||
$query->select('userid')->from('user_checkin_macs')->where("mac", "like", "%{$keys['checkin_mac']}%");
|
||||
});
|
||||
if ($getCheckinData) {
|
||||
if (isset($keys['checkin_face'])) {
|
||||
$builder->whereIn('userid', function ($query) use ($keys) {
|
||||
$query->select('userid')->from('user_checkin_faces')->whereNotNull("faceimg");
|
||||
});
|
||||
}
|
||||
if (isset($keys['checkin_mac'])) {
|
||||
$builder->whereIn('userid', function ($query) use ($keys) {
|
||||
$query->select('userid')->from('user_checkin_macs')->where("mac", "like", "%{$keys['checkin_mac']}%");
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$builder->whereNull('disable_at');
|
||||
@@ -768,11 +789,11 @@ class UsersController extends AbstractController
|
||||
}
|
||||
$list = $builder->orderByDesc('userid')->paginate(Base::getPaginate(50, 20));
|
||||
//
|
||||
if ($getCheckinMac) {
|
||||
$list->transform(function (User $user) use ($getCheckinMac) {
|
||||
if ($getCheckinMac) {
|
||||
$user->checkin_macs = UserCheckinMac::select(['id', 'mac', 'remark'])->whereUserid($user->userid)->orderBy('id')->get();
|
||||
}
|
||||
if ($getCheckinData) {
|
||||
$list->transform(function (User $user) {
|
||||
$checkinFace = UserCheckinFace::select(['faceimg'])->whereUserid($user->userid)->first();
|
||||
$user->checkin_face = $checkinFace ? Base::fillUrl($checkinFace->faceimg) : '';
|
||||
$user->checkin_macs = UserCheckinMac::select(['id', 'mac', 'remark'])->whereUserid($user->userid)->orderBy('id')->get();
|
||||
return $user;
|
||||
});
|
||||
}
|
||||
@@ -795,6 +816,7 @@ class UsersController extends AbstractController
|
||||
* - settemp 设为临时帐号
|
||||
* - cleartemp 取消临时身份(取消临时帐号)
|
||||
* - checkin_macs 修改自动签到mac地址(需要参数 checkin_macs)
|
||||
* - checkin_face 修改签到人脸图片(需要参数 checkin_face)
|
||||
* - department 修改部门(需要参数 department)
|
||||
* - setdisable 设为离职(需要参数 disable_time、transfer_userid)
|
||||
* - cleardisable 取消离职
|
||||
@@ -805,6 +827,7 @@ class UsersController extends AbstractController
|
||||
* @apiParam {String} [nickname] 昵称
|
||||
* @apiParam {String} [profession] 职位
|
||||
* @apiParam {String} [checkin_macs] 自动签到mac地址
|
||||
* @apiParam {String} [checkin_face] 人脸图片地址
|
||||
* @apiParam {String} [department] 部门
|
||||
* @apiParam {String} [disable_time] 离职时间
|
||||
* @apiParam {String} [transfer_userid] 离职交接人
|
||||
@@ -869,6 +892,11 @@ class UsersController extends AbstractController
|
||||
}
|
||||
return UserCheckinMac::saveMac($userInfo->userid, $array);
|
||||
|
||||
case 'checkin_face':
|
||||
$faceimg = $data['checkin_face'] ? $data['checkin_face'] : '';
|
||||
|
||||
return UserCheckinFace::saveFace($userInfo->userid, $userInfo->nickname, $faceimg, "管理员上传");
|
||||
|
||||
case 'department':
|
||||
if (!is_array($data['department'])) {
|
||||
$data['department'] = [];
|
||||
@@ -900,7 +928,7 @@ class UsersController extends AbstractController
|
||||
if ($transferUser->userid === $userInfo->userid) {
|
||||
return Base::retError('不能移交给自己');
|
||||
}
|
||||
if (in_array('disable', $transferUser->identity)) {
|
||||
if ($transferUser->isDisable()) {
|
||||
return Base::retError('交接人已离职,请选择另一个交接人');
|
||||
}
|
||||
break;
|
||||
@@ -1063,7 +1091,7 @@ class UsersController extends AbstractController
|
||||
return Base::retError('链接已经使用过', ['code' => 2]);
|
||||
|
||||
$oldTime = Carbon::parse($res->created_at)->timestamp;
|
||||
$time = Base::Time();
|
||||
$time = Timer::Time();
|
||||
|
||||
// 30分钟失效
|
||||
if (abs($time - $oldTime) > 1800) {
|
||||
@@ -1087,6 +1115,9 @@ class UsersController extends AbstractController
|
||||
* @apiGroup users
|
||||
* @apiName umeng__alias
|
||||
*
|
||||
* @apiParam {String} action
|
||||
* - update: 更新(默认)
|
||||
* - remove: 删除
|
||||
* @apiParam {String} alias 别名
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
@@ -1102,6 +1133,13 @@ class UsersController extends AbstractController
|
||||
'alias.between:2,20' => '别名的长度在2-20个字符',
|
||||
]);
|
||||
//
|
||||
if ($data['action'] === 'remove') {
|
||||
if ($data['alias']) {
|
||||
UmengAlias::whereAlias($data['alias'])->delete();
|
||||
}
|
||||
return Base::retSuccess('删除成功');
|
||||
}
|
||||
//
|
||||
if (!in_array(Base::platform(), ['ios', 'android'])) {
|
||||
return Base::retError('设备类型错误');
|
||||
}
|
||||
@@ -1147,6 +1185,7 @@ class UsersController extends AbstractController
|
||||
* @apiParam {String} [name] 会话ID
|
||||
* @apiParam {String} [sharekey] 分享的key
|
||||
* @apiParam {String} [username] 用户名称
|
||||
* @apiParam {String} [userimg] 用户头像
|
||||
* @apiParam {Array} [userids] 邀请成员
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
@@ -1161,6 +1200,7 @@ class UsersController extends AbstractController
|
||||
$userids = Request::input('userids');
|
||||
$sharekey = trim(Request::input('sharekey'));
|
||||
$username = trim(Request::input('username'));
|
||||
$userimg = trim(Request::input('userimg')) ?: Base::fillUrl('avatar/' . $username . '.png');
|
||||
$user = null;
|
||||
if (!empty($sharekey) && $type === 'join') {
|
||||
if (!Meeting::getShareInfo($sharekey)) {
|
||||
@@ -1183,7 +1223,7 @@ class UsersController extends AbstractController
|
||||
$meeting->save();
|
||||
} elseif ($type === 'create') {
|
||||
$meetingid = strtoupper(Base::generatePassword(11, 1));
|
||||
$name = $name ?: "{$user?->nickname} 发起的会议";
|
||||
$name = $name ?: Doo::translate("{$user?->nickname} 发起的会议");
|
||||
$channel = "DooTask:" . substr(md5($meetingid . env("APP_KEY")), 16);
|
||||
$meeting = Meeting::createInstance([
|
||||
'meetingid' => $meetingid,
|
||||
@@ -1238,7 +1278,7 @@ class UsersController extends AbstractController
|
||||
//
|
||||
$data['appid'] = $meetingSetting['appid'];
|
||||
$data['uid'] = $uid;
|
||||
$data['userimg'] = $sharekey ? Base::fillUrl('avatar/' . $username . '.png') : $user?->userimg;
|
||||
$data['userimg'] = $sharekey ? $userimg : $user?->userimg;
|
||||
$data['nickname'] = $sharekey ? $username : $user?->nickname;
|
||||
$data['token'] = $token;
|
||||
$data['msgs'] = $msgs;
|
||||
@@ -1643,8 +1683,14 @@ class UsersController extends AbstractController
|
||||
$user = User::auth();
|
||||
//
|
||||
$list = UserCheckinMac::whereUserid($user->userid)->orderBy('id')->get();
|
||||
$userface = UserCheckinFace::whereUserid($user->userid)->first();
|
||||
|
||||
$data = [
|
||||
'list' => $list,
|
||||
'faceimg' => $userface ? Base::fillUrl($userface->faceimg) : ''
|
||||
];
|
||||
//
|
||||
return Base::retSuccess('success', $list);
|
||||
return Base::retSuccess('success', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1655,7 +1701,11 @@ class UsersController extends AbstractController
|
||||
* @apiGroup users
|
||||
* @apiName checkin__save
|
||||
*
|
||||
* @apiParam {Array} list 优先级数据,格式:[{mac,remark}]
|
||||
* @apiParam {String} type 类型
|
||||
* - face: 人脸识别设置
|
||||
* - mac: MAC设置
|
||||
* @apiParam {String} faceimg 人脸图片地址
|
||||
* @apiParam {Array} list 优先级数据,格式:[{mac,remark}]
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
@@ -1669,30 +1719,53 @@ class UsersController extends AbstractController
|
||||
if ($setting['open'] !== 'open') {
|
||||
return Base::retError('此功能未开启,请联系管理员开启');
|
||||
}
|
||||
if ($setting['edit'] !== 'open') {
|
||||
return Base::retError('未开放修改权限,请联系管理员');
|
||||
}
|
||||
//
|
||||
$type = Request::input('type');
|
||||
$list = Request::input('list');
|
||||
$array = [];
|
||||
if (empty($list) || !is_array($list)) {
|
||||
return Base::retError('参数错误');
|
||||
}
|
||||
foreach ($list AS $item) {
|
||||
$item = Base::newTrim($item);
|
||||
if (Base::isMac($item['mac'])) {
|
||||
$mac = strtoupper($item['mac']);
|
||||
$array[$mac] = [
|
||||
'mac' => $mac,
|
||||
'remark' => substr($item['remark'], 0, 50),
|
||||
];
|
||||
}
|
||||
}
|
||||
if (count($array) > 3) {
|
||||
return Base::retError('最多只能添加3个MAC地址');
|
||||
$faceimg = Request::input('faceimg');
|
||||
//
|
||||
$data = [
|
||||
'list' => $list,
|
||||
'faceimg' => $faceimg
|
||||
];
|
||||
switch ($type) {
|
||||
case 'face':
|
||||
if ($setting['face_upload'] !== 'open') {
|
||||
return Base::retError('未开放修改权限,请联系管理员');
|
||||
}
|
||||
UserCheckinFace::saveFace($user->userid, $user->nickname(), $faceimg, "用户上传");
|
||||
break;
|
||||
|
||||
case 'mac':
|
||||
if ($setting['edit'] !== 'open') {
|
||||
return Base::retError('未开放修改权限,请联系管理员');
|
||||
}
|
||||
$array = [];
|
||||
if (empty($list) || !is_array($list)) {
|
||||
return Base::retError('参数错误');
|
||||
}
|
||||
foreach ($list as $item) {
|
||||
$item = Base::newTrim($item);
|
||||
if (Base::isMac($item['mac'])) {
|
||||
$mac = strtoupper($item['mac']);
|
||||
$array[$mac] = [
|
||||
'mac' => $mac,
|
||||
'remark' => substr($item['remark'], 0, 50),
|
||||
];
|
||||
}
|
||||
}
|
||||
if (count($array) > 3) {
|
||||
return Base::retError('最多只能添加3个MAC地址');
|
||||
}
|
||||
$saveMacRes = UserCheckinMac::saveMac($user->userid, $array);
|
||||
$data['list'] = $saveMacRes['data'];
|
||||
break;
|
||||
|
||||
default:
|
||||
return Base::retError('参数错误');
|
||||
}
|
||||
//
|
||||
return UserCheckinMac::saveMac($user->userid, $array);
|
||||
return Base::retSuccess('修改成功', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2044,24 +2117,22 @@ class UsersController extends AbstractController
|
||||
{
|
||||
$user = User::auth();
|
||||
//
|
||||
global $_A;
|
||||
if (!isset($_A["__annual__report_".$user->userid])) {
|
||||
$year = '2023';
|
||||
$time = '2300-01-01 00:00:01';
|
||||
$prefix = \DB::getTablePrefix();
|
||||
$hireTimestamp = strtotime($user->created_at);
|
||||
DB::statement("SET SQL_MODE=''");
|
||||
$year = '2023';
|
||||
$time = '2300-01-01 00:00:01';
|
||||
$prefix = \DB::getTablePrefix();
|
||||
$hireTimestamp = strtotime($user->created_at);
|
||||
DB::statement("SET SQL_MODE=''");
|
||||
|
||||
// 我的任务
|
||||
$taskDb = DB::table('project_tasks as t')
|
||||
->join('project_task_users as tu', 't.id', '=', 'tu.task_id')
|
||||
->where('tu.owner', 1)
|
||||
->whereYear('t.created_at', $year)
|
||||
->where('tu.userid', $user->userid);
|
||||
// 我的任务
|
||||
$taskDb = DB::table('project_tasks as t')
|
||||
->join('project_task_users as tu', 't.id', '=', 'tu.task_id')
|
||||
->where('tu.owner', 1)
|
||||
->whereYear('t.created_at', $year)
|
||||
->where('tu.userid', $user->userid);
|
||||
|
||||
// 我的任务 - 时长(分钟)
|
||||
$durationTaskDb = $taskDb->clone()
|
||||
->selectRaw("
|
||||
// 我的任务 - 时长(分钟)
|
||||
$durationTaskDb = $taskDb->clone()
|
||||
->selectRaw("
|
||||
{$prefix}t.id,
|
||||
{$prefix}t.flow_item_name,
|
||||
{$prefix}t.name as task_name,
|
||||
@@ -2073,14 +2144,14 @@ class UsersController extends AbstractController
|
||||
{$prefix}t.created_at,
|
||||
ifnull(TIMESTAMPDIFF(MINUTE, {$prefix}t.start_at, {$prefix}t.complete_at), 0) as duration
|
||||
")
|
||||
->leftJoin('projects as p', 'p.id', '=', 't.project_id')
|
||||
->leftJoin('project_columns as c', 'c.id', '=', 't.column_id')
|
||||
->whereNotNull('t.start_at')
|
||||
->whereNotNull('t.complete_at');
|
||||
->leftJoin('projects as p', 'p.id', '=', 't.project_id')
|
||||
->leftJoin('project_columns as c', 'c.id', '=', 't.column_id')
|
||||
->whereNotNull('t.start_at')
|
||||
->whereNotNull('t.complete_at');
|
||||
|
||||
// 最多聊天用户
|
||||
$longestChat = DB::table('web_socket_dialogs as d')
|
||||
->selectRaw("
|
||||
// 最多聊天用户
|
||||
$longestChat = DB::table('web_socket_dialogs as d')
|
||||
->selectRaw("
|
||||
{$prefix}d.id,
|
||||
{$prefix}d.name as dialog_name,
|
||||
{$prefix}d.type as dialog_type,
|
||||
@@ -2091,113 +2162,112 @@ class UsersController extends AbstractController
|
||||
{$prefix}u.nickname as user_nickname,
|
||||
ifnull({$prefix}d.avatar, {$prefix}u.userimg) as avatar
|
||||
")
|
||||
->leftJoinSub(function ($query) use ($user, $year) {
|
||||
$query->select('web_socket_dialog_msgs.dialog_id', DB::raw('count(*) as chat_num'))
|
||||
->from('web_socket_dialog_msgs')
|
||||
->where('web_socket_dialog_msgs.userid', $user->userid)
|
||||
->whereYear('web_socket_dialog_msgs.created_at', $year)
|
||||
->groupBy('web_socket_dialog_msgs.dialog_id');
|
||||
}, 'm', 'm.dialog_id', '=', 'd.id')
|
||||
->leftJoin('web_socket_dialog_users as du', function ($query) use ($user) {
|
||||
$query->on('d.id', '=', 'du.dialog_id');
|
||||
$query->where('du.userid', '!=', $user->userid);
|
||||
$query->where('d.type', 'user');
|
||||
})
|
||||
->leftJoin('users as u', 'du.userid', '=', 'u.userid')
|
||||
->where('d.type', '!=', 'user')
|
||||
->orWhere('u.bot', 0)
|
||||
->orderByDesc('m.chat_num')
|
||||
->first();
|
||||
if (!empty($longestChat)) {
|
||||
if ($longestChat->avatar) {
|
||||
$longestChat->avatar = url($longestChat->avatar);
|
||||
} else if ($longestChat->dialog_type == 'user') {
|
||||
$longestChat->avatar = User::getAvatar($longestChat->userid, $longestChat->avatar, $longestChat->user_email, $longestChat->user_nickname);
|
||||
} else {
|
||||
$longestChat->avatar = match ($longestChat->dialog_group_type) {
|
||||
'department' => url("images/avatar/default_group_department.png"),
|
||||
'project' => url("images/avatar/default_group_project.png"),
|
||||
'task' => url("images/avatar/default_group_task.png"),
|
||||
default => url("images/avatar/default_group_people.png"),
|
||||
};
|
||||
}
|
||||
->leftJoinSub(function ($query) use ($user, $year) {
|
||||
$query->select('web_socket_dialog_msgs.dialog_id', DB::raw('count(*) as chat_num'))
|
||||
->from('web_socket_dialog_msgs')
|
||||
->where('web_socket_dialog_msgs.userid', $user->userid)
|
||||
->whereYear('web_socket_dialog_msgs.created_at', $year)
|
||||
->groupBy('web_socket_dialog_msgs.dialog_id');
|
||||
}, 'm', 'm.dialog_id', '=', 'd.id')
|
||||
->leftJoin('web_socket_dialog_users as du', function ($query) use ($user) {
|
||||
$query->on('d.id', '=', 'du.dialog_id');
|
||||
$query->where('du.userid', '!=', $user->userid);
|
||||
$query->where('d.type', 'user');
|
||||
})
|
||||
->leftJoin('users as u', 'du.userid', '=', 'u.userid')
|
||||
->where('d.type', '!=', 'user')
|
||||
->orWhere('u.bot', 0)
|
||||
->orderByDesc('m.chat_num')
|
||||
->first();
|
||||
if (!empty($longestChat)) {
|
||||
if ($longestChat->avatar) {
|
||||
$longestChat->avatar = url($longestChat->avatar);
|
||||
} else if ($longestChat->dialog_type == 'user') {
|
||||
$longestChat->avatar = User::getAvatar($longestChat->userid, $longestChat->avatar, $longestChat->user_email, $longestChat->user_nickname);
|
||||
} else {
|
||||
$longestChat->avatar = match ($longestChat->dialog_group_type) {
|
||||
'department' => url("images/avatar/default_group_department.png"),
|
||||
'project' => url("images/avatar/default_group_project.png"),
|
||||
'task' => url("images/avatar/default_group_task.png"),
|
||||
default => url("images/avatar/default_group_people.png"),
|
||||
};
|
||||
}
|
||||
|
||||
// 最晚在线时间
|
||||
$timezone = config('app.timezone');
|
||||
$latestOnline = UserCheckinRecord::whereUserid($user->userid)
|
||||
->whereYear(DB::raw('from_unixtime(report_time)'), $year)
|
||||
->orderByRaw("TIME_FORMAT(DATE_ADD(CONVERT_TZ(from_unixtime(report_time), 'UTC', '$timezone'), INTERVAL 18 HOUR), '%H%i%s') desc")
|
||||
->first();
|
||||
|
||||
//
|
||||
$_A["__annual__report_".$user->userid] = [
|
||||
// 本人信息
|
||||
'user' => [
|
||||
'userid' => $user->userid,
|
||||
'email' => $user->email,
|
||||
'nickname' => $user->nickname,
|
||||
'avatar' => User::getAvatar($user->userid, $user->userimg, $user->email, $user->nickname)
|
||||
],
|
||||
// 入职时间(年月日)
|
||||
'hire_date' => date("Y-m-d", $hireTimestamp),
|
||||
// 在职时间(天为单位)
|
||||
'tenure_days' => floor((strtotime(date('Y-m-d')) - $hireTimestamp) / (24 * 60 * 60)),
|
||||
// 最晚在线时间
|
||||
'latest_online_time' => date("Y-m-d H:i:s", $latestOnline->report_time),
|
||||
// 跟谁聊天最多(发消息的次数。可以是群、私聊、机器人除外)
|
||||
'longest_chat_user' => $longestChat,
|
||||
// 跟所有ai机器人聊天的次数
|
||||
'chat_al_num' => DB::table('web_socket_dialog_msgs as m')
|
||||
->join('web_socket_dialogs as d', 'd.id', '=', 'm.dialog_id')
|
||||
->join('web_socket_dialog_users as du', 'd.id', '=', 'du.dialog_id')
|
||||
->join('users as u', 'du.userid', '=', 'u.userid')
|
||||
->where('u.email', 'like', "%ai-%")
|
||||
->where('u.bot', 1)
|
||||
->where('m.userid', $user->userid)
|
||||
->whereYear('m.created_at', $year)
|
||||
->count(),
|
||||
// 文件创建数量
|
||||
'file_created_num' => File::whereCreatedId($user->userid)->whereYear('created_at', $year)->count(),
|
||||
// 参与过的项目
|
||||
'projects' => DB::table('projects as p')
|
||||
->select('p.id', 'p.name')
|
||||
->join('project_users as pu', 'p.id', '=', 'pu.project_id')
|
||||
->join('project_task_users as ptu', 'p.id', '=', 'ptu.project_id')
|
||||
->where(function($query) use ($user,$year) {
|
||||
$query->where('pu.userid', $user->userid);
|
||||
$query->whereYear('pu.created_at', $year);
|
||||
})
|
||||
->orWhere(function($query) use ($user,$year) {
|
||||
$query->where('ptu.userid', $user->userid);
|
||||
$query->whereYear('ptu.created_at', $year);
|
||||
})
|
||||
->groupBy('p.id')
|
||||
->take(100)
|
||||
->get(),
|
||||
// 任务统计
|
||||
'tasks' => [
|
||||
// 总数量
|
||||
'total' => $taskDb->count(),
|
||||
// 完成数量
|
||||
'completed' => $taskDb->clone()->whereNotNUll('t.complete_at')->count(),
|
||||
// 超时数量
|
||||
'overtime' => $taskDb->clone()->whereRaw("ifnull({$prefix}t.complete_at,'$time') > ifnull({$prefix}t.end_at,'$time')")->count(),
|
||||
// 做得最久的任务
|
||||
'longest_task' => $durationTaskDb->clone()->orderByDesc('duration')->first(),
|
||||
// 做得最快的任务
|
||||
'fastest_task' => $durationTaskDb->clone()->orderBy('duration')->first(),
|
||||
// 每个月完成多少个任务
|
||||
'month_completed_task' => $taskDb->clone()
|
||||
->selectRaw("MONTH({$prefix}t.complete_at) AS month, COUNT({$prefix}t.id) AS num")
|
||||
->whereNotNUll('t.complete_at')
|
||||
->whereYear('t.complete_at', $year)
|
||||
->groupBy('month')
|
||||
->get()
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
// 最晚在线时间
|
||||
$timezone = config('app.timezone');
|
||||
$latestOnline = UserCheckinRecord::whereUserid($user->userid)
|
||||
->whereYear(DB::raw('from_unixtime(report_time)'), $year)
|
||||
->orderByRaw("TIME_FORMAT(DATE_ADD(CONVERT_TZ(from_unixtime(report_time), 'UTC', '$timezone'), INTERVAL 18 HOUR), '%H%i%s') desc")
|
||||
->first();
|
||||
|
||||
//
|
||||
return Base::retSuccess('success', $_A["__annual__report_".$user->userid]);
|
||||
$data = [
|
||||
// 本人信息
|
||||
'user' => [
|
||||
'userid' => $user->userid,
|
||||
'email' => $user->email,
|
||||
'nickname' => $user->nickname,
|
||||
'avatar' => User::getAvatar($user->userid, $user->userimg, $user->email, $user->nickname)
|
||||
],
|
||||
// 入职时间(年月日)
|
||||
'hire_date' => date("Y-m-d", $hireTimestamp),
|
||||
// 在职时间(天为单位)
|
||||
'tenure_days' => floor((strtotime(date('Y-m-d')) - $hireTimestamp) / (24 * 60 * 60)),
|
||||
// 最晚在线时间
|
||||
'latest_online_time' => date("Y-m-d H:i:s", $latestOnline->report_time),
|
||||
// 跟谁聊天最多(发消息的次数。可以是群、私聊、机器人除外)
|
||||
'longest_chat_user' => $longestChat,
|
||||
// 跟所有ai机器人聊天的次数
|
||||
'chat_al_num' => DB::table('web_socket_dialog_msgs as m')
|
||||
->join('web_socket_dialogs as d', 'd.id', '=', 'm.dialog_id')
|
||||
->join('web_socket_dialog_users as du', 'd.id', '=', 'du.dialog_id')
|
||||
->join('users as u', 'du.userid', '=', 'u.userid')
|
||||
->where('u.email', 'like', "%ai-%")
|
||||
->where('u.bot', 1)
|
||||
->where('m.userid', $user->userid)
|
||||
->whereYear('m.created_at', $year)
|
||||
->count(),
|
||||
// 文件创建数量
|
||||
'file_created_num' => File::whereCreatedId($user->userid)->whereYear('created_at', $year)->count(),
|
||||
// 参与过的项目
|
||||
'projects' => DB::table('projects as p')
|
||||
->select('p.id', 'p.name')
|
||||
->join('project_users as pu', 'p.id', '=', 'pu.project_id')
|
||||
->join('project_task_users as ptu', 'p.id', '=', 'ptu.project_id')
|
||||
->where(function($query) use ($user,$year) {
|
||||
$query->where('pu.userid', $user->userid);
|
||||
$query->whereYear('pu.created_at', $year);
|
||||
})
|
||||
->orWhere(function($query) use ($user,$year) {
|
||||
$query->where('ptu.userid', $user->userid);
|
||||
$query->whereYear('ptu.created_at', $year);
|
||||
})
|
||||
->groupBy('p.id')
|
||||
->take(100)
|
||||
->get(),
|
||||
// 任务统计
|
||||
'tasks' => [
|
||||
// 总数量
|
||||
'total' => $taskDb->count(),
|
||||
// 完成数量
|
||||
'completed' => $taskDb->clone()->whereNotNUll('t.complete_at')->count(),
|
||||
// 超时数量
|
||||
'overtime' => $taskDb->clone()->whereRaw("ifnull({$prefix}t.complete_at,'$time') > ifnull({$prefix}t.end_at,'$time')")->count(),
|
||||
// 做得最久的任务
|
||||
'longest_task' => $durationTaskDb->clone()->orderByDesc('duration')->first(),
|
||||
// 做得最快的任务
|
||||
'fastest_task' => $durationTaskDb->clone()->orderBy('duration')->first(),
|
||||
// 每个月完成多少个任务
|
||||
'month_completed_task' => $taskDb->clone()
|
||||
->selectRaw("MONTH({$prefix}t.complete_at) AS month, COUNT({$prefix}t.id) AS num")
|
||||
->whereNotNUll('t.complete_at')
|
||||
->whereYear('t.complete_at', $year)
|
||||
->groupBy('month')
|
||||
->get()
|
||||
]
|
||||
];
|
||||
//
|
||||
return Base::retSuccess('success', $data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,10 @@ use Cache;
|
||||
use Request;
|
||||
use Redirect;
|
||||
use Response;
|
||||
use App\Module\Doo;
|
||||
use App\Models\File;
|
||||
use App\Models\User;
|
||||
use App\Models\UserTransfer;
|
||||
use App\Module\Doo;
|
||||
use App\Module\Base;
|
||||
use App\Module\Extranet;
|
||||
use App\Module\RandomColor;
|
||||
@@ -23,7 +25,8 @@ use App\Tasks\CheckinRemindTask;
|
||||
use App\Tasks\CloseMeetingRoomTask;
|
||||
use App\Tasks\UnclaimedTaskRemindTask;
|
||||
use Hhxsv5\LaravelS\Swoole\Task\Task;
|
||||
use LasseRafn\InitialAvatarGenerator\InitialAvatar;
|
||||
use Laravolt\Avatar\Avatar;
|
||||
use Swoole\Coroutine;
|
||||
|
||||
|
||||
/**
|
||||
@@ -66,6 +69,7 @@ class IndexController extends InvokeController
|
||||
$script = asset_main($array['resources/assets/js/app.js']['file']);
|
||||
}
|
||||
return response()->view('main', [
|
||||
'system_alias' => Base::settingFind('system', 'system_alias', 'WebPage'),
|
||||
'version' => Base::getVersion(),
|
||||
'style' => $style,
|
||||
'script' => $script,
|
||||
@@ -83,7 +87,7 @@ class IndexController extends InvokeController
|
||||
|
||||
/**
|
||||
* 头像
|
||||
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response
|
||||
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response|\Symfony\Component\HttpFoundation\BinaryFileResponse
|
||||
*/
|
||||
public function avatar()
|
||||
{
|
||||
@@ -91,34 +95,120 @@ class IndexController extends InvokeController
|
||||
if ($segment && preg_match('/.*?\.png$/i', $segment)) {
|
||||
$name = substr($segment, 0, -4);
|
||||
} else {
|
||||
$name = Request::input('name', 'H');
|
||||
$name = Request::input('name', 'D');
|
||||
}
|
||||
$size = Request::input('size', 128);
|
||||
$color = Request::input('color');
|
||||
$background = Request::input('background');
|
||||
// 移除各种括号及其内容
|
||||
$pattern = '/[((\[【{[<<『「](.*?)[))\]】}]>>』」]/u';
|
||||
$name = preg_replace($pattern, '', $name) ?: preg_replace($pattern, '$1', $name);
|
||||
// 移除常见标识词(不区分大小写)
|
||||
$filterWords = [
|
||||
// 测试相关
|
||||
'测试', '测试号', '测试账号', '内测', '体验', '试用', 'test', 'testing', 'beta',
|
||||
// 账号相关
|
||||
'账号', '帐号', '账户', '帐户', 'account', 'acc', 'id', 'uid',
|
||||
// 临时标识
|
||||
'临时', '暂用', '备用', '主号', '副号', '小号', '大号', 'temp', 'temporary', 'backup',
|
||||
// 系统相关
|
||||
'系统', '管理员', 'admin', 'administrator', 'system', 'sys', 'root',
|
||||
// 用户相关
|
||||
'用户', 'user', '会员', 'member', 'vip', 'svip', 'mvip', 'premium',
|
||||
// 官方相关
|
||||
'官方', '正式', '认证', 'official', 'verified', 'auth',
|
||||
// 客服相关
|
||||
'客服', '售后', '服务', 'service', 'support', 'helper', 'assistant',
|
||||
// 游戏相关
|
||||
'game', 'gaming', 'player', 'gamer',
|
||||
// 社交媒体相关
|
||||
'ins', 'instagram', 'fb', 'facebook', 'tiktok', 'tweet', 'weibo', 'wechat',
|
||||
// 常见后缀
|
||||
'official', 'real', 'fake', 'copy', 'channel', 'studio', 'team', 'group',
|
||||
// 职业相关
|
||||
'dev', 'developer', 'designer', 'artist', 'writer', 'editor',
|
||||
// 其他
|
||||
'bot', 'robot', 'auto', 'anonymous', 'guest', 'default', 'new', 'old'
|
||||
];
|
||||
$filterWords = array_map(function ($word) {
|
||||
return preg_quote($word, '/');
|
||||
}, $filterWords);
|
||||
$name = preg_replace('/' . implode('|', $filterWords) . '/iu', '', $name) ?: $name;
|
||||
// 移除分隔符和特殊字符
|
||||
$filterSymbols = [
|
||||
// 常见分隔符
|
||||
'-', '_', '=', '+', '/', '\\', '|',
|
||||
'~', '@', '#', '$', '%', '^', '&', '*',
|
||||
// 空格类字符
|
||||
' ', ' ', "\t", "\n", "\r",
|
||||
// 标点符号(中英文)
|
||||
'。', ',', '、', ';', ':', '?', '!',
|
||||
'.', '…', '‥', '′', '″', '℃',
|
||||
'.', ',', ';', ':', '?', '!',
|
||||
// 引号类(修正版)
|
||||
'"', "'", '‘', '’', '“', '”', '`',
|
||||
// 特殊符号
|
||||
'★', '☆', '○', '●', '◎', '◇', '◆',
|
||||
'□', '■', '△', '▲', '▽', '▼',
|
||||
'♀', '♂', '♪', '♫', '♯', '♭', '♬',
|
||||
'→', '←', '↑', '↓', '↖', '↗', '↙', '↘',
|
||||
'√', '×', '÷', '±', '∵', '∴',
|
||||
'♠', '♥', '♣', '♦',
|
||||
// emoji 表情符号范围
|
||||
'\x{1F300}-\x{1F9FF}',
|
||||
'\x{2600}-\x{26FF}',
|
||||
'\x{2700}-\x{27BF}',
|
||||
'\x{1F900}-\x{1F9FF}',
|
||||
'\x{1F600}-\x{1F64F}'
|
||||
];
|
||||
$filterSymbols = array_map(function ($symbol) {
|
||||
return preg_quote($symbol, '/');
|
||||
}, $filterSymbols);
|
||||
$name = preg_replace('/[' . implode('', $filterSymbols) . ']/u', '', $name) ?: $name;
|
||||
//
|
||||
if (preg_match('/^[\x{4e00}-\x{9fa5}]+$/u', $name)) {
|
||||
$name = mb_substr($name, mb_strlen($name) - 2);
|
||||
}
|
||||
if (empty($name)) {
|
||||
$name = 'D';
|
||||
}
|
||||
if (empty($color)) {
|
||||
$color = '#ffffff';
|
||||
$cacheKey = "avatarBackgroundColor::" . md5($name);
|
||||
$background = Cache::rememberForever($cacheKey, function() {
|
||||
$background = Cache::rememberForever($cacheKey, function () {
|
||||
return RandomColor::one(['luminosity' => 'dark']);
|
||||
});
|
||||
}
|
||||
//
|
||||
$avatar = new InitialAvatar();
|
||||
$content = $avatar->name($name)
|
||||
->size($size)
|
||||
->color($color)
|
||||
->background($background)
|
||||
->fontSize(0.35)
|
||||
->autoFont()
|
||||
->generate()
|
||||
->stream('png', 100);
|
||||
$path = public_path('uploads/tmp/avatar/' . substr(md5($name), 0, 2));
|
||||
$file = Base::joinPath($path, md5($name) . '.png');
|
||||
if (file_exists($file)) {
|
||||
return response()->file($file, [
|
||||
'Pragma' => 'public',
|
||||
'Cache-Control' => 'max-age=1814400',
|
||||
'Content-type' => 'image/png',
|
||||
'Expires' => gmdate('D, d M Y H:i:s \G\M\T', time() + 1814400),
|
||||
]);
|
||||
}
|
||||
Base::makeDir($path);
|
||||
//
|
||||
return response($content)
|
||||
$avatar = new Avatar([
|
||||
'shape' => 'square',
|
||||
'width' => $size,
|
||||
'height' => $size,
|
||||
'chars' => 2,
|
||||
'fontSize' => $size / 2.9,
|
||||
'uppercase' => true,
|
||||
'fonts' => [resource_path('assets/statics/fonts/Source_Han_Sans_SC_Regular.otf')],
|
||||
'foregrounds' => [$color],
|
||||
'backgrounds' => [$background],
|
||||
'border' => [
|
||||
'size' => 0,
|
||||
'color' => 'foreground',
|
||||
'radius' => 0,
|
||||
],
|
||||
]);
|
||||
return response($avatar->create($name)->save($file))
|
||||
->header('Pragma', 'public')
|
||||
->header('Cache-Control', 'max-age=1814400')
|
||||
->header('Content-type', 'image/png')
|
||||
@@ -155,7 +245,7 @@ class IndexController extends InvokeController
|
||||
Task::deliver(new DeleteTmpTask('task_worker', 12));
|
||||
Task::deliver(new DeleteTmpTask('tmp'));
|
||||
Task::deliver(new DeleteTmpTask('file'));
|
||||
Task::deliver(new DeleteTmpTask('file_pack'));
|
||||
Task::deliver(new DeleteTmpTask('tmp_file', 24));
|
||||
// 删除机器人消息
|
||||
Task::deliver(new DeleteBotMsgTask());
|
||||
// 周期任务
|
||||
@@ -178,80 +268,135 @@ class IndexController extends InvokeController
|
||||
public function desktop__publish($name = '')
|
||||
{
|
||||
$publishVersion = Request::header('publish-version');
|
||||
$fileNum = Request::get('file_num', 1);
|
||||
$latestFile = public_path("uploads/desktop/latest");
|
||||
$latestVersion = file_exists($latestFile) ? trim(file_get_contents($latestFile)) : "0.0.1";
|
||||
if (strtolower($name) === 'latest') {
|
||||
$name = $latestVersion;
|
||||
}
|
||||
// 上传
|
||||
|
||||
// 上传(header 中包含 publish-version)
|
||||
if (preg_match("/^\d+\.\d+\.\d+$/", $publishVersion)) {
|
||||
$uploadSuccessFileNum = (int)Cache::get($publishVersion, 0);
|
||||
// 判断密钥
|
||||
$publishKey = Request::header('publish-key');
|
||||
if ($publishKey !== env('APP_KEY')) {
|
||||
return Base::retError("key error");
|
||||
}
|
||||
if (version_compare($publishVersion, $latestVersion) > -1) { // 限制上传版本必须 ≥ 当前版本
|
||||
$publishPath = "uploads/desktop/{$publishVersion}/";
|
||||
$res = Base::upload([
|
||||
"file" => Request::file('file'),
|
||||
"type" => 'publish',
|
||||
"path" => $publishPath,
|
||||
"fileName" => true
|
||||
]);
|
||||
if (Base::isSuccess($res)) {
|
||||
file_put_contents($latestFile, $publishVersion);
|
||||
$uploadSuccessFileNum = $uploadSuccessFileNum + 1;
|
||||
Cache::set($publishVersion, $uploadSuccessFileNum, 7200);
|
||||
// 判断版本
|
||||
$action = Request::get('action');
|
||||
$draftPath = "uploads/desktop-draft/{$publishVersion}/";
|
||||
if ($action === 'release') {
|
||||
// 将草稿版本发布为正式版本
|
||||
$draftPath = public_path($draftPath);
|
||||
$releasePath = public_path("uploads/desktop/{$publishVersion}/");
|
||||
if (!file_exists($draftPath)) {
|
||||
return Base::retError("draft version not exists");
|
||||
}
|
||||
if ($uploadSuccessFileNum >= $fileNum){
|
||||
$directoryPath = public_path("uploads/desktop");
|
||||
$files = array_filter(scandir($directoryPath), function($file) use($directoryPath) {
|
||||
return preg_match("/^\d+\.\d+\.\d+$/", $file) && is_dir($directoryPath . '/' . $file) && $file != '.' && $file != '..';
|
||||
});
|
||||
sort($files);
|
||||
foreach ($files as $key => $file) {
|
||||
if ($file != $publishVersion && $key < count($files) - 2) {
|
||||
Base::deleteDirAndFile($directoryPath . '/' . $file);
|
||||
}
|
||||
if (file_exists($releasePath)) {
|
||||
Base::deleteDirAndFile($releasePath);
|
||||
}
|
||||
Base::copyDirectory($draftPath, $releasePath);
|
||||
file_put_contents($latestFile, $publishVersion);
|
||||
// 删除旧版本
|
||||
Base::deleteDirAndFile(public_path("uploads/desktop-draft"));
|
||||
$dirs = Base::recursiveDirs(public_path("uploads/desktop"), false);
|
||||
sort($dirs);
|
||||
$num = 0;
|
||||
foreach ($dirs as $dir) {
|
||||
if (!preg_match("/\/\d+\.\d+\.\d+$/", $dir)) {
|
||||
continue;
|
||||
}
|
||||
$num++;
|
||||
if ($num < 5) {
|
||||
continue; // 保留最新的5个版本
|
||||
}
|
||||
if (filemtime($dir) > time() - 3600 * 24 * 30) {
|
||||
continue; // 保留最近30天的版本
|
||||
}
|
||||
Base::deleteDirAndFile($dir);
|
||||
}
|
||||
return $res;
|
||||
return Base::retSuccess('success');
|
||||
}
|
||||
// 上传草稿版本
|
||||
return Base::upload([
|
||||
"file" => Request::file('file'),
|
||||
"type" => 'publish',
|
||||
"path" => $draftPath,
|
||||
"fileName" => true,
|
||||
"quality" => 100
|
||||
]);
|
||||
}
|
||||
// 列表
|
||||
if (preg_match("/^\d+\.\d+\.\d+$/", $name)) {
|
||||
$path = "uploads/desktop/{$name}";
|
||||
$dirPath = public_path($path);
|
||||
$lists = Base::readDir($dirPath);
|
||||
|
||||
// 列表(访问路径 desktop/publish/{version})
|
||||
if (preg_match("/^v*(\d+\.\d+\.\d+)$/", $name, $match)) {
|
||||
$paths = [
|
||||
"uploads/desktop/{$match[1]}/",
|
||||
"uploads/desktop/v{$match[1]}/",
|
||||
"uploads/desktop-draft/{$match[1]}/",
|
||||
"uploads/desktop-draft/v{$match[1]}/",
|
||||
];
|
||||
$avaiPath = null;
|
||||
foreach ($paths as $path) {
|
||||
$dirPath = public_path($path);
|
||||
$isDraft = str_contains($path, 'draft');
|
||||
if (is_dir($dirPath)) {
|
||||
$avaiPath = $path;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (empty($avaiPath)) {
|
||||
abort(404);
|
||||
}
|
||||
$lists = Base::recursiveFiles($dirPath, false);
|
||||
$files = [];
|
||||
foreach ($lists as $file) {
|
||||
if (str_ends_with($file, '.yml') || str_ends_with($file, '.yaml') || str_ends_with($file, '.blockmap')) {
|
||||
if (preg_match('/\.(zip|yml|yaml|blockmap)$/i', $file) || str_ends_with($file, '-win.exe')) {
|
||||
continue;
|
||||
}
|
||||
$fileName = Base::leftDelete($file, $dirPath);
|
||||
$fileName = basename($file, $dirPath);
|
||||
$fileSize = filesize($file);
|
||||
$files[] = [
|
||||
'name' => substr($fileName, 1),
|
||||
'name' => $fileName,
|
||||
'time' => date("Y-m-d H:i:s", filemtime($file)),
|
||||
'size' => $fileSize > 0 ? Base::readableBytes($fileSize) : 0,
|
||||
'url' => Base::fillUrl($path . $fileName),
|
||||
'url' => Base::fillUrl(Base::joinPath($avaiPath, $fileName)),
|
||||
];
|
||||
}
|
||||
$otherVersion = [];
|
||||
$dirs = Base::recursiveDirs(public_path("uploads/desktop"), false);
|
||||
foreach ($dirs as $dir) {
|
||||
if (!preg_match("/\/\d+\.\d+\.\d+$/", $dir)) {
|
||||
continue;
|
||||
}
|
||||
$version = basename($dir);
|
||||
if ($version === $match[1]) {
|
||||
continue;
|
||||
}
|
||||
$otherVersion[] = [
|
||||
'version' => $version,
|
||||
'url' => Base::fillUrl("desktop/publish/{$version}"),
|
||||
];
|
||||
}
|
||||
//
|
||||
return view('desktop', ['version' => $name, 'files' => $files]);
|
||||
return view('desktop', [
|
||||
'system_alias' => Base::settingFind('system', 'system_alias', 'WebPage'),
|
||||
'version' => $match[1],
|
||||
'files' => $files,
|
||||
'is_draft' => $isDraft,
|
||||
'latest_version' => $latestVersion,
|
||||
'other_version' => array_reverse($otherVersion),
|
||||
]);
|
||||
}
|
||||
// 下载
|
||||
if ($name && file_exists($latestFile)) {
|
||||
$publishVersion = file_get_contents($latestFile);
|
||||
if (preg_match("/^\d+\.\d+\.\d+$/", $publishVersion)) {
|
||||
$filePath = public_path("uploads/desktop/{$publishVersion}/{$name}");
|
||||
if (file_exists($filePath)) {
|
||||
return Response::download($filePath);
|
||||
}
|
||||
|
||||
// 下载(Latest 版本内的文件,访问路径 desktop/publish/{fileName})
|
||||
if ($name) {
|
||||
$filePath = public_path("uploads/desktop/{$latestVersion}/{$name}");
|
||||
if (file_exists($filePath)) {
|
||||
return Response::download($filePath);
|
||||
}
|
||||
}
|
||||
return abort(404);
|
||||
|
||||
// 404
|
||||
abort(404);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -294,6 +439,7 @@ class IndexController extends InvokeController
|
||||
// 文件超过 10m 不支持在线预览,提示下载
|
||||
if (filesize($file) > 10 * 1024 * 1024) {
|
||||
return view('download', [
|
||||
'system_alias' => Base::settingFind('system', 'system_alias', 'WebPage'),
|
||||
'name' => $name,
|
||||
'size' => Base::readableBytes(filesize($file)),
|
||||
'url' => Base::fillUrl($path),
|
||||
@@ -314,7 +460,7 @@ class IndexController extends InvokeController
|
||||
], 'inline');
|
||||
}
|
||||
// EEUI App 直接在线预览查看
|
||||
if (str_contains($userAgent, 'eeui') && Base::judgeClientVersion("0.34.47")) {
|
||||
if (Base::isEEUIApp() && Base::judgeClientVersion("0.34.47")) {
|
||||
if ($browser === 'safari-mobile') {
|
||||
$redirectUrl = Base::fillUrl($path);
|
||||
return <<<EOF
|
||||
@@ -354,6 +500,36 @@ class IndexController extends InvokeController
|
||||
return Redirect::to($redirectUrl, 301);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修复操作离职后续操作(todo 临时,后期删除)
|
||||
* @return array
|
||||
*/
|
||||
public function migration__userdialog()
|
||||
{
|
||||
if (Request::header('app-key') !== env('APP_KEY')) {
|
||||
return Base::retError("key error");
|
||||
}
|
||||
go(function() {
|
||||
Coroutine::sleep(3);
|
||||
$handled = [];
|
||||
UserTransfer::orderBy('id')->chunkById(10, function ($transfers) use ($handled) {
|
||||
/** @var UserTransfer $transfer */
|
||||
foreach ($transfers as $transfer) {
|
||||
if (in_array($transfer->original_userid, $handled)) {
|
||||
continue;
|
||||
}
|
||||
$handled[] = $transfer->original_userid;
|
||||
//
|
||||
$user = User::find($transfer->original_userid);
|
||||
if ($user?->isDisable()) {
|
||||
$transfer->exitDialog();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
return Base::retSuccess('success');
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存配置 (todo 已废弃)
|
||||
* @return string
|
||||
@@ -362,85 +538,4 @@ class IndexController extends InvokeController
|
||||
{
|
||||
return '<!-- Deprecated -->';
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取所有中文
|
||||
* @return array|string
|
||||
*/
|
||||
public function allcn()
|
||||
{
|
||||
if (!Base::is_internal_ip(Base::getIp())) {
|
||||
// 限制内网访问
|
||||
return "Forbidden Access";
|
||||
}
|
||||
$list = Base::readDir(resource_path());
|
||||
$array = [];
|
||||
foreach ($list as $item) {
|
||||
$content = file_get_contents($item);
|
||||
preg_match_all("/\\\$L\((.*?)\)/", $content, $matchs);
|
||||
if ($matchs) {
|
||||
foreach ($matchs[1] as $text) {
|
||||
$array[trim(trim($text, '"'), "'")] = trim(trim($text, '"'), "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
return array_values($array);
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取所有中文
|
||||
* @return array|string
|
||||
*/
|
||||
public function allcn__php()
|
||||
{
|
||||
if (!Base::is_internal_ip(Base::getIp())) {
|
||||
// 限制内网访问
|
||||
return "Forbidden Access";
|
||||
}
|
||||
$list = Base::readDir(app_path());
|
||||
$array = [];
|
||||
foreach ($list as $item) {
|
||||
$content = file_get_contents($item);
|
||||
preg_match_all("/(retSuccess|retError|ApiException)\((.*?)[,|)]/", $content, $matchs);
|
||||
if ($matchs) {
|
||||
foreach ($matchs[2] as $text) {
|
||||
$array[trim(trim($text, '"'), "'")] = trim(trim($text, '"'), "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
return array_values($array);
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取所有中文
|
||||
* @return array|string
|
||||
*/
|
||||
public function allcn__all()
|
||||
{
|
||||
if (!Base::is_internal_ip(Base::getIp())) {
|
||||
// 限制内网访问
|
||||
return "Forbidden Access";
|
||||
}
|
||||
$list = array_merge(Base::readDir(app_path()), Base::readDir(resource_path()));
|
||||
$array = [];
|
||||
foreach ($list as $item) {
|
||||
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);
|
||||
if ($matchs) {
|
||||
foreach ($matchs[0] as $text) {
|
||||
$tmp = preg_replace("/\/\/(.*?)$/", "", $text);
|
||||
$tmp = preg_replace("/\/\/(.*?)\n/", "", $tmp);
|
||||
$tmp = str_replace(":", "", $tmp);
|
||||
if (!preg_match("/[\u{4e00}-\u{9fa5}\u{FE30}-\u{FFA0}]/u", $tmp)){
|
||||
continue; // 没有中文
|
||||
}
|
||||
$val = trim(trim($text, '"'), "'");
|
||||
$array[md5($val)] = $val;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return implode("\n", array_values($array));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,7 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Module\Base;
|
||||
use App\Tasks\IhttpTask;
|
||||
use Hhxsv5\LaravelS\Swoole\Task\Task;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||
@@ -32,24 +29,7 @@ class InvokeController extends BaseController
|
||||
$msg = "404 not found (" . str_replace("__", "/", $app) . ").";
|
||||
return Base::ajaxError($msg);
|
||||
}
|
||||
// 使用websocket请求
|
||||
$apiWebsocket = Request::header('Api-Websocket');
|
||||
if ($apiWebsocket) {
|
||||
$userid = User::userid();
|
||||
if ($userid > 0) {
|
||||
$url = 'http://127.0.0.1:' . env('LARAVELS_LISTEN_PORT') . Request::getRequestUri();
|
||||
$task = new IhttpTask($url, Request::post(), [
|
||||
'Content-Type' => Request::header('Content-Type'),
|
||||
'language' => Request::header('language'),
|
||||
'token' => Request::header('token'),
|
||||
]);
|
||||
$task->setApiWebsocket($apiWebsocket);
|
||||
$task->setApiUserid($userid);
|
||||
Task::deliver($task);
|
||||
return Base::retSuccess('wait');
|
||||
}
|
||||
}
|
||||
// 正常请求
|
||||
//
|
||||
$res = $this->__before($method, $action);
|
||||
if ($res === true || Base::isSuccess($res)) {
|
||||
return $this->$app();
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Http\Middleware;
|
||||
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
|
||||
|
||||
use App\Module\Doo;
|
||||
use App\Services\RequestContext;
|
||||
use Closure;
|
||||
|
||||
class WebApi
|
||||
@@ -18,11 +19,15 @@ class WebApi
|
||||
*/
|
||||
public function handle($request, Closure $next)
|
||||
{
|
||||
global $_A;
|
||||
$_A = [];
|
||||
// 为每个请求生成唯一ID
|
||||
$request->requestId = RequestContext::generateRequestId();
|
||||
RequestContext::set('start_time', microtime(true));
|
||||
RequestContext::set('header_language', $request->header('language'));
|
||||
|
||||
// 加载Doo类
|
||||
Doo::load();
|
||||
|
||||
// 解密请求内容
|
||||
$encrypt = Doo::pgpParseStr($request->header('encrypt'));
|
||||
if ($request->isMethod('post')) {
|
||||
$version = $request->header('version');
|
||||
@@ -47,6 +52,7 @@ class WebApi
|
||||
$request->setTrustedProxies([$request->getClientIp()], $request::HEADER_X_FORWARDED_PROTO);
|
||||
}
|
||||
|
||||
// 执行下一个中间件
|
||||
$response = $next($request);
|
||||
|
||||
// 加密返回内容
|
||||
@@ -57,6 +63,16 @@ class WebApi
|
||||
}
|
||||
}
|
||||
|
||||
// 返回响应
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function terminate()
|
||||
{
|
||||
// 请求结束后清理上下文
|
||||
RequestContext::clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,7 +146,7 @@ class LdapUser extends Model
|
||||
$path = "uploads/user/ldap/";
|
||||
$file = "{$path}{$user->userid}.jpeg";
|
||||
Base::makeDir(public_path($path));
|
||||
if (Base::saveContentImage(public_path($file), $userimg)) {
|
||||
if (Base::saveContentImage(public_path($file), $userimg, 90)) {
|
||||
$user->userimg = $file;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,26 @@ class AbstractModel extends Model
|
||||
const ID = 'id';
|
||||
|
||||
protected $dates = [
|
||||
'top_at',
|
||||
'last_at',
|
||||
|
||||
'start_at',
|
||||
'end_at',
|
||||
|
||||
'archived_at',
|
||||
'complete_at',
|
||||
'loop_at',
|
||||
|
||||
'receive_at',
|
||||
|
||||
'line_at',
|
||||
'disable_at',
|
||||
|
||||
'clear_at',
|
||||
|
||||
'read_at',
|
||||
'done_at',
|
||||
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'deleted_at',
|
||||
|
||||
@@ -245,14 +245,13 @@ class File extends AbstractModel
|
||||
}
|
||||
}
|
||||
//
|
||||
$setting = Base::setting('system');
|
||||
$path = 'uploads/tmp/' . date("Ym") . '/';
|
||||
$path = 'uploads/tmp/file/' . date("Ym") . '/';
|
||||
$data = Base::upload([
|
||||
"file" => Request::file('files'),
|
||||
"type" => 'more',
|
||||
"autoThumb" => false,
|
||||
"path" => $path,
|
||||
"size" => ($setting['file_upload_limit'] ?: 0) * 1024
|
||||
"quality" => 100
|
||||
]);
|
||||
if (Base::isError($data)) {
|
||||
throw new ApiException($data['msg']);
|
||||
@@ -942,7 +941,7 @@ class File extends AbstractModel
|
||||
*/
|
||||
public static function filePushMsg($action, $data = null, $userid = null)
|
||||
{
|
||||
$userid = User::auth()->userid();
|
||||
$userid = User::userid();
|
||||
if (empty($userid)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Models;
|
||||
|
||||
|
||||
use App\Module\Base;
|
||||
use App\Module\Timer;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
/**
|
||||
@@ -76,7 +77,7 @@ class FileContent extends AbstractModel
|
||||
'name' => $name,
|
||||
'ext' => $fileExt
|
||||
]));
|
||||
return Base::fillUrl("online/preview/{$name}?key={$key}&version=" . Base::getVersion() . "&__=" . Base::msecTime());
|
||||
return Base::fillUrl("online/preview/{$name}?key={$key}&version=" . Base::getVersion() . "&__=" . Timer::msecTime());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -106,7 +107,7 @@ class FileContent extends AbstractModel
|
||||
* @param File $file
|
||||
* @param $content
|
||||
* @param $download
|
||||
* @return array|\Symfony\Component\HttpFoundation\StreamedResponse
|
||||
* @return array|\Symfony\Component\HttpFoundation\BinaryFileResponse
|
||||
*/
|
||||
public static function formatContent($file, $content, $download = false)
|
||||
{
|
||||
@@ -118,7 +119,7 @@ class FileContent extends AbstractModel
|
||||
} else {
|
||||
$filePath = public_path($content['url']);
|
||||
}
|
||||
return Base::streamDownload($filePath, $name);
|
||||
return Base::BinaryFileResponse($filePath, $name);
|
||||
}
|
||||
if (empty($content)) {
|
||||
$content = match ($file->type) {
|
||||
@@ -147,7 +148,7 @@ class FileContent extends AbstractModel
|
||||
if ($download) {
|
||||
$filePath = public_path($path);
|
||||
if (isset($filePath)) {
|
||||
return Base::streamDownload($filePath, $name);
|
||||
return Base::BinaryFileResponse($filePath, $name);
|
||||
} else {
|
||||
abort(403, "This file not support download.");
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ use Illuminate\Support\Carbon;
|
||||
* @property int|null $userid 创建人
|
||||
* @property Carbon|null $created_at
|
||||
* @property Carbon|null $updated_at
|
||||
* @property string|null $end_at
|
||||
* @property Carbon|null $end_at
|
||||
* @property Carbon|null $deleted_at
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel cancelAppend()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel cancelHidden()
|
||||
|
||||
@@ -22,7 +22,7 @@ use Request;
|
||||
* @property int|null $personal 是否个人项目
|
||||
* @property string|null $user_simple 成员总数|1,2,3
|
||||
* @property int|null $dialog_id 聊天会话ID
|
||||
* @property string|null $archived_at 归档时间
|
||||
* @property \Illuminate\Support\Carbon|null $archived_at 归档时间
|
||||
* @property int|null $archived_userid 归档会员
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
@@ -325,44 +325,65 @@ class Project extends AbstractModel
|
||||
/**
|
||||
* 推送消息
|
||||
* @param string $action
|
||||
* @param array|self $data 发送内容,默认为[id=>项目ID]
|
||||
* @param array|self $data 推送内容
|
||||
* @param array $userid 指定会员,默认为项目所有成员
|
||||
*/
|
||||
public function pushMsg($action, $data = null, $userid = null)
|
||||
{
|
||||
if ($data === null) {
|
||||
$data = ['id' => $this->id];
|
||||
} elseif ($data instanceof self) {
|
||||
// 处理数据
|
||||
if ($data instanceof self) {
|
||||
$data = $data->toArray();
|
||||
}
|
||||
//
|
||||
$array = [$userid, []];
|
||||
|
||||
$data = is_array($data) ? $data : [];
|
||||
$data['id'] = $this->id;
|
||||
$data['name'] = $this->name;
|
||||
$data['desc'] = $this->desc;
|
||||
|
||||
// 处理接收用户
|
||||
$recipients = [$userid, []];
|
||||
if ($userid === null) {
|
||||
$array[0] = $this->relationUserids();
|
||||
$recipients[0] = $this->relationUserids();
|
||||
} elseif (!is_array($userid)) {
|
||||
$array[0] = [$userid];
|
||||
$recipients[0] = [$userid];
|
||||
}
|
||||
//
|
||||
|
||||
// 移除不需要的字段
|
||||
unset($data['top_at']);
|
||||
|
||||
// 处理所有者权限
|
||||
if (isset($data['owner'])) {
|
||||
$owners = ProjectUser::whereProjectId($data['id'])->whereOwner(1)->pluck('userid')->toArray();
|
||||
$array = [array_intersect($array[0], $owners), array_diff($array[0], $owners)];
|
||||
$owners = ProjectUser::whereProjectId($data['id'])
|
||||
->whereOwner(1)
|
||||
->pluck('userid')
|
||||
->toArray();
|
||||
$recipients = [
|
||||
array_intersect($recipients[0], $owners),
|
||||
array_diff($recipients[0], $owners)
|
||||
];
|
||||
}
|
||||
//
|
||||
foreach ($array as $index => $item) {
|
||||
|
||||
// 发送推送
|
||||
foreach ($recipients as $index => $userids) {
|
||||
if (empty($userids)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($index > 0) {
|
||||
$data['owner'] = 0;
|
||||
}
|
||||
|
||||
$params = [
|
||||
'ignoreFd' => Request::header('fd'),
|
||||
'userid' => array_values($item),
|
||||
'userid' => array_values($userids),
|
||||
'msg' => [
|
||||
'type' => 'project',
|
||||
'action' => $action,
|
||||
'data' => $data,
|
||||
]
|
||||
];
|
||||
$task = new PushTask($params, false);
|
||||
Task::deliver($task);
|
||||
|
||||
Task::deliver(new PushTask($params, false));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,13 +4,14 @@ namespace App\Models;
|
||||
|
||||
use App\Exceptions\ApiException;
|
||||
use App\Module\Base;
|
||||
use App\Module\Doo;
|
||||
|
||||
/**
|
||||
* App\Models\ProjectPermission
|
||||
*
|
||||
* @property int $id
|
||||
* @property int|null $project_id 项目ID
|
||||
* @property string $permissions 权限
|
||||
* @property array $permissions 权限
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel cancelAppend()
|
||||
@@ -38,6 +39,7 @@ class ProjectPermission extends AbstractModel
|
||||
const TASK_LIST_SORT = 'task_list_sort'; // 列表排序
|
||||
const TASK_ADD = 'task_add'; // 任务添加
|
||||
const TASK_UPDATE = 'task_update'; // 任务更新
|
||||
const TASK_TIME = 'task_time'; // 任务时间
|
||||
const TASK_STATUS = 'task_status'; // 任务状态
|
||||
const TASK_REMOVE = 'task_remove'; // 任务删除
|
||||
const TASK_ARCHIVED = 'task_archived'; // 任务归档
|
||||
@@ -69,7 +71,7 @@ class ProjectPermission extends AbstractModel
|
||||
/**
|
||||
* 权限
|
||||
* @param $value
|
||||
* @return string
|
||||
* @return array
|
||||
*/
|
||||
public function getPermissionsAttribute($value)
|
||||
{
|
||||
@@ -110,7 +112,8 @@ class ProjectPermission extends AbstractModel
|
||||
self::TASK_LIST_REMOVE => [self::PERMISSIONS['project_leader']],
|
||||
self::TASK_LIST_SORT => $projectTaskList,
|
||||
self::TASK_ADD => $projectTaskList,
|
||||
self::TASK_UPDATE => [self::PERMISSIONS['project_leader'], self::PERMISSIONS['task_leader'], self::PERMISSIONS['task_assist']],
|
||||
self::TASK_UPDATE => $taskUpdate = [self::PERMISSIONS['project_leader'], self::PERMISSIONS['task_leader'], self::PERMISSIONS['task_assist']],
|
||||
self::TASK_TIME => $taskUpdate,
|
||||
self::TASK_STATUS => $taskStatus = [self::PERMISSIONS['project_leader'], self::PERMISSIONS['task_leader']],
|
||||
self::TASK_REMOVE => $taskStatus,
|
||||
self::TASK_ARCHIVED => $taskStatus,
|
||||
@@ -153,13 +156,14 @@ class ProjectPermission extends AbstractModel
|
||||
$userid = User::userid();
|
||||
$permissions = self::getPermission($project->id, $action);
|
||||
switch ($action) {
|
||||
// 任务添加,任务更新, 任务状态, 任务删除, 任务完成, 任务归档, 任务移动
|
||||
// 任务添加,任务更新, 任务状态, 任务删除, 任务完成, 任务归档, 任务移动
|
||||
case self::TASK_LIST_ADD:
|
||||
case self::TASK_LIST_UPDATE:
|
||||
case self::TASK_LIST_REMOVE:
|
||||
case self::TASK_LIST_SORT:
|
||||
case self::TASK_ADD:
|
||||
case self::TASK_UPDATE:
|
||||
case self::TASK_TIME:
|
||||
case self::TASK_STATUS:
|
||||
case self::TASK_REMOVE:
|
||||
case self::TASK_ARCHIVED:
|
||||
@@ -195,7 +199,7 @@ class ProjectPermission extends AbstractModel
|
||||
$desc = [];
|
||||
rsort($permissions);
|
||||
foreach ($permissions as $permission) {
|
||||
$desc[] = self::PERMISSIONS_DESC[$permission];
|
||||
$desc[] = Doo::translate(self::PERMISSIONS_DESC[$permission]);
|
||||
}
|
||||
$desc = array_reverse($desc);
|
||||
throw new ApiException(sprintf("仅限%s操作", implode('、', $desc)));
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Module\Timer;
|
||||
use DB;
|
||||
use Arr;
|
||||
use Request;
|
||||
@@ -26,12 +27,12 @@ use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
* @property string|null $name 标题
|
||||
* @property string|null $color 颜色
|
||||
* @property string|null $desc 描述
|
||||
* @property string|null $start_at 计划开始时间
|
||||
* @property string|null $end_at 计划结束时间
|
||||
* @property string|null $archived_at 归档时间
|
||||
* @property \Illuminate\Support\Carbon|null $start_at 计划开始时间
|
||||
* @property \Illuminate\Support\Carbon|null $end_at 计划结束时间
|
||||
* @property \Illuminate\Support\Carbon|null $archived_at 归档时间
|
||||
* @property int|null $archived_userid 归档会员
|
||||
* @property int|null $archived_follow 跟随项目归档(项目取消归档时任务也取消归档)
|
||||
* @property string|null $complete_at 完成时间
|
||||
* @property \Illuminate\Support\Carbon|null $complete_at 完成时间
|
||||
* @property int|null $userid 创建人
|
||||
* @property int|null $visibility 任务可见性:1-项目人员 2-任务人员 3-指定成员
|
||||
* @property int|null $p_level 优先级
|
||||
@@ -39,7 +40,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
* @property string|null $p_color 优先级颜色
|
||||
* @property int|null $sort 排序(ASC)
|
||||
* @property string|null $loop 重复周期
|
||||
* @property string|null $loop_at 下一次重复时间
|
||||
* @property \Illuminate\Support\Carbon|null $loop_at 下一次重复时间
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property \Illuminate\Support\Carbon|null $deleted_at
|
||||
@@ -368,7 +369,7 @@ class ProjectTask extends AbstractModel
|
||||
}
|
||||
}, $matches[0]);
|
||||
}, $content);
|
||||
return Base::cutStr(strip_tags($content), 100, 0, "...");
|
||||
return Base::cutStr(strip_tags($content), 100);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -438,7 +439,7 @@ class ProjectTask extends AbstractModel
|
||||
// 时间
|
||||
if ($times) {
|
||||
list($start, $end) = is_string($times) ? explode(",", $times) : (is_array($times) ? $times : []);
|
||||
if (Base::isDate($start) && Base::isDate($end) && $start != $end) {
|
||||
if (Timer::isDate($start) && Timer::isDate($end) && $start != $end) {
|
||||
$task->start_at = Carbon::parse($start);
|
||||
$task->end_at = Carbon::parse($end);
|
||||
}
|
||||
@@ -537,7 +538,7 @@ class ProjectTask extends AbstractModel
|
||||
if ($task->parent_id == 0 && $subtasks && is_array($subtasks)) {
|
||||
foreach ($subtasks as $subtask) {
|
||||
list($start, $end) = is_string($subtask['times']) ? explode(",", $subtask['times']) : (is_array($subtask['times']) ? $subtask['times'] : []);
|
||||
if (Base::isDate($start) && Base::isDate($end) && $start != $end) {
|
||||
if (Timer::isDate($start) && Timer::isDate($end) && $start != $end) {
|
||||
if (Carbon::parse($start)->lt($task->start_at)) {
|
||||
throw new ApiException('子任务开始时间不能小于主任务开始时间');
|
||||
}
|
||||
@@ -663,7 +664,7 @@ class ProjectTask extends AbstractModel
|
||||
if ($mainTask?->complete_at) {
|
||||
throw new ApiException('主任务已完成,无法修改子任务状态');
|
||||
}
|
||||
if (Base::isDate($data['complete_at'])) {
|
||||
if (Timer::isDate($data['complete_at'])) {
|
||||
// 标记已完成
|
||||
if ($this->complete_at) {
|
||||
throw new ApiException('任务已完成');
|
||||
@@ -740,6 +741,7 @@ class ProjectTask extends AbstractModel
|
||||
}
|
||||
}
|
||||
$updateMarking['is_update_project'] = true;
|
||||
$this->updated_at = Carbon::now();
|
||||
$this->syncDialogUser();
|
||||
}
|
||||
// 可见性
|
||||
@@ -768,22 +770,23 @@ class ProjectTask extends AbstractModel
|
||||
if (Arr::exists($data, 'times')) {
|
||||
$oldAt = [Carbon::parse($this->start_at), Carbon::parse($this->end_at)];
|
||||
$oldStringAt = $this->start_at ? ($oldAt[0]->toDateTimeString() . '~' . $oldAt[1]->toDateTimeString()) : '';
|
||||
$clearSubTaskTime = false;
|
||||
$this->start_at = null;
|
||||
$this->end_at = null;
|
||||
$times = $data['times'];
|
||||
list($start, $end, $desc) = is_string($times) ? explode(",", $times) : (is_array($times) ? $times : []);
|
||||
if (Base::isDate($start) && Base::isDate($end) && $start != $end) {
|
||||
if (Timer::isDate($start) && Timer::isDate($end) && $start != $end) {
|
||||
$start_at = Carbon::parse($start);
|
||||
$end_at = Carbon::parse($end);
|
||||
if ($this->parent_id > 0) {
|
||||
// 判断同步主任务时间(子任务时间 超出 主任务)
|
||||
if ($mainTask) {
|
||||
$isUp = false;
|
||||
if ($start_at->lt(Carbon::parse($mainTask->start_at))) {
|
||||
if (empty($mainTask->start_at) || $start_at->lt(Carbon::parse($mainTask->start_at))) {
|
||||
$mainTask->start_at = $start_at;
|
||||
$isUp = true;
|
||||
}
|
||||
if ($end_at->gt(Carbon::parse($mainTask->end_at))) {
|
||||
if (empty($mainTask->end_at) || $end_at->gt(Carbon::parse($mainTask->end_at))) {
|
||||
$mainTask->end_at = $end_at;
|
||||
$isUp = true;
|
||||
}
|
||||
@@ -801,6 +804,7 @@ class ProjectTask extends AbstractModel
|
||||
// 清空子任务时间(子任务时间等于主任务时间)
|
||||
$this->start_at = $mainTask->start_at;
|
||||
$this->end_at = $mainTask->end_at;
|
||||
$clearSubTaskTime = true;
|
||||
}
|
||||
}
|
||||
if ($this->parent_id == 0) {
|
||||
@@ -831,7 +835,7 @@ class ProjectTask extends AbstractModel
|
||||
}
|
||||
});
|
||||
}
|
||||
$newStringAt = $this->start_at ? ($this->start_at->toDateTimeString() . '~' . $this->end_at->toDateTimeString()) : '';
|
||||
$newStringAt = $this->start_at && !$clearSubTaskTime ? ($this->start_at->toDateTimeString() . '~' . $this->end_at->toDateTimeString()) : '';
|
||||
$newDesc = $desc ? "(备注:{$desc})" : "";
|
||||
$this->addLog("修改{任务}时间" . $newDesc, [
|
||||
'change' => [$oldStringAt, $newStringAt]
|
||||
@@ -895,6 +899,7 @@ class ProjectTask extends AbstractModel
|
||||
$row->delete();
|
||||
}
|
||||
}
|
||||
$this->updated_at = Carbon::now();
|
||||
$this->syncDialogUser();
|
||||
}
|
||||
// 背景色
|
||||
@@ -961,8 +966,6 @@ class ProjectTask extends AbstractModel
|
||||
}
|
||||
}
|
||||
$this->save();
|
||||
if ($this->start_at instanceof \DateTimeInterface) $this->start_at = $this->start_at->format('Y-m-d H:i:s');
|
||||
if ($this->end_at instanceof \DateTimeInterface) $this->end_at = $this->end_at->format('Y-m-d H:i:s');
|
||||
});
|
||||
return true;
|
||||
}
|
||||
@@ -1278,7 +1281,9 @@ class ProjectTask extends AbstractModel
|
||||
// 标记已完成
|
||||
if ($this->parent_id == 0) {
|
||||
if (self::whereParentId($this->id)->whereCompleteAt(null)->exists()) {
|
||||
throw new ApiException('子任务未完成');
|
||||
throw new ApiException('子任务未完成', [
|
||||
'task_id' => $this->id
|
||||
], -4004);
|
||||
}
|
||||
}
|
||||
if (!$this->hasOwner()) {
|
||||
@@ -1424,7 +1429,11 @@ class ProjectTask extends AbstractModel
|
||||
'detail' => $detail,
|
||||
];
|
||||
if ($this->parent_id) {
|
||||
$record['subtitle'] = $this->name;
|
||||
$record['subtask'] = [
|
||||
'id' => $this->id,
|
||||
'parent_id' => $this->parent_id,
|
||||
'name' => $this->name,
|
||||
];
|
||||
}
|
||||
if ($record) {
|
||||
$array['record'] = $record;
|
||||
@@ -1609,14 +1618,18 @@ class ProjectTask extends AbstractModel
|
||||
'dialog_id' => $this->dialog_id,
|
||||
];
|
||||
//
|
||||
$projectOwnerids = ProjectUser::whereProjectId($this->project_id)->whereOwner(1)->pluck('userid')->toArray(); // 项目负责人
|
||||
//
|
||||
$array = [];
|
||||
if (empty($userids)) {
|
||||
// 默认 项目成员 与 项目负责人,任务负责人、协助人的差集
|
||||
$projectUserids = ProjectUser::whereProjectId($this->project_id)->pluck('userid')->toArray(); // 项目成员
|
||||
$projectOwner = ProjectUser::whereProjectId($this->project_id)->whereOwner(1)->pluck('userid')->toArray(); // 项目负责人
|
||||
$taskOwnerAndAssists = ProjectTaskUser::select(['userid', 'owner'])->whereIn('owner', [0, 1])->whereTaskId($this->id)->pluck('userid')->toArray();
|
||||
$subUserids = ProjectTaskUser::whereTaskPid($this->id)->pluck('userid')->toArray();
|
||||
$userids = array_diff($projectUserids, $projectOwner, $taskOwnerAndAssists, $subUserids);
|
||||
$userids = array_diff($projectUserids, $projectOwnerids, $taskOwnerAndAssists, $subUserids);
|
||||
} else {
|
||||
// 保证项目负责人都能看到
|
||||
$userids = array_diff($userids, $projectOwnerids);
|
||||
}
|
||||
//
|
||||
$array[] = [
|
||||
@@ -1680,16 +1693,6 @@ class ProjectTask extends AbstractModel
|
||||
if (empty($botUser)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$dataId = $this->parent_id ?: $this->id;
|
||||
$taskHtml = "<span class=\"mention task\" data-id=\"{$dataId}\">#{$this->name}</span>";
|
||||
$text = match ($type) {
|
||||
1 => "您的任务 {$taskHtml} 即将超时。",
|
||||
2 => "您的任务 {$taskHtml} 已经超时。",
|
||||
3 => "您的任务 {$taskHtml} 时间已修改。",
|
||||
default => "您有一个新任务 {$taskHtml}。",
|
||||
};
|
||||
|
||||
/** @var User $user */
|
||||
foreach ($receivers as $receiver) {
|
||||
$data = [
|
||||
@@ -1700,13 +1703,34 @@ class ProjectTask extends AbstractModel
|
||||
if (in_array($type, [1, 2]) && ProjectTaskPushLog::where($data)->exists()) {
|
||||
continue;
|
||||
}
|
||||
if ($owners[$receiver->userid]) {
|
||||
$title = match ($type) {
|
||||
1 => "您负责的任务即将超时",
|
||||
2 => "您负责的任务已经超时",
|
||||
3 => "您负责的任务时间已修改",
|
||||
default => "您有一个新任务",
|
||||
};
|
||||
} else {
|
||||
$title = match ($type) {
|
||||
1 => "您协助的任务即将超时",
|
||||
2 => "您协助的任务已经超时",
|
||||
3 => "您协助的任务时间已修改",
|
||||
default => "您有一个新协助任务",
|
||||
};
|
||||
}
|
||||
//
|
||||
$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
|
||||
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'template', [
|
||||
'type' => 'task_list',
|
||||
'title' => $title . $suffix,
|
||||
'list' => [
|
||||
[
|
||||
'id' => $this->parent_id ?: $this->id,
|
||||
'name' => $this->name,
|
||||
]
|
||||
],
|
||||
], in_array($type, [0, 3]) ? $userid : $botUser->userid);
|
||||
}
|
||||
}
|
||||
@@ -1714,11 +1738,12 @@ class ProjectTask extends AbstractModel
|
||||
|
||||
/**
|
||||
* 移动任务
|
||||
* @param int $project_id
|
||||
* @param int $column_id
|
||||
* @param int $projectId
|
||||
* @param int $columnId
|
||||
* @param int $flowItemId
|
||||
* @param array $owner
|
||||
* @param array $assist
|
||||
* @param string $completeAt
|
||||
* @return bool
|
||||
*/
|
||||
public function moveTask(int $projectId, int $columnId,int $flowItemId = 0,array $owner = [], array $assist = [], string $completeAt='')
|
||||
|
||||
@@ -53,8 +53,8 @@ class ProjectTaskContent extends AbstractModel
|
||||
$array = $this->toArray();
|
||||
$array['content'] = file_get_contents($filePath) ?: '';
|
||||
if ($array['content']) {
|
||||
$replace = Base::fillUrl('uploads/task');
|
||||
$array['content'] = str_replace('{{RemoteURL}}uploads/task', $replace, $array['content']);
|
||||
$replace = Base::fillUrl('uploads');
|
||||
$array['content'] = str_replace('{{RemoteURL}}uploads', $replace, $array['content']);
|
||||
}
|
||||
return $array;
|
||||
}
|
||||
@@ -74,18 +74,24 @@ class ProjectTaskContent extends AbstractModel
|
||||
$oldContent = $content;
|
||||
$path = 'uploads/task/content/' . date("Ym") . '/' . $task_id . '/';
|
||||
//
|
||||
preg_match_all("/<img\s+src=\"data:image\/(png|jpg|jpeg|webp);base64,(.*?)\"/s", $content, $matchs);
|
||||
preg_match_all('/<img[^>]*?src=\\\\?["\']data:image\/(png|jpg|jpeg|webp);base64,(.*?)\\\\?["\']/s', $content, $matchs);
|
||||
foreach ($matchs[2] as $key => $text) {
|
||||
$tmpPath = $path . 'attached/';
|
||||
Base::makeDir(public_path($tmpPath));
|
||||
$tmpPath .= md5($text) . "." . $matchs[1][$key];
|
||||
if (Base::saveContentImage(public_path($tmpPath), base64_decode($text))) {
|
||||
if (Base::saveContentImage(public_path($tmpPath), base64_decode($text), 90)) {
|
||||
$paramet = getimagesize(public_path($tmpPath));
|
||||
$content = str_replace($matchs[0][$key], '<img src="{{RemoteURL}}' . $tmpPath . '" original-width="' . $paramet[0] . '" original-height="' . $paramet[1] . '"', $content);
|
||||
}
|
||||
}
|
||||
$pattern = '/<img(.*?)src=("|\')https*:\/\/(.*?)\/(uploads\/task\/content\/(.*?))\2/is';
|
||||
$content = preg_replace($pattern, '<img$1src=$2{{RemoteURL}}$4$2', $content);
|
||||
preg_match_all('/(<img[^>]*?src=\\\\?["\'])(https?:\/\/[^\/]+\/)(uploads\/[^\s"\'>]+)(\\\\?["\'][^>]*?>)/i', $content, $matches);
|
||||
foreach ($matches[0] as $key => $fullMatch) {
|
||||
$filePath = public_path($matches[3][$key]);
|
||||
if (file_exists($filePath)) {
|
||||
$replacement = $matches[1][$key] . '{{RemoteURL}}' . $matches[3][$key] . $matches[4][$key];
|
||||
$content = str_replace($fullMatch, $replacement, $content);
|
||||
}
|
||||
}
|
||||
//
|
||||
$filePath = $path . md5($content);
|
||||
$publicPath = public_path($filePath);
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace App\Models;
|
||||
* @property int|null $project_id 项目ID
|
||||
* @property int|null $userid 成员ID
|
||||
* @property int|null $owner 是否负责人
|
||||
* @property string|null $top_at 置顶时间
|
||||
* @property \Illuminate\Support\Carbon|null $top_at 置顶时间
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read \App\Models\Project|null $project
|
||||
|
||||
@@ -74,7 +74,7 @@ class Report extends AbstractModel
|
||||
public function receivesUser(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(User::class, ReportReceive::class, "rid", "userid")
|
||||
->withPivot("receive_time", "read");
|
||||
->withPivot("receive_at", "read");
|
||||
}
|
||||
|
||||
public function sendUser()
|
||||
@@ -82,15 +82,6 @@ class Report extends AbstractModel
|
||||
return $this->hasOne(User::class, "userid", "userid");
|
||||
}
|
||||
|
||||
public function getTypeAttribute($value): string
|
||||
{
|
||||
return match ($value) {
|
||||
Report::WEEKLY => "周报",
|
||||
Report::DAILY => "日报",
|
||||
default => "",
|
||||
};
|
||||
}
|
||||
|
||||
public function getContentAttribute($value): string
|
||||
{
|
||||
return htmlspecialchars_decode($value);
|
||||
|
||||
@@ -10,7 +10,7 @@ use Illuminate\Database\Eloquent\Model;
|
||||
*
|
||||
* @property int $id
|
||||
* @property int $rid
|
||||
* @property string|null $receive_time 接收时间
|
||||
* @property \Illuminate\Support\Carbon|null $receive_at 接收时间
|
||||
* @property int $userid 接收人
|
||||
* @property int $read 是否已读
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel cancelAppend()
|
||||
@@ -24,7 +24,7 @@ use Illuminate\Database\Eloquent\Model;
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel saveOrIgnore()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ReportReceive whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ReportReceive whereRead($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ReportReceive whereReceiveTime($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ReportReceive whereReceiveAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ReportReceive whereRid($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ReportReceive whereUserid($value)
|
||||
* @mixin \Eloquent
|
||||
@@ -38,7 +38,7 @@ class ReportReceive extends AbstractModel
|
||||
|
||||
protected $fillable = [
|
||||
"rid",
|
||||
"receive_time",
|
||||
"receive_at",
|
||||
"userid",
|
||||
"read",
|
||||
];
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Models;
|
||||
|
||||
use App\Module\Base;
|
||||
use App\Module\Timer;
|
||||
|
||||
/**
|
||||
* App\Models\Setting
|
||||
@@ -32,6 +33,32 @@ use App\Module\Base;
|
||||
*/
|
||||
class Setting extends AbstractModel
|
||||
{
|
||||
/**
|
||||
* 格式化设置参数
|
||||
* @param $value
|
||||
* @return array|mixed
|
||||
*/
|
||||
public function getSettingAttribute($value)
|
||||
{
|
||||
if (is_array($value)) {
|
||||
return $value;
|
||||
}
|
||||
$value = Base::json2array($value);
|
||||
switch ($this->name) {
|
||||
case 'system':
|
||||
$value['system_alias'] = $value['system_alias'] ?: env('APP_NAME');
|
||||
if (!is_array($value['task_default_time']) || count($value['task_default_time']) != 2 || !Timer::isTime($value['task_default_time'][0]) || !Timer::isTime($value['task_default_time'][1])) {
|
||||
$value['task_default_time'] = ['09:00', '18:00'];
|
||||
}
|
||||
break;
|
||||
case 'fileSetting':
|
||||
$value['permission_pack_type'] = $value['permission_pack_type'] ?: 'all';
|
||||
$value['permission_pack_userids'] = is_array($value['permission_pack_userids']) ? $value['permission_pack_userids'] : [];
|
||||
break;
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证邮箱地址(过滤忽略地址)
|
||||
* @param $array
|
||||
|
||||
@@ -10,8 +10,8 @@ use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
* @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 $start_at 开始时间
|
||||
* @property \Illuminate\Support\Carbon|null $end_at 结束时间
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property \Illuminate\Support\Carbon|null $deleted_at
|
||||
|
||||
@@ -157,7 +157,7 @@ class UmengAlias extends AbstractModel
|
||||
'vivo_category' => 'IM',
|
||||
'huawei_channel_importance' => 'NORMAL',
|
||||
'huawei_channel_category' => 'IM',
|
||||
'channel_fcm' => 1,
|
||||
'channel_fcm' => 0,
|
||||
],
|
||||
]);
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ namespace App\Models;
|
||||
use App\Exceptions\ApiException;
|
||||
use App\Module\Base;
|
||||
use App\Module\Doo;
|
||||
use App\Module\Table\OnlineData;
|
||||
use App\Services\RequestContext;
|
||||
use Cache;
|
||||
use Carbon\Carbon;
|
||||
|
||||
@@ -13,28 +15,29 @@ use Carbon\Carbon;
|
||||
* App\Models\User
|
||||
*
|
||||
* @property int $userid
|
||||
* @property array $identity 身份
|
||||
* @property array $department 所属部门
|
||||
* @property array $identity
|
||||
* @property array $department
|
||||
* @property string|null $az A-Z
|
||||
* @property string|null $pinyin 拼音(主要用于搜索)
|
||||
* @property string|null $email 邮箱
|
||||
* @property string|null $email
|
||||
* @property string|null $tel 联系电话
|
||||
* @property string $nickname 昵称
|
||||
* @property string|null $profession 职位/职称
|
||||
* @property string $userimg 头像
|
||||
* @property string $nickname
|
||||
* @property string|null $profession
|
||||
* @property string $userimg
|
||||
* @property string|null $encrypt
|
||||
* @property string|null $password 登录密码
|
||||
* @property int|null $changepass 登录需要修改密码
|
||||
* @property int|null $login_num 累计登录次数
|
||||
* @property string|null $last_ip 最后登录IP
|
||||
* @property string|null $last_at 最后登录时间
|
||||
* @property \Illuminate\Support\Carbon|null $last_at 最后登录时间
|
||||
* @property string|null $line_ip 最后在线IP(接口)
|
||||
* @property string|null $line_at 最后在线时间(接口)
|
||||
* @property \Illuminate\Support\Carbon|null $line_at 最后在线时间(接口)
|
||||
* @property int|null $task_dialog_id 最后打开的任务会话ID
|
||||
* @property string|null $created_ip 注册IP
|
||||
* @property string|null $disable_at 禁用时间(离职时间)
|
||||
* @property \Illuminate\Support\Carbon|null $disable_at
|
||||
* @property int|null $email_verity 邮箱是否已验证
|
||||
* @property int|null $bot 是否机器人
|
||||
* @property string|null $lang 语言首选项
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel cancelAppend()
|
||||
@@ -58,6 +61,7 @@ use Carbon\Carbon;
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|User whereEmailVerity($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|User whereEncrypt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|User whereIdentity($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|User whereLang($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|User whereLastAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|User whereLastIp($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|User whereLineAt($value)
|
||||
@@ -95,7 +99,13 @@ class User extends AbstractModel
|
||||
*/
|
||||
public function getNicknameAttribute($value)
|
||||
{
|
||||
return $value ?: Base::cardFormat($this->email);
|
||||
if ($value) {
|
||||
if (UserBot::isSystemBot($this->email)) {
|
||||
return Doo::translate($value);
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
return Base::formatName($this->email);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -150,7 +160,7 @@ class User extends AbstractModel
|
||||
});
|
||||
$array = [];
|
||||
foreach ($list as $item) {
|
||||
$array[] = $item['name'] . ($item['owner_userid'] === $this->userid ? '(M)' : '');
|
||||
$array[] = $item['name'] . ($item['owner_userid'] === $this->userid ? ' (M)' : '');
|
||||
}
|
||||
return implode(', ', $array);
|
||||
}
|
||||
@@ -185,7 +195,7 @@ class User extends AbstractModel
|
||||
*/
|
||||
public function getOnlineStatus()
|
||||
{
|
||||
$online = $this->bot || Cache::get("User::online:" . $this->userid) === "on";
|
||||
$online = $this->bot || OnlineData::live($this->userid) > 0;
|
||||
if ($online) {
|
||||
return true;
|
||||
}
|
||||
@@ -411,7 +421,7 @@ class User extends AbstractModel
|
||||
throw new ApiException('请登录后继续...', [], -1);
|
||||
}
|
||||
}
|
||||
if (in_array('disable', $user->identity)) {
|
||||
if ($user->isDisable()) {
|
||||
throw new ApiException('帐号已停用...', [], -1);
|
||||
}
|
||||
if ($identity) {
|
||||
@@ -426,9 +436,8 @@ class User extends AbstractModel
|
||||
*/
|
||||
private static function authInfo()
|
||||
{
|
||||
global $_A;
|
||||
if (isset($_A["__static_auth"])) {
|
||||
return $_A["__static_auth"];
|
||||
if (RequestContext::has('auth')) {
|
||||
return RequestContext::get('auth');
|
||||
}
|
||||
if (Doo::userId() > 0
|
||||
&& !Doo::userExpired()
|
||||
@@ -440,13 +449,19 @@ class User extends AbstractModel
|
||||
if (Carbon::parse($user->line_at)->addSeconds(30)->lt(Carbon::now())) {
|
||||
$upArray['line_at'] = Carbon::now();
|
||||
}
|
||||
$headerLanguage = RequestContext::get('header_language');
|
||||
if (empty($user->lang) || $headerLanguage) {
|
||||
if (Doo::checkLanguage($headerLanguage) && $user->lang != $headerLanguage) {
|
||||
$upArray['lang'] = $headerLanguage;
|
||||
}
|
||||
}
|
||||
if ($upArray) {
|
||||
$user->updateInstance($upArray);
|
||||
$user->save();
|
||||
}
|
||||
return $_A["__static_auth"] = $user;
|
||||
return RequestContext::save('auth', $user);
|
||||
}
|
||||
return $_A["__static_auth"] = false;
|
||||
return RequestContext::save('auth', false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -480,22 +495,21 @@ class User extends AbstractModel
|
||||
* @param int $userid 会员ID
|
||||
* @return self
|
||||
*/
|
||||
public static function userid2basic($userid)
|
||||
public static function userid2basic($userid, $addField = [])
|
||||
{
|
||||
global $_A;
|
||||
if (empty($userid)) {
|
||||
return null;
|
||||
}
|
||||
$userid = intval($userid);
|
||||
if (isset($_A["__static_userid2basic_" . $userid])) {
|
||||
return $_A["__static_userid2basic_" . $userid];
|
||||
if (RequestContext::has("userid2basic_" . $userid)) {
|
||||
return RequestContext::get("userid2basic_" . $userid);
|
||||
}
|
||||
$userInfo = self::whereUserid($userid)->select(User::$basicField)->first();
|
||||
$userInfo = self::whereUserid($userid)->select(array_merge(User::$basicField, $addField))->first();
|
||||
if ($userInfo) {
|
||||
$userInfo->online = $userInfo->getOnlineStatus();
|
||||
$userInfo->department_name = $userInfo->getDepartmentName();
|
||||
}
|
||||
return $_A["__static_userid2basic_" . $userid] = ($userInfo ?: []);
|
||||
return RequestContext::save("userid2basic_" . $userid, $userInfo ?: []);
|
||||
}
|
||||
|
||||
|
||||
@@ -578,7 +592,7 @@ class User extends AbstractModel
|
||||
case 'ai-gemini@bot.system':
|
||||
return url("images/avatar/default_gemini.png");
|
||||
case 'ai-zhipu@bot.system':
|
||||
return url("images/avatar/default_zhipu.png");
|
||||
return url("images/avatar/default_zhipu.png");
|
||||
case 'bot-manager@bot.system':
|
||||
return url("images/avatar/default_bot.png");
|
||||
case 'meeting-alert@bot.system':
|
||||
@@ -651,7 +665,9 @@ class User extends AbstractModel
|
||||
])->save();
|
||||
}
|
||||
//
|
||||
$update['nickname'] = UserBot::systemBotName($email);
|
||||
if (empty($update['nickname'])) {
|
||||
$update['nickname'] = UserBot::systemBotName($email);
|
||||
}
|
||||
}
|
||||
if ($update) {
|
||||
$botUser->updateInstance($update);
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Models;
|
||||
use App\Module\Base;
|
||||
use App\Module\Doo;
|
||||
use App\Module\Extranet;
|
||||
use App\Module\Timer;
|
||||
use App\Tasks\JokeSoupTask;
|
||||
use Cache;
|
||||
use Carbon\Carbon;
|
||||
@@ -16,7 +17,7 @@ use Carbon\Carbon;
|
||||
* @property int|null $userid 所属人ID
|
||||
* @property int|null $bot_id 机器人ID
|
||||
* @property int|null $clear_day 消息自动清理天数
|
||||
* @property string|null $clear_at 下一次清理时间
|
||||
* @property \Illuminate\Support\Carbon|null $clear_at 下一次清理时间
|
||||
* @property string|null $webhook_url 消息webhook地址
|
||||
* @property int|null $webhook_num 消息webhook请求次数
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
@@ -44,6 +45,16 @@ use Carbon\Carbon;
|
||||
class UserBot extends AbstractModel
|
||||
{
|
||||
|
||||
/**
|
||||
* 判断是否系统机器人
|
||||
* @param $email
|
||||
* @return bool
|
||||
*/
|
||||
public static function isSystemBot($email)
|
||||
{
|
||||
return str_ends_with($email, '@bot.system') && self::systemBotName($email);
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统机器人名称
|
||||
* @param $name string 邮箱 或 邮箱前缀
|
||||
@@ -54,7 +65,7 @@ class UserBot extends AbstractModel
|
||||
if (str_contains($name, "@")) {
|
||||
$name = explode("@", $name)[0];
|
||||
}
|
||||
return match ($name) {
|
||||
$name = match ($name) {
|
||||
'system-msg' => '系统消息',
|
||||
'task-alert' => '任务提醒',
|
||||
'check-in' => '签到打卡',
|
||||
@@ -71,6 +82,7 @@ class UserBot extends AbstractModel
|
||||
'okr-alert' => 'OKR提醒',
|
||||
default => '', // 不是系统机器人时返回空(也可以拿来判断是否是系统机器人)
|
||||
};
|
||||
return Doo::translate($name);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,79 +92,118 @@ class UserBot extends AbstractModel
|
||||
*/
|
||||
public static function quickMsgs($email)
|
||||
{
|
||||
return match ($email) {
|
||||
'check-in@bot.system' => [
|
||||
[
|
||||
'key' => 'checkin',
|
||||
'label' => Doo::translate('我要打卡')
|
||||
], [
|
||||
'key' => 'it',
|
||||
'label' => Doo::translate('IT资讯')
|
||||
], [
|
||||
'key' => '36ke',
|
||||
'label' => Doo::translate('36氪')
|
||||
], [
|
||||
'key' => '60s',
|
||||
'label' => Doo::translate('60s读世界')
|
||||
], [
|
||||
'key' => 'joke',
|
||||
'label' => Doo::translate('开心笑话')
|
||||
], [
|
||||
'key' => 'soup',
|
||||
'label' => Doo::translate('心灵鸡汤')
|
||||
]
|
||||
],
|
||||
'anon-msg@bot.system' => [
|
||||
[
|
||||
'key' => 'help',
|
||||
'label' => Doo::translate('使用说明')
|
||||
], [
|
||||
'key' => 'privacy',
|
||||
'label' => Doo::translate('隐私说明')
|
||||
],
|
||||
],
|
||||
'bot-manager@bot.system' => [
|
||||
[
|
||||
'key' => '/help',
|
||||
'label' => Doo::translate('帮助指令')
|
||||
], [
|
||||
'key' => '/api',
|
||||
'label' => Doo::translate('Api接口文档')
|
||||
], [
|
||||
'key' => '/list',
|
||||
'label' => Doo::translate('我的机器人')
|
||||
],
|
||||
],
|
||||
'ai-openai@bot.system',
|
||||
'ai-claude@bot.system',
|
||||
'ai-wenxin@bot.system',
|
||||
'ai-gemini@bot.system',
|
||||
'ai-zhipu@bot.system',
|
||||
'ai-qianwen@bot.system' => [
|
||||
[
|
||||
'key' => '%3A.clear',
|
||||
'label' => Doo::translate('清空上下文')
|
||||
]
|
||||
],
|
||||
default => [],
|
||||
};
|
||||
switch ($email) {
|
||||
case 'check-in@bot.system':
|
||||
$menu = [
|
||||
/*[
|
||||
'key' => 'it',
|
||||
'label' => Doo::translate('IT资讯')
|
||||
], [
|
||||
'key' => '36ke',
|
||||
'label' => Doo::translate('36氪')
|
||||
], [
|
||||
'key' => '60s',
|
||||
'label' => Doo::translate('60s读世界')
|
||||
], [
|
||||
'key' => 'joke',
|
||||
'label' => Doo::translate('开心笑话')
|
||||
], [
|
||||
'key' => 'soup',
|
||||
'label' => Doo::translate('心灵鸡汤')
|
||||
]*/
|
||||
];
|
||||
$setting = Base::setting('checkinSetting');
|
||||
if ($setting['open'] !== 'open') {
|
||||
return $menu;
|
||||
}
|
||||
if (in_array('locat', $setting['modes']) && Base::isEEUIApp()) {
|
||||
$menu[] = [
|
||||
'key' => 'locat-checkin',
|
||||
'label' => Doo::translate('定位签到'),
|
||||
'config' => [
|
||||
'key' => $setting['locat_bd_lbs_key'],
|
||||
'lng' => $setting['locat_bd_lbs_point']['lng'],
|
||||
'lat' => $setting['locat_bd_lbs_point']['lat'],
|
||||
'radius' => $setting['locat_bd_lbs_point']['radius'],
|
||||
]
|
||||
];
|
||||
}
|
||||
if (in_array('manual', $setting['modes'])) {
|
||||
$menu[] = [
|
||||
'key' => 'manual-checkin',
|
||||
'label' => Doo::translate('手动签到')
|
||||
];
|
||||
}
|
||||
return $menu;
|
||||
|
||||
case 'anon-msg@bot.system':
|
||||
return [
|
||||
[
|
||||
'key' => 'help',
|
||||
'label' => Doo::translate('使用说明')
|
||||
], [
|
||||
'key' => 'privacy',
|
||||
'label' => Doo::translate('隐私说明')
|
||||
],
|
||||
];
|
||||
|
||||
case 'meeting-alert@bot.system':
|
||||
if (!Base::judgeClientVersion('0.39.89')) {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
[
|
||||
'key' => 'meeting-create',
|
||||
'label' => Doo::translate('新会议')
|
||||
],
|
||||
[
|
||||
'key' => 'meeting-join',
|
||||
'label' => Doo::translate('加入会议')
|
||||
],
|
||||
];
|
||||
|
||||
case 'bot-manager@bot.system':
|
||||
return [
|
||||
[
|
||||
'key' => '/help',
|
||||
'label' => Doo::translate('帮助指令')
|
||||
], [
|
||||
'key' => '/api',
|
||||
'label' => Doo::translate('API接口文档')
|
||||
], [
|
||||
'key' => '/list',
|
||||
'label' => Doo::translate('我的机器人')
|
||||
],
|
||||
];
|
||||
|
||||
default:
|
||||
if (preg_match('/^ai-(.*?)@bot.system$/', $email)) {
|
||||
return [
|
||||
[
|
||||
'key' => '%3A.clear',
|
||||
'label' => Doo::translate('清空上下文')
|
||||
]
|
||||
];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 签到机器人
|
||||
* @param $command
|
||||
* @param $userid
|
||||
* @param $extra
|
||||
* @return string
|
||||
*/
|
||||
public static function checkinBotQuickMsg($command, $userid)
|
||||
public static function checkinBotQuickMsg($command, $userid, $extra = [])
|
||||
{
|
||||
if (Cache::get("UserBot::checkinBotQuickMsg:{$userid}") === "yes") {
|
||||
return "操作频繁!";
|
||||
}
|
||||
Cache::put("UserBot::checkinBotQuickMsg:{$userid}", "yes", Carbon::now()->addSecond());
|
||||
//
|
||||
if ($command === 'checkin') {
|
||||
if ($command === 'manual-checkin') {
|
||||
$setting = Base::setting('checkinSetting');
|
||||
if ($setting['open'] !== 'open') {
|
||||
return '暂未开启签到功能。';
|
||||
@@ -160,9 +211,25 @@ class UserBot extends AbstractModel
|
||||
if (!in_array('manual', $setting['modes'])) {
|
||||
return '暂未开放手动签到。';
|
||||
}
|
||||
if ($error = UserBot::checkinBotCheckin($userid, Base::time(), true)) {
|
||||
return $error;
|
||||
UserBot::checkinBotCheckin('manual-' . $userid, Timer::time(), true);
|
||||
return null;
|
||||
} elseif ($command === 'locat-checkin') {
|
||||
$setting = Base::setting('checkinSetting');
|
||||
if ($setting['open'] !== 'open') {
|
||||
return '暂未开启签到功能。';
|
||||
}
|
||||
if (!in_array('locat', $setting['modes'])) {
|
||||
return '暂未开放定位签到。';
|
||||
}
|
||||
if (empty($extra)) {
|
||||
return '当前客户端版本低(所需版本≥v0.39.75)。';
|
||||
}
|
||||
if ($extra['type'] === 'bd') {
|
||||
// todo 判断距离
|
||||
} else {
|
||||
return '错误的定位签到。';
|
||||
}
|
||||
UserBot::checkinBotCheckin('locat-' . $userid, Timer::time(), true);
|
||||
return null;
|
||||
} else {
|
||||
return Extranet::checkinBotQuickMsg($command);
|
||||
@@ -171,10 +238,11 @@ class UserBot extends AbstractModel
|
||||
|
||||
/**
|
||||
* 签到机器人签到
|
||||
* @param $mac
|
||||
* @param mixed $mac
|
||||
* - 多个使用,分隔
|
||||
* - 支持:mac地址、(manual|locat|face|checkin)-userid
|
||||
* @param $time
|
||||
* @param bool $alreadyTip 签到过是否提示
|
||||
* @return string|null 返回string表示错误信息,返回null表示签到成功
|
||||
*/
|
||||
public static function checkinBotCheckin($mac, $time, $alreadyTip = false)
|
||||
{
|
||||
@@ -190,18 +258,20 @@ class UserBot extends AbstractModel
|
||||
$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 "不在有效时间内,有效时间为:" . date("H:i", $timeAdvance) . "-" . date("H:i", $timeDelay);
|
||||
$errorTime = false;
|
||||
if (Timer::time() < $timeAdvance || $timeDelay < Timer::time()) {
|
||||
$errorTime = "不在有效时间内,有效时间为:" . date("H:i", $timeAdvance) . "-" . date("H:i", $timeDelay);
|
||||
}
|
||||
//
|
||||
$macs = explode(",", $mac);
|
||||
$checkins = [];
|
||||
$array = [];
|
||||
foreach ($macs as $mac) {
|
||||
$mac = strtoupper($mac);
|
||||
$array = [];
|
||||
if (Base::isMac($mac)) {
|
||||
// 路由器签到
|
||||
if ($UserCheckinMac = UserCheckinMac::whereMac($mac)->first()) {
|
||||
$array = [
|
||||
$array[] = [
|
||||
'userid' => $UserCheckinMac->userid,
|
||||
'mac' => $UserCheckinMac->mac,
|
||||
'date' => $nowDate,
|
||||
@@ -211,23 +281,34 @@ class UserBot extends AbstractModel
|
||||
'remark' => $UserCheckinMac->remark,
|
||||
];
|
||||
}
|
||||
} elseif (Base::isNumber($mac)) {
|
||||
} elseif (preg_match('/^(manual|locat|face|checkin)-(\d+)$/i', $mac, $match)) {
|
||||
// 机器签到、手动签到、定位签到
|
||||
$type = str_replace('checkin', 'face', strtolower($match[1]));
|
||||
$mac = intval($match[2]);
|
||||
$remark = match ($type) {
|
||||
'manual' => $setting['manual_remark'] ?: 'Manual',
|
||||
'locat' => $setting['locat_remark'] ?: 'Location',
|
||||
'face' => $setting['face_remark'] ?: 'Machine',
|
||||
default => '',
|
||||
};
|
||||
if ($UserInfo = User::whereUserid($mac)->whereBot(0)->first()) {
|
||||
$array = [
|
||||
$array[] = [
|
||||
'userid' => $UserInfo->userid,
|
||||
'mac' => '00:00:00:00:00:00',
|
||||
'date' => $nowDate,
|
||||
];
|
||||
$checkins[] = [
|
||||
'userid' => $UserInfo->userid,
|
||||
'remark' => '手动签到',
|
||||
'remark' => $remark,
|
||||
];
|
||||
}
|
||||
}
|
||||
if ($array) {
|
||||
$record = UserCheckinRecord::where($array)->first();
|
||||
}
|
||||
if (!$errorTime) {
|
||||
foreach ($array as $item) {
|
||||
$record = UserCheckinRecord::where($item)->first();
|
||||
if (empty($record)) {
|
||||
$record = UserCheckinRecord::createInstance($array);
|
||||
$record = UserCheckinRecord::createInstance($item);
|
||||
}
|
||||
$record->times = Base::array2json(array_merge($record->times, [$nowTime]));
|
||||
$record->report_time = $time;
|
||||
@@ -236,68 +317,102 @@ class UserBot extends AbstractModel
|
||||
}
|
||||
//
|
||||
if ($checkins && $botUser = User::botGetOrCreate('check-in')) {
|
||||
$getJokeSoup = function($type) {
|
||||
$getJokeSoup = function($type, $userid) {
|
||||
$pre = $type == "up" ? "每日开心:" : "心灵鸡汤:";
|
||||
$key = $type == "up" ? "jokes" : "soups";
|
||||
$array = Base::json2array(Cache::get(JokeSoupTask::keyName($key)));
|
||||
if ($array) {
|
||||
$item = $array[array_rand($array)];
|
||||
if ($item) {
|
||||
return $pre . $item;
|
||||
Doo::setLanguage($userid);
|
||||
return Doo::translate($pre . $item);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
$sendMsg = function($type, $checkin) use ($alreadyTip, $getJokeSoup, $botUser, $nowDate) {
|
||||
$sendMsg = function($type, $checkin) use ($errorTime, $alreadyTip, $getJokeSoup, $botUser, $nowDate) {
|
||||
$dialog = WebSocketDialog::checkUserDialog($botUser, $checkin['userid']);
|
||||
if (!$dialog) {
|
||||
return;
|
||||
}
|
||||
// 判断错误
|
||||
if ($errorTime) {
|
||||
if ($alreadyTip) {
|
||||
$text = $errorTime;
|
||||
$text .= $checkin['remark'] ? " ({$checkin['remark']})": "";
|
||||
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'template', [
|
||||
'type' => 'content',
|
||||
'content' => $text,
|
||||
], $botUser->userid, false, false, true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// 判断已打卡
|
||||
$cacheKey = "Checkin::sendMsg-{$nowDate}-{$type}:" . $checkin['userid'];
|
||||
$typeDesc = $type == "up" ? "上班" : "下班";
|
||||
$typeContent = $type == "up" ? "上班" : "下班";
|
||||
if (Cache::get($cacheKey) === "yes") {
|
||||
if ($alreadyTip && $dialog = WebSocketDialog::checkUserDialog($botUser, $checkin['userid'])) {
|
||||
$text = "<p>今日已{$typeDesc}打卡,无需重复打卡。</p>";
|
||||
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'text', ['text' => $text], $botUser->userid, false, false, $type != "up");
|
||||
if ($alreadyTip) {
|
||||
$text = "今日已{$typeContent}打卡,无需重复打卡。";
|
||||
$text .= $checkin['remark'] ? " ({$checkin['remark']})": "";
|
||||
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'template', [
|
||||
'type' => 'content',
|
||||
'content' => $text,
|
||||
], $botUser->userid, false, false, true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
Cache::put($cacheKey, "yes", Carbon::now()->addDay());
|
||||
//
|
||||
if ($dialog = WebSocketDialog::checkUserDialog($botUser, $checkin['userid'])) {
|
||||
$hi = date("H:i");
|
||||
$remark = $checkin['remark'] ? " ({$checkin['remark']})": "";
|
||||
$text = "<p>{$typeDesc}打卡成功,打卡时间: {$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, false, false, $type != "up");
|
||||
}
|
||||
// 打卡成功
|
||||
$hi = date("H:i");
|
||||
$remark = $checkin['remark'] ? " ({$checkin['remark']})": "";
|
||||
$subcontent = $getJokeSoup($type, $checkin['userid']);
|
||||
$title = "{$typeContent}打卡成功,打卡时间: {$hi}{$remark}";
|
||||
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'template', [
|
||||
'type' => 'content',
|
||||
'title' => $title,
|
||||
'content' => [
|
||||
[
|
||||
'content' => $title
|
||||
], [
|
||||
'content' => $subcontent,
|
||||
'language' => false,
|
||||
'style' => 'padding-top:4px;opacity:0.6',
|
||||
]
|
||||
],
|
||||
], $botUser->userid, false, false, $type != "up");
|
||||
};
|
||||
if ($timeAdvance <= Base::time() && Base::time() < $timeEnd) {
|
||||
if ($timeAdvance <= Timer::time() && Timer::time() < $timeEnd) {
|
||||
// 上班打卡通知(从最早打卡时间 到 下班打卡时间)
|
||||
foreach ($checkins as $checkin) {
|
||||
$sendMsg('up', $checkin);
|
||||
}
|
||||
}
|
||||
if ($timeEnd <= Base::time() && Base::time() <= $timeDelay) {
|
||||
if ($timeEnd <= Timer::time() && Timer::time() <= $timeDelay) {
|
||||
// 下班打卡通知(下班打卡时间 到 最晚打卡时间)
|
||||
foreach ($checkins as $checkin) {
|
||||
$sendMsg('down', $checkin);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐私机器人
|
||||
* @param $command
|
||||
* @return string
|
||||
* @return array
|
||||
*/
|
||||
public static function anonBotQuickMsg($command)
|
||||
{
|
||||
return match ($command) {
|
||||
"help" => "使用说明:打开你想要发匿名消息的个人对话,点击输入框右边的 ⊕ 号,选择 <u>匿名消息</u> 即可输入你想要发送的匿名消息内容。",
|
||||
"privacy" => "匿名消息将通过 <u>匿名消息(机器人)</u> 发送给对方,不会记录你的身份信息。",
|
||||
default => '',
|
||||
"help" => [
|
||||
"title" => "匿名消息使用说明",
|
||||
"content" => "使用说明:打开你想要发匿名消息的个人对话,点击输入框右边的 ⊕ 号,选择「匿名消息」即可输入你想要发送的匿名消息内容。"
|
||||
],
|
||||
"privacy" => [
|
||||
"title" => "匿名消息隐私说明",
|
||||
"content" => "匿名消息将通过「匿名消息(机器人)」发送给对方,不会记录你的身份信息。"
|
||||
],
|
||||
default => [],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
109
app/Models/UserCheckinFace.php
Normal file
109
app/Models/UserCheckinFace.php
Normal file
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Exceptions\ApiException;
|
||||
use App\Module\Base;
|
||||
use App\Module\Ihttp;
|
||||
|
||||
/**
|
||||
* App\Models\UserCheckinFace
|
||||
*
|
||||
* @property int $id
|
||||
* @property int|null $userid 会员id
|
||||
* @property string|null $faceimg 人脸图片
|
||||
* @property int|null $status 状态
|
||||
* @property string|null $remark 备注
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel cancelAppend()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel cancelHidden()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel change($array)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel getKeyValue()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinFace newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinFace newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinFace query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel remove()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel saveOrIgnore()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinFace whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinFace whereFaceimg($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinFace whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinFace whereRemark($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinFace whereStatus($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinFace whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinFace whereUserid($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class UserCheckinFace extends AbstractModel
|
||||
{
|
||||
|
||||
public static function saveFace($userid, $nickname, $faceimg, $remark='')
|
||||
{
|
||||
// 取上传图片的URL
|
||||
$faceimg = Base::unFillUrl($faceimg);
|
||||
$record = "";
|
||||
if ($faceimg != '') {
|
||||
$faceFile = public_path($faceimg);
|
||||
$record = base64_encode(file_get_contents($faceFile));
|
||||
}
|
||||
|
||||
$url = 'http://' . env('APP_IPPR') . '.14' . ":7788/user";
|
||||
$data = [
|
||||
'name' => $nickname,
|
||||
'enrollid' => $userid,
|
||||
'admin' => 0,
|
||||
'backupnum' => 50,
|
||||
];
|
||||
if ($record != '') {
|
||||
$data['record'] = $record;
|
||||
}
|
||||
|
||||
$res = Ihttp::ihttp_post($url, json_encode($data), 15);
|
||||
if($res['data'] && $data = json_decode($res['data'])){
|
||||
if($data->ret != 1 && $data->msg){
|
||||
throw new ApiException($data->msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return AbstractModel::transaction(function() use ($userid, $faceimg, $remark) {
|
||||
$checkinFace = self::query()->whereUserid($userid)->first();
|
||||
if ($checkinFace) {
|
||||
self::updateData(['id' => $checkinFace->id], [
|
||||
'faceimg' => $faceimg,
|
||||
'status' => 1,
|
||||
'remark' => $remark
|
||||
]);
|
||||
} else {
|
||||
$checkinFace = new UserCheckinFace();
|
||||
$checkinFace->faceimg = $faceimg;
|
||||
$checkinFace->userid = $userid;
|
||||
$checkinFace->remark = $remark;
|
||||
$checkinFace->save();
|
||||
}
|
||||
if ($faceimg == '') {
|
||||
$res = UserCheckinFace::deleteDeviceUser($userid);
|
||||
if ($res) {
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
return Base::retSuccess('设置成功');
|
||||
});
|
||||
}
|
||||
|
||||
public static function deleteDeviceUser($userid) {
|
||||
$url = 'http://' . env('APP_IPPR') . '.14' . ":7788/user/delete";
|
||||
$data = [
|
||||
'enrollid' => $userid,
|
||||
'backupnum' => 50, // 13 删除整个用户 50 删除图片
|
||||
];
|
||||
|
||||
$res = Ihttp::ihttp_post($url, json_encode($data));
|
||||
if($res['data'] && $data = json_decode($res['data'])){
|
||||
if($data->ret != 1 && $data->msg){
|
||||
throw new ApiException($data->msg);
|
||||
// return Base::retError($data->msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,7 @@ class UserDelete extends AbstractModel
|
||||
$value = Base::json2array($value);
|
||||
// 昵称
|
||||
if (!$value['nickname']) {
|
||||
$value['nickname'] = Base::cardFormat($value['email']);
|
||||
$value['nickname'] = Base::formatName($value['email']);
|
||||
}
|
||||
// 头像
|
||||
$value['userimg'] = User::getAvatar($value['userid'], $value['userimg'], $value['email'], $value['nickname']);
|
||||
@@ -71,7 +71,7 @@ class UserDelete extends AbstractModel
|
||||
}
|
||||
$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');
|
||||
$cache['delete_at'] = $row->created_at->toDateTimeString();
|
||||
return $cache;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ namespace App\Models;
|
||||
|
||||
use App\Exceptions\ApiException;
|
||||
use App\Module\Base;
|
||||
use App\Module\Doo;
|
||||
use App\Module\Timer;
|
||||
use Carbon\Carbon;
|
||||
use Guanguans\Notify\Factory;
|
||||
use Guanguans\Notify\Messages\EmailMessage;
|
||||
@@ -64,36 +66,48 @@ class UserEmailVerification extends AbstractModel
|
||||
]);
|
||||
$row->save();
|
||||
$setting = Base::setting('emailSetting');
|
||||
$alias = Base::settingFind('system', 'system_alias', 'Task');
|
||||
try {
|
||||
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>";
|
||||
$subject = Doo::translate($alias . "修改邮箱验证");
|
||||
$content = sprintf("<p>%s</p><p style='color: #0000DD;'><u>%s</u></p><p>%s</p>",
|
||||
Doo::translate($user->nickname . " 您好,您正在修改 " . $alias . " 的邮箱,验证码如下。请在30分钟内输入验证码"),
|
||||
$code,
|
||||
Doo::translate("如果不是本人操作,您的帐号可能存在风险,请及时修改密码!")
|
||||
);
|
||||
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>";
|
||||
$subject = Doo::translate($alias . "注销帐号验证");
|
||||
$content = sprintf("<p>%s</p><p style='color: #0000DD;'><u>%s</u></p><p>%s</p>",
|
||||
Doo::translate($user->nickname . " 您好,您正在注销 " . $alias . " 的帐号,验证码如下。请在30分钟内输入验证码"),
|
||||
$code,
|
||||
Doo::translate("如果不是本人操作,您的帐号可能存在风险,请及时修改密码!")
|
||||
);
|
||||
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>";
|
||||
$subject = Doo::translate($alias . "绑定邮箱验证");
|
||||
$content = sprintf("<p>%s</p><p style='display: flex; justify-content: center;'>%s</p>",
|
||||
Doo::translate($user->nickname . " 您好,您正在绑定 " . $alias . " 的邮箱,请于30分钟之内点击以下链接完成验证:"),
|
||||
"<a href='{$url}' target='_blank'>{$url}</a>"
|
||||
);
|
||||
break;
|
||||
}
|
||||
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']}>")
|
||||
->from($alias . " <{$setting['account']}>")
|
||||
->to($email)
|
||||
->subject($subject)
|
||||
->html($content))
|
||||
->send();
|
||||
} catch (\Throwable $e) {
|
||||
if (str_contains($e->getMessage(), "Timed Out")) {
|
||||
throw new ApiException("language.TimedOut");
|
||||
throw new ApiException("邮件发送超时,请检查邮箱配置是否正确");
|
||||
} elseif ($e->getCode() === 550) {
|
||||
throw new ApiException('邮件内容被拒绝,请检查邮箱是否开启接收功能');
|
||||
} else {
|
||||
@@ -122,7 +136,7 @@ class UserEmailVerification extends AbstractModel
|
||||
}
|
||||
|
||||
$oldTime = Carbon::parse($emailVerify->created_at)->timestamp;
|
||||
$time = Base::Time();
|
||||
$time = Timer::Time();
|
||||
|
||||
// 30分钟失效
|
||||
if (abs($time - $oldTime) > 1800) {
|
||||
|
||||
@@ -50,28 +50,58 @@ class UserTransfer extends AbstractModel
|
||||
// 移交文件
|
||||
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,
|
||||
]);
|
||||
}
|
||||
$this->exitDialog();
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出群组
|
||||
* @return void
|
||||
*/
|
||||
public function exitDialog()
|
||||
{
|
||||
$lastId = 0;
|
||||
$limit = 100;
|
||||
while (true) {
|
||||
$query = 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('web_socket_dialogs.group_type', '!=', 'okr')
|
||||
->where('u.userid', $this->original_userid)
|
||||
->orderBy('web_socket_dialogs.id')
|
||||
->limit($limit);
|
||||
if ($lastId) {
|
||||
$query->where('web_socket_dialogs.id', '>', $lastId);
|
||||
}
|
||||
$list = $query->get();
|
||||
|
||||
// 没有数据了就退出
|
||||
if ($list->isEmpty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
// 记录最后一条记录的ID
|
||||
$lastId = $list->last()->id;
|
||||
|
||||
// 离职员工退出群
|
||||
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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 如果返回的数据少于限制数,说明已经是最后一批
|
||||
if ($list->count() < $limit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ use App\Tasks\PushTask;
|
||||
use Cache;
|
||||
use Carbon\Carbon;
|
||||
use Hhxsv5\LaravelS\Swoole\Task\Task;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
@@ -77,6 +78,23 @@ class WebSocketDialog extends AbstractModel
|
||||
return $this->hasMany(WebSocketDialogUser::class, 'dialog_id', 'id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取对话成员(连表查)
|
||||
* @param $addField
|
||||
* @return User|\Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder
|
||||
*/
|
||||
public function dialogUserBuilder($addField = [])
|
||||
{
|
||||
$columns = array_map(function ($column) {
|
||||
return "users." . $column;
|
||||
}, array_merge(User::$basicField, $addField));
|
||||
$columns[] = "du.*";
|
||||
return User::select($columns)
|
||||
->join('web_socket_dialog_users as du', 'users.userid', '=', 'du.userid')
|
||||
->where('du.dialog_id', $this->id)
|
||||
->whereNull('users.disable_at');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取对话列表
|
||||
@@ -87,9 +105,11 @@ class WebSocketDialog extends AbstractModel
|
||||
*/
|
||||
public static function getDialogList($userid, $updated = "", $deleted = "")
|
||||
{
|
||||
$builder = WebSocketDialog::select(['web_socket_dialogs.*', 'u.top_at', 'u.last_at', 'u.mark_unread', 'u.silence', 'u.hide', 'u.color', 'u.updated_at as user_at'])
|
||||
->join('web_socket_dialog_users as u', 'web_socket_dialogs.id', '=', 'u.dialog_id')
|
||||
->where('u.userid', $userid);
|
||||
$builder = DB::table('web_socket_dialog_users as u')
|
||||
->select(['d.*', 'u.top_at', 'u.last_at', 'u.mark_unread', 'u.silence', 'u.hide', 'u.color', 'u.updated_at as user_at'])
|
||||
->join('web_socket_dialogs as d', 'u.dialog_id', '=', 'd.id')
|
||||
->where('u.userid', $userid)
|
||||
->whereNull('d.deleted_at');
|
||||
if ($updated) {
|
||||
$builder->where('u.updated_at', '>', $updated);
|
||||
}
|
||||
@@ -97,8 +117,8 @@ class WebSocketDialog extends AbstractModel
|
||||
->orderByDesc('u.top_at')
|
||||
->orderByDesc('u.last_at')
|
||||
->paginate(Base::getPaginate(100, 50));
|
||||
$list->transform(function (WebSocketDialog $item) use ($userid) {
|
||||
return $item->formatData($userid);
|
||||
$list->transform(function ($item) use ($userid) {
|
||||
return self::synthesizeData($item, $userid);
|
||||
});
|
||||
//
|
||||
$data = $list->toArray();
|
||||
@@ -122,241 +142,259 @@ class WebSocketDialog extends AbstractModel
|
||||
$array = [];
|
||||
if ($unreadAt) {
|
||||
// 未读对话
|
||||
$list = WebSocketDialog::select(['web_socket_dialogs.*', 'u.top_at', 'u.last_at', 'u.mark_unread', 'u.silence', 'u.hide', 'u.color', 'u.updated_at as user_at'])
|
||||
->join('web_socket_dialog_users as u', 'web_socket_dialogs.id', '=', 'u.dialog_id')
|
||||
->join('web_socket_dialog_msg_reads as r', 'web_socket_dialogs.id', '=', 'r.dialog_id')
|
||||
$list = DB::table('web_socket_dialog_users as u')
|
||||
->select(['d.*', 'u.top_at', 'u.last_at', 'u.mark_unread', 'u.silence', 'u.hide', 'u.color', 'u.updated_at as user_at'])
|
||||
->join('web_socket_dialogs as d', 'u.dialog_id', '=', 'd.id')
|
||||
->join('web_socket_dialog_msg_reads as r', 'd.id', '=', 'r.dialog_id')
|
||||
->where('u.userid', $userid)
|
||||
->where('u.last_at', '<', $unreadAt)
|
||||
->whereNull('d.deleted_at')
|
||||
->where('r.userid', $userid)
|
||||
->where('r.read_at')
|
||||
->where('u.last_at', '<', $unreadAt)
|
||||
->groupBy('u.dialog_id')
|
||||
->take(20)
|
||||
->get();
|
||||
$list->transform(function (WebSocketDialog $item) use ($userid, &$ids, &$array) {
|
||||
$list->transform(function ($item) use ($userid, &$ids, &$array) {
|
||||
if (!in_array($item->id, $ids)) {
|
||||
$ids[] = $item->id;
|
||||
$array[] = $item->formatData($userid);
|
||||
$array[] = self::synthesizeData($item, $userid);
|
||||
}
|
||||
});
|
||||
// 标记未读会话
|
||||
$list = WebSocketDialog::select(['web_socket_dialogs.*', 'u.top_at', 'u.last_at', 'u.mark_unread', 'u.silence', 'u.hide', 'u.color', 'u.updated_at as user_at'])
|
||||
->join('web_socket_dialog_users as u', 'web_socket_dialogs.id', '=', 'u.dialog_id')
|
||||
$list = DB::table('web_socket_dialog_users as u')
|
||||
->select(['d.*', 'u.top_at', 'u.last_at', 'u.mark_unread', 'u.silence', 'u.hide', 'u.color', 'u.updated_at as user_at'])
|
||||
->join('web_socket_dialogs as d', 'u.dialog_id', '=', 'd.id')
|
||||
->where('u.userid', $userid)
|
||||
->where('u.mark_unread', 1)
|
||||
->where('u.last_at', '<', $unreadAt)
|
||||
->whereNull('d.deleted_at')
|
||||
->take(20)
|
||||
->get();
|
||||
$list->transform(function (WebSocketDialog $item) use ($userid, &$ids, &$array) {
|
||||
$list->transform(function ($item) use ($userid, &$ids, &$array) {
|
||||
if (!in_array($item->id, $ids)) {
|
||||
$ids[] = $item->id;
|
||||
$array[] = $item->formatData($userid);
|
||||
$array[] = self::synthesizeData($item, $userid);
|
||||
}
|
||||
});
|
||||
}
|
||||
if ($todoAt) {
|
||||
// 待办会话
|
||||
$list = WebSocketDialog::select(['web_socket_dialogs.*', 'u.top_at', 'u.last_at', 'u.mark_unread', 'u.silence', 'u.hide', 'u.color', 'u.updated_at as user_at'])
|
||||
->join('web_socket_dialog_users as u', 'web_socket_dialogs.id', '=', 'u.dialog_id')
|
||||
->join('web_socket_dialog_msg_todos as t', 'web_socket_dialogs.id', '=', 't.dialog_id')
|
||||
$list = DB::table('web_socket_dialog_users as u')
|
||||
->select(['d.*', 'u.top_at', 'u.last_at', 'u.mark_unread', 'u.silence', 'u.hide', 'u.color', 'u.updated_at as user_at'])
|
||||
->join('web_socket_dialogs as d', 'u.dialog_id', '=', 'd.id')
|
||||
->join('web_socket_dialog_msg_todos as t', 'd.id', '=', 't.dialog_id')
|
||||
->where('u.userid', $userid)
|
||||
->where('u.last_at', '<', $todoAt)
|
||||
->whereNull('d.deleted_at')
|
||||
->where('t.userid', $userid)
|
||||
->where('t.done_at')
|
||||
->where('u.last_at', '<', $todoAt)
|
||||
->groupBy('u.dialog_id')
|
||||
->take(20)
|
||||
->get();
|
||||
$list->transform(function (WebSocketDialog $item) use ($userid, &$ids, &$array) {
|
||||
$list->transform(function ($item) use ($userid, &$ids, &$array) {
|
||||
if (!in_array($item->id, $ids)) {
|
||||
$ids[] = $item->id;
|
||||
$array[] = $item->formatData($userid);
|
||||
$array[] = self::synthesizeData($item, $userid);
|
||||
}
|
||||
});
|
||||
}
|
||||
return $array;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 格式化对话
|
||||
* 综合数据
|
||||
* @param $data
|
||||
* @param int $userid 会员ID
|
||||
* @param bool $hasData
|
||||
* @return $this
|
||||
* @param bool $hasData 已存在的消息类型
|
||||
* @return array
|
||||
*/
|
||||
public function formatData($userid, $hasData = false)
|
||||
public static function synthesizeData($data, $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;
|
||||
};
|
||||
//
|
||||
$time = Carbon::parse($this->user_at ?? $dialogUserFun('updated_at'));
|
||||
$this->hide = $this->hide ?? $dialogUserFun('hide');
|
||||
$this->top_at = $this->top_at ?? $dialogUserFun('top_at');
|
||||
$this->last_at = $this->last_at ?? $dialogUserFun('last_at');
|
||||
$this->user_at = $time->toDateTimeString('millisecond');
|
||||
$this->user_ms = $time->valueOf();
|
||||
//
|
||||
if (isset($this->search_msg_id)) {
|
||||
// 最后消息 (搜索预览消息)
|
||||
$this->last_msg = WebSocketDialogMsg::whereDialogId($this->id)->find($this->search_msg_id);
|
||||
$this->last_at = $this->last_msg ? Carbon::parse($this->last_msg->created_at)->format('Y-m-d H:i:s') : null;
|
||||
} else {
|
||||
// 未读信息
|
||||
if (Base::judgeClientVersion("0.34.0")) {
|
||||
$this->generateUnread($userid);
|
||||
} else {
|
||||
$this->generateUnread_03398($userid, $hasData);
|
||||
}
|
||||
// 未读标记
|
||||
$this->mark_unread = $this->mark_unread ?? $dialogUserFun('mark_unread');
|
||||
// 是否免打扰
|
||||
$this->silence = $this->silence ?? $dialogUserFun('silence');
|
||||
// 对话人数
|
||||
$this->people = WebSocketDialogUser::whereDialogId($this->id)->count();
|
||||
// 有待办
|
||||
$this->todo_num = WebSocketDialogMsgTodo::whereDialogId($this->id)->whereUserid($userid)->whereDoneAt(null)->count();
|
||||
// 最后消息
|
||||
$this->last_msg = WebSocketDialogMsg::whereDialogId($this->id)->orderByDesc('id')->first();
|
||||
// 判断数据
|
||||
if (is_numeric($data)) {
|
||||
$data = WebSocketDialog::find($data)?->toArray();
|
||||
} elseif ($data instanceof Model) {
|
||||
$data = $data->toArray();
|
||||
} elseif (is_object($data)) {
|
||||
$data = (array)$data;
|
||||
}
|
||||
if (!is_array($data) || !isset($data['id'])) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
// 会话必要字段
|
||||
$fields = [
|
||||
'id', 'type', 'group_type', 'name', 'avatar', 'owner_id', 'link_id', 'top_userid', 'top_msg_id', 'created_at', 'updated_at', 'deleted_at',
|
||||
];
|
||||
if (!empty(array_diff($fields, array_keys($data)))) {
|
||||
// 补全数据
|
||||
foreach ($fields as $field) {
|
||||
$data[$field] = $data[$field] ?? null;
|
||||
}
|
||||
}
|
||||
$data['avatar'] = Base::fillUrl($data['avatar']);
|
||||
|
||||
// 会员必要字段
|
||||
$fields = [
|
||||
'top_at', 'last_at', 'mark_unread', 'silence', 'hide', 'color', 'user_at',
|
||||
];
|
||||
if (!empty(array_diff($fields, array_keys($data)))) {
|
||||
// 补全数据(查询数据库)
|
||||
$array = WebSocketDialogUser::whereDialogId($data['id'])->whereUserid($userid)->first()?->toArray();
|
||||
foreach ($fields as $field) {
|
||||
if ($field === 'user_at') {
|
||||
$data[$field] = $data[$field] ?? $array['updated_at'] ?? null;
|
||||
} else {
|
||||
$data[$field] = $data[$field] ?? $array[$field] ?? null;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 会员数据处理
|
||||
if (isset($data['user_at']) && !isset($data['user_ms'])) {
|
||||
$time = Carbon::parse($data['user_at']);
|
||||
$data['user_at'] = $time->toDateTimeString('millisecond');
|
||||
$data['user_ms'] = $time->valueOf();
|
||||
}
|
||||
|
||||
// 信息数据
|
||||
if (isset($data['search_msg_id'])) {
|
||||
// 最后消息 (搜索预览消息)
|
||||
$data['last_msg'] = $data['last_msg'] ?? WebSocketDialogMsg::whereDialogId($data['id'])->find($data['search_msg_id'])?->toArray();
|
||||
$data['last_at'] = $data['last_msg'] ? Carbon::parse($data['last_msg']['created_at'])->toDateTimeString() : null;
|
||||
} else {
|
||||
// 未读消息
|
||||
$data = array_merge($data, self::generateUnread($data['id'], $userid));
|
||||
// 对话人数
|
||||
$data['people'] = $data['people'] ?? WebSocketDialogUser::whereDialogId($data['id'])->count();
|
||||
// 有待办
|
||||
$data['todo_num'] = $data['todo_num'] ?? WebSocketDialogMsgTodo::whereDialogId($data['id'])->whereUserid($userid)->whereDoneAt(null)->count();
|
||||
// 最后消息
|
||||
$data['last_msg'] = $data['last_msg'] ?? WebSocketDialogMsg::whereDialogId($data['id'])->orderByDesc('id')->first()?->toArray();
|
||||
}
|
||||
$data['last_msg'] = self::lastMsgFormat($data['last_msg']);
|
||||
|
||||
// 对方信息
|
||||
$this->pinyin = Base::cn2pinyin($this->name);
|
||||
$this->quick_msgs = [];
|
||||
$this->dialog_user = null;
|
||||
$this->group_info = null;
|
||||
$this->bot = 0;
|
||||
switch ($this->type) {
|
||||
$data['pinyin'] = Base::cn2pinyin($data['name']);
|
||||
$data['quick_msgs'] = [];
|
||||
$data['dialog_user'] = null;
|
||||
$data['group_info'] = null;
|
||||
$data['bot'] = 0;
|
||||
switch ($data['type']) {
|
||||
case "user":
|
||||
$dialog_user = WebSocketDialogUser::whereDialogId($this->id)->where('userid', '!=', $userid)->first();
|
||||
$dialog_user = WebSocketDialogUser::whereDialogId($data['id'])->where('userid', '!=', $userid)->first();
|
||||
if ($dialog_user->userid === 0) {
|
||||
$dialog_user->userid = $userid;
|
||||
}
|
||||
$basic = User::userid2basic($dialog_user->userid);
|
||||
if ($basic) {
|
||||
$this->name = $basic->nickname;
|
||||
$this->email = $basic->email;
|
||||
$this->userimg = $basic->userimg;
|
||||
$this->bot = $basic->getBotOwner();
|
||||
$this->quick_msgs = UserBot::quickMsgs($basic->email);
|
||||
$data['name'] = $basic->nickname;
|
||||
$data['email'] = $basic->email;
|
||||
$data['userimg'] = $basic->userimg;
|
||||
$data['bot'] = $basic->getBotOwner();
|
||||
$data['quick_msgs'] = UserBot::quickMsgs($basic->email);
|
||||
} else {
|
||||
$this->name = 'non-existent';
|
||||
$this->dialog_delete = 1;
|
||||
$data['name'] = 'non-existent';
|
||||
$data['dialog_delete'] = 1;
|
||||
}
|
||||
$this->dialog_user = $dialog_user;
|
||||
$this->dialog_mute = Base::settingFind('system', 'user_private_chat_mute');
|
||||
$data['dialog_user'] = $dialog_user;
|
||||
$data['dialog_mute'] = Base::settingFind('system', 'user_private_chat_mute');
|
||||
break;
|
||||
case "group":
|
||||
switch ($this->group_type) {
|
||||
switch ($data['group_type']) {
|
||||
case 'user':
|
||||
$this->dialog_mute = Base::settingFind('system', 'user_group_chat_mute');
|
||||
$data['dialog_mute'] = Base::settingFind('system', 'user_group_chat_mute');
|
||||
break;
|
||||
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;
|
||||
$data['group_info'] = Project::withTrashed()->select(['id', 'name', 'archived_at', 'deleted_at'])->whereDialogId($data['id'])->first()?->cancelAppend()->cancelHidden()->toArray();
|
||||
if ($data['group_info']) {
|
||||
$data['name'] = $data['group_info']['name'];
|
||||
} else {
|
||||
$this->name = '[Delete]';
|
||||
$this->dialog_delete = 1;
|
||||
$data['name'] = '[Delete]';
|
||||
$data['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;
|
||||
$data['group_info'] = ProjectTask::withTrashed()->select(['id', 'name', 'complete_at', 'archived_at', 'deleted_at'])->whereDialogId($data['id'])->first()?->cancelAppend()->cancelHidden()->toArray();
|
||||
if ($data['group_info']) {
|
||||
$data['name'] = $data['group_info']['name'];
|
||||
} else {
|
||||
$this->name = '[Delete]';
|
||||
$this->dialog_delete = 1;
|
||||
$data['name'] = '[Delete]';
|
||||
$data['dialog_delete'] = 1;
|
||||
}
|
||||
break;
|
||||
case 'all':
|
||||
$this->name = Doo::translate('全体成员');
|
||||
$this->dialog_mute = Base::settingFind('system', 'all_group_mute');
|
||||
$data['name'] = Doo::translate('全体成员');
|
||||
$data['dialog_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_todo = $msgBuilder->clone()->where('todo', '>', 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();
|
||||
Cache::forever("Dialog::tag:" . $this->id, Base::array2json([
|
||||
'has_tag' => $this->has_tag,
|
||||
'has_todo' => $this->has_todo,
|
||||
'has_image' => $this->has_image,
|
||||
'has_file' => $this->has_file,
|
||||
'has_link' => $this->has_link,
|
||||
$msgBuilder = WebSocketDialogMsg::whereDialogId($data['id']);
|
||||
$data['has_tag'] = $msgBuilder->clone()->where('tag', '>', 0)->exists();
|
||||
$data['has_todo'] = $msgBuilder->clone()->where('todo', '>', 0)->exists();
|
||||
$data['has_image'] = $msgBuilder->clone()->whereMtype('image')->exists();
|
||||
$data['has_file'] = $msgBuilder->clone()->whereMtype('file')->exists();
|
||||
$data['has_link'] = $msgBuilder->clone()->whereLink(1)->exists();
|
||||
Cache::forever("Dialog::tag:" . $data['id'], Base::array2json([
|
||||
'has_tag' => $data['has_tag'],
|
||||
'has_todo' => $data['has_todo'],
|
||||
'has_image' => $data['has_image'],
|
||||
'has_file' => $data['has_file'],
|
||||
'has_link' => $data['has_link'],
|
||||
]));
|
||||
} else {
|
||||
$tagData = Base::json2array(Cache::get("Dialog::tag:" . $this->id));
|
||||
$tagData = Base::json2array(Cache::get("Dialog::tag:" . $data['id']));
|
||||
if ($tagData) {
|
||||
$this->has_tag = !!$tagData['has_tag'];
|
||||
$this->has_todo = !!$tagData['has_todo'];
|
||||
$this->has_image = !!$tagData['has_image'];
|
||||
$this->has_file = !!$tagData['has_file'];
|
||||
$this->has_link = !!$tagData['has_link'];
|
||||
$data['has_tag'] = !!$tagData['has_tag'];
|
||||
$data['has_todo'] = !!$tagData['has_todo'];
|
||||
$data['has_image'] = !!$tagData['has_image'];
|
||||
$data['has_file'] = !!$tagData['has_file'];
|
||||
$data['has_link'] = !!$tagData['has_link'];
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成未读数据
|
||||
* @param $userid
|
||||
* @return $this
|
||||
* 格式化最后消息
|
||||
* @param array $lastMsg
|
||||
* @return array
|
||||
*/
|
||||
public function generateUnread($userid)
|
||||
public static function lastMsgFormat($lastMsg)
|
||||
{
|
||||
$builder = WebSocketDialogMsgRead::whereDialogId($this->id)->whereUserid($userid)->whereReadAt(null);
|
||||
if ($lastMsg && $lastMsg['type'] != 'preview') {
|
||||
$msgData = $lastMsg;
|
||||
$msgData['emoji'] = Base::array_only_recursive($msgData['emoji'], ['symbol']);
|
||||
$msgData['msg'] = ['preview' => WebSocketDialogMsg::previewMsg($msgData)];
|
||||
$msgData['type'] = 'preview';
|
||||
$lastMsg = array_intersect_key($msgData, array_flip(['id', 'type', 'msg', 'userid', 'percentage', 'emoji', 'created_at']));
|
||||
}
|
||||
return $lastMsg;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取未读数据
|
||||
* @param $dialogId
|
||||
* @param $userid
|
||||
* @return array
|
||||
*/
|
||||
public static function generateUnread($dialogId, $userid)
|
||||
{
|
||||
$data = [];
|
||||
// 未读消息
|
||||
$this->unread = $builder->count();
|
||||
$builder = WebSocketDialogMsgRead::whereDialogId($dialogId)->whereUserid($userid)->whereReadAt(null);
|
||||
// 总未读消息
|
||||
$data['unread'] = $builder->clone()->count();
|
||||
// 最早一条未读消息
|
||||
$this->unread_one = $this->unread > 0 ? intval($builder->clone()->orderBy('msg_id')->value('msg_id')) : 0;
|
||||
$data['unread_one'] = $data['unread'] > 0 ? intval($builder->clone()->orderBy('msg_id')->value('msg_id')) : 0;
|
||||
// @我的消息
|
||||
$this->mention = $this->unread > 0 ? $builder->clone()->whereMention(1)->count() : 0;
|
||||
$data['mention'] = $data['unread'] > 0 ? $builder->clone()->whereMention(1)->count() : 0;
|
||||
// @我的消息(id集合)
|
||||
$this->mention_ids = $this->mention > 0 ? $builder->clone()->whereMention(1)->orderByDesc('msg_id')->take(20)->pluck('msg_id')->toArray() : [];
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成未读数据 // todo: 旧版兼容,后续删除
|
||||
* @param $userid
|
||||
* @param $positionData
|
||||
* @return $this
|
||||
*/
|
||||
public function generateUnread_03398($userid, $positionData = false)
|
||||
{
|
||||
$builder = WebSocketDialogMsgRead::whereDialogId($this->id)->whereUserid($userid)->whereReadAt(null);
|
||||
$this->unread = $builder->count();
|
||||
$this->mention = $this->unread > 0 ? $builder->clone()->whereMention(1)->count() : 0;
|
||||
if ($positionData) {
|
||||
$array = [];
|
||||
// @我的消息
|
||||
if ($this->mention > 0) {
|
||||
$list = $builder->clone()->whereMention(1)->orderByDesc('msg_id')->take(20)->get();
|
||||
foreach ($list as $item) {
|
||||
$array[] = [
|
||||
'msg_id' => $item->msg_id,
|
||||
'label' => Doo::translate('@我的消息'),
|
||||
];
|
||||
}
|
||||
}
|
||||
// 最早一条未读消息
|
||||
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;
|
||||
$data['mention_ids'] = $data['mention'] > 0 ? $builder->clone()->whereMention(1)->orderByDesc('msg_id')->take(20)->pluck('msg_id')->toArray() : [];
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -674,27 +712,22 @@ class WebSocketDialog extends AbstractModel
|
||||
* 获取会员对话(没有自动创建)
|
||||
* @param User $user 发起会话的会员
|
||||
* @param int $receiver 另一个会员ID
|
||||
* @return self|null
|
||||
* @return WebSocketDialog|null
|
||||
*/
|
||||
public static function checkUserDialog($user, $receiver)
|
||||
{
|
||||
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', $user->userid)
|
||||
->where('u2.userid', $receiver)
|
||||
->where('web_socket_dialogs.type', 'user')
|
||||
->first();
|
||||
$dialogUser = self::getUserDialog($user->userid, $receiver, 0, $cacheKey);
|
||||
if ($dialogUser) {
|
||||
return $dialogUser;
|
||||
}
|
||||
if ($receiver > 0 && $user->isTemp() && !User::whereUserid($receiver)->whereBot(1)->exists() ) {
|
||||
throw new ApiException('无法发起会话,请联系管理员。');
|
||||
}
|
||||
return AbstractModel::transaction(function () use ($receiver, $user) {
|
||||
return AbstractModel::transaction(function () use ($cacheKey, $receiver, $user) {
|
||||
Cache::forget($cacheKey);
|
||||
$dialog = self::createInstance([
|
||||
'type' => 'user',
|
||||
]);
|
||||
@@ -711,12 +744,47 @@ class WebSocketDialog extends AbstractModel
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户对话(支持缓存)
|
||||
* @param $userid1
|
||||
* @param $userid2
|
||||
* @param $ttl
|
||||
* @param null $cacheKey
|
||||
* @return \Illuminate\Database\Eloquent\Builder|WebSocketDialog|null
|
||||
*/
|
||||
public static function getUserDialog($userid1, $userid2, $ttl, &$cacheKey = null)
|
||||
{
|
||||
$userids = [$userid1, $userid2];
|
||||
sort($userids);
|
||||
$cacheKey = "Dialog::user:" . implode('-', $userids);
|
||||
if (empty($ttl)) {
|
||||
return WebSocketDialog::query()
|
||||
->whereType('user')
|
||||
->whereExists(function ($query) use ($userids) {
|
||||
$query->select(DB::raw(1))
|
||||
->from('web_socket_dialog_users')
|
||||
->whereColumn('web_socket_dialog_users.dialog_id', 'web_socket_dialogs.id')
|
||||
->where('web_socket_dialog_users.userid', $userids[0]);
|
||||
})
|
||||
->whereExists(function ($query) use ($userids) {
|
||||
$query->select(DB::raw(1))
|
||||
->from('web_socket_dialog_users')
|
||||
->whereColumn('web_socket_dialog_users.dialog_id', 'web_socket_dialogs.id')
|
||||
->where('web_socket_dialog_users.userid', $userids[1]);
|
||||
})
|
||||
->first();
|
||||
}
|
||||
return Cache::remember($cacheKey, $ttl, function() use ($userids) {
|
||||
return self::getUserDialog($userids[0], $userids[1], 0);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送消息文件
|
||||
*
|
||||
* @param User $user 发起会话的会员
|
||||
* @param array $dialogIds 对话id
|
||||
* @param file $files 文件对象
|
||||
* @param file|mixed $files 文件对象
|
||||
* @param string $image64 base64文件
|
||||
* @param string $fileName 文件名称
|
||||
* @param int $replyId 恢复id
|
||||
@@ -727,6 +795,7 @@ class WebSocketDialog extends AbstractModel
|
||||
{
|
||||
$filePath = '';
|
||||
$result = [];
|
||||
$data = [];
|
||||
foreach ($dialogIds as $dialog_id) {
|
||||
$dialog = WebSocketDialog::checkDialog($dialog_id);
|
||||
//
|
||||
@@ -737,18 +806,19 @@ class WebSocketDialog extends AbstractModel
|
||||
"image64" => $image64,
|
||||
"path" => $path,
|
||||
"fileName" => $fileName,
|
||||
"quality" => 85
|
||||
]);
|
||||
} else if ($filePath) {
|
||||
Base::makeDir(public_path($path));
|
||||
copy($filePath, public_path($path) . basename($filePath));
|
||||
} else {
|
||||
$setting = Base::setting('system');
|
||||
$data = Base::upload([
|
||||
"file" => $files,
|
||||
"type" => 'more',
|
||||
"path" => $path,
|
||||
"fileName" => $fileName,
|
||||
"size" => ($setting['file_upload_limit'] ?: 0) * 1024
|
||||
"quality" => 100,
|
||||
"convertVideo" => true
|
||||
]);
|
||||
}
|
||||
//
|
||||
|
||||
@@ -149,19 +149,33 @@ class WebSocketDialogMsg extends AbstractModel
|
||||
if (!is_array($msg)) {
|
||||
$msg = Base::json2array($msg);
|
||||
}
|
||||
if ($type === 'file') {
|
||||
$msg['type'] = in_array($msg['ext'], ['jpg', 'jpeg', 'webp', 'png', 'gif']) ? 'img' : 'file';
|
||||
$msg['path'] = Base::fillUrl($msg['path']);
|
||||
$msg['thumb'] = Base::fillUrl($msg['thumb'] ?: Base::extIcon($msg['ext']));
|
||||
} else if ($type === 'record') {
|
||||
$msg['path'] = Base::fillUrl($msg['path']);
|
||||
$textUserid = is_array($msg['text_userid']) ? $msg['text_userid'] : [];
|
||||
if (isset($msg['text_userid'])) {
|
||||
unset($msg['text_userid']);
|
||||
}
|
||||
if ($msg['text'] && !in_array(Doo::userId(), $textUserid)) {
|
||||
$msg['text'] = "";
|
||||
}
|
||||
switch ($type) {
|
||||
case 'file':
|
||||
$msg['type'] = in_array($msg['ext'], ['jpg', 'jpeg', 'webp', 'png', 'gif']) ? 'img' : 'file';
|
||||
$msg['path'] = Base::fillUrl($msg['path']);
|
||||
$msg['thumb'] = Base::fillUrl($msg['thumb'] ?: Base::extIcon($msg['ext']));
|
||||
break;
|
||||
|
||||
case 'record':
|
||||
$msg['path'] = Base::fillUrl($msg['path']);
|
||||
$textUserid = is_array($msg['text_userid']) ? $msg['text_userid'] : [];
|
||||
if (isset($msg['text_userid'])) {
|
||||
unset($msg['text_userid']);
|
||||
}
|
||||
if ($msg['text'] && !in_array(Doo::userId(), $textUserid)) {
|
||||
$msg['text'] = "";
|
||||
}
|
||||
break;
|
||||
|
||||
case 'location':
|
||||
$msg['thumb'] = Base::fillUrl($msg['thumb'] ?: "images/other/location.jpg");
|
||||
break;
|
||||
|
||||
case 'template':
|
||||
if ($msg['data']['thumb']) {
|
||||
$msg['data']['thumb']['url'] = Base::fillUrl($msg['data']['thumb']['url']);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return $msg;
|
||||
}
|
||||
@@ -540,97 +554,195 @@ class WebSocketDialogMsg extends AbstractModel
|
||||
|
||||
/**
|
||||
* 预览消息
|
||||
* @param bool $preserveHtml 保留html格式
|
||||
* @param null|array $data
|
||||
* @param WebSocketDialogMsg|array $data 消息数据
|
||||
* @param bool $preserveHtml 保留html格式
|
||||
* @return string
|
||||
*/
|
||||
public function previewMsg($preserveHtml = false, $data = null)
|
||||
public static function previewMsg($data, $preserveHtml = false)
|
||||
{
|
||||
if ($data === null) {
|
||||
if ($data instanceof WebSocketDialogMsg) {
|
||||
$data = [
|
||||
'type' => $this->type,
|
||||
'msg' => $this->msg,
|
||||
'type' => $data->type,
|
||||
'msg' => $data->msg,
|
||||
];
|
||||
}
|
||||
if (!is_array($data)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
switch ($data['type']) {
|
||||
case 'text':
|
||||
case 'word-chain':
|
||||
return self::previewTextMsg($data['msg'], $preserveHtml);
|
||||
|
||||
case 'vote':
|
||||
return $this->previewTextMsg($data['msg']['text'], $preserveHtml);
|
||||
$action = Doo::translate("投票");
|
||||
return "[{$action}] " . self::previewTextMsg($data['msg'], $preserveHtml);
|
||||
|
||||
case 'word-chain':
|
||||
$action = Doo::translate("接龙");
|
||||
return "[{$action}] " . self::previewTextMsg($data['msg'], $preserveHtml);
|
||||
|
||||
case 'record':
|
||||
return "[语音]";
|
||||
$action = Doo::translate("语音");
|
||||
return "[{$action}]";
|
||||
|
||||
case 'location':
|
||||
$action = Doo::translate("位置");
|
||||
return "[{$action}] " . Base::cutStr($data['msg']['title'], 50);
|
||||
|
||||
case 'meeting':
|
||||
return "[会议] ${$data['msg']['name']}";
|
||||
$action = Doo::translate("会议");
|
||||
return "[{$action}] " . Base::cutStr($data['msg']['name'], 50);
|
||||
|
||||
case 'file':
|
||||
if ($data['msg']['type'] == 'img') {
|
||||
return "[图片]";
|
||||
}
|
||||
return "[文件] {$data['msg']['name']}";
|
||||
return self::previewFileMsg($data['msg']);
|
||||
|
||||
case 'tag':
|
||||
$action = $data['msg']['action'] === 'remove' ? '取消标注' : '标注';
|
||||
return "[{$action}] {$this->previewMsg(false, $data['msg']['data'])}";
|
||||
$action = Doo::translate($data['msg']['action'] === 'remove' ? '取消标注' : '标注');
|
||||
return "[{$action}] " . self::previewMsg($data['msg']['data']);
|
||||
|
||||
case 'top':
|
||||
$action = $data['msg']['action'] === 'remove' ? '取消置顶' : '置顶';
|
||||
return "[{$action}] {$this->previewMsg(false, $data['msg']['data'])}";
|
||||
$action = Doo::translate($data['msg']['action'] === 'remove' ? '取消置顶' : '置顶');
|
||||
return "[{$action}] " . self::previewMsg($data['msg']['data']);
|
||||
|
||||
case 'todo':
|
||||
$action = $data['msg']['action'] === 'remove' ? '取消待办' : ($data['msg']['action'] === 'done' ? '完成' : '设待办');
|
||||
return "[{$action}] {$this->previewMsg(false, $data['msg']['data'])}";
|
||||
$action = Doo::translate($data['msg']['action'] === 'remove' ? '取消待办' : ($data['msg']['action'] === 'done' ? '完成' : '设待办'));
|
||||
return "[{$action}] " . self::previewMsg($data['msg']['data']);
|
||||
|
||||
case 'notice':
|
||||
return $data['msg']['notice'];
|
||||
return Base::cutStr(Doo::translate($data['msg']['notice']), 50);
|
||||
|
||||
case 'template':
|
||||
return self::previewTemplateMsg($data['msg']);
|
||||
|
||||
case 'preview':
|
||||
return $data['msg']['preview'];
|
||||
|
||||
default:
|
||||
return "[未知的消息]";
|
||||
$action = Doo::translate("未知的消息");
|
||||
return "[{$action}]";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成关键词
|
||||
* @return string
|
||||
*/
|
||||
public function generateMsgKey()
|
||||
{
|
||||
return match ($this->type) {
|
||||
'text' => str_replace(" ", " ", strip_tags($this->msg['text'])),
|
||||
'meeting', 'file' => $this->msg['name'],
|
||||
default => '',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回引用消息(如果是文本消息则截取)
|
||||
* @param int $strlen
|
||||
* @return array|mixed
|
||||
*/
|
||||
public function quoteTextMsg($strlen = 30)
|
||||
{
|
||||
$msg = $this->msg;
|
||||
if ($this->type === 'text') {
|
||||
$msg['text'] = $this->previewTextMsg($msg['text']);
|
||||
if (mb_strlen($msg['text']) > $strlen) {
|
||||
$msg['text'] = mb_substr($msg['text'], 0, $strlen - 3) . "...";
|
||||
}
|
||||
}
|
||||
return $msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回文本预览消息
|
||||
* @param $text
|
||||
* @param array $msgData
|
||||
* @param bool $preserveHtml 保留html格式
|
||||
* @return string|string[]|null
|
||||
*/
|
||||
private function previewTextMsg($text, $preserveHtml = false)
|
||||
private static function previewTextMsg($msgData, $preserveHtml = false)
|
||||
{
|
||||
$text = $msgData['text'] ?? '';
|
||||
if (!$text) return '';
|
||||
if ($msgData['type'] === 'md') {
|
||||
$text = Base::markdown2html($text);
|
||||
}
|
||||
$text = preg_replace("/<img\s+class=\"emoticon\"[^>]*?alt=\"(\S+)\"[^>]*?>/", "[$1]", $text);
|
||||
$text = preg_replace("/<img\s+class=\"emoticon\"[^>]*?>/", "[动画表情]", $text);
|
||||
$text = preg_replace("/<img\s+class=\"browse\"[^>]*?>/", "[图片]", $text);
|
||||
$text = preg_replace("/<img\s+class=\"emoticon\"[^>]*?>/", "[" . Doo::translate('动画表情') . "]", $text);
|
||||
$text = preg_replace("/<img\s+class=\"browse\"[^>]*?>/", "[" . Doo::translate('图片') . "]", $text);
|
||||
if (!$preserveHtml) {
|
||||
$text = str_replace("</p><p>", "</p> <p>", $text);
|
||||
$text = strip_tags($text);
|
||||
$text = str_replace([" ", "&", "<", ">"], [" ", "&", "<", ">"], $text);
|
||||
$text = str_replace([" ", """, "&", "<", ">"], [" ", '"', "&", "<", ">"], $text);
|
||||
$text = preg_replace("/\s+/", " ", $text);
|
||||
$text = Base::cutStr($text, 50);
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* 预览文件消息
|
||||
* @param $msg
|
||||
* @return string
|
||||
*/
|
||||
private static function previewFileMsg($msg)
|
||||
{
|
||||
if ($msg['type'] == 'img') {
|
||||
$action = Doo::translate("图片");
|
||||
return "[{$action}]";
|
||||
} elseif ($msg['ext'] == 'mp4') {
|
||||
$action = Doo::translate("视频");
|
||||
return "[{$action}]";
|
||||
}
|
||||
$action = Doo::translate("文件");
|
||||
return "[{$action}] " . Base::cutStr($msg['name'], 50);
|
||||
}
|
||||
|
||||
/**
|
||||
* 预览模板消息
|
||||
* @param $msg
|
||||
* @return string
|
||||
*/
|
||||
private static function previewTemplateMsg($msg)
|
||||
{
|
||||
if (!empty($msg['title_raw'])) {
|
||||
return $msg['title_raw'];
|
||||
}
|
||||
if ($msg['type'] === 'task_list' && count($msg['list']) === 1) {
|
||||
return Doo::translate($msg['title']) . ": " . Base::cutStr($msg['list'][0]['name'], 50);
|
||||
}
|
||||
if (!empty($msg['title'])) {
|
||||
return Doo::translate($msg['title']);
|
||||
}
|
||||
if ($msg['type'] === 'content' && is_string($msg['content']) && $msg['content'] !== '') {
|
||||
return Base::cutStr(Doo::translate($msg['content']), 50);
|
||||
}
|
||||
return Doo::translate('未知的消息');
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成关键词并保存
|
||||
* @param string $key
|
||||
* @return void
|
||||
*/
|
||||
public function generateKeyAndSave($key = ''): void
|
||||
{
|
||||
if (empty($key)) {
|
||||
$key = '';
|
||||
switch ($this->type) {
|
||||
case 'text':
|
||||
if (!preg_match("/<span[^>]*?data-quick-key=([\"'])([^\"']+?)\\1[^>]*?>/is", $this->msg['text'])) {
|
||||
$key = strip_tags($this->msg['text']);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'vote':
|
||||
case 'word-chain':
|
||||
$key = strip_tags($this->msg['text']);
|
||||
break;
|
||||
|
||||
case 'file':
|
||||
$key = $this->msg['name'];
|
||||
$key = preg_replace("/^(image|\d+)\.(png|jpg|jpeg|webp|gif)$/i", "", $key);
|
||||
$key = preg_replace("/^LongText-(.*?)/i", "", $key);
|
||||
break;
|
||||
|
||||
case 'meeting':
|
||||
$key = $this->msg['name'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
$key = str_replace([""", "&", "<", ">"], "", $key);
|
||||
$key = str_replace(["\r", "\n", "\t", " "], " ", $key);
|
||||
$key = preg_replace("/^\/[A-Za-z]+/", " ", $key);
|
||||
$key = preg_replace("/\s+/", " ", $key);
|
||||
$this->key = trim($key);
|
||||
$this->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回引用消息(如果是文本只取预览)
|
||||
* @return array|mixed
|
||||
*/
|
||||
public function quoteTextMsg()
|
||||
{
|
||||
$msg = $this->msg;
|
||||
if ($this->type === 'text') {
|
||||
$msg['text'] = self::previewTextMsg($msg);
|
||||
}
|
||||
return $msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理文本消息内容,用于发送前
|
||||
* @param $text
|
||||
@@ -648,9 +760,9 @@ class WebSocketDialogMsg extends AbstractModel
|
||||
$imagePath = "uploads/chat/" . date("Ym") . "/" . $dialog_id . "/";
|
||||
Base::makeDir(public_path($imagePath));
|
||||
$imagePath .= md5s($base64) . "." . $matchs[1][$key];
|
||||
if (Base::saveContentImage(public_path($imagePath), base64_decode($base64))) {
|
||||
if (Base::saveContentImage(public_path($imagePath), base64_decode($base64), 90)) {
|
||||
$imageSize = getimagesize(public_path($imagePath));
|
||||
if ($extension = Image::thumbImage(public_path($imagePath), public_path($imagePath) . "_thumb.{*}", 320, 0)) {
|
||||
if ($extension = Image::thumbImage(public_path($imagePath), public_path($imagePath) . "_thumb.{*}", 320, 0, 80)) {
|
||||
$imagePath .= "_thumb.{$extension}";
|
||||
}
|
||||
$text = str_replace($matchs[0][$key], "[:IMAGE:browse:{$imageSize[0]}:{$imageSize[1]}:{$imagePath}::]", $text);
|
||||
@@ -724,7 +836,7 @@ class WebSocketDialogMsg extends AbstractModel
|
||||
}
|
||||
if (file_exists(public_path($imagePath))) {
|
||||
$imageSize = getimagesize(public_path($imagePath));
|
||||
if ($extension = Image::thumbImage(public_path($imagePath), public_path($imagePath) . "_thumb.{*}", 320, 0)) {
|
||||
if ($extension = Image::thumbImage(public_path($imagePath), public_path($imagePath) . "_thumb.{*}", 320, 0, 80)) {
|
||||
$imagePath .= "_thumb.{$extension}";
|
||||
}
|
||||
$text = str_replace($matchs[0][$key], "[:IMAGE:browse:{$imageSize[0]}:{$imageSize[1]}:{$imagePath}::]", $text);
|
||||
@@ -732,9 +844,9 @@ class WebSocketDialogMsg extends AbstractModel
|
||||
$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 (Base::saveContentImage(public_path($imagePath), $image)) {
|
||||
} else if (Base::saveContentImage(public_path($imagePath), $image, 90)) {
|
||||
$imageSize = getimagesize(public_path($imagePath));
|
||||
if ($extension = Image::thumbImage(public_path($imagePath), public_path($imagePath) . "_thumb.{*}", 320, 0)) {
|
||||
if ($extension = Image::thumbImage(public_path($imagePath), public_path($imagePath) . "_thumb.{*}", 320, 0, 80)) {
|
||||
$imagePath .= "_thumb.{$extension}";
|
||||
}
|
||||
$text = str_replace($matchs[0][$key], "[:IMAGE:browse:{$imageSize[0]}:{$imageSize[1]}:{$imagePath}::]", $text);
|
||||
@@ -768,7 +880,7 @@ class WebSocketDialogMsg extends AbstractModel
|
||||
$text = str_replace($matchs[0][$key], "[:{$matchChar[1]}:{$keyId}:{$matchValye[1]}:]", $text);
|
||||
}
|
||||
// 处理快捷消息
|
||||
preg_match_all("/<span[^>]*?data-quick-key=([\"'])(.*?)\\1[^>]*?>(.*?)<\/span>/is", $text, $matchs);
|
||||
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];
|
||||
@@ -788,7 +900,7 @@ class WebSocketDialogMsg extends AbstractModel
|
||||
}
|
||||
}
|
||||
// 处理链接标签
|
||||
preg_match_all("/<a[^>]*?href=([\"'])(.*?)\\1[^>]*?>(.*?)<\/a>/is", $text, $matchs);
|
||||
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;
|
||||
@@ -806,7 +918,7 @@ class WebSocketDialogMsg extends AbstractModel
|
||||
$text = str_replace($str, "[:LINK:{$herf}:{$title}:]", $text);
|
||||
}
|
||||
// 文件分享链接
|
||||
preg_match_all("/(https*:\/\/)((\w|=|\?|\.|\/|&|-|:|\+|%|;|#|@|,|!)+)/i", $text, $matchs);
|
||||
preg_match_all("/(https?:\/\/)((\w|=|\?|\.|\/|&|-|:|\+|%|;|#|@|,|!)+)/i", $text, $matchs);
|
||||
if ($matchs) {
|
||||
foreach ($matchs[0] as $str) {
|
||||
preg_match("/\/single\/file\/(.*?)$/i", $str, $match);
|
||||
@@ -853,6 +965,7 @@ class WebSocketDialogMsg extends AbstractModel
|
||||
/**
|
||||
* 发送消息、修改消息
|
||||
* @param string $action 动作
|
||||
* - null:发送消息
|
||||
* - reply-98:回复消息ID=98
|
||||
* - update-99:更新消息ID=99(标记修改)
|
||||
* - change-99:更新消息ID=99(不标记修改)
|
||||
@@ -865,14 +978,15 @@ class WebSocketDialogMsg extends AbstractModel
|
||||
* @param bool $push_retry 推送-失败后重试1次(有时候在事务里执行,数据还没生成时会出现找不到消息的情况)
|
||||
* @param bool|null $push_silence 推送-静默
|
||||
* - type = [text|file|record|meeting] 默认为:false
|
||||
* @param string|null $search_key 搜索关键词(用于搜索,留空则自动生成)
|
||||
* @return array
|
||||
*/
|
||||
public static function sendMsg($action, $dialog_id, $type, $msg, $sender = null, $push_self = false, $push_retry = false, $push_silence = null)
|
||||
public static function sendMsg($action, $dialog_id, $type, $msg, $sender = null, $push_self = false, $push_retry = false, $push_silence = null, $search_key = null)
|
||||
{
|
||||
$link = 0;
|
||||
$mtype = $type;
|
||||
if ($type === 'text') {
|
||||
if (str_contains($msg['text'], '<a ') || preg_match("/https*:\/\//", $msg['text'])) {
|
||||
if (str_contains($msg['text'], '<a ') || preg_match("/https?:\/\//", $msg['text'])) {
|
||||
$link = 1;
|
||||
}
|
||||
if (str_contains($msg['text'], '<img ')) {
|
||||
@@ -889,6 +1003,27 @@ class WebSocketDialogMsg extends AbstractModel
|
||||
if (in_array($msg['ext'], ['jpg', 'jpeg', 'webp', 'png', 'gif'])) {
|
||||
$mtype = 'image';
|
||||
}
|
||||
} elseif ($type === 'location') {
|
||||
if (preg_match('/^https?:\/\//', $msg['thumb'])) {
|
||||
$thumb = file_get_contents($msg['thumb']);
|
||||
if (empty($thumb)) {
|
||||
throw new ApiException('获取地图快照失败');
|
||||
}
|
||||
$fileUrl = "uploads/chat/" . date("Ym") . "/" . $dialog_id . "/" . md5s($msg['thumb']) . ".jpg";
|
||||
$filePath = public_path($fileUrl);
|
||||
Base::makeDir(dirname($filePath));
|
||||
if (!Base::saveContentImage($filePath, $thumb, 90)) {
|
||||
throw new ApiException('保存地图快照失败');
|
||||
}
|
||||
$imageSize = getimagesize($filePath);
|
||||
if ($imageSize[0] < 20 || $imageSize[1] < 20) {
|
||||
throw new ApiException('地图快照尺寸太小');
|
||||
}
|
||||
$msg['thumb_original'] = $msg['thumb'];
|
||||
$msg['thumb'] = $fileUrl;
|
||||
$msg['width'] = $imageSize[0];
|
||||
$msg['height'] = $imageSize[1];
|
||||
}
|
||||
}
|
||||
if ($push_silence === null) {
|
||||
$push_silence = !in_array($type, ["text", "file", "record", "meeting"]);
|
||||
@@ -927,7 +1062,7 @@ class WebSocketDialogMsg extends AbstractModel
|
||||
];
|
||||
}
|
||||
} else {
|
||||
if ($dialogMsg->type !== 'text') {
|
||||
if (!in_array($dialogMsg->type, ['text', 'template'])) {
|
||||
throw new ApiException('此消息不支持此操作');
|
||||
}
|
||||
if ($dialogMsg->userid != $sender) {
|
||||
@@ -942,8 +1077,7 @@ class WebSocketDialogMsg extends AbstractModel
|
||||
'modify' => $modify,
|
||||
];
|
||||
$dialogMsg->updateInstance($updateData);
|
||||
$dialogMsg->key = $dialogMsg->generateMsgKey();
|
||||
$dialogMsg->save();
|
||||
$dialogMsg->generateKeyAndSave($search_key);
|
||||
//
|
||||
WebSocketDialogUser::whereDialogId($dialog->id)->whereUserid($sender)->whereHide(1)->change([
|
||||
'hide' => 0, // 修改消息时,显示会话(仅自己)
|
||||
@@ -988,10 +1122,9 @@ class WebSocketDialogMsg extends AbstractModel
|
||||
'msg' => $msg,
|
||||
'read' => 0,
|
||||
]);
|
||||
AbstractModel::transaction(function () use ($dialogMsg) {
|
||||
AbstractModel::transaction(function () use ($search_key, $dialogMsg) {
|
||||
$dialogMsg->send = 1;
|
||||
$dialogMsg->key = $dialogMsg->generateMsgKey();
|
||||
$dialogMsg->save();
|
||||
$dialogMsg->generateKeyAndSave($search_key);
|
||||
//
|
||||
if ($dialogMsg->type === 'meeting') {
|
||||
MeetingMsg::createInstance([
|
||||
|
||||
@@ -16,7 +16,7 @@ use Carbon\Carbon;
|
||||
* @property int|null $email 是否发了邮件
|
||||
* @property int|null $after 在阅读之后才添加的记录
|
||||
* @property int|null $dot 红点标记
|
||||
* @property string|null $read_at 阅读时间
|
||||
* @property \Illuminate\Support\Carbon|null $read_at 阅读时间
|
||||
* @property-read \App\Models\WebSocketDialogMsg|null $webSocketDialogMsg
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel cancelAppend()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel cancelHidden()
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace App\Models;
|
||||
* @property int|null $dialog_id 对话ID
|
||||
* @property int|null $msg_id 消息ID
|
||||
* @property int|null $userid 接收会员ID
|
||||
* @property string|null $done_at 完成时间
|
||||
* @property \Illuminate\Support\Carbon|null $done_at 完成时间
|
||||
* @property-read array|mixed $msg_data
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel cancelAppend()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel cancelHidden()
|
||||
|
||||
36
app/Models/WebSocketDialogMsgTranslate.php
Normal file
36
app/Models/WebSocketDialogMsgTranslate.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
/**
|
||||
* App\Models\WebSocketDialogMsgTranslate
|
||||
*
|
||||
* @property int $id
|
||||
* @property int|null $dialog_id 对话ID
|
||||
* @property int|null $msg_id 消息ID
|
||||
* @property string|null $language 语言
|
||||
* @property string|null $content 翻译内容
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel cancelAppend()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel cancelHidden()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel change($array)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel getKeyValue()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgTranslate newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgTranslate newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgTranslate query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel remove()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel saveOrIgnore()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgTranslate whereContent($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgTranslate whereDialogId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgTranslate whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgTranslate whereLanguage($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgTranslate whereMsgId($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class WebSocketDialogMsgTranslate extends AbstractModel
|
||||
{
|
||||
function __construct(array $attributes = [])
|
||||
{
|
||||
parent::__construct($attributes);
|
||||
$this->timestamps = false;
|
||||
}
|
||||
}
|
||||
@@ -10,8 +10,8 @@ use Carbon\Carbon;
|
||||
* @property int $id
|
||||
* @property int|null $dialog_id 对话ID
|
||||
* @property int|null $userid 会员ID
|
||||
* @property string|null $top_at 置顶时间
|
||||
* @property string|null $last_at 最后消息时间
|
||||
* @property \Illuminate\Support\Carbon|null $top_at 置顶时间
|
||||
* @property \Illuminate\Support\Carbon|null $last_at 最后消息时间
|
||||
* @property int|null $mark_unread 是否标记为未读:0否,1是
|
||||
* @property int|null $silence 是否免打扰:0否,1是
|
||||
* @property int|null $hide 不显示会话:0否,1是
|
||||
@@ -47,7 +47,7 @@ use Carbon\Carbon;
|
||||
*/
|
||||
class WebSocketDialogUser extends AbstractModel
|
||||
{
|
||||
protected $dateFormat = 'Y-m-d H:i:s.v';
|
||||
protected $dateFormat = 'Y-m-d H:i:s.u';
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasOne
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -11,7 +11,7 @@ use FFI;
|
||||
class Doo
|
||||
{
|
||||
private static $doo;
|
||||
private static $passphrase = "LYHevk5n";
|
||||
private static $userLanguage = "";
|
||||
|
||||
/**
|
||||
* char转为字符串
|
||||
@@ -269,12 +269,58 @@ class Doo
|
||||
/**
|
||||
* 翻译
|
||||
* @param $text
|
||||
* @param string $type
|
||||
* @param string $lang
|
||||
* @return string
|
||||
*/
|
||||
public static function translate($text, string $type = ""): string
|
||||
public static function translate($text, string $lang = ""): string
|
||||
{
|
||||
return self::string(self::doo()->translate($text, $type));
|
||||
return self::string(self::doo()->translate($text, $lang ?: self::$userLanguage));
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置语言
|
||||
* @param string|integer $lang 语言 或 会员ID
|
||||
* @return void
|
||||
*/
|
||||
public static function setLanguage($lang) {
|
||||
if (Base::isNumber($lang)) {
|
||||
$lang = User::find(intval($lang))?->lang ?: "";
|
||||
}
|
||||
self::$userLanguage = $lang;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取语言列表 或 语言名称
|
||||
* @param string|false $lang
|
||||
* @return string|string[]
|
||||
*/
|
||||
public static function getLanguages($lang = false)
|
||||
{
|
||||
$array = [
|
||||
"zh" => "简体中文",
|
||||
"zh-CHT" => "繁体中文",
|
||||
"en" => "英语",
|
||||
"ko" => "韩语",
|
||||
"ja" => "日语",
|
||||
"de" => "德语",
|
||||
"fr" => "法语",
|
||||
"id" => "印度尼西亚语",
|
||||
"ru" => "俄语",
|
||||
];
|
||||
if ($lang !== false) {
|
||||
return $array[$lang] ?? "";
|
||||
}
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查语言是否存在
|
||||
* @param $lang
|
||||
* @return bool
|
||||
*/
|
||||
public static function checkLanguage($lang): bool
|
||||
{
|
||||
return array_key_exists($lang, self::getLanguages());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -55,6 +55,59 @@ class Extranet
|
||||
return Base::retSuccess("success", $resData['text']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 openAI 翻译
|
||||
* @param $text
|
||||
* @param $targetLanguage
|
||||
* @return array
|
||||
*/
|
||||
public static function openAItranslations($text, $targetLanguage)
|
||||
{
|
||||
$systemSetting = Base::setting('system');
|
||||
$aibotSetting = Base::setting('aibotSetting');
|
||||
if ($systemSetting['translation'] !== 'open' || empty($aibotSetting['openai_key'])) {
|
||||
return Base::retError("翻译功能未开启");
|
||||
}
|
||||
$extra = [
|
||||
'Content-Type' => 'application/json',
|
||||
'Authorization' => 'Bearer ' . $aibotSetting['openai_key'],
|
||||
];
|
||||
if ($aibotSetting['openai_agency']) {
|
||||
$extra['CURLOPT_PROXY'] = $aibotSetting['openai_agency'];
|
||||
if (str_contains($aibotSetting['openai_agency'], 'socks')) {
|
||||
$extra['CURLOPT_PROXYTYPE'] = CURLPROXY_SOCKS5;
|
||||
} else {
|
||||
$extra['CURLOPT_PROXYTYPE'] = CURLPROXY_HTTP;
|
||||
}
|
||||
}
|
||||
$res = Ihttp::ihttp_request('https://api.openai.com/v1/chat/completions', json_encode([
|
||||
"model" => "gpt-3.5-turbo",
|
||||
"messages" => [
|
||||
[
|
||||
"role" => "system",
|
||||
"content" => "你是一个专业的翻译器,翻译的结果尽量符合“项目任务管理系统”的使用,并且翻译的结果不用额外添加换行尽量保持原格式,将提供的文本翻译成“{$targetLanguage}”语言。"
|
||||
],
|
||||
[
|
||||
"role" => "user",
|
||||
"content" => $text
|
||||
]
|
||||
]
|
||||
]), $extra, 15);
|
||||
if (Base::isError($res)) {
|
||||
return Base::retError("翻译失败", $res);
|
||||
}
|
||||
$resData = Base::json2array($res['data']);
|
||||
if (empty($resData['choices'])) {
|
||||
return Base::retError("翻译失败", $resData);
|
||||
}
|
||||
$result = $resData['choices'][0]['message']['content'];
|
||||
$result = preg_replace('/^\"|\"$/', '', $result);
|
||||
if (empty($result)) {
|
||||
return Base::retError("翻译失败", $result);
|
||||
}
|
||||
return Base::retSuccess("success", $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取IP地址经纬度
|
||||
* @param string $ip
|
||||
@@ -326,22 +379,74 @@ class Extranet
|
||||
$text = "心灵鸡汤:{$data}";
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
$text = "";
|
||||
break;
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取搜狗表情包
|
||||
* @param $keyword
|
||||
* @return array
|
||||
*/
|
||||
public static function sticker($keyword)
|
||||
{
|
||||
$data = self::curl("https://pic.sogou.com/napi/wap/searchlist", 1800, 15, [], [
|
||||
'CURLOPT_CUSTOMREQUEST' => 'POST',
|
||||
'CURLOPT_POSTFIELDS' => json_encode([
|
||||
"initQuery" => $keyword . " 表情",
|
||||
"queryFrom" => "wap",
|
||||
"ie" => "utf8",
|
||||
"keyword" => $keyword . " 表情",
|
||||
// "mode" => 20,
|
||||
"showMode" => 0,
|
||||
"start" => 1,
|
||||
"reqType" => "client",
|
||||
"reqFrom" => "wap_result",
|
||||
"prevIsRedis" => "n",
|
||||
"pagetype" => 0,
|
||||
"amsParams" => []
|
||||
]),
|
||||
'CURLOPT_HTTPHEADER' => [
|
||||
'Content-Type: application/json',
|
||||
'Referer: https://pic.sogou.com/'
|
||||
]
|
||||
]);
|
||||
$data = Base::json2array($data);
|
||||
if ($data['status'] === 0 && $data['data']['picResult']['items']) {
|
||||
$data = $data['data']['picResult']['items'];
|
||||
$data = array_filter($data, function ($item) {
|
||||
return intval($item['thumbHeight']) > 10 && intval($item['thumbWidth']) > 10;
|
||||
});
|
||||
return array_map(function ($item) {
|
||||
return [
|
||||
'name' => $item['title'],
|
||||
'src' => $item['thumbUrl'],
|
||||
'height' => $item['thumbHeight'],
|
||||
'width' => $item['thumbWidth'],
|
||||
];
|
||||
}, $data);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $url
|
||||
* @param int $cacheSecond 缓存时间(秒),如果结果为空则缓存有效30秒
|
||||
* @param int $timeout
|
||||
* @param array $post
|
||||
* @param array $extra
|
||||
* @return string
|
||||
*/
|
||||
private static function curl($url, int $cacheSecond = 0, int $timeout = 15): string
|
||||
private static function curl($url, int $cacheSecond = 0, int $timeout = 15, array $post = [], array $extra = []): string
|
||||
{
|
||||
if ($cacheSecond > 0) {
|
||||
$key = "curlCache::" . md5($url);
|
||||
$content = Cache::remember($key, Carbon::now()->addSeconds($cacheSecond), function () use ($cacheSecond, $key, $timeout, $url) {
|
||||
$result = Ihttp::ihttp_request($url, [], [], $timeout);
|
||||
$key = "curlCache::" . md5($url) . "::" . md5(json_encode($post)) . "::" . md5(json_encode($extra));
|
||||
$content = Cache::remember($key, Carbon::now()->addSeconds($cacheSecond), function () use ($extra, $post, $cacheSecond, $key, $timeout, $url) {
|
||||
$result = Ihttp::ihttp_request($url, $post, $extra, $timeout);
|
||||
$content = Base::isSuccess($result) ? trim($result['data']) : '';
|
||||
if (empty($content) && $cacheSecond > 30) {
|
||||
Cache::put($key, "", Carbon::now()->addSeconds(30));
|
||||
@@ -349,7 +454,7 @@ class Extranet
|
||||
return $content;
|
||||
});
|
||||
} else {
|
||||
$result = Ihttp::ihttp_request($url, [], [], $timeout);
|
||||
$result = Ihttp::ihttp_request($url, $post, $extra, $timeout);
|
||||
$content = Base::isSuccess($result) ? trim($result['data']) : '';
|
||||
}
|
||||
//
|
||||
|
||||
@@ -24,6 +24,15 @@ class Image
|
||||
public function __construct($imagePath) {
|
||||
$this->path = $imagePath;
|
||||
$this->image = new Imagick($this->path);
|
||||
$this->updateSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新图片尺寸
|
||||
* @return void
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
private function updateSize() {
|
||||
$geo = $this->image->getImageGeometry();
|
||||
$this->height = $geo['height'];
|
||||
$this->width = $geo['width'];
|
||||
@@ -47,6 +56,36 @@ class Image
|
||||
return $this->height;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按比例裁剪
|
||||
* @param float $ratio
|
||||
* @return $this
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
public function ratioCrop(float $ratio = 0): static
|
||||
{
|
||||
if ($ratio === 0) {
|
||||
return $this;
|
||||
}
|
||||
if ($ratio < 1) {
|
||||
$ratio = 1 / $ratio;
|
||||
}
|
||||
$width = $this->width;
|
||||
$height = $this->height;
|
||||
if ($width > $height * $ratio) {
|
||||
$newWidth = $height * $ratio;
|
||||
$newHeight = $height;
|
||||
} elseif ($height > $width * $ratio) {
|
||||
$newWidth = $width;
|
||||
$newHeight = $width * $ratio;
|
||||
} else {
|
||||
return $this;
|
||||
}
|
||||
$this->image->cropImage($newWidth, $newHeight, ($width - $newWidth) / 2, ($height - $newHeight) / 2);
|
||||
$this->updateSize();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建缩略图
|
||||
* @param int $width
|
||||
@@ -97,6 +136,7 @@ class Image
|
||||
} else {
|
||||
$this->image->thumbnailImage($width, $height);
|
||||
}
|
||||
$this->updateSize();
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -161,6 +201,15 @@ class Image
|
||||
$this->image->destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁对象
|
||||
* @return void
|
||||
*/
|
||||
public function destroy()
|
||||
{
|
||||
$this->image->destroy();
|
||||
}
|
||||
|
||||
/** ******************************************************************************/
|
||||
/** ******************************************************************************/
|
||||
/** ******************************************************************************/
|
||||
@@ -171,10 +220,11 @@ class Image
|
||||
* @param string $savePath 保存路径
|
||||
* @param int $width 宽度
|
||||
* @param int $height 高度
|
||||
* @param int $quality 压缩质量(0-100), 0 为不压缩
|
||||
* @param string $mode 模式(percentage|cover|contain)
|
||||
* @return string|null 成功返回图片后缀,失败返回 false
|
||||
*/
|
||||
public static function thumbImage(string $imagePath, string $savePath, int $width, int $height, string $mode = 'percentage'): ?string
|
||||
public static function thumbImage(string $imagePath, string $savePath, int $width, int $height, int $quality = 0, string $mode = 'percentage'): ?string
|
||||
{
|
||||
if (!file_exists($imagePath)) {
|
||||
return null;
|
||||
@@ -187,6 +237,13 @@ class Image
|
||||
$image = new Image($imagePath);
|
||||
$image->thumb($width, $height, $mode);
|
||||
$image->saveTo($savePath);
|
||||
if ($quality > 0) {
|
||||
Image::compressImage($savePath, $quality);
|
||||
}
|
||||
if ($savePath != $imagePath && filesize($savePath) >= filesize($imagePath)) {
|
||||
unlink($savePath);
|
||||
symlink(basename($imagePath), $savePath);
|
||||
}
|
||||
return $extension;
|
||||
} catch (\ImagickException) {
|
||||
return null;
|
||||
@@ -194,33 +251,34 @@ class Image
|
||||
}
|
||||
|
||||
/**
|
||||
* 压缩图片
|
||||
* @param string $imagePath 图片路径
|
||||
* @param string|null $savePath 保存路径(默认覆盖原图)
|
||||
* @param int $quality 压缩质量(0-100)
|
||||
* @param float $minSize 最小尺寸(单位:KB)
|
||||
* 压缩图片(如果压缩后的图片比原图还大那就直接使用原图)
|
||||
* @param array|string $path 图片路径(如果是数组,第1个元素为原图路径,第2个元素为保存路径)
|
||||
* @param int $quality 压缩质量(0-100)
|
||||
* @param float $minSize 最小尺寸,小于这个尺寸不压缩(单位:KB)
|
||||
* @return bool
|
||||
*/
|
||||
public static function compressImage(string $imagePath, string $savePath = null, int $quality = 100, float $minSize = 10): bool
|
||||
public static function compressImage(array|string $path, int $quality = 100, float $minSize = 5): bool
|
||||
{
|
||||
if (Base::settingFind("system", "image_compress") === 'close') {
|
||||
return false;
|
||||
}
|
||||
if (is_array($path)) {
|
||||
$imagePath = $path[0];
|
||||
$savePath = $path[1] ?? $imagePath;
|
||||
} else {
|
||||
$imagePath = $path;
|
||||
$savePath = $path;
|
||||
}
|
||||
if (!file_exists($imagePath)) {
|
||||
return false;
|
||||
}
|
||||
$quality = min(max($quality, 1), 100);
|
||||
$imageSize = filesize($imagePath);
|
||||
if ($minSize > 0 && $imageSize < $minSize * 1024) {
|
||||
return false;
|
||||
}
|
||||
if (empty($savePath)) {
|
||||
$savePath = $imagePath;
|
||||
}
|
||||
$tmpPath = $imagePath . '.compress.tmp';
|
||||
try {
|
||||
$image = new Image($imagePath);
|
||||
$image->compress($quality);
|
||||
$image->saveTo($tmpPath);
|
||||
if (self::compressAuto($imagePath, $tmpPath, $quality)) {
|
||||
if (filesize($tmpPath) >= $imageSize) {
|
||||
copy($imagePath, $savePath);
|
||||
unlink($tmpPath);
|
||||
@@ -228,8 +286,89 @@ class Image
|
||||
rename($tmpPath, $savePath);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动压缩图片(仅限于compressImage方法使用)
|
||||
* @param string $imagePath
|
||||
* @param string $savePath
|
||||
* @param int $quality
|
||||
* @return bool
|
||||
*/
|
||||
private static function compressAuto(string $imagePath, string $savePath, int $quality = 100): bool
|
||||
{
|
||||
if (strtolower(pathinfo($imagePath, PATHINFO_EXTENSION)) === 'png') {
|
||||
$minQuality = $quality - 20;
|
||||
$compressedContent = shell_exec("pngquant --quality={$minQuality}-{$quality} --strip - < " . $imagePath);
|
||||
if ($compressedContent) {
|
||||
file_put_contents($savePath, $compressedContent);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
try {
|
||||
$image = new Image($imagePath);
|
||||
$image->compress($quality);
|
||||
$image->saveTo($savePath);
|
||||
return true;
|
||||
} catch (\ImagickException) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** ******************************************************************************/
|
||||
/** ******************************************************************************/
|
||||
/** ******************************************************************************/
|
||||
|
||||
// ImageMagick 策略限制配置
|
||||
private static $limits = [
|
||||
'width' => 16384, // 16KP
|
||||
'height' => 16384, // 16KP
|
||||
'area' => 128000000, // 128MP (128 * 1000000 pixels)
|
||||
'memory' => 256, // 256MiB
|
||||
];
|
||||
|
||||
/**
|
||||
* 验证上传的图片
|
||||
* @param $file
|
||||
* @return array
|
||||
*/
|
||||
public static function validateImage($file)
|
||||
{
|
||||
try {
|
||||
// 获取图片信息
|
||||
$imageInfo = getimagesize($file);
|
||||
if ($imageInfo === false) {
|
||||
return Base::retError('无法获取图片信息');
|
||||
}
|
||||
|
||||
$width = $imageInfo[0];
|
||||
$height = $imageInfo[1];
|
||||
$area = $width * $height;
|
||||
|
||||
// 检查尺寸限制
|
||||
if ($width > self::$limits['width']) {
|
||||
return Base::retError(sprintf('图片宽度(%dpx)超过限制(%dpx)', $width, self::$limits['width']));
|
||||
}
|
||||
|
||||
if ($height > self::$limits['height']) {
|
||||
return Base::retError(sprintf('图片高度(%dpx)超过限制(%dpx)', $height, self::$limits['height']));
|
||||
}
|
||||
|
||||
if ($area > self::$limits['area']) {
|
||||
return Base::retError(sprintf('图片总像素(%dpx)超过限制(%dpx)', $area, self::$limits['area']));
|
||||
}
|
||||
|
||||
// 估算内存使用(每个像素约4字节)
|
||||
$estimatedMemory = ($area * 4) / (1024 * 1024); // 转换为 MB
|
||||
if ($estimatedMemory > self::$limits['memory']) {
|
||||
return Base::retError(sprintf('预计内存使用(%dMB)超过限制(%dMB)', $estimatedMemory, self::$limits['memory']));
|
||||
}
|
||||
|
||||
return Base::retSuccess('success');
|
||||
} catch (\Exception $e) {
|
||||
return Base::retError('验证过程发生错误:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
86
app/Module/Table/AbstractData.php
Normal file
86
app/Module/Table/AbstractData.php
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace App\Module\Table;
|
||||
|
||||
use ReflectionClass;
|
||||
use Swoole\Table;
|
||||
|
||||
abstract class AbstractData
|
||||
{
|
||||
/** @var self */
|
||||
protected static $instance = null;
|
||||
|
||||
/** @var Table */
|
||||
protected $table;
|
||||
|
||||
protected function getTableName(): string
|
||||
{
|
||||
$className = (new ReflectionClass(static::class))->getShortName();
|
||||
return lcfirst($className) . 'Table';
|
||||
}
|
||||
|
||||
private function __clone() {}
|
||||
private function __wakeup() {}
|
||||
|
||||
protected function __construct()
|
||||
{
|
||||
$this->table = app('swoole')->{$this->getTableName()};
|
||||
}
|
||||
|
||||
public function getTable()
|
||||
{
|
||||
return $this->table;
|
||||
}
|
||||
|
||||
public static function instance()
|
||||
{
|
||||
if (static::$instance === null) {
|
||||
static::$instance = new static();
|
||||
}
|
||||
return static::$instance;
|
||||
}
|
||||
|
||||
public static function set($key, $value)
|
||||
{
|
||||
return self::instance()->table->set($key, ['value' => $value]);
|
||||
}
|
||||
|
||||
public static function get($key, $default = null)
|
||||
{
|
||||
$data = self::instance()->table->get($key);
|
||||
return $data ? $data['value'] : $default;
|
||||
}
|
||||
|
||||
public static function del($key)
|
||||
{
|
||||
return self::instance()->table->del($key);
|
||||
}
|
||||
|
||||
public static function exist($key)
|
||||
{
|
||||
return self::instance()->table->exist($key);
|
||||
}
|
||||
|
||||
public static function setMultiple(array $items)
|
||||
{
|
||||
foreach ($items as $key => $value) {
|
||||
self::set($key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
public static function clear()
|
||||
{
|
||||
foreach (self::instance()->table as $key => $row) {
|
||||
self::del($key);
|
||||
}
|
||||
}
|
||||
|
||||
public static function getAll()
|
||||
{
|
||||
$result = [];
|
||||
foreach (self::instance()->table as $key => $row) {
|
||||
$result[$key] = $row['value'];
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
7
app/Module/Table/GlobalData.php
Normal file
7
app/Module/Table/GlobalData.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace App\Module\Table;
|
||||
|
||||
class GlobalData extends AbstractData
|
||||
{
|
||||
}
|
||||
63
app/Module/Table/OnlineData.php
Normal file
63
app/Module/Table/OnlineData.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace App\Module\Table;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Tasks\LineTask;
|
||||
use App\Tasks\PushTask;
|
||||
use Carbon\Carbon;
|
||||
use Hhxsv5\LaravelS\Swoole\Task\Task;
|
||||
|
||||
class OnlineData extends AbstractData
|
||||
{
|
||||
/**
|
||||
* 上线
|
||||
* @param $userid
|
||||
* @return float|int|mixed
|
||||
*/
|
||||
public static function online($userid)
|
||||
{
|
||||
$key = "online::" . $userid;
|
||||
$value = self::instance()->getTable()->incr($key, 'value');
|
||||
if ($value === 1) {
|
||||
// 通知上线
|
||||
Task::deliver(new LineTask($userid, true));
|
||||
// 推送离线时收到的消息
|
||||
Task::deliver(new PushTask("RETRY::" . $userid));
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 离线
|
||||
* @param $userid
|
||||
* @return float|int|mixed
|
||||
*/
|
||||
public static function offline($userid)
|
||||
{
|
||||
$key = "online::" . $userid;
|
||||
$value = self::instance()->getTable()->decr($key, 'value');
|
||||
if ($value === 0) {
|
||||
// 更新最后在线时间
|
||||
User::whereUserid($userid)->update([
|
||||
'line_at' => Carbon::now()
|
||||
]);
|
||||
// 通知下线
|
||||
Task::deliver(new LineTask($userid, false));
|
||||
// 清除在线状态
|
||||
self::instance()->getTable()->del($key);
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取在线状态
|
||||
* @param $userid
|
||||
* @return int
|
||||
*/
|
||||
public static function live($userid)
|
||||
{
|
||||
$key = "online::" . $userid;
|
||||
return intval(self::instance()->getTable()->get($key));
|
||||
}
|
||||
}
|
||||
@@ -15,23 +15,48 @@ class TimeRange
|
||||
public function __construct($data)
|
||||
{
|
||||
if (is_array($data)) {
|
||||
$range = $this->format($data['timerange']);
|
||||
if ($data['updated_at'] || $data['at_after']) {
|
||||
$range[0] = $data['updated_at'] ?: $data['at_after'];
|
||||
}
|
||||
if ($data['deleted_at']) {
|
||||
$range[1] = $data['deleted_at'];
|
||||
$keys = array_keys($data);
|
||||
if (count($keys) === 2 && $keys[0] === 0 && $keys[1] === 1) {
|
||||
$range = $data;
|
||||
} else {
|
||||
$range = $this->format($data['timerange']);
|
||||
if ($data['updated_at'] || $data['at_after']) {
|
||||
$range[0] = $data['updated_at'] ?: $data['at_after'];
|
||||
}
|
||||
if ($data['deleted_at']) {
|
||||
$range[1] = $data['deleted_at'];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$range = $this->format($data);
|
||||
}
|
||||
//
|
||||
$updated = Base::isNumber($range[0]) ? intval($range[0]) : trim($range[0]);
|
||||
$deleted = Base::isNumber($range[1]) ? intval($range[1]) : trim($range[1]);
|
||||
//
|
||||
$timezone = config('app.timezone');
|
||||
$this->updated = $updated ? Carbon::parse($updated)->setTimezone($timezone) : null;
|
||||
$this->deleted = $deleted ? Carbon::parse($deleted)->setTimezone($timezone) : null;
|
||||
$this->updated = $range[0] ? Base::newCarbon($range[0]) : null;
|
||||
$this->deleted = $range[1] ? Base::newCarbon($range[1]) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Carbon|null
|
||||
*/
|
||||
public function firstTime(): ?Carbon
|
||||
{
|
||||
return $this->updated;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Carbon|null
|
||||
*/
|
||||
public function lastTime(): ?Carbon
|
||||
{
|
||||
return $this->deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isExist(): bool
|
||||
{
|
||||
return $this->updated && $this->deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -43,7 +68,7 @@ class TimeRange
|
||||
private function format($timerange)
|
||||
{
|
||||
$search = str_contains($timerange, ":") ? ["|"] : ["|", "-"];
|
||||
return explode(",", str_replace($search, ",", $timerange));
|
||||
return Base::newTrim(explode(",", str_replace($search, ",", $timerange)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
380
app/Module/Timer.php
Normal file
380
app/Module/Timer.php
Normal file
@@ -0,0 +1,380 @@
|
||||
<?php
|
||||
|
||||
namespace App\Module;
|
||||
|
||||
use App\Services\RequestContext;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class Timer
|
||||
{
|
||||
/**
|
||||
* 获取时间戳
|
||||
* @return int
|
||||
*/
|
||||
public static function time()
|
||||
{
|
||||
return intval(RequestContext::get("start_time", time()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取毫秒时间戳
|
||||
* @return float
|
||||
*/
|
||||
public static function msecTime()
|
||||
{
|
||||
list($msec, $sec) = explode(' ', microtime());
|
||||
$time = explode(".", $sec . ($msec * 1000));
|
||||
return $time[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* 时间差(不够1个小时算一个小时)
|
||||
* @param int $s 开始时间戳
|
||||
* @param int $e 结束时间戳
|
||||
* @return string
|
||||
*/
|
||||
public static function timeDiff($s, $e)
|
||||
{
|
||||
$time = $e - $s;
|
||||
$days = 0;
|
||||
if ($time >= 86400) { // 如果大于1天
|
||||
$days = (int)($time / 86400);
|
||||
$time = $time % 86400; // 计算天后剩余的毫秒数
|
||||
}
|
||||
$hours = 0;
|
||||
if ($time >= 3600) { // 如果大于1小时
|
||||
$hours = (int)($time / 3600);
|
||||
$time = $time % 3600; // 计算小时后剩余的毫秒数
|
||||
}
|
||||
$minutes = ceil($time / 60); // 剩下的毫秒数都算作分
|
||||
$daysStr = $days > 0 ? $days . '天' : '';
|
||||
$hoursStr = ($hours > 0 || ($days > 0 && $minutes > 0)) ? $hours . '时' : '';
|
||||
$minuteStr = ($minutes > 0) ? $minutes . '分' : '';
|
||||
return $daysStr . $hoursStr . $minuteStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* 时间秒数格式化
|
||||
* @param int $time 时间秒数
|
||||
* @return string
|
||||
*/
|
||||
public static function timeFormat($time)
|
||||
{
|
||||
$days = 0;
|
||||
if ($time >= 86400) { // 如果大于1天
|
||||
$days = (int)($time / 86400);
|
||||
$time = $time % 86400; // 计算天后剩余的毫秒数
|
||||
}
|
||||
$hours = 0;
|
||||
if ($time >= 3600) { // 如果大于1小时
|
||||
$hours = (int)($time / 3600);
|
||||
$time = $time % 3600; // 计算小时后剩余的毫秒数
|
||||
}
|
||||
$minutes = ceil($time / 60); // 剩下的毫秒数都算作分
|
||||
$daysStr = $days > 0 ? $days . '天' : '';
|
||||
$hoursStr = ($hours > 0 || ($days > 0 && $minutes > 0)) ? $hours . '时' : '';
|
||||
$minuteStr = ($minutes > 0) ? $minutes . '分' : '';
|
||||
return $daysStr . $hoursStr . $minuteStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测日期格式
|
||||
* @param string $str 需要检测的字符串
|
||||
* @return bool
|
||||
*/
|
||||
public static function isDate($str)
|
||||
{
|
||||
$strArr = explode('-', $str);
|
||||
if (empty($strArr) || count($strArr) != 3) {
|
||||
return false;
|
||||
} else {
|
||||
list($year, $month, $day) = $strArr;
|
||||
if (checkdate(intval($month), intval($day), intval($year))) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测时间格式
|
||||
* @param string $str 需要检测的字符串
|
||||
* @return bool
|
||||
*/
|
||||
public static function isTime($str)
|
||||
{
|
||||
$strArr = explode(':', $str);
|
||||
$count = count($strArr);
|
||||
if ($count < 2 || $count > 3) {
|
||||
return false;
|
||||
}
|
||||
$hour = $strArr[0];
|
||||
if ($hour < 0 || $hour > 23) {
|
||||
return false;
|
||||
}
|
||||
$minute = $strArr[1];
|
||||
if ($minute < 0 || $minute > 59) {
|
||||
return false;
|
||||
}
|
||||
if ($count == 3) {
|
||||
$second = $strArr[2];
|
||||
if ($second < 0 || $second > 59) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测 日期格式 或 时间格式
|
||||
* @param string $str 需要检测的字符串
|
||||
* @return bool
|
||||
*/
|
||||
public static function isDateOrTime($str)
|
||||
{
|
||||
return self::isDate($str) || self::isTime($str);
|
||||
}
|
||||
|
||||
/**
|
||||
* 时间转毫秒时间戳
|
||||
* @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 $date
|
||||
* @return false|string
|
||||
*/
|
||||
public static function forumDate($date)
|
||||
{
|
||||
$dur = time() - $date;
|
||||
if ($date > Carbon::now()->startOf('day')->timestamp) {
|
||||
//今天
|
||||
if ($dur < 60) {
|
||||
return max($dur, 1) . '秒前';
|
||||
} elseif ($dur < 3600) {
|
||||
return floor($dur / 60) . '分钟前';
|
||||
} elseif ($dur < 86400) {
|
||||
return floor($dur / 3600) . '小时前';
|
||||
} else {
|
||||
return date("H:i", $date);
|
||||
}
|
||||
} elseif ($date > Carbon::now()->subDays()->startOf('day')->timestamp) {
|
||||
//昨天
|
||||
return '昨天';
|
||||
} elseif ($date > Carbon::now()->subDays(2)->startOf('day')->timestamp) {
|
||||
//前天
|
||||
return '前天';
|
||||
} elseif ($dur > 86400) {
|
||||
//x天前
|
||||
return floor($dur / 86400) . '天前';
|
||||
}
|
||||
return date("Y-m-d", $date);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取(时间戳转)今天是星期几,只返回(几)
|
||||
* @param string|number $unixTime
|
||||
* @return string
|
||||
*/
|
||||
public static function getWeek($unixTime = '')
|
||||
{
|
||||
$unixTime = is_numeric($unixTime) ? $unixTime : time();
|
||||
$weekarray = ['日', '一', '二', '三', '四', '五', '六'];
|
||||
return $weekarray[date('w', $unixTime)];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取(时间戳转)现在时间段:深夜、凌晨、早晨、上午.....
|
||||
* @param string|number $unixTime
|
||||
* @return string
|
||||
*/
|
||||
public static function getDayeSegment($unixTime = '')
|
||||
{
|
||||
$unixTime = is_numeric($unixTime) ? $unixTime : time();
|
||||
$H = date('H', $unixTime);
|
||||
if ($H >= 19) {
|
||||
return '晚上';
|
||||
} elseif ($H >= 18) {
|
||||
return '傍晚';
|
||||
} elseif ($H >= 13) {
|
||||
return '下午';
|
||||
} elseif ($H >= 12) {
|
||||
return '中午';
|
||||
} elseif ($H >= 8) {
|
||||
return '上午';
|
||||
} elseif ($H >= 5) {
|
||||
return '早晨';
|
||||
} elseif ($H >= 1) {
|
||||
return '凌晨';
|
||||
} elseif ($H >= 0) {
|
||||
return '深夜';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 秒 (转) 年、天、时、分、秒
|
||||
* @param $time
|
||||
* @return array|bool
|
||||
*/
|
||||
public static function sec2time($time)
|
||||
{
|
||||
if (is_numeric($time)) {
|
||||
$value = array(
|
||||
"years" => 0, "days" => 0, "hours" => 0,
|
||||
"minutes" => 0, "seconds" => 0,
|
||||
);
|
||||
if ($time >= 86400) {
|
||||
$value["days"] = floor($time / 86400);
|
||||
$time = ($time % 86400);
|
||||
}
|
||||
if ($time >= 3600) {
|
||||
$value["hours"] = floor($time / 3600);
|
||||
$time = ($time % 3600);
|
||||
}
|
||||
if ($time >= 60) {
|
||||
$value["minutes"] = floor($time / 60);
|
||||
$time = ($time % 60);
|
||||
}
|
||||
$value["seconds"] = floor($time);
|
||||
return (array)$value;
|
||||
} else {
|
||||
return (bool)FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 年、天、时、分、秒 (转) 秒
|
||||
* @param $value
|
||||
* @return int
|
||||
*/
|
||||
public static function time2sec($value)
|
||||
{
|
||||
$time = intval($value["seconds"]);
|
||||
$time += intval($value["minutes"] * 60);
|
||||
$time += intval($value["hours"] * 3600);
|
||||
$time += intval($value["days"] * 86400);
|
||||
$time += intval($value["years"] * 31536000);
|
||||
return $time;
|
||||
}
|
||||
|
||||
/**
|
||||
* 阿拉伯数字转化为中文
|
||||
* @param $num
|
||||
* @return string
|
||||
*/
|
||||
public static function chinaNum($num)
|
||||
{
|
||||
$china = array('零', '一', '二', '三', '四', '五', '六', '七', '八', '九');
|
||||
$arr = str_split($num);
|
||||
$txt = '';
|
||||
for ($i = 0; $i < count($arr); $i++) {
|
||||
$txt .= $china[$arr[$i]];
|
||||
}
|
||||
return $txt;
|
||||
}
|
||||
|
||||
/**
|
||||
* 阿拉伯数字转化为中文(用于星期,七改成日)
|
||||
* @param $num
|
||||
* @return string
|
||||
*/
|
||||
public static function chinaNumZ($num)
|
||||
{
|
||||
return str_replace("七", "日", Timer::chinaNum($num));
|
||||
}
|
||||
|
||||
/**
|
||||
* 时间是否在时间范围内
|
||||
* @param array $timeRanges 如:['08:00', '12:00'] 或 [['08:00', '12:00'], ['14:00', '18:00']]
|
||||
* @param string|null $currentTime
|
||||
* @return bool
|
||||
*/
|
||||
public static function isTimeInRanges(array $timeRanges, ?string $currentTime = null): bool
|
||||
{
|
||||
// 如果没有传入当前时间,使用当前时间
|
||||
$currentTime = $currentTime ?? date('H:i');
|
||||
|
||||
// 转换当前时间为分钟数,便于比较
|
||||
$currentMinutes = self::timeToMinutes($currentTime);
|
||||
if ($currentMinutes === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 将单个时间范围转换为数组格式
|
||||
if (isset($timeRanges[0]) && !is_array($timeRanges[0])) {
|
||||
$timeRanges = [$timeRanges];
|
||||
}
|
||||
|
||||
// 过滤并检查有效的时间范围
|
||||
foreach ($timeRanges as $range) {
|
||||
if (!self::isValidTimeRange($range)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$startMinutes = self::timeToMinutes($range[0]);
|
||||
$endMinutes = self::timeToMinutes($range[1]);
|
||||
|
||||
if ($startMinutes === false || $endMinutes === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($startMinutes <= $currentMinutes && $currentMinutes <= $endMinutes) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 辅助函数:检查时间范围是否有效
|
||||
* @param $range
|
||||
* @return bool
|
||||
*/
|
||||
private static function isValidTimeRange($range): bool
|
||||
{
|
||||
return is_array($range)
|
||||
&& count($range) === 2
|
||||
&& is_string($range[0])
|
||||
&& is_string($range[1])
|
||||
&& !empty($range[0])
|
||||
&& !empty($range[1])
|
||||
&& preg_match('/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/', $range[0])
|
||||
&& preg_match('/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/', $range[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 辅助函数:将时间转换为分钟数
|
||||
* @param string $time
|
||||
* @return false|float|int
|
||||
*/
|
||||
private static function timeToMinutes(string $time)
|
||||
{
|
||||
if (!preg_match('/^([01]?[0-9]|2[0-3]):([0-5][0-9])$/', $time, $matches)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return intval($matches[1]) * 60 + intval($matches[2]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -87,6 +87,9 @@ class ProjectTaskObserver
|
||||
if (in_array('project', $dataType)) {
|
||||
return ProjectUser::whereProjectId($projectTask->project_id)->pluck('userid')->toArray();
|
||||
}
|
||||
if (in_array('projectOwnerUser', $dataType)) {
|
||||
return ProjectUser::whereProjectId($projectTask->project_id)->where('owner', 1)->pluck('userid')->toArray();
|
||||
}
|
||||
$array = [];
|
||||
if (in_array('task', $dataType)) {
|
||||
$array = array_merge($array, ProjectTaskUser::whereTaskId($projectTask->id)->pluck('userid')->toArray());
|
||||
@@ -112,7 +115,8 @@ class ProjectTaskObserver
|
||||
case 3:
|
||||
$dataType = $projectTask->visibility == 2 ? ['task'] : ['task', 'visibility'];
|
||||
$forgetUserids = self::userids($projectTask, $dataType);
|
||||
$recordUserids = array_diff($projectUserids, $forgetUserids);
|
||||
$projectOwnerUserIds = self::userids($projectTask, 'projectOwnerUser');
|
||||
$recordUserids = array_diff($projectUserids, $forgetUserids, $projectOwnerUserIds);
|
||||
Deleted::record('projectTask', $projectTask->id, $recordUserids);
|
||||
Deleted::forget('projectTask', $projectTask->id, $forgetUserids);
|
||||
break;
|
||||
|
||||
@@ -49,7 +49,8 @@ class WebSocketDialogObserver
|
||||
*/
|
||||
public function restored(WebSocketDialog $webSocketDialog)
|
||||
{
|
||||
Deleted::forget('dialog', $webSocketDialog->id, $this->userids($webSocketDialog));
|
||||
$userids = $this->userids($webSocketDialog);
|
||||
Deleted::forget('dialog', $webSocketDialog->id, $userids);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
142
app/Services/RequestContext.php
Normal file
142
app/Services/RequestContext.php
Normal file
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class RequestContext
|
||||
{
|
||||
/** @var array<string, array<string, mixed>> */
|
||||
private static array $context = [];
|
||||
|
||||
private const REQUEST_ID_PREFIX = 'req_';
|
||||
|
||||
/**
|
||||
* 生成请求唯一ID
|
||||
*/
|
||||
public static function generateRequestId(): string
|
||||
{
|
||||
return self::REQUEST_ID_PREFIX . uniqid() . mt_rand(10000, 99999);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前请求ID
|
||||
*/
|
||||
private static function getCurrentRequestId(): ?string
|
||||
{
|
||||
/** @var Request $request */
|
||||
$request = request();
|
||||
return $request?->requestId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置请求上下文
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @param string|null $requestId
|
||||
* @return void
|
||||
*/
|
||||
public static function set(string $key, mixed $value, ?string $requestId = null): void
|
||||
{
|
||||
$requestId = $requestId ?? self::getCurrentRequestId();
|
||||
if ($requestId === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::$context[$requestId] ??= [];
|
||||
self::$context[$requestId][$key] = $value;
|
||||
}
|
||||
|
||||
// 与 set 方法的区别是,save 方法会返回传入的 value 值
|
||||
public static function save(string $key, mixed $value, ?string $requestId = null): mixed
|
||||
{
|
||||
self::set($key, $value, $requestId);
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求上下文
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $default
|
||||
* @param string|null $requestId
|
||||
* @return mixed
|
||||
*/
|
||||
public static function get(string $key, mixed $default = null, ?string $requestId = null): mixed
|
||||
{
|
||||
$requestId = $requestId ?? self::getCurrentRequestId();
|
||||
if ($requestId === null) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
return self::$context[$requestId][$key] ?? $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断请求上下文是否存在
|
||||
*
|
||||
* @param string $key
|
||||
* @param string|null $requestId
|
||||
* @return bool
|
||||
*/
|
||||
public static function has(string $key, ?string $requestId = null): bool
|
||||
{
|
||||
$requestId = $requestId ?? self::getCurrentRequestId();
|
||||
if ($requestId === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isset(self::$context[$requestId][$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理请求上下文
|
||||
*
|
||||
* @param string|null $requestId
|
||||
* @return void
|
||||
*/
|
||||
public static function clear(?string $requestId = null): void
|
||||
{
|
||||
$requestId = $requestId ?? self::getCurrentRequestId();
|
||||
if ($requestId === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
unset(self::$context[$requestId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前请求的所有上下文数据
|
||||
*
|
||||
* @param string|null $requestId
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public static function getAll(?string $requestId = null): array
|
||||
{
|
||||
$requestId = $requestId ?? self::getCurrentRequestId();
|
||||
if ($requestId === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return self::$context[$requestId] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量设置上下文数据
|
||||
*
|
||||
* @param array<string, mixed> $data
|
||||
* @param string|null $requestId
|
||||
* @return void
|
||||
*/
|
||||
public static function setMultiple(array $data, ?string $requestId = null): void
|
||||
{
|
||||
$requestId = $requestId ?? self::getCurrentRequestId();
|
||||
if ($requestId === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::$context[$requestId] ??= [];
|
||||
self::$context[$requestId] = array_merge(self::$context[$requestId], $data);
|
||||
}
|
||||
}
|
||||
@@ -6,10 +6,9 @@ namespace App\Services;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Models\WebSocket;
|
||||
use App\Models\WebSocketDialogMsg;
|
||||
use App\Module\Base;
|
||||
use App\Module\Doo;
|
||||
use App\Tasks\LineTask;
|
||||
use App\Module\Table\OnlineData;
|
||||
use App\Tasks\PushTask;
|
||||
use Cache;
|
||||
use Carbon\Carbon;
|
||||
@@ -25,7 +24,6 @@ use Swoole\WebSocket\Server;
|
||||
class WebSocketService implements WebSocketHandlerInterface
|
||||
{
|
||||
/**
|
||||
* 声明没有参数的构造函数
|
||||
* WebSocketService constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
@@ -37,14 +35,14 @@ class WebSocketService implements WebSocketHandlerInterface
|
||||
* 连接建立时触发
|
||||
* @param Server $server
|
||||
* @param Request $request
|
||||
* @return void
|
||||
*/
|
||||
public function onOpen(Server $server, Request $request)
|
||||
{
|
||||
$fd = $request->fd;
|
||||
$get = Base::newTrim($request->get);
|
||||
$action = $get['action'];
|
||||
Cache::forget("User::encrypt:" . $fd);
|
||||
switch ($action) {
|
||||
switch ($get['action']) {
|
||||
/**
|
||||
* 网页访问
|
||||
*/
|
||||
@@ -52,23 +50,21 @@ class WebSocketService implements WebSocketHandlerInterface
|
||||
{
|
||||
Doo::load($get['token'], $get['language']);
|
||||
//
|
||||
if (Doo::userId() > 0
|
||||
&& !Doo::userExpired()
|
||||
&& $user = User::whereUserid(Doo::userId())->whereEmail(Doo::userEmail())->whereEncrypt(Doo::userEncrypt())->first()) {
|
||||
// 保存用户
|
||||
$this->saveUser($fd, $user->userid);
|
||||
// 发送open事件
|
||||
$count = 0;
|
||||
$userid = Doo::userId();
|
||||
if ($userid > 0 && !Doo::userExpired()) {
|
||||
$count = User::whereUserid($userid)->whereEmail(Doo::userEmail())->whereEncrypt(Doo::userEncrypt())->count();
|
||||
}
|
||||
if ($count) {
|
||||
// 用户正常
|
||||
$server->push($fd, Base::array2json([
|
||||
'type' => 'open',
|
||||
'data' => [
|
||||
'fd' => $fd,
|
||||
'ud' => $user->userid,
|
||||
'ud' => $userid,
|
||||
],
|
||||
]));
|
||||
// 通知上线
|
||||
Task::deliver(new LineTask($user->userid, true));
|
||||
// 推送离线时收到的消息
|
||||
Task::deliver(new PushTask("RETRY::" . $user->userid));
|
||||
$this->userOn($fd, $userid);
|
||||
} else {
|
||||
// 用户不存在
|
||||
$server->push($fd, Base::array2json([
|
||||
@@ -78,7 +74,6 @@ class WebSocketService implements WebSocketHandlerInterface
|
||||
],
|
||||
]));
|
||||
$server->close($fd);
|
||||
$this->deleteUser($fd);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -92,39 +87,23 @@ class WebSocketService implements WebSocketHandlerInterface
|
||||
* 收到消息时触发
|
||||
* @param Server $server
|
||||
* @param Frame $frame
|
||||
* @return void
|
||||
*/
|
||||
public function onMessage(Server $server, Frame $frame)
|
||||
{
|
||||
$msg = Base::json2array($frame->data);
|
||||
$type = $msg['type']; // 消息类型
|
||||
$msgId = $msg['msgId']; // 消息ID(用于回调)
|
||||
$data = $msg['data']; // 消息详情
|
||||
//
|
||||
$type = $msg['type']; // 消息类型
|
||||
$data = $msg['data']; // 消息详情
|
||||
$msgId = $msg['msgId'] ?? $msg['msg_id']; // 消息ID(用于回调)
|
||||
|
||||
// 处理消息
|
||||
$reData = [];
|
||||
switch ($type) {
|
||||
/**
|
||||
* 收到回执
|
||||
*/
|
||||
// 收到回执
|
||||
case 'receipt':
|
||||
return;
|
||||
|
||||
/**
|
||||
* 已阅消息
|
||||
*/
|
||||
case 'readMsg':
|
||||
$ids = is_array($data['id']) ? $data['id'] : [$data['id']];
|
||||
$userid = $this->getUserid($frame->fd);
|
||||
WebSocketDialogMsg::whereIn('id', $ids)->chunkById(20, function($list) use ($userid) {
|
||||
/** @var WebSocketDialogMsg $item */
|
||||
foreach ($list as $item) {
|
||||
$item->readSuccess($userid);
|
||||
}
|
||||
});
|
||||
return;
|
||||
|
||||
/**
|
||||
* 访问状态
|
||||
*/
|
||||
// 访问状态
|
||||
case 'path':
|
||||
$row = WebSocket::whereFd($frame->fd)->first();
|
||||
if ($row) {
|
||||
@@ -141,9 +120,7 @@ class WebSocketService implements WebSocketHandlerInterface
|
||||
}
|
||||
return;
|
||||
|
||||
/**
|
||||
* 加密参数
|
||||
*/
|
||||
// 加密参数
|
||||
case 'encrypt':
|
||||
if ($data['type'] === 'pgp') {
|
||||
$data['key'] = Doo::pgpPublicFormat($data['key']);
|
||||
@@ -151,7 +128,8 @@ class WebSocketService implements WebSocketHandlerInterface
|
||||
Cache::put("User::encrypt:" . $frame->fd, Base::array2json($data), Carbon::now()->addDay());
|
||||
return;
|
||||
}
|
||||
//
|
||||
|
||||
// 返回消息
|
||||
if ($msgId) {
|
||||
PushTask::push([
|
||||
'fd' => $frame->fd,
|
||||
@@ -169,15 +147,11 @@ class WebSocketService implements WebSocketHandlerInterface
|
||||
* @param Server $server
|
||||
* @param $fd
|
||||
* @param $reactorId
|
||||
* @throws \Exception
|
||||
* @return void
|
||||
*/
|
||||
public function onClose(Server $server, $fd, $reactorId)
|
||||
{
|
||||
$userid = $this->getUserid($fd);
|
||||
if($userid){
|
||||
Task::deliver(new LineTask($userid, false)); // 通知离线
|
||||
$this->deleteUser($fd);
|
||||
}
|
||||
$this->userOff($fd);
|
||||
}
|
||||
|
||||
/** ****************************************************************************** */
|
||||
@@ -185,65 +159,49 @@ class WebSocketService implements WebSocketHandlerInterface
|
||||
/** ****************************************************************************** */
|
||||
|
||||
/**
|
||||
* 保存用户
|
||||
* 用户上线
|
||||
* @param $fd
|
||||
* @param $userid
|
||||
* @return void
|
||||
*/
|
||||
private function saveUser($fd, $userid)
|
||||
private function userOn($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,
|
||||
]);
|
||||
OnlineData::online($userid);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除用户
|
||||
* 用户下线
|
||||
* @param $fd
|
||||
* @return void
|
||||
*/
|
||||
private function deleteUser($fd)
|
||||
private function userOff($fd)
|
||||
{
|
||||
Cache::forget("User::fd:" . $fd);
|
||||
//
|
||||
$array = [];
|
||||
WebSocket::whereFd($fd)->chunk(10, function($list) use (&$array) {
|
||||
$paths = [];
|
||||
WebSocket::whereFd($fd)->chunk(10, function($list) use (&$paths) {
|
||||
/** @var WebSocket $item */
|
||||
foreach ($list as $item) {
|
||||
$item->delete();
|
||||
if ($item->userid) {
|
||||
// 离线时更新会员最后在线时间
|
||||
User::whereUserid($item->userid)->update([
|
||||
'line_at' => Carbon::now()
|
||||
]);
|
||||
Cache::forget("User::online:" . $item->userid);
|
||||
OnlineData::offline($item->userid);
|
||||
}
|
||||
if ($item->path && str_starts_with($item->path, "/single/file/")) {
|
||||
$array[$item->path] = $item->path;
|
||||
$paths[$item->path] = $item->path;
|
||||
}
|
||||
}
|
||||
});
|
||||
foreach ($array as $path) {
|
||||
foreach ($paths as $path) {
|
||||
$this->pushPath($path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据fd获取会员ID
|
||||
* @param $fd
|
||||
* @return int
|
||||
*/
|
||||
private function getUserid($fd)
|
||||
{
|
||||
return intval(WebSocket::whereFd($fd)->value('userid'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送给相同访问状态的会员
|
||||
* 通知相同访问路径的用户
|
||||
* @param $path
|
||||
*/
|
||||
private function pushPath($path)
|
||||
|
||||
@@ -3,7 +3,6 @@ namespace App\Tasks;
|
||||
|
||||
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
|
||||
|
||||
use App\Models\AbstractModel;
|
||||
use App\Models\ProjectTask;
|
||||
use App\Module\Base;
|
||||
use Carbon\Carbon;
|
||||
|
||||
@@ -10,6 +10,7 @@ use App\Module\Base;
|
||||
use App\Module\Doo;
|
||||
use App\Module\Ihttp;
|
||||
use Carbon\Carbon;
|
||||
use DB;
|
||||
|
||||
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
|
||||
|
||||
@@ -62,6 +63,22 @@ class BotReceiveMsgTask extends AbstractTask
|
||||
*/
|
||||
private function botManagerReceive(WebSocketDialogMsg $msg, User $botUser)
|
||||
{
|
||||
// 位置消息
|
||||
if ($msg->type === 'location') {
|
||||
// 签到机器人
|
||||
if ($botUser->email === 'check-in@bot.system') {
|
||||
$content = UserBot::checkinBotQuickMsg('locat-checkin', $msg->userid, $msg->msg);
|
||||
if ($content) {
|
||||
WebSocketDialogMsg::sendMsg(null, $msg->dialog_id, 'template', [
|
||||
'type' => 'content',
|
||||
'content' => $content,
|
||||
], $botUser->userid, false, false, true); // todo 未能在任务end事件来发送任务
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 文本消息
|
||||
if ($msg->type !== 'text') {
|
||||
return;
|
||||
}
|
||||
@@ -69,7 +86,7 @@ class BotReceiveMsgTask extends AbstractTask
|
||||
if ($this->mention) {
|
||||
$original = preg_replace("/<span class=\"mention user\" data-id=\"(\d+)\">(.*?)<\/span>/", "", $original);
|
||||
}
|
||||
if (preg_match("/<span[^>]*?data-quick-key=([\"'])(.*?)\\1[^>]*?>(.*?)<\/span>/is", $original, $match)) {
|
||||
if (preg_match("/<span[^>]*?data-quick-key=([\"'])([^\"']+?)\\1[^>]*?>(.*?)<\/span>/is", $original, $match)) {
|
||||
$command = $match[2];
|
||||
if (str_starts_with($command, '%3A.')) {
|
||||
$command = ":" . substr($command, 4);
|
||||
@@ -93,16 +110,23 @@ class BotReceiveMsgTask extends AbstractTask
|
||||
}
|
||||
// 签到机器人
|
||||
if ($botUser->email === 'check-in@bot.system') {
|
||||
$text = UserBot::checkinBotQuickMsg($command, $msg->userid);
|
||||
if ($text) {
|
||||
WebSocketDialogMsg::sendMsg(null, $msg->dialog_id, 'text', ['text' => $text], $botUser->userid, false, false, true); // todo 未能在任务end事件来发送任务
|
||||
$content = UserBot::checkinBotQuickMsg($command, $msg->userid);
|
||||
if ($content) {
|
||||
WebSocketDialogMsg::sendMsg(null, $msg->dialog_id, 'template', [
|
||||
'type' => 'content',
|
||||
'content' => $content,
|
||||
], $botUser->userid, false, false, true); // todo 未能在任务end事件来发送任务
|
||||
}
|
||||
}
|
||||
// 隐私机器人
|
||||
if ($botUser->email === 'anon-msg@bot.system') {
|
||||
$text = UserBot::anonBotQuickMsg($command);
|
||||
if ($text) {
|
||||
WebSocketDialogMsg::sendMsg(null, $msg->dialog_id, 'text', ['text' => $text], $botUser->userid, false, false, true); // todo 未能在任务end事件来发送任务
|
||||
$array = UserBot::anonBotQuickMsg($command);
|
||||
if ($array) {
|
||||
WebSocketDialogMsg::sendMsg(null, $msg->dialog_id, 'template', [
|
||||
'type' => 'content',
|
||||
'title' => $array['title'],
|
||||
'content' => $array['content'],
|
||||
], $botUser->userid, false, false, true); // todo 未能在任务end事件来发送任务
|
||||
}
|
||||
}
|
||||
// 管理机器人
|
||||
@@ -112,15 +136,17 @@ class BotReceiveMsgTask extends AbstractTask
|
||||
} 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事件来发送任务
|
||||
WebSocketDialogMsg::sendMsg(null, $msg->dialog_id, 'template', [
|
||||
'type' => 'content',
|
||||
'content' => "非常抱歉,我不是你的机器人,无法完成你的指令。",
|
||||
], $botUser->userid, false, false, true); // todo 未能在任务end事件来发送任务
|
||||
return;
|
||||
}
|
||||
//
|
||||
$array = Base::newTrim(explode(" ", "{$command} "));
|
||||
$type = $array[0];
|
||||
$data = [];
|
||||
$notice = "";
|
||||
$content = "";
|
||||
if (!$isManager && in_array($type, ['/list', '/newbot'])) {
|
||||
return; // 这些操作仅支持【机器人管理】机器人
|
||||
}
|
||||
@@ -143,20 +169,19 @@ class BotReceiveMsgTask extends AbstractTask
|
||||
->orderByDesc('id')
|
||||
->get();
|
||||
if ($data->isEmpty()) {
|
||||
$type = "notice";
|
||||
$notice = "您没有创建机器人。";
|
||||
$content = "您没有创建机器人。";
|
||||
}
|
||||
break;
|
||||
|
||||
/**
|
||||
* 详情
|
||||
*/
|
||||
case '/hello':
|
||||
case '/info':
|
||||
$botId = $isManager ? $array[1] : $botUser->userid;
|
||||
$data = $this->botManagerOne($botId, $msg->userid);
|
||||
if (!$data) {
|
||||
$type = "notice";
|
||||
$notice = "机器人不存在。";
|
||||
$content = "机器人不存在。";
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -169,27 +194,27 @@ class BotReceiveMsgTask extends AbstractTask
|
||||
->where('users.bot', 1)
|
||||
->where('user_bots.userid', $msg->userid)
|
||||
->count() >= 50) {
|
||||
$type = "notice";
|
||||
$notice = "超过最大创建数量。";
|
||||
$content = "超过最大创建数量。";
|
||||
break;
|
||||
}
|
||||
if (strlen($array[1]) < 2 || strlen($array[1]) > 20) {
|
||||
$type = "notice";
|
||||
$notice = "机器人名称由2-20个字符组成。";
|
||||
$content = "机器人名称由2-20个字符组成。";
|
||||
break;
|
||||
}
|
||||
$data = User::botGetOrCreate("user-" . Base::generatePassword(), [
|
||||
'nickname' => $array[1]
|
||||
], $msg->userid);
|
||||
if (empty($data)) {
|
||||
$type = "notice";
|
||||
$notice = "创建失败。";
|
||||
$content = "创建失败。";
|
||||
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事件来发送任务
|
||||
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'template', [
|
||||
'type' => '/hello',
|
||||
'title' => '创建成功。',
|
||||
'data' => $data,
|
||||
], $data->userid); // todo 未能在任务end事件来发送任务
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -200,8 +225,7 @@ class BotReceiveMsgTask extends AbstractTask
|
||||
$botId = $isManager ? $array[1] : $botUser->userid;
|
||||
$nameString = $isManager ? $array[2] : $array[1];
|
||||
if (strlen($nameString) < 2 || strlen($nameString) > 20) {
|
||||
$type = "notice";
|
||||
$notice = "机器人名称由2-20个字符组成。";
|
||||
$content = "机器人名称由2-20个字符组成。";
|
||||
break;
|
||||
}
|
||||
$data = $this->botManagerOne($botId, $msg->userid);
|
||||
@@ -211,8 +235,7 @@ class BotReceiveMsgTask extends AbstractTask
|
||||
$data->pinyin = Base::cn2pinyin($nameString);
|
||||
$data->save();
|
||||
} else {
|
||||
$type = "notice";
|
||||
$notice = "机器人不存在。";
|
||||
$content = "机器人不存在。";
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -226,8 +249,7 @@ class BotReceiveMsgTask extends AbstractTask
|
||||
if ($data) {
|
||||
$data->deleteUser('delete bot');
|
||||
} else {
|
||||
$type = "notice";
|
||||
$notice = "机器人不存在。";
|
||||
$content = "机器人不存在。";
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -240,8 +262,7 @@ class BotReceiveMsgTask extends AbstractTask
|
||||
if ($data) {
|
||||
User::generateToken($data);
|
||||
} else {
|
||||
$type = "notice";
|
||||
$notice = "机器人不存在。";
|
||||
$content = "机器人不存在。";
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -256,8 +277,7 @@ class BotReceiveMsgTask extends AbstractTask
|
||||
$data->password = Doo::md5s(Base::generatePassword(32), $data->encrypt);
|
||||
$data->save();
|
||||
} else {
|
||||
$type = "notice";
|
||||
$notice = "机器人不存在。";
|
||||
$content = "机器人不存在。";
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -278,8 +298,7 @@ class BotReceiveMsgTask extends AbstractTask
|
||||
$data->clear_day = $userBot->clear_day;
|
||||
$data->clear_at = $userBot->clear_at; // 这两个参数只是作为输出,所以不保存
|
||||
} else {
|
||||
$type = "notice";
|
||||
$notice = "机器人不存在。";
|
||||
$content = "机器人不存在。";
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -291,8 +310,7 @@ class BotReceiveMsgTask extends AbstractTask
|
||||
$webhookUrl = $isManager ? $array[2] : $array[1];
|
||||
$data = $this->botManagerOne($botId, $msg->userid);
|
||||
if (strlen($webhookUrl) > 255) {
|
||||
$type = "notice";
|
||||
$notice = "webhook地址最长仅支持255个字符。";
|
||||
$content = "webhook地址最长仅支持255个字符。";
|
||||
} elseif ($data) {
|
||||
$userBot = UserBot::whereBotId($botId)->whereUserid($msg->userid)->first();
|
||||
if ($userBot) {
|
||||
@@ -303,8 +321,7 @@ class BotReceiveMsgTask extends AbstractTask
|
||||
$data->webhook_url = $userBot->webhook_url ?: '-';
|
||||
$data->webhook_num = $userBot->webhook_num; // 这两个参数只是作为输出,所以不保存
|
||||
} else {
|
||||
$type = "notice";
|
||||
$notice = "机器人不存在。";
|
||||
$content = "机器人不存在。";
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -316,43 +333,65 @@ class BotReceiveMsgTask extends AbstractTask
|
||||
$nameKey = $isManager ? $array[2] : $array[1];
|
||||
$data = $this->botManagerOne($botId, $msg->userid);
|
||||
if ($data) {
|
||||
$list = WebSocketDialog::select(['web_socket_dialogs.*', 'u.top_at', 'u.last_at', 'u.mark_unread', 'u.silence', 'u.hide', 'u.color', '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}%")
|
||||
$list = DB::table('web_socket_dialog_users as u')
|
||||
->select(['d.*', 'u.top_at', 'u.last_at', 'u.mark_unread', 'u.silence', 'u.hide', 'u.color', 'u.updated_at as user_at'])
|
||||
->join('web_socket_dialogs as d', 'u.dialog_id', '=', 'd.id')
|
||||
->where('u.userid', $data->userid)
|
||||
->where('d.name', 'LIKE', "%{$nameKey}%")
|
||||
->whereNull('d.deleted_at')
|
||||
->orderByDesc('u.top_at')
|
||||
->orderByDesc('u.last_at')
|
||||
->take(20)
|
||||
->get();
|
||||
if ($list->isEmpty()) {
|
||||
$type = "notice";
|
||||
$notice = "没有搜索到相关会话。";
|
||||
->get()
|
||||
->map(function($item) use ($data) {
|
||||
return WebSocketDialog::synthesizeData($item, $data->userid);
|
||||
})
|
||||
->all();
|
||||
if (empty($list)) {
|
||||
$content = "没有搜索到相关会话。";
|
||||
} else {
|
||||
$list->transform(function (WebSocketDialog $item) use ($data) {
|
||||
return $item->formatData($data->userid);
|
||||
});
|
||||
$data->list = $list; // 这个参数只是作为输出,所以不保存
|
||||
}
|
||||
} else {
|
||||
$type = "notice";
|
||||
$notice = "机器人不存在。";
|
||||
$content = "机器人不存在。";
|
||||
}
|
||||
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);
|
||||
|
||||
if ($content) {
|
||||
$msgData = [
|
||||
'type' => 'content',
|
||||
'content' => $content,
|
||||
];
|
||||
} else {
|
||||
$msgData = [
|
||||
'type' => $type,
|
||||
'data' => $data,
|
||||
];
|
||||
$msgData['title'] = match ($type) {
|
||||
'/hello' => '您好',
|
||||
'/help' => '帮助指令',
|
||||
'/list' => '我的机器人',
|
||||
'/info' => '机器人信息',
|
||||
'/newbot' => '新建机器人',
|
||||
'/setname' => '设置名称',
|
||||
'/deletebot' => '删除机器人',
|
||||
'/token' => '机器人Token',
|
||||
'/revoke' => '更新Token',
|
||||
'/webhook' => '设置Webhook',
|
||||
'/clearday' => '设置保留消息时间',
|
||||
'/dialog' => '对话列表',
|
||||
'/api' => 'API接口文档',
|
||||
default => '不支持的指令',
|
||||
};
|
||||
if ($type == '/api') {
|
||||
$msgData['version'] = Base::getVersion();
|
||||
} elseif ($type == '/help') {
|
||||
$msgData['manager'] = $isManager;
|
||||
}
|
||||
}
|
||||
$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事件来发送任务
|
||||
WebSocketDialogMsg::sendMsg(null, $msg->dialog_id, 'template', $msgData, $botUser->userid, false, false, true); // todo 未能在任务end事件来发送任务
|
||||
}
|
||||
}
|
||||
|
||||
@@ -369,7 +408,7 @@ class BotReceiveMsgTask extends AbstractTask
|
||||
$serverUrl = 'http://' . env('APP_IPPR') . '.3';
|
||||
$userBot = null;
|
||||
$extras = [];
|
||||
$error = null;
|
||||
$errorContent = null;
|
||||
switch ($botUser->email) {
|
||||
// ChatGPT 机器人
|
||||
case 'ai-openai@bot.system':
|
||||
@@ -383,10 +422,10 @@ class BotReceiveMsgTask extends AbstractTask
|
||||
'chunk_size' => 7,
|
||||
];
|
||||
if (empty($extras['openai_key'])) {
|
||||
$error = 'Robot disabled.';
|
||||
$errorContent = '机器人未启用。';
|
||||
} elseif (in_array($this->client['platform'], ['win', 'mac', 'web'])
|
||||
&& !Base::judgeClientVersion("0.29.11", $this->client['version'])) {
|
||||
$error = 'The client version is low (required version ≥ v0.29.11).';
|
||||
$errorContent = '当前客户端版本低(所需版本≥v0.29.11)。';
|
||||
}
|
||||
break;
|
||||
// Claude 机器人
|
||||
@@ -399,10 +438,10 @@ class BotReceiveMsgTask extends AbstractTask
|
||||
'server_url' => $serverUrl,
|
||||
];
|
||||
if (empty($extras['claude_token'])) {
|
||||
$error = 'Robot disabled.';
|
||||
$errorContent = '机器人未启用。';
|
||||
} elseif (in_array($this->client['platform'], ['win', 'mac', 'web'])
|
||||
&& !Base::judgeClientVersion("0.29.11", $this->client['version'])) {
|
||||
$error = 'The client version is low (required version ≥ v0.29.11).';
|
||||
$errorContent = '当前客户端版本低(所需版本≥v0.29.11)。';
|
||||
}
|
||||
break;
|
||||
// Wenxin 机器人
|
||||
@@ -416,10 +455,10 @@ class BotReceiveMsgTask extends AbstractTask
|
||||
'server_url' => $serverUrl,
|
||||
];
|
||||
if (empty($extras['wenxin_key'])) {
|
||||
$error = 'Robot disabled.';
|
||||
$errorContent = '机器人未启用。';
|
||||
} elseif (in_array($this->client['platform'], ['win', 'mac', 'web'])
|
||||
&& !Base::judgeClientVersion("0.29.11", $this->client['version'])) {
|
||||
$error = 'The client version is low (required version ≥ v0.29.12).';
|
||||
$errorContent = '当前客户端版本低(所需版本≥v0.29.12)。';
|
||||
}
|
||||
break;
|
||||
// QianWen 机器人
|
||||
@@ -432,10 +471,10 @@ class BotReceiveMsgTask extends AbstractTask
|
||||
'server_url' => $serverUrl,
|
||||
];
|
||||
if (empty($extras['qianwen_key'])) {
|
||||
$error = 'Robot disabled.';
|
||||
$errorContent = '机器人未启用。';
|
||||
} elseif (in_array($this->client['platform'], ['win', 'mac', 'web'])
|
||||
&& !Base::judgeClientVersion("0.29.11", $this->client['version'])) {
|
||||
$error = 'The client version is low (required version ≥ v0.29.12).';
|
||||
$errorContent = '当前客户端版本低(所需版本≥v0.29.12)。';
|
||||
}
|
||||
break;
|
||||
// Gemini 机器人
|
||||
@@ -450,10 +489,10 @@ class BotReceiveMsgTask extends AbstractTask
|
||||
'server_url' => $serverUrl,
|
||||
];
|
||||
if (empty($extras['gemini_key'])) {
|
||||
$error = 'Robot disabled.';
|
||||
$errorContent = '机器人未启用。';
|
||||
} elseif (in_array($this->client['platform'], ['win', 'mac', 'web'])
|
||||
&& !Base::judgeClientVersion("0.29.11", $this->client['version'])) {
|
||||
$error = 'The client version is low (required version ≥ v0.29.12).';
|
||||
$errorContent = '当前客户端版本低(所需版本≥v0.29.12)。';
|
||||
}
|
||||
break;
|
||||
// 智谱清言 机器人
|
||||
@@ -466,23 +505,26 @@ class BotReceiveMsgTask extends AbstractTask
|
||||
'server_url' => $serverUrl,
|
||||
];
|
||||
if (empty($extras['zhipu_key'])) {
|
||||
$error = 'Robot disabled.';
|
||||
$errorContent = '机器人未启用。';
|
||||
} elseif (in_array($this->client['platform'], ['win', 'mac', 'web'])
|
||||
&& !Base::judgeClientVersion("0.29.11", $this->client['version'])) {
|
||||
$error = 'The client version is low (required version ≥ v0.29.12).';
|
||||
$errorContent = '当前客户端版本低(所需版本≥v0.29.12)。';
|
||||
}
|
||||
break;
|
||||
break;
|
||||
// 其他机器人
|
||||
default:
|
||||
$userBot = UserBot::whereBotId($botUser->userid)->first();
|
||||
$webhookUrl = $userBot?->webhook_url;
|
||||
break;
|
||||
}
|
||||
if ($error) {
|
||||
WebSocketDialogMsg::sendMsg(null, $msg->dialog_id, 'text', ['text' => $error], $botUser->userid, false, false, true); // todo 未能在任务end事件来发送任务
|
||||
if ($errorContent) {
|
||||
WebSocketDialogMsg::sendMsg(null, $msg->dialog_id, 'template', [
|
||||
'type' => 'content',
|
||||
'content' => $errorContent,
|
||||
], $botUser->userid, false, false, true); // todo 未能在任务end事件来发送任务
|
||||
return;
|
||||
}
|
||||
if (!preg_match("/^https*:\/\//", $webhookUrl)) {
|
||||
if (!preg_match("/^https?:\/\//", $webhookUrl)) {
|
||||
return;
|
||||
}
|
||||
//
|
||||
@@ -504,13 +546,19 @@ class BotReceiveMsgTask extends AbstractTask
|
||||
$userBot->webhook_num++;
|
||||
$userBot->save();
|
||||
}
|
||||
if($res['data'] && $data = json_decode($res['data'])){
|
||||
if($data['code'] != 200 && $data['message']){
|
||||
if ($res['data'] && $data = Base::json2array($res['data'])) {
|
||||
if ($data['code'] != 200 && $data['message']) {
|
||||
WebSocketDialogMsg::sendMsg(null, $msg->dialog_id, 'text', ['text' => $res['data']['message']], $botUser->userid, false, false, true);
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
//throw $th;
|
||||
info(Base::array2json([
|
||||
'bot_userid' => $botUser->userid,
|
||||
'dialog' => $dialog->id,
|
||||
'msg' => $msg->id,
|
||||
'webhook_url' => $webhookUrl,
|
||||
'error' => $th->getMessage(),
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,9 @@ use App\Models\UserCheckinRecord;
|
||||
use App\Models\WebSocketDialog;
|
||||
use App\Models\WebSocketDialogMsg;
|
||||
use App\Module\Base;
|
||||
use App\Module\Doo;
|
||||
use App\Module\Extranet;
|
||||
use App\Module\Timer;
|
||||
use Cache;
|
||||
use Carbon\Carbon;
|
||||
|
||||
@@ -40,14 +42,14 @@ class CheckinRemindTask extends AbstractTask
|
||||
//
|
||||
if ($remindin > 0) {
|
||||
$timeRemindin = $timeStart - $remindin;
|
||||
if ($timeRemindin <= Base::time() && Base::time() <= $timeStart) {
|
||||
if ($timeRemindin <= Timer::time() && Timer::time() <= $timeStart) {
|
||||
// 签到打卡提醒
|
||||
$this->remind('in');
|
||||
}
|
||||
}
|
||||
if ($remindexceed > 0) {
|
||||
$timeRemindexceed = $timeStart + $remindexceed;
|
||||
if ($timeRemindexceed <= Base::time() && Base::time() <= $timeRemindexceed + 300) {
|
||||
if ($timeRemindexceed <= Timer::time() && Timer::time() <= $timeRemindexceed + 300) {
|
||||
// 签到缺卡提醒
|
||||
$this->remind('exceed');
|
||||
}
|
||||
@@ -83,11 +85,28 @@ class CheckinRemindTask extends AbstractTask
|
||||
$dialog = WebSocketDialog::checkUserDialog($botUser, $user->userid);
|
||||
if ($dialog) {
|
||||
if ($type === 'exceed') {
|
||||
$text = "<p><strong style='color:red'>缺卡提醒:</strong>上班时间到了,你还没有打卡哦~</p>";
|
||||
$title = '缺卡提醒';
|
||||
$style = 'color:#f55;';
|
||||
$content = '上班时间到了,你还没有打卡哦~';
|
||||
} else {
|
||||
$text = "<p><strong>打卡提醒:</strong>快到上班时间了,别忘了打卡哦~</p>";
|
||||
$title = '打卡提醒';
|
||||
$style = '';
|
||||
$content = '快到上班时间了,别忘了打卡哦~';
|
||||
}
|
||||
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'text', ['text' => $text], $botUser->userid);
|
||||
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'template', [
|
||||
'type' => 'content',
|
||||
'title' => $title,
|
||||
'content' => [
|
||||
[
|
||||
'content' => $title,
|
||||
'style' => $style . 'font-weight:bold',
|
||||
],
|
||||
[
|
||||
'content' => $content,
|
||||
'style' => 'padding-top:4px;opacity:0.6',
|
||||
],
|
||||
],
|
||||
], $botUser->userid);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -6,8 +6,8 @@ use App\Models\File;
|
||||
use App\Models\TaskWorker;
|
||||
use App\Models\Tmp;
|
||||
use App\Models\WebSocketTmpMsg;
|
||||
use App\Module\Base;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\File as SupportFile;
|
||||
|
||||
/**
|
||||
* 删除过期临时数据任务
|
||||
@@ -99,22 +99,19 @@ class DeleteTmpTask extends AbstractTask
|
||||
break;
|
||||
|
||||
/**
|
||||
* file_pack 临时压缩下载文件
|
||||
* tmp_file 删除临时文件
|
||||
*/
|
||||
case 'file_pack':
|
||||
case 'tmp_file':
|
||||
{
|
||||
$path = public_path('tmp/file/');
|
||||
if (!SupportFile::exists($path)) {
|
||||
$day = intval(env("AUTO_EMPTY_TEMP_FILE", 30));
|
||||
if ($day <= 0) {
|
||||
return;
|
||||
}
|
||||
$dirIterator = new \RecursiveDirectoryIterator($path);
|
||||
$iterator = new \RecursiveIteratorIterator($dirIterator);
|
||||
foreach ($iterator as $file) {
|
||||
if ($file->isFile()) {
|
||||
$time = $file->getMTime();
|
||||
if ($time < time() - 3600 * 24) {
|
||||
unlink($file->getPathname());
|
||||
}
|
||||
$files = Base::recursiveFiles(public_path('uploads/tmp'));
|
||||
foreach ($files as $file) {
|
||||
$time = @filemtime($file);
|
||||
if ($time && $time < time() - 3600 * 24 * $day) {
|
||||
unlink($file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,164 +7,278 @@ use App\Models\User;
|
||||
use App\Models\WebSocketDialogMsg;
|
||||
use App\Models\WebSocketDialogMsgRead;
|
||||
use App\Module\Base;
|
||||
use App\Module\Doo;
|
||||
use App\Module\Timer;
|
||||
use Carbon\Carbon;
|
||||
use Guanguans\Notify\Factory;
|
||||
use Guanguans\Notify\Messages\EmailMessage;
|
||||
|
||||
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
|
||||
|
||||
/**
|
||||
* 未读消息邮件通知任务
|
||||
* 根据设置的时间范围,将未读消息通过邮件发送给用户
|
||||
*/
|
||||
class EmailNoticeTask extends AbstractTask
|
||||
{
|
||||
/** @var array 允许发送通知的消息类型 */
|
||||
private const ALLOWED_MSG_TYPES = ["text", "file", "record", "meeting"];
|
||||
|
||||
/** @var int 每批处理的数据量 */
|
||||
private const CHUNK_SIZE = 100;
|
||||
|
||||
/** @var array 邮件相关设置 */
|
||||
private array $emailSetting;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->emailSetting = Base::setting('emailSetting');
|
||||
}
|
||||
|
||||
public function start()
|
||||
{
|
||||
$setting = Base::setting('emailSetting');
|
||||
// 消息通知
|
||||
if ($setting['notice_msg'] === 'open') {
|
||||
$userMinute = intval($setting['msg_unread_user_minute']);
|
||||
$groupMinute = intval($setting['msg_unread_group_minute']);
|
||||
\DB::statement("SET SQL_MODE=''");
|
||||
$builder = WebSocketDialogMsg::select(['web_socket_dialog_msgs.*', 'r.id as r_id', 'r.userid as r_userid'])
|
||||
->join('web_socket_dialog_msg_reads as r', 'web_socket_dialog_msgs.id', '=', 'r.msg_id')
|
||||
->whereNull("r.read_at")
|
||||
->where("r.silence", 0)
|
||||
->where("r.email", 0);
|
||||
if ($userMinute > -1) {
|
||||
$builder->clone()
|
||||
->where("web_socket_dialog_msgs.dialog_type", "user")
|
||||
->whereIn("web_socket_dialog_msgs.type", ["text", "file", "record", "meeting"])
|
||||
->whereBetween("web_socket_dialog_msgs.created_at", [
|
||||
Carbon::now()->subMinutes($userMinute + 10),
|
||||
Carbon::now()->subMinutes($userMinute)
|
||||
])
|
||||
->groupBy('r_userid')
|
||||
->chunkById(100, function ($rows) {
|
||||
$this->unreadMsgEmail($rows, "user");
|
||||
});
|
||||
}
|
||||
if ($groupMinute > -1) {
|
||||
$builder->clone()
|
||||
->where("web_socket_dialog_msgs.dialog_type", "group")
|
||||
->whereIn("web_socket_dialog_msgs.type", ["text", "file", "record", "meeting"])
|
||||
->whereBetween("web_socket_dialog_msgs.created_at", [
|
||||
Carbon::now()->subMinutes($groupMinute + 10),
|
||||
Carbon::now()->subMinutes($groupMinute)
|
||||
])
|
||||
->groupBy('r_userid')
|
||||
->chunkById(100, function ($rows) {
|
||||
$this->unreadMsgEmail($rows, "group");
|
||||
});
|
||||
}
|
||||
// 检查是否可以发送邮件
|
||||
if (!$this->canSendEmails()) {
|
||||
return;
|
||||
}
|
||||
|
||||
\DB::statement("SET SQL_MODE=''");
|
||||
|
||||
// 分别处理用户消息和群组消息
|
||||
$this->processMessages('user');
|
||||
$this->processMessages('group');
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否可以发送邮件通知
|
||||
* 需要开启通知功能且在指定的时间范围内
|
||||
*/
|
||||
private function canSendEmails(): bool
|
||||
{
|
||||
if ($this->emailSetting['notice_msg'] !== 'open') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$timeRanges = is_array($this->emailSetting['msg_unread_time_ranges'])
|
||||
? $this->emailSetting['msg_unread_time_ranges']
|
||||
: [];
|
||||
|
||||
return Timer::isTimeInRanges($timeRanges);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理指定类型的未读消息
|
||||
* @param string $dialogType 对话类型:user|group
|
||||
*/
|
||||
private function processMessages(string $dialogType): void
|
||||
{
|
||||
// 获取未读时间限制(分钟)
|
||||
$minute = $dialogType === 'user'
|
||||
? intval($this->emailSetting['msg_unread_user_minute'])
|
||||
: intval($this->emailSetting['msg_unread_group_minute']);
|
||||
|
||||
if ($minute <= -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取上次处理时间
|
||||
$lastProcessKey = 'time' . ucfirst($dialogType);
|
||||
$startTime = Base::settingFind('emailLastNotice', $lastProcessKey);
|
||||
$startTime = $startTime ? Carbon::parse($startTime) : Carbon::today();
|
||||
|
||||
// 计算本次处理的结束时间(当前时间减去未读时间限制)
|
||||
$endTime = Carbon::now()->subMinutes($minute);
|
||||
|
||||
// 如果开始时间晚于结束时间,则不处理
|
||||
if ($startTime->isAfter($endTime)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取需要处理的用户列表
|
||||
$query = WebSocketDialogMsgRead::select('web_socket_dialog_msg_reads.userid')
|
||||
->join('web_socket_dialog_msgs as m', 'm.id', '=', 'web_socket_dialog_msg_reads.msg_id')
|
||||
->whereNull('web_socket_dialog_msg_reads.read_at')
|
||||
->where('web_socket_dialog_msg_reads.silence', 0)
|
||||
->where('web_socket_dialog_msg_reads.email', 0)
|
||||
->where('m.dialog_type', $dialogType)
|
||||
->whereBetween('m.created_at', [$startTime, $endTime])
|
||||
->whereIn('m.type', self::ALLOWED_MSG_TYPES)
|
||||
->orderBy('web_socket_dialog_msg_reads.userid')
|
||||
->groupBy('web_socket_dialog_msg_reads.userid');
|
||||
|
||||
// 分批处理用户的未读消息
|
||||
$query->chunk(self::CHUNK_SIZE, function($users) use ($dialogType, $startTime, $endTime) {
|
||||
foreach ($users as $userData) {
|
||||
$this->sendUserEmail($userData->userid, $dialogType, $startTime, $endTime);
|
||||
}
|
||||
});
|
||||
|
||||
// 更新处理时间
|
||||
Base::setting('emailLastNotice', [
|
||||
$lastProcessKey => $endTime->toDateTimeString()
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送用户的未读消息邮件
|
||||
*/
|
||||
private function sendUserEmail(int $userId, string $dialogType, Carbon $startTime, Carbon $endTime): void
|
||||
{
|
||||
// 验证用户
|
||||
$user = User::whereDisableAt(null)->find($userId);
|
||||
if (!$user || $user->bot || !is_null($user->disable_at) || !Base::isEmail($user->email)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取未读消息
|
||||
$messages = $this->getUnreadMessages($userId, $dialogType, $startTime, $endTime);
|
||||
if ($messages->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置用户语言
|
||||
Doo::setLanguage($user->lang);
|
||||
|
||||
// 按对话分组并生成邮件内容
|
||||
$messagesByDialog = $messages->groupBy('dialog_id');
|
||||
$emailContent = $this->generateEmailContent($user, $messagesByDialog, $dialogType);
|
||||
|
||||
try {
|
||||
// 发送邮件
|
||||
$this->sendEmail($user, $emailContent);
|
||||
// 标记消息已发送邮件
|
||||
WebSocketDialogMsgRead::whereIn('id', $messages->pluck('r_id'))
|
||||
->update(['email' => 1]);
|
||||
} catch (\Throwable $e) {
|
||||
info("Email send failed for user {$userId}: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户的未读消息
|
||||
*/
|
||||
private function getUnreadMessages($userId, $dialogType, Carbon $startTime, Carbon $endTime)
|
||||
{
|
||||
return 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')
|
||||
->where([
|
||||
'r.userid' => $userId,
|
||||
'r.silence' => 0,
|
||||
'r.email' => 0,
|
||||
'web_socket_dialog_msgs.dialog_type' => $dialogType
|
||||
])
|
||||
->whereNull('r.read_at')
|
||||
->whereBetween('web_socket_dialog_msgs.created_at', [$startTime, $endTime])
|
||||
->whereIn('web_socket_dialog_msgs.type', self::ALLOWED_MSG_TYPES)
|
||||
->orderBy('web_socket_dialog_msgs.created_at')
|
||||
->limit(self::CHUNK_SIZE)
|
||||
->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成邮件内容
|
||||
*/
|
||||
private function generateEmailContent($user, $messagesByDialog, $dialogType)
|
||||
{
|
||||
$msgType = $dialogType === "group" ? "群聊" : "单聊";
|
||||
|
||||
// 生成邮件头部
|
||||
$content = view('email.unread', [
|
||||
'type' => 'head',
|
||||
'title' => Doo::translate(sprintf('%s,您好。', $user->nickname)),
|
||||
'desc' => Doo::translate(sprintf('您有%d条未读%s消息,请及时处理。', count($messagesByDialog), $msgType)),
|
||||
])->render();
|
||||
|
||||
$subject = null;
|
||||
// 处理每个对话的消息
|
||||
foreach ($messagesByDialog as $items) {
|
||||
$dialogId = 0;
|
||||
$dialogName = null;
|
||||
|
||||
foreach ($items as $item) {
|
||||
$item->cancelAppend();
|
||||
$item->userInfo = User::userid2basic($item->userid, ['lang']);
|
||||
Doo::setLanguage($item->userInfo->lang);
|
||||
$item->preview = WebSocketDialogMsg::previewMsg($item, true);
|
||||
$item->preview = str_replace('<p>', '<p style="margin:0;padding:0">', $item->preview);
|
||||
|
||||
if (empty($dialogId)) {
|
||||
$dialogId = $item->dialog_id;
|
||||
}
|
||||
if ($dialogName === null) {
|
||||
$dialogName = $this->getDialogName($item, $dialogType);
|
||||
}
|
||||
}
|
||||
|
||||
// 生成邮件主题
|
||||
if ($subject === null) {
|
||||
$subject = count($messagesByDialog) > 1
|
||||
? sprintf('来自%d个%s未读消息提醒', count($messagesByDialog), $msgType)
|
||||
: sprintf('来自%s未读消息提醒', $dialogName);
|
||||
}
|
||||
|
||||
// 添加对话内容
|
||||
$content .= view('email.unread', [
|
||||
'type' => 'content',
|
||||
'dialogUrl' => '', // 不显示回复消息按钮
|
||||
// 'dialogUrl' => config("app.url") . "/manage/messenger?dialog_id={$dialogId}",
|
||||
'dialogName' => trim($dialogName),
|
||||
'title' => Doo::translate(sprintf('%d条未读信息', count($items))),
|
||||
'button' => Doo::translate('回复消息'),
|
||||
'unread' => count($items),
|
||||
'items' => $items,
|
||||
])->render();
|
||||
}
|
||||
|
||||
$content = str_replace("{{RemoteURL}}", config("app.url") . "/", $content);
|
||||
|
||||
return [
|
||||
'subject' => Doo::translate($subject),
|
||||
'content' => $content
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取对话名称
|
||||
*/
|
||||
private function getDialogName($message, $dialogType)
|
||||
{
|
||||
if ($dialogType === "user" && $message->userInfo) {
|
||||
return $message->userInfo->profession
|
||||
? sprintf('%s (%s) ', $message->userInfo->nickname, $message->userInfo->profession)
|
||||
: $message->userInfo->nickname;
|
||||
}
|
||||
return $message->webSocketDialog?->getGroupName();
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送邮件
|
||||
*/
|
||||
private function sendEmail($user, $emailData): void
|
||||
{
|
||||
Setting::validateAddr($user->email, function($to) use ($emailData) {
|
||||
Factory::mailer()
|
||||
->setDsn(sprintf(
|
||||
'smtp://%s:%s@%s:%s?verify_peer=0',
|
||||
$this->emailSetting['account'],
|
||||
$this->emailSetting['password'],
|
||||
$this->emailSetting['smtp_server'],
|
||||
$this->emailSetting['port']
|
||||
))
|
||||
->setMessage(EmailMessage::create()
|
||||
->from(sprintf('%s <%s>', Base::settingFind('system', 'system_alias', 'Task'), $this->emailSetting['account']))
|
||||
->to($to)
|
||||
->subject($emailData['subject'])
|
||||
->html($emailData['content']))
|
||||
->send();
|
||||
});
|
||||
}
|
||||
|
||||
public function end()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 未读消息通知
|
||||
* @param $rows
|
||||
* @param $dialogType
|
||||
* @return void
|
||||
*/
|
||||
private function unreadMsgEmail($rows, $dialogType)
|
||||
{
|
||||
$array = $rows->groupBy('r_userid');
|
||||
foreach ($array as $userid => $data) {
|
||||
$data = WebSocketDialogMsg::select(['web_socket_dialog_msgs.*', 'r.id as r_id', 'r.userid as r_userid'])
|
||||
->join('web_socket_dialog_msg_reads as r', 'web_socket_dialog_msgs.id', '=', 'r.msg_id')
|
||||
->whereNull("r.read_at")
|
||||
->where("r.silence", 0)
|
||||
->where("r.email", 0)
|
||||
->where("r.userid", $userid)
|
||||
->where("web_socket_dialog_msgs.dialog_type", $dialogType)
|
||||
->whereIn("web_socket_dialog_msgs.type", ["text", "file", "record", "meeting"])
|
||||
->take(100)
|
||||
->get();
|
||||
if (empty($data)) {
|
||||
continue;
|
||||
}
|
||||
$user = User::whereBot(0)->whereNull('disable_at')->find($userid);
|
||||
if (empty($user)) {
|
||||
continue;
|
||||
}
|
||||
if (!Base::isEmail($user->email)) {
|
||||
continue;
|
||||
}
|
||||
$setting = Base::setting('emailSetting');
|
||||
$msgType = $dialogType === "group" ? "群聊" : "成员";
|
||||
$subject = null;
|
||||
$content = view('email.unread', [
|
||||
'type' => 'head',
|
||||
'nickname' => $user->nickname,
|
||||
'msgType' => $msgType,
|
||||
'count' => count($data),
|
||||
])->render();
|
||||
$lists = $data->groupBy('dialog_id');
|
||||
/** @var WebSocketDialogMsg[] $items */
|
||||
foreach ($lists as $items) {
|
||||
$dialogId = 0;
|
||||
$dialogName = null;
|
||||
foreach ($items as $item) {
|
||||
$item->cancelAppend();
|
||||
$item->userInfo = User::userid2basic($item->userid);
|
||||
$item->preview = $item->previewMsg(true);
|
||||
$item->preview = str_replace('<p>', '<p style="margin:0;padding:0">', $item->preview);
|
||||
if (empty($dialogId)) {
|
||||
$dialogId = $item->dialog_id;
|
||||
}
|
||||
if ($dialogName === null) {
|
||||
if ($dialogType === "user" && $item->userInfo) {
|
||||
if ($item->userInfo->profession) {
|
||||
$dialogName = $item->userInfo->nickname . " ({$item->userInfo->profession})";
|
||||
} else {
|
||||
$dialogName = $item->userInfo->nickname;
|
||||
}
|
||||
} else {
|
||||
$dialogName = $item->webSocketDialog?->getGroupName();
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($subject === null) {
|
||||
$count = count($lists);
|
||||
if ($count > 1) {
|
||||
$subject = "来自{$count}个{$msgType}未读消息提醒";
|
||||
} else {
|
||||
$subject = "来自{$dialogName}未读消息提醒";
|
||||
}
|
||||
}
|
||||
$content .= view('email.unread', [
|
||||
'type' => 'content',
|
||||
'dialogUrl' => config("app.url") . "/manage/messenger?dialog_id={$dialogId}",
|
||||
'dialogName' => $dialogName,
|
||||
'unread' => count($items),
|
||||
'items' => $items,
|
||||
])->render();
|
||||
$content = str_replace("{{RemoteURL}}", config("app.url") . "/", $content);
|
||||
}
|
||||
try {
|
||||
Setting::validateAddr($user->email, function($to) use ($content, $subject, $setting) {
|
||||
Factory::mailer()
|
||||
->setDsn("smtp://{$setting['account']}:{$setting['password']}@{$setting['smtp_server']}:{$setting['port']}?verify_peer=0")
|
||||
->setMessage(EmailMessage::create()
|
||||
->from(env('APP_NAME', 'Task') . " <{$setting['account']}>")
|
||||
->to($to)
|
||||
->subject($subject)
|
||||
->html($content))
|
||||
->send();
|
||||
});
|
||||
} catch (\Throwable $e) {
|
||||
info("unreadMsgEmail: " . $e->getMessage());
|
||||
}
|
||||
WebSocketDialogMsgRead::whereIn('id', $data->pluck('r_id'))->update([
|
||||
'email' => 1
|
||||
]);
|
||||
}
|
||||
// 任务结束处理
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
<?php
|
||||
namespace App\Tasks;
|
||||
|
||||
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
|
||||
|
||||
use App\Module\Base;
|
||||
use App\Module\Ihttp;
|
||||
|
||||
/**
|
||||
* Ihttp任务
|
||||
* Class IhttpTask
|
||||
* @package App\Tasks
|
||||
*/
|
||||
class IhttpTask extends AbstractTask
|
||||
{
|
||||
protected $url;
|
||||
protected $post;
|
||||
protected $extra;
|
||||
protected $apiWebsocket;
|
||||
protected $apiUserid;
|
||||
protected $endPush = [];
|
||||
|
||||
/**
|
||||
* IhttpTask constructor.
|
||||
* @param $url
|
||||
* @param array $post
|
||||
* @param array $extra
|
||||
*/
|
||||
public function __construct($url, $post = [], $extra = [])
|
||||
{
|
||||
parent::__construct(...func_get_args());
|
||||
$this->url = $url;
|
||||
$this->post = $post;
|
||||
$this->extra = $extra;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $apiWebsocket
|
||||
*/
|
||||
public function setApiWebsocket($apiWebsocket): void
|
||||
{
|
||||
$this->apiWebsocket = $apiWebsocket;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $apiUserid
|
||||
*/
|
||||
public function setApiUserid($apiUserid): void
|
||||
{
|
||||
$this->apiUserid = $apiUserid;
|
||||
}
|
||||
|
||||
public function start()
|
||||
{
|
||||
$res = Ihttp::ihttp_request($this->url, $this->post, $this->extra);
|
||||
if ($this->apiWebsocket && $this->apiUserid) {
|
||||
$data = Base::isSuccess($res) ? Base::json2array($res['data']) : $res;
|
||||
$this->endPush[] = [
|
||||
'userid' => $this->apiUserid,
|
||||
'msg' => [
|
||||
'type' => 'apiWebsocket',
|
||||
'apiWebsocket' => $this->apiWebsocket,
|
||||
'apiSuccess' => Base::isSuccess($res),
|
||||
'data' => $data,
|
||||
]
|
||||
];
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public function end()
|
||||
{
|
||||
PushTask::push($this->endPush);
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ use App\Models\User;
|
||||
use App\Module\Base;
|
||||
use App\Models\Project;
|
||||
use App\Models\ProjectTask;
|
||||
use App\Models\ProjectUser;
|
||||
use Carbon\Carbon;
|
||||
use App\Models\WebSocketDialogMsg;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
@@ -47,7 +46,7 @@ class UnclaimedTaskRemindTask extends AbstractTask
|
||||
Project::whereNull('deleted_at')->whereNull('archived_at')->chunk(100, function ($projects) {
|
||||
foreach ($projects as $project) {
|
||||
//
|
||||
$projectTasks = ProjectTask::select('project_tasks.id', 'project_tasks.name')
|
||||
$projectTasks = ProjectTask::select(['project_tasks.id', 'project_tasks.name'])
|
||||
->leftJoin('project_task_users', function ($query) {
|
||||
$query->on('project_tasks.id', '=', 'project_task_users.task_id');
|
||||
})
|
||||
@@ -63,17 +62,15 @@ class UnclaimedTaskRemindTask extends AbstractTask
|
||||
if (empty($botUser)) {
|
||||
return;
|
||||
}
|
||||
if (!ProjectUser::whereUserid($botUser->userid)->whereProjectId($project->id)->exists()) {
|
||||
$project->joinProject($botUser->userid);
|
||||
$project->syncDialogUser();
|
||||
}
|
||||
//
|
||||
$taskHtml = '<span style="line-height: 26px;">任务待领取</span> <br/>';
|
||||
foreach ($projectTasks as $projectTask) {
|
||||
$taskHtml .= "<span class=\"mention task\" style=\"line-height: 26px;\" data-id=\"{$projectTask->id}\">#{$projectTask->name}</span> <br/>";
|
||||
}
|
||||
WebSocketDialogMsg::sendMsg(null, $project->dialog_id, 'text', [
|
||||
'text' => $taskHtml
|
||||
WebSocketDialogMsg::sendMsg(null, $project->dialog_id, 'template', [
|
||||
'type' => 'task_list',
|
||||
'title' => '任务待领取',
|
||||
'list' => $projectTasks->map(function ($item) {
|
||||
return [
|
||||
'id' => $item->id,
|
||||
'name' => $item->name,
|
||||
];
|
||||
}),
|
||||
], $botUser->userid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ use App\Models\WebSocketDialog;
|
||||
use App\Models\WebSocketDialogMsg;
|
||||
use App\Models\WebSocketDialogMsgRead;
|
||||
use App\Module\Base;
|
||||
use App\Module\Doo;
|
||||
use App\Services\RequestContext;
|
||||
use Carbon\Carbon;
|
||||
use Hhxsv5\LaravelS\Swoole\Task\Task;
|
||||
use Request;
|
||||
@@ -77,11 +79,7 @@ class WebSocketDialogMsgTask extends AbstractTask
|
||||
|
||||
public function start()
|
||||
{
|
||||
global $_A;
|
||||
$_A = [
|
||||
'__fill_url_remote_url' => true,
|
||||
];
|
||||
|
||||
RequestContext::set('fill_url_remote_url', true);
|
||||
//
|
||||
$msg = WebSocketDialogMsg::find($this->id);
|
||||
if (empty($msg)) {
|
||||
@@ -191,13 +189,25 @@ class WebSocketDialogMsgTask extends AbstractTask
|
||||
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,
|
||||
]);
|
||||
$langs = User::select(['userid', 'lang'])
|
||||
->whereIn('userid', $umengUserid)
|
||||
->whereDisableAt(null)
|
||||
->get()
|
||||
->groupBy('lang')
|
||||
->map(function($group) {
|
||||
return $group->pluck('userid');
|
||||
});
|
||||
foreach ($langs as $lang => $uids) {
|
||||
Doo::setLanguage($lang);
|
||||
$umengMsg = [
|
||||
'title' => $umengTitle,
|
||||
'body' => WebSocketDialogMsg::previewMsg($msg),
|
||||
'description' => "MID:{$msg->id}",
|
||||
'seconds' => 3600,
|
||||
'badge' => 1,
|
||||
];
|
||||
$this->endArray[] = new PushUmengMsg($uids->toArray(), $umengMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
2
bin/version.js
vendored
2
bin/version.js
vendored
File diff suppressed because one or more lines are too long
96
cmd
96
cmd
@@ -168,14 +168,12 @@ run_electron() {
|
||||
rm -rf "./electron/public"
|
||||
fi
|
||||
#
|
||||
BUILD_FRONTEND="build"
|
||||
if [ "$argv" == "dev" ]; then
|
||||
switch_debug "$argv"
|
||||
else
|
||||
mkdir -p ./electron/public
|
||||
cp ./electron/index.html ./electron/public/index.html
|
||||
npx vite build -- fromcmd electronBuild
|
||||
BUILD_FRONTEND="dev"
|
||||
fi
|
||||
node ./electron/build.js $argv
|
||||
env BUILD_FRONTEND=$BUILD_FRONTEND node ./electron/build.js $argv
|
||||
}
|
||||
|
||||
run_exec() {
|
||||
@@ -227,6 +225,63 @@ run_mysql() {
|
||||
run_exec mariadb "gunzip < /$inputname | mysql -u$username -p$password $database"
|
||||
run_exec php "php artisan migrate"
|
||||
judge "还原数据库"
|
||||
elif [ "$1" = "open" ]; then
|
||||
container_name=`docker_name mariadb`
|
||||
if [ -z "$container_name" ]; then
|
||||
error "没有找到 mariadb 容器!"
|
||||
exit 1
|
||||
fi
|
||||
mkdir -p ${cur_path}/docker/mysql/tmp
|
||||
cat > ${cur_path}/docker/mysql/tmp/${container_name}.conf <<EOF
|
||||
user nginx;
|
||||
worker_processes auto;
|
||||
error_log /var/log/nginx/error.log notice;
|
||||
pid /var/run/nginx.pid;
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
stream {
|
||||
upstream mysql {
|
||||
server ${container_name}:3306 max_fails=1 fail_timeout=30s;
|
||||
}
|
||||
server {
|
||||
listen 3306;
|
||||
proxy_pass mysql;
|
||||
proxy_connect_timeout 5s;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
default_value="$(env_get DB_PORT_OPEN)"
|
||||
if [ -n "$default_value" ]; then
|
||||
read_tip="请输入代理端口 (3300-65500, 默认: ${default_value}): "
|
||||
else
|
||||
read_tip="请输入代理端口 (3300-65500): "
|
||||
fi
|
||||
read -rp "$read_tip" inputport
|
||||
inputport=${inputport:-$default_value}
|
||||
if [ $inputport -lt 3300 ] || [ $inputport -gt 65500 ]; then
|
||||
error "端口范围不正确!"
|
||||
exit 1
|
||||
fi
|
||||
env_set DB_PORT_OPEN $inputport
|
||||
run_mysql rm-port
|
||||
docker run --name ${container_name}-port \
|
||||
--network dootask-networks-$(env_get APP_ID) \
|
||||
-p ${inputport}:3306 \
|
||||
-v ${cur_path}/docker/mysql/tmp/${container_name}.conf:/etc/nginx/nginx.conf \
|
||||
-d nginx:alpine > /dev/null
|
||||
judge "开启代理"
|
||||
elif [ "$1" = "close" ]; then
|
||||
container_name=`docker_name mariadb`
|
||||
if [ -z "$container_name" ]; then
|
||||
error "没有找到 mariadb 容器!"
|
||||
exit 1
|
||||
fi
|
||||
docker stop ${container_name}-port > /dev/null
|
||||
docker rm ${container_name}-port > /dev/null
|
||||
judge "关闭代理"
|
||||
elif [ "$1" = "rm-port" ]; then
|
||||
docker rm -f $(docker_name mariadb)-port &> /dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -379,13 +434,18 @@ if [ $# -gt 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
chmod -R 775 "${cur_path}/docker/mysql/data"
|
||||
sleep 3
|
||||
done
|
||||
run_exec php "php artisan migrate --seed"
|
||||
if [ ! -f "${cur_path}/docker/mysql/data/$(env_get DB_DATABASE)/$(env_get DB_PREFIX)migrations.ibd" ]; then
|
||||
error "数据库安装失败!"
|
||||
exit 1
|
||||
fi
|
||||
# 数据库迁移
|
||||
remaining=20
|
||||
while [ ! -f "${cur_path}/docker/mysql/data/$(env_get DB_DATABASE)/$(env_get DB_PREFIX)migrations.ibd" ]; do
|
||||
((remaining=$remaining-1))
|
||||
if [ $remaining -lt 0 ]; then
|
||||
error "数据库安装失败!"
|
||||
exit 1
|
||||
fi
|
||||
sleep 3
|
||||
run_exec php "php artisan migrate --seed"
|
||||
done
|
||||
# 设置初始化密码
|
||||
res=`run_exec mariadb "sh /etc/mysql/repassword.sh"`
|
||||
$COMPOSE up -d
|
||||
@@ -421,6 +481,7 @@ if [ $# -gt 0 ]; then
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
run_mysql rm-port
|
||||
$COMPOSE down
|
||||
env_set APP_DEBUG "false"
|
||||
rm -rf "./docker/mysql/data"
|
||||
@@ -505,7 +566,11 @@ if [ $# -gt 0 ]; then
|
||||
e="php artisan $@" && run_exec php "$e"
|
||||
elif [[ "$1" == "php" ]]; then
|
||||
shift 1
|
||||
e="php $@" && run_exec php "$e"
|
||||
if [[ "$1" == "restart" ]] || [[ "$1" == "reboot" ]]; then
|
||||
restart_php
|
||||
else
|
||||
e="php $@" && run_exec php "$e"
|
||||
fi
|
||||
elif [[ "$1" == "nginx" ]]; then
|
||||
shift 1
|
||||
e="nginx $@" && run_exec nginx "$e"
|
||||
@@ -518,6 +583,10 @@ if [ $# -gt 0 ]; then
|
||||
run_mysql backup
|
||||
elif [[ "$1" == "recovery" ]] || [[ "$1" == "r" ]]; then
|
||||
run_mysql recovery
|
||||
elif [[ "$1" == "agent" ]] || [[ "$1" == "open" ]]; then
|
||||
run_mysql open
|
||||
elif [[ "$1" == "unagent" ]] || [[ "$1" == "close" ]]; then
|
||||
run_mysql close
|
||||
else
|
||||
e="mysql $@" && run_exec mariadb "$e"
|
||||
fi
|
||||
@@ -545,6 +614,9 @@ if [ $# -gt 0 ]; then
|
||||
$COMPOSE stop "$@"
|
||||
$COMPOSE start "$@"
|
||||
else
|
||||
if [[ "$1" == "down" ]]; then
|
||||
run_mysql rm-port
|
||||
fi
|
||||
$COMPOSE "$@"
|
||||
fi
|
||||
else
|
||||
|
||||
@@ -26,7 +26,8 @@
|
||||
"hedeqiang/umeng": "^2.1",
|
||||
"laravel/framework": "^v8.83.27",
|
||||
"laravel/tinker": "^v2.6.1",
|
||||
"lasserafn/php-initial-avatar-generator": "^4.2",
|
||||
"laravolt/avatar": "^5.1",
|
||||
"league/commonmark": "^2.5",
|
||||
"maatwebsite/excel": "^3.1.31",
|
||||
"madnest/madzipper": "^v1.1.0",
|
||||
"mews/captcha": "^3.2.6",
|
||||
|
||||
1114
composer.lock
generated
1114
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -134,7 +134,6 @@ return [
|
||||
*/
|
||||
|
||||
'event_handlers' => [
|
||||
'ServerStart' => \App\Events\ServerStartEvent::class,
|
||||
'WorkerStart' => \App\Events\WorkerStartEvent::class,
|
||||
],
|
||||
|
||||
@@ -222,7 +221,38 @@ return [
|
||||
|
|
||||
*/
|
||||
|
||||
'swoole_tables' => [],
|
||||
'swoole_tables' => [
|
||||
'initFlag' => [
|
||||
'size' => 1,
|
||||
'column' => [
|
||||
[
|
||||
'name' => 'value',
|
||||
'type' => \Swoole\Table::TYPE_INT,
|
||||
'size' => 8
|
||||
],
|
||||
],
|
||||
],
|
||||
'globalData' => [
|
||||
'size' => 1024,
|
||||
'column' => [
|
||||
[
|
||||
'name' => 'value',
|
||||
'type' => \Swoole\Table::TYPE_STRING,
|
||||
'size' => 1024
|
||||
],
|
||||
],
|
||||
],
|
||||
'onlineData' => [
|
||||
'size' => 10240,
|
||||
'column' => [
|
||||
[
|
||||
'name' => 'value',
|
||||
'type' => \Swoole\Table::TYPE_INT,
|
||||
'size' => 8
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
||||
@@ -26,11 +26,7 @@ class AddWebSocketDialogMsgsKey extends Migration
|
||||
\App\Models\WebSocketDialogMsg::chunkById(100, function ($lists) {
|
||||
/** @var \App\Models\WebSocketDialogMsg $item */
|
||||
foreach ($lists as $item) {
|
||||
$key = $item->generateMsgKey();
|
||||
if ($key) {
|
||||
$item->key = $key;
|
||||
$item->save();
|
||||
}
|
||||
$item->generateKeyAndSave();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ class AddIndexToProjectTasksTable extends Migration
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return voidw
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
|
||||
@@ -23,7 +23,7 @@ class AddIndexToProjectTaskUsersTable extends Migration
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return voidw
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
|
||||
@@ -25,7 +25,7 @@ class AddIndexSome20231217 extends Migration
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return voidw
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
|
||||
@@ -110,7 +110,7 @@ class UpdateOwnerAddIndexSome20231217 extends Migration
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return voidw
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
|
||||
@@ -21,7 +21,7 @@ class AddIndexSome20240315 extends Migration
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return voidw
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
|
||||
@@ -22,7 +22,7 @@ class AddIndexSome20240317 extends Migration
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return voidw
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
use App\Models\ProjectLog;
|
||||
use App\Models\ProjectTaskUser;
|
||||
use App\Models\User;
|
||||
use App\Models\UserTransfer;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
@@ -35,7 +36,9 @@ class ProjectLogsAddTaskOnly extends Migration
|
||||
UserTransfer::chunkById(100, function ($lists) {
|
||||
/** @var UserTransfer $item */
|
||||
foreach ($lists as $item) {
|
||||
ProjectTaskUser::transfer($item->original_userid, $item->new_userid);
|
||||
if (User::whereUserid($item->original_userid)->where("identity", "like", "%,disable,%")->exists()) {
|
||||
ProjectTaskUser::transfer($item->original_userid, $item->new_userid);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
use App\Models\ProjectPermission;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class UpdateProjectPermissions extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
ProjectPermission::orderBy('id')->chunk(100, function ($rows) {
|
||||
/** @var ProjectPermission $row */
|
||||
foreach ($rows as $row) {
|
||||
$permissions = $row->permissions;
|
||||
if (!isset($permissions[ProjectPermission::TASK_TIME])) {
|
||||
$permissions[ProjectPermission::TASK_TIME] = $permissions[ProjectPermission::TASK_UPDATE];
|
||||
$row->permissions = \App\Module\Base::array2json($permissions);
|
||||
$row->save();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class RenamePreReportReceivesReceiveTime extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('report_receives', function (Blueprint $table) {
|
||||
if (Schema::hasColumn('report_receives', 'receive_time')) {
|
||||
$table->renameColumn('receive_time', 'receive_at');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('report_receives', function (Blueprint $table) {
|
||||
//
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateUserCheckinFacesTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
if (Schema::hasTable('user_checkin_faces'))
|
||||
return;
|
||||
|
||||
Schema::create('user_checkin_faces', function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->bigInteger('userid')->nullable()->default(0)->comment('会员id');
|
||||
$table->string('faceimg')->nullable()->default('')->comment('人脸图片');
|
||||
$table->integer('status')->nullable()->default(0)->comment('状态');
|
||||
$table->string('remark',100)->nullable()->default('')->comment('备注');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('user_checkin_faces');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
use App\Models\WebSocketDialog;
|
||||
use App\Models\WebSocketDialogMsg;
|
||||
use App\Models\WebSocketDialogMsgRead;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddWebSocketDialogMsgReadsLive extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
info("update web_socket_dialog_msg_reads live field");
|
||||
$isAdd = false;
|
||||
Schema::table('web_socket_dialog_msg_reads', function (Blueprint $table) use (&$isAdd) {
|
||||
if (!Schema::hasColumn('web_socket_dialog_msg_reads', 'live')) {
|
||||
$isAdd = true;
|
||||
$table->integer('live')->nullable()->default(0)->index()->after('dot')->comment('是否在会话里');
|
||||
$table->index(['userid', 'live', 'msg_id']);
|
||||
}
|
||||
});
|
||||
info("update web_socket_dialog_msgs deleted_at");
|
||||
Schema::table('web_socket_dialog_msgs', function (Blueprint $table) {
|
||||
$table->index('deleted_at');
|
||||
});
|
||||
if ($isAdd) {
|
||||
// 关键词包含
|
||||
$contains = [
|
||||
'您可以通过发送以下命令来控制我',
|
||||
'我的机器人。',
|
||||
'机器人名称:',
|
||||
'已加入的会话:',
|
||||
'你可以通过执行以下命令来请求我:',
|
||||
'假期类型:',
|
||||
'评论了此审批',
|
||||
'我是你的机器人助理',
|
||||
'打卡时间: ',
|
||||
'匿名消息使用说明',
|
||||
'匿名消息将通过',
|
||||
'导出任务统计已完成',
|
||||
'我不是你的机器人',
|
||||
'机器人名称由',
|
||||
'您没有创建机器人',
|
||||
'缺卡提醒:',
|
||||
'打卡提醒:',
|
||||
'文件下载打包已完成',
|
||||
'您有一个新任务 #',
|
||||
'您协助的任务 #',
|
||||
'您负责的任务 #',
|
||||
];
|
||||
info("update web_socket_dialog_msgs key contains");
|
||||
foreach ($contains as $key) {
|
||||
WebSocketDialogMsg::whereType('text')->where('key', 'like', "%{$key}%")->update(['key' => '']);
|
||||
}
|
||||
|
||||
// 关键词开始以
|
||||
$starts = [
|
||||
'/hello',
|
||||
'/help',
|
||||
'/list',
|
||||
'/info',
|
||||
'/newbot',
|
||||
'/setname',
|
||||
'/deletebot',
|
||||
'/token',
|
||||
'/revoke',
|
||||
'/webhook',
|
||||
'/clearday',
|
||||
'/dialog',
|
||||
'/api',
|
||||
];
|
||||
info("update web_socket_dialog_msgs key starts");
|
||||
foreach ($starts as $key) {
|
||||
WebSocketDialogMsg::whereType('text')->where('key', 'like', "{$key}%")->update(['key' => '']);
|
||||
}
|
||||
|
||||
// 关键词等于
|
||||
$equals = [
|
||||
'我要打卡',
|
||||
'IT资讯',
|
||||
'36氪',
|
||||
'60s读世界',
|
||||
'开心笑话',
|
||||
'心灵鸡汤',
|
||||
'使用说明',
|
||||
'隐私说明',
|
||||
'帮助指令',
|
||||
'API接口文档',
|
||||
'我的机器人',
|
||||
'清空上下文',
|
||||
'操作频繁!',
|
||||
'暂未开启签到功能。',
|
||||
'暂未开放手动签到。',
|
||||
'设置成功',
|
||||
'机器人不存在。',
|
||||
];
|
||||
info("update web_socket_dialog_msgs key equals");
|
||||
foreach ($equals as $key) {
|
||||
WebSocketDialogMsg::whereType('text')->whereKey($key)->update(['key' => '']);
|
||||
}
|
||||
|
||||
// 更新是否在会话里面
|
||||
info("update web_socket_dialog_msg_reads live value");
|
||||
WebSocketDialog::chunk(100, function ($dialogs) {
|
||||
/** @var WebSocketDialog $dialog */
|
||||
foreach ($dialogs as $dialog) {
|
||||
WebSocketDialogMsgRead::whereDialogId($dialog->id)->whereIn('userid', function ($query) use ($dialog) {
|
||||
$query->select('userid')->from('web_socket_dialog_users')->whereDialogId($dialog->id);
|
||||
})->update(['live' => 1]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
use App\Models\WebSocketDialog;
|
||||
use App\Models\WebSocketDialogMsg;
|
||||
use App\Models\WebSocketDialogMsgRead;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddWebSocketDialogMsgReadsLive2 extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
WebSocketDialogMsg::whereType('text')->where('key', 'like', "LongText-%")->update(['key' => '']);
|
||||
WebSocketDialogMsg::whereType('file')->where('key', 'like', "image.%")->update(['key' => '']);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateWebSocketDialogMsgTranslatesTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
if (Schema::hasTable('web_socket_dialog_msg_translates'))
|
||||
return;
|
||||
|
||||
Schema::create('web_socket_dialog_msg_translates', 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->string('language', 50)->nullable()->default('')->comment('语言');
|
||||
$table->longText('content')->nullable()->comment('翻译内容');
|
||||
$table->index(['msg_id', 'language']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('web_socket_dialog_msg_translates');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddIndexSome20241102 extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('web_socket_dialog_msgs', function (Blueprint $table) {
|
||||
$table->index(['dialog_id', 'deleted_at']);
|
||||
});
|
||||
Schema::table('deleteds', function (Blueprint $table) {
|
||||
$table->index(['type', 'userid']);
|
||||
$table->index(['type', 'userid', 'created_at']);
|
||||
});
|
||||
Schema::table('report_receives', function (Blueprint $table) {
|
||||
$table->index(['userid', 'read', 'rid']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
// 回滚数据 - 无法回滚
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
use App\Models\WebSocketDialog;
|
||||
use App\Models\WebSocketDialogMsg;
|
||||
use App\Models\WebSocketDialogMsgRead;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class RemoveWebSocketDialogMsgReadsLive extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('web_socket_dialog_msg_reads', function (Blueprint $table) use (&$isAdd) {
|
||||
if (Schema::hasColumn('web_socket_dialog_msg_reads', 'live')) {
|
||||
$table->dropIndex(['userid', 'live', 'msg_id']);
|
||||
$table->dropColumn('live');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
35
database/migrations/2024_11_05_151611_add_users_lang.php
Normal file
35
database/migrations/2024_11_05_151611_add_users_lang.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddUsersLang extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
if (!Schema::hasColumn('users', 'lang')) {
|
||||
$table->string('lang', 20)->nullable()->default('')->after('bot')->comment('语言首选项');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropColumn("lang");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddIndexSome20241107 extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('web_socket_dialogs', function (Blueprint $table) {
|
||||
$table->index('deleted_at');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
// 回滚数据 - 无法回滚
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
use App\Models\ProjectPermission;
|
||||
use App\Module\Base;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class UpdateSettingEmailSettings extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
$setting = Base::setting('emailSetting');
|
||||
if (!isset($setting['msg_unread_time_ranges'])) {
|
||||
$setting['msg_unread_time_ranges'] = [
|
||||
['00:00', '09:00'],
|
||||
['18:00', '23:59'],
|
||||
];
|
||||
Base::setting('emailSetting', $setting);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user