Compare commits
162
Commits
329b6cb23d
...
v1.0.20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
83c683d473 | ||
|
|
4963988ec2 | ||
|
|
9555474b0b | ||
|
|
022185079c | ||
|
|
acc3f05f12 | ||
|
|
a150f9e017 | ||
|
|
a523df6a07 | ||
|
|
31c583eeb5 | ||
|
|
e3ef3e5275 | ||
|
|
84190b9773 | ||
|
|
7808d22775 | ||
|
|
fba559cd22 | ||
|
|
ecd2fa1c33 | ||
|
|
dcc75efa87 | ||
|
|
a9465299a9 | ||
|
|
42152c7b79 | ||
|
|
1a896ef457 | ||
|
|
9d8990a028 | ||
|
|
17d88fc338 | ||
|
|
bb40f347a3 | ||
|
|
e3236d262d | ||
|
|
3d4e32e5da | ||
|
|
c8e2e16ba5 | ||
|
|
bdc4cbb240 | ||
|
|
99b44faf09 | ||
|
|
7f6cebd4e2 | ||
|
|
75fd4bed18 | ||
|
|
b3e3cbf84b | ||
|
|
8db6f962c9 | ||
|
|
0aee11d824 | ||
|
|
e35cf3aa59 | ||
|
|
457e716b38 | ||
|
|
ab4a164680 | ||
|
|
9dfca25f8c | ||
|
|
e369a38e2e | ||
|
|
f1e10df6a7 | ||
|
|
fcf358a768 | ||
|
|
36e2a37af2 | ||
|
|
348f7396a1 | ||
|
|
619369104c | ||
|
|
c0b04fc554 | ||
|
|
5e5748b521 | ||
|
|
67d79f7527 | ||
|
|
ee530d2a8c | ||
|
|
5f36844279 | ||
|
|
5b9391a50a | ||
|
|
6bc73414d9 | ||
|
|
4f1408b9d0 | ||
|
|
d555aad9ac | ||
|
|
029eea70cc | ||
|
|
2f42204794 | ||
|
|
22b1e65385 | ||
|
|
72bdb8d49b | ||
|
|
5ec6bc472f | ||
|
|
81b7356d67 | ||
|
|
44af16f451 | ||
|
|
c6157db0de | ||
|
|
39ac73dc66 | ||
|
|
6e43ff349d | ||
|
|
ab79391cdc | ||
|
|
67bc9382c5 | ||
|
|
7e40e0202e | ||
|
|
7ffdd715ee | ||
|
|
1709316ce1 | ||
|
|
2170c48763 | ||
|
|
568563475e | ||
|
|
8a92e46eb1 | ||
|
|
1875cec8a5 | ||
|
|
c2d1c48aae | ||
|
|
4c162dad34 | ||
|
|
e14958c643 | ||
|
|
c49686c411 | ||
|
|
65161a7fad | ||
|
|
187f270866 | ||
|
|
50d4bba045 | ||
|
|
6eedc06a07 | ||
|
|
b55b43eba4 | ||
|
|
20289a3168 | ||
|
|
d45bf11477 | ||
|
|
7370b2fc98 | ||
|
|
da288338cd | ||
|
|
2f366fe219 | ||
|
|
ea430f2468 | ||
|
|
f1715a3e32 | ||
|
|
f76f79744a | ||
|
|
833a303bb6 | ||
|
|
19daaff907 | ||
|
|
1658be7d8d | ||
|
|
db94d8f932 | ||
|
|
6c537852a0 | ||
|
|
53618fe362 | ||
|
|
15bf9bd688 | ||
|
|
ce22661a9b | ||
|
|
22e415dd7b | ||
|
|
844c5381c4 | ||
|
|
e45809a0db | ||
|
|
0625c17716 | ||
|
|
4e7f340cc6 | ||
|
|
523edffc4e | ||
|
|
3cc371c53e | ||
|
|
c7ddb1a8b0 | ||
|
|
b46e9079d5 | ||
|
|
dc20070bf8 | ||
|
|
45c9fc814a | ||
|
|
78163441dd | ||
|
|
407dc13ac4 | ||
|
|
9762be1244 | ||
|
|
0c06bf16c2 | ||
|
|
971cad7365 | ||
|
|
1db39e77d3 | ||
|
|
b5e9da0e9b | ||
|
|
34e1ac6264 | ||
|
|
ef192daa93 | ||
|
|
8947269351 | ||
|
|
b6a58b7376 | ||
|
|
cd7bc31e6b | ||
|
|
e619f3a1e2 | ||
|
|
230864fa43 | ||
|
|
fa11b388db | ||
|
|
b6eb7980e4 | ||
|
|
3410c540ab | ||
|
|
040a46934d | ||
|
|
7eb409f6d0 | ||
|
|
7b3d4e2a65 | ||
|
|
4e113e8fae | ||
|
|
7a34a481aa | ||
|
|
a402ea86ef | ||
|
|
3eaa560aec | ||
|
|
e61da7d8f9 | ||
|
|
355b192071 | ||
|
|
c732f7cead | ||
|
|
812e33693b | ||
|
|
a7b490240c | ||
|
|
959fc30758 | ||
|
|
fe86a84204 | ||
|
|
42bcfcf70e | ||
|
|
d82c5200e7 | ||
|
|
658765df6f | ||
|
|
d12c76fe03 | ||
|
|
2262eabe07 | ||
|
|
81c1a14228 | ||
|
|
fdfb0439d9 | ||
|
|
e2a3c193d8 | ||
|
|
40760ed517 | ||
|
|
6839dafdb9 | ||
|
|
bc2c920705 | ||
|
|
59b0153f3e | ||
|
|
ffaf7f84b4 | ||
|
|
289ba499d3 | ||
|
|
723992f360 | ||
|
|
05e15998f5 | ||
|
|
44d55c0ed6 | ||
|
|
06bb86ccd5 | ||
|
|
6f1cf2083d | ||
|
|
fd71f330d2 | ||
|
|
cdff62b3b5 | ||
|
|
2ba868b3b6 | ||
|
|
c270ae177d | ||
|
|
323e038e86 | ||
|
|
e816b885b2 | ||
|
|
53230943e8 | ||
|
|
2de29cb3a8 |
@@ -0,0 +1,338 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
types:
|
||||||
|
- closed
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-deploy:
|
||||||
|
runs-on: act_runner_java
|
||||||
|
if: ${{ github.event.pull_request.merged == true }}
|
||||||
|
outputs:
|
||||||
|
deployment_status: ${{ steps.set_status.outputs.status }}
|
||||||
|
env:
|
||||||
|
JAVA_HOME: /usr/lib/jvm/java-25-openjdk
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up environment
|
||||||
|
run: |
|
||||||
|
echo "PR #${{ github.event.number }} merged into master"
|
||||||
|
echo "Source branch: ${{ github.event.pull_request.head.ref }}"
|
||||||
|
echo "Target branch: ${{ github.event.pull_request.base.ref }}"
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: |
|
||||||
|
echo "Running test suite..."
|
||||||
|
|
||||||
|
- name: Setup Maven settings
|
||||||
|
run: |
|
||||||
|
if [ -z "${{ vars.TIMI_NEXUS_USERNAME }}" ] || [ -z "${{ vars.TIMI_NEXUS_PASSWORD }}" ]; then
|
||||||
|
echo "Missing vars.TIMI_NEXUS_USERNAME or vars.TIMI_NEXUS_PASSWORD"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
mkdir -p ~/.m2
|
||||||
|
cat > ~/.m2/settings.xml <<EOF
|
||||||
|
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
|
||||||
|
<servers>
|
||||||
|
<server>
|
||||||
|
<id>timi_nexus</id>
|
||||||
|
<username>${{ vars.TIMI_NEXUS_USERNAME }}</username>
|
||||||
|
<password>${{ vars.TIMI_NEXUS_PASSWORD }}</password>
|
||||||
|
</server>
|
||||||
|
<server>
|
||||||
|
<id>e2ins_nexus</id>
|
||||||
|
<username>${{ vars.E2INS_NEXUS_USERNAME }}</username>
|
||||||
|
<password>${{ vars.E2INS_NEXUS_PASSWORD }}</password>
|
||||||
|
</server>
|
||||||
|
</servers>
|
||||||
|
</settings>
|
||||||
|
EOF
|
||||||
|
|
||||||
|
- name: Build project
|
||||||
|
run: |
|
||||||
|
mvn -B -DskipTests clean package -P prod-linux
|
||||||
|
|
||||||
|
- name: Deploy service
|
||||||
|
if: success()
|
||||||
|
env:
|
||||||
|
CONTAINER_NAME: ${{ vars.CONTAINER_NAME }}
|
||||||
|
CONTAINER_TARGET_PATH: ${{ vars.CONTAINER_TARGET_PATH }}
|
||||||
|
MAX_RETRIES: 3
|
||||||
|
RETRY_DELAY: 10
|
||||||
|
run: |
|
||||||
|
if [ -z "$CONTAINER_NAME" ] || [ -z "$CONTAINER_TARGET_PATH" ]; then
|
||||||
|
echo "Missing production environment variables"
|
||||||
|
echo "Required: CONTAINER_NAME, CONTAINER_TARGET_PATH"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
retry_command() {
|
||||||
|
local cmd="$1"
|
||||||
|
local desc="$2"
|
||||||
|
local attempt=1
|
||||||
|
|
||||||
|
while [ $attempt -le $MAX_RETRIES ]; do
|
||||||
|
echo "[$desc] Attempt $attempt/$MAX_RETRIES..."
|
||||||
|
if eval "$cmd"; then
|
||||||
|
echo "OK: $desc succeeded"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
echo "FAIL: $desc failed (attempt $attempt/$MAX_RETRIES)"
|
||||||
|
if [ $attempt -lt $MAX_RETRIES ]; then
|
||||||
|
echo "Retrying in ${RETRY_DELAY}s..."
|
||||||
|
sleep $RETRY_DELAY
|
||||||
|
fi
|
||||||
|
attempt=$((attempt + 1))
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "FAIL: $desc failed after $MAX_RETRIES attempts"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
version=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.version)
|
||||||
|
artifact_id=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.artifactId)
|
||||||
|
jar_file="target/${artifact_id}-${version}.jar"
|
||||||
|
|
||||||
|
if [ ! -f "$jar_file" ]; then
|
||||||
|
echo "Build artifact not found: $jar_file"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v docker >/dev/null 2>&1; then
|
||||||
|
echo "docker command not found in runner environment"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! docker inspect "$CONTAINER_NAME" >/dev/null 2>&1; then
|
||||||
|
echo "Docker container not found: $CONTAINER_NAME"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
target_jar="${artifact_id}.jar"
|
||||||
|
container_target="${CONTAINER_TARGET_PATH%/}/$target_jar"
|
||||||
|
echo "Deploying $jar_file to container $CONTAINER_NAME:$container_target"
|
||||||
|
|
||||||
|
if ! retry_command "docker cp \"$jar_file\" \"$CONTAINER_NAME:$container_target\"" "Docker copy"; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Restarting Docker container: $CONTAINER_NAME"
|
||||||
|
if ! retry_command "docker restart \"$CONTAINER_NAME\"" "Docker restart"; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Deployment completed successfully"
|
||||||
|
|
||||||
|
- name: Create release
|
||||||
|
if: ${{ success() && startsWith(github.event.pull_request.title, 'v') }}
|
||||||
|
env:
|
||||||
|
GITEA_TOKEN: ${{ secrets.RUNNER_TOKEN }}
|
||||||
|
GITEA_SERVER_URL: ${{ github.server_url }}
|
||||||
|
GITEA_INTERNAL_URL: ${{ vars.TIMI_GITEA_INTERNAL_URL }}
|
||||||
|
GITEA_REPOSITORY: ${{ github.repository }}
|
||||||
|
RELEASE_TAG: ${{ github.event.pull_request.title }}
|
||||||
|
RELEASE_TARGET: ${{ github.sha }}
|
||||||
|
MAX_RETRIES: 3
|
||||||
|
RETRY_DELAY: 10
|
||||||
|
run: |
|
||||||
|
if [ -z "$GITEA_TOKEN" ]; then
|
||||||
|
echo "Missing secrets.RUNNER_TOKEN"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$GITEA_INTERNAL_URL" ]; then
|
||||||
|
api_base_url="$GITEA_INTERNAL_URL"
|
||||||
|
echo "Using internal Gitea URL: $api_base_url"
|
||||||
|
else
|
||||||
|
api_base_url="$GITEA_SERVER_URL"
|
||||||
|
echo "Using public Gitea URL: $api_base_url"
|
||||||
|
fi
|
||||||
|
|
||||||
|
version=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.version)
|
||||||
|
artifact_id=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.artifactId)
|
||||||
|
jar_file="target/${artifact_id}-${version}.jar"
|
||||||
|
|
||||||
|
if [ ! -f "$jar_file" ]; then
|
||||||
|
echo "Build artifact not found: $jar_file"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
file_size=$(stat -c%s "$jar_file" 2>/dev/null || stat -f%z "$jar_file" 2>/dev/null || echo "unknown")
|
||||||
|
echo "Found fat jar: $jar_file (size: $file_size bytes)"
|
||||||
|
|
||||||
|
api_url="$api_base_url/api/v1/repos/$GITEA_REPOSITORY/releases"
|
||||||
|
payload=$(cat <<EOF
|
||||||
|
{
|
||||||
|
"tag_name": "$RELEASE_TAG",
|
||||||
|
"name": "$RELEASE_TAG",
|
||||||
|
"target_commitish": "$RELEASE_TARGET",
|
||||||
|
"draft": false,
|
||||||
|
"prerelease": false
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
echo "Creating release with tag: $RELEASE_TAG"
|
||||||
|
echo "API URL: $api_url"
|
||||||
|
echo "Target commit: $RELEASE_TARGET"
|
||||||
|
|
||||||
|
release_response_file=$(mktemp /tmp/release_response_XXXXXX.json)
|
||||||
|
trap "rm -f $release_response_file" EXIT
|
||||||
|
|
||||||
|
release_id=""
|
||||||
|
attempt=1
|
||||||
|
while [ $attempt -le $MAX_RETRIES ] && [ -z "$release_id" ]; do
|
||||||
|
echo "[Create release] Attempt $attempt/$MAX_RETRIES..."
|
||||||
|
|
||||||
|
> "$release_response_file"
|
||||||
|
|
||||||
|
http_code=$(curl -sS -w "%{http_code}" -o "$release_response_file" -X POST "$api_url" \
|
||||||
|
-H "Authorization: token $GITEA_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
--connect-timeout 30 \
|
||||||
|
--max-time 60 \
|
||||||
|
-d "$payload" 2>/dev/null) || http_code="000"
|
||||||
|
|
||||||
|
response=$(cat "$release_response_file" 2>/dev/null || echo "{}")
|
||||||
|
echo "HTTP Status: $http_code"
|
||||||
|
|
||||||
|
if [ "$http_code" = "201" ]; then
|
||||||
|
if command -v jq >/dev/null 2>&1; then
|
||||||
|
release_id=$(echo "$response" | jq -r '.id' 2>/dev/null)
|
||||||
|
else
|
||||||
|
release_id=$(echo "$response" | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2 | tr -d '\n\r')
|
||||||
|
fi
|
||||||
|
echo "OK: Release created: id=$release_id"
|
||||||
|
elif [ "$http_code" = "409" ]; then
|
||||||
|
echo "Release already exists (HTTP 409), fetching existing release..."
|
||||||
|
existing=$(curl -sS "$api_url" -H "Authorization: token $GITEA_TOKEN" --connect-timeout 30 2>/dev/null || echo "[]")
|
||||||
|
if command -v jq >/dev/null 2>&1; then
|
||||||
|
release_id=$(echo "$existing" | jq -r ".[] | select(.tag_name==\"$RELEASE_TAG\") | .id" 2>/dev/null | head -1)
|
||||||
|
else
|
||||||
|
release_id=$(echo "$existing" | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2 | tr -d '\n\r')
|
||||||
|
fi
|
||||||
|
if [ -n "$release_id" ]; then
|
||||||
|
echo "OK: Found existing release: id=$release_id"
|
||||||
|
else
|
||||||
|
echo "FAIL: Could not find existing release id"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "FAIL: Create release failed (HTTP $http_code)"
|
||||||
|
if [ $attempt -lt $MAX_RETRIES ]; then
|
||||||
|
echo "Retrying in ${RETRY_DELAY}s..."
|
||||||
|
sleep $RETRY_DELAY
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
attempt=$((attempt + 1))
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -z "$release_id" ]; then
|
||||||
|
echo "FAIL: Failed to create or find release after $MAX_RETRIES attempts"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
asset_name=$(basename "$jar_file")
|
||||||
|
echo "Uploading asset: $asset_name (size: $file_size bytes)"
|
||||||
|
upload_url="$api_url/$release_id/assets?name=$asset_name"
|
||||||
|
echo "Upload URL: $upload_url"
|
||||||
|
|
||||||
|
asset_response_file=$(mktemp /tmp/asset_response_XXXXXX.json)
|
||||||
|
trap "rm -f $release_response_file $asset_response_file" EXIT
|
||||||
|
|
||||||
|
upload_success=false
|
||||||
|
attempt=1
|
||||||
|
while [ $attempt -le $MAX_RETRIES ] && [ "$upload_success" = "false" ]; do
|
||||||
|
echo "[Upload asset] Attempt $attempt/$MAX_RETRIES..."
|
||||||
|
|
||||||
|
> "$asset_response_file"
|
||||||
|
|
||||||
|
http_code=$(curl -sS -w "%{http_code}" -o "$asset_response_file" -X POST "$upload_url" \
|
||||||
|
-H "Authorization: token $GITEA_TOKEN" \
|
||||||
|
--connect-timeout 30 \
|
||||||
|
--max-time 300 \
|
||||||
|
-F "attachment=@$jar_file" 2>/dev/null) || http_code="000"
|
||||||
|
|
||||||
|
if [ "$http_code" = "201" ]; then
|
||||||
|
upload_success=true
|
||||||
|
echo "OK: Successfully uploaded: $asset_name"
|
||||||
|
else
|
||||||
|
echo "FAIL: Upload failed (HTTP $http_code)"
|
||||||
|
cat "$asset_response_file" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$upload_success" = "false" ] && [ $attempt -lt $MAX_RETRIES ]; then
|
||||||
|
echo "Retrying in ${RETRY_DELAY}s..."
|
||||||
|
sleep $RETRY_DELAY
|
||||||
|
fi
|
||||||
|
attempt=$((attempt + 1))
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$upload_success" = "false" ]; then
|
||||||
|
echo "FAIL: Failed to upload asset after $MAX_RETRIES attempts"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Mark deployment success
|
||||||
|
id: set_status
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
echo "status=success" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
notify-on-failure:
|
||||||
|
runs-on: act_runner_java
|
||||||
|
needs: build-deploy
|
||||||
|
if: ${{ always() && github.event.pull_request.merged == true && needs.build-deploy.result == 'failure' }}
|
||||||
|
steps:
|
||||||
|
- name: Notify CI failure
|
||||||
|
env:
|
||||||
|
PR_NUMBER: ${{ github.event.number }}
|
||||||
|
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||||
|
PR_URL: ${{ github.event.pull_request.html_url }}
|
||||||
|
SOURCE_BRANCH: ${{ github.event.pull_request.head.ref }}
|
||||||
|
AUTHOR: ${{ github.event.pull_request.user.login }}
|
||||||
|
COMMIT_SHA: ${{ github.sha }}
|
||||||
|
REPO: ${{ github.repository }}
|
||||||
|
SERVER_URL: ${{ github.server_url }}
|
||||||
|
WEBHOOK_URL: ${{ vars.NOTIFY_WEBHOOK_URL }}
|
||||||
|
run: |
|
||||||
|
echo "========================================="
|
||||||
|
echo "CI Pipeline Failed - Manual Review Required"
|
||||||
|
echo "========================================="
|
||||||
|
echo ""
|
||||||
|
echo "PR: #$PR_NUMBER - $PR_TITLE"
|
||||||
|
echo "Branch: $SOURCE_BRANCH"
|
||||||
|
echo "Author: $AUTHOR"
|
||||||
|
echo "Commit: $COMMIT_SHA"
|
||||||
|
echo ""
|
||||||
|
echo "Actions:"
|
||||||
|
echo " 1. Re-run CI: $SERVER_URL/$REPO/actions"
|
||||||
|
echo " 2. Revert PR: $PR_URL (click 'Revert' button)"
|
||||||
|
echo ""
|
||||||
|
echo "========================================="
|
||||||
|
|
||||||
|
if [ -n "$WEBHOOK_URL" ]; then
|
||||||
|
message="CI 部署失败\n\nPR: #$PR_NUMBER - $PR_TITLE\n分支: $SOURCE_BRANCH\n提交者: $AUTHOR\n\n请检查并决定:\n- 重试 CI\n- 回滚合并"
|
||||||
|
|
||||||
|
payload=$(cat <<EOF
|
||||||
|
{
|
||||||
|
"msgtype": "text",
|
||||||
|
"text": {
|
||||||
|
"content": "$message"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
curl -sS -X POST "$WEBHOOK_URL" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "$payload" || echo "Warning: Failed to send notification"
|
||||||
|
|
||||||
|
echo "OK: Notification sent"
|
||||||
|
else
|
||||||
|
echo "Note: Set vars.NOTIFY_WEBHOOK_URL to enable webhook notifications"
|
||||||
|
fi
|
||||||
@@ -2,6 +2,8 @@
|
|||||||
/data
|
/data
|
||||||
/logs
|
/logs
|
||||||
/target
|
/target
|
||||||
|
/temp
|
||||||
|
/AGENTS.md
|
||||||
|
|
||||||
multilingualField/
|
multilingualField/
|
||||||
!.mvn/wrapper/maven-wrapper.jar
|
!.mvn/wrapper/maven-wrapper.jar
|
||||||
|
|||||||
Generated
+5
@@ -1,3 +1,8 @@
|
|||||||
# Default ignored files
|
# Default ignored files
|
||||||
/shelf/
|
/shelf/
|
||||||
/workspace.xml
|
/workspace.xml
|
||||||
|
/httpRequests
|
||||||
|
/dataSources
|
||||||
|
/dataSources.local.xml
|
||||||
|
CopilotChatHistory.xml
|
||||||
|
developer-tools.xml
|
||||||
Generated
+5
@@ -0,0 +1,5 @@
|
|||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<state>
|
||||||
|
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
||||||
|
</state>
|
||||||
|
</component>
|
||||||
Generated
+17
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||||
|
<data-source source="LOCAL" name="vm.imyeyu.develop" uuid="05742bdc-a11a-4147-9168-ef976a70258a">
|
||||||
|
<driver-ref>mariadb</driver-ref>
|
||||||
|
<synchronize>true</synchronize>
|
||||||
|
<jdbc-driver>org.mariadb.jdbc.Driver</jdbc-driver>
|
||||||
|
<jdbc-url>jdbc:mariadb://vm.imyeyu.develop:3306/timi_server</jdbc-url>
|
||||||
|
<jdbc-additional-properties>
|
||||||
|
<property name="com.intellij.clouds.kubernetes.db.host.port" />
|
||||||
|
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
|
||||||
|
<property name="com.intellij.clouds.kubernetes.db.container.port" />
|
||||||
|
</jdbc-additional-properties>
|
||||||
|
<working-dir>$ProjectFileDir$</working-dir>
|
||||||
|
</data-source>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="IncorrectHttpHeaderInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="customHeaders">
|
||||||
|
<set>
|
||||||
|
<option value="Token" />
|
||||||
|
</set>
|
||||||
|
</option>
|
||||||
|
</inspection_tool>
|
||||||
|
</profile>
|
||||||
|
</component>
|
||||||
Generated
+35
-29
@@ -1,3 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
<component name="MavenProjectsManager">
|
<component name="MavenProjectsManager">
|
||||||
@@ -10,49 +11,54 @@
|
|||||||
<component name="NullableNotNullManager">
|
<component name="NullableNotNullManager">
|
||||||
<option name="myDefaultNullable" value="org.jetbrains.annotations.Nullable" />
|
<option name="myDefaultNullable" value="org.jetbrains.annotations.Nullable" />
|
||||||
<option name="myDefaultNotNull" value="jakarta.validation.constraints.NotNull" />
|
<option name="myDefaultNotNull" value="jakarta.validation.constraints.NotNull" />
|
||||||
|
<option name="myOrdered" value="true" />
|
||||||
<option name="myNullables">
|
<option name="myNullables">
|
||||||
<value>
|
<value>
|
||||||
<list size="14">
|
<list size="16">
|
||||||
<item index="0" class="java.lang.String" itemvalue="com.android.annotations.Nullable" />
|
<item index="0" class="java.lang.String" itemvalue="com.android.annotations.Nullable" />
|
||||||
<item index="1" class="java.lang.String" itemvalue="org.jspecify.nullness.Nullable" />
|
<item index="1" class="java.lang.String" itemvalue="org.jspecify.nullness.Nullable" />
|
||||||
<item index="2" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNullable" />
|
<item index="2" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNullable" />
|
||||||
<item index="3" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableDecl" />
|
<item index="3" class="java.lang.String" itemvalue="androidx.annotation.Nullable" />
|
||||||
<item index="4" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
|
<item index="4" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
|
||||||
<item index="5" class="java.lang.String" itemvalue="androidx.annotation.Nullable" />
|
<item index="5" class="java.lang.String" itemvalue="org.jspecify.annotations.Nullable" />
|
||||||
<item index="6" class="java.lang.String" itemvalue="org.eclipse.jdt.annotation.Nullable" />
|
<item index="6" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
|
||||||
<item index="7" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
|
<item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.Nullable" />
|
||||||
<item index="8" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
|
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableDecl" />
|
||||||
<item index="9" class="java.lang.String" itemvalue="jakarta.annotation.Nullable" />
|
<item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableType" />
|
||||||
<item index="10" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" />
|
<item index="10" class="java.lang.String" itemvalue="org.eclipse.jdt.annotation.Nullable" />
|
||||||
<item index="11" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
|
<item index="11" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
|
||||||
<item index="12" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.Nullable" />
|
<item index="12" class="java.lang.String" itemvalue="jakarta.annotation.Nullable" />
|
||||||
<item index="13" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableType" />
|
<item index="13" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
|
||||||
|
<item index="14" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" />
|
||||||
|
<item index="15" class="java.lang.String" itemvalue="org.springframework.lang.Nullable" />
|
||||||
</list>
|
</list>
|
||||||
</value>
|
</value>
|
||||||
</option>
|
</option>
|
||||||
<option name="myNotNulls">
|
<option name="myNotNulls">
|
||||||
<value>
|
<value>
|
||||||
<list size="15">
|
<list size="17">
|
||||||
<item index="0" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNonNull" />
|
<item index="0" class="java.lang.String" itemvalue="jakarta.validation.constraints.NotNull" />
|
||||||
<item index="1" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.NonNull" />
|
<item index="1" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNonNull" />
|
||||||
<item index="2" class="java.lang.String" itemvalue="jakarta.annotation.Nonnull" />
|
<item index="2" class="java.lang.String" itemvalue="androidx.annotation.NonNull" />
|
||||||
<item index="3" class="java.lang.String" itemvalue="androidx.annotation.NonNull" />
|
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
|
||||||
<item index="4" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullType" />
|
<item index="4" class="java.lang.String" itemvalue="org.jspecify.nullness.NonNull" />
|
||||||
<item index="5" class="java.lang.String" itemvalue="lombok.NonNull" />
|
<item index="5" class="java.lang.String" itemvalue="com.android.annotations.NonNull" />
|
||||||
<item index="6" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
|
<item index="6" class="java.lang.String" itemvalue="org.jspecify.annotations.NonNull" />
|
||||||
<item index="7" class="java.lang.String" itemvalue="jakarta.validation.constraints.NotNull" />
|
<item index="7" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
|
||||||
<item index="8" class="java.lang.String" itemvalue="org.jspecify.nullness.NonNull" />
|
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.NonNull" />
|
||||||
<item index="9" class="java.lang.String" itemvalue="com.android.annotations.NonNull" />
|
<item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullDecl" />
|
||||||
<item index="10" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
|
<item index="10" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullType" />
|
||||||
<item index="11" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullDecl" />
|
<item index="11" class="java.lang.String" itemvalue="org.eclipse.jdt.annotation.NonNull" />
|
||||||
<item index="12" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
|
<item index="12" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
|
||||||
<item index="13" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
|
<item index="13" class="java.lang.String" itemvalue="jakarta.annotation.Nonnull" />
|
||||||
<item index="14" class="java.lang.String" itemvalue="org.eclipse.jdt.annotation.NonNull" />
|
<item index="14" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
|
||||||
|
<item index="15" class="java.lang.String" itemvalue="lombok.NonNull" />
|
||||||
|
<item index="16" class="java.lang.String" itemvalue="org.springframework.lang.NonNull" />
|
||||||
</list>
|
</list>
|
||||||
</value>
|
</value>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" project-jdk-name="openjdk-21" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_25" project-jdk-name="temurin-25" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/out" />
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
Generated
+9
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="SqlDialectMappings">
|
||||||
|
<file url="file://$PROJECT_DIR$/src/main/resources/db/migration/timiserver/V1__init_schema.sql" dialect="MariaDB" />
|
||||||
|
<file url="file://$PROJECT_DIR$/src/main/resources/db/migration/timiserver/V2__init_system_data.sql" dialect="GenericSQL" />
|
||||||
|
<file url="file://$PROJECT_DIR$/src/main/resources/mapper/timi-server/gao/GaoEventRecordMapper.xml" dialect="MariaDB" />
|
||||||
|
<file url="PROJECT" dialect="MariaDB" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
/cache
|
||||||
|
/project.local.yml
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
TimiServerAPI 是一个基于 Spring Boot 的高耦合业务后端服务,代码位于 src/main/java/com/imyeyu/api,下分多个业务模块,如 blog、common、gitea、forevermc、journal、minecraft、music、system 等。项目使用多数据源 MySQL(timiserver、gitea、forevermc)、MongoDB、Redis、MyBatis,并引入 Flyway 做数据库迁移。运行环境为 Windows,JDK 21。
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
项目约定:全部沟通、注释、文档使用中文;Java 使用 tab 缩进;注释要求符合 javadoc;优先使用官方 API,不随意引入外部依赖;字符串优先用 formatted(),长动态字符串用 StringBuilder;优先用代码级工具做精确修改;避免盲目通读大文件,先缩小目录和符号范围。
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Windows / PowerShell 常用命令:`Get-ChildItem -Force` 查看目录,`rg --files` 列文件,`rg -n "PATTERN" PATH` 搜索文本,`mvn -q -DskipTests compile` 做基础编译验证,`git diff -- PATH` 查看改动。配置文件主要在 `src/main/resources`,Java 源码在 `src/main/java`。
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
完成任务后,优先做最小范围验证。Java 侧先尝试 `mvn -q -DskipTests compile`;如果失败,区分是本次改动导致还是项目已有编译问题。不要回滚用户已有改动,不要做破坏性 git 操作。多数据源场景要特别检查 `@Primary`、`@Qualifier` 和自动装配框架(如 Flyway)的绑定关系。
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
# the name by which the project can be referenced within Serena
|
||||||
|
project_name: "TimiServerAPI"
|
||||||
|
|
||||||
|
|
||||||
|
# list of languages for which language servers are started; choose from:
|
||||||
|
# al bash clojure cpp csharp
|
||||||
|
# csharp_omnisharp dart elixir elm erlang
|
||||||
|
# fortran fsharp go groovy haskell
|
||||||
|
# haxe java julia kotlin lua
|
||||||
|
# markdown
|
||||||
|
# matlab nix pascal perl php
|
||||||
|
# php_phpactor powershell python python_jedi r
|
||||||
|
# rego ruby ruby_solargraph rust scala
|
||||||
|
# swift terraform toml typescript typescript_vts
|
||||||
|
# vue yaml zig
|
||||||
|
# (This list may be outdated. For the current list, see values of Language enum here:
|
||||||
|
# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py
|
||||||
|
# For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.)
|
||||||
|
# Note:
|
||||||
|
# - For C, use cpp
|
||||||
|
# - For JavaScript, use typescript
|
||||||
|
# - For Free Pascal/Lazarus, use pascal
|
||||||
|
# Special requirements:
|
||||||
|
# Some languages require additional setup/installations.
|
||||||
|
# See here for details: https://oraios.github.io/serena/01-about/020_programming-languages.html#language-servers
|
||||||
|
# When using multiple languages, the first language server that supports a given file will be used for that file.
|
||||||
|
# The first language is the default language and the respective language server will be used as a fallback.
|
||||||
|
# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored.
|
||||||
|
languages:
|
||||||
|
- java
|
||||||
|
|
||||||
|
# the encoding used by text files in the project
|
||||||
|
# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings
|
||||||
|
encoding: "utf-8"
|
||||||
|
|
||||||
|
# line ending convention to use when writing source files.
|
||||||
|
# Possible values: unset (use global setting), "lf", "crlf", or "native" (platform default)
|
||||||
|
# This does not affect Serena's own files (e.g. memories and configuration files), which always use native line endings.
|
||||||
|
line_ending:
|
||||||
|
|
||||||
|
# The language backend to use for this project.
|
||||||
|
# If not set, the global setting from serena_config.yml is used.
|
||||||
|
# Valid values: LSP, JetBrains
|
||||||
|
# Note: the backend is fixed at startup. If a project with a different backend
|
||||||
|
# is activated post-init, an error will be returned.
|
||||||
|
language_backend:
|
||||||
|
|
||||||
|
# whether to use project's .gitignore files to ignore files
|
||||||
|
ignore_all_files_in_gitignore: true
|
||||||
|
|
||||||
|
# advanced configuration option allowing to configure language server-specific options.
|
||||||
|
# Maps the language key to the options.
|
||||||
|
# Have a look at the docstring of the constructors of the LS implementations within solidlsp (e.g., for C# or PHP) to see which options are available.
|
||||||
|
# No documentation on options means no options are available.
|
||||||
|
ls_specific_settings: {}
|
||||||
|
|
||||||
|
# list of additional paths to ignore in this project.
|
||||||
|
# Same syntax as gitignore, so you can use * and **.
|
||||||
|
# Note: global ignored_paths from serena_config.yml are also applied additively.
|
||||||
|
ignored_paths: []
|
||||||
|
|
||||||
|
# whether the project is in read-only mode
|
||||||
|
# If set to true, all editing tools will be disabled and attempts to use them will result in an error
|
||||||
|
# Added on 2025-04-18
|
||||||
|
read_only: false
|
||||||
|
|
||||||
|
# list of tool names to exclude.
|
||||||
|
# This extends the existing exclusions (e.g. from the global configuration)
|
||||||
|
#
|
||||||
|
# Below is the complete list of tools for convenience.
|
||||||
|
# To make sure you have the latest list of tools, and to view their descriptions,
|
||||||
|
# execute `uv run scripts/print_tool_overview.py`.
|
||||||
|
#
|
||||||
|
# * `activate_project`: Activates a project based on the project name or path.
|
||||||
|
# * `check_onboarding_performed`: Checks whether project onboarding was already performed.
|
||||||
|
# * `create_text_file`: Creates/overwrites a file in the project directory.
|
||||||
|
# * `delete_memory`: Delete a memory file. Should only happen if a user asks for it explicitly,
|
||||||
|
# for example by saying that the information retrieved from a memory file is no longer correct
|
||||||
|
# or no longer relevant for the project.
|
||||||
|
# * `edit_memory`: Replaces content matching a regular expression in a memory.
|
||||||
|
# * `execute_shell_command`: Executes a shell command.
|
||||||
|
# * `find_file`: Finds files in the given relative paths
|
||||||
|
# * `find_referencing_symbols`: Finds symbols that reference the given symbol using the language server backend
|
||||||
|
# * `find_symbol`: Performs a global (or local) search using the language server backend.
|
||||||
|
# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes.
|
||||||
|
# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file.
|
||||||
|
# * `initial_instructions`: Provides instructions Serena usage (i.e. the 'Serena Instructions Manual')
|
||||||
|
# for clients that do not read the initial instructions when the MCP server is connected.
|
||||||
|
# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol.
|
||||||
|
# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol.
|
||||||
|
# * `list_dir`: Lists files and directories in the given directory (optionally with recursion).
|
||||||
|
# * `list_memories`: List available memories. Any memory can be read using the `read_memory` tool.
|
||||||
|
# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building).
|
||||||
|
# * `read_file`: Reads a file within the project directory.
|
||||||
|
# * `read_memory`: Read the content of a memory file. This tool should only be used if the information
|
||||||
|
# is relevant to the current task. You can infer whether the information
|
||||||
|
# is relevant from the memory file name.
|
||||||
|
# You should not read the same memory file multiple times in the same conversation.
|
||||||
|
# * `rename_memory`: Renames or moves a memory. Moving between project and global scope is supported
|
||||||
|
# (e.g., renaming "global/foo" to "bar" moves it from global to project scope).
|
||||||
|
# * `rename_symbol`: Renames a symbol throughout the codebase using language server refactoring capabilities.
|
||||||
|
# For JB, we use a separate tool.
|
||||||
|
# * `replace_content`: Replaces content in a file (optionally using regular expressions).
|
||||||
|
# * `replace_symbol_body`: Replaces the full definition of a symbol using the language server backend.
|
||||||
|
# * `safe_delete_symbol`:
|
||||||
|
# * `search_for_pattern`: Performs a search for a pattern in the project.
|
||||||
|
# * `write_memory`: Write some information (utf-8-encoded) about this project that can be useful for future tasks to a memory in md format.
|
||||||
|
# The memory name should be meaningful.
|
||||||
|
excluded_tools: []
|
||||||
|
|
||||||
|
# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default).
|
||||||
|
# This extends the existing inclusions (e.g. from the global configuration).
|
||||||
|
included_optional_tools: []
|
||||||
|
|
||||||
|
# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools.
|
||||||
|
# This cannot be combined with non-empty excluded_tools or included_optional_tools.
|
||||||
|
fixed_tools: []
|
||||||
|
|
||||||
|
# list of mode names to that are always to be included in the set of active modes
|
||||||
|
# The full set of modes to be activated is base_modes + default_modes.
|
||||||
|
# If the setting is undefined, the base_modes from the global configuration (serena_config.yml) apply.
|
||||||
|
# Otherwise, this setting overrides the global configuration.
|
||||||
|
# Set this to [] to disable base modes for this project.
|
||||||
|
# Set this to a list of mode names to always include the respective modes for this project.
|
||||||
|
base_modes:
|
||||||
|
|
||||||
|
# list of mode names that are to be activated by default.
|
||||||
|
# The full set of modes to be activated is base_modes + default_modes.
|
||||||
|
# If the setting is undefined, the default_modes from the global configuration (serena_config.yml) apply.
|
||||||
|
# Otherwise, this overrides the setting from the global configuration (serena_config.yml).
|
||||||
|
# This setting can, in turn, be overridden by CLI parameters (--mode).
|
||||||
|
default_modes:
|
||||||
|
|
||||||
|
# initial prompt for the project. It will always be given to the LLM upon activating the project
|
||||||
|
# (contrary to the memories, which are loaded on demand).
|
||||||
|
initial_prompt: ""
|
||||||
|
|
||||||
|
# time budget (seconds) per tool call for the retrieval of additional symbol information
|
||||||
|
# such as docstrings or parameter information.
|
||||||
|
# This overrides the corresponding setting in the global configuration; see the documentation there.
|
||||||
|
# If null or missing, use the setting from the global configuration.
|
||||||
|
symbol_info_budget:
|
||||||
|
|
||||||
|
# list of regex patterns which, when matched, mark a memory entry as read‑only.
|
||||||
|
# Extends the list from the global configuration, merging the two lists.
|
||||||
|
read_only_memory_patterns: []
|
||||||
|
|
||||||
|
# list of regex patterns for memories to completely ignore.
|
||||||
|
# Matching memories will not appear in list_memories or activate_project output
|
||||||
|
# and cannot be accessed via read_memory or write_memory.
|
||||||
|
# To access ignored memory files, use the read_file tool on the raw file path.
|
||||||
|
# Extends the list from the global configuration, merging the two lists.
|
||||||
|
# Example: ["_archive/.*", "_episodes/.*"]
|
||||||
|
ignored_memory_patterns: []
|
||||||
+163
@@ -0,0 +1,163 @@
|
|||||||
|
# HTTP 调试与文档规范
|
||||||
|
|
||||||
|
本目录下的 `.http` 文件同时承担接口调试和接口文档职责,也要保证 AIAgent 能稳定读取
|
||||||
|
|
||||||
|
## 目录分工
|
||||||
|
|
||||||
|
### `.http`
|
||||||
|
|
||||||
|
`.http` 只描述接口级信息:
|
||||||
|
|
||||||
|
1. 接口中文名
|
||||||
|
2. 权限要求
|
||||||
|
3. 前置条件
|
||||||
|
4. 请求参数对应的对象说明
|
||||||
|
5. 返回参数对应的对象说明
|
||||||
|
|
||||||
|
`.http` 不重复展开对象字段明细,复杂字段、嵌套结构、枚举值统一写到 `./object` 下
|
||||||
|
|
||||||
|
### `./object`
|
||||||
|
|
||||||
|
`./object` 只描述对象级信息:
|
||||||
|
|
||||||
|
1. 字段名
|
||||||
|
2. 字段类型
|
||||||
|
3. 字段说明
|
||||||
|
4. 枚举值及含义
|
||||||
|
|
||||||
|
所有模块都可能复用的通用对象必须写在 `./object/common.md`
|
||||||
|
|
||||||
|
按业务领域拆分对象说明文件,例如:
|
||||||
|
|
||||||
|
1. `./object/common.md`
|
||||||
|
2. `./object/user.md`
|
||||||
|
3. `./object/role.md`
|
||||||
|
4. `./object/permission.md`
|
||||||
|
|
||||||
|
不要把同一个对象的字段说明散落到多个文件里
|
||||||
|
|
||||||
|
## 全局约定
|
||||||
|
|
||||||
|
### 返回包装
|
||||||
|
|
||||||
|
除使用 `IgnoreGlobalReturn` 跳过全局包装的方法外,接口默认都会经过
|
||||||
|
[GlobalReturnHandler](E:/IDEAProject/timi-spring/src/main/java/com/imyeyu/spring/util/GlobalReturnHandler.java)
|
||||||
|
统一包装返回
|
||||||
|
|
||||||
|
因此 `.http` 里的 `返回参数` 默认只描述业务 `data` 对象,不再重复赘述全局包装结构
|
||||||
|
|
||||||
|
全局返回包装、通用成功码、错误信息、常见分页壳子等内容统一维护在 `./object/common.md`
|
||||||
|
|
||||||
|
如果接口使用
|
||||||
|
[IgnoreGlobalReturn](E:/IDEAProject/timi-spring/src/main/java/com/imyeyu/spring/annotation/IgnoreGlobalReturn.java)
|
||||||
|
跳过全局包装,必须在对应接口的 `前置条件` 或补充说明里明确写出“该接口不走全局返回包装”
|
||||||
|
|
||||||
|
### 文档真实性
|
||||||
|
|
||||||
|
1. 文档只记录服务端真实行为
|
||||||
|
2. 不写猜测
|
||||||
|
3. 不写“看代码”
|
||||||
|
4. 接口改了就同步更新 `.http` 和 `./object`
|
||||||
|
|
||||||
|
### 变量复用
|
||||||
|
|
||||||
|
能复用的变量统一放在文件顶部,例如 `@user_token`、`@userId`
|
||||||
|
|
||||||
|
## `.http` 模板
|
||||||
|
|
||||||
|
每个接口块固定使用下面格式,不要擅自增删字段名:
|
||||||
|
|
||||||
|
```http
|
||||||
|
### <接口中文名>
|
||||||
|
### 权限:
|
||||||
|
### 前置条件:
|
||||||
|
### 请求参数:
|
||||||
|
### 返回参数:
|
||||||
|
|
||||||
|
POST {{baseUrl}}/path
|
||||||
|
Content-Type: application/json
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
{}
|
||||||
|
```
|
||||||
|
|
||||||
|
## `.http` 填写要求
|
||||||
|
|
||||||
|
### 接口中文名
|
||||||
|
|
||||||
|
直接写接口用途,别写成控制器方法名
|
||||||
|
|
||||||
|
### 权限
|
||||||
|
|
||||||
|
1. 优先直接翻译控制器上的真实注解
|
||||||
|
2. 同时存在角色和权限时一起写,例如 `需要 ADMIN 角色 + USER_UPDATE 权限`
|
||||||
|
3. 只有 Token 要求时写 `需要有效 Token`
|
||||||
|
4. 无需登录时直接写 `无需登录`
|
||||||
|
|
||||||
|
### 前置条件
|
||||||
|
|
||||||
|
这里只写接口调用前必须知道的约束,例如:
|
||||||
|
|
||||||
|
1. 需要先登录并回填 `user_token`
|
||||||
|
2. 需要先创建用户再传入 `userId`
|
||||||
|
3. 重复调用可能因为唯一约束失败
|
||||||
|
4. 接口当前实现为空
|
||||||
|
5. 该接口不走全局返回包装
|
||||||
|
|
||||||
|
没有额外限制时写 `无`
|
||||||
|
|
||||||
|
### 请求参数
|
||||||
|
|
||||||
|
只写请求对象名和对象说明文件,不在 `.http` 内展开字段
|
||||||
|
|
||||||
|
推荐格式:
|
||||||
|
|
||||||
|
1. `无`
|
||||||
|
2. `UserCreateRequest,详见 ./object/user.md`
|
||||||
|
3. `Page<UserQuery>,详见 ./object/common.md 和 ./object/user.md`
|
||||||
|
|
||||||
|
### 返回参数
|
||||||
|
|
||||||
|
只写业务返回对象名和对象说明文件,不在 `.http` 内展开字段
|
||||||
|
|
||||||
|
推荐格式:
|
||||||
|
|
||||||
|
1. `无`
|
||||||
|
2. `User,详见 ./object/user.md`
|
||||||
|
3. `PageResult<User>,详见 ./object/common.md 和 ./object/user.md`
|
||||||
|
4. `二进制数据流,该接口不走全局返回包装`
|
||||||
|
|
||||||
|
## `./object` 编写要求
|
||||||
|
|
||||||
|
对象说明统一使用 Markdown 表格,推荐结构如下
|
||||||
|
|
||||||
|
```md
|
||||||
|
## User
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| name | String | 登录名,系统内唯一 |
|
||||||
|
|
||||||
|
## RoleCode
|
||||||
|
|
||||||
|
| 枚举 | 说明 |
|
||||||
|
|---|---|
|
||||||
|
| SYSTEM | 系统 |
|
||||||
|
| ADMIN | 管理员 |
|
||||||
|
```
|
||||||
|
|
||||||
|
### 对象说明规则
|
||||||
|
|
||||||
|
1. 一个对象一个标题,标题名必须和 `.http` 里引用的对象名一致
|
||||||
|
2. 字段说明只写真实存在且调用方需要关心的内容
|
||||||
|
3. 枚举单独列出,不要把枚举值混进普通字段表
|
||||||
|
4. 嵌套对象、列表元素对象也要有独立标题
|
||||||
|
5. 通用壳子对象统一写在 `./object/common.md`
|
||||||
|
|
||||||
|
## 维护要求
|
||||||
|
|
||||||
|
1. 一个控制器通常对应一个 `.http` 文件
|
||||||
|
2. 新增接口时,同步补齐对应 `.http`
|
||||||
|
3. 新增或修改对象字段时,同步更新对应 `./object/*.md`
|
||||||
|
4. 注释语言统一用中文
|
||||||
|
5. 示例请求优先保证可调试,其次才是看起来完整
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
### AttachmentController 接口测试
|
||||||
|
### 在 IDEA 中选择 dev 或 prod 环境后执行
|
||||||
|
|
||||||
|
@attachmentId =
|
||||||
|
@attachmentBizType = TEMP_FILE
|
||||||
|
@attachmentBizId = demo-biz-id
|
||||||
|
@attachmentAttachType = SOURCE
|
||||||
|
@attachmentName = demo.png
|
||||||
|
@attachmentFilePath = E:/Temp/demo.png
|
||||||
|
@attachmentTempFileId =
|
||||||
|
|
||||||
|
### 查询附件详情
|
||||||
|
### 前置条件:`id` 必须传有效附件 ID
|
||||||
|
### 请求参数:URL 参数 `id` 为附件 ID
|
||||||
|
### 返回参数:Attachment,详见 ./object/common.md
|
||||||
|
POST {{baseUrl}}/attach/detail?id={{attachmentId}}
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
### 创建附件
|
||||||
|
### 前置条件:回填 `attachmentFilePath` 为本地文件绝对路径
|
||||||
|
### 请求参数:multipart/form-data
|
||||||
|
### 返回参数:Attachment,详见 ./object/common.md
|
||||||
|
POST {{baseUrl}}/attach/create
|
||||||
|
Content-Type: multipart/form-data; boundary=AttachmentCreateBoundary
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
--AttachmentCreateBoundary
|
||||||
|
Content-Disposition: form-data; name="bizType"
|
||||||
|
|
||||||
|
{{attachmentBizType}}
|
||||||
|
--AttachmentCreateBoundary
|
||||||
|
Content-Disposition: form-data; name="bizId"
|
||||||
|
|
||||||
|
{{attachmentBizId}}
|
||||||
|
--AttachmentCreateBoundary
|
||||||
|
Content-Disposition: form-data; name="attachType"
|
||||||
|
|
||||||
|
{{attachmentAttachType}}
|
||||||
|
--AttachmentCreateBoundary
|
||||||
|
Content-Disposition: form-data; name="name"
|
||||||
|
|
||||||
|
{{attachmentName}}
|
||||||
|
--AttachmentCreateBoundary
|
||||||
|
Content-Disposition: form-data; name="file"; filename="demo.png"
|
||||||
|
Content-Type: application/octet-stream
|
||||||
|
|
||||||
|
< {{attachmentFilePath}}
|
||||||
|
--AttachmentCreateBoundary--
|
||||||
|
|
||||||
|
### 更新附件元数据
|
||||||
|
### 前置条件:`id` 必须传有效附件 ID
|
||||||
|
### 请求参数:multipart/form-data
|
||||||
|
### 返回参数:无
|
||||||
|
POST {{baseUrl}}/attach/update
|
||||||
|
Content-Type: multipart/form-data; boundary=AttachmentUpdateMetaBoundary
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
--AttachmentUpdateMetaBoundary
|
||||||
|
Content-Disposition: form-data; name="id"
|
||||||
|
|
||||||
|
{{attachmentId}}
|
||||||
|
--AttachmentUpdateMetaBoundary
|
||||||
|
Content-Disposition: form-data; name="name"
|
||||||
|
|
||||||
|
{{attachmentName}}-updated
|
||||||
|
--AttachmentUpdateMetaBoundary
|
||||||
|
Content-Disposition: form-data; name="attachType"
|
||||||
|
|
||||||
|
{{attachmentAttachType}}
|
||||||
|
--AttachmentUpdateMetaBoundary--
|
||||||
|
|
||||||
|
### 更新附件文件
|
||||||
|
### 前置条件:`id` 必须传有效附件 ID,`attachmentFilePath` 必须传本地文件绝对路径
|
||||||
|
### 请求参数:multipart/form-data
|
||||||
|
### 返回参数:无
|
||||||
|
POST {{baseUrl}}/attach/update
|
||||||
|
Content-Type: multipart/form-data; boundary=AttachmentUpdateFileBoundary
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
--AttachmentUpdateFileBoundary
|
||||||
|
Content-Disposition: form-data; name="id"
|
||||||
|
|
||||||
|
{{attachmentId}}
|
||||||
|
--AttachmentUpdateFileBoundary
|
||||||
|
Content-Disposition: form-data; name="name"
|
||||||
|
|
||||||
|
{{attachmentName}}-file-updated
|
||||||
|
--AttachmentUpdateFileBoundary
|
||||||
|
Content-Disposition: form-data; name="file"; filename="demo.png"
|
||||||
|
Content-Type: application/octet-stream
|
||||||
|
|
||||||
|
< {{attachmentFilePath}}
|
||||||
|
--AttachmentUpdateFileBoundary--
|
||||||
|
|
||||||
|
### 删除附件
|
||||||
|
### 前置条件:`id` 必须传有效附件 ID
|
||||||
|
### 请求参数:URL 参数 `id` 为附件 ID
|
||||||
|
### 返回参数:无
|
||||||
|
POST {{baseUrl}}/attach/delete?id={{attachmentId}}
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
### 按业务查询单个附件
|
||||||
|
### 前置条件:`bizType` 和 `bizId` 必须传有效值
|
||||||
|
### 请求参数:URL 参数 `bizType`、`bizId`
|
||||||
|
### 返回参数:Attachment,详见 ./object/common.md
|
||||||
|
GET {{baseUrl}}/attach/detail/byBiz?bizType={{attachmentBizType}}&bizId={{attachmentBizId}}
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
### 按业务和附件类型查询单个附件
|
||||||
|
### 前置条件:`bizType`、`bizId`、`attachType` 必须传有效值
|
||||||
|
### 请求参数:URL 参数 `bizType`、`bizId`、`attachType`
|
||||||
|
### 返回参数:Attachment,详见 ./object/common.md
|
||||||
|
GET {{baseUrl}}/attach/detail/byAttachType?bizType={{attachmentBizType}}&bizId={{attachmentBizId}}&attachType={{attachmentAttachType}}
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
### 按业务查询附件列表
|
||||||
|
### 前置条件:`bizType` 和 `bizId` 必须传有效值
|
||||||
|
### 请求参数:URL 参数 `bizType`、`bizId`,可重复传 `attachTypeList`
|
||||||
|
### 返回参数:Attachment[]
|
||||||
|
GET {{baseUrl}}/attach/list/byBiz?bizType={{attachmentBizType}}&bizId={{attachmentBizId}}&attachTypeList={{attachmentAttachType}}
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
### 按业务统计附件数量
|
||||||
|
### 前置条件:`bizType` 和 `bizId` 必须传有效值
|
||||||
|
### 请求参数:URL 参数 `bizType`、`bizId`,可重复传 `attachTypeList`
|
||||||
|
### 返回参数:number
|
||||||
|
GET {{baseUrl}}/attach/count/byBiz?bizType={{attachmentBizType}}&bizId={{attachmentBizId}}&attachTypeList={{attachmentAttachType}}
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
### 按业务分页查询附件
|
||||||
|
### 前置条件:`bizType` 和 `bizId` 必须传有效值
|
||||||
|
### 请求参数:URL 参数 `bizType`、`bizId`,可重复传 `attachTypeList`,请求体为 Page
|
||||||
|
### 返回参数:PageResult<Attachment>
|
||||||
|
POST {{baseUrl}}/attach/page/byBiz?bizType={{attachmentBizType}}&bizId={{attachmentBizId}}&attachTypeList={{attachmentAttachType}}
|
||||||
|
Content-Type: application/json
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"index": 0,
|
||||||
|
"size": 10
|
||||||
|
}
|
||||||
|
|
||||||
|
### 按业务差分更新附件
|
||||||
|
### 前置条件:新增项需要传有效 `tempFileId`
|
||||||
|
### 请求参数:BizUpdateReq
|
||||||
|
### 返回参数:无
|
||||||
|
POST {{baseUrl}}/attach/update/byBiz
|
||||||
|
Content-Type: application/json
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"bizType": "{{attachmentBizType}}",
|
||||||
|
"bizId": "{{attachmentBizId}}",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "{{attachmentId}}",
|
||||||
|
"attachType": "{{attachmentAttachType}}",
|
||||||
|
"name": "{{attachmentName}}-biz-updated"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tempFileId": "{{attachmentTempFileId}}",
|
||||||
|
"attachType": "{{attachmentAttachType}}",
|
||||||
|
"name": "{{attachmentName}}-from-temp"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
### 按业务删除全部附件
|
||||||
|
### 前置条件:`bizType` 和 `bizId` 必须传有效值
|
||||||
|
### 请求参数:BizDeleteReq
|
||||||
|
### 返回参数:无
|
||||||
|
POST {{baseUrl}}/attach/delete/byBiz
|
||||||
|
Content-Type: application/json
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"bizType": "{{attachmentBizType}}",
|
||||||
|
"bizId": "{{attachmentBizId}}"
|
||||||
|
}
|
||||||
|
|
||||||
|
### 读取附件原图
|
||||||
|
### 前置条件:`attachmentId` 必须传有效附件 ID
|
||||||
|
### 请求参数:路径参数 `id`
|
||||||
|
### 返回参数:附件二进制流
|
||||||
|
GET {{baseUrl}}/attach/read/{{attachmentId}}
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
### 读取附件缩略图
|
||||||
|
### 前置条件:`attachmentId` 必须传有效图片或视频附件 ID
|
||||||
|
### 请求参数:路径参数 `id`,URL 参数 `thumbWidth`、`thumbHeight`
|
||||||
|
### 返回参数:附件二进制流
|
||||||
|
GET {{baseUrl}}/attach/read/{{attachmentId}}?thumbWidth=200&thumbHeight=200
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
### 下载附件
|
||||||
|
### 前置条件:`attachmentId` 必须传有效附件 ID
|
||||||
|
### 请求参数:路径参数 `id`
|
||||||
|
### 返回参数:附件二进制流
|
||||||
|
GET {{baseUrl}}/attach/download/{{attachmentId}}
|
||||||
|
Token: {{user_token}}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
### MultilingualController 接口测试
|
||||||
|
### 在 IDEA 中选择 dev 或 prod 环境后执行
|
||||||
|
|
||||||
|
@multilingualId =
|
||||||
|
@multilingualZhCN = 测试简体中文
|
||||||
|
@multilingualZhTW = 測試繁體中文
|
||||||
|
@multilingualEnUS = Test English
|
||||||
|
@multilingualRuRU = Тест
|
||||||
|
@multilingualJaJP = テスト
|
||||||
|
@multilingualDeDE = Test Deutsch
|
||||||
|
@multilingualKoKR = 테스트
|
||||||
|
|
||||||
|
### 查询多语言详情
|
||||||
|
### 权限:需要 ADMIN 角色 + MULTILINGUAL_READ 权限
|
||||||
|
### 前置条件:需要先登录并回填 `user_token`,URL 参数 `id` 必须传有效多语言 ID
|
||||||
|
### 请求参数:无请求体,URL 参数:`id` 为多语言 ID
|
||||||
|
### 返回参数:Multilingual,详见 ./object/common.md
|
||||||
|
POST {{baseUrl}}/system/multilingual/detail?id={{multilingualId}}
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
### 创建多语言
|
||||||
|
### 权限:需要 ADMIN 角色 + MULTILINGUAL_CREATE 权限
|
||||||
|
### 前置条件:需要先登录并回填 `user_token`
|
||||||
|
### 请求参数:Multilingual,详见 ./object/common.md
|
||||||
|
### 返回参数:Multilingual,详见 ./object/common.md
|
||||||
|
POST {{baseUrl}}/system/multilingual/create
|
||||||
|
Content-Type: application/json
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"zhCN": "{{multilingualZhCN}}",
|
||||||
|
"zhTW": "{{multilingualZhTW}}",
|
||||||
|
"enUS": "{{multilingualEnUS}}",
|
||||||
|
"ruRU": "{{multilingualRuRU}}",
|
||||||
|
"jaJP": "{{multilingualJaJP}}",
|
||||||
|
"deDE": "{{multilingualDeDE}}",
|
||||||
|
"koKR": "{{multilingualKoKR}}"
|
||||||
|
}
|
||||||
|
|
||||||
|
### 更新多语言
|
||||||
|
### 权限:需要 ADMIN 角色 + MULTILINGUAL_UPDATE 权限
|
||||||
|
### 前置条件:需要先登录并回填 `user_token`,`id` 必须存在
|
||||||
|
### 请求参数:Multilingual,详见 ./object/common.md
|
||||||
|
### 返回参数:无
|
||||||
|
POST {{baseUrl}}/system/multilingual/update
|
||||||
|
Content-Type: application/json
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"id": "{{multilingualId}}",
|
||||||
|
"zhCN": "{{multilingualZhCN}}-已更新",
|
||||||
|
"zhTW": "{{multilingualZhTW}}-已更新",
|
||||||
|
"enUS": "{{multilingualEnUS}}-updated",
|
||||||
|
"ruRU": "{{multilingualRuRU}}-updated",
|
||||||
|
"jaJP": "{{multilingualJaJP}}-updated",
|
||||||
|
"deDE": "{{multilingualDeDE}}-updated",
|
||||||
|
"koKR": "{{multilingualKoKR}}-updated"
|
||||||
|
}
|
||||||
|
|
||||||
|
### 删除多语言
|
||||||
|
### 权限:需要 ADMIN 角色 + MULTILINGUAL_DELETE 权限
|
||||||
|
### 前置条件:需要先登录并回填 `user_token`,URL 参数 `id` 必须传有效多语言 ID
|
||||||
|
### 请求参数:无请求体,URL 参数:`id` 为多语言 ID
|
||||||
|
### 返回参数:无
|
||||||
|
POST {{baseUrl}}/system/multilingual/delete?id={{multilingualId}}
|
||||||
|
Token: {{user_token}}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
### GaoRegisterAction 接口测试
|
||||||
|
### 在 IDEA 中选择 dev 或 prod 环境后执行
|
||||||
|
|
||||||
|
@actionId =
|
||||||
|
|
||||||
|
### 分页查询登记行为
|
||||||
|
POST {{baseUrl}}/gao/action/list
|
||||||
|
Content-Type: application/json
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"index": 0,
|
||||||
|
"size": 16
|
||||||
|
}
|
||||||
|
|
||||||
|
### 查询启用行为
|
||||||
|
POST {{baseUrl}}/gao/action/enabled/list
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
### 查询行为详情
|
||||||
|
POST {{baseUrl}}/gao/action/detail?id={{actionId}}
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
### 创建登记行为
|
||||||
|
POST {{baseUrl}}/gao/action/create
|
||||||
|
Content-Type: application/json
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "签到",
|
||||||
|
"description": "客户到店签到",
|
||||||
|
"enabled": true,
|
||||||
|
"requirePhoto": false,
|
||||||
|
"sort": 10,
|
||||||
|
"permissionCodeList": [
|
||||||
|
"RECORD:CREATE"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
### 更新登记行为
|
||||||
|
POST {{baseUrl}}/gao/action/update
|
||||||
|
Content-Type: application/json
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"id": "{{actionId}}",
|
||||||
|
"name": "物品领取",
|
||||||
|
"description": "登记客户领取物品",
|
||||||
|
"enabled": true,
|
||||||
|
"requirePhoto": true,
|
||||||
|
"sort": 20,
|
||||||
|
"permissionCodeList": [
|
||||||
|
"RECORD:CREATE",
|
||||||
|
"STAT:READ"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
### 删除登记行为
|
||||||
|
POST {{baseUrl}}/gao/action/delete?id={{actionId}}
|
||||||
|
Token: {{user_token}}
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
### GaoCustomer 接口测试
|
||||||
|
### 在 IDEA 中选择 dev 或 prod 环境后执行
|
||||||
|
|
||||||
|
@customerId =
|
||||||
|
@introducerCustomerId =
|
||||||
|
@customerCode = GAO-0001
|
||||||
|
|
||||||
|
### 分页查询客户
|
||||||
|
POST {{baseUrl}}/gao/customer/list
|
||||||
|
Content-Type: application/json
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"index": 0,
|
||||||
|
"size": 16
|
||||||
|
}
|
||||||
|
|
||||||
|
### 搜索客户
|
||||||
|
POST {{baseUrl}}/gao/customer/search
|
||||||
|
Content-Type: application/json
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"keyword": "张",
|
||||||
|
"introducerCustomerId": "{{introducerCustomerId}}"
|
||||||
|
}
|
||||||
|
|
||||||
|
### 查询客户详情
|
||||||
|
POST {{baseUrl}}/gao/customer/detail?id={{customerId}}
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
### 按客户编码查询
|
||||||
|
POST {{baseUrl}}/gao/customer/detail/code?code={{customerCode}}
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
### 查询某客户邀请了哪些人
|
||||||
|
POST {{baseUrl}}/gao/customer/invited/list?introducerCustomerId={{customerId}}
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
### 创建客户
|
||||||
|
POST {{baseUrl}}/gao/customer/create
|
||||||
|
Content-Type: application/json
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"code": "{{customerCode}}",
|
||||||
|
"name": "张三",
|
||||||
|
"age": 56,
|
||||||
|
"telephone": "020-88886666",
|
||||||
|
"address": "广州市天河区某街道",
|
||||||
|
"remark": "首诊客户",
|
||||||
|
"disease": "肩颈不适",
|
||||||
|
"conditioningPart": [
|
||||||
|
{
|
||||||
|
"code": "neck",
|
||||||
|
"name": "颈部"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "shoulder",
|
||||||
|
"name": "肩部"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"introducerCustomerId": "{{introducerCustomerId}}"
|
||||||
|
}
|
||||||
|
|
||||||
|
### 更新客户
|
||||||
|
POST {{baseUrl}}/gao/customer/update
|
||||||
|
Content-Type: application/json
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"id": "{{customerId}}",
|
||||||
|
"code": "{{customerCode}}",
|
||||||
|
"name": "张三",
|
||||||
|
"age": 57,
|
||||||
|
"telephone": "020-88886667",
|
||||||
|
"address": "广州市天河区某街道 88 号",
|
||||||
|
"remark": "复诊客户",
|
||||||
|
"disease": "腰背调理",
|
||||||
|
"conditioningPart": {
|
||||||
|
"parts": [
|
||||||
|
"waist",
|
||||||
|
"back"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"introducerCustomerId": "{{introducerCustomerId}}"
|
||||||
|
}
|
||||||
|
|
||||||
|
### 删除客户
|
||||||
|
POST {{baseUrl}}/gao/customer/delete?id={{customerId}}
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
### 创建客户邀请关系
|
||||||
|
POST {{baseUrl}}/gao/customer/invite/create
|
||||||
|
Content-Type: application/json
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"customerId": "{{customerId}}",
|
||||||
|
"introducerCustomerId": "{{introducerCustomerId}}"
|
||||||
|
}
|
||||||
|
|
||||||
|
### 查询客户邀请关系详情
|
||||||
|
POST {{baseUrl}}/gao/customer/invite/detail?customerId={{customerId}}
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
### 更新客户邀请关系
|
||||||
|
POST {{baseUrl}}/gao/customer/invite/update
|
||||||
|
Content-Type: application/json
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"customerId": "{{customerId}}",
|
||||||
|
"introducerCustomerId": "{{introducerCustomerId}}"
|
||||||
|
}
|
||||||
|
|
||||||
|
### 删除客户邀请关系
|
||||||
|
POST {{baseUrl}}/gao/customer/invite/delete?customerId={{customerId}}
|
||||||
|
Token: {{user_token}}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
### GaoRegisterRecord 接口测试
|
||||||
|
### 在 IDEA 中选择 dev 或 prod 环境后执行
|
||||||
|
|
||||||
|
@recordId =
|
||||||
|
@customerId =
|
||||||
|
@customerCode = GAO-0001
|
||||||
|
@actionId =
|
||||||
|
|
||||||
|
### 分页查询登记记录
|
||||||
|
POST {{baseUrl}}/gao/record/list
|
||||||
|
Content-Type: application/json
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"index": 0,
|
||||||
|
"size": 16
|
||||||
|
}
|
||||||
|
|
||||||
|
### 查询登记记录详情
|
||||||
|
POST {{baseUrl}}/gao/record/detail?id={{recordId}}
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
### 创建登记记录,直接传客户 ID
|
||||||
|
POST {{baseUrl}}/gao/record/create
|
||||||
|
Content-Type: application/json
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"customerId": "{{customerId}}",
|
||||||
|
"actionId": "{{actionId}}",
|
||||||
|
"remark": "客户到店签到"
|
||||||
|
}
|
||||||
|
|
||||||
|
### 创建登记记录,扫码后传客户编码
|
||||||
|
POST {{baseUrl}}/gao/record/create
|
||||||
|
Content-Type: application/json
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"customerCode": "{{customerCode}}",
|
||||||
|
"actionId": "{{actionId}}",
|
||||||
|
"remark": "扫码快速登记"
|
||||||
|
}
|
||||||
|
|
||||||
|
### 更新登记记录
|
||||||
|
POST {{baseUrl}}/gao/record/update
|
||||||
|
Content-Type: application/json
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"id": "{{recordId}}",
|
||||||
|
"customerId": "{{customerId}}",
|
||||||
|
"actionId": "{{actionId}}",
|
||||||
|
"remark": "补充备注",
|
||||||
|
"registeredAt": 1785110400
|
||||||
|
}
|
||||||
|
|
||||||
|
### 删除登记记录
|
||||||
|
POST {{baseUrl}}/gao/record/delete?id={{recordId}}
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
### 按日查询某个行为记录
|
||||||
|
POST {{baseUrl}}/gao/record/period/list
|
||||||
|
Content-Type: application/json
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"actionId": "{{actionId}}",
|
||||||
|
"periodType": "DAY",
|
||||||
|
"periodValue": 20260727
|
||||||
|
}
|
||||||
|
|
||||||
|
### 按月统计某个行为记录
|
||||||
|
POST {{baseUrl}}/gao/record/period/stat
|
||||||
|
Content-Type: application/json
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"actionId": "{{actionId}}",
|
||||||
|
"periodType": "MONTH",
|
||||||
|
"periodValue": 202607
|
||||||
|
}
|
||||||
|
|
||||||
|
### 查询客户所有登记统计
|
||||||
|
POST {{baseUrl}}/gao/record/customer/stat?customerId={{customerId}}
|
||||||
|
Token: {{user_token}}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"dev": {
|
||||||
|
"baseUrl": "http://localhost:8091"
|
||||||
|
},
|
||||||
|
"prod": {
|
||||||
|
"baseUrl": "https://api.example.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
# 通用对象说明
|
||||||
|
|
||||||
|
## TimiResponse<T>
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| code | Integer | 响应码,`20000` 为成功 |
|
||||||
|
| msg | String | 响应消息 |
|
||||||
|
| data | T | 业务数据体,具体结构由接口返回对象决定 |
|
||||||
|
|
||||||
|
## Page<T>
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| index | int | 页码下标,从 `0` 开始 |
|
||||||
|
| size | long | 每页数量 |
|
||||||
|
| equalsExample | T | 精确匹配示例对象,非空字段参与精确查询 |
|
||||||
|
| likesExample | T | 模糊匹配示例对象,非空字段参与模糊查询 |
|
||||||
|
| equalsLogic | Logic | 精确匹配字段之间的连接逻辑,默认 `AND` |
|
||||||
|
| likesLogic | Logic | 模糊匹配字段之间的连接逻辑,默认 `OR` |
|
||||||
|
| orderMap | LinkedHashMap<String, OrderType> | 排序字段映射,键通常为字段名,值为排序方向 |
|
||||||
|
|
||||||
|
## PageResult<T>
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| total | long | 总数据量 |
|
||||||
|
| pages | int | 总页数 |
|
||||||
|
| list | List<T> | 当前页数据列表 |
|
||||||
|
|
||||||
|
## UUIDEntity
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| id | String | 主键 ID,UUID 字符串 |
|
||||||
|
| createdAt | Long | 创建时间,Unix 时间戳毫秒 |
|
||||||
|
| updatedAt | Long | 更新时间,Unix 时间戳毫秒 |
|
||||||
|
| deletedAt | Long | 删除时间,Unix 时间戳毫秒,未删除时通常为空 |
|
||||||
|
|
||||||
|
## Multilingual
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| id | String | 多语言 ID |
|
||||||
|
| zhCN | String | 简体中文文本 |
|
||||||
|
| zhTW | String | 繁体中文文本 |
|
||||||
|
| enUS | String | 英文文本 |
|
||||||
|
| ruRU | String | 俄文文本 |
|
||||||
|
| jaJP | String | 日文文本 |
|
||||||
|
| deDE | String | 德文文本 |
|
||||||
|
| koKR | String | 韩文文本 |
|
||||||
|
| lastAccessAt | Long | 最后访问时间,Unix 时间戳毫秒 |
|
||||||
|
| createdAt | Long | 创建时间,Unix 时间戳毫秒 |
|
||||||
|
| updatedAt | Long | 更新时间,Unix 时间戳毫秒 |
|
||||||
|
| deletedAt | Long | 删除时间,Unix 时间戳毫秒,未删除时通常为空 |
|
||||||
|
|
||||||
|
## Logic
|
||||||
|
|
||||||
|
| 枚举 | 说明 |
|
||||||
|
|---|---|
|
||||||
|
| AND | 多个条件同时满足 |
|
||||||
|
| OR | 多个条件满足其一 |
|
||||||
|
|
||||||
|
## OrderType
|
||||||
|
|
||||||
|
| 枚举 | 说明 |
|
||||||
|
|---|---|
|
||||||
|
| ASC | 升序 |
|
||||||
|
| DESC | 降序 |
|
||||||
|
|
||||||
|
## ImageType
|
||||||
|
|
||||||
|
| 枚举 | 说明 |
|
||||||
|
|---|---|
|
||||||
|
| AUTO | 双线性 |
|
||||||
|
| SMOOTH | 模糊 |
|
||||||
|
| PIXELATED | 像素 |
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# 权限对象说明
|
||||||
|
|
||||||
|
## Permission
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| id | String | 权限 ID |
|
||||||
|
| createdAt | Long | 创建时间,Unix 时间戳毫秒 |
|
||||||
|
| updatedAt | Long | 更新时间,Unix 时间戳毫秒 |
|
||||||
|
| deletedAt | Long | 删除时间,Unix 时间戳毫秒 |
|
||||||
|
| code | String | 权限代码,需与数据库 `permission.code` 保持一致 |
|
||||||
|
| nameLangId | String | 权限名称的多语言键 |
|
||||||
|
| description | String | 权限说明 |
|
||||||
|
| name | String | 当前语言下的权限名称 |
|
||||||
|
|
||||||
|
## BatchCreateReq
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| prefixCode | String | 权限代码前缀,例如 `ROLE` |
|
||||||
|
| prefixName | String | 权限名称前缀,例如 `角色` |
|
||||||
|
| types | Type[] | 需要批量生成的权限类型列表,详见 `BatchCreateType` |
|
||||||
|
|
||||||
|
## BatchCreateType
|
||||||
|
|
||||||
|
| 枚举 | 说明 |
|
||||||
|
|---|---|
|
||||||
|
| CREATE | 创建权限 |
|
||||||
|
| READ | 读取权限 |
|
||||||
|
| UPDATE | 更新权限 |
|
||||||
|
| DELETE | 删除权限 |
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# 角色对象说明
|
||||||
|
|
||||||
|
## Role
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| id | String | 角色 ID |
|
||||||
|
| createdAt | Long | 创建时间,Unix 时间戳毫秒 |
|
||||||
|
| updatedAt | Long | 更新时间,Unix 时间戳毫秒 |
|
||||||
|
| deletedAt | Long | 删除时间,Unix 时间戳毫秒 |
|
||||||
|
| code | String | 角色代码,常见值见 `RoleCode` |
|
||||||
|
| nameLangId | String | 角色名称的多语言键 |
|
||||||
|
| description | String | 角色说明 |
|
||||||
|
| name | String | 当前语言下的角色名称 |
|
||||||
|
| permissionList | List<Permission> | 角色自身直接绑定的权限列表 |
|
||||||
|
| allPermissionList | List<Permission> | 角色全部权限列表,包含继承自子角色的权限 |
|
||||||
|
| childRoleList | List<Role> | 子角色列表 |
|
||||||
|
| permissionIdList | Set<String> | 权限 ID 列表,创建或更新角色时使用 |
|
||||||
|
| childRoleIdList | Set<String> | 子角色 ID 列表,创建或更新角色时使用 |
|
||||||
|
|
||||||
|
## UserRoleAuthorizeReq
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| userId | String | 目标用户 ID |
|
||||||
|
| roleIdList | List<String> | 需要授权的角色 ID 列表 |
|
||||||
|
| roleCodeList | List<String> | 需要授权的角色代码列表 |
|
||||||
|
|
||||||
|
## RoleCode
|
||||||
|
|
||||||
|
| 枚举 | 说明 |
|
||||||
|
|---|---|
|
||||||
|
| SYSTEM | 系统 |
|
||||||
|
| ADMIN | 管理员 |
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
# 用户对象说明
|
||||||
|
|
||||||
|
## User
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| id | String | 用户 ID |
|
||||||
|
| createdAt | Long | 创建时间,Unix 时间戳毫秒 |
|
||||||
|
| updatedAt | Long | 更新时间,Unix 时间戳毫秒 |
|
||||||
|
| deletedAt | Long | 删除时间,Unix 时间戳毫秒 |
|
||||||
|
| name | String | 登录名,系统内唯一 |
|
||||||
|
| nick | String | 用户昵称 |
|
||||||
|
| password | String | 明文密码,请求时填写,响应通常不返回 |
|
||||||
|
| email | String | 邮箱地址 |
|
||||||
|
| emailVerifyAt | Long | 邮箱验证时间,Unix 时间戳毫秒 |
|
||||||
|
| phoneNo | String | 手机号,11 位大陆手机号 |
|
||||||
|
| phoneNoVerifyAt | Long | 手机号验证时间,Unix 时间戳毫秒 |
|
||||||
|
| wrapperType | ImageType | 背景图渲染算法,详见 `ImageType` |
|
||||||
|
| avatarType | ImageType | 头像渲染算法,详见 `ImageType` |
|
||||||
|
| exp | Integer | 经验值 |
|
||||||
|
| sex | Integer | 性别,当前代码未定义枚举说明 |
|
||||||
|
| birthdate | Long | 出生日期,Unix 时间戳毫秒 |
|
||||||
|
| qq | String | QQ 号 |
|
||||||
|
| description | String | 个人说明 |
|
||||||
|
| lastLoginIP | String | 上次登录 IP |
|
||||||
|
| lastLoginAt | Long | 上次登录时间,Unix 时间戳毫秒 |
|
||||||
|
| unmuteAt | Long | 解除禁言时间,Unix 时间戳毫秒 |
|
||||||
|
| unbanAt | Long | 解除封禁时间,Unix 时间戳毫秒 |
|
||||||
|
| roleList | List<String> | 角色代码列表 |
|
||||||
|
| permissionList | List<String> | 权限代码列表 |
|
||||||
|
| attachmentList | List<Attachment> | 附件列表 |
|
||||||
|
| settingList | List<Setting> | 设置列表 |
|
||||||
|
|
||||||
|
## LoginRequest
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| user | String | 用户标识,可能是 UID、邮箱或用户名 |
|
||||||
|
| password | String | 明文密码 |
|
||||||
|
|
||||||
|
## LoginResponse
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| token | String | 登录令牌 |
|
||||||
|
| expireAt | Long | 令牌过期时间,Unix 时间戳毫秒 |
|
||||||
|
| user | User | 当前登录用户 |
|
||||||
|
|
||||||
|
## UpdatePasswordRequest
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| oldValue | String | 原密码 |
|
||||||
|
| newValue | String | 新密码 |
|
||||||
|
|
||||||
|
## PhoneVerifyCodeSendRequest
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| phoneNo | String | 手机号,必须为 11 位大陆手机号 |
|
||||||
|
|
||||||
|
## PhoneVerifyRequest
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| phoneNo | String | 手机号,必须为 11 位大陆手机号 |
|
||||||
|
| verifyCode | String | 6 位数字验证码 |
|
||||||
|
|
||||||
|
## CancelRequest
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| password | String | 当前密码,用于确认注销 |
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
### PermissionController 接口测试
|
||||||
|
### 在 IDEA 中选择 dev 或 prod 环境后执行
|
||||||
|
|
||||||
|
@user_token =
|
||||||
|
@permissionId =
|
||||||
|
@permissionCode = TEST_PERMISSION
|
||||||
|
@permissionNameLangId = permission.test.name
|
||||||
|
@permissionDescription = 测试权限
|
||||||
|
@moduleCode = CORE
|
||||||
|
|
||||||
|
### 分页查询权限
|
||||||
|
### 权限:需要 PERMISSION_READ 权限
|
||||||
|
### 前置条件:需要先登录并回填 `user_token`
|
||||||
|
### 请求参数:Page<Permission>,详见 ./http/object/permission.md
|
||||||
|
### 返回参数:PageResult<Permission>,详见 ./http/object/permission.md
|
||||||
|
POST {{baseUrl}}/user/permission/list
|
||||||
|
Content-Type: application/json
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"index": 0,
|
||||||
|
"size": 10,
|
||||||
|
"likesExample": {
|
||||||
|
"code": "{{permissionCode}}",
|
||||||
|
"moduleCode": "{{moduleCode}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
### 创建权限
|
||||||
|
### 权限:当前代码要求 PERMISSION_READ 权限
|
||||||
|
### 前置条件:需要先登录并回填 `user_token`
|
||||||
|
### 请求参数:Permission,详见 ./http/object/permission.md
|
||||||
|
### 返回参数:无
|
||||||
|
POST {{baseUrl}}/user/permission/create
|
||||||
|
Content-Type: application/json
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"moduleCode": "{{moduleCode}}",
|
||||||
|
"code": "{{permissionCode}}",
|
||||||
|
"nameLangId": "{{permissionNameLangId}}",
|
||||||
|
"description": "{{permissionDescription}}"
|
||||||
|
}
|
||||||
|
|
||||||
|
### 批量创建权限
|
||||||
|
### 权限:需要 PERMISSION_CREATE 权限
|
||||||
|
### 前置条件:需要先登录并回填 `user_token`
|
||||||
|
### 请求参数:BatchCreateReq,详见 ./http/object/permission.md
|
||||||
|
### 返回参数:无
|
||||||
|
POST {{baseUrl}}/user/permission/create/batch
|
||||||
|
Content-Type: application/json
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"moduleCode": "{{moduleCode}}",
|
||||||
|
"prefixCode": "ROLE",
|
||||||
|
"prefixName": "角色",
|
||||||
|
"types": ["CREATE", "READ", "UPDATE", "DELETE"]
|
||||||
|
}
|
||||||
|
|
||||||
|
### 更新权限
|
||||||
|
### 权限:需要 PERMISSION_UPDATE 权限
|
||||||
|
### 前置条件:需要先登录并回填 `user_token`,`id` 必须存在
|
||||||
|
### 请求参数:Permission,详见 ./http/object/permission.md
|
||||||
|
### 返回参数:无
|
||||||
|
POST {{baseUrl}}/user/permission/update
|
||||||
|
Content-Type: application/json
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"id": "{{permissionId}}",
|
||||||
|
"moduleCode": "{{moduleCode}}",
|
||||||
|
"code": "{{permissionCode}}",
|
||||||
|
"nameLangId": "{{permissionNameLangId}}",
|
||||||
|
"description": "测试权限-已更新"
|
||||||
|
}
|
||||||
|
|
||||||
|
### 删除权限
|
||||||
|
### 权限:需要 PERMISSION_DELETE 权限
|
||||||
|
### 前置条件:需要先登录并回填 `user_token`,URL 参数 `id` 必须传有效权限 ID
|
||||||
|
### 请求参数:无请求体,URL 参数:`id` 为权限 ID
|
||||||
|
### 返回参数:无
|
||||||
|
POST {{baseUrl}}/user/permission/delete?id={{permissionId}}
|
||||||
|
Token: {{user_token}}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
### RoleController 接口测试
|
||||||
|
### 在 IDEA 中选择 dev 或 prod 环境后执行
|
||||||
|
|
||||||
|
@permissionId =
|
||||||
|
@roleId =
|
||||||
|
@roleCode = TEST_ROLE
|
||||||
|
@roleNameLangId = role.test.name
|
||||||
|
@roleDescription = 测试角色
|
||||||
|
@childRoleId =
|
||||||
|
@authorizedUserId =
|
||||||
|
@moduleCode = CORE
|
||||||
|
|
||||||
|
### 分页查询角色
|
||||||
|
### 权限:需要 ADMIN 角色 + ROLE_READ 权限
|
||||||
|
### 前置条件:需要先登录并回填 `user_token`
|
||||||
|
### 请求参数:Page<Role>,详见 ./http/object/role.md
|
||||||
|
### 返回参数:PageResult<Role>,详见 ./http/object/role.md
|
||||||
|
POST {{baseUrl}}/user/role/list
|
||||||
|
Content-Type: application/json
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"index": 0,
|
||||||
|
"size": 16,
|
||||||
|
"likesExample": {
|
||||||
|
"moduleCode": "{{moduleCode}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
### 查询角色详情
|
||||||
|
### 权限:需要 ADMIN 角色 + ROLE_READ 权限
|
||||||
|
### 前置条件:需要先登录并回填 `user_token`,URL 参数 `id` 必须传有效角色 ID
|
||||||
|
### 请求参数:无请求体,URL 参数:`id` 为角色 ID
|
||||||
|
### 返回参数:Role,详见 ./http/object/role.md
|
||||||
|
POST {{baseUrl}}/user/role/detail?id={{roleId}}
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
### 创建角色
|
||||||
|
### 权限:需要 ADMIN 角色 + ROLE_CREATE 权限
|
||||||
|
### 前置条件:需要先登录并回填 `user_token`
|
||||||
|
### 请求参数:Role,详见 ./http/object/role.md
|
||||||
|
### 返回参数:无
|
||||||
|
POST {{baseUrl}}/user/role/create
|
||||||
|
Content-Type: application/json
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"moduleCode": "{{moduleCode}}",
|
||||||
|
"code": "{{roleCode}}",
|
||||||
|
"nameLangId": "{{roleNameLangId}}",
|
||||||
|
"description": "{{roleDescription}}",
|
||||||
|
"permissionIdList": ["{{permissionId}}"],
|
||||||
|
"childRoleIdList": []
|
||||||
|
}
|
||||||
|
|
||||||
|
### 更新角色
|
||||||
|
### 权限:需要 ADMIN 角色 + ROLE_UPDATE 权限
|
||||||
|
### 前置条件:需要先登录并回填 `user_token`,`id` 必须存在
|
||||||
|
### 请求参数:Role,详见 ./http/object/role.md
|
||||||
|
### 返回参数:无
|
||||||
|
POST {{baseUrl}}/user/role/update
|
||||||
|
Content-Type: application/json
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"id": "{{roleId}}",
|
||||||
|
"moduleCode": "{{moduleCode}}",
|
||||||
|
"code": "{{roleCode}}",
|
||||||
|
"nameLangId": "{{roleNameLangId}}",
|
||||||
|
"description": "测试角色-已更新",
|
||||||
|
"permissionIdList": ["{{permissionId}}"],
|
||||||
|
"childRoleIdList": ["{{childRoleId}}"]
|
||||||
|
}
|
||||||
|
|
||||||
|
### 删除角色
|
||||||
|
### 权限:需要 ADMIN 角色 + ROLE_DELETE 权限
|
||||||
|
### 前置条件:需要先登录并回填 `user_token`,URL 参数 `id` 必须传有效角色 ID
|
||||||
|
### 请求参数:无请求体,URL 参数:`id` 为角色 ID
|
||||||
|
### 返回参数:无
|
||||||
|
POST {{baseUrl}}/user/role/delete?id={{roleId}}
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
### 查询用户角色
|
||||||
|
### 权限:需要 ADMIN 角色 + USER_ROLE_READ 权限
|
||||||
|
### 前置条件:需要先登录并回填 `user_token`,URL 参数 `userId` 必须传有效用户 ID
|
||||||
|
### 请求参数:无请求体,URL 参数:`userId` 为用户 ID
|
||||||
|
### 返回参数:List<Role>,详见 ./http/object/role.md
|
||||||
|
POST {{baseUrl}}/user/role/authorized/list?userId={{authorizedUserId}}
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
### 设置用户角色
|
||||||
|
### 权限:需要 ADMIN 角色 + 同时具备 USER_ROLE_CREATE 和 USER_ROLE_DELETE 权限
|
||||||
|
### 前置条件:需要先登录并回填 `user_token`
|
||||||
|
### 请求参数:UserRoleAuthorizeReq,详见 ./http/object/role.md
|
||||||
|
### 返回参数:无
|
||||||
|
POST {{baseUrl}}/user/role/authorized/create
|
||||||
|
Content-Type: application/json
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"userId": "{{authorizedUserId}}",
|
||||||
|
"moduleCode": "{{moduleCode}}",
|
||||||
|
"roleIdList": ["{{roleId}}"],
|
||||||
|
"roleCodeList": ["{{roleCode}}"]
|
||||||
|
}
|
||||||
|
|
||||||
|
### 查询用户已授权权限
|
||||||
|
### 权限:需要 ADMIN 角色 + USER_ROLE_READ 权限
|
||||||
|
### 前置条件:需要先登录并回填 `user_token`,URL 参数 `userId` 必须传有效用户 ID
|
||||||
|
### 请求参数:无请求体,URL 参数:`userId` 为用户 ID
|
||||||
|
### 返回参数:List<Permission>,详见 ./http/object/permission.md
|
||||||
|
POST {{baseUrl}}/user/role/authorized/permission?userId={{authorizedUserId}}
|
||||||
|
Token: {{user_token}}
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
### UserController 接口测试
|
||||||
|
### 在 IDEA 中选择 dev 或 prod 环境后执行
|
||||||
|
|
||||||
|
@userId =
|
||||||
|
@name = System
|
||||||
|
@password = u#HA027xq$i9]P}uB]LweJspTb!3oS^.
|
||||||
|
@newPassword = qwe123
|
||||||
|
@nick = 测试用户
|
||||||
|
@email = test@example.com
|
||||||
|
|
||||||
|
### 分页查询用户
|
||||||
|
### 权限:需要 ADMIN 角色 + USER_READ 权限
|
||||||
|
### 前置条件:需要先登录并回填 `user_token`
|
||||||
|
### 请求参数:Page<User>,详见 ./http/object/user.md
|
||||||
|
### 返回参数:PageResult<User>,详见 ./http/object/user.md
|
||||||
|
POST {{baseUrl}}/user/list
|
||||||
|
Content-Type: application/json
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"index": 0,
|
||||||
|
"size": 16
|
||||||
|
}
|
||||||
|
|
||||||
|
### 查询用户详情
|
||||||
|
### 权限:需要 ADMIN 角色 + USER_READ 权限
|
||||||
|
### 前置条件:需要先登录并回填 `user_token`,URL 参数 `id` 必须传有效用户 ID
|
||||||
|
### 请求参数:无请求体,URL 参数:`id` 为用户 ID
|
||||||
|
### 返回参数:User,详见 ./http/object/user.md
|
||||||
|
POST {{baseUrl}}/user/detail?id={{userId}}
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
### 创建用户
|
||||||
|
### 权限:需要 ADMIN 角色 + USER_CREATE 权限
|
||||||
|
### 前置条件:需要先登录并回填 `user_token`,重复用户名通常会失败
|
||||||
|
### 请求参数:User,详见 ./http/object/user.md
|
||||||
|
### 返回参数:无
|
||||||
|
POST {{baseUrl}}/user/create
|
||||||
|
Content-Type: application/json
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "{{name}}",
|
||||||
|
"nick": "{{nick}}",
|
||||||
|
"email": "{{email}}",
|
||||||
|
"phoneNo": "13800138000",
|
||||||
|
"password": "{{password}}"
|
||||||
|
}
|
||||||
|
|
||||||
|
### 更新用户
|
||||||
|
### 权限:需要 ADMIN 角色 + USER_UPDATE 权限
|
||||||
|
### 前置条件:需要先登录并回填 `user_token`,`id` 必须存在
|
||||||
|
### 请求参数:User,详见 ./http/object/user.md
|
||||||
|
### 返回参数:无
|
||||||
|
POST {{baseUrl}}/user/update
|
||||||
|
Content-Type: application/json
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"id": "{{userId}}",
|
||||||
|
"name": "{{name}}",
|
||||||
|
"nick": "{{nick}}",
|
||||||
|
"email": "{{email}}",
|
||||||
|
"phoneNo": "13800138000"
|
||||||
|
}
|
||||||
|
|
||||||
|
### 删除用户
|
||||||
|
### 权限:需要 ADMIN 角色
|
||||||
|
### 前置条件:需要先登录并回填 `user_token`,URL 参数 `id` 必须传有效用户 ID
|
||||||
|
### 请求参数:无请求体,URL 参数:`id` 为用户 ID
|
||||||
|
### 返回参数:无
|
||||||
|
POST {{baseUrl}}/user/delete?id={{userId}}
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
### 注册
|
||||||
|
### 权限:无需登录
|
||||||
|
### 前置条件:重复用户名通常会失败
|
||||||
|
### 请求参数:User,详见 ./http/object/user.md
|
||||||
|
### 返回参数:LoginResponse,详见 ./http/object/user.md
|
||||||
|
POST {{baseUrl}}/user/register
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "Test",
|
||||||
|
"nick": "测试用户",
|
||||||
|
"phoneNo": "13800138000",
|
||||||
|
"password": "qwe123"
|
||||||
|
}
|
||||||
|
|
||||||
|
### 登录
|
||||||
|
### 权限:无需登录
|
||||||
|
### 前置条件:成功后需要把响应中的 token 回填到 `user_token`
|
||||||
|
### 请求参数:LoginRequest,详见 ./http/object/user.md
|
||||||
|
### 返回参数:LoginResponse,详见 ./http/object/user.md
|
||||||
|
POST {{baseUrl}}/user/login
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"user": "{{name}}",
|
||||||
|
"password": "{{password}}"
|
||||||
|
}
|
||||||
|
|
||||||
|
> {%
|
||||||
|
client.global.set("user_token", response.body?.data?.token)
|
||||||
|
%}
|
||||||
|
|
||||||
|
### 令牌登录
|
||||||
|
### 权限:需要有效 Token
|
||||||
|
### 前置条件:需要先登录并回填 `user_token`
|
||||||
|
### 请求参数:无
|
||||||
|
### 返回参数:LoginResponse,详见 ./http/object/user.md
|
||||||
|
GET {{baseUrl}}/user/login/token
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
### 登出
|
||||||
|
### 权限:当前控制器未声明 RequiredToken/权限注解,按服务端实际会话处理
|
||||||
|
### 前置条件:当前接口通常用于退出当前登录态
|
||||||
|
### 请求参数:无
|
||||||
|
### 返回参数:无
|
||||||
|
POST {{baseUrl}}/user/logout
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
### 修改当前用户密码
|
||||||
|
### 权限:需要 USER_UPDATE 权限
|
||||||
|
### 前置条件:需要先登录并回填 `user_token`
|
||||||
|
### 请求参数:UpdatePasswordRequest,详见 ./http/object/user.md
|
||||||
|
### 返回参数:无
|
||||||
|
POST {{baseUrl}}/user/update/password
|
||||||
|
Content-Type: application/json
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"oldValue": "{{password}}",
|
||||||
|
"newValue": "{{newPassword}}"
|
||||||
|
}
|
||||||
|
|
||||||
|
### 修改当前用户邮箱
|
||||||
|
### 权限:需要 USER_UPDATE 权限
|
||||||
|
### 前置条件:当前方法体为空,接口尚未实现
|
||||||
|
### 请求参数:无
|
||||||
|
### 返回参数:无
|
||||||
|
POST {{baseUrl}}/user/update/email
|
||||||
|
Content-Type: application/json
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
{}
|
||||||
|
|
||||||
|
### 发送当前用户手机号验证码
|
||||||
|
### 权限:需要 USER_UPDATE 权限
|
||||||
|
### 前置条件:需要先登录并回填 `user_token`
|
||||||
|
### 请求参数:PhoneVerifyCodeSendRequest,详见 ./http/object/user.md
|
||||||
|
### 返回参数:无
|
||||||
|
POST {{baseUrl}}/user/phone/verify/send
|
||||||
|
Content-Type: application/json
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"phoneNo": "13800138000"
|
||||||
|
}
|
||||||
|
|
||||||
|
### 验证当前用户手机号
|
||||||
|
### 权限:需要 USER_UPDATE 权限
|
||||||
|
### 前置条件:需要先调用手机号验证码发送接口获取验证码
|
||||||
|
### 请求参数:PhoneVerifyRequest,详见 ./http/object/user.md
|
||||||
|
### 返回参数:无
|
||||||
|
POST {{baseUrl}}/user/phone/verify
|
||||||
|
Content-Type: application/json
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"phoneNo": "13800138000",
|
||||||
|
"verifyCode": "123456"
|
||||||
|
}
|
||||||
|
|
||||||
|
### 注销当前用户
|
||||||
|
### 权限:需要 USER_DELETE 权限
|
||||||
|
### 前置条件:需要先登录并回填 `user_token`
|
||||||
|
### 请求参数:CancelRequest,详见 ./http/object/user.md
|
||||||
|
### 返回参数:无
|
||||||
|
POST {{baseUrl}}/user/cancel
|
||||||
|
Content-Type: application/json
|
||||||
|
Token: {{user_token}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"password": "{{password}}"
|
||||||
|
}
|
||||||
@@ -5,34 +5,134 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-parent</artifactId>
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
<version>3.4.0</version>
|
<version>3.5.11</version>
|
||||||
<relativePath/>
|
<relativePath/>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<groupId>com.imyeyu.timiserverapi</groupId>
|
<groupId>com.imyeyu.timiserverapi</groupId>
|
||||||
<artifactId>TimiServerAPI</artifactId>
|
<artifactId>TimiServerAPI</artifactId>
|
||||||
<version>1.0.0</version>
|
<version>1.0.19</version>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
<name>TimiServerAPI</name>
|
<name>TimiServerAPI</name>
|
||||||
<description>imyeyu.com API</description>
|
<description>imyeyu.com API</description>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<springboot.version>3.4.0</springboot.version>
|
<java.version>25</java.version>
|
||||||
<maven.compiler.source>21</maven.compiler.source>
|
<springboot.version>3.5.11</springboot.version>
|
||||||
<maven.compiler.multilingualField>21</maven.compiler.multilingualField>
|
<maven.compiler.source>25</maven.compiler.source>
|
||||||
|
<maven.compiler.target>25</maven.compiler.target>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<repositories>
|
<profiles>
|
||||||
<repository>
|
<profile>
|
||||||
<id>apache-maven</id>
|
<id>dev-windows</id>
|
||||||
<url>https://repo.maven.apache.org/maven2/</url>
|
<activation>
|
||||||
</repository>
|
<activeByDefault>true</activeByDefault>
|
||||||
</repositories>
|
</activation>
|
||||||
|
<properties>
|
||||||
|
<native.classifier>windows-x86_64</native.classifier>
|
||||||
|
</properties>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.bytedeco</groupId>
|
||||||
|
<artifactId>ffmpeg</artifactId>
|
||||||
|
<version>7.1.1-1.5.12</version>
|
||||||
|
<classifier>${native.classifier}</classifier>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.bytedeco</groupId>
|
||||||
|
<artifactId>opencv</artifactId>
|
||||||
|
<version>4.11.0-1.5.12</version>
|
||||||
|
<classifier>${native.classifier}</classifier>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</profile>
|
||||||
|
<profile>
|
||||||
|
<id>prod-linux</id>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.bytedeco</groupId>
|
||||||
|
<artifactId>ffmpeg</artifactId>
|
||||||
|
<version>7.1.1-1.5.12</version>
|
||||||
|
<classifier>linux-x86_64</classifier>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.bytedeco</groupId>
|
||||||
|
<artifactId>opencv</artifactId>
|
||||||
|
<version>4.11.0-1.5.12</version>
|
||||||
|
<classifier>linux-x86_64</classifier>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<excludes>
|
||||||
|
<exclude>
|
||||||
|
<groupId>org.bytedeco</groupId>
|
||||||
|
<artifactId>ffmpeg</artifactId>
|
||||||
|
<classifier>windows-x86_64</classifier>
|
||||||
|
</exclude>
|
||||||
|
<exclude>
|
||||||
|
<groupId>org.bytedeco</groupId>
|
||||||
|
<artifactId>opencv</artifactId>
|
||||||
|
<classifier>windows-x86_64</classifier>
|
||||||
|
</exclude>
|
||||||
|
</excludes>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</profile>
|
||||||
|
</profiles>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
<defaultGoal>compile</defaultGoal>
|
<defaultGoal>compile</defaultGoal>
|
||||||
<plugins>
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.14.1</version>
|
||||||
|
<configuration>
|
||||||
|
<proc>full</proc>
|
||||||
|
<annotationProcessorPaths>
|
||||||
|
<path>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>1.18.46</version>
|
||||||
|
</path>
|
||||||
|
</annotationProcessorPaths>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok-maven-plugin</artifactId>
|
||||||
|
<version>1.18.20.0</version>
|
||||||
|
<configuration>
|
||||||
|
<sourceDirectory>${project.basedir}/src/main/java</sourceDirectory>
|
||||||
|
<outputDirectory>${project.build.directory}/delombok</outputDirectory>
|
||||||
|
<addOutputDirectory>false</addOutputDirectory>
|
||||||
|
<encoding>UTF-8</encoding>
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>generate-sources</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>delombok</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>1.18.46</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
@@ -46,27 +146,73 @@
|
|||||||
</executions>
|
</executions>
|
||||||
<configuration>
|
<configuration>
|
||||||
<excludeDevtools>true</excludeDevtools>
|
<excludeDevtools>true</excludeDevtools>
|
||||||
<mainClass>com.imyeyu.server.TimiServerAPI</mainClass>
|
<mainClass>com.imyeyu.api.TimiServerAPI</mainClass>
|
||||||
<finalName>${project.artifactId}</finalName>
|
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>apache-maven</id>
|
||||||
|
<url>https://repo.maven.apache.org/maven2/</url>
|
||||||
|
</repository>
|
||||||
|
<repository>
|
||||||
|
<id>timi_nexus</id>
|
||||||
|
<url>https://nexus.imyeyu.com/repository/maven-public/</url>
|
||||||
|
<releases>
|
||||||
|
<enabled>true</enabled>
|
||||||
|
</releases>
|
||||||
|
<snapshots>
|
||||||
|
<enabled>true</enabled>
|
||||||
|
</snapshots>
|
||||||
|
</repository>
|
||||||
|
<repository>
|
||||||
|
<id>e2ins_nexus</id>
|
||||||
|
<url>https://nexus.e2ins.com/repository/maven-public/</url>
|
||||||
|
<releases>
|
||||||
|
<enabled>true</enabled>
|
||||||
|
</releases>
|
||||||
|
<snapshots>
|
||||||
|
<enabled>true</enabled>
|
||||||
|
</snapshots>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.imyeyu.spring</groupId>
|
<groupId>com.imyeyu.spring</groupId>
|
||||||
<artifactId>timi-spring</artifactId>
|
<artifactId>timi-spring</artifactId>
|
||||||
<version>0.0.1</version>
|
<version>0.0.19</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.imyeyu.network</groupId>
|
<groupId>com.imyeyu.network</groupId>
|
||||||
<artifactId>timi-network</artifactId>
|
<artifactId>timi-network</artifactId>
|
||||||
<version>0.0.1</version>
|
<version>0.0.14</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.imyeyu.lang</groupId>
|
<groupId>com.imyeyu.lang</groupId>
|
||||||
<artifactId>timi-lang</artifactId>
|
<artifactId>timi-lang</artifactId>
|
||||||
|
<version>0.0.5</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.imyeyu.java</groupId>
|
||||||
|
<artifactId>timi-java</artifactId>
|
||||||
|
<version>0.0.7</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.imyeyu.utils</groupId>
|
||||||
|
<artifactId>timi-utils</artifactId>
|
||||||
|
<version>0.0.13</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>me.majiajie</groupId>
|
||||||
|
<artifactId>tinypinyin</artifactId>
|
||||||
|
<version>2.0.3</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.imyeyu.font.icon</groupId>
|
||||||
|
<artifactId>timi-icon-font</artifactId>
|
||||||
<version>0.0.1</version>
|
<version>0.0.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
@@ -86,11 +232,37 @@
|
|||||||
<artifactId>mariadb-java-client</artifactId>
|
<artifactId>mariadb-java-client</artifactId>
|
||||||
<version>3.5.3</version>
|
<version>3.5.3</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.flywaydb</groupId>
|
||||||
|
<artifactId>flyway-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.flywaydb</groupId>
|
||||||
|
<artifactId>flyway-mysql</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.e2ins.ms.notify</groupId>
|
||||||
|
<artifactId>e2ins-msg-notify-service-sdk</artifactId>
|
||||||
|
<version>0.0.9</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.tencentcloudapi</groupId>
|
||||||
|
<artifactId>tencentcloud-sdk-java-sms</artifactId>
|
||||||
|
<version>3.1.1281</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-mail</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-freemarker</artifactId>
|
<artifactId>spring-boot-starter-freemarker</artifactId>
|
||||||
<version>${springboot.version}</version>
|
<version>${springboot.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||||
|
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-data-mongodb</artifactId>
|
<artifactId>spring-boot-starter-data-mongodb</artifactId>
|
||||||
@@ -113,18 +285,18 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.projectlombok</groupId>
|
<groupId>org.projectlombok</groupId>
|
||||||
<artifactId>lombok</artifactId>
|
<artifactId>lombok</artifactId>
|
||||||
<version>1.18.34</version>
|
<version>1.18.46</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.jgit</groupId>
|
<groupId>org.eclipse.jgit</groupId>
|
||||||
<artifactId>org.eclipse.jgit</artifactId>
|
<artifactId>org.eclipse.jgit</artifactId>
|
||||||
<version>6.7.0.202309050840-r</version>
|
<version>7.2.1.202505142326-r</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.jgit</groupId>
|
<groupId>org.eclipse.jgit</groupId>
|
||||||
<artifactId>org.eclipse.jgit.archive</artifactId>
|
<artifactId>org.eclipse.jgit.archive</artifactId>
|
||||||
<version>6.7.0.202309050840-r</version>
|
<version>7.2.1.202505142326-r</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.commons</groupId>
|
<groupId>org.apache.commons</groupId>
|
||||||
@@ -155,7 +327,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.tika</groupId>
|
<groupId>org.apache.tika</groupId>
|
||||||
<artifactId>tika-core</artifactId>
|
<artifactId>tika-core</artifactId>
|
||||||
<version>2.9.2</version>
|
<version>3.2.2</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jcodec</groupId>
|
<groupId>org.jcodec</groupId>
|
||||||
@@ -167,15 +339,43 @@
|
|||||||
<artifactId>jcodec-javase</artifactId>
|
<artifactId>jcodec-javase</artifactId>
|
||||||
<version>0.2.5</version>
|
<version>0.2.5</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.bytedeco</groupId>
|
||||||
|
<artifactId>javacv</artifactId>
|
||||||
|
<version>1.5.12</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.bytedeco</groupId>
|
||||||
|
<artifactId>ffmpeg</artifactId>
|
||||||
|
<version>7.1.1-1.5.12</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.bytedeco</groupId>
|
||||||
|
<artifactId>opencv</artifactId>
|
||||||
|
<version>4.11.0-1.5.12</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
<classifier>windows-x86_64</classifier>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jsoup</groupId>
|
<groupId>org.jsoup</groupId>
|
||||||
<artifactId>jsoup</artifactId>
|
<artifactId>jsoup</artifactId>
|
||||||
<version>1.18.1</version>
|
<version>1.18.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.poi</groupId>
|
||||||
|
<artifactId>poi-ooxml</artifactId>
|
||||||
|
<version>5.4.1</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>commons-codec</groupId>
|
<groupId>commons-codec</groupId>
|
||||||
<artifactId>commons-codec</artifactId>
|
<artifactId>commons-codec</artifactId>
|
||||||
<version>1.17.0</version>
|
<version>1.17.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.jayway.jsonpath</groupId>
|
||||||
|
<artifactId>json-path</artifactId>
|
||||||
|
<version>2.9.0</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
+9
-30
@@ -1,10 +1,6 @@
|
|||||||
package com.imyeyu.server;
|
package com.imyeyu.api;
|
||||||
|
|
||||||
import com.imyeyu.io.IO;
|
import com.imyeyu.io.IO;
|
||||||
import com.imyeyu.java.TimiJava;
|
|
||||||
import com.imyeyu.java.bean.Language;
|
|
||||||
import com.imyeyu.java.ref.Ref;
|
|
||||||
import com.imyeyu.spring.TimiSpring;
|
|
||||||
import com.imyeyu.utils.OS;
|
import com.imyeyu.utils.OS;
|
||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -13,7 +9,6 @@ import org.springframework.boot.SpringApplication;
|
|||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.ApplicationContextAware;
|
import org.springframework.context.ApplicationContextAware;
|
||||||
import org.springframework.core.env.Environment;
|
|
||||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@@ -27,12 +22,10 @@ import java.io.File;
|
|||||||
* @since 2021-02-23 21:35
|
* @since 2021-02-23 21:35
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@SpringBootApplication(scanBasePackages = {"com.imyeyu.server", "com.imyeyu.spring"})
|
@SpringBootApplication(scanBasePackages = {"com.imyeyu.api", "com.imyeyu.spring"})
|
||||||
@EnableTransactionManagement
|
@EnableTransactionManagement
|
||||||
public class TimiServerAPI implements OS.FileSystem, ApplicationContextAware {
|
public class TimiServerAPI implements OS.FileSystem, ApplicationContextAware {
|
||||||
|
|
||||||
private static final String DEV_LANG_CONFIG = "dev.lang";
|
|
||||||
|
|
||||||
public static ApplicationContext applicationContext;
|
public static ApplicationContext applicationContext;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -40,32 +33,18 @@ public class TimiServerAPI implements OS.FileSystem, ApplicationContextAware {
|
|||||||
TimiServerAPI.applicationContext = applicationContext;
|
TimiServerAPI.applicationContext = applicationContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Language getUserLanguage() {
|
|
||||||
Language userLanguage = TimiSpring.getLanguage();
|
|
||||||
Environment env = applicationContext.getBean(Environment.class);
|
|
||||||
if (env.containsProperty(DEV_LANG_CONFIG)) {
|
|
||||||
String property = env.getProperty(DEV_LANG_CONFIG);
|
|
||||||
if (TimiJava.isNotEmpty(property)) {
|
|
||||||
userLanguage = Ref.toType(Language.class, property);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return userLanguage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
try {
|
try {
|
||||||
{
|
{
|
||||||
// 导出配置
|
File application = new File("config" + SEP + "application.yml");
|
||||||
String[] files = {"application.yml", "logback.xml"};
|
if (!application.exists()) {
|
||||||
for (int i = 0; i < files.length; i++) {
|
IO.resourceToDisk(TimiServerAPI.class, "application_export.yml", application.getAbsolutePath());
|
||||||
File file = new File("config" + SEP + files[i]);
|
}
|
||||||
if (!file.exists() || !file.isFile()) {
|
File logback = new File("config" + SEP + "logback.xml");
|
||||||
log.info("exporting default config at {}", file.getAbsolutePath());
|
if (!logback.exists()) {
|
||||||
IO.resourceToDisk(TimiServerAPI.class, files[i], file.getAbsolutePath());
|
IO.resourceToDisk(TimiServerAPI.class, "logback.xml", logback.getAbsolutePath());
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 启动 SpringBoot
|
// 启动 SpringBoot
|
||||||
SpringApplication.run(TimiServerAPI.class, args);
|
SpringApplication.run(TimiServerAPI.class, args);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package com.imyeyu.api.annotation;
|
||||||
|
|
||||||
|
import com.imyeyu.api.util.CaptchaManager;
|
||||||
|
import com.imyeyu.spring.annotation.CaptchaValidAbstractInterceptor;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.aspectj.lang.annotation.Aspect;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图形验证码校验注解处理器
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2023-07-15 10:01
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Aspect
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class CaptchaValidInterceptor extends CaptchaValidAbstractInterceptor {
|
||||||
|
|
||||||
|
@Value("${spring.profiles.active}")
|
||||||
|
private String env;
|
||||||
|
|
||||||
|
private final CaptchaManager captchaManager;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void verify(String captchaId, String captcha) {
|
||||||
|
if (!env.equals("prod")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
captchaManager.verify(captchaId, captcha);
|
||||||
|
}
|
||||||
|
}
|
||||||
+15
-5
@@ -1,6 +1,4 @@
|
|||||||
package com.imyeyu.server.annotation;
|
package com.imyeyu.api.annotation;
|
||||||
|
|
||||||
import com.imyeyu.server.modules.common.bean.SettingKey;
|
|
||||||
|
|
||||||
import java.lang.annotation.ElementType;
|
import java.lang.annotation.ElementType;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
@@ -17,8 +15,20 @@ import java.lang.annotation.Target;
|
|||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
public @interface EnableSetting {
|
public @interface EnableSetting {
|
||||||
|
|
||||||
/** @return 配置键 */
|
/**
|
||||||
SettingKey value();
|
*
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-04-27 14:45
|
||||||
|
*/
|
||||||
|
enum Logic {
|
||||||
|
|
||||||
|
AND,
|
||||||
|
|
||||||
|
OR
|
||||||
|
}
|
||||||
|
|
||||||
|
Logic logic() default Logic.AND;
|
||||||
|
|
||||||
/** @return 未启用配置时响应消息语言映射键 */
|
/** @return 未启用配置时响应消息语言映射键 */
|
||||||
String message() default "service.offline";
|
String message() default "service.offline";
|
||||||
+23
-8
@@ -1,17 +1,20 @@
|
|||||||
package com.imyeyu.server.annotation;
|
package com.imyeyu.api.annotation;
|
||||||
|
|
||||||
|
import com.imyeyu.api.modules.common.service.SettingService;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import com.imyeyu.java.bean.timi.TimiCode;
|
|
||||||
import com.imyeyu.java.bean.timi.TimiException;
|
|
||||||
import com.imyeyu.server.modules.common.service.SettingService;
|
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.lang.NonNull;
|
import org.springframework.lang.NonNull;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.web.method.HandlerMethod;
|
import org.springframework.web.method.HandlerMethod;
|
||||||
import org.springframework.web.servlet.HandlerInterceptor;
|
import org.springframework.web.servlet.HandlerInterceptor;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 启用配置注解处理器
|
* 启用配置注解处理器
|
||||||
*
|
*
|
||||||
@@ -24,16 +27,28 @@ public class EnableSettingInterceptor implements HandlerInterceptor {
|
|||||||
|
|
||||||
private final SettingService service;
|
private final SettingService service;
|
||||||
|
|
||||||
public boolean preHandle(@NonNull HttpServletRequest req, @NonNull HttpServletResponse resp, @NonNull Object handler) {
|
public boolean preHandle(@NonNull HttpServletRequest req, @NonNull HttpServletResponse resp, @NonNull Object handler) throws InvocationTargetException, IllegalAccessException {
|
||||||
if (handler instanceof HandlerMethod handlerMethod) {
|
if (handler instanceof HandlerMethod handlerMethod) {
|
||||||
EnableSetting annotation = handlerMethod.getMethodAnnotation(EnableSetting.class);
|
EnableSetting annotation = handlerMethod.getMethodAnnotation(EnableSetting.class);
|
||||||
if (annotation == null) {
|
if (annotation == null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (service.is(annotation.value())) {
|
EnableSetting.Logic logic = annotation.logic();
|
||||||
return true;
|
List<Boolean> valueList = new ArrayList<>();
|
||||||
|
for (Method method : annotation.getClass().getMethods()) {
|
||||||
|
if (method.getName().equals("message")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Enum<?>[] enums = (Enum<?>[]) method.invoke(annotation);
|
||||||
|
for (Enum<?> anEnum : enums) {
|
||||||
|
valueList.add(service.getSystem(anEnum).isTrue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (logic == EnableSetting.Logic.AND) {
|
||||||
|
return valueList.stream().allMatch(Boolean::booleanValue);
|
||||||
|
} else {
|
||||||
|
return valueList.stream().anyMatch(Boolean::booleanValue);
|
||||||
}
|
}
|
||||||
throw new TimiException(TimiCode.ERROR_SERVICE_OFF, annotation.message());
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.imyeyu.api.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标记需要在响应阶段自动回填的多语言字段
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-07-17 00:00
|
||||||
|
*/
|
||||||
|
@Target({ElementType.FIELD})
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface MultilingualField {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 多语言 ID 来源字段名
|
||||||
|
*
|
||||||
|
* @return 来源字段名
|
||||||
|
*/
|
||||||
|
String value();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模板参数来源字段名
|
||||||
|
*
|
||||||
|
* @return 同类中的 Map 字段名
|
||||||
|
*/
|
||||||
|
String argMap() default "";
|
||||||
|
}
|
||||||
+14
-8
@@ -1,4 +1,4 @@
|
|||||||
package com.imyeyu.server.annotation;
|
package com.imyeyu.api.annotation;
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
@@ -23,27 +23,33 @@ import org.springframework.stereotype.Component;
|
|||||||
@Component
|
@Component
|
||||||
public class RequestRateLimitInterceptor extends RequestRateLimitAbstractInterceptor {
|
public class RequestRateLimitInterceptor extends RequestRateLimitAbstractInterceptor {
|
||||||
|
|
||||||
|
private static final String KEY_PREFIX = "REQUEST:RATE_LIMIT:";
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@Qualifier("redisRateLimit")
|
@Qualifier("redisRateLimit")
|
||||||
private Redis<String, Integer> redisRequestRateLimit;
|
private Redis<String, Integer> redis;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean beforeRun(HttpServletRequest req, HttpServletResponse resp, String id, int cycle, int limit) {
|
public boolean beforeRun(HttpServletRequest req, HttpServletResponse resp, String id, int cycle, int limit) {
|
||||||
// 键
|
// 键
|
||||||
String key = "TimiServerAPI." + TimiSpring.getRequestIP() + "." + id;
|
String key = getKey(id);
|
||||||
if (redisRequestRateLimit.has(key)) {
|
if (redis.has(key)) {
|
||||||
Integer count = redisRequestRateLimit.get(key);
|
Integer count = redis.get(key);
|
||||||
if (count != null) {
|
if (count != null) {
|
||||||
if (count < limit) {
|
if (count < limit) {
|
||||||
redisRequestRateLimit.setAndKeepTTL(key, ++count);
|
redis.setAndKeepTTL(key, ++count);
|
||||||
} else {
|
} else {
|
||||||
log.warn("请求频率过高:[" + key + "].C" + count + "L" + limit);
|
log.warn("请求频率过高:[" + key + "].C" + count + "L" + limit);
|
||||||
throw new TimiException(TimiCode.REQUEST_BAD).msgKey("request_rate_limit");
|
throw new TimiException(TimiCode.REQUEST_BAD, "request_rate_limit");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
redisRequestRateLimit.set(key, 0, Time.S * cycle);
|
redis.set(key, 0, Time.S * cycle);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getKey(String id) {
|
||||||
|
return "%sTIMISERVERAPI:%s:%s".formatted(KEY_PREFIX, TimiSpring.getRequestIP(), id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.imyeyu.api.annotation;
|
||||||
|
|
||||||
|
import com.imyeyu.api.bean.PermissionMatchMode;
|
||||||
|
import com.imyeyu.api.bean.CorePermissionCode;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// 要求 CORE 模块权限
|
||||||
|
///
|
||||||
|
/// @author Codex
|
||||||
|
/// @since 2026-07-26
|
||||||
|
@Target({ElementType.METHOD, ElementType.TYPE})
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface RequireCorePermission {
|
||||||
|
|
||||||
|
/// 所需权限
|
||||||
|
///
|
||||||
|
/// @return 权限列表
|
||||||
|
CorePermissionCode[] value();
|
||||||
|
|
||||||
|
/// 匹配模式
|
||||||
|
///
|
||||||
|
/// @return 匹配模式
|
||||||
|
PermissionMatchMode mode() default PermissionMatchMode.ALL;
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package com.imyeyu.api.annotation;
|
||||||
|
|
||||||
|
import com.imyeyu.api.bean.PermissionMatchMode;
|
||||||
|
import com.imyeyu.api.bean.CorePermissionCode;
|
||||||
|
import com.imyeyu.api.bean.ModuleCode;
|
||||||
|
import com.imyeyu.api.modules.user.service.PermissionChecker;
|
||||||
|
import com.imyeyu.java.TimiJava;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import org.springframework.lang.NonNull;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.method.HandlerMethod;
|
||||||
|
import org.springframework.web.servlet.HandlerInterceptor;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// CORE 权限注解处理器
|
||||||
|
///
|
||||||
|
/// @author Codex
|
||||||
|
/// @since 2026-07-27
|
||||||
|
@Component
|
||||||
|
public class RequireCorePermissionInterceptor implements HandlerInterceptor {
|
||||||
|
|
||||||
|
private final PermissionChecker permissionChecker;
|
||||||
|
|
||||||
|
public RequireCorePermissionInterceptor(PermissionChecker permissionChecker) {
|
||||||
|
this.permissionChecker = permissionChecker;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean preHandle(@NonNull HttpServletRequest req, @NonNull HttpServletResponse resp, @NonNull Object handler) {
|
||||||
|
if (!(handler instanceof HandlerMethod handlerMethod)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
RequireCorePermission annotation = handlerMethod.getMethodAnnotation(RequireCorePermission.class);
|
||||||
|
if (annotation == null || TimiJava.isEmpty(annotation.value())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
String[] permissionCodeList = java.util.Arrays.stream(annotation.value()).map(CorePermissionCode::getValue).toArray(String[]::new);
|
||||||
|
if (annotation.mode() == PermissionMatchMode.ANY) {
|
||||||
|
permissionChecker.checkAny(ModuleCode.CORE, permissionCodeList);
|
||||||
|
} else {
|
||||||
|
permissionChecker.checkAll(ModuleCode.CORE, permissionCodeList);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
package com.imyeyu.api.bean;
|
||||||
|
|
||||||
|
import com.imyeyu.api.modules.user.bean.BuiltinPermissionCode;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// CORE 模块权限枚举
|
||||||
|
///
|
||||||
|
/// @author Codex
|
||||||
|
/// @since 2026-07-26
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum CorePermissionCode implements BuiltinPermissionCode {
|
||||||
|
|
||||||
|
PERMISSION_CREATE("PERMISSION:CREATE", "权限创建", false, true),
|
||||||
|
PERMISSION_READ("PERMISSION:READ", "权限读取", false, true),
|
||||||
|
PERMISSION_UPDATE("PERMISSION:UPDATE", "权限修改", false, true),
|
||||||
|
PERMISSION_DELETE("PERMISSION:DELETE", "权限删除", false, true),
|
||||||
|
|
||||||
|
ROLE_CREATE("ROLE:CREATE", "角色创建", false, true),
|
||||||
|
ROLE_READ("ROLE:READ", "角色读取", false, true),
|
||||||
|
ROLE_UPDATE("ROLE:UPDATE", "角色修改", false, true),
|
||||||
|
ROLE_DELETE("ROLE:DELETE", "角色删除", false, true),
|
||||||
|
|
||||||
|
ROLE_PERMISSION_CREATE("ROLE_PERMISSION:CREATE", "角色权限创建", false, true),
|
||||||
|
ROLE_PERMISSION_READ("ROLE_PERMISSION:READ", "角色权限读取", false, true),
|
||||||
|
ROLE_PERMISSION_DELETE("ROLE_PERMISSION:DELETE", "角色权限删除", false, true),
|
||||||
|
|
||||||
|
ROLE_PERMISSION_INHERIT_CREATE("ROLE_PERMISSION_INHERIT:CREATE", "角色权限继承创建", false, true),
|
||||||
|
ROLE_PERMISSION_INHERIT_READ("ROLE_PERMISSION_INHERIT:READ", "角色权限继承读取", false, true),
|
||||||
|
ROLE_PERMISSION_INHERIT_DELETE("ROLE_PERMISSION_INHERIT:DELETE", "角色权限继承删除", false, true),
|
||||||
|
|
||||||
|
SETTING_CREATE("SETTING:CREATE", "设置创建", false, true),
|
||||||
|
SETTING_READ("SETTING:READ", "设置读取", false, true),
|
||||||
|
SETTING_UPDATE("SETTING:UPDATE", "设置修改", false, true),
|
||||||
|
SETTING_DELETE("SETTING:DELETE", "设置删除", false, true),
|
||||||
|
|
||||||
|
MULTILINGUAL_CREATE("MULTILINGUAL:CREATE", "多语言创建", false, true),
|
||||||
|
MULTILINGUAL_READ("MULTILINGUAL:READ", "多语言读取", false, true),
|
||||||
|
MULTILINGUAL_UPDATE("MULTILINGUAL:UPDATE", "多语言修改", false, true),
|
||||||
|
MULTILINGUAL_DELETE("MULTILINGUAL:DELETE", "多语言删除", false, true),
|
||||||
|
|
||||||
|
TAG_CREATE("TAG:CREATE", "标签创建", false, true),
|
||||||
|
TAG_READ("TAG:READ", "标签读取", false, true),
|
||||||
|
TAG_APPLY("TAG:APPLY", "标签应用", false, true),
|
||||||
|
TAG_UPDATE("TAG:UPDATE", "标签修改", false, true),
|
||||||
|
TAG_DELETE("TAG:DELETE", "标签删除", false, true),
|
||||||
|
|
||||||
|
USER_CREATE("USER:CREATE", "用户创建", false, true),
|
||||||
|
USER_READ("USER:READ", "用户读取", false, true),
|
||||||
|
USER_UPDATE("USER:UPDATE", "用户修改", false, true),
|
||||||
|
USER_DELETE("USER:DELETE", "用户删除", false, true),
|
||||||
|
|
||||||
|
FEEDBACK_CREATE("FEEDBACK:CREATE", "反馈创建", false, true),
|
||||||
|
FEEDBACK_READ("FEEDBACK:READ", "反馈读取", false, true),
|
||||||
|
FEEDBACK_UPDATE("FEEDBACK:UPDATE", "反馈修改", false, true),
|
||||||
|
FEEDBACK_DELETE("FEEDBACK:DELETE", "反馈删除", false, true),
|
||||||
|
|
||||||
|
NOTIFY_CREATE("NOTIFY:CREATE", "通知创建", false, true),
|
||||||
|
NOTIFY_READ("NOTIFY:READ", "通知读取", false, true),
|
||||||
|
NOTIFY_DELETE("NOTIFY:DELETE", "通知删除", false, true),
|
||||||
|
NOTIFY_CANCEL("NOTIFY:CANCEL", "通知取消", false, true),
|
||||||
|
|
||||||
|
USER_ROLE_CREATE("USER_ROLE:CREATE", "用户角色创建", true, true),
|
||||||
|
USER_ROLE_READ("USER_ROLE:READ", "用户角色读取", true, true),
|
||||||
|
USER_ROLE_DELETE("USER_ROLE:DELETE", "用户角色删除", true, true);
|
||||||
|
|
||||||
|
final String value;
|
||||||
|
final String defaultName;
|
||||||
|
final boolean grantToSystem;
|
||||||
|
final boolean grantToAdmin;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ModuleCode getModuleCode() {
|
||||||
|
return ModuleCode.CORE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDefaultName() {
|
||||||
|
return defaultName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isGrantToSystem() {
|
||||||
|
return grantToSystem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isGrantToAdmin() {
|
||||||
|
return grantToAdmin;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package com.imyeyu.api.bean;
|
||||||
|
|
||||||
|
import com.imyeyu.api.modules.user.bean.BuiltinRoleCode;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// CORE 模块角色枚举
|
||||||
|
///
|
||||||
|
/// @author Codex
|
||||||
|
/// @since 2026-07-26
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum CoreRoleCode implements BuiltinRoleCode {
|
||||||
|
|
||||||
|
SYSTEM("系统", true, null),
|
||||||
|
|
||||||
|
ADMIN("管理员", false, SYSTEM.name());
|
||||||
|
|
||||||
|
final String defaultName;
|
||||||
|
final boolean protectedRole;
|
||||||
|
final String parentCode;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ModuleCode getModuleCode() {
|
||||||
|
return ModuleCode.CORE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDefaultName() {
|
||||||
|
return defaultName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isProtected() {
|
||||||
|
return protectedRole;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getParentCode() {
|
||||||
|
return parentCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.imyeyu.api.bean;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// 模块代码
|
||||||
|
///
|
||||||
|
/// @author Codex
|
||||||
|
/// @since 2026-07-26
|
||||||
|
public enum ModuleCode {
|
||||||
|
|
||||||
|
CORE,
|
||||||
|
|
||||||
|
GAO,
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.imyeyu.api.bean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 权限匹配模式
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-06-05 17:45
|
||||||
|
*/
|
||||||
|
public enum PermissionMatchMode {
|
||||||
|
|
||||||
|
/** 全部满足 */
|
||||||
|
ALL,
|
||||||
|
|
||||||
|
/** 满足任意一个 */
|
||||||
|
ANY,
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.imyeyu.api.bean;
|
||||||
|
|
||||||
|
import com.imyeyu.spring.bean.Page;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2025-12-12 23:07
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class PreviewPage<T> extends Page<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2025-12-12 23:09
|
||||||
|
*/
|
||||||
|
public enum Type {
|
||||||
|
|
||||||
|
NORMAL,
|
||||||
|
|
||||||
|
PREVIEW
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Type type;
|
||||||
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.imyeyu.server.bean;
|
package com.imyeyu.api.bean;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import com.imyeyu.utils.OS;
|
import com.imyeyu.utils.OS;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.imyeyu.api.bean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 角色匹配模式
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-06-05 17:45
|
||||||
|
*/
|
||||||
|
public enum RoleMatchMode {
|
||||||
|
|
||||||
|
/** 全部满足 */
|
||||||
|
ALL,
|
||||||
|
|
||||||
|
/** 满足任意一个 */
|
||||||
|
ANY,
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.imyeyu.api.bean.wechat;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2025-09-27 11:33
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class InitCodeResponse {
|
||||||
|
|
||||||
|
private String session_key;
|
||||||
|
|
||||||
|
private String openid;
|
||||||
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.imyeyu.server.config;
|
package com.imyeyu.api.config;
|
||||||
|
|
||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package com.imyeyu.api.config;
|
||||||
|
|
||||||
|
import com.e2ins.ms.notify.SmsNotifyService;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||||
|
import com.fasterxml.jackson.databind.MapperFeature;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
|
||||||
|
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
|
||||||
|
import com.fasterxml.jackson.databind.json.JsonMapper;
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||||
|
import com.imyeyu.utils.Time;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.yaml.snakeyaml.Yaml;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2025-05-16 18:53
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class BeanConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ObjectMapper jackson() {
|
||||||
|
return JsonMapper.builder()
|
||||||
|
.addModule(new JavaTimeModule())
|
||||||
|
.serializationInclusion(JsonInclude.Include.NON_NULL)
|
||||||
|
.defaultDateFormat(Time.dateTime)
|
||||||
|
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
|
||||||
|
.enable(MapperFeature.USE_STD_BEAN_NAMING)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SmsNotifyService smsNotifyService() {
|
||||||
|
// TODO
|
||||||
|
return new SmsNotifyService(null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Yaml yaml() {
|
||||||
|
return new Yaml();
|
||||||
|
}
|
||||||
|
}
|
||||||
+4
-13
@@ -1,4 +1,4 @@
|
|||||||
package com.imyeyu.server.config;
|
package com.imyeyu.api.config;
|
||||||
|
|
||||||
import jakarta.servlet.Filter;
|
import jakarta.servlet.Filter;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
@@ -29,21 +29,12 @@ public class CORSConfig {
|
|||||||
/** 允许跨域的地址 */
|
/** 允许跨域的地址 */
|
||||||
private String[] allowOrigin;
|
private String[] allowOrigin;
|
||||||
|
|
||||||
/** 是否允许请求带有验证信息 */
|
|
||||||
private boolean allowCredentials;
|
|
||||||
|
|
||||||
/** 允许请求的方法 */
|
|
||||||
private String allowMethods;
|
|
||||||
|
|
||||||
/** 允许服务端访问的客户端请求头 */
|
|
||||||
private String allowHeaders;
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public FilterRegistrationBean<Filter> corsFilter() {
|
public FilterRegistrationBean<Filter> corsFilter() {
|
||||||
CorsConfiguration config = new CorsConfiguration();
|
CorsConfiguration config = new CorsConfiguration();
|
||||||
config.addAllowedHeader(allowHeaders);
|
config.addAllowedHeader("*");
|
||||||
config.addAllowedMethod(allowMethods);
|
config.addAllowedMethod("*");
|
||||||
config.setAllowCredentials(allowCredentials);
|
config.setAllowCredentials(true);
|
||||||
config.setAllowedOriginPatterns(Arrays.asList(allowOrigin));
|
config.setAllowedOriginPatterns(Arrays.asList(allowOrigin));
|
||||||
|
|
||||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.imyeyu.server.config;
|
package com.imyeyu.api.config;
|
||||||
|
|
||||||
import com.mongodb.client.MongoClient;
|
import com.mongodb.client.MongoClient;
|
||||||
import com.mongodb.client.gridfs.GridFSBucket;
|
import com.mongodb.client.gridfs.GridFSBucket;
|
||||||
+65
-121
@@ -1,14 +1,18 @@
|
|||||||
package com.imyeyu.server.config;
|
package com.imyeyu.api.config;
|
||||||
|
|
||||||
import lombok.Data;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import lombok.EqualsAndHashCode;
|
import com.imyeyu.api.modules.common.entity.Multilingual;
|
||||||
import com.imyeyu.server.modules.blog.entity.ArticleRanking;
|
import com.imyeyu.api.modules.gao.bean.GaoUserInviteCache;
|
||||||
import com.imyeyu.server.modules.common.entity.Multilingual;
|
|
||||||
import com.imyeyu.spring.bean.RedisConfigParams;
|
import com.imyeyu.spring.bean.RedisConfigParams;
|
||||||
import com.imyeyu.spring.config.AbstractRedisConfig;
|
import com.imyeyu.spring.config.AbstractRedisConfig;
|
||||||
import com.imyeyu.spring.util.Redis;
|
import com.imyeyu.spring.util.Redis;
|
||||||
import com.imyeyu.spring.util.RedisSerializers;
|
import com.imyeyu.spring.util.RedisSerializers;
|
||||||
|
import com.imyeyu.utils.Time;
|
||||||
|
import io.lettuce.core.api.StatefulConnection;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
|
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
import org.springframework.cache.interceptor.KeyGenerator;
|
import org.springframework.cache.interceptor.KeyGenerator;
|
||||||
@@ -35,6 +39,9 @@ import java.time.Duration;
|
|||||||
@ConfigurationProperties(prefix = "spring.redis")
|
@ConfigurationProperties(prefix = "spring.redis")
|
||||||
public class RedisConfig extends AbstractRedisConfig {
|
public class RedisConfig extends AbstractRedisConfig {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ObjectMapper jackson;
|
||||||
|
|
||||||
// ---------- 连接配置 ----------
|
// ---------- 连接配置 ----------
|
||||||
|
|
||||||
/** 地址 */
|
/** 地址 */
|
||||||
@@ -46,95 +53,8 @@ public class RedisConfig extends AbstractRedisConfig {
|
|||||||
/** 密码 */
|
/** 密码 */
|
||||||
private String password;
|
private String password;
|
||||||
|
|
||||||
/** 超时(毫秒) */
|
/** Redis 逻辑库 */
|
||||||
private int timeout;
|
private int database;
|
||||||
|
|
||||||
/** 连接池 */
|
|
||||||
private Lettuce lettuce;
|
|
||||||
|
|
||||||
/** 数据库 */
|
|
||||||
private Database database;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 连接池
|
|
||||||
*
|
|
||||||
* @author 夜雨
|
|
||||||
* @since 2023-08-21 16:23
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
public static class Lettuce {
|
|
||||||
|
|
||||||
/** 配置 */
|
|
||||||
private Pool pool;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 配置
|
|
||||||
*
|
|
||||||
* @author 夜雨
|
|
||||||
* @since 2023-08-21 16:23
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
public static class Pool {
|
|
||||||
|
|
||||||
/** 最大活跃连接 */
|
|
||||||
private int maxActive;
|
|
||||||
|
|
||||||
/** 最小空闲连接 */
|
|
||||||
private int minIdle;
|
|
||||||
|
|
||||||
/** 最大空闲连接 */
|
|
||||||
private int maxIdle;
|
|
||||||
|
|
||||||
/** 最大等待时间(秒) */
|
|
||||||
private int maxWait;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 数据库
|
|
||||||
*
|
|
||||||
* @author 夜雨
|
|
||||||
* @since 2023-08-21 16:25
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
public static class Database {
|
|
||||||
|
|
||||||
/** 分布式锁 */
|
|
||||||
private int locker;
|
|
||||||
|
|
||||||
/** 多语言环境 */
|
|
||||||
private int language;
|
|
||||||
|
|
||||||
/** 多语言键环境 */
|
|
||||||
private int languageMap;
|
|
||||||
|
|
||||||
/** 文章排位 */
|
|
||||||
private int articleRanking;
|
|
||||||
|
|
||||||
/** 文章阅读记录 */
|
|
||||||
private int articleRead;
|
|
||||||
|
|
||||||
/** 用户登录令牌 */
|
|
||||||
private int userToken;
|
|
||||||
|
|
||||||
/** 用户经验值标记 */
|
|
||||||
private int userExpFlag;
|
|
||||||
|
|
||||||
/** 用户邮箱验证 */
|
|
||||||
private int userEmailVerify;
|
|
||||||
|
|
||||||
/** 用户重置密码验证 */
|
|
||||||
private int userResetPWVerify;
|
|
||||||
|
|
||||||
/** 访问频率控制 */
|
|
||||||
private int rateLimit;
|
|
||||||
|
|
||||||
/** 系统配置 */
|
|
||||||
private int setting;
|
|
||||||
|
|
||||||
/** Minecraft 登录 */
|
|
||||||
private int fmcPlayerToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected RedisConfigParams configParams() {
|
protected RedisConfigParams configParams() {
|
||||||
@@ -142,22 +62,23 @@ public class RedisConfig extends AbstractRedisConfig {
|
|||||||
setHost(host);
|
setHost(host);
|
||||||
setPort(port);
|
setPort(port);
|
||||||
setPassword(password);
|
setPassword(password);
|
||||||
setTimeout(timeout);
|
setTimeout(Time.SI * 8);
|
||||||
setMaxActive(lettuce.pool.maxActive);
|
setMaxActive(8);
|
||||||
setMinIdle(lettuce.pool.minIdle);
|
setMinIdle(1);
|
||||||
setMaxIdle(lettuce.pool.maxIdle);
|
setMaxIdle(8);
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return 连接池配置 */
|
/** @return 连接池配置 */
|
||||||
@Bean
|
@Bean
|
||||||
@Override
|
@Override
|
||||||
public GenericObjectPoolConfig<?> getPoolConfig() {
|
public GenericObjectPoolConfig<StatefulConnection<?, ?>> getPoolConfig() {
|
||||||
GenericObjectPoolConfig<?> config = new GenericObjectPoolConfig<>();
|
RedisConfigParams configArgs = configParams();
|
||||||
config.setMaxTotal(lettuce.pool.maxActive);
|
GenericObjectPoolConfig<StatefulConnection<?, ?>> config = new GenericObjectPoolConfig<>();
|
||||||
config.setMinIdle(lettuce.pool.minIdle);
|
config.setMaxTotal(8);
|
||||||
config.setMaxIdle(lettuce.pool.maxIdle);
|
config.setMinIdle(configArgs.getMinIdle());
|
||||||
config.setMaxWait(Duration.ofMillis(lettuce.pool.maxWait));
|
config.setMaxIdle(configArgs.getMaxIdle());
|
||||||
|
config.setMaxWait(Duration.ofMillis(-1));
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,13 +100,13 @@ public class RedisConfig extends AbstractRedisConfig {
|
|||||||
/** @return 分布式锁, ID: 尝试加锁次数 */
|
/** @return 分布式锁, ID: 尝试加锁次数 */
|
||||||
@Bean("redisLocker")
|
@Bean("redisLocker")
|
||||||
public Redis<String, Integer> getLockerRedisTemplate() {
|
public Redis<String, Integer> getLockerRedisTemplate() {
|
||||||
return getRedis(database.locker, RedisSerializers.STRING, RedisSerializers.INTEGER);
|
return getRedis(database, RedisSerializers.STRING, RedisSerializers.INTEGER);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return 多语言环境,ID: {@link Multilingual} */
|
/** @return 多语言环境,ID: {@link Multilingual} */
|
||||||
@Bean("redisLanguage")
|
@Bean("redisLanguage")
|
||||||
public Redis<Long, Multilingual> getLanguageRedisTemplate() {
|
public Redis<String, Multilingual> getLanguageRedisTemplate() {
|
||||||
return getRedis(database.language, RedisSerializers.LONG, new RedisSerializer<>() {
|
return getRedis(database, RedisSerializers.STRING, new RedisSerializer<>() {
|
||||||
|
|
||||||
public Multilingual deserialize(@Nullable byte[] bytes) {
|
public Multilingual deserialize(@Nullable byte[] bytes) {
|
||||||
if (bytes == null || bytes.length == 0) {
|
if (bytes == null || bytes.length == 0) {
|
||||||
@@ -212,57 +133,80 @@ public class RedisConfig extends AbstractRedisConfig {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean("redisCaptcha")
|
||||||
|
public Redis<String, String> getCaptchaRedisTemplate() {
|
||||||
|
return getRedis(database, RedisSerializers.STRING, RedisSerializers.STRING);
|
||||||
|
}
|
||||||
|
|
||||||
/** @return 文章访问记录,IP: [文章 ID] */
|
/** @return 文章访问记录,IP: [文章 ID] */
|
||||||
@Bean("redisLanguageMap")
|
@Bean("redisLanguageMap")
|
||||||
public Redis<String, Long> getLanguageMapRedisTemplate() {
|
public Redis<String, Long> getLanguageMapRedisTemplate() {
|
||||||
return getRedis(database.languageMap, RedisSerializers.STRING, RedisSerializers.LONG);
|
return getRedis(database, RedisSerializers.STRING, RedisSerializers.LONG);
|
||||||
}
|
|
||||||
|
|
||||||
/** @return 文章访问统计,文章 ID: {@link ArticleRanking}(JSON) */
|
|
||||||
@Bean("redisArticleRanking")
|
|
||||||
public Redis<Long, ArticleRanking> getArticleRankingRedisTemplate() {
|
|
||||||
return getRedis(database.articleRanking, RedisSerializers.LONG, RedisSerializers.gsonSerializer(ArticleRanking.class));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return 文章访问记录,IP: [文章 ID] */
|
/** @return 文章访问记录,IP: [文章 ID] */
|
||||||
@Bean("redisArticleRead")
|
@Bean("redisArticleRead")
|
||||||
public Redis<String, Long> getArticleReadRedisTemplate() {
|
public Redis<String, Long> getArticleReadRedisTemplate() {
|
||||||
return getRedis(database.articleRead, RedisSerializers.STRING, RedisSerializers.LONG);
|
return getRedis(database, RedisSerializers.STRING, RedisSerializers.LONG);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return 用户登录经验标记,UID: NULL,暂时没有值,数据死亡时间为次日零时 */
|
/** @return 用户登录经验标记,UID: NULL,暂时没有值,数据死亡时间为次日零时 */
|
||||||
@Bean("redisUserExpFlag")
|
@Bean("redisUserExpFlag")
|
||||||
public Redis<Long, String> getUserExpFlagRedisTemplate() {
|
public Redis<Long, String> getUserExpFlagRedisTemplate() {
|
||||||
return getRedis(database.userExpFlag, RedisSerializers.LONG, RedisSerializers.STRING);
|
return getRedis(database, RedisSerializers.LONG, RedisSerializers.STRING);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return 用户登录令牌,令牌: 用户 ID */
|
||||||
|
@Bean("redisUserToken")
|
||||||
|
public Redis<String, String> getUserTokenRedisTemplate() {
|
||||||
|
return getRedis(database, RedisSerializers.STRING, RedisSerializers.STRING);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return 用户邮箱验证密钥,密钥: UID */
|
/** @return 用户邮箱验证密钥,密钥: UID */
|
||||||
@Bean("redisUserEmailVerify")
|
@Bean("redisUserEmailVerify")
|
||||||
public Redis<String, Long> getUserEmailVerifyRedisTemplate() {
|
public Redis<String, Long> getUserEmailVerifyRedisTemplate() {
|
||||||
return getRedis(database.userEmailVerify, RedisSerializers.STRING, RedisSerializers.LONG);
|
return getRedis(database, RedisSerializers.STRING, RedisSerializers.LONG);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return 用户重置密码密钥,密钥: UID */
|
/** @return 用户重置密码密钥,密钥: UID */
|
||||||
@Bean("redisUserResetPWVerify")
|
@Bean("redisUserResetPWVerify")
|
||||||
public Redis<String, Long> getUserResetPWVerifyRedisTemplate() {
|
public Redis<String, Long> getUserResetPWVerifyRedisTemplate() {
|
||||||
return getRedis(database.userResetPWVerify, RedisSerializers.STRING, RedisSerializers.LONG);
|
return getRedis(database, RedisSerializers.STRING, RedisSerializers.LONG);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return 用户手机号验证码,手机号: 验证码 */
|
||||||
|
@Bean("redisUserPhoneVerify")
|
||||||
|
public Redis<String, String> getUserPhoneVerifyRedisTemplate() {
|
||||||
|
return getRedis(database, RedisSerializers.STRING, RedisSerializers.STRING);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return 接口访问控制,IP#方法: 生命周期内访问次数 */
|
/** @return 接口访问控制,IP#方法: 生命周期内访问次数 */
|
||||||
@Bean("redisRateLimit")
|
@Bean("redisRateLimit")
|
||||||
public Redis<String, Integer> getRateLimitRedisTemplate() {
|
public Redis<String, Integer> getRateLimitRedisTemplate() {
|
||||||
return getRedis(database.rateLimit, RedisSerializers.STRING, RedisSerializers.INTEGER);
|
return getRedis(database, RedisSerializers.STRING, RedisSerializers.INTEGER);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return 系统配置,Key 枚举: String 配置值 */
|
/** @return 系统配置,Key 枚举: String 配置值 */
|
||||||
@Bean("redisSetting")
|
@Bean("redisSetting")
|
||||||
public Redis<String, String> getSettingRedisTemplate() {
|
public Redis<String, String> getSettingRedisTemplate() {
|
||||||
return getRedis(database.setting, RedisSerializers.STRING, RedisSerializers.STRING);
|
return getRedis(database, RedisSerializers.STRING, RedisSerializers.STRING);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return Minecraft 登录,令牌: 玩家 ID */
|
/** @return Minecraft 登录,令牌: 玩家 ID */
|
||||||
@Bean("redisMCPlayerToken")
|
@Bean("redisMCPlayerToken")
|
||||||
public Redis<String, Long> getMCPlayerLoginRedisTemplate() {
|
public Redis<String, Long> getMCPlayerLoginRedisTemplate() {
|
||||||
return getRedis(database.fmcPlayerToken, RedisSerializers.STRING, RedisSerializers.LONG);
|
return getRedis(database, RedisSerializers.STRING, RedisSerializers.LONG);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return 共享剪切板,会话 ID: 内容 */
|
||||||
|
@Bean("redisClipboard")
|
||||||
|
public Redis<String, String> getClipboardRedisTemplate() {
|
||||||
|
return getRedis(database, RedisSerializers.STRING, RedisSerializers.STRING);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @return GAO 用户邀请,code: 邀请缓存
|
||||||
|
@Bean("redisGaoUserInvite")
|
||||||
|
public Redis<String, GaoUserInviteCache> getGaoUserInviteRedisTemplate() {
|
||||||
|
return getRedis(database, RedisSerializers.STRING, RedisSerializers.jacksonSerializer(jackson, GaoUserInviteCache.class));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.imyeyu.server.config;
|
package com.imyeyu.api.config;
|
||||||
|
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package com.imyeyu.api.config;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||||
|
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 线程池配置
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2023-08-21 16:31
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Slf4j
|
||||||
|
@Configuration
|
||||||
|
public class ThreadPoolConfig {
|
||||||
|
|
||||||
|
@Bean(name = "threadPoolTaskExecutor")
|
||||||
|
public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
|
||||||
|
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||||
|
executor.setCorePoolSize(16);
|
||||||
|
executor.setMaxPoolSize(32);
|
||||||
|
executor.setQueueCapacity(16);
|
||||||
|
executor.setKeepAliveSeconds(60);
|
||||||
|
executor.setAwaitTerminationSeconds(60);
|
||||||
|
executor.setThreadNamePrefix("thread-pool-task-executor-");
|
||||||
|
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
|
||||||
|
executor.initialize();
|
||||||
|
return executor;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package com.imyeyu.api.config;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||||
|
import com.fasterxml.jackson.databind.MapperFeature;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.json.JsonMapper;
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||||
|
import com.imyeyu.api.annotation.EnableSettingInterceptor;
|
||||||
|
import com.imyeyu.api.annotation.RequestRateLimitInterceptor;
|
||||||
|
import com.imyeyu.api.annotation.RequireCorePermissionInterceptor;
|
||||||
|
import com.imyeyu.api.modules.gao.annotation.RequireGaoPermissionInterceptor;
|
||||||
|
import com.imyeyu.api.modules.journal.util.JournalAPIInterceptor;
|
||||||
|
import com.imyeyu.api.modules.system.util.SystemAPIInterceptor;
|
||||||
|
import com.imyeyu.spring.annotation.RequestBodyValueArgumentResolver;
|
||||||
|
import com.imyeyu.utils.Time;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.http.converter.HttpMessageConverter;
|
||||||
|
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||||
|
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||||
|
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||||
|
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统配置
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2021-07-20 16:44
|
||||||
|
*/
|
||||||
|
@EnableWebMvc
|
||||||
|
@Configuration
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class WebConfig implements WebMvcConfigurer {
|
||||||
|
|
||||||
|
private final ObjectMapper jackson;
|
||||||
|
private final SystemAPIInterceptor systemAPIInterceptor;
|
||||||
|
private final JournalAPIInterceptor journalAPIInterceptor;
|
||||||
|
private final EnableSettingInterceptor enableSettingInterceptor;
|
||||||
|
private final RequestRateLimitInterceptor requestRateLimitInterceptor;
|
||||||
|
private final RequireGaoPermissionInterceptor requireGaoPermissionInterceptor;
|
||||||
|
private final RequireCorePermissionInterceptor requireCorePermissionInterceptor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 过滤器
|
||||||
|
*
|
||||||
|
* @param registry 注册表
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void addInterceptors(InterceptorRegistry registry) {
|
||||||
|
registry.addInterceptor(systemAPIInterceptor).addPathPatterns(SystemAPIInterceptor.PATH);
|
||||||
|
registry.addInterceptor(journalAPIInterceptor).addPathPatterns(JournalAPIInterceptor.PATH);
|
||||||
|
registry.addInterceptor(enableSettingInterceptor).addPathPatterns("/**");
|
||||||
|
registry.addInterceptor(requireCorePermissionInterceptor).addPathPatterns("/**");
|
||||||
|
registry.addInterceptor(requireGaoPermissionInterceptor).addPathPatterns("/**");
|
||||||
|
registry.addInterceptor(requestRateLimitInterceptor).addPathPatterns("/**");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
|
||||||
|
argumentResolvers.add(new RequestBodyValueArgumentResolver(jackson));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通信消息转换
|
||||||
|
*
|
||||||
|
* @param converters 转换器
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
|
||||||
|
JsonMapper jsonMapper = JsonMapper.builder()
|
||||||
|
.addModule(new JavaTimeModule())
|
||||||
|
// 设置默认属性包含规则:忽略 null 值
|
||||||
|
.serializationInclusion(JsonInclude.Include.NON_NULL)
|
||||||
|
// 日期格式化
|
||||||
|
.defaultDateFormat(Time.dateTime)
|
||||||
|
// 忽略不存在字段
|
||||||
|
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
|
||||||
|
// 启用默认视图包含
|
||||||
|
.enable(MapperFeature.DEFAULT_VIEW_INCLUSION).build();
|
||||||
|
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(jsonMapper);
|
||||||
|
converter.setDefaultCharset(StandardCharsets.UTF_8);
|
||||||
|
converters.add(converter);
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-6
@@ -1,4 +1,4 @@
|
|||||||
package com.imyeyu.server.config.dbsource;
|
package com.imyeyu.api.config.dbsource;
|
||||||
|
|
||||||
import com.zaxxer.hikari.HikariDataSource;
|
import com.zaxxer.hikari.HikariDataSource;
|
||||||
import com.imyeyu.utils.Time;
|
import com.imyeyu.utils.Time;
|
||||||
@@ -24,13 +24,12 @@ import java.sql.SQLException;
|
|||||||
* @since 2022-11-29 22:39
|
* @since 2022-11-29 22:39
|
||||||
*/
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
@MapperScan(basePackages = "com.imyeyu.server.modules.forevermc.mapper", sqlSessionFactoryRef = "foreverMCSqlSessionFactory")
|
@MapperScan(basePackages = "com.imyeyu.api.modules.forevermc.mapper", sqlSessionFactoryRef = "foreverMCSqlSessionFactory")
|
||||||
public class ForeverMCDBConfig {
|
public class ForeverMCDBConfig {
|
||||||
|
|
||||||
public static final String ROLLBACKER = "foreverMCTransactionManager";
|
public static final String ROLLBACKER = "foreverMCTransactionManager";
|
||||||
|
|
||||||
@Bean(name = "foreverMCDataSource")
|
@Bean(name = "foreverMCDataSource")
|
||||||
@Primary
|
|
||||||
@ConfigurationProperties(prefix = "spring.datasource.forevermc")
|
@ConfigurationProperties(prefix = "spring.datasource.forevermc")
|
||||||
public DataSource getPrimaryDateSource() throws SQLException {
|
public DataSource getPrimaryDateSource() throws SQLException {
|
||||||
HikariDataSource dataSource = new HikariDataSource();
|
HikariDataSource dataSource = new HikariDataSource();
|
||||||
@@ -50,7 +49,6 @@ public class ForeverMCDBConfig {
|
|||||||
|
|
||||||
|
|
||||||
@Bean(name = "foreverMCSqlSessionFactory")
|
@Bean(name = "foreverMCSqlSessionFactory")
|
||||||
@Primary
|
|
||||||
public SqlSessionFactory primarySqlSessionFactory(@Qualifier("foreverMCDataSource") DataSource datasource) throws Exception {
|
public SqlSessionFactory primarySqlSessionFactory(@Qualifier("foreverMCDataSource") DataSource datasource) throws Exception {
|
||||||
org.apache.ibatis.session.Configuration config = new org.apache.ibatis.session.Configuration();
|
org.apache.ibatis.session.Configuration config = new org.apache.ibatis.session.Configuration();
|
||||||
config.setUseGeneratedKeys(true);
|
config.setUseGeneratedKeys(true);
|
||||||
@@ -58,13 +56,12 @@ public class ForeverMCDBConfig {
|
|||||||
|
|
||||||
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
|
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
|
||||||
bean.setDataSource(datasource);
|
bean.setDataSource(datasource);
|
||||||
bean.setTypeAliasesPackage("com.imyeyu.server.modules.forevermc.entity");
|
bean.setTypeAliasesPackage("com.imyeyu.api.modules.forevermc.entity");
|
||||||
bean.setConfiguration(config);
|
bean.setConfiguration(config);
|
||||||
return bean.getObject();
|
return bean.getObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean("foreverMCSqlSessionTemplate")
|
@Bean("foreverMCSqlSessionTemplate")
|
||||||
@Primary
|
|
||||||
public SqlSessionTemplate primarySqlSessionTemplate(@Qualifier("foreverMCSqlSessionFactory") SqlSessionFactory sessionfactory) {
|
public SqlSessionTemplate primarySqlSessionTemplate(@Qualifier("foreverMCSqlSessionFactory") SqlSessionFactory sessionfactory) {
|
||||||
return new SqlSessionTemplate(sessionfactory);
|
return new SqlSessionTemplate(sessionfactory);
|
||||||
}
|
}
|
||||||
+7
-10
@@ -1,4 +1,4 @@
|
|||||||
package com.imyeyu.server.config.dbsource;
|
package com.imyeyu.api.config.dbsource;
|
||||||
|
|
||||||
import com.imyeyu.utils.Time;
|
import com.imyeyu.utils.Time;
|
||||||
import com.zaxxer.hikari.HikariDataSource;
|
import com.zaxxer.hikari.HikariDataSource;
|
||||||
@@ -31,14 +31,13 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
@MapperScan(basePackages = {
|
@MapperScan(basePackages = {
|
||||||
"com.imyeyu.server.modules.gitea.mapper",
|
"com.imyeyu.api.modules.gitea.mapper",
|
||||||
}, sqlSessionFactoryRef = "giteaSqlSessionFactory")
|
}, sqlSessionFactoryRef = "giteaSqlSessionFactory")
|
||||||
public class GiteaDBConfig {
|
public class GiteaDBConfig {
|
||||||
|
|
||||||
public static final String ROLLBACKER = "giteaTransactionManager";
|
public static final String ROLLBACKER = "giteaTransactionManager";
|
||||||
|
|
||||||
@Bean(name = "giteaDataSource")
|
@Bean(name = "giteaDataSource")
|
||||||
@Primary
|
|
||||||
@ConfigurationProperties(prefix = "spring.datasource.gitea")
|
@ConfigurationProperties(prefix = "spring.datasource.gitea")
|
||||||
public DataSource dateSource() throws SQLException {
|
public DataSource dateSource() throws SQLException {
|
||||||
HikariDataSource dataSource = new HikariDataSource();
|
HikariDataSource dataSource = new HikariDataSource();
|
||||||
@@ -58,7 +57,6 @@ public class GiteaDBConfig {
|
|||||||
|
|
||||||
|
|
||||||
@Bean(name = "giteaSqlSessionFactory")
|
@Bean(name = "giteaSqlSessionFactory")
|
||||||
@Primary
|
|
||||||
public SqlSessionFactory sessionFactory(@Qualifier("giteaDataSource") DataSource datasource) throws Exception {
|
public SqlSessionFactory sessionFactory(@Qualifier("giteaDataSource") DataSource datasource) throws Exception {
|
||||||
org.apache.ibatis.session.Configuration config = new org.apache.ibatis.session.Configuration();
|
org.apache.ibatis.session.Configuration config = new org.apache.ibatis.session.Configuration();
|
||||||
config.setUseGeneratedKeys(true);
|
config.setUseGeneratedKeys(true);
|
||||||
@@ -69,16 +67,16 @@ public class GiteaDBConfig {
|
|||||||
{
|
{
|
||||||
ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
|
ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
|
||||||
List<String> mapperLocations = new ArrayList<>();
|
List<String> mapperLocations = new ArrayList<>();
|
||||||
mapperLocations.add("classpath:mapper/gitea/**/*.xml");
|
// mapperLocations.add("classpath:mapper/gitea/**/*.xml");
|
||||||
for (int i = 0; i < mapperLocations.size(); i++) {
|
for (String mapperLocation : mapperLocations) {
|
||||||
resources.addAll(List.of(resourceResolver.getResources(mapperLocations.get(i))));
|
resources.addAll(List.of(resourceResolver.getResources(mapperLocation)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
String[] typeAliases = {
|
String[] typeAliases = {
|
||||||
"com.imyeyu.server.modules.gitea.entity",
|
"com.imyeyu.api.modules.gitea.entity",
|
||||||
};
|
};
|
||||||
String[] typeHandlers = {
|
String[] typeHandlers = {
|
||||||
"com.imyeyu.server.handler"
|
"com.imyeyu.spring.handler"
|
||||||
};
|
};
|
||||||
|
|
||||||
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
|
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
|
||||||
@@ -92,7 +90,6 @@ public class GiteaDBConfig {
|
|||||||
|
|
||||||
|
|
||||||
@Bean("giteaSqlSessionTemplate")
|
@Bean("giteaSqlSessionTemplate")
|
||||||
@Primary
|
|
||||||
public SqlSessionTemplate sessionTemplate(@Qualifier("giteaSqlSessionFactory") SqlSessionFactory sessionfactory) {
|
public SqlSessionTemplate sessionTemplate(@Qualifier("giteaSqlSessionFactory") SqlSessionFactory sessionfactory) {
|
||||||
return new SqlSessionTemplate(sessionfactory);
|
return new SqlSessionTemplate(sessionfactory);
|
||||||
}
|
}
|
||||||
+29
-25
@@ -1,4 +1,4 @@
|
|||||||
package com.imyeyu.server.config.dbsource;
|
package com.imyeyu.api.config.dbsource;
|
||||||
|
|
||||||
import com.zaxxer.hikari.HikariDataSource;
|
import com.zaxxer.hikari.HikariDataSource;
|
||||||
import com.imyeyu.utils.Time;
|
import com.imyeyu.utils.Time;
|
||||||
@@ -8,6 +8,7 @@ import org.mybatis.spring.SqlSessionFactoryBean;
|
|||||||
import org.mybatis.spring.SqlSessionTemplate;
|
import org.mybatis.spring.SqlSessionTemplate;
|
||||||
import org.mybatis.spring.annotation.MapperScan;
|
import org.mybatis.spring.annotation.MapperScan;
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.boot.autoconfigure.flyway.FlywayDataSource;
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
@@ -31,14 +32,17 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
@MapperScan(basePackages = {
|
@MapperScan(basePackages = {
|
||||||
"com.imyeyu.server.modules.git.mapper",
|
"com.imyeyu.api.modules.git.mapper",
|
||||||
"com.imyeyu.server.modules.bill.mapper",
|
"com.imyeyu.api.modules.gao.mapper",
|
||||||
"com.imyeyu.server.modules.blog.mapper",
|
"com.imyeyu.api.modules.bill.mapper",
|
||||||
"com.imyeyu.server.modules.lyric.mapper",
|
"com.imyeyu.api.modules.user.mapper",
|
||||||
"com.imyeyu.server.modules.mirror.mapper",
|
"com.imyeyu.api.modules.blog.mapper",
|
||||||
"com.imyeyu.server.modules.system.mapper",
|
"com.imyeyu.api.modules.lyric.mapper",
|
||||||
"com.imyeyu.server.modules.common.mapper",
|
"com.imyeyu.api.modules.mirror.mapper",
|
||||||
"com.imyeyu.server.modules.minecraft.mapper"
|
"com.imyeyu.api.modules.system.mapper",
|
||||||
|
"com.imyeyu.api.modules.common.mapper",
|
||||||
|
"com.imyeyu.api.modules.journal.mapper",
|
||||||
|
"com.imyeyu.api.modules.minecraft.mapper"
|
||||||
}, sqlSessionFactoryRef = "timiServerSqlSessionFactory")
|
}, sqlSessionFactoryRef = "timiServerSqlSessionFactory")
|
||||||
public class TimiServerDBConfig {
|
public class TimiServerDBConfig {
|
||||||
|
|
||||||
@@ -46,6 +50,7 @@ public class TimiServerDBConfig {
|
|||||||
|
|
||||||
@Bean(name = "timiServerDataSource")
|
@Bean(name = "timiServerDataSource")
|
||||||
@Primary
|
@Primary
|
||||||
|
@FlywayDataSource
|
||||||
@ConfigurationProperties(prefix = "spring.datasource.timiserver")
|
@ConfigurationProperties(prefix = "spring.datasource.timiserver")
|
||||||
public DataSource getPrimaryDateSource() throws SQLException {
|
public DataSource getPrimaryDateSource() throws SQLException {
|
||||||
HikariDataSource dataSource = new HikariDataSource();
|
HikariDataSource dataSource = new HikariDataSource();
|
||||||
@@ -76,27 +81,26 @@ public class TimiServerDBConfig {
|
|||||||
{
|
{
|
||||||
ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
|
ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
|
||||||
List<String> mapperLocations = new ArrayList<>();
|
List<String> mapperLocations = new ArrayList<>();
|
||||||
mapperLocations.add("classpath:mapper/git/**/*.xml");
|
mapperLocations.add("classpath:mapper/timi-server/**/*.xml");
|
||||||
mapperLocations.add("classpath:mapper/blog/**/*.xml");
|
for (String mapperLocation : mapperLocations) {
|
||||||
mapperLocations.add("classpath:mapper/common/**/*.xml");
|
resources.addAll(List.of(resourceResolver.getResources(mapperLocation)));
|
||||||
mapperLocations.add("classpath:mapper/system/**/*.xml");
|
|
||||||
mapperLocations.add("classpath:mapper/minecraft/**/*.xml");
|
|
||||||
for (int i = 0; i < mapperLocations.size(); i++) {
|
|
||||||
resources.addAll(List.of(resourceResolver.getResources(mapperLocations.get(i))));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
String[] typeAliases = {
|
String[] typeAliases = {
|
||||||
"com.imyeyu.server.modules.git.entity",
|
"com.imyeyu.api.modules.git.entity",
|
||||||
"com.imyeyu.server.modules.bill.entity",
|
"com.imyeyu.api.modules.gao.entity",
|
||||||
"com.imyeyu.server.modules.blog.entity",
|
"com.imyeyu.api.modules.bill.entity",
|
||||||
"com.imyeyu.server.modules.lyric.entity",
|
"com.imyeyu.api.modules.user.entity",
|
||||||
"com.imyeyu.server.modules.mirror.entity",
|
"com.imyeyu.api.modules.blog.entity",
|
||||||
"com.imyeyu.server.modules.system.entity",
|
"com.imyeyu.api.modules.lyric.entity",
|
||||||
"com.imyeyu.server.modules.common.entity",
|
"com.imyeyu.api.modules.mirror.entity",
|
||||||
"com.imyeyu.server.modules.minecraft.entity"
|
"com.imyeyu.api.modules.system.entity",
|
||||||
|
"com.imyeyu.api.modules.common.entity",
|
||||||
|
"com.imyeyu.api.modules.journal.entity",
|
||||||
|
"com.imyeyu.api.modules.minecraft.entity"
|
||||||
};
|
};
|
||||||
String[] typeHandlers = {
|
String[] typeHandlers = {
|
||||||
"com.imyeyu.server.handler"
|
"com.imyeyu.spring.handler"
|
||||||
};
|
};
|
||||||
|
|
||||||
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
|
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.imyeyu.server.modules.common.bean;
|
package com.imyeyu.api.modules.common.bean;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.imyeyu.server.modules.common.bean;
|
package com.imyeyu.api.modules.common.bean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package com.imyeyu.api.modules.common.bean;
|
||||||
|
|
||||||
|
import com.imyeyu.api.modules.common.entity.Setting;
|
||||||
|
import com.imyeyu.java.bean.timi.TimiCode;
|
||||||
|
import com.imyeyu.java.bean.timi.TimiException;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置模块
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-04-24 00:59
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum SettingModule {
|
||||||
|
|
||||||
|
COMMON(Setting.Module.Common.class),
|
||||||
|
|
||||||
|
DOMAIN(Setting.Module.Domain.class),
|
||||||
|
|
||||||
|
FOREVER_MC(Setting.Module.ForeverMC.class),
|
||||||
|
|
||||||
|
MULTILINGUAL(Setting.Module.Multilingual.class),
|
||||||
|
|
||||||
|
GITEA(Setting.Module.Gitea.class),
|
||||||
|
|
||||||
|
MUSIC(Setting.Module.Music.class),
|
||||||
|
|
||||||
|
JOURNAL(Setting.Module.Journal.class),
|
||||||
|
|
||||||
|
TEMP_FILE(Setting.Module.TempFile.class),
|
||||||
|
|
||||||
|
USER(Setting.Module.User.class),
|
||||||
|
|
||||||
|
SYSTEM(Setting.Module.System.class),
|
||||||
|
|
||||||
|
SYSTEM_STATUS(Setting.Module.SystemStatus.class);
|
||||||
|
|
||||||
|
/** 模块枚举类 */
|
||||||
|
final Class<? extends Enum<?>> clazz;
|
||||||
|
|
||||||
|
public static <T extends Enum<?>> SettingModule fromClass(Class<T> clazz) {
|
||||||
|
for (SettingModule value : values()) {
|
||||||
|
if (value.clazz.equals(clazz)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new TimiException(TimiCode.ERROR, "not found System Module: %s".formatted(clazz.getName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.imyeyu.api.modules.common.bean.attachment;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 附件访问密钥 VO
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-01-13 10:05
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class AccessKey {
|
||||||
|
|
||||||
|
/** 访问密钥 */
|
||||||
|
private String value;
|
||||||
|
|
||||||
|
/** 附件 ID */
|
||||||
|
private String attachmentId;
|
||||||
|
|
||||||
|
/** true 为一次性访问 */
|
||||||
|
private boolean isDisposable;
|
||||||
|
|
||||||
|
/** 过期时间戳 */
|
||||||
|
private Long expiredAt;
|
||||||
|
|
||||||
|
/** 创建时间戳 */
|
||||||
|
private Long createdAt;
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.imyeyu.api.modules.common.bean.attachment;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2025-10-20 15:04
|
||||||
|
*/
|
||||||
|
public class MediaAttach {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2025-09-28 02:01
|
||||||
|
*/
|
||||||
|
public enum Type {
|
||||||
|
|
||||||
|
SOURCE,
|
||||||
|
|
||||||
|
THUMB
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package com.imyeyu.api.modules.common.bean.attachment;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2025-12-11 18:14
|
||||||
|
*/
|
||||||
|
public class Metadata {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图片
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2025-12-11 18:15
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public static class Image {
|
||||||
|
|
||||||
|
private int width;
|
||||||
|
|
||||||
|
private int height;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缩略图
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-01-04 18:10
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public static class ThumbImage extends Image {
|
||||||
|
|
||||||
|
private String sourceId;
|
||||||
|
|
||||||
|
private String sourceMongoId;
|
||||||
|
|
||||||
|
private String sourceMimeType;
|
||||||
|
|
||||||
|
/** 请求宽度(缓存键之一,null 表示未指定) */
|
||||||
|
private Integer requestWidth;
|
||||||
|
|
||||||
|
/** 请求高度(缓存键之一,null 表示未指定) */
|
||||||
|
private Integer requestHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.imyeyu.api.modules.common.bean.attachment;
|
||||||
|
|
||||||
|
import com.imyeyu.api.modules.common.entity.Setting;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-05-13 17:27
|
||||||
|
*/
|
||||||
|
public interface SettingSupport {
|
||||||
|
|
||||||
|
String getId();
|
||||||
|
|
||||||
|
List<Setting> getSettingList();
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.imyeyu.api.modules.common.bean.attachment;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 临时文件元数据
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2025-09-27 01:47
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class TempFileMetaData {
|
||||||
|
|
||||||
|
/** 附件 ID */
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
/** 文件名 */
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/** 原始文件名 */
|
||||||
|
private String originalName;
|
||||||
|
|
||||||
|
/** 储存 ID */
|
||||||
|
private String mongoId;
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package com.imyeyu.api.modules.common.config;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
/// 通知消息队列配置
|
||||||
|
///
|
||||||
|
/// @author 夜雨
|
||||||
|
/// @since 2026-08-20
|
||||||
|
@Data
|
||||||
|
@Configuration
|
||||||
|
@ConfigurationProperties(prefix = "notify.queue")
|
||||||
|
public class NotifyQueueConfig {
|
||||||
|
|
||||||
|
private Mail mail = new Mail();
|
||||||
|
|
||||||
|
private Sms sms = new Sms();
|
||||||
|
|
||||||
|
private Wechat wechat = new Wechat();
|
||||||
|
|
||||||
|
private HTTP http = new HTTP();
|
||||||
|
|
||||||
|
/// 邮件队列配置
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public static class Mail extends Queue {
|
||||||
|
|
||||||
|
private String defaultFrom;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 短信队列配置
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public static class Sms extends Queue {
|
||||||
|
|
||||||
|
private String appId;
|
||||||
|
|
||||||
|
private String sign;
|
||||||
|
|
||||||
|
private String secretId;
|
||||||
|
|
||||||
|
private String secretKey;
|
||||||
|
|
||||||
|
private Captcha captcha;
|
||||||
|
|
||||||
|
/// 短信验证码
|
||||||
|
@Data
|
||||||
|
public static class Captcha {
|
||||||
|
|
||||||
|
private String templateId;
|
||||||
|
|
||||||
|
private int ttlMinute;
|
||||||
|
|
||||||
|
private int argIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 微信队列配置
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public static class Wechat extends Queue {
|
||||||
|
}
|
||||||
|
|
||||||
|
/// HTTP 队列配置
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public static class HTTP extends Queue {
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 通用队列配置
|
||||||
|
@Data
|
||||||
|
public static class Queue {
|
||||||
|
|
||||||
|
/// true 为启用
|
||||||
|
private boolean enable;
|
||||||
|
|
||||||
|
private String cron = "0/30 * * * * ?";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,254 @@
|
|||||||
|
package com.imyeyu.api.modules.common.controller;
|
||||||
|
|
||||||
|
import com.imyeyu.api.modules.common.entity.Attachment;
|
||||||
|
import com.imyeyu.api.modules.common.service.AttachmentService;
|
||||||
|
import com.imyeyu.api.modules.common.service.TempFileService;
|
||||||
|
import com.imyeyu.api.modules.common.vo.attach.TempFileResp;
|
||||||
|
import com.imyeyu.java.TimiJava;
|
||||||
|
import com.imyeyu.network.Network;
|
||||||
|
import com.imyeyu.spring.TimiSpring;
|
||||||
|
import com.imyeyu.spring.annotation.AOPLog;
|
||||||
|
import com.imyeyu.spring.annotation.IgnoreGlobalReturn;
|
||||||
|
import com.imyeyu.spring.annotation.RequestRateLimit;
|
||||||
|
import com.imyeyu.spring.bean.Page;
|
||||||
|
import com.imyeyu.spring.bean.PageResult;
|
||||||
|
import com.imyeyu.utils.Time;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// @author 夜雨
|
||||||
|
/// @since 2026-07-27 19:26
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@RequestMapping("/attach")
|
||||||
|
public class AttachmentController {
|
||||||
|
|
||||||
|
private final TempFileService tempFileService;
|
||||||
|
private final AttachmentService service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询附件详情
|
||||||
|
*
|
||||||
|
* @param id 附件 ID
|
||||||
|
* @return 附件详情
|
||||||
|
*/
|
||||||
|
@RequestRateLimit
|
||||||
|
@PostMapping("/detail")
|
||||||
|
public Attachment detail(@RequestParam String id) {
|
||||||
|
return service.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建附件
|
||||||
|
*
|
||||||
|
* @param attachment 附件
|
||||||
|
* @return 创建结果
|
||||||
|
*/
|
||||||
|
@AOPLog
|
||||||
|
@RequestRateLimit
|
||||||
|
@PostMapping(value = "/create", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||||
|
public Attachment create(Attachment attachment) {
|
||||||
|
service.create(attachment);
|
||||||
|
return attachment;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新附件
|
||||||
|
*
|
||||||
|
* @param attachment 附件
|
||||||
|
*/
|
||||||
|
@AOPLog
|
||||||
|
@RequestRateLimit
|
||||||
|
@PostMapping(value = "/update", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||||
|
public void update(Attachment attachment) {
|
||||||
|
service.update(attachment);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除附件
|
||||||
|
*
|
||||||
|
* @param id 附件 ID
|
||||||
|
*/
|
||||||
|
@AOPLog
|
||||||
|
@RequestRateLimit
|
||||||
|
@PostMapping("/delete")
|
||||||
|
public void delete(@RequestParam String id) {
|
||||||
|
service.delete(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按业务查询单个附件
|
||||||
|
*
|
||||||
|
* @param bizType 业务类型
|
||||||
|
* @param bizId 业务 ID
|
||||||
|
* @return 附件详情
|
||||||
|
*/
|
||||||
|
@RequestRateLimit
|
||||||
|
@GetMapping("/detail/byBiz")
|
||||||
|
public Attachment detailByBiz(@RequestParam Attachment.BizType bizType, @RequestParam String bizId) {
|
||||||
|
return service.getByBizId(bizType, bizId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按业务和附件类型查询单个附件
|
||||||
|
*
|
||||||
|
* @param bizType 业务类型
|
||||||
|
* @param bizId 业务 ID
|
||||||
|
* @param attachType 附件类型
|
||||||
|
* @return 附件详情
|
||||||
|
*/
|
||||||
|
@RequestRateLimit
|
||||||
|
@GetMapping("/detail/byAttachType")
|
||||||
|
public Attachment detailByAttachType(
|
||||||
|
@RequestParam Attachment.BizType bizType,
|
||||||
|
@RequestParam String bizId,
|
||||||
|
@RequestParam String attachType
|
||||||
|
) {
|
||||||
|
return service.getByAttachType(bizType, bizId, attachType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按业务查询附件列表
|
||||||
|
*
|
||||||
|
* @param bizType 业务类型
|
||||||
|
* @param bizId 业务 ID
|
||||||
|
* @param attachTypeList 附件类型列表
|
||||||
|
* @return 附件列表
|
||||||
|
*/
|
||||||
|
@RequestRateLimit
|
||||||
|
@GetMapping("/list/biz")
|
||||||
|
public List<Attachment> listByBiz(
|
||||||
|
@RequestParam Attachment.BizType bizType,
|
||||||
|
@RequestParam String bizId,
|
||||||
|
@RequestParam(required = false) List<String> attachTypeList
|
||||||
|
) {
|
||||||
|
List<String> types = TimiJava.defaultIfNull(attachTypeList, List.of());
|
||||||
|
return service.listByBizId(bizType, bizId, types.toArray(String[]::new));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按业务统计附件数量
|
||||||
|
*
|
||||||
|
* @param bizType 业务类型
|
||||||
|
* @param bizId 业务 ID
|
||||||
|
* @param attachTypeList 附件类型列表
|
||||||
|
* @return 附件数量
|
||||||
|
*/
|
||||||
|
@RequestRateLimit
|
||||||
|
@GetMapping("/count/biz")
|
||||||
|
public long countByBiz(
|
||||||
|
@RequestParam Attachment.BizType bizType,
|
||||||
|
@RequestParam String bizId,
|
||||||
|
@RequestParam(required = false) List<String> attachTypeList
|
||||||
|
) {
|
||||||
|
List<String> types = TimiJava.defaultIfNull(attachTypeList, List.of());
|
||||||
|
return service.countByBizId(bizType, bizId, types.toArray(String[]::new));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按业务分页查询附件
|
||||||
|
*
|
||||||
|
* @param bizType 业务类型
|
||||||
|
* @param bizId 业务 ID
|
||||||
|
* @param attachTypeList 附件类型列表
|
||||||
|
* @param page 分页参数
|
||||||
|
* @return 分页结果
|
||||||
|
*/
|
||||||
|
@RequestRateLimit
|
||||||
|
@PostMapping("/page/biz")
|
||||||
|
public PageResult<Attachment> pageByBiz(
|
||||||
|
@RequestParam Attachment.BizType bizType,
|
||||||
|
@RequestParam String bizId,
|
||||||
|
@RequestParam(required = false) List<String> attachTypeList,
|
||||||
|
@RequestBody Page<Attachment> page
|
||||||
|
) {
|
||||||
|
return service.pageByBizId(bizType, bizId, attachTypeList, page);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AOPLog
|
||||||
|
@IgnoreGlobalReturn
|
||||||
|
@GetMapping({"/read/{id}", "/download/{id}"})
|
||||||
|
public void readById(@PathVariable String id, @RequestParam(required = false) Integer thumbWidth, @RequestParam(required = false) Integer thumbHeight) throws UnsupportedEncodingException {
|
||||||
|
HttpServletResponse resp = TimiSpring.getResponse();
|
||||||
|
Attachment attach = service.get(id);
|
||||||
|
if (attach == null) {
|
||||||
|
resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
responseAttachment(attach, thumbWidth, thumbHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AOPLog
|
||||||
|
@PostMapping("/temp/upload")
|
||||||
|
public List<TempFileResp> upload(@RequestParam("file") List<MultipartFile> files, @RequestParam(value = "ttl", required = false) String ttl) {
|
||||||
|
return tempFileService.create(files, Time.D);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AOPLog
|
||||||
|
@RequestRateLimit
|
||||||
|
@IgnoreGlobalReturn
|
||||||
|
@GetMapping({"/temp/read", "/temp/download"})
|
||||||
|
public void read(@RequestParam String id) throws UnsupportedEncodingException {
|
||||||
|
HttpServletResponse resp = TimiSpring.getResponse();
|
||||||
|
Attachment attach = service.get(id);
|
||||||
|
if (TimiSpring.getRequest().getRequestURI().endsWith("/download")) {
|
||||||
|
resp.setHeader("Content-Disposition", Network.getFileDownloadHeader(attach.getName()));
|
||||||
|
}
|
||||||
|
response(attach);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void responseAttachment(Attachment attach, Integer thumbWidth, Integer thumbHeight) throws UnsupportedEncodingException {
|
||||||
|
HttpServletResponse resp = TimiSpring.getResponse();
|
||||||
|
if (attach != null && TimiSpring.getRequest().getRequestURI().endsWith("/download") && attach.getName() != null) {
|
||||||
|
resp.setHeader("Content-Disposition", Network.getFileDownloadHeader(attach.getName()));
|
||||||
|
}
|
||||||
|
responseThumb(attach, thumbWidth, thumbHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void responseThumb(Attachment attachment, Integer thumbWidth, Integer thumbHeight) {
|
||||||
|
HttpServletResponse resp = TimiSpring.getResponse();
|
||||||
|
if (TimiJava.isEmpty(attachment)) {
|
||||||
|
resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (thumbWidth == null && thumbHeight == null) {
|
||||||
|
// 原图
|
||||||
|
response(attachment);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 缩略图
|
||||||
|
Attachment thumb = service.fetchThumb(attachment, thumbWidth, thumbHeight);
|
||||||
|
response(TimiJava.defaultIfNull(thumb, attachment));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void response(Attachment attachment) {
|
||||||
|
HttpServletResponse resp = TimiSpring.getResponse();
|
||||||
|
try {
|
||||||
|
if (TimiJava.isEmpty(attachment)) {
|
||||||
|
resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resp.setContentType(attachment.getMimeType());
|
||||||
|
attachment.doResponse();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("response attachment error", e);
|
||||||
|
resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,264 @@
|
|||||||
|
package com.imyeyu.api.modules.common.controller;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonView;
|
||||||
|
import com.imyeyu.api.modules.common.entity.Comment;
|
||||||
|
import com.imyeyu.api.modules.user.entity.UserBehaviorRecord;
|
||||||
|
import com.imyeyu.api.modules.common.service.CommentService;
|
||||||
|
import com.imyeyu.api.modules.common.vo.comment.CommentCreateReq;
|
||||||
|
import com.imyeyu.api.modules.common.vo.comment.CommentLayerQueryReq;
|
||||||
|
import com.imyeyu.api.modules.common.vo.comment.CommentLayerResp;
|
||||||
|
import com.imyeyu.api.modules.common.vo.comment.CommentNodeResp;
|
||||||
|
import com.imyeyu.api.modules.common.vo.comment.CommentReplyReq;
|
||||||
|
import com.imyeyu.api.modules.common.vo.comment.CommentThreadResp;
|
||||||
|
import com.imyeyu.api.modules.common.vo.comment.FlatCommentListResp;
|
||||||
|
import com.imyeyu.api.modules.common.vo.comment.NestedCommentListResp;
|
||||||
|
import com.imyeyu.spring.TimiSpring;
|
||||||
|
import com.imyeyu.spring.annotation.AOPLog;
|
||||||
|
import com.imyeyu.spring.annotation.RequestRateLimit;
|
||||||
|
import com.imyeyu.spring.bean.Page;
|
||||||
|
import com.imyeyu.spring.bean.PageResult;
|
||||||
|
import com.imyeyu.spring.util.ResponseView;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 评论操作接口
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2021-02-23 21:36
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Validated
|
||||||
|
@RestController
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@RequestMapping("/comment")
|
||||||
|
public class CommentController {
|
||||||
|
|
||||||
|
private final CommentService service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建主评论
|
||||||
|
*
|
||||||
|
* @param req 创建请求
|
||||||
|
* @return 评论树详情
|
||||||
|
*/
|
||||||
|
@AOPLog
|
||||||
|
@JsonView(ResponseView.Public.class)
|
||||||
|
@RequestRateLimit
|
||||||
|
@PostMapping("/create")
|
||||||
|
public Comment create(@RequestBody @Valid CommentCreateReq req) {
|
||||||
|
Comment entity = new Comment();
|
||||||
|
entity.setBizType(req.getBizType());
|
||||||
|
entity.setBizId(req.getBizId());
|
||||||
|
entity.setUserId(req.getUserId());
|
||||||
|
entity.setUserNick(req.getUserNick());
|
||||||
|
entity.setContent(req.getContent());
|
||||||
|
entity.setIp(TimiSpring.getRequestIP());
|
||||||
|
service.create(entity);
|
||||||
|
return service.detailTree(entity.getId(), req.getUserId());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 回复评论
|
||||||
|
*
|
||||||
|
* @param req 回复请求
|
||||||
|
* @return 评论树详情
|
||||||
|
*/
|
||||||
|
@AOPLog
|
||||||
|
@JsonView(ResponseView.Public.class)
|
||||||
|
@RequestRateLimit
|
||||||
|
@PostMapping("/reply")
|
||||||
|
public Comment reply(@RequestBody @Valid CommentReplyReq req) {
|
||||||
|
Comment entity = new Comment();
|
||||||
|
entity.setParentId(req.getParentId());
|
||||||
|
entity.setUserId(req.getUserId());
|
||||||
|
entity.setUserNick(req.getUserNick());
|
||||||
|
entity.setContent(req.getContent());
|
||||||
|
entity.setIp(TimiSpring.getRequestIP());
|
||||||
|
service.create(entity);
|
||||||
|
return service.detailTree(entity.getId(), req.getUserId());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除当前用户自己的评论
|
||||||
|
*
|
||||||
|
* @param id 评论 ID
|
||||||
|
* @param userId 用户 ID
|
||||||
|
*/
|
||||||
|
@AOPLog
|
||||||
|
@RequestRateLimit
|
||||||
|
@PostMapping("/delete")
|
||||||
|
public void delete(@RequestParam String id, @RequestParam String userId) {
|
||||||
|
service.deleteOwned(id, userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 点赞评论
|
||||||
|
*
|
||||||
|
* @param commentId 评论 ID
|
||||||
|
* @param userId 用户 ID
|
||||||
|
*/
|
||||||
|
@AOPLog
|
||||||
|
@RequestRateLimit
|
||||||
|
@PostMapping("/like")
|
||||||
|
public void like(@RequestParam String commentId, @RequestParam String userId) {
|
||||||
|
service.like(commentId, userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 不喜欢评论
|
||||||
|
*
|
||||||
|
* @param commentId 评论 ID
|
||||||
|
* @param userId 用户 ID
|
||||||
|
*/
|
||||||
|
@AOPLog
|
||||||
|
@RequestRateLimit
|
||||||
|
@PostMapping("/unlike")
|
||||||
|
public void unlike(@RequestParam String commentId, @RequestParam String userId) {
|
||||||
|
service.unlike(commentId, userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询用户点赞评论记录
|
||||||
|
*
|
||||||
|
* @param page 分页参数
|
||||||
|
* @param userId 用户 ID
|
||||||
|
* @return 点赞评论记录分页
|
||||||
|
*/
|
||||||
|
@JsonView(ResponseView.Public.class)
|
||||||
|
@RequestRateLimit
|
||||||
|
@PostMapping("/like/list")
|
||||||
|
public PageResult<UserBehaviorRecord> listLikedRecord(@RequestBody Page<UserBehaviorRecord> page, @RequestParam String userId) {
|
||||||
|
return service.listLikedRecord(page, userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询扁平主评论列表
|
||||||
|
*
|
||||||
|
* @param bizType 业务类型
|
||||||
|
* @param bizId 业务 ID
|
||||||
|
* @param offset 起始偏移量
|
||||||
|
* @param currentUserId 当前用户 ID,可为 null
|
||||||
|
* @return 扁平主评论列表
|
||||||
|
*/
|
||||||
|
@JsonView(ResponseView.Public.class)
|
||||||
|
@RequestRateLimit
|
||||||
|
@GetMapping("/flat/list")
|
||||||
|
public FlatCommentListResp listFlatRoot(
|
||||||
|
@RequestParam String bizType,
|
||||||
|
@RequestParam String bizId,
|
||||||
|
@RequestParam(defaultValue = "0") int offset,
|
||||||
|
@RequestParam(required = false) String currentUserId
|
||||||
|
) {
|
||||||
|
return service.listFlatRoot(bizType, bizId, offset, currentUserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询扁平评论线程
|
||||||
|
*
|
||||||
|
* @param commentId 评论 ID
|
||||||
|
* @param offset 起始偏移量
|
||||||
|
* @param currentUserId 当前用户 ID,可为 null
|
||||||
|
* @return 评论线程
|
||||||
|
*/
|
||||||
|
@JsonView(ResponseView.Public.class)
|
||||||
|
@RequestRateLimit
|
||||||
|
@GetMapping("/flat/thread")
|
||||||
|
public CommentThreadResp getFlatThread(
|
||||||
|
@RequestParam String commentId,
|
||||||
|
@RequestParam(defaultValue = "0") int offset,
|
||||||
|
@RequestParam(required = false) String currentUserId
|
||||||
|
) {
|
||||||
|
return service.getUserThread(commentId, offset, currentUserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询层级主评论列表
|
||||||
|
*
|
||||||
|
* @param bizType 业务类型
|
||||||
|
* @param bizId 业务 ID
|
||||||
|
* @param offset 起始偏移量
|
||||||
|
* @param currentUserId 当前用户 ID,可为 null
|
||||||
|
* @return 层级主评论列表
|
||||||
|
*/
|
||||||
|
@JsonView(ResponseView.Public.class)
|
||||||
|
@RequestRateLimit
|
||||||
|
@GetMapping("/nested/list")
|
||||||
|
public NestedCommentListResp listNestedRoot(
|
||||||
|
@RequestParam String bizType,
|
||||||
|
@RequestParam String bizId,
|
||||||
|
@RequestParam(defaultValue = "0") int offset,
|
||||||
|
@RequestParam(required = false) String currentUserId
|
||||||
|
) {
|
||||||
|
return service.listNestedRoot(bizType, bizId, offset, currentUserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询评论节点
|
||||||
|
*
|
||||||
|
* @param commentId 评论 ID
|
||||||
|
* @param currentUserId 当前用户 ID,可为 null
|
||||||
|
* @return 评论节点
|
||||||
|
*/
|
||||||
|
@JsonView(ResponseView.Public.class)
|
||||||
|
@RequestRateLimit
|
||||||
|
@GetMapping("/nested/detail")
|
||||||
|
public CommentNodeResp getNestedDetail(@RequestParam String commentId, @RequestParam(required = false) String currentUserId) {
|
||||||
|
return service.getUserNode(commentId, currentUserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询直属子评论分页
|
||||||
|
*
|
||||||
|
* @param commentId 评论 ID
|
||||||
|
* @param offset 起始偏移量
|
||||||
|
* @param currentUserId 当前用户 ID,可为 null
|
||||||
|
* @return 子评论分页
|
||||||
|
*/
|
||||||
|
@JsonView(ResponseView.Public.class)
|
||||||
|
@RequestRateLimit
|
||||||
|
@GetMapping("/nested/children")
|
||||||
|
public CommentLayerResp getNestedChildren(
|
||||||
|
@RequestParam String commentId,
|
||||||
|
@RequestParam(defaultValue = "0") int offset,
|
||||||
|
@RequestParam(required = false) String currentUserId
|
||||||
|
) {
|
||||||
|
return service.getUserChildren(commentId, offset, currentUserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询单层评论列表
|
||||||
|
*
|
||||||
|
* @param req 查询请求
|
||||||
|
* @param currentUserId 当前用户 ID,可为 null
|
||||||
|
* @return 评论层
|
||||||
|
*/
|
||||||
|
@JsonView(ResponseView.Public.class)
|
||||||
|
@RequestRateLimit
|
||||||
|
@PostMapping("/layer/list")
|
||||||
|
public CommentLayerResp listLayer(@RequestBody CommentLayerQueryReq req, @RequestParam(required = false) String currentUserId) {
|
||||||
|
return service.listSdkLayer(req, currentUserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询评论树详情
|
||||||
|
*
|
||||||
|
* @param id 评论 ID
|
||||||
|
* @param currentUserId 当前用户 ID,可为 null
|
||||||
|
* @return 评论树详情
|
||||||
|
*/
|
||||||
|
@JsonView(ResponseView.Public.class)
|
||||||
|
@RequestRateLimit
|
||||||
|
@GetMapping("/detail/tree")
|
||||||
|
public Comment detailTree(@RequestParam String id, @RequestParam(required = false) String currentUserId) {
|
||||||
|
return service.detailTree(id, currentUserId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package com.imyeyu.api.modules.common.controller;
|
||||||
|
|
||||||
|
import com.imyeyu.api.util.CaptchaManager;
|
||||||
|
import com.imyeyu.io.IO;
|
||||||
|
import com.imyeyu.java.bean.timi.TimiCode;
|
||||||
|
import com.imyeyu.java.bean.timi.TimiException;
|
||||||
|
import com.imyeyu.spring.TimiSpring;
|
||||||
|
import com.imyeyu.spring.annotation.AOPLog;
|
||||||
|
import com.imyeyu.utils.Decoder;
|
||||||
|
import jakarta.servlet.ServletOutputStream;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统接口
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2021-02-23 21:38
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Validated
|
||||||
|
@RestController
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@RequestMapping("/")
|
||||||
|
public class CommonController {
|
||||||
|
|
||||||
|
private final CaptchaManager captchaManager;
|
||||||
|
|
||||||
|
@AOPLog
|
||||||
|
@RequestMapping("")
|
||||||
|
public String root() {
|
||||||
|
return "IT WORKING! " + TimiSpring.getRequestIP();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取验证码
|
||||||
|
*
|
||||||
|
* @param width 宽度
|
||||||
|
* @param height 高度
|
||||||
|
* @return Base64 编码的图形验证码和唯一识别 ID
|
||||||
|
*/
|
||||||
|
@GetMapping("/captcha")
|
||||||
|
public CaptchaManager.Result captcha(Integer width, Integer height) {
|
||||||
|
TimiException.requiredTrue(width != null && 64 < width, "not found width or less than 64");
|
||||||
|
TimiException.requiredTrue(height != null && 19 < height, "not found height or less than 19");
|
||||||
|
return captchaManager.generate(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取验证码(返回图形数据流,唯一识别 ID 在返回头 X-Captcha-ID)
|
||||||
|
*
|
||||||
|
* @param width 宽度
|
||||||
|
* @param height 高度
|
||||||
|
*/
|
||||||
|
@GetMapping("/captcha/img")
|
||||||
|
public void captchaImg(Integer width, Integer height, HttpServletResponse resp) {
|
||||||
|
CaptchaManager.Result result = captchaManager.generate(width, height);
|
||||||
|
resp.setHeader("Pragma", "no-cache");
|
||||||
|
resp.setHeader("Cache-Control", "no-cache");
|
||||||
|
resp.setHeader("X-Captcha-ID", result.id());
|
||||||
|
resp.setHeader("Access-Control-Expose-Headers", "X-Captcha-ID");
|
||||||
|
resp.setDateHeader("Expires", -1);
|
||||||
|
resp.setContentType("image/png");
|
||||||
|
try {
|
||||||
|
ServletOutputStream os = resp.getOutputStream();
|
||||||
|
if (width < 64) {
|
||||||
|
ImageIO.write(captchaManager.error(TimiCode.ARG_BAD), "png", os);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 高度
|
||||||
|
if (height < 19) {
|
||||||
|
ImageIO.write(captchaManager.error(TimiCode.ARG_BAD), "png", os);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 输出图像流
|
||||||
|
IO.toOutputStream(os, Decoder.base64(result.data().split(",")[1]));
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("captcha img error", e);
|
||||||
|
resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
||||||
|
try {
|
||||||
|
ImageIO.write(captchaManager.error(TimiCode.ERROR), "png", resp.getOutputStream());
|
||||||
|
} catch (IOException subE) {
|
||||||
|
log.error("write error image fail", subE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
package com.imyeyu.api.modules.common.controller;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonView;
|
||||||
|
import com.imyeyu.api.annotation.RequireCorePermission;
|
||||||
|
import com.imyeyu.api.bean.CorePermissionCode;
|
||||||
|
import com.imyeyu.api.modules.common.entity.Feedback;
|
||||||
|
import com.imyeyu.api.modules.common.service.FeedbackService;
|
||||||
|
import com.imyeyu.api.modules.user.service.UserLoginService;
|
||||||
|
import com.imyeyu.spring.TimiSpring;
|
||||||
|
import com.imyeyu.spring.annotation.AOPLog;
|
||||||
|
import com.imyeyu.spring.annotation.CaptchaValid;
|
||||||
|
import com.imyeyu.spring.annotation.RequestRateLimit;
|
||||||
|
import com.imyeyu.spring.bean.CaptchaData;
|
||||||
|
import com.imyeyu.spring.bean.Page;
|
||||||
|
import com.imyeyu.spring.bean.PageResult;
|
||||||
|
import com.imyeyu.spring.util.ResponseView;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
/// 通用反馈接口
|
||||||
|
///
|
||||||
|
/// @author Codex
|
||||||
|
/// @since 2026-08-19
|
||||||
|
@RestController
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@RequestMapping("/feedback")
|
||||||
|
public class FeedbackController {
|
||||||
|
|
||||||
|
private final FeedbackService service;
|
||||||
|
private final UserLoginService userLoginService;
|
||||||
|
|
||||||
|
/// 提交反馈。登录用户和匿名用户均需提交图形验证码,匿名用户按 IP 限流
|
||||||
|
///
|
||||||
|
/// @param captchaData 带图形验证码的反馈请求
|
||||||
|
@AOPLog
|
||||||
|
@JsonView(ResponseView.Public.class)
|
||||||
|
@CaptchaValid
|
||||||
|
@RequestRateLimit(value = 3, inSeconds = 60)
|
||||||
|
@PostMapping("/create")
|
||||||
|
public void create(@RequestBody @Valid CaptchaData<Feedback> captchaData) {
|
||||||
|
Feedback feedback = captchaData.getData();
|
||||||
|
feedback.setIp(TimiSpring.getRequestIP());
|
||||||
|
if (userLoginService.isLogged()) {
|
||||||
|
feedback.setUserId(userLoginService.getRequireLoginUserId());
|
||||||
|
}
|
||||||
|
service.create(feedback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 查询反馈列表
|
||||||
|
///
|
||||||
|
/// @param page 分页参数
|
||||||
|
/// @return 反馈分页
|
||||||
|
@JsonView(ResponseView.Admin.class)
|
||||||
|
@RequestRateLimit
|
||||||
|
@RequireCorePermission(CorePermissionCode.FEEDBACK_READ)
|
||||||
|
@PostMapping("/list")
|
||||||
|
public PageResult<Feedback> list(@RequestBody Page<Feedback> page) {
|
||||||
|
return service.page(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 查询反馈详情
|
||||||
|
///
|
||||||
|
/// @param id 反馈 ID
|
||||||
|
/// @return 反馈详情
|
||||||
|
@JsonView(ResponseView.Admin.class)
|
||||||
|
@RequestRateLimit
|
||||||
|
@RequireCorePermission(CorePermissionCode.FEEDBACK_READ)
|
||||||
|
@PostMapping("/detail")
|
||||||
|
public Feedback detail(@RequestParam String id) {
|
||||||
|
return service.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 更新反馈
|
||||||
|
///
|
||||||
|
/// @param feedback 反馈
|
||||||
|
@AOPLog
|
||||||
|
@RequestRateLimit
|
||||||
|
@RequireCorePermission(CorePermissionCode.FEEDBACK_UPDATE)
|
||||||
|
@PostMapping("/update")
|
||||||
|
public void update(@RequestBody Feedback feedback) {
|
||||||
|
feedback.setRespondedBy(userLoginService.getRequireLoginUserId());
|
||||||
|
service.update(feedback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 删除反馈
|
||||||
|
///
|
||||||
|
/// @param id 反馈 ID
|
||||||
|
@AOPLog
|
||||||
|
@RequestRateLimit
|
||||||
|
@RequireCorePermission(CorePermissionCode.FEEDBACK_DELETE)
|
||||||
|
@PostMapping("/delete")
|
||||||
|
public void delete(@RequestParam String id) {
|
||||||
|
service.delete(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package com.imyeyu.api.modules.common.controller;
|
||||||
|
|
||||||
|
import com.imyeyu.api.modules.common.service.IconService;
|
||||||
|
import com.imyeyu.network.Network;
|
||||||
|
import com.imyeyu.spring.annotation.IgnoreGlobalReturn;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* timi-icon 前端接口
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2022-09-14 23:59
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Validated
|
||||||
|
@RestController
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@RequestMapping("/icon")
|
||||||
|
public class IconController {
|
||||||
|
|
||||||
|
private final IconService service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询全部图标名称
|
||||||
|
*
|
||||||
|
* @return 图标名称列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/name/list")
|
||||||
|
public List<String> listName() {
|
||||||
|
return service.listName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/svg/map")
|
||||||
|
public Map<String, String> mapSvg() {
|
||||||
|
return service.mapSvg();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/unicode/map")
|
||||||
|
public Map<String, String> mapUnicode() {
|
||||||
|
return service.mapUnicode();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出 TTF 字体文件
|
||||||
|
*
|
||||||
|
* @param resp HTTP 响应
|
||||||
|
* @throws IOException 写出字体文件失败
|
||||||
|
*/
|
||||||
|
@IgnoreGlobalReturn
|
||||||
|
@GetMapping(value = "/export/ttf", produces = "font/ttf")
|
||||||
|
public void exportTtf(HttpServletResponse resp) throws IOException {
|
||||||
|
byte[] body = service.exportTtf();
|
||||||
|
resp.setContentType("font/ttf");
|
||||||
|
resp.setContentLength(body.length);
|
||||||
|
resp.setHeader("Content-Disposition", Network.getFileDownloadHeader("timi-icon.ttf"));
|
||||||
|
resp.getOutputStream().write(body);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package com.imyeyu.api.modules.common.controller;
|
||||||
|
|
||||||
|
import com.imyeyu.api.modules.system.entity.Logger;
|
||||||
|
import com.imyeyu.api.modules.system.service.LoggerService;
|
||||||
|
import com.imyeyu.spring.annotation.AOPLog;
|
||||||
|
import com.imyeyu.spring.annotation.RequiredToken;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-06-13 09:31
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@RequestMapping("/log")
|
||||||
|
public class LoggerController {
|
||||||
|
|
||||||
|
private final LoggerService service;
|
||||||
|
|
||||||
|
@AOPLog
|
||||||
|
@RequiredToken
|
||||||
|
@PostMapping("/create")
|
||||||
|
public void create(@Valid @RequestBody Logger logger) {
|
||||||
|
service.create(logger);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
package com.imyeyu.api.modules.common.controller;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonView;
|
||||||
|
import com.imyeyu.api.annotation.RequireCorePermission;
|
||||||
|
import com.imyeyu.api.bean.CorePermissionCode;
|
||||||
|
import com.imyeyu.api.modules.common.entity.Multilingual;
|
||||||
|
import com.imyeyu.api.modules.common.service.MultilingualService;
|
||||||
|
import com.imyeyu.spring.annotation.AOPLog;
|
||||||
|
import com.imyeyu.spring.annotation.RequestRateLimit;
|
||||||
|
import com.imyeyu.spring.util.ResponseView;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 多语言管理接口
|
||||||
|
*
|
||||||
|
* @author Codex
|
||||||
|
* @since 2026-07-27
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@RequestMapping("/system/multilingual")
|
||||||
|
public class MultilingualController {
|
||||||
|
|
||||||
|
private final MultilingualService service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询多语言详情
|
||||||
|
*
|
||||||
|
* @param id 多语言 ID
|
||||||
|
* @return 多语言详情
|
||||||
|
*/
|
||||||
|
@JsonView(ResponseView.Admin.class)
|
||||||
|
@RequestRateLimit
|
||||||
|
@RequireCorePermission(CorePermissionCode.MULTILINGUAL_READ)
|
||||||
|
@PostMapping("/detail")
|
||||||
|
public Multilingual detail(@RequestParam String id) {
|
||||||
|
return service.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建多语言
|
||||||
|
*
|
||||||
|
* @param multilingual 多语言
|
||||||
|
* @return 创建结果
|
||||||
|
*/
|
||||||
|
@AOPLog
|
||||||
|
@JsonView(ResponseView.Admin.class)
|
||||||
|
@RequestRateLimit
|
||||||
|
@RequireCorePermission(CorePermissionCode.MULTILINGUAL_CREATE)
|
||||||
|
@PostMapping("/create")
|
||||||
|
public Multilingual create(@RequestBody Multilingual multilingual) {
|
||||||
|
service.create(multilingual);
|
||||||
|
return multilingual;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新多语言
|
||||||
|
*
|
||||||
|
* @param multilingual 多语言
|
||||||
|
*/
|
||||||
|
@AOPLog
|
||||||
|
@RequestRateLimit
|
||||||
|
@RequireCorePermission(CorePermissionCode.MULTILINGUAL_UPDATE)
|
||||||
|
@PostMapping("/update")
|
||||||
|
public void update(@RequestBody Multilingual multilingual) {
|
||||||
|
service.update(multilingual);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除多语言
|
||||||
|
*
|
||||||
|
* @param id 多语言 ID
|
||||||
|
*/
|
||||||
|
@AOPLog
|
||||||
|
@RequestRateLimit
|
||||||
|
@RequireCorePermission(CorePermissionCode.MULTILINGUAL_DELETE)
|
||||||
|
@PostMapping("/delete")
|
||||||
|
public void delete(@RequestParam String id) {
|
||||||
|
service.delete(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,174 @@
|
|||||||
|
package com.imyeyu.api.modules.common.controller;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonView;
|
||||||
|
import com.imyeyu.api.annotation.RequireCorePermission;
|
||||||
|
import com.imyeyu.api.bean.CorePermissionCode;
|
||||||
|
import com.imyeyu.api.modules.common.entity.Notify;
|
||||||
|
import com.imyeyu.api.modules.common.entity.NotifyDetail;
|
||||||
|
import com.imyeyu.api.modules.common.service.NotifyDetailService;
|
||||||
|
import com.imyeyu.api.modules.common.service.NotifyService;
|
||||||
|
import com.imyeyu.api.modules.user.entity.User;
|
||||||
|
import com.imyeyu.api.modules.user.service.UserLoginService;
|
||||||
|
import com.imyeyu.api.modules.user.service.UserService;
|
||||||
|
import com.imyeyu.java.TimiJava;
|
||||||
|
import com.imyeyu.spring.annotation.RequiredToken;
|
||||||
|
import com.imyeyu.spring.bean.Page;
|
||||||
|
import com.imyeyu.spring.bean.PageResult;
|
||||||
|
import com.imyeyu.spring.util.ResponseView;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/// 通知接口
|
||||||
|
///
|
||||||
|
/// @author 夜雨
|
||||||
|
/// @since 2026-08-20
|
||||||
|
@RestController
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@RequestMapping("/notify")
|
||||||
|
public class NotifyController {
|
||||||
|
|
||||||
|
private final UserService userService;
|
||||||
|
private final NotifyService service;
|
||||||
|
private final UserLoginService userLoginService;
|
||||||
|
private final NotifyDetailService detailService;
|
||||||
|
|
||||||
|
/// 创建通知,同时创建通知详情
|
||||||
|
///
|
||||||
|
/// @param notify 通知及通知详情
|
||||||
|
/// @return 创建后的通知
|
||||||
|
@RequireCorePermission(CorePermissionCode.NOTIFY_CREATE)
|
||||||
|
@RequiredToken
|
||||||
|
@PostMapping("/create")
|
||||||
|
public Notify create(@RequestBody Notify notify) {
|
||||||
|
service.create(notify);
|
||||||
|
return notify;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 查询通知详情
|
||||||
|
///
|
||||||
|
/// @param id 通知 ID
|
||||||
|
/// @return 通知及通知详情
|
||||||
|
@JsonView(ResponseView.Public.class)
|
||||||
|
@RequireCorePermission(CorePermissionCode.NOTIFY_READ)
|
||||||
|
@RequiredToken
|
||||||
|
@RequestMapping("/detail")
|
||||||
|
public Notify detail(@RequestParam String id) {
|
||||||
|
Notify notify = service.get(id);
|
||||||
|
{
|
||||||
|
List<NotifyDetail> detailList = detailService.listByNotifyId(id);
|
||||||
|
Set<String> userIdSet = detailList.stream()
|
||||||
|
.filter(item -> item.getMsgType() == NotifyDetail.MsgType.INTERNAL)
|
||||||
|
.flatMap(item -> Stream.of(item.getSendFrom(), item.getSendTo()))
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
Map<String, User> userMap = userService.mapByIdList(userIdSet);
|
||||||
|
for (NotifyDetail detail : detailList) {
|
||||||
|
if (TimiJava.isNotEmpty(detail.getSendFrom())) {
|
||||||
|
detail.setFromUser(userMap.get(detail.getSendFrom()));
|
||||||
|
}
|
||||||
|
if (TimiJava.isNotEmpty(detail.getSendTo())) {
|
||||||
|
detail.setToUser(userMap.get(detail.getSendTo()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
notify.setDetailList(detailList);
|
||||||
|
}
|
||||||
|
return notify;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 删除通知及其通知详情
|
||||||
|
///
|
||||||
|
/// @param id 通知 ID
|
||||||
|
@RequireCorePermission(CorePermissionCode.NOTIFY_DELETE)
|
||||||
|
@RequiredToken
|
||||||
|
@PostMapping("/delete")
|
||||||
|
public void delete(@RequestParam String id) {
|
||||||
|
service.delete(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 取消尚未成功发送的通知详情
|
||||||
|
///
|
||||||
|
/// @param id 通知 ID
|
||||||
|
@RequireCorePermission(CorePermissionCode.NOTIFY_CANCEL)
|
||||||
|
@RequiredToken
|
||||||
|
@PostMapping("/cancel")
|
||||||
|
public void cancel(@RequestParam String id) {
|
||||||
|
service.cancel(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 查询当前用户的站内通知
|
||||||
|
///
|
||||||
|
/// @param page 分页参数
|
||||||
|
/// @param unreadOnly true 为只查询未读通知
|
||||||
|
/// @return 站内通知分页结果
|
||||||
|
@RequireCorePermission(CorePermissionCode.NOTIFY_READ)
|
||||||
|
@RequiredToken
|
||||||
|
@PostMapping("/internal/internal")
|
||||||
|
public PageResult<NotifyDetail> listInternal(
|
||||||
|
@RequestBody(required = false) Page<NotifyDetail> page,
|
||||||
|
@RequestParam(defaultValue = "false") boolean unreadOnly
|
||||||
|
) {
|
||||||
|
return detailService.pageInternalByUserId(page, userLoginService.getRequireLoginUserId(), unreadOnly);
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonView(ResponseView.Public.class)
|
||||||
|
@RequireCorePermission(CorePermissionCode.NOTIFY_READ)
|
||||||
|
@RequiredToken
|
||||||
|
@PostMapping("/internal/detail")
|
||||||
|
public NotifyDetail internalDetail(@RequestParam String id) {
|
||||||
|
NotifyDetail detail = detailService.getInternalByUserId(id, userLoginService.getRequireLoginUserId());
|
||||||
|
if (TimiJava.isNotEmpty(detail.getSendFrom())) {
|
||||||
|
detail.setFromUser(userService.get(detail.getSendFrom()));
|
||||||
|
}
|
||||||
|
if (TimiJava.isNotEmpty(detail.getSendTo())) {
|
||||||
|
detail.setToUser(userService.get(detail.getSendTo()));
|
||||||
|
}
|
||||||
|
return detail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 删除当前用户的一条站内通知
|
||||||
|
///
|
||||||
|
/// @param id 通知详情 ID
|
||||||
|
@RequireCorePermission(CorePermissionCode.NOTIFY_READ)
|
||||||
|
@RequiredToken
|
||||||
|
@PostMapping("/internal/delete")
|
||||||
|
public void deleteInternal(@RequestParam String id) {
|
||||||
|
detailService.deleteInternalByUserId(id, userLoginService.getRequireLoginUserId());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 查询当前用户未读通知数量
|
||||||
|
///
|
||||||
|
/// @return 未读通知数量
|
||||||
|
@RequireCorePermission(CorePermissionCode.NOTIFY_READ)
|
||||||
|
@RequiredToken
|
||||||
|
@PostMapping("/internal/unread/count")
|
||||||
|
public long countUnreadSite() {
|
||||||
|
return detailService.countUnreadSiteByUserId(userLoginService.getRequireLoginUserId());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 标记当前用户的一条站内通知已读
|
||||||
|
///
|
||||||
|
/// @param id 通知详情 ID
|
||||||
|
@RequireCorePermission(CorePermissionCode.NOTIFY_READ)
|
||||||
|
@RequiredToken
|
||||||
|
@PostMapping("/internal/read")
|
||||||
|
public void markSiteRead(@RequestParam String id) {
|
||||||
|
detailService.markSiteRead(userLoginService.getRequireLoginUserId(), id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 标记当前用户全部站内通知已读
|
||||||
|
@RequireCorePermission(CorePermissionCode.NOTIFY_READ)
|
||||||
|
@RequiredToken
|
||||||
|
@PostMapping("/internal/read/all")
|
||||||
|
public void markAllSiteRead() {
|
||||||
|
detailService.markAllSiteRead(userLoginService.getRequireLoginUserId());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
package com.imyeyu.api.modules.common.controller;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonView;
|
||||||
|
import com.imyeyu.api.annotation.RequireCorePermission;
|
||||||
|
import com.imyeyu.api.bean.CorePermissionCode;
|
||||||
|
import com.imyeyu.api.modules.common.entity.Setting;
|
||||||
|
import com.imyeyu.api.modules.common.service.SettingService;
|
||||||
|
import com.imyeyu.spring.annotation.AOPLog;
|
||||||
|
import com.imyeyu.spring.annotation.RequestRateLimit;
|
||||||
|
import com.imyeyu.spring.bean.Page;
|
||||||
|
import com.imyeyu.spring.bean.PageResult;
|
||||||
|
import com.imyeyu.spring.util.ResponseView;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-04-23 16:31
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@RequestMapping("/system/setting")
|
||||||
|
public class SettingController {
|
||||||
|
|
||||||
|
private final SettingService service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询配置列表
|
||||||
|
*
|
||||||
|
* @param page 分页查询
|
||||||
|
* @return 配置列表
|
||||||
|
*/
|
||||||
|
@JsonView(ResponseView.Admin.class)
|
||||||
|
@RequestRateLimit
|
||||||
|
@RequireCorePermission(CorePermissionCode.SETTING_READ)
|
||||||
|
@PostMapping("/list")
|
||||||
|
public PageResult<Setting> list(@RequestBody Page<Setting> page) {
|
||||||
|
return service.page(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询配置详情
|
||||||
|
*
|
||||||
|
* @param id 配置 ID
|
||||||
|
* @return 配置详情
|
||||||
|
*/
|
||||||
|
@JsonView(ResponseView.Admin.class)
|
||||||
|
@RequestRateLimit
|
||||||
|
@PostMapping("/detail")
|
||||||
|
public Setting detail(@RequestBody String id) {
|
||||||
|
return service.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建配置
|
||||||
|
*
|
||||||
|
* @param setting 配置
|
||||||
|
* @return 配置
|
||||||
|
*/
|
||||||
|
@AOPLog
|
||||||
|
@JsonView(ResponseView.Admin.class)
|
||||||
|
@RequestRateLimit
|
||||||
|
@PostMapping("/create")
|
||||||
|
public Setting create(@RequestBody Setting setting) {
|
||||||
|
service.create(setting);
|
||||||
|
return setting;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新配置
|
||||||
|
*
|
||||||
|
* @param setting 配置
|
||||||
|
*/
|
||||||
|
@AOPLog
|
||||||
|
@RequestRateLimit
|
||||||
|
@PostMapping("/update")
|
||||||
|
public void update(@RequestBody Setting setting) {
|
||||||
|
service.update(setting);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除配置
|
||||||
|
*
|
||||||
|
* @param id 配置 ID
|
||||||
|
*/
|
||||||
|
@AOPLog
|
||||||
|
@RequestRateLimit
|
||||||
|
@RequireCorePermission(CorePermissionCode.SETTING_DELETE)
|
||||||
|
@PostMapping("/delete")
|
||||||
|
public void delete(@RequestBody String id) {
|
||||||
|
service.delete(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
package com.imyeyu.api.modules.common.controller;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonView;
|
||||||
|
import com.imyeyu.api.modules.common.entity.Tag;
|
||||||
|
import com.imyeyu.api.modules.common.entity.TagApply;
|
||||||
|
import com.imyeyu.api.modules.common.service.TagService;
|
||||||
|
import com.imyeyu.spring.annotation.AOPLog;
|
||||||
|
import com.imyeyu.spring.annotation.RequestRateLimit;
|
||||||
|
import com.imyeyu.spring.bean.Page;
|
||||||
|
import com.imyeyu.spring.bean.PageResult;
|
||||||
|
import com.imyeyu.spring.util.ResponseView;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/// 标签接口
|
||||||
|
///
|
||||||
|
/// @author 夜雨
|
||||||
|
/// @since 2026-08-12 15:09
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@RequestMapping("/tag")
|
||||||
|
public class TagController {
|
||||||
|
|
||||||
|
private final TagService service;
|
||||||
|
|
||||||
|
/// 查询标签列表
|
||||||
|
///
|
||||||
|
/// @param page 分页参数
|
||||||
|
/// @return 标签分页
|
||||||
|
@RequestRateLimit
|
||||||
|
@JsonView(ResponseView.Public.class)
|
||||||
|
@PostMapping("/list")
|
||||||
|
public PageResult<Tag> list(@RequestBody Page<Tag> page) {
|
||||||
|
return service.page(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 查询标签详情
|
||||||
|
///
|
||||||
|
/// @param id 标签 ID
|
||||||
|
/// @return 标签详情
|
||||||
|
@RequestRateLimit
|
||||||
|
@JsonView(ResponseView.Public.class)
|
||||||
|
@PostMapping("/detail")
|
||||||
|
public Tag detail(@RequestParam String id) {
|
||||||
|
return service.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 创建标签
|
||||||
|
///
|
||||||
|
/// @param tag 标签
|
||||||
|
/// @return 标签
|
||||||
|
@AOPLog
|
||||||
|
@RequestRateLimit
|
||||||
|
@JsonView(ResponseView.Public.class)
|
||||||
|
@PostMapping("/create")
|
||||||
|
public Tag create(@RequestBody Tag tag) {
|
||||||
|
service.create(tag);
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 更新标签
|
||||||
|
///
|
||||||
|
/// @param tag 标签
|
||||||
|
@AOPLog
|
||||||
|
@RequestRateLimit
|
||||||
|
@PostMapping("/update")
|
||||||
|
public void update(@RequestBody Tag tag) {
|
||||||
|
service.update(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 删除标签
|
||||||
|
///
|
||||||
|
/// @param id 标签 ID
|
||||||
|
@AOPLog
|
||||||
|
@RequestRateLimit
|
||||||
|
@PostMapping("/delete")
|
||||||
|
public void delete(@RequestParam String id) {
|
||||||
|
service.delete(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 按业务查询标签
|
||||||
|
///
|
||||||
|
/// @param bizType 业务类型
|
||||||
|
/// @param bizId 业务 ID
|
||||||
|
/// @return 标签列表
|
||||||
|
@RequestRateLimit
|
||||||
|
@JsonView(ResponseView.Public.class)
|
||||||
|
@GetMapping("/list/biz/id")
|
||||||
|
public List<Tag> listByBizType(@RequestParam TagApply.BizType bizType, @RequestParam String bizId) {
|
||||||
|
return service.listByBizType(bizType, bizId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 差分保存业务标签
|
||||||
|
///
|
||||||
|
/// @param apply 标签请求
|
||||||
|
@AOPLog
|
||||||
|
@RequestRateLimit
|
||||||
|
@PostMapping("/apply")
|
||||||
|
public void apply(@RequestBody TagApply apply) {
|
||||||
|
service.apply(apply.getBizType(), apply.getBizId(), apply.getTagIdList());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
package com.imyeyu.api.modules.common.entity;
|
||||||
|
|
||||||
|
import com.imyeyu.api.modules.common.bean.attachment.SettingSupport;
|
||||||
|
import com.imyeyu.spring.annotation.table.PageIgnore;
|
||||||
|
import com.imyeyu.spring.annotation.table.Transient;
|
||||||
|
import com.imyeyu.spring.entity.UUIDEntity;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文章
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2021-03-01 17:10
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class Article extends UUIDEntity implements SettingSupport {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 类型
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-06-05 15:55
|
||||||
|
*/
|
||||||
|
public enum BizType {
|
||||||
|
|
||||||
|
/** 博客 */
|
||||||
|
BLOG,
|
||||||
|
|
||||||
|
/// 反馈
|
||||||
|
FEEDBACK
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 正文类型
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-06-05 15:56
|
||||||
|
*/
|
||||||
|
public enum ContentType {
|
||||||
|
|
||||||
|
/** 纯文本 */
|
||||||
|
TEXT,
|
||||||
|
|
||||||
|
/** Markdown */
|
||||||
|
MARKDOWN,
|
||||||
|
|
||||||
|
/** TipTap 富文本 */
|
||||||
|
TIP_TAP
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 文章状态
|
||||||
|
public enum Status {
|
||||||
|
|
||||||
|
/// 草稿
|
||||||
|
DART,
|
||||||
|
|
||||||
|
/// 正常
|
||||||
|
NORMAL,
|
||||||
|
|
||||||
|
/// 隐藏
|
||||||
|
HIDDEN
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 业务类型 */
|
||||||
|
protected BizType bizType;
|
||||||
|
|
||||||
|
/** 业务 ID */
|
||||||
|
protected String bizId;
|
||||||
|
|
||||||
|
/** 标题 */
|
||||||
|
protected String title;
|
||||||
|
|
||||||
|
/** 描述 */
|
||||||
|
protected String description;
|
||||||
|
|
||||||
|
/** 正文类型 */
|
||||||
|
protected ContentType contentType;
|
||||||
|
|
||||||
|
/** 内容 */
|
||||||
|
@PageIgnore
|
||||||
|
protected String content;
|
||||||
|
|
||||||
|
/** 阅读数量 */
|
||||||
|
protected Integer reads;
|
||||||
|
|
||||||
|
/** 喜欢数量 */
|
||||||
|
protected Integer likes;
|
||||||
|
|
||||||
|
/** 文章状态 */
|
||||||
|
protected Status status;
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
protected List<Setting> settingList;
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
protected List<Comment> commentList;
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
protected List<Attachment> attachmentList;
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
package com.imyeyu.api.modules.common.entity;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
import com.imyeyu.api.TimiServerAPI;
|
||||||
|
import com.imyeyu.api.modules.common.bean.attachment.AccessKey;
|
||||||
|
import com.imyeyu.api.modules.common.service.AttachmentService;
|
||||||
|
import com.imyeyu.java.TimiJava;
|
||||||
|
import com.imyeyu.java.ref.Ref;
|
||||||
|
import com.imyeyu.spring.TimiSpring;
|
||||||
|
import com.imyeyu.spring.annotation.table.Transient;
|
||||||
|
import com.imyeyu.spring.entity.UUIDEntity;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2023-08-15 10:17
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class Attachment extends UUIDEntity {
|
||||||
|
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// @author 夜雨
|
||||||
|
/// @since 2026-07-17 20:52
|
||||||
|
public enum BizType {
|
||||||
|
|
||||||
|
ARTICLE,
|
||||||
|
|
||||||
|
USER,
|
||||||
|
|
||||||
|
GAO_CUSTOMER,
|
||||||
|
|
||||||
|
GAO_EVENT_RECORD,
|
||||||
|
|
||||||
|
TEMP_FILE,
|
||||||
|
|
||||||
|
MIRROR
|
||||||
|
}
|
||||||
|
|
||||||
|
protected BizType bizType;
|
||||||
|
|
||||||
|
protected String bizId;
|
||||||
|
|
||||||
|
protected String attachType;
|
||||||
|
|
||||||
|
protected String mongoId;
|
||||||
|
|
||||||
|
protected String title;
|
||||||
|
|
||||||
|
protected String name;
|
||||||
|
|
||||||
|
protected String mimeType;
|
||||||
|
|
||||||
|
protected ObjectNode metadata;
|
||||||
|
|
||||||
|
protected Long size;
|
||||||
|
|
||||||
|
protected String md5;
|
||||||
|
|
||||||
|
protected String uploaderIp;
|
||||||
|
|
||||||
|
protected Boolean isDestroyed;
|
||||||
|
|
||||||
|
protected Long destroyAt;
|
||||||
|
|
||||||
|
/** 数据流 */
|
||||||
|
@Transient
|
||||||
|
protected InputStream inputStream;
|
||||||
|
|
||||||
|
/** 保存自临时文件 ID */
|
||||||
|
@Transient
|
||||||
|
protected String tempFileId;
|
||||||
|
|
||||||
|
/** 访问密钥 */
|
||||||
|
@Transient
|
||||||
|
protected AccessKey accessKey;
|
||||||
|
|
||||||
|
/** 请求文件 */
|
||||||
|
@Transient
|
||||||
|
protected MultipartFile file;
|
||||||
|
|
||||||
|
public String buildMongoName() {
|
||||||
|
StringBuilder result = new StringBuilder(bizType.name());
|
||||||
|
if (TimiJava.isNotEmpty(attachType)) {
|
||||||
|
result.append('_').append(attachType.toUpperCase());
|
||||||
|
}
|
||||||
|
return result.append('_').append(name).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public InputStream openInputStream() {
|
||||||
|
AttachmentService service = TimiServerAPI.applicationContext.getBean(AttachmentService.class);
|
||||||
|
return service.getInputStreamByMongoId(mongoId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAttachTypeValue(Enum<?> attachType) {
|
||||||
|
this.attachType = attachType.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T extends Enum<T>> T getAttachTypeValue(Class<T> attachTypeClass) {
|
||||||
|
return Ref.toType(attachTypeClass, attachType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 执行接口流式返回 */
|
||||||
|
public void doResponse() throws IOException {
|
||||||
|
TimiSpring.responseRangeStream(openInputStream(), size);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package com.imyeyu.api.modules.common.entity;
|
||||||
|
|
||||||
|
import com.imyeyu.spring.annotation.table.AutoUUID;
|
||||||
|
import com.imyeyu.spring.annotation.table.Id;
|
||||||
|
import com.imyeyu.spring.entity.Creatable;
|
||||||
|
import com.imyeyu.spring.entity.Destroyable;
|
||||||
|
import com.imyeyu.spring.entity.IDEntity;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/// 通知基础队列实体
|
||||||
|
///
|
||||||
|
/// @author 夜雨
|
||||||
|
/// @since 2026-08-20
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class BaseQueueEntity implements IDEntity<String>, Creatable, Destroyable {
|
||||||
|
|
||||||
|
/** 主键 */
|
||||||
|
@Id
|
||||||
|
@AutoUUID
|
||||||
|
protected String id;
|
||||||
|
|
||||||
|
/** 所属通知详情 ID */
|
||||||
|
protected String notifyDetailId;
|
||||||
|
|
||||||
|
/** 内容 */
|
||||||
|
protected String data;
|
||||||
|
|
||||||
|
/** 参数 */
|
||||||
|
protected String args;
|
||||||
|
|
||||||
|
/** 发送去向 */
|
||||||
|
protected String sendTo;
|
||||||
|
|
||||||
|
/** 发送时间 */
|
||||||
|
protected Long sendAt;
|
||||||
|
|
||||||
|
/** 剩余重试次数 */
|
||||||
|
protected Integer retry;
|
||||||
|
|
||||||
|
/** 超时时间 */
|
||||||
|
protected Long timeoutAt;
|
||||||
|
|
||||||
|
/** 创建时间 */
|
||||||
|
protected Long createdAt;
|
||||||
|
|
||||||
|
/// 从通知详情创建队列实体
|
||||||
|
///
|
||||||
|
/// @param detail 通知详情
|
||||||
|
public BaseQueueEntity(NotifyDetail detail) {
|
||||||
|
notifyDetailId = detail.getId();
|
||||||
|
data = detail.getData();
|
||||||
|
args = detail.getArgs();
|
||||||
|
sendTo = detail.getSendTo();
|
||||||
|
sendAt = detail.getSendAt();
|
||||||
|
retry = detail.getRetry();
|
||||||
|
timeoutAt = detail.getTimeoutAt();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
package com.imyeyu.api.modules.common.entity;
|
||||||
|
|
||||||
|
import com.imyeyu.api.modules.user.entity.User;
|
||||||
|
import com.imyeyu.spring.annotation.table.Transient;
|
||||||
|
import com.imyeyu.spring.entity.UUIDEntity;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 评论
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-05-27 17:20
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class Comment extends UUIDEntity {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 评论状态
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-05-27 17:20
|
||||||
|
*/
|
||||||
|
public enum Status {
|
||||||
|
|
||||||
|
/** 正常 */
|
||||||
|
NORMAL,
|
||||||
|
|
||||||
|
/** 隐藏 */
|
||||||
|
HIDDEN
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 业务类型 */
|
||||||
|
protected String bizType;
|
||||||
|
|
||||||
|
/** 业务 ID */
|
||||||
|
protected String bizId;
|
||||||
|
|
||||||
|
/** 父评论 ID,主评论为 null */
|
||||||
|
protected String parentId;
|
||||||
|
|
||||||
|
/** 根评论 ID,主评论为自身 ID */
|
||||||
|
protected String rootId;
|
||||||
|
|
||||||
|
/** 评论用户 ID */
|
||||||
|
protected String userId;
|
||||||
|
|
||||||
|
/** 评论用户昵称,游客评论时使用 */
|
||||||
|
protected String userNick;
|
||||||
|
|
||||||
|
/** 被回复用户 ID */
|
||||||
|
protected String replyUserId;
|
||||||
|
|
||||||
|
/** 被回复用户昵称,被回复人为游客时使用 */
|
||||||
|
protected String replyUserNick;
|
||||||
|
|
||||||
|
/** 评论层级,1 为主评论 */
|
||||||
|
protected Integer level;
|
||||||
|
|
||||||
|
/** 评论内容 */
|
||||||
|
@NotBlank
|
||||||
|
protected String content;
|
||||||
|
|
||||||
|
/** IP 地址 */
|
||||||
|
protected String ip;
|
||||||
|
|
||||||
|
/** 回复数 */
|
||||||
|
protected Integer replyCount;
|
||||||
|
|
||||||
|
/** 点赞数 */
|
||||||
|
protected Integer likeCount;
|
||||||
|
|
||||||
|
/** 评论状态 */
|
||||||
|
protected Status status;
|
||||||
|
|
||||||
|
/** true 为置顶 */
|
||||||
|
protected Boolean isTop;
|
||||||
|
|
||||||
|
/** 最后回复时间 */
|
||||||
|
protected Long lastReplyAt;
|
||||||
|
|
||||||
|
/** 评论用户信息 */
|
||||||
|
@Transient
|
||||||
|
protected User user;
|
||||||
|
|
||||||
|
/** 被回复用户信息 */
|
||||||
|
@Transient
|
||||||
|
protected User replyUser;
|
||||||
|
|
||||||
|
/** 子评论列表 */
|
||||||
|
@Transient
|
||||||
|
protected List<Comment> replyList;
|
||||||
|
|
||||||
|
/** true 为当前用户已点赞 */
|
||||||
|
@Transient
|
||||||
|
protected Boolean liked;
|
||||||
|
|
||||||
|
/** true 为当前用户已不喜欢 */
|
||||||
|
@Transient
|
||||||
|
protected Boolean unliked;
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package com.imyeyu.api.modules.common.entity;
|
||||||
|
|
||||||
|
import com.imyeyu.spring.annotation.table.Transient;
|
||||||
|
import com.imyeyu.spring.entity.UUIDEntity;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据字典
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-05-13 14:40
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class DataDict extends UUIDEntity {
|
||||||
|
|
||||||
|
/** 业务类型 */
|
||||||
|
private String bizType;
|
||||||
|
|
||||||
|
/** 代码 */
|
||||||
|
private String code;
|
||||||
|
|
||||||
|
/** 父级代码 */
|
||||||
|
private String parentCode;
|
||||||
|
|
||||||
|
/** 字典名称语言键 */
|
||||||
|
private String nameLangId;
|
||||||
|
|
||||||
|
/** 字典名称(读取) */
|
||||||
|
@Transient
|
||||||
|
protected String name;
|
||||||
|
|
||||||
|
/** 搜索名称 */
|
||||||
|
@Transient
|
||||||
|
protected String searchName;
|
||||||
|
|
||||||
|
/** 字典中文名称(写入) */
|
||||||
|
@Transient
|
||||||
|
protected String zhCN;
|
||||||
|
|
||||||
|
/** 选项列表 */
|
||||||
|
@Transient
|
||||||
|
protected List<DataDictOption> dataDictOptionList;
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package com.imyeyu.api.modules.common.entity;
|
||||||
|
|
||||||
|
import com.imyeyu.spring.annotation.table.Transient;
|
||||||
|
import com.imyeyu.spring.entity.UUIDEntity;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据字典选项
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-05-13 14:40
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class DataDictOption extends UUIDEntity {
|
||||||
|
|
||||||
|
/** 所属字典 ID */
|
||||||
|
private String dictId;
|
||||||
|
|
||||||
|
/** 值代码 */
|
||||||
|
private String valueCode;
|
||||||
|
|
||||||
|
/** 父级值代码 */
|
||||||
|
private String valueParentCode;
|
||||||
|
|
||||||
|
/** 值语言键 */
|
||||||
|
private String valueLangId;
|
||||||
|
|
||||||
|
/** 值(读取) */
|
||||||
|
@Transient
|
||||||
|
protected String value;
|
||||||
|
|
||||||
|
/** 中文值(写入) */
|
||||||
|
@Transient
|
||||||
|
protected String zhCN;
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
protected String searchValue;
|
||||||
|
|
||||||
|
/** 所属字典 */
|
||||||
|
@Transient
|
||||||
|
protected DataDict dataDict;
|
||||||
|
|
||||||
|
/** 附加参数设置 */
|
||||||
|
@Transient
|
||||||
|
protected List<Setting> settingList;
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package com.imyeyu.api.modules.common.entity;
|
||||||
|
|
||||||
|
import com.imyeyu.spring.annotation.table.AutoUUID;
|
||||||
|
import com.imyeyu.spring.annotation.table.DeleteColumn;
|
||||||
|
import com.imyeyu.spring.annotation.table.Id;
|
||||||
|
import com.imyeyu.spring.entity.Creatable;
|
||||||
|
import com.imyeyu.spring.entity.Deletable;
|
||||||
|
import com.imyeyu.spring.entity.IDEntity;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 邮件过滤
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-05-13 14:40
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class EmailFilter implements IDEntity<String>, Creatable, Deletable {
|
||||||
|
|
||||||
|
/** 主键 */
|
||||||
|
@Id
|
||||||
|
@AutoUUID
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
/** 邮箱 */
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
/** 创建时间 */
|
||||||
|
private Long createdAt;
|
||||||
|
|
||||||
|
/** 删除时间 */
|
||||||
|
@DeleteColumn
|
||||||
|
private Long deletedAt;
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package com.imyeyu.api.modules.common.entity;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonView;
|
||||||
|
import com.imyeyu.api.bean.ModuleCode;
|
||||||
|
import com.imyeyu.spring.annotation.table.Transient;
|
||||||
|
import com.imyeyu.spring.entity.UUIDEntity;
|
||||||
|
import com.imyeyu.spring.util.ResponseView;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/// 通用反馈
|
||||||
|
///
|
||||||
|
/// @author Codex
|
||||||
|
/// @since 2026-08-19
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class Feedback extends UUIDEntity {
|
||||||
|
|
||||||
|
/// 反馈处理状态
|
||||||
|
public enum Status {
|
||||||
|
|
||||||
|
/// 待处理
|
||||||
|
PENDING,
|
||||||
|
|
||||||
|
/// 已回复
|
||||||
|
REPLIED,
|
||||||
|
|
||||||
|
/// 已关闭
|
||||||
|
CLOSED
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 所属模块
|
||||||
|
@JsonView(ResponseView.Public.class)
|
||||||
|
private ModuleCode module;
|
||||||
|
|
||||||
|
/// 提交用户 ID,匿名反馈为 null
|
||||||
|
@JsonView(ResponseView.Admin.class)
|
||||||
|
private String userId;
|
||||||
|
|
||||||
|
/// 反馈内容文章 ID
|
||||||
|
@JsonView(ResponseView.Public.class)
|
||||||
|
private String contentId;
|
||||||
|
|
||||||
|
/// 联系方式
|
||||||
|
@JsonView(ResponseView.Public.class)
|
||||||
|
private String contact;
|
||||||
|
|
||||||
|
/// 提交 IP
|
||||||
|
@JsonView(ResponseView.Admin.class)
|
||||||
|
private String ip;
|
||||||
|
|
||||||
|
/// 处理状态
|
||||||
|
@JsonView(ResponseView.Public.class)
|
||||||
|
private Status status;
|
||||||
|
|
||||||
|
/// 管理员回复文章 ID
|
||||||
|
@JsonView(ResponseView.Public.class)
|
||||||
|
private String responseId;
|
||||||
|
|
||||||
|
/// 回复用户 ID
|
||||||
|
@JsonView(ResponseView.Admin.class)
|
||||||
|
private String respondedBy;
|
||||||
|
|
||||||
|
/// 回复时间
|
||||||
|
@JsonView(ResponseView.Public.class)
|
||||||
|
private Long respondedAt;
|
||||||
|
|
||||||
|
/// 管理员回复文章
|
||||||
|
@Transient
|
||||||
|
@JsonView(ResponseView.Public.class)
|
||||||
|
private Article content;
|
||||||
|
|
||||||
|
/// 管理员回复文章
|
||||||
|
@Transient
|
||||||
|
@JsonView(ResponseView.Public.class)
|
||||||
|
private Article response;
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.imyeyu.api.modules.common.entity;
|
||||||
|
|
||||||
|
import com.imyeyu.spring.annotation.table.Table;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
/// HTTP 回调队列
|
||||||
|
///
|
||||||
|
/// @author 夜雨
|
||||||
|
/// @since 2026-08-20
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@Table("http_queue")
|
||||||
|
public class HTTPQueue extends BaseQueueEntity {
|
||||||
|
|
||||||
|
/** HTTP 请求方法 */
|
||||||
|
protected String method;
|
||||||
|
|
||||||
|
/// 从通知详情创建 HTTP 队列
|
||||||
|
///
|
||||||
|
/// @param detail 通知详情
|
||||||
|
public HTTPQueue(NotifyDetail detail) {
|
||||||
|
super(detail);
|
||||||
|
method = "POST";
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-3
@@ -1,8 +1,8 @@
|
|||||||
package com.imyeyu.server.modules.common.entity;
|
package com.imyeyu.api.modules.common.entity;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import com.imyeyu.spring.entity.Entity;
|
import com.imyeyu.spring.entity.UUIDEntity;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 字体图标
|
* 字体图标
|
||||||
@@ -12,7 +12,7 @@ import com.imyeyu.spring.entity.Entity;
|
|||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
public class Icon extends Entity {
|
public class Icon extends UUIDEntity {
|
||||||
|
|
||||||
/** 名称 */
|
/** 名称 */
|
||||||
private String name;
|
private String name;
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package com.imyeyu.api.modules.common.entity;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/// 邮件发送队列
|
||||||
|
///
|
||||||
|
/// @author 夜雨
|
||||||
|
/// @since 2026-08-20
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class MailQueue extends BaseQueueEntity {
|
||||||
|
|
||||||
|
/** 邮件标题 */
|
||||||
|
protected String subject;
|
||||||
|
|
||||||
|
/** 邮件附件 */
|
||||||
|
protected String attachments;
|
||||||
|
|
||||||
|
/** 发送来源 */
|
||||||
|
protected String sendFrom;
|
||||||
|
|
||||||
|
/// 从通知详情创建邮件队列
|
||||||
|
///
|
||||||
|
/// @param detail 通知详情
|
||||||
|
public MailQueue(NotifyDetail detail) {
|
||||||
|
super(detail);
|
||||||
|
subject = detail.getSubject();
|
||||||
|
sendFrom = detail.getSendFrom();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 邮件附件
|
||||||
|
@Data
|
||||||
|
public static class Attachment {
|
||||||
|
|
||||||
|
/** 附件类型 */
|
||||||
|
private Type type;
|
||||||
|
|
||||||
|
/** 附件值 */
|
||||||
|
private String value;
|
||||||
|
|
||||||
|
/// 附件来源类型
|
||||||
|
public enum Type {
|
||||||
|
|
||||||
|
/** 系统附件 */
|
||||||
|
E2INS_ATTACH,
|
||||||
|
|
||||||
|
/** URL 附件 */
|
||||||
|
URL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package com.imyeyu.api.modules.common.entity;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import com.imyeyu.java.bean.Language;
|
||||||
|
import com.imyeyu.spring.annotation.table.Column;
|
||||||
|
import com.imyeyu.spring.entity.UUIDEntity;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2023-10-24 16:41
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class Multilingual extends UUIDEntity {
|
||||||
|
|
||||||
|
protected String zhCN;
|
||||||
|
|
||||||
|
@Column("zh_cn_pinyin_full")
|
||||||
|
@JsonIgnore
|
||||||
|
protected String zhCNPinyinFull;
|
||||||
|
|
||||||
|
@Column("zh_cn_pinyin_initial")
|
||||||
|
@JsonIgnore
|
||||||
|
protected String zhCNPinyinInitial;
|
||||||
|
|
||||||
|
@Column("zh_cn_pinyin_mixed")
|
||||||
|
@JsonIgnore
|
||||||
|
protected String zhCNPinyinMixed;
|
||||||
|
|
||||||
|
protected String zhTW;
|
||||||
|
|
||||||
|
protected String enUS;
|
||||||
|
|
||||||
|
protected String ruRU;
|
||||||
|
|
||||||
|
protected String jaJP;
|
||||||
|
|
||||||
|
protected String deDE;
|
||||||
|
|
||||||
|
protected String koKR;
|
||||||
|
|
||||||
|
protected Long lastAccessAt;
|
||||||
|
|
||||||
|
public String getValue(Language.Enum language) {
|
||||||
|
return switch (language) {
|
||||||
|
case zh_CN -> zhCN;
|
||||||
|
case zh_TW -> zhTW;
|
||||||
|
case en_US -> enUS;
|
||||||
|
case ru_RU -> ruRU;
|
||||||
|
case ja_JP -> jaJP;
|
||||||
|
case de_DE -> deDE;
|
||||||
|
case ko_KR -> koKR;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.imyeyu.api.modules.common.entity;
|
||||||
|
|
||||||
|
import com.imyeyu.spring.annotation.table.Transient;
|
||||||
|
import com.imyeyu.spring.entity.UUIDEntity;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-05-13 14:40
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class Notify extends UUIDEntity {
|
||||||
|
|
||||||
|
/** 业务类型 */
|
||||||
|
private String bizType;
|
||||||
|
|
||||||
|
/** 提醒业务类型 */
|
||||||
|
private String bizRemindedType;
|
||||||
|
|
||||||
|
/** 提醒业务 ID */
|
||||||
|
private String bizRemindedId;
|
||||||
|
|
||||||
|
/** 参数 */
|
||||||
|
private String args;
|
||||||
|
|
||||||
|
/** 创建者 */
|
||||||
|
private String createdBy;
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
protected List<NotifyDetail> detailList;
|
||||||
|
}
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
package com.imyeyu.api.modules.common.entity;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonView;
|
||||||
|
import com.imyeyu.api.TimiServerAPI;
|
||||||
|
import com.imyeyu.api.modules.common.service.BaseQueueService;
|
||||||
|
import com.imyeyu.api.modules.common.service.HTTPQueueService;
|
||||||
|
import com.imyeyu.api.modules.common.service.MailQueueService;
|
||||||
|
import com.imyeyu.api.modules.common.service.SmsQueueService;
|
||||||
|
import com.imyeyu.api.modules.common.service.WechatQueueService;
|
||||||
|
import com.imyeyu.api.modules.user.entity.User;
|
||||||
|
import com.imyeyu.spring.annotation.table.Transient;
|
||||||
|
import com.imyeyu.spring.entity.UUIDEntity;
|
||||||
|
import com.imyeyu.spring.util.ResponseView;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知详情
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-05-13 14:40
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class NotifyDetail extends UUIDEntity {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2025-10-28 15:53
|
||||||
|
*/
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum MsgType {
|
||||||
|
|
||||||
|
/** 站内通知,不进入外部消息队列 */
|
||||||
|
INTERNAL(null),
|
||||||
|
|
||||||
|
SMS(SmsQueueService.class),
|
||||||
|
|
||||||
|
MAIL(MailQueueService.class),
|
||||||
|
|
||||||
|
HTTP(HTTPQueueService.class),
|
||||||
|
|
||||||
|
WECHAT(WechatQueueService.class);
|
||||||
|
|
||||||
|
private final Class<? extends BaseQueueService<?>> queueServiceClass;
|
||||||
|
|
||||||
|
/// 获取消息队列服务
|
||||||
|
///
|
||||||
|
/// @return 消息队列服务
|
||||||
|
public BaseQueueService<?> getService() {
|
||||||
|
return TimiServerAPI.applicationContext.getBean(queueServiceClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 判断是否需要外部消息队列
|
||||||
|
///
|
||||||
|
/// @return true 为需要外部消息队列
|
||||||
|
public boolean isQueue() {
|
||||||
|
return queueServiceClass != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2025-10-28 15:55
|
||||||
|
*/
|
||||||
|
public enum Status {
|
||||||
|
|
||||||
|
WAITING,
|
||||||
|
|
||||||
|
REMINDED,
|
||||||
|
|
||||||
|
TIMEOUT,
|
||||||
|
|
||||||
|
CANCEL,
|
||||||
|
|
||||||
|
FAIL
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 所属通知 ID */
|
||||||
|
@JsonView(ResponseView.Public.class)
|
||||||
|
private String notifyId;
|
||||||
|
|
||||||
|
/** 消息类型 */
|
||||||
|
@JsonView(ResponseView.Public.class)
|
||||||
|
private MsgType msgType;
|
||||||
|
|
||||||
|
/** 模板 ID */
|
||||||
|
@JsonView(ResponseView.Admin.class)
|
||||||
|
private String templateId;
|
||||||
|
|
||||||
|
/** 标题 */
|
||||||
|
@JsonView(ResponseView.Public.class)
|
||||||
|
private String subject;
|
||||||
|
|
||||||
|
/** 内容 */
|
||||||
|
@JsonView(ResponseView.Public.class)
|
||||||
|
private String data;
|
||||||
|
|
||||||
|
/** 参数 */
|
||||||
|
@JsonView(ResponseView.Public.class)
|
||||||
|
private String args;
|
||||||
|
|
||||||
|
/** 发送来源 */
|
||||||
|
@JsonView(ResponseView.Public.class)
|
||||||
|
private String sendFrom;
|
||||||
|
|
||||||
|
/** 发送去向 */
|
||||||
|
@JsonView(ResponseView.Public.class)
|
||||||
|
private String sendTo;
|
||||||
|
|
||||||
|
/** 发送时间 */
|
||||||
|
@JsonView(ResponseView.Public.class)
|
||||||
|
private Long sendAt;
|
||||||
|
|
||||||
|
/** 重试计数 */
|
||||||
|
@JsonView(ResponseView.Admin.class)
|
||||||
|
private Integer retry;
|
||||||
|
|
||||||
|
/** 超时时间 */
|
||||||
|
@JsonView(ResponseView.Admin.class)
|
||||||
|
private Long timeoutAt;
|
||||||
|
|
||||||
|
/** 结果描述 */
|
||||||
|
@JsonView(ResponseView.Admin.class)
|
||||||
|
private String resultDesc;
|
||||||
|
|
||||||
|
/** 状态 */
|
||||||
|
@JsonView(ResponseView.Public.class)
|
||||||
|
private Status status;
|
||||||
|
|
||||||
|
/** 已读时间,站内通知未读时为空 */
|
||||||
|
@JsonView(ResponseView.Public.class)
|
||||||
|
private Long readAt;
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
protected Notify notify;
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
protected User fromUser;
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
protected User toUser;
|
||||||
|
}
|
||||||
@@ -0,0 +1,518 @@
|
|||||||
|
package com.imyeyu.api.modules.common.entity;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonView;
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
import com.imyeyu.api.TimiServerAPI;
|
||||||
|
import com.imyeyu.api.modules.common.bean.SettingModule;
|
||||||
|
import com.imyeyu.spring.annotation.table.Transient;
|
||||||
|
import com.imyeyu.spring.entity.UUIDEntity;
|
||||||
|
import com.imyeyu.spring.util.ResponseView;
|
||||||
|
import com.imyeyu.utils.Time;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统配置
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2021-07-20 21:46
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class Setting extends UUIDEntity {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2025-01-10 17:08
|
||||||
|
*/
|
||||||
|
public enum ValueType {
|
||||||
|
|
||||||
|
/** 字符串 */
|
||||||
|
STRING,
|
||||||
|
|
||||||
|
/** 单选 */
|
||||||
|
SELECT_RADIO,
|
||||||
|
|
||||||
|
/** 多选 */
|
||||||
|
SELECT_CHECKBOX,
|
||||||
|
|
||||||
|
/** 整数 */
|
||||||
|
INTEGER,
|
||||||
|
|
||||||
|
/** 十六进制颜色 */
|
||||||
|
COLOR,
|
||||||
|
|
||||||
|
/** 浮点数 */
|
||||||
|
FLOAT,
|
||||||
|
|
||||||
|
/** 日期 */
|
||||||
|
DATE,
|
||||||
|
|
||||||
|
/** 日期时间 */
|
||||||
|
DATETIME,
|
||||||
|
|
||||||
|
/** 时间 */
|
||||||
|
TIME,
|
||||||
|
|
||||||
|
/** 时长 */
|
||||||
|
DURATION,
|
||||||
|
|
||||||
|
/** 布尔值 */
|
||||||
|
BOOLEAN,
|
||||||
|
|
||||||
|
/** JSON 列表 */
|
||||||
|
JSON_LIST,
|
||||||
|
|
||||||
|
/** JSON 对象 */
|
||||||
|
JSON_OBJECT
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonView(ResponseView.Public.class)
|
||||||
|
private String bizType;
|
||||||
|
|
||||||
|
@JsonView(ResponseView.Public.class)
|
||||||
|
private String bizId;
|
||||||
|
|
||||||
|
@JsonView(ResponseView.Public.class)
|
||||||
|
private String code;
|
||||||
|
|
||||||
|
@JsonView(ResponseView.Public.class)
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
@JsonView(ResponseView.Public.class)
|
||||||
|
private String value;
|
||||||
|
|
||||||
|
@JsonView(ResponseView.Public.class)
|
||||||
|
private ValueType valueType;
|
||||||
|
|
||||||
|
@JsonView(ResponseView.Public.class)
|
||||||
|
private JsonNode valueArgs;
|
||||||
|
|
||||||
|
@JsonView(ResponseView.Admin.class)
|
||||||
|
private Boolean isPrivate;
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
private Enum<?> keyRaw;
|
||||||
|
|
||||||
|
public Setting(Enum<?> key) {
|
||||||
|
setKeyRaw(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Setting(Enum<?> key, Object value, ValueType valueType) {
|
||||||
|
setKeyRaw(key);
|
||||||
|
this.value = value.toString();
|
||||||
|
this.valueType = valueType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 以字符串形式返回配置值
|
||||||
|
*
|
||||||
|
* @return 配置字符串值
|
||||||
|
*/
|
||||||
|
public String asString() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long asTime() {
|
||||||
|
return Time.parseToMS(asString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 以整数形式返回配置值
|
||||||
|
*
|
||||||
|
* @return 配置整数值
|
||||||
|
* @throws NumberFormatException 配置值不是合法整数
|
||||||
|
*/
|
||||||
|
public Integer asInteger() {
|
||||||
|
return Integer.parseInt(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 以长整数形式返回配置值
|
||||||
|
*
|
||||||
|
* @return 配置长整数值
|
||||||
|
* @throws NumberFormatException 配置值不是合法长整数
|
||||||
|
*/
|
||||||
|
public Long asLong() {
|
||||||
|
return Long.parseLong(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 以双精度浮点数形式返回配置值
|
||||||
|
*
|
||||||
|
* @return 配置双精度浮点数值
|
||||||
|
* @throws NumberFormatException 配置值不是合法双精度浮点数
|
||||||
|
*/
|
||||||
|
public Double asDouble() {
|
||||||
|
return Double.parseDouble(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 以布尔值形式返回配置值
|
||||||
|
*
|
||||||
|
* @return 配置布尔值
|
||||||
|
*/
|
||||||
|
public Boolean asBoolean() {
|
||||||
|
return Boolean.parseBoolean(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isTrue() {
|
||||||
|
return asBoolean();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFalse() {
|
||||||
|
return !isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 以 JSON 节点形式返回配置值
|
||||||
|
*
|
||||||
|
* @return 配置 JSON 节点
|
||||||
|
* @throws IllegalArgumentException 配置值不是合法 JSON
|
||||||
|
*/
|
||||||
|
public JsonNode asJsonNode() {
|
||||||
|
return asJsonNode(JsonNode.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 以 JSON 数组形式返回配置值
|
||||||
|
*
|
||||||
|
* @return 配置 JSON 数组
|
||||||
|
* @throws IllegalArgumentException 配置值不是合法 JSON 数组
|
||||||
|
*/
|
||||||
|
public ArrayNode asArrayNode() {
|
||||||
|
return asJsonNode(ArrayNode.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 以 JSON 对象形式返回配置值
|
||||||
|
*
|
||||||
|
* @return 配置 JSON 对象
|
||||||
|
* @throws IllegalArgumentException 配置值不是合法 JSON 对象
|
||||||
|
*/
|
||||||
|
public ObjectNode asObjectNode() {
|
||||||
|
return asJsonNode(ObjectNode.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析配置 JSON 值
|
||||||
|
*
|
||||||
|
* @param type 目标类型
|
||||||
|
* @param <T> 目标类型
|
||||||
|
* @return 解析后的 JSON 节点
|
||||||
|
* @throws IllegalArgumentException 配置值不是合法 JSON
|
||||||
|
*/
|
||||||
|
public <T extends JsonNode> T asJsonNode(Class<T> type) {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
ObjectMapper mapper = TimiServerAPI.applicationContext.getBean(ObjectMapper.class);
|
||||||
|
return mapper.readValue(value, type);
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
throw new IllegalArgumentException("setting value is not valid json: %s".formatted(value), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setKeyRaw(Enum<?> key) {
|
||||||
|
this.keyRaw = key;
|
||||||
|
this.bizType = SettingModule.fromClass(key.getClass()).toString();
|
||||||
|
this.code = key.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean is(Enum<?> key) {
|
||||||
|
return SettingModule.fromClass(key.getClass()).toString().equals(bizType) && key.toString().equals(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-04-24 14:23
|
||||||
|
*/
|
||||||
|
public static final class Module {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-04-24 15:39
|
||||||
|
*/
|
||||||
|
public enum Common {
|
||||||
|
|
||||||
|
/** 微信收款码 */
|
||||||
|
WECHAT_RECEIVE_QR_CODE
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 域名
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-04-26 17:18
|
||||||
|
*/
|
||||||
|
public enum Domain {
|
||||||
|
|
||||||
|
ROOT,
|
||||||
|
|
||||||
|
API,
|
||||||
|
|
||||||
|
GIT,
|
||||||
|
|
||||||
|
BLOG,
|
||||||
|
|
||||||
|
SPACE,
|
||||||
|
|
||||||
|
FOREVER_MC
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-04-24 15:14
|
||||||
|
*/
|
||||||
|
public enum Blog {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-04-24 15:03
|
||||||
|
*/
|
||||||
|
public enum User {
|
||||||
|
|
||||||
|
/** 启用登录 */
|
||||||
|
ENABLE_LOGIN,
|
||||||
|
|
||||||
|
/** 启用注册 */
|
||||||
|
ENABLE_REGISTER,
|
||||||
|
|
||||||
|
/** 允许数据更新 */
|
||||||
|
ENABLE_PROFILE_UPDATE,
|
||||||
|
|
||||||
|
/** 令牌有效期 */
|
||||||
|
TOKEN_TTL
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-04-24 15:21
|
||||||
|
*/
|
||||||
|
public enum Comment {
|
||||||
|
|
||||||
|
/** 启用服务 */
|
||||||
|
ENABLE
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-04-24 15:03
|
||||||
|
*/
|
||||||
|
public enum ICP {
|
||||||
|
|
||||||
|
IMYEYU_COM
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-04-24 15:01
|
||||||
|
*/
|
||||||
|
public enum ForeverMC {
|
||||||
|
|
||||||
|
PLAYER_BIND_MAX,
|
||||||
|
|
||||||
|
LOGIN_TOKEN_TTL
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-04-24 14:57
|
||||||
|
*/
|
||||||
|
public enum Multilingual {
|
||||||
|
|
||||||
|
/** 翻译 API */
|
||||||
|
TRANSLATE_API,
|
||||||
|
|
||||||
|
/** 翻译 AppId */
|
||||||
|
TRANSLATE_APP_ID,
|
||||||
|
|
||||||
|
/** 翻译密钥 */
|
||||||
|
TRANSLATE_KEY,
|
||||||
|
|
||||||
|
/** 翻译计划任务表达式 */
|
||||||
|
TRANSLATE_CORN,
|
||||||
|
|
||||||
|
/** 缓存时长 */
|
||||||
|
CACHE_TTL
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-04-24 14:55
|
||||||
|
*/
|
||||||
|
public enum Gitea {
|
||||||
|
|
||||||
|
/** 接口地址 */
|
||||||
|
API,
|
||||||
|
|
||||||
|
/** 关于页面所属文章 ID */
|
||||||
|
ABOUT_ARTICLE_ID,
|
||||||
|
|
||||||
|
/** 仓库文件地址 */
|
||||||
|
REPOSITORY_PATH
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-04-24 14:52
|
||||||
|
*/
|
||||||
|
public enum Music {
|
||||||
|
|
||||||
|
/** 启用服务 */
|
||||||
|
ENABLE,
|
||||||
|
|
||||||
|
/** 通信消息最大帧长度 */
|
||||||
|
MAX_FRAME_LENGTH,
|
||||||
|
|
||||||
|
/** 受控端 IP */
|
||||||
|
PLAYER_IP,
|
||||||
|
|
||||||
|
/** 受控端端口 */
|
||||||
|
PLAYER_PORT,
|
||||||
|
|
||||||
|
/** 控制端 IP */
|
||||||
|
CONTROLLER_IP,
|
||||||
|
|
||||||
|
/** 控制端端口 */
|
||||||
|
CONTROLLER_PORT,
|
||||||
|
|
||||||
|
/** 控制端入口地址 */
|
||||||
|
CONTROLLER_URI,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-04-24 14:49
|
||||||
|
*/
|
||||||
|
public enum Journal {
|
||||||
|
|
||||||
|
/** 启用服务 */
|
||||||
|
ENABLE,
|
||||||
|
|
||||||
|
/** 审核中 */
|
||||||
|
REVIEWING,
|
||||||
|
|
||||||
|
/** 访问密钥 */
|
||||||
|
KEY,
|
||||||
|
|
||||||
|
/** 微信小程序 AppId */
|
||||||
|
APP_ID,
|
||||||
|
|
||||||
|
/** 微信小程序 AppSecret */
|
||||||
|
APP_SECRET,
|
||||||
|
|
||||||
|
/** 备忘录内容 */
|
||||||
|
MEMO,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-04-24 14:47
|
||||||
|
*/
|
||||||
|
public enum TempFile {
|
||||||
|
|
||||||
|
/** 启用服务 */
|
||||||
|
ENABLE,
|
||||||
|
|
||||||
|
/** 最小缓存时长 */
|
||||||
|
TTL_MIN,
|
||||||
|
|
||||||
|
/** 默认缓存时长 */
|
||||||
|
TTL_DEFAULT,
|
||||||
|
|
||||||
|
/** 最大缓存时长 */
|
||||||
|
TTL_MAX,
|
||||||
|
|
||||||
|
/** 已过期文件残留时长 */
|
||||||
|
RESIDUE_TTL,
|
||||||
|
|
||||||
|
/** 每个 IP 限制有效临时文件容量 */
|
||||||
|
LIMIT_SIZE_OF_IP
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-04-27 14:35
|
||||||
|
*/
|
||||||
|
public enum FileSync {
|
||||||
|
|
||||||
|
ENABLE,
|
||||||
|
|
||||||
|
CRON,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-04-24 14:23
|
||||||
|
*/
|
||||||
|
public enum System {
|
||||||
|
|
||||||
|
/** 文件系统基准路径 */
|
||||||
|
FILE_BASE_PATH,
|
||||||
|
|
||||||
|
/** 设置缓存时长 */
|
||||||
|
SETTING_TTL,
|
||||||
|
|
||||||
|
/** 哀悼滤镜 */
|
||||||
|
ENABLE_MOURN_FILTER,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-04-24 14:23
|
||||||
|
*/
|
||||||
|
public enum SystemStatus {
|
||||||
|
|
||||||
|
/** 状态采集数据量 */
|
||||||
|
LIMIT,
|
||||||
|
|
||||||
|
/** 状态采集网卡 */
|
||||||
|
NETWORK_MAC,
|
||||||
|
|
||||||
|
/** 状态采集频率 */
|
||||||
|
RATE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.imyeyu.api.modules.common.entity;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/// 短信发送队列
|
||||||
|
///
|
||||||
|
/// @author 夜雨
|
||||||
|
/// @since 2026-08-20
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class SmsQueue extends BaseQueueEntity {
|
||||||
|
|
||||||
|
/** 短信模板 ID */
|
||||||
|
protected String templateId;
|
||||||
|
|
||||||
|
/// 从通知详情创建短信队列
|
||||||
|
///
|
||||||
|
/// @param detail 通知详情
|
||||||
|
public SmsQueue(NotifyDetail detail) {
|
||||||
|
super(detail);
|
||||||
|
templateId = detail.getTemplateId();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package com.imyeyu.api.modules.common.entity;
|
||||||
|
|
||||||
|
import com.imyeyu.api.annotation.MultilingualField;
|
||||||
|
import com.imyeyu.spring.annotation.table.Transient;
|
||||||
|
import com.imyeyu.spring.entity.UUIDEntity;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
/// 标签
|
||||||
|
///
|
||||||
|
/// @author 夜雨
|
||||||
|
/// @since 2024-08-28 14:26
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class Tag extends UUIDEntity {
|
||||||
|
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// @author 夜雨
|
||||||
|
/// @since 2026-08-12 16:49
|
||||||
|
public enum OwnerType {
|
||||||
|
|
||||||
|
GAO_STORE
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 归属类型
|
||||||
|
protected OwnerType ownerType;
|
||||||
|
|
||||||
|
/// 归属 ID
|
||||||
|
protected String ownerId;
|
||||||
|
|
||||||
|
/// 名称多语言 ID
|
||||||
|
protected String nameLangId;
|
||||||
|
|
||||||
|
/// 当前语言名称
|
||||||
|
@Transient
|
||||||
|
@MultilingualField("nameLangId")
|
||||||
|
protected String name;
|
||||||
|
|
||||||
|
/// 中文名称
|
||||||
|
@Transient
|
||||||
|
protected String zhCN;
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package com.imyeyu.api.modules.common.entity;
|
||||||
|
|
||||||
|
import com.imyeyu.spring.annotation.table.Transient;
|
||||||
|
import com.imyeyu.spring.entity.UUIDEntity;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标签应用
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2025-12-02 17:01
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class TagApply extends UUIDEntity {
|
||||||
|
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// @author 夜雨
|
||||||
|
/// @since 2026-08-12 14:27
|
||||||
|
public enum BizType {
|
||||||
|
|
||||||
|
GAO_CUSTOMER
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 业务类型 */
|
||||||
|
protected BizType bizType;
|
||||||
|
|
||||||
|
/** 业务 ID */
|
||||||
|
protected String bizId;
|
||||||
|
|
||||||
|
/** 标签 ID */
|
||||||
|
protected String tagId;
|
||||||
|
|
||||||
|
/// 标签 ID 列表
|
||||||
|
@Transient
|
||||||
|
private List<String> tagIdList;
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.imyeyu.api.modules.common.entity;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
/// 微信消息发送队列
|
||||||
|
///
|
||||||
|
/// @author 夜雨
|
||||||
|
/// @since 2026-08-20
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class WechatQueue extends BaseQueueEntity {
|
||||||
|
|
||||||
|
/// 从通知详情创建微信队列
|
||||||
|
///
|
||||||
|
/// @param detail 通知详情
|
||||||
|
public WechatQueue(NotifyDetail detail) {
|
||||||
|
super(detail);
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-9
@@ -1,22 +1,16 @@
|
|||||||
package com.imyeyu.server.modules.blog.mapper;
|
package com.imyeyu.api.modules.common.mapper;
|
||||||
|
|
||||||
import com.imyeyu.server.modules.blog.entity.Article;
|
import com.imyeyu.api.modules.common.entity.Article;
|
||||||
import com.imyeyu.spring.mapper.BaseMapper;
|
import com.imyeyu.spring.mapper.BaseMapper;
|
||||||
import org.apache.ibatis.annotations.Select;
|
import org.apache.ibatis.annotations.Select;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 文章
|
* 文章
|
||||||
*
|
*
|
||||||
* @author 夜雨
|
* @author 夜雨
|
||||||
* @since 2021-02-23 21:34
|
* @since 2021-02-23 21:34
|
||||||
*/
|
*/
|
||||||
public interface ArticleMapper extends BaseMapper<Article, Long> {
|
public interface ArticleMapper extends BaseMapper<Article, String> {
|
||||||
|
|
||||||
long countByKeyword(String keyword);
|
|
||||||
|
|
||||||
List<Article> selectByKeyword(String keyword, Long offset, int limit);
|
|
||||||
|
|
||||||
@Select("UPDATE `article` SET `likes` = `likes` + 1 WHERE `id` = #{articleId}")
|
@Select("UPDATE `article` SET `likes` = `likes` + 1 WHERE `id` = #{articleId}")
|
||||||
void like(Long articleId);
|
void like(Long articleId);
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package com.imyeyu.api.modules.common.mapper;
|
||||||
|
|
||||||
|
import com.imyeyu.api.modules.common.entity.Attachment;
|
||||||
|
import com.imyeyu.spring.bean.Page;
|
||||||
|
import com.imyeyu.spring.mapper.BaseMapper;
|
||||||
|
import com.imyeyu.spring.mapper.RawMapper;
|
||||||
|
import org.apache.ibatis.annotations.Select;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2023-08-15 10:22
|
||||||
|
*/
|
||||||
|
public interface AttachmentMapper extends BaseMapper<Attachment, String>, RawMapper<Attachment, String> {
|
||||||
|
|
||||||
|
/** 有效条件,非删除和销毁 */
|
||||||
|
String VALID = NOT_DELETE + " AND (`destroy_at` IS NULL OR " + UNIX_TIME + " < `destroy_at`)";
|
||||||
|
|
||||||
|
@Select("SELECT * FROM attachment WHERE biz_type = #{bizType} AND biz_id = #{bizId} " + VALID + LIMIT_1)
|
||||||
|
Attachment selectByBizId(Attachment.BizType bizType, String bizId);
|
||||||
|
|
||||||
|
@Select("SELECT * FROM attachment WHERE biz_type = #{bizType} AND biz_id = #{bizId} " + VALID)
|
||||||
|
List<Attachment> selectAllByBizId(Attachment.BizType bizType, String bizId);
|
||||||
|
|
||||||
|
@Select("SELECT * FROM attachment WHERE mongo_id = #{mongoId} " + VALID + LIMIT_1)
|
||||||
|
Attachment selectByMongoId(String mongoId);
|
||||||
|
|
||||||
|
@Select("SELECT * FROM attachment WHERE biz_type = #{bizType} AND biz_id = #{bizId} AND attach_type = #{attachType} " + VALID + LIMIT_1)
|
||||||
|
Attachment selectByAttachType(Attachment.BizType bizType, String bizId, String attachType);
|
||||||
|
|
||||||
|
List<Attachment> selectByBizId(Attachment.BizType bizType, String bizId, List<String> attachTypes, Page<Attachment> page);
|
||||||
|
|
||||||
|
List<Attachment> selectAllByBizIdList(Attachment.BizType bizType, Collection<String> bizIdList, List<String> attachTypes);
|
||||||
|
|
||||||
|
long countByBizId(Attachment.BizType bizType, String bizId, List<String> attachTypes);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按源附件 ID 与请求尺寸查询已缓存缩略图
|
||||||
|
*
|
||||||
|
* @param bizType 源附件业务类型
|
||||||
|
* @param sourceId 源附件 ID
|
||||||
|
* @param requestWidth 请求宽度,{@code null} 表示未指定
|
||||||
|
* @param requestHeight 请求高度,{@code null} 表示未指定
|
||||||
|
* @return 缩略图附件,不存在返回 {@code null}
|
||||||
|
*/
|
||||||
|
Attachment selectThumb(Attachment.BizType bizType, String sourceId, Integer requestWidth, Integer requestHeight);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询源附件的所有未销毁缩略图(含已软删除)
|
||||||
|
*
|
||||||
|
* @param sourceId 源附件 ID
|
||||||
|
* @return 缩略图列表
|
||||||
|
*/
|
||||||
|
@Select("SELECT * FROM attachment WHERE biz_id = #{sourceId} AND attach_type = 'THUMB' AND is_destroyed = FALSE")
|
||||||
|
List<Attachment> selectThumbsBySourceId(String sourceId);
|
||||||
|
|
||||||
|
@Select("SELECT * FROM attachment WHERE `is_destroyed` = FALSE AND `destroy_at` < " + UNIX_TIME)
|
||||||
|
List<Attachment> selectNeedDestroy();
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.imyeyu.api.modules.common.mapper;
|
||||||
|
|
||||||
|
import com.imyeyu.api.modules.common.entity.BaseQueueEntity;
|
||||||
|
import com.imyeyu.api.modules.common.util.QueueSQLProvider;
|
||||||
|
import com.imyeyu.spring.mapper.BaseMapper;
|
||||||
|
import org.apache.ibatis.annotations.DeleteProvider;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import org.apache.ibatis.annotations.SelectProvider;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/// 通知队列基础 Mapper
|
||||||
|
///
|
||||||
|
/// @param <T> 队列实体类型
|
||||||
|
/// @author 夜雨
|
||||||
|
/// @since 2026-08-20
|
||||||
|
public interface BaseQueueMapper<T extends BaseQueueEntity> extends BaseMapper<T, String> {
|
||||||
|
|
||||||
|
/** 查询已到发送时间的队列 */
|
||||||
|
@SelectProvider(type = QueueSQLProvider.class, method = "listReadyQueue")
|
||||||
|
List<T> listReadyQueue();
|
||||||
|
|
||||||
|
/** 根据通知详情 ID 物理删除队列 */
|
||||||
|
@DeleteProvider(type = QueueSQLProvider.class, method = "destroyByNotifyDetailId")
|
||||||
|
void destroyByNotifyDetailId(@Param("notifyDetailId") String notifyDetailId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
package com.imyeyu.api.modules.common.mapper;
|
||||||
|
|
||||||
|
import com.imyeyu.api.modules.common.entity.Comment;
|
||||||
|
import com.imyeyu.spring.mapper.BaseMapper;
|
||||||
|
import org.apache.ibatis.annotations.Select;
|
||||||
|
import org.apache.ibatis.annotations.Update;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 评论 Mapper
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-05-27 17:20
|
||||||
|
*/
|
||||||
|
public interface CommentMapper extends BaseMapper<Comment, String> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据 ID 列表查询评论
|
||||||
|
*
|
||||||
|
* @param idList 评论 ID 列表
|
||||||
|
* @return 评论列表
|
||||||
|
*/
|
||||||
|
List<Comment> listByIdList(List<String> idList);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询业务下的主评论列表
|
||||||
|
*
|
||||||
|
* @param bizType 业务类型
|
||||||
|
* @param bizId 业务 ID
|
||||||
|
* @param offset 起始偏移量
|
||||||
|
* @param limit 查询条数
|
||||||
|
* @return 主评论列表
|
||||||
|
*/
|
||||||
|
List<Comment> selectRootList(String bizType, String bizId, int offset, int limit);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统计业务下的主评论数量
|
||||||
|
*
|
||||||
|
* @param bizType 业务类型
|
||||||
|
* @param bizId 业务 ID
|
||||||
|
* @return 主评论数量
|
||||||
|
*/
|
||||||
|
long countRoot(String bizType, String bizId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询多个根评论下的回复预览列表
|
||||||
|
*
|
||||||
|
* @param rootIdList 根评论 ID 列表
|
||||||
|
* @param limit 每个根评论最多返回条数
|
||||||
|
* @return 回复列表
|
||||||
|
*/
|
||||||
|
List<Comment> selectReplyPreviewByRootIds(List<String> rootIdList, int limit);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询父评论下的回复列表
|
||||||
|
*
|
||||||
|
* @param parentId 父评论 ID
|
||||||
|
* @param offset 起始偏移量
|
||||||
|
* @param limit 查询条数
|
||||||
|
* @return 回复列表
|
||||||
|
*/
|
||||||
|
List<Comment> selectReplyListByParentId(String parentId, int offset, int limit);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询根评论下的回复列表
|
||||||
|
*
|
||||||
|
* @param rootId 根评论 ID
|
||||||
|
* @param offset 起始偏移量
|
||||||
|
* @param limit 查询条数
|
||||||
|
* @return 回复列表
|
||||||
|
*/
|
||||||
|
List<Comment> selectReplyListByRootId(String rootId, int offset, int limit);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统计父评论下的回复数量
|
||||||
|
*
|
||||||
|
* @param parentId 父评论 ID
|
||||||
|
* @return 回复数量
|
||||||
|
*/
|
||||||
|
long countReplyByParentId(String parentId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统计根评论下的回复数量
|
||||||
|
*
|
||||||
|
* @param rootId 根评论 ID
|
||||||
|
* @return 回复数量
|
||||||
|
*/
|
||||||
|
long countReplyByRootId(String rootId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询多个根评论下的全部回复列表
|
||||||
|
*
|
||||||
|
* @param rootIdList 根评论 ID 列表
|
||||||
|
* @return 回复列表
|
||||||
|
*/
|
||||||
|
List<Comment> selectAllReplyListByRootIds(List<String> rootIdList);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询根评论下的全部回复列表
|
||||||
|
*
|
||||||
|
* @param rootId 根评论 ID
|
||||||
|
* @return 回复列表
|
||||||
|
*/
|
||||||
|
List<Comment> selectAllReplyListByRootId(String rootId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询单层评论列表
|
||||||
|
*
|
||||||
|
* @param bizType 业务类型
|
||||||
|
* @param bizId 业务 ID
|
||||||
|
* @param parentId 父评论 ID
|
||||||
|
* @param offset 起始偏移量
|
||||||
|
* @param limit 查询条数
|
||||||
|
* @return 评论列表
|
||||||
|
*/
|
||||||
|
List<Comment> selectLayerList(String bizType, String bizId, String parentId, int offset, int limit);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统计单层评论数量
|
||||||
|
*
|
||||||
|
* @param bizType 业务类型
|
||||||
|
* @param bizId 业务 ID
|
||||||
|
* @param parentId 父评论 ID
|
||||||
|
* @return 评论数量
|
||||||
|
*/
|
||||||
|
long countLayer(String bizType, String bizId, String parentId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询根评论最新回复时间
|
||||||
|
*
|
||||||
|
* @param rootId 根评论 ID
|
||||||
|
* @return 最新回复时间
|
||||||
|
*/
|
||||||
|
@Select("SELECT created_at FROM comment WHERE root_id = #{rootId} AND parent_id IS NOT NULL" + NOT_DELETE + "ORDER BY created_at DESC LIMIT 1")
|
||||||
|
Long selectLatestReplyAtByRootId(String rootId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调整回复数
|
||||||
|
*
|
||||||
|
* @param id 评论 ID
|
||||||
|
* @param delta 变更值
|
||||||
|
*/
|
||||||
|
@Update("UPDATE comment SET reply_count = GREATEST(reply_count + #{delta}, 0), updated_at = " + UNIX_TIME + " WHERE id = #{id}" + NOT_DELETE)
|
||||||
|
void increaseReplyCount(String id, int delta);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调整点赞数
|
||||||
|
*
|
||||||
|
* @param id 评论 ID
|
||||||
|
* @param delta 变更值
|
||||||
|
*/
|
||||||
|
@Update("UPDATE comment SET like_count = GREATEST(like_count + #{delta}, 0), updated_at = " + UNIX_TIME + " WHERE id = #{id}" + NOT_DELETE)
|
||||||
|
void increaseLikeCount(String id, int delta);
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.imyeyu.api.modules.common.mapper;
|
||||||
|
|
||||||
|
import com.imyeyu.api.modules.common.entity.DataDict;
|
||||||
|
import com.imyeyu.spring.mapper.BaseMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据字典 Mapper
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-05-13 14:46
|
||||||
|
*/
|
||||||
|
public interface DataDictMapper extends BaseMapper<DataDict, String> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.imyeyu.api.modules.common.mapper;
|
||||||
|
|
||||||
|
import com.imyeyu.api.modules.common.entity.DataDictOption;
|
||||||
|
import com.imyeyu.spring.mapper.BaseMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据字典选项 Mapper
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-05-13 14:46
|
||||||
|
*/
|
||||||
|
public interface DataDictOptionMapper extends BaseMapper<DataDictOption, String> {
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user