初始化代码
parent
e79a78cac6
commit
d21e382159
|
@ -0,0 +1,257 @@
|
|||
pipeline {
|
||||
agent any
|
||||
environment {
|
||||
DOCKER_HUB_URL = 'registry.cn-hangzhou.aliyuncs.com'
|
||||
DOCKER_HUB_WORKSPACE = 'xxxxxx'
|
||||
DINGTALK_ID = 'xxxxxx'
|
||||
// 获取Maven pom.xml项目版本号
|
||||
//APP_VERSION = readMavenPom().getVersion()
|
||||
APP_VERSION = '0.1.0'
|
||||
}
|
||||
parameters {
|
||||
choice(
|
||||
choices: [ 'N', 'Y' ],
|
||||
description: '是否发布服务端',
|
||||
name: 'DEPLOY_SERVER')
|
||||
choice(
|
||||
choices: [ 'N', 'Y' ],
|
||||
description: '是否发布wwwroot_release',
|
||||
name: 'DEPLOY_WWWROOT')
|
||||
choice(
|
||||
choices: [ 'N', 'Y' ],
|
||||
description: '是否发布前端',
|
||||
name: 'DEPLOY_UI')
|
||||
choice(
|
||||
choices: [ 'latest' ],
|
||||
description: '镜像TAG',
|
||||
name: 'IMAGE_TAG')
|
||||
choice(
|
||||
choices: [ 'prod' ],
|
||||
description: '发布环境',
|
||||
name: 'DEPLOY_ENV')
|
||||
}
|
||||
options {
|
||||
//设置在项目打印日志时带上对应时间
|
||||
timestamps()
|
||||
//不允许同时执行流水线,被用来防止同时访问共享资源等
|
||||
disableConcurrentBuilds()
|
||||
// 表示保留n次构建历史
|
||||
buildDiscarder(logRotator(numToKeepStr: '2'))
|
||||
}
|
||||
stages {
|
||||
stage("Preparing") {
|
||||
steps {
|
||||
dingtalk (
|
||||
robot: '${DINGTALK_ID}',
|
||||
type: 'MARKDOWN',
|
||||
at: [],
|
||||
atAll: false,
|
||||
title: '(${JOB_NAME} #${BUILD_NUMBER})',
|
||||
text: [
|
||||
'### 开始构建(${JOB_NAME} #${BUILD_NUMBER})',
|
||||
'---',
|
||||
'- DEPLOY_SERVER: ${DEPLOY_SERVER}',
|
||||
'- DEPLOY_UI: ${DEPLOY_UI}',
|
||||
'- IMAGE_TAG: ${IMAGE_TAG}',
|
||||
'- DEPLOY_ENV: ${DEPLOY_ENV}',
|
||||
],
|
||||
messageUrl: '${BUILD_URL}',
|
||||
picUrl: ''
|
||||
)
|
||||
}
|
||||
}
|
||||
stage("Checkout") {
|
||||
steps {
|
||||
dir('./ChestnutCMS') {
|
||||
checkout([$class: 'GitSCM', branches: [[name: '*/dev']], extensions: [], userRemoteConfigs: [[credentialsId: 'LwyGitee', url: 'https://gitee.com/liweiyi/ChestnutCMS.git']]])
|
||||
}
|
||||
dir('./wwwroot_release') {
|
||||
checkout([$class: 'GitSCM', branches: [[name: '*/master']], extensions: [], userRemoteConfigs: [[credentialsId: 'gitea', url: 'http://gitea.huaray.com/liweiyi/swikoon_wwwroot_release.git']]])
|
||||
}
|
||||
}
|
||||
}
|
||||
stage("Build") {
|
||||
when {
|
||||
expression { return params.DEPLOY_SERVER == 'Y' }
|
||||
}
|
||||
steps {
|
||||
dir('./ChestnutCMS') {
|
||||
withEnv(['JAVA_HOME=/var/jenkins_home/jdk/jdk-17.0.4.1']) {
|
||||
withMaven(maven: 'M3.8') {
|
||||
sh 'mvn -U clean package -Dmaven.test.skip=true'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stage("chestnut-admin") {
|
||||
when {
|
||||
expression { return params.DEPLOY_SERVER == 'Y' }
|
||||
}
|
||||
steps {
|
||||
withEnv(['APP_PATH=chestnut-admin', 'APP_NAME=chestnut-admin']) {
|
||||
echo "docker build start: ${APP_PATH}#${APP_VERSION}"
|
||||
dir('./ChestnutCMS') {
|
||||
withCredentials([usernamePassword(credentialsId: 'ALIYUN-DOCKER-REGISTRY-LWY', passwordVariable: 'DOCKERPWD', usernameVariable: 'DOCKERUSER')]) {
|
||||
sh '''
|
||||
cd ${APP_PATH}
|
||||
echo ${DOCKERPWD} | docker login --username=${DOCKERUSER} --password-stdin ${DOCKER_HUB_URL}
|
||||
docker build -t ${DOCKER_HUB_URL}/${DOCKER_HUB_WORKSPACE}/${APP_NAME}:${IMAGE_TAG} . --build-arg APP_NAME=${APP_PATH} --build-arg APP_VERSION=${APP_VERSION}
|
||||
docker logout ${DOCKER_HUB_URL}
|
||||
'''
|
||||
}
|
||||
}
|
||||
echo "docker push start: ${APP_PATH}#${APP_VERSION}"
|
||||
dir('./ChestnutCMS') {
|
||||
withCredentials([usernamePassword(credentialsId: 'ALIYUN-DOCKER-REGISTRY-LWY', passwordVariable: 'DOCKERPWD', usernameVariable: 'DOCKERUSER')]) {
|
||||
sh '''
|
||||
echo ${DOCKERPWD} | docker login --username=${DOCKERUSER} --password-stdin ${DOCKER_HUB_URL}
|
||||
docker push ${DOCKER_HUB_URL}/${DOCKER_HUB_WORKSPACE}/${APP_NAME}:${IMAGE_TAG}
|
||||
docker logout ${DOCKER_HUB_URL}
|
||||
|
||||
cp -f bin/docker-image-clear.sh docker-image-clear.sh
|
||||
sed -i "s/{{DOCKER_HUB_URL}}/${DOCKER_HUB_URL}/g" docker-image-clear.sh
|
||||
sed -i "s/{{IMAGE_REPOSITORY}}/${DOCKER_HUB_WORKSPACE}\\/${APP_NAME}/g" docker-image-clear.sh
|
||||
/bin/bash docker-image-clear.sh
|
||||
rm -f docker-image-clear.sh
|
||||
'''
|
||||
}
|
||||
}
|
||||
// deploy
|
||||
dir('./ChestnutCMS') {
|
||||
withCredentials([usernamePassword(credentialsId: 'ALIYUN-DOCKER-REGISTRY-LWY', passwordVariable: 'DOCKERPWD', usernameVariable: 'DOCKERUSER')]) {
|
||||
sh '''
|
||||
cp -f bin/docker-deploy.sh ${APP_PATH}
|
||||
|
||||
cd ${APP_PATH}
|
||||
|
||||
cp -f ../bin/docker-deploy.sh docker-deploy.sh
|
||||
|
||||
sed -i "s/{{DOCKERUSER}}/${DOCKERUSER}/g" docker-deploy.sh
|
||||
sed -i "s/{{DOCKERPWD}}/${DOCKERPWD}/g" docker-deploy.sh
|
||||
sed -i "s/{{DOCKER_HUB_URL}}/${DOCKER_HUB_URL}/g" docker-deploy.sh
|
||||
sed -i "s/{{IMAGE_REPOSITORY}}/${DOCKER_HUB_WORKSPACE}\\/${APP_NAME}/g" docker-deploy.sh
|
||||
sed -i "s/{{IMAGE_TAG}}/${IMAGE_TAG}/g" docker-deploy.sh
|
||||
|
||||
cp -f docker/docker-compose_${DEPLOY_ENV}.yml docker-compose.yml
|
||||
|
||||
sed -i "s/{{DOCKER_IMAGE}}/${DOCKER_HUB_URL}\\/${DOCKER_HUB_WORKSPACE}\\/${APP_NAME}:${IMAGE_TAG}/g" docker-compose.yml
|
||||
'''
|
||||
sshPublisher(publishers: [sshPublisherDesc(configName: 'GameCluster', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '''
|
||||
mkdir -p /www/docker/chestnut-admin
|
||||
cd /www/docker/chestnut-admin
|
||||
sh docker-deploy.sh
|
||||
rm -f docker-deploy.sh
|
||||
''', execTimeout: 600000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: 'chestnut-admin/',
|
||||
remoteDirectorySDF: false, removePrefix: 'chestnut-admin/',
|
||||
sourceFiles: 'chestnut-admin/docker-compose.yml,chestnut-admin/docker-deploy.sh')],
|
||||
usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: true)])
|
||||
}
|
||||
}
|
||||
|
||||
// delete tmp file
|
||||
dir('./ChestnutCMS') {
|
||||
sh 'rm -f ${APP_PATH}/docker-deploy.sh'
|
||||
sh 'rm -f ${APP_PATH}/docker-compose.yml'
|
||||
}
|
||||
echo "build end: ${APP_PATH}"
|
||||
}
|
||||
}
|
||||
}
|
||||
stage("wwwroot_release") {
|
||||
when {
|
||||
expression { return params.DEPLOY_WWWROOT == 'Y' }
|
||||
}
|
||||
steps {
|
||||
dir('./wwwroot_release') {
|
||||
sh 'zip -q -r wwwroot_release.zip * --exclude *.svn* --exclude *.git*'
|
||||
sshPublisher(publishers: [sshPublisherDesc(configName: 'GameCluster', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '''
|
||||
cd /www/docker/chestnut-admin/wwwroot_release
|
||||
unzip -o -q wwwroot_release.zip
|
||||
rm -f wwwroot_release.zip
|
||||
''', execTimeout: 600000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: 'chestnut-admin/wwwroot_release/',
|
||||
remoteDirectorySDF: false, removePrefix: '',
|
||||
sourceFiles: 'wwwroot_release.zip')],
|
||||
usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: true)])
|
||||
}
|
||||
}
|
||||
}
|
||||
stage("chestnut-ui") {
|
||||
when {
|
||||
expression { return params.DEPLOY_UI == 'Y' }
|
||||
}
|
||||
steps {
|
||||
dir('./ChestnutCMS/chestnut-ui') {
|
||||
nodejs('NodeJS16_13') {
|
||||
sh '''
|
||||
npm install --registry=https://registry.npmmirror.com
|
||||
npm run build:prod
|
||||
cd dist
|
||||
zip -q -r ui.zip *
|
||||
'''
|
||||
}
|
||||
}
|
||||
dir('./ChestnutCMS/chestnut-ui/dist') {
|
||||
sshPublisher(publishers: [sshPublisherDesc(configName: 'GameCluster', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '''
|
||||
mkdir -p /www/docker/chestnut-ui
|
||||
cd /www/docker/chestnut-ui
|
||||
unzip -o -q ui.zip
|
||||
rm -f ui.zip
|
||||
''', execTimeout: 600000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: 'chestnut-ui/',
|
||||
remoteDirectorySDF: false, removePrefix: '',
|
||||
sourceFiles: 'ui.zip')],
|
||||
usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: true)])
|
||||
sh 'rm -f ui.zip'
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
post {
|
||||
success {
|
||||
dingtalk (
|
||||
robot: '${DINGTALK_ID}',
|
||||
type: 'MARKDOWN',
|
||||
at: [],
|
||||
atAll: false,
|
||||
title: '${JOB_NAME} #${BUILD_NUMBER}',
|
||||
text: [
|
||||
'### 构建成功(${JOB_NAME} #${BUILD_NUMBER})',
|
||||
'---',
|
||||
'- IMAGE_TAG: ${IMAGE_TAG}',
|
||||
'- DEPLOY_ENV: ${DEPLOY_ENV}',
|
||||
],
|
||||
messageUrl: '${BUILD_URL}',
|
||||
picUrl: ''
|
||||
)
|
||||
}
|
||||
failure {
|
||||
dingtalk (
|
||||
robot: '${DINGTALK_ID}',
|
||||
type: 'LINK',
|
||||
at: [],
|
||||
atAll: false,
|
||||
title: '${JOB_NAME} #${BUILD_NUMBER}',
|
||||
text: [
|
||||
'构建失败',
|
||||
],
|
||||
messageUrl: '${BUILD_URL}',
|
||||
picUrl: ''
|
||||
)
|
||||
}
|
||||
unstable {
|
||||
dingtalk (
|
||||
robot: '${DINGTALK_ID}',
|
||||
type: 'LINK',
|
||||
at: [],
|
||||
atAll: false,
|
||||
title: '${JOB_NAME} #${BUILD_NUMBER}',
|
||||
text: [
|
||||
'构建流程可能出现问题,详情请查看流程日志',
|
||||
],
|
||||
messageUrl: '${BUILD_URL}',
|
||||
picUrl: ''
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,201 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [2023] [兮玥|190785909@qq.com]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -0,0 +1,113 @@
|
|||
# ChestnutCMS v1.3.21
|
||||
|
||||
### 系统简介
|
||||
|
||||
ChestnutCMS是前后端分离的企业级内容管理系统。项目基于[RuoYi-Vue](https://gitee.com/y_project/RuoYi-Vue)重构,集成[SaToken](https://gitee.com/dromara/sa-token)用户权限,[xxl-job](https://gitee.com/xuxueli0323/xxl-job)任务调度。支持站群管理、多平台静态化、元数据模型扩展、轻松组织各种复杂内容形态、多语言、全文检索。
|
||||
|
||||
### 系统预览
|
||||
|
||||
后台预览地址:<http://admin.1000mz.com>
|
||||
|
||||
账号:demo / a123456
|
||||
|
||||
企业站演示地址:<http://swikoon.1000mz.com>
|
||||
|
||||
> 企业演示站的静态资源已提交到仓库[chestnut-cms-wwwroot](https://gitee.com/liweiyi/chestnut-cms-wwwroot)。
|
||||
|
||||
资讯站演示地址:<http://news.1000mz.com>(会员演示账号:xxx333@126.com / a123456)
|
||||
|
||||
图片站演示地址:<http://tpz.1000mz.com>
|
||||
|
||||
|
||||
### 开发环境
|
||||
- JDK 17
|
||||
- Maven 3.8
|
||||
- MySQL 8.0
|
||||
- Redis 6.x
|
||||
|
||||
### 主要技术框架
|
||||
|
||||
| 技术框架 | 版本 | 应用说明 |
|
||||
|-------------------|---------|---------|
|
||||
| Spring Boot | 3.1.5 | 基础开发框架 |
|
||||
| Spring Boot Admin | 3.1.7 | 监控框架 |
|
||||
| Mybatis Plus | 3.5.3.2 | ORM框架 |
|
||||
| Flyway | 9.22.3 | 数据库版本管理 |
|
||||
| Yitter | 1.0.6 | 雪花ID |
|
||||
| Redisson | 3.24.1 | 分布式锁 |
|
||||
| FreeMarker | 2.3.32 | 模板引擎 |
|
||||
| Sa-Token | 1.37.0 | 权限认证 |
|
||||
| Xxl-Job | 2.4.0 | 任务调度 |
|
||||
| Lombok | 1.18.20 | 你懂的 |
|
||||
|
||||
### 相关文档
|
||||
|
||||
|
||||
- [Wiki-快速上手](https://gitee.com/liweiyi/ChestnutCMS/wikis/%E5%BF%AB%E9%80%9F%E5%BC%80%E5%A7%8B)
|
||||
- [WiKi-常用配置](https://gitee.com/liweiyi/ChestnutCMS/wikis/%E5%B8%B8%E7%94%A8%E9%85%8D%E7%BD%AE%E8%AF%B4%E6%98%8E)
|
||||
- [WiKi-Docker部署说明](https://gitee.com/liweiyi/ChestnutCMS/wikis/Docker%E9%83%A8%E7%BD%B2%E8%AF%B4%E6%98%8E)
|
||||
- [WiKi-站点访问配置](https://gitee.com/liweiyi/ChestnutCMS/wikis/%E7%AB%99%E7%82%B9%E8%AE%BF%E9%97%AE%E9%85%8D%E7%BD%AE)
|
||||
- [WiKi-使用手册](https://gitee.com/liweiyi/ChestnutCMS/wikis/%E7%B3%BB%E7%BB%9F%E4%BD%BF%E7%94%A8%E6%89%8B%E5%86%8C/%E5%BB%BA%E7%AB%99%E6%B5%81%E7%A8%8B)
|
||||
- [WiKi-模板手册](https://gitee.com/liweiyi/ChestnutCMS/wikis/%E6%A8%A1%E6%9D%BF%E6%89%8B%E5%86%8C/%E6%A8%A1%E6%9D%BF%E5%85%A8%E5%B1%80%E5%8F%98%E9%87%8F%E8%AF%B4%E6%98%8E)
|
||||
- [WiKi-二次开发手册](https://gitee.com/liweiyi/ChestnutCMS/wikis/%E4%BA%8C%E6%AC%A1%E5%BC%80%E5%8F%91%E6%89%8B%E5%86%8C/%E5%AE%9A%E6%97%B6%E4%BB%BB%E5%8A%A1)
|
||||
- [WiKi-常见问题](https://gitee.com/liweiyi/ChestnutCMS/wikis/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98)
|
||||
- [WiKi-版权声明](https://gitee.com/liweiyi/ChestnutCMS/wikis/%E7%89%88%E6%9D%83%E5%A3%B0%E6%98%8E)
|
||||
- [WiKi-免责声明](https://gitee.com/liweiyi/ChestnutCMS/wikis/%E5%85%8D%E8%B4%A3%E5%A3%B0%E6%98%8E)
|
||||
- [WiKi-版本变更日志](https://gitee.com/liweiyi/ChestnutCMS/wikis/%E7%89%88%E6%9C%AC%E5%8F%98%E6%9B%B4%E6%97%A5%E5%BF%97)
|
||||
|
||||
### 功能模块
|
||||
|
||||
| 模块 | 简介 |
|
||||
|--------------|----------------------------------------------|
|
||||
| 站点管理 | 多站点,支持图片水印、标题查重、扩展模型等扩展配置 |
|
||||
| 栏目管理 | 普通栏目+链接栏目,扩展配置优先级高于站点扩展配置 |
|
||||
| 内容管理 | 内容类型:文章+图片集+音视频集,页面部件:动态自定义区块+广告,内容回收站 |
|
||||
| 资源管理 | 图片、音视频等各类静态资源管理,支持OSS/COS/MinIO对象存储 |
|
||||
| 发布通道 | 支持多通道不同类型静态文件发布,可同时发布到PC、H5,html、json等 |
|
||||
| 模板管理 | 静态化模板,支持在线编辑 |
|
||||
| 模板指令 | FreeMarker自定义标签及模板函数的参数及用法说明 |
|
||||
| 文件管理 | 当前站点资源目录及发布通道静态化目录管理,支持文本在线编辑 |
|
||||
| 扩展模型 | 站点、栏目及内容的动态模型扩展,系统默认数据表保存,支持自定义 |
|
||||
| 词汇管理 | 热词、TAG词、敏感词、易错词 |
|
||||
| 内容索引 | 默认支持ElasticSearch+IK创建内容索引,支持标题内容全文检索 |
|
||||
| 检索词库 | 自定义检索词库,支持扩展词和停用词动态扩展 |
|
||||
| 检索日志 | 用户搜索的日志记录,支持一键加入扩展词库 |
|
||||
| 友链管理 | 友情链接 |
|
||||
| 广告管理 | 广告基于页面部件扩展的简单广告功能,支持权重及上下线时间配置,支持广告点击/展现日志统计 |
|
||||
| 评论管理 | 基础功能模块 |
|
||||
| 调查问卷 | 基础功能模块,默认支持文字类型单选、多选、输入 |
|
||||
| 自定义表单 | 基于元数据模块扩展,支持模板标签 |
|
||||
| 会员管理 | 支持自定义会员等级,等级经验值来源动态配置 |
|
||||
| 访问统计 | 对接百度统计API |
|
||||
| 用户管理 | 后台用户管理,支持用户独立权限配置 |
|
||||
| 机构管理 | 多级系统组织机构(公司、部门、小组) |
|
||||
| 角色管理 | 支持按角色分配菜单权限、站点和栏目相关操作权限配置 |
|
||||
| 岗位管理 | 配置系统用户所属担任职务 |
|
||||
| 菜单管理 | 配置系统菜单,操作权限,按钮权限标识等 |
|
||||
| 字典管理 | 对系统中经常使用的一些固定的数据进行维护,代码层面定义 |
|
||||
| 参数管理 | 对系统动态配置常用参数,代码层面定义 |
|
||||
| 通知公告 | 系统通知公告信息发布维护 |
|
||||
| 安全配置 | 密码强度、密码过期、首次登陆强制修改、登陆异常策略配置 |
|
||||
| 国际化 | 为菜单等动态数据国际化配置提供基础支持,可覆盖后台代码配置 |
|
||||
| 安全配置 | 密码强度、密码过期、首次登陆强制修改、登陆异常策略配置 |
|
||||
| 系统日志 | 统一日志管理,支持扩展 |
|
||||
| 操作日志 | 系统操作日志扩展,记录操作参数、异常信息及请求耗时 |
|
||||
| 登录日志 | 系统登录日志扩展,记录用户登录日志,包含登录异常 |
|
||||
| 在线用户 | 当前系统中活跃用户状态监控,支持踢下线 |
|
||||
| 任务调度 | 基于XXL-JOB的分布式任务调度 |
|
||||
| 定时任务 | 基于Spring的TaskScheduler实现的单机定时任务 |
|
||||
| 异步任务 | 异步任务状态查看,支持手动结束 |
|
||||
| 服务监控 | 监视当前系统CPU、内存、磁盘、堆栈等相关信息 |
|
||||
| 缓存监控 | 对系统的缓存信息查询,命令统计等 |
|
||||
| GroovyScript | 支持Groovy脚本在线执行 |
|
||||
|
||||
### 开源协议补充说明
|
||||
|
||||
1. ChestnutCMS 遵循《Apache-2.0开源协议》,使用本系统不得用于违反国家有关政策的相关软件和应用。
|
||||
2. 系统可免费商用,但是必须包含原始版权声明和许可声明 ,不可移除后台登录页面底部的版权申明“Copyright © 2022-2023 ChestnutCMS (1000mz.com) All Rights Reserved.”。
|
||||
3. 本项目所包含的第三方源码版权信息需另行标注。
|
||||
|
||||
### QQ交流群
|
||||
群号:568506424 口令:举个栗子
|
||||
|
||||
如果本项目对您有一丢丢小帮助 :kissing_heart: 点个小星星吧 :star2:
|
|
@ -0,0 +1,35 @@
|
|||
#!/bin/bash
|
||||
DOCKERUSER={{DOCKERUSER}}
|
||||
DOCKERPWD={{DOCKERPWD}}
|
||||
DOCKER_HUB_URL={{DOCKER_HUB_URL}}
|
||||
IMAGE_REPOSITORY={{IMAGE_REPOSITORY}}
|
||||
IMAGE_TAG={{IMAGE_TAG}}
|
||||
|
||||
DOCKER_IMAGE=${DOCKER_HUB_URL}/${IMAGE_REPOSITORY}
|
||||
# pull docker image
|
||||
echo ${DOCKERPWD} | docker login --username=${DOCKERUSER} --password-stdin ${DOCKER_HUB_URL}
|
||||
docker pull ${DOCKER_IMAGE}:${IMAGE_TAG}
|
||||
docker logout ${DOCKER_HUB_URL}
|
||||
|
||||
# 处理none镜像
|
||||
NONE_IMAGE_ID_ARR=$(docker images | grep "${DOCKER_IMAGE}" | grep "<none>" | awk '{print $3}')
|
||||
for NONE_IMAGE_ID in ${NONE_IMAGE_ID_ARR[*]}; do
|
||||
# 停止容器
|
||||
NONE_RUNNING_CONTAINER_ID_ARR=$(docker ps -a | grep $NONE_IMAGE_ID | awk '{print $1}')
|
||||
for NONE_RUNNING_CONTAINER_ID in ${NONE_RUNNING_CONTAINER_ID_ARR[*]}; do
|
||||
docker stop ${NONE_RUNNING_CONTAINER_ID}
|
||||
echo ">>>>> Stop docker container done. CONTAINER_ID: ${NONE_RUNNING_CONTAINER_ID}"
|
||||
done
|
||||
# 删除容器
|
||||
NONE_STOPPED_CONTAINER_ID_ARR=$(docker ps -a | grep $NONE_IMAGE_ID | grep 'Exited' | awk '{print $1}')
|
||||
for NONE_STOPPED_CONTAINER_ID in ${NONE_STOPPED_CONTAINER_ID_ARR[*]}; do
|
||||
docker rm ${NONE_STOPPED_CONTAINER_ID}
|
||||
echo ">>>>> Delete docker container done. CONTAINER_ID: ${NONE_STOPPED_CONTAINER_ID}"
|
||||
done
|
||||
# 删除镜像
|
||||
docker rmi $NONE_IMAGE_ID
|
||||
echo ">>>>>delete docker <none> image done: $NONE_IMAGE_ID"
|
||||
done
|
||||
|
||||
# 启动容器
|
||||
docker-compose up -d
|
|
@ -0,0 +1,22 @@
|
|||
#!/bin/bash
|
||||
DOCKER_IMAGE={{DOCKER_HUB_URL}}/{{IMAGE_REPOSITORY}}
|
||||
|
||||
# 删除docker镜像
|
||||
NONE_IMAGE_ID_ARR=$(docker images | grep "${DOCKER_IMAGE}" | awk '{print $3}')
|
||||
for NONE_IMAGE_ID in ${NONE_IMAGE_ID_ARR[*]}; do
|
||||
# 停止容器
|
||||
NONE_RUNNING_CONTAINER_ID_ARR=$(docker ps -a | grep $NONE_IMAGE_ID | awk '{print $1}')
|
||||
for NONE_RUNNING_CONTAINER_ID in ${NONE_RUNNING_CONTAINER_ID_ARR[*]}; do
|
||||
docker stop ${NONE_RUNNING_CONTAINER_ID}
|
||||
echo ">>>>> Stop docker container done. CONTAINER_ID: ${NONE_RUNNING_CONTAINER_ID}"
|
||||
done
|
||||
# 删除容器
|
||||
NONE_STOPPED_CONTAINER_ID_ARR=$(docker ps -a | grep $NONE_IMAGE_ID | grep 'Exited' | awk '{print $1}')
|
||||
for NONE_STOPPED_CONTAINER_ID in ${NONE_STOPPED_CONTAINER_ID_ARR[*]}; do
|
||||
docker rm ${NONE_STOPPED_CONTAINER_ID}
|
||||
echo ">>>>> Delete docker container done. CONTAINER_ID: ${NONE_STOPPED_CONTAINER_ID}"
|
||||
done
|
||||
# 删除镜像
|
||||
docker rmi $NONE_IMAGE_ID
|
||||
echo ">>>>>delete docker image done: $NONE_IMAGE_ID"
|
||||
done
|
|
@ -0,0 +1,37 @@
|
|||
FROM openjdk:17-jdk-oraclelinux8 as builder
|
||||
|
||||
ARG APP_NAME
|
||||
ARG APP_VERSION
|
||||
ENV SPRING_PROFILES_ACTIVE=prod
|
||||
|
||||
WORKDIR /home/app
|
||||
|
||||
COPY target/$APP_NAME.jar app.jar
|
||||
|
||||
RUN java -Djarmode=layertools -Dspring.profiles.active=${SPRING_PROFILES_ACTIVE} -jar app.jar extract
|
||||
|
||||
FROM openjdk:17-jdk-oraclelinux8
|
||||
|
||||
WORKDIR /home/app
|
||||
|
||||
COPY --from=builder /home/app/dependencies/ ./
|
||||
|
||||
COPY --from=builder /home/app/spring-boot-loader/ ./
|
||||
|
||||
COPY --from=builder /home/app/snapshot-dependencies/ ./
|
||||
|
||||
COPY --from=builder /home/app/application ./
|
||||
|
||||
ENV TZ="Asia/Shanghai" \
|
||||
SERVER_PORT=8090 \
|
||||
JVM_OPTS="-Xms2g -Xmx2g -Xmn512m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m" \
|
||||
JAVA_OPTS=""
|
||||
|
||||
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone \
|
||||
&& mkdir -p logs
|
||||
|
||||
VOLUME ["/home/app/logs","/home/app/uploadPath","/home/app/wwwroot_release","/home/app/_xy_member"]
|
||||
|
||||
EXPOSE $SERVER_PORT $NETTY_SOCKET_PORT
|
||||
|
||||
ENTRYPOINT ["sh","-c","java $JVM_OPTS $JAVA_OPTS org.springframework.boot.loader.JarLauncher"]
|
|
@ -0,0 +1,106 @@
|
|||
version: '3'
|
||||
networks:
|
||||
default:
|
||||
external:
|
||||
name: cc_bridge
|
||||
services:
|
||||
cc-mysql:
|
||||
image: mysql:8.0.32
|
||||
container_name: cc-mysql
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=hello1234
|
||||
- TZ=Asia/Shanghai
|
||||
networks:
|
||||
- default
|
||||
ports:
|
||||
- '3306:3306'
|
||||
volumes:
|
||||
- ./mysql/data:/var/lib/mysql
|
||||
- ./mysql/conf/my.cnf:/etc/my.cnf
|
||||
- ./mysql/init:/docker-entrypoint-initdb.d
|
||||
|
||||
cc-redis:
|
||||
image: redis:6.2.13
|
||||
container_name: cc-redis
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- TZ=Asia/Shanghai
|
||||
ports:
|
||||
- 6379:6379
|
||||
volumes:
|
||||
- "./redis/conf:/usr/local/etc/redis"
|
||||
- "./redis/data:/data"
|
||||
command:
|
||||
redis-server --port 6379 --requirepass "b18a03" --appendonly yes
|
||||
networks:
|
||||
- default
|
||||
|
||||
cc-minio:
|
||||
image: minio/minio:latest
|
||||
container_name: cc-minio
|
||||
ports:
|
||||
- 9000:9000
|
||||
- 9999:9999
|
||||
volumes:
|
||||
- ./minio/data:/data
|
||||
- ./minio/config:/root/.minio
|
||||
environment:
|
||||
MINIO_ROOT_USER: "root"
|
||||
MINIO_ROOT_PASSWORD: "minioadmin"
|
||||
MINIO_ACCESS_KEY: minioccadmin
|
||||
MINIO_SECRET_KEY: minioccadmin
|
||||
logging:
|
||||
options:
|
||||
max-size: "50M" # 最大文件上传限制
|
||||
max-file: "10"
|
||||
driver: json-file
|
||||
command: server /data --console-address ":9999"
|
||||
restart: always
|
||||
networks:
|
||||
- default
|
||||
|
||||
cc-xxl-job-admin:
|
||||
image: xuxueli/xxl-job-admin:2.4.0
|
||||
restart: unless-stopped
|
||||
container_name: cc-xxl-job-admin
|
||||
ports:
|
||||
- 18080:8080
|
||||
environment:
|
||||
TZ: Asia/Shanghai
|
||||
PARAMS: "--spring.datasource.url=jdbc:mysql://cc-mysql/xxl_job?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai --spring.datasource.username=root --spring.datasource.password=xxxxxx"
|
||||
volumes:
|
||||
- ./xxl-job/logs:/data/applogs
|
||||
depends_on:
|
||||
- cc-mysql
|
||||
networks:
|
||||
- default
|
||||
|
||||
cc-elasticsearch:
|
||||
# 构建镜像的相关配置在 chestnut-modules/chestnut-search/docker 目录下
|
||||
image: elasticsearch-ik:8.5.2
|
||||
restart: unless-stopped
|
||||
container_name: cc-elasticsearch
|
||||
healthcheck:
|
||||
test: [ "CMD-SHELL", "curl --silent --fail localhost:9200/_cluster/health || exit 1" ]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
ports:
|
||||
- 9200:9200
|
||||
- 9300:9300
|
||||
environment:
|
||||
discovery.type: single-node
|
||||
ES_JAVA_OPTS: -Xms1024m -Xmx1024m
|
||||
volumes:
|
||||
- ./elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml
|
||||
- ./elasticsearch/data:/usr/share/elasticsearch/data
|
||||
- ./elasticsearch/logs:/usr/share/elasticsearch/logs
|
||||
- ./elasticsearch/ik-config:/usr/share/elasticsearch/plugins/ik/config
|
||||
ulimits:
|
||||
memlock:
|
||||
soft: -1
|
||||
hard: -1
|
||||
networks:
|
||||
- default
|
|
@ -0,0 +1,24 @@
|
|||
version: '3'
|
||||
networks:
|
||||
default:
|
||||
external:
|
||||
name: cc_bridge
|
||||
services:
|
||||
chestnut-cms:
|
||||
image: {{DOCKER_IMAGE}}
|
||||
container_name: chestnut-cms
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- default
|
||||
environment:
|
||||
SERVER_PORT: 8090
|
||||
SPRING_PROFILES_ACTIVE: prod
|
||||
JVM_OPTS: "-Xms2048M -Xmx2048m -Xmn512m -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m"
|
||||
ports:
|
||||
- "8090:8090"
|
||||
- "8899:8899"
|
||||
volumes:
|
||||
- ./logs:/home/app/logs
|
||||
- ./uploadPath:/home/app/uploadPath
|
||||
- ./_xy_member:/home/app/_xy_member
|
||||
- ./wwwroot_release:/home/app/wwwroot_release
|
|
@ -0,0 +1,29 @@
|
|||
[mysqld]
|
||||
datadir=/var/lib/mysql/data
|
||||
port = 3306
|
||||
socket=/tmp/mysql.sock
|
||||
|
||||
symbolic-links=0
|
||||
#log-error=/var/log/mysqld.log
|
||||
pid-file=/tmp/mysqld/mysqld.pid
|
||||
sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'
|
||||
|
||||
[client]
|
||||
default-character-set=utf8
|
||||
|
||||
[mysql]
|
||||
default-character-set=utf8
|
||||
|
||||
[mysqld]
|
||||
log-bin=mysql-bin
|
||||
binlog-format=ROW
|
||||
server_id=1
|
||||
max_connections=1000
|
||||
lower_case_table_names=1
|
||||
#user=mysql
|
||||
|
||||
init_connect='SET collation_connection = utf8_general_ci'
|
||||
init_connect='SET NAMES utf8'
|
||||
character-set-server=utf8
|
||||
collation-server=utf8_general_ci
|
||||
skip-character-set-client-handshake
|
|
@ -0,0 +1 @@
|
|||
requirepass b18a03
|
|
@ -0,0 +1,170 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>chestnut</artifactId>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<version>1.3.21</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<packaging>jar</packaging>
|
||||
<artifactId>chestnut-admin</artifactId>
|
||||
|
||||
<description>
|
||||
web服务入口
|
||||
</description>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- spring-boot-devtools -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-devtools</artifactId>
|
||||
<optional>true</optional> <!-- 表示依赖不会传递 -->
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- 系统模块 -->
|
||||
<dependency>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-system</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 代码生成 -->
|
||||
<dependency>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-generator</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 元数据模块 -->
|
||||
<dependency>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-meta</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 数据统计模块 -->
|
||||
<dependency>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-stat</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 会员模块 -->
|
||||
<dependency>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-member</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 文章 -->
|
||||
<dependency>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-cms-article</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 广告 -->
|
||||
<dependency>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-cms-advertisement</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 页面区块 -->
|
||||
<dependency>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-cms-block</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 扩展模型 -->
|
||||
<dependency>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-cms-exmodel</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 图集 -->
|
||||
<dependency>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-cms-image</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 音视频集 -->
|
||||
<dependency>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-cms-media</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 友链 -->
|
||||
<dependency>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-cms-link</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 词汇 -->
|
||||
<dependency>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-cms-word</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 内容索引 -->
|
||||
<dependency>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-cms-search</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- CMS评论模块 -->
|
||||
<dependency>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-cms-comment</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- CMS调查投票模块 -->
|
||||
<dependency>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-cms-vote</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- CMS访问统计模块 -->
|
||||
<dependency>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-cms-stat</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- CMS自定义表单模块 -->
|
||||
<dependency>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-cms-customform</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- CMS会员扩展模块 -->
|
||||
<dependency>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-cms-member</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<!--spring-boot打包插件-->
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<version>${spring-boot.version}</version>
|
||||
<configuration>
|
||||
<!-- 如果没有该配置,devtools不会生效 -->
|
||||
<!-- <fork>true</fork> -->
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>repackage</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
<finalName>${project.artifactId}</finalName>
|
||||
</build>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,22 @@
|
|||
package com.chestnut;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||
|
||||
/**
|
||||
* 启动程序
|
||||
*
|
||||
* @author 兮玥
|
||||
* @email 190785909@qq.com
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class ChestnutApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
long s = System.currentTimeMillis();
|
||||
System.setProperty("spring.devtools.restart.enabled", "false");
|
||||
SpringApplication.run(ChestnutApplication.class, args);
|
||||
System.out.println("ChestnutApplication startup, cost: " + (System.currentTimeMillis() - s) + "ms");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package com.chestnut;
|
||||
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
|
||||
|
||||
/**
|
||||
* web容器中进行部署
|
||||
*
|
||||
* @author 兮玥
|
||||
* @email 190785909@qq.com
|
||||
*/
|
||||
public class ChestnutServletInitializer extends SpringBootServletInitializer {
|
||||
|
||||
@Override
|
||||
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
|
||||
return application.sources(ChestnutApplication.class);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,265 @@
|
|||
# 项目相关配置
|
||||
chestnut:
|
||||
# 名称
|
||||
name: ChestnutCMS
|
||||
# 版本
|
||||
version: 1.3.21
|
||||
# 版权年份
|
||||
copyrightYear: 2023
|
||||
system:
|
||||
# 演示模式开关
|
||||
demoMode: false
|
||||
# 文件路径 示例( Windows配置D:/chestnut/uploadPath,Linux配置 /home/chestnut/uploadPath)
|
||||
uploadPath: 'E:/dev/workspace_chestnut/uploadPath'
|
||||
# 验证码类型 math 数组计算 char 字符验证
|
||||
captchaType: math
|
||||
member:
|
||||
uploadPath: 'E:/dev/workspace_chestnut/_xy_member/'
|
||||
|
||||
# 开发环境配置
|
||||
server:
|
||||
# 服务器的HTTP端口,默认为8080
|
||||
port: 8080
|
||||
# 开启优雅停机
|
||||
shutdown: graceful
|
||||
servlet:
|
||||
# 应用的访问路径
|
||||
context-path: /
|
||||
tomcat:
|
||||
# tomcat的URI编码
|
||||
uri-encoding: UTF-8
|
||||
# 连接数满后的排队数,默认为100
|
||||
accept-count: 1000
|
||||
threads:
|
||||
# tomcat最大线程数,默认为200
|
||||
max: 800
|
||||
# Tomcat启动初始化的线程数,默认值10
|
||||
min-spare: 100
|
||||
|
||||
# 日志配置
|
||||
logging:
|
||||
level:
|
||||
com.chestnut: debug
|
||||
org.springframework: warn
|
||||
|
||||
# Spring配置
|
||||
spring:
|
||||
# 资源信息
|
||||
messages:
|
||||
# 国际化资源文件路径
|
||||
basename: i18n/messages
|
||||
lifecycle:
|
||||
# 设置停机缓冲时间,默认:30s
|
||||
timeout-per-shutdown-phase: 20s
|
||||
# 文件上传
|
||||
servlet:
|
||||
multipart:
|
||||
# 单个文件大小
|
||||
max-file-size: 20MB
|
||||
# 设置总上传的文件大小
|
||||
max-request-size: 100MB
|
||||
# 服务模块
|
||||
devtools:
|
||||
restart:
|
||||
# 热部署开关
|
||||
enabled: false
|
||||
freemarker:
|
||||
check-template-location: false
|
||||
elasticsearch:
|
||||
uris: http://127.0.0.1:9200
|
||||
username: elastic
|
||||
password: hello1234
|
||||
# redis 配置
|
||||
data:
|
||||
redis:
|
||||
# 地址
|
||||
host: 140.143.157.1
|
||||
# 端口,默认为6379
|
||||
port: 6379
|
||||
# 数据库索引
|
||||
database: 15
|
||||
# 密码
|
||||
password: x7Zhj3twzSdDwx2A
|
||||
# 连接超时时间
|
||||
timeout: 10s
|
||||
lettuce:
|
||||
pool:
|
||||
# 连接池中的最小空闲连接
|
||||
min-idle: 0
|
||||
# 连接池中的最大空闲连接
|
||||
max-idle: 8
|
||||
# 连接池的最大数据库连接数
|
||||
max-active: 8
|
||||
# #连接池最大阻塞等待时间(使用负值表示没有限制)
|
||||
max-wait: -1ms
|
||||
flyway:
|
||||
enabled: false
|
||||
# 迁移sql脚本文件存放路径,默认:classpath:db/migration
|
||||
locations: classpath:db/migration/mysql
|
||||
# 迁移sql脚本文件名称的前缀,默认:V
|
||||
sql-migration-prefix: V
|
||||
# 迁移sql脚本文件名称分隔符,默认2个下划线:__
|
||||
sql-migration-separator: __
|
||||
# 迁移sql脚本文件名称后缀
|
||||
sql-migration-suffixes: .sql
|
||||
# 迁移时是否进行校验
|
||||
validate-on-migrate: true
|
||||
# 当迁移发现数据库非空且存在没有元数据的表时,自动执行基准迁移,新建schema_version表
|
||||
baseline-on-migrate: true
|
||||
# 数据库配置
|
||||
datasource:
|
||||
type: com.zaxxer.hikari.HikariDataSource
|
||||
dynamic:
|
||||
primary: master
|
||||
# 严格模式 匹配不到数据源则报错
|
||||
strict: true
|
||||
# 主库
|
||||
datasource:
|
||||
master:
|
||||
type: ${spring.datasource.type}
|
||||
driverClassName: com.mysql.cj.jdbc.Driver
|
||||
url: jdbc:mysql://cd-cynosdbmysql-grp-9rqrhxsm.sql.tencentcdb.com:27981/cms?useSSL=false&characterEncoding=UTF-8&serverTimezone=GMT%2B8
|
||||
username: root
|
||||
password: Sxyanzhu@cf
|
||||
# 从库
|
||||
#slave:
|
||||
# lazy: true
|
||||
# type: ${spring.datasource.type}
|
||||
# driverClassName: com.mysql.cj.jdbc.Driver
|
||||
# url: jdbc:mysql://127.0.0.1:3308/ry_cms1?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
|
||||
# username:
|
||||
# password:
|
||||
hikari:
|
||||
# 连接池名
|
||||
pool-name: HikariCP
|
||||
# 连接超时时间:毫秒, 默认30秒
|
||||
connection-timeout: 2000
|
||||
# 最小空闲连接,默认值10,小于0或大于maximum-pool-size,都会重置为maximum-pool-size
|
||||
minimum-idle: 5
|
||||
# 最大连接数,小于等于0会被重置为默认值10;大于零小于1会被重置为minimum-idle的值
|
||||
maximum-pool-size: 20
|
||||
# 空闲连接最大存活时间,默认值600000(10分钟),大于等于max-lifetime且max-lifetime>0,会被重置为0;不等于0且小于10秒,会被重置为10秒。
|
||||
idle-timeout: 200000
|
||||
# 连接池返回的连接默认自动提交,默认只 true
|
||||
auto-commit: true
|
||||
# 连接最大存活时间,不等于0且小于30秒,会被重置为默认值30分钟.设置应该比mysql设置的超时时间短
|
||||
max-lifetime: 1800000
|
||||
# 用于测试连接是否可用的查询语句
|
||||
connection-test-query: SELECT 1
|
||||
|
||||
# 邮件配置
|
||||
mail:
|
||||
host: smtp.163.com
|
||||
port: 465
|
||||
username: xxx@163.com
|
||||
# 授权码
|
||||
password: xxx
|
||||
# 编码格式
|
||||
default-encoding: utf-8
|
||||
# 协议
|
||||
protocol: smtps
|
||||
properties:
|
||||
mail:
|
||||
smtp:
|
||||
ssl:
|
||||
enable: true
|
||||
auth: true
|
||||
starttls:
|
||||
enable: true
|
||||
required: true
|
||||
# 监控配置
|
||||
application:
|
||||
name: "ChestnutCMS"
|
||||
boot:
|
||||
admin:
|
||||
client:
|
||||
# 增加客户端开关
|
||||
enabled: false
|
||||
# Admin Server URL
|
||||
url: http://127.0.0.1:8090/admin
|
||||
instance:
|
||||
service-host-type: IP
|
||||
username: chestnut
|
||||
password: 123456
|
||||
|
||||
# Actuator 监控端点的配置项
|
||||
management:
|
||||
trace:
|
||||
http:
|
||||
enabled: true
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: '*'
|
||||
endpoint:
|
||||
health:
|
||||
show-details: ALWAYS
|
||||
logfile:
|
||||
external-file: ./logs/client.log
|
||||
|
||||
sa-token:
|
||||
# token名称 (同时也是cookie名称)
|
||||
token-name: Authorization
|
||||
# token前缀
|
||||
token-prefix: Bearer
|
||||
# token有效期,单位s 默认30天, -1代表永不过期
|
||||
timeout: 2592000
|
||||
# token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
|
||||
active-timeout: -1
|
||||
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
|
||||
is-concurrent: true
|
||||
# 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
|
||||
is-share: true
|
||||
# token风格
|
||||
token-style: uuid
|
||||
# 是否输出操作日志
|
||||
is-log: true
|
||||
|
||||
# MyBatis配置
|
||||
mybatis-plus:
|
||||
global-config:
|
||||
enable-sql-runner: true
|
||||
db-config:
|
||||
logic-delete-field: deleted # 全局逻辑删除的实体字段名
|
||||
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
|
||||
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
|
||||
# 搜索指定包别名
|
||||
typeAliasesPackage: com.chestnut.**.domain
|
||||
# 配置mapper的扫描,找到所有的mapper.xml映射文件
|
||||
mapperLocations: classpath*:mapper/**/*Mapper.xml
|
||||
configuration:
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
|
||||
# 防止XSS攻击
|
||||
xss:
|
||||
# 过滤开关
|
||||
enabled: true
|
||||
mode: clean
|
||||
# 过滤链接
|
||||
urlPatterns:
|
||||
- /system/*
|
||||
- /monitor/*
|
||||
- /tool/*
|
||||
|
||||
xxl:
|
||||
job:
|
||||
enable: false
|
||||
accessToken: default_token
|
||||
adminAddresses: http://127.0.0.1:18080/xxl-job-admin
|
||||
executor:
|
||||
### 执行器AppName [选填]:执行器心跳注册分组依据;为空则关闭自动注册
|
||||
appname: chestnut-admin
|
||||
### 执行器注册 [选填]:优先使用该配置作为注册地址,为空时使用内嵌服务 ”IP:PORT“ 作为注册地址。从而更灵活的支持容器类型执行器动态IP和动态映射端口问题。
|
||||
#address:
|
||||
### 执行器IP [选填]:默认为空表示自动获取IP,多网卡时可手动设置指定IP,该IP不会绑定Host仅作为通讯实用;地址信息用于 "执行器注册" 和 "调度中心请求并触发任务";
|
||||
ip:
|
||||
### 执行器端口号 [选填]:小于等于0则自动获取;默认端口为9999,单机部署多个执行器时,注意要配置不同执行器端口;
|
||||
port: 9968
|
||||
### 执行器运行日志文件存储磁盘路径 [选填] :需要对该路径拥有读写权限;为空则使用默认路径;
|
||||
logpath: E:/dev/workspace_chestnut/ChestnutCMS/chestnut-modules/chestnut-xxljob/jobhandler
|
||||
### 执行器日志文件保存天数 [选填] : 过期日志自动清理, 限制值大于等于3时生效; 否则, 如-1, 关闭自动清理功能;默认:30
|
||||
logretentiondays: 30
|
||||
|
||||
jasypt:
|
||||
encryptor:
|
||||
password: qsakjdnfij234234sdf67
|
|
@ -0,0 +1,245 @@
|
|||
# 项目相关配置
|
||||
chestnut:
|
||||
# 名称
|
||||
name: ChestnutCMS
|
||||
# 版本
|
||||
version: 1.3.21
|
||||
# 版权年份
|
||||
copyrightYear: 2023
|
||||
system:
|
||||
# 演示模式开关
|
||||
demoMode: true
|
||||
# 文件路径 示例( Windows配置D:/chestnut/uploadPath,Linux配置 /home/app/uploadPath)
|
||||
uploadPath: /home/app/uploadPath
|
||||
# 验证码类型 math 数组计算 char 字符验证
|
||||
captchaType: math
|
||||
freemarker:
|
||||
templateLoaderPath: /home/app/statics
|
||||
cms:
|
||||
resourceRoot: /home/app/wwwroot_release
|
||||
publish:
|
||||
consumerCount: 1
|
||||
|
||||
# 开发环境配置
|
||||
server:
|
||||
# 服务器的HTTP端口,默认为8090
|
||||
port: 8090
|
||||
servlet:
|
||||
# 应用的访问路径
|
||||
context-path: /
|
||||
tomcat:
|
||||
# tomcat的URI编码
|
||||
uri-encoding: UTF-8
|
||||
# 连接数满后的排队数,默认为100
|
||||
accept-count: 1000
|
||||
threads:
|
||||
# tomcat最大线程数,默认为200
|
||||
max: 800
|
||||
# Tomcat启动初始化的线程数,默认值10
|
||||
min-spare: 100
|
||||
|
||||
# 日志配置
|
||||
logging:
|
||||
level:
|
||||
com.chestnut: debug
|
||||
org.springframework: warn
|
||||
|
||||
# Spring配置
|
||||
spring:
|
||||
# 资源信息
|
||||
messages:
|
||||
# 国际化资源文件路径
|
||||
basename: i18n/messages
|
||||
# 文件上传
|
||||
servlet:
|
||||
multipart:
|
||||
# 单个文件大小
|
||||
max-file-size: 20MB
|
||||
# 设置总上传的文件大小
|
||||
max-request-size: 100MB
|
||||
# 服务模块
|
||||
devtools:
|
||||
restart:
|
||||
# 热部署开关
|
||||
enabled: false
|
||||
freemarker:
|
||||
check-template-location: false
|
||||
elasticsearch:
|
||||
uris: http://cc-elasticsearch:9200
|
||||
username: elastic
|
||||
password: hello1234
|
||||
# redis 配置
|
||||
data:
|
||||
redis:
|
||||
# 地址
|
||||
host: 140.143.157.1
|
||||
# 端口,默认为6379
|
||||
port: 6379
|
||||
# 数据库索引
|
||||
database: 15
|
||||
# 密码
|
||||
password: x7Zhj3twzSdDwx2A
|
||||
# 连接超时时间
|
||||
timeout: 10s
|
||||
lettuce:
|
||||
pool:
|
||||
# 连接池中的最小空闲连接
|
||||
min-idle: 0
|
||||
# 连接池中的最大空闲连接
|
||||
max-idle: 8
|
||||
# 连接池的最大数据库连接数
|
||||
max-active: 8
|
||||
# #连接池最大阻塞等待时间(使用负值表示没有限制)
|
||||
max-wait: -1ms
|
||||
flyway:
|
||||
enabled: false
|
||||
# 迁移sql脚本文件存放路径,默认:classpath:db/migration
|
||||
locations: classpath:db/migration/mysql
|
||||
# 迁移sql脚本文件名称的前缀,默认:V
|
||||
sql-migration-prefix: V
|
||||
# 迁移sql脚本文件名称分隔符,默认2个下划线:__
|
||||
sql-migration-separator: __
|
||||
# 迁移sql脚本文件名称后缀
|
||||
sql-migration-suffixes: .sql
|
||||
# 迁移时是否进行校验
|
||||
validate-on-migrate: true
|
||||
# 当迁移发现数据库非空且存在没有元数据的表时,自动执行基准迁移,新建schema_version表
|
||||
baseline-on-migrate: true
|
||||
# 数据库配置
|
||||
datasource:
|
||||
type: com.zaxxer.hikari.HikariDataSource
|
||||
dynamic:
|
||||
primary: master
|
||||
# 严格模式 匹配不到数据源则报错
|
||||
strict: true
|
||||
# 主库
|
||||
datasource:
|
||||
master:
|
||||
type: ${spring.datasource.type}
|
||||
driverClassName: com.mysql.cj.jdbc.Driver
|
||||
url: jdbc:mysql://cd-cynosdbmysql-grp-9rqrhxsm.sql.tencentcdb.com:27981/cms?useSSL=false&characterEncoding=UTF-8&serverTimezone=GMT%2B8
|
||||
username: root
|
||||
password: Sxyanzhu@cf
|
||||
# 从库
|
||||
# slave:
|
||||
# lazy: true
|
||||
# type: ${spring.datasource.type}
|
||||
# driverClassName: com.mysql.cj.jdbc.Driver
|
||||
# url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
|
||||
# username:
|
||||
# password:
|
||||
hikari:
|
||||
# 连接池名
|
||||
pool-name: HikariCP
|
||||
# 连接超时时间:毫秒, 默认30秒
|
||||
connection-timeout: 2000
|
||||
# 最小空闲连接,默认值10,小于0或大于maximum-pool-size,都会重置为maximum-pool-size
|
||||
minimum-idle: 5
|
||||
# 最大连接数,小于等于0会被重置为默认值10;大于零小于1会被重置为minimum-idle的值
|
||||
maximum-pool-size: 20
|
||||
# 空闲连接最大存活时间,默认值600000(10分钟),大于等于max-lifetime且max-lifetime>0,会被重置为0;不等于0且小于10秒,会被重置为10秒。
|
||||
idle-timeout: 200000
|
||||
# 连接池返回的连接默认自动提交,默认只 true
|
||||
auto-commit: true
|
||||
# 连接最大存活时间,不等于0且小于30秒,会被重置为默认值30分钟.设置应该比mysql设置的超时时间短
|
||||
max-lifetime: 1800000
|
||||
# 用于测试连接是否可用的查询语句
|
||||
connection-test-query: SELECT 1
|
||||
# 邮件配置
|
||||
mail:
|
||||
host: smtp.163.com
|
||||
port: 465
|
||||
username: xxx@163.com
|
||||
# 授权码
|
||||
password: xxx
|
||||
# 编码格式
|
||||
default-encoding: utf-8
|
||||
# 协议
|
||||
protocol: smtps
|
||||
properties:
|
||||
mail:
|
||||
smtp:
|
||||
ssl:
|
||||
enable: true
|
||||
auth: true
|
||||
starttls:
|
||||
enable: true
|
||||
required: true
|
||||
# 监控配置
|
||||
application:
|
||||
name: "ChestnutCMS"
|
||||
boot:
|
||||
admin:
|
||||
client:
|
||||
# 增加客户端开关
|
||||
enabled: false
|
||||
# Admin Server URL
|
||||
url: http://127.0.0.1:8090/admin
|
||||
instance:
|
||||
service-host-type: IP
|
||||
username: chestnut
|
||||
password: 123456
|
||||
|
||||
# SaToken配置
|
||||
sa-token:
|
||||
# token名称 (同时也是cookie名称)
|
||||
token-name: Authorization
|
||||
# token前缀
|
||||
token-prefix: Bearer
|
||||
# token有效期,单位s 默认30天, -1代表永不过期
|
||||
timeout: 2592000
|
||||
# token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
|
||||
active-timeout: -1
|
||||
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
|
||||
is-concurrent: true
|
||||
# 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
|
||||
is-share: true
|
||||
# token风格
|
||||
token-style: uuid
|
||||
# 是否输出操作日志
|
||||
is-log: true
|
||||
|
||||
# MyBatis配置
|
||||
mybatis-plus:
|
||||
global-config:
|
||||
enable-sql-runner: true
|
||||
# 搜索指定包别名
|
||||
typeAliasesPackage: com.chestnut.**.domain
|
||||
# 配置mapper的扫描,找到所有的mapper.xml映射文件
|
||||
mapperLocations: classpath*:mapper/**/*Mapper.xml
|
||||
configuration:
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
|
||||
# 防止XSS攻击
|
||||
xss:
|
||||
# 过滤开关
|
||||
enabled: true
|
||||
mode: clean
|
||||
# 过滤链接
|
||||
urlPatterns:
|
||||
- /system/*
|
||||
- /monitor/*
|
||||
- /tool/*
|
||||
|
||||
xxl:
|
||||
job:
|
||||
enable: false
|
||||
accessToken: default_token
|
||||
adminAddresses: http://127.0.0.1:18080/xxl-job-admin
|
||||
executor:
|
||||
### 执行器AppName [选填]:执行器心跳注册分组依据;为空则关闭自动注册
|
||||
appname: chestnut-admin
|
||||
### 执行器注册 [选填]:优先使用该配置作为注册地址,为空时使用内嵌服务 ”IP:PORT“ 作为注册地址。从而更灵活的支持容器类型执行器动态IP和动态映射端口问题。
|
||||
#address:
|
||||
### 执行器IP [选填]:默认为空表示自动获取IP,多网卡时可手动设置指定IP,该IP不会绑定Host仅作为通讯实用;地址信息用于 "执行器注册" 和 "调度中心请求并触发任务";
|
||||
ip:
|
||||
### 执行器端口号 [选填]:小于等于0则自动获取;默认端口为9999,单机部署多个执行器时,注意要配置不同执行器端口;
|
||||
port: 9968
|
||||
### 执行器运行日志文件存储磁盘路径 [选填] :需要对该路径拥有读写权限;为空则使用默认路径;
|
||||
logpath: E:/dev/workspace_chestnut/ChestnutCMS/chestnut-modules/chestnut-xxljob/jobhandler
|
||||
### 执行器日志文件保存天数 [选填] : 过期日志自动清理, 限制值大于等于3时生效; 否则, 如-1, 关闭自动清理功能;默认:30
|
||||
logretentiondays: 30
|
||||
|
||||
jasypt:
|
||||
encryptor:
|
||||
password: qsakjdnfij234234sdf67
|
|
@ -0,0 +1,220 @@
|
|||
# 项目相关配置
|
||||
chestnut:
|
||||
# 名称
|
||||
name: ChestnutCMS
|
||||
# 版本
|
||||
version: 1.3.21
|
||||
# 版权年份
|
||||
copyrightYear: 2023
|
||||
system:
|
||||
# 演示模式开关
|
||||
demoMode: true
|
||||
# 文件路径 示例( Windows配置D:/chestnut/uploadPath,Linux配置 /home/app/uploadPath)
|
||||
uploadPath: /home/app/uploadPath
|
||||
# 验证码类型 math 数组计算 char 字符验证
|
||||
captchaType: math
|
||||
freemarker:
|
||||
templateLoaderPath: /home/app/statics
|
||||
cms:
|
||||
resourceRoot: /home/app/wwwroot_release
|
||||
|
||||
# 开发环境配置
|
||||
server:
|
||||
# 服务器的HTTP端口,默认为8090
|
||||
port: 8090
|
||||
servlet:
|
||||
# 应用的访问路径
|
||||
context-path: /
|
||||
tomcat:
|
||||
# tomcat的URI编码
|
||||
uri-encoding: UTF-8
|
||||
# 连接数满后的排队数,默认为100
|
||||
accept-count: 1000
|
||||
threads:
|
||||
# tomcat最大线程数,默认为200
|
||||
max: 800
|
||||
# Tomcat启动初始化的线程数,默认值10
|
||||
min-spare: 100
|
||||
|
||||
# 日志配置
|
||||
logging:
|
||||
level:
|
||||
com.chestnut: debug
|
||||
org.springframework: warn
|
||||
|
||||
# Spring配置
|
||||
spring:
|
||||
# 资源信息
|
||||
messages:
|
||||
# 国际化资源文件路径
|
||||
basename: i18n/messages
|
||||
# 文件上传
|
||||
servlet:
|
||||
multipart:
|
||||
# 单个文件大小
|
||||
max-file-size: 20MB
|
||||
# 设置总上传的文件大小
|
||||
max-request-size: 100MB
|
||||
# 服务模块
|
||||
devtools:
|
||||
restart:
|
||||
# 热部署开关
|
||||
enabled: false
|
||||
freemarker:
|
||||
check-template-location: false
|
||||
elasticsearch:
|
||||
uris: http://cc-elasticsearch:9200
|
||||
username: elastic
|
||||
password: hello1234
|
||||
# redis 配置
|
||||
data:
|
||||
redis:
|
||||
# 地址
|
||||
host: 140.143.157.1
|
||||
# 端口,默认为6379
|
||||
port: 6379
|
||||
# 数据库索引
|
||||
database: 15
|
||||
# 密码
|
||||
password: x7Zhj3twzSdDwx2A
|
||||
# 连接超时时间
|
||||
timeout: 10s
|
||||
lettuce:
|
||||
pool:
|
||||
# 连接池中的最小空闲连接
|
||||
min-idle: 0
|
||||
# 连接池中的最大空闲连接
|
||||
max-idle: 8
|
||||
# 连接池的最大数据库连接数
|
||||
max-active: 8
|
||||
# #连接池最大阻塞等待时间(使用负值表示没有限制)
|
||||
max-wait: -1ms
|
||||
flyway:
|
||||
enabled: false
|
||||
# 迁移sql脚本文件存放路径,默认:classpath:db/migration
|
||||
locations: classpath:db/migration/mysql
|
||||
# 迁移sql脚本文件名称的前缀,默认:V
|
||||
sql-migration-prefix: V
|
||||
# 迁移sql脚本文件名称分隔符,默认2个下划线:__
|
||||
sql-migration-separator: __
|
||||
# 迁移sql脚本文件名称后缀
|
||||
sql-migration-suffixes: .sql
|
||||
# 迁移时是否进行校验
|
||||
validate-on-migrate: true
|
||||
# 当迁移发现数据库非空且存在没有元数据的表时,自动执行基准迁移,新建schema_version表
|
||||
baseline-on-migrate: true
|
||||
# 数据库配置
|
||||
datasource:
|
||||
# type: com.alibaba.druid.pool.DruidDataSource
|
||||
type: com.zaxxer.hikari.HikariDataSource
|
||||
dynamic:
|
||||
primary: master
|
||||
# 严格模式 匹配不到数据源则报错
|
||||
strict: true
|
||||
# 主库
|
||||
datasource:
|
||||
master:
|
||||
type: ${spring.datasource.type}
|
||||
driverClassName: com.mysql.cj.jdbc.Driver
|
||||
url: jdbc:mysql://cd-cynosdbmysql-grp-9rqrhxsm.sql.tencentcdb.com:27981/cms?useSSL=false&characterEncoding=UTF-8&serverTimezone=GMT%2B8
|
||||
username: root
|
||||
password: Sxyanzhu@cf
|
||||
# 从库
|
||||
#slave:
|
||||
# lazy: true
|
||||
# type: ${spring.datasource.type}
|
||||
# driverClassName: com.mysql.cj.jdbc.Driver
|
||||
# url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
|
||||
# username:
|
||||
# password:
|
||||
hikari:
|
||||
# 连接池名
|
||||
pool-name: HikariCP
|
||||
# 连接超时时间:毫秒, 默认30秒
|
||||
connection-timeout: 2000
|
||||
# 最小空闲连接,默认值10,小于0或大于maximum-pool-size,都会重置为maximum-pool-size
|
||||
minimum-idle: 5
|
||||
# 最大连接数,小于等于0会被重置为默认值10;大于零小于1会被重置为minimum-idle的值
|
||||
maximum-pool-size: 20
|
||||
# 空闲连接最大存活时间,默认值600000(10分钟),大于等于max-lifetime且max-lifetime>0,会被重置为0;不等于0且小于10秒,会被重置为10秒。
|
||||
idle-timeout: 200000
|
||||
# 连接池返回的连接默认自动提交,默认只 true
|
||||
auto-commit: true
|
||||
# 连接最大存活时间,不等于0且小于30秒,会被重置为默认值30分钟.设置应该比mysql设置的超时时间短
|
||||
max-lifetime: 1800000
|
||||
# 用于测试连接是否可用的查询语句
|
||||
connection-test-query: SELECT 1
|
||||
# 监控配置
|
||||
application:
|
||||
name: "ChestnutCMS"
|
||||
boot:
|
||||
admin:
|
||||
client:
|
||||
# 增加客户端开关
|
||||
enabled: false
|
||||
# Admin Server URL
|
||||
url: http://127.0.0.1:8090/admin
|
||||
instance:
|
||||
service-host-type: IP
|
||||
username: chestnut
|
||||
password: 123456
|
||||
|
||||
# SaToken配置
|
||||
sa-token:
|
||||
# token名称 (同时也是cookie名称)
|
||||
token-name: Authorization
|
||||
# token前缀
|
||||
token-prefix: Bearer
|
||||
# token有效期,单位s 默认30天, -1代表永不过期
|
||||
timeout: 2592000
|
||||
# token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
|
||||
active-timeout: -1
|
||||
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
|
||||
is-concurrent: true
|
||||
# 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
|
||||
is-share: true
|
||||
# token风格
|
||||
token-style: uuid
|
||||
# 是否输出操作日志
|
||||
is-log: true
|
||||
|
||||
# MyBatis配置
|
||||
mybatis-plus:
|
||||
global-config:
|
||||
enable-sql-runner: true
|
||||
# 搜索指定包别名
|
||||
typeAliasesPackage: com.chestnut.**.domain
|
||||
# 配置mapper的扫描,找到所有的mapper.xml映射文件
|
||||
mapperLocations: classpath*:mapper/**/*Mapper.xml
|
||||
configuration:
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
|
||||
# 防止XSS攻击
|
||||
xss:
|
||||
# 过滤开关
|
||||
enabled: true
|
||||
mode: clean
|
||||
# 过滤链接
|
||||
urlPatterns:
|
||||
- /system/*
|
||||
- /monitor/*
|
||||
- /tool/*
|
||||
|
||||
xxl:
|
||||
job:
|
||||
enable: false
|
||||
accessToken: default_token
|
||||
adminAddresses: http://127.0.0.1:18080/xxl-job-admin
|
||||
executor:
|
||||
### 执行器AppName [选填]:执行器心跳注册分组依据;为空则关闭自动注册
|
||||
appname: chestnut-admin
|
||||
### 执行器注册 [选填]:优先使用该配置作为注册地址,为空时使用内嵌服务 ”IP:PORT“ 作为注册地址。从而更灵活的支持容器类型执行器动态IP和动态映射端口问题。
|
||||
#address:
|
||||
### 执行器IP [选填]:默认为空表示自动获取IP,多网卡时可手动设置指定IP,该IP不会绑定Host仅作为通讯实用;地址信息用于 "执行器注册" 和 "调度中心请求并触发任务";
|
||||
ip:
|
||||
### 执行器端口号 [选填]:小于等于0则自动获取;默认端口为9999,单机部署多个执行器时,注意要配置不同执行器端口;
|
||||
port: 9968
|
||||
### 执行器运行日志文件存储磁盘路径 [选填] :需要对该路径拥有读写权限;为空则使用默认路径;
|
||||
logpath: E:/dev/workspace_chestnut/ChestnutCMS/chestnut-modules/chestnut-xxljob/jobhandler
|
||||
### 执行器日志文件保存天数 [选填] : 过期日志自动清理, 限制值大于等于3时生效; 否则, 如-1, 关闭自动清理功能;默认:30
|
||||
logretentiondays: 30
|
|
@ -0,0 +1,5 @@
|
|||
# Spring配置
|
||||
spring:
|
||||
profiles:
|
||||
active: dev
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
Application Version: ${chestnut.version}
|
||||
Spring Boot Version: ${spring-boot.version}
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,43 @@
|
|||
-- ----------------------------
|
||||
-- Table structure for sys_scheduled_task
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `sys_scheduled_task`;
|
||||
CREATE TABLE `sys_scheduled_task` (
|
||||
`task_id` bigint NOT NULL,
|
||||
`task_type` varchar(50) NOT NULL,
|
||||
`status` varchar(1) NOT NULL,
|
||||
`task_trigger` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
|
||||
`trigger_args` varchar(255) NOT NULL,
|
||||
`create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '创建者',
|
||||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||
`update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '更新者',
|
||||
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
|
||||
`remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '备注',
|
||||
PRIMARY KEY (`task_id`) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for sys_scheduled_task_log
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `sys_scheduled_task_log`;
|
||||
CREATE TABLE `sys_scheduled_task_log` (
|
||||
`log_id` bigint NOT NULL,
|
||||
`task_id` bigint NOT NULL,
|
||||
`task_type` varchar(50) NOT NULL,
|
||||
`ready_time` datetime NOT NULL,
|
||||
`start_time` datetime NOT NULL,
|
||||
`end_time` datetime NOT NULL,
|
||||
`interrupt_time` datetime DEFAULT NULL,
|
||||
`percent` int DEFAULT NULL,
|
||||
`result` varchar(1) NOT NULL,
|
||||
`message` varchar(2000) DEFAULT NULL,
|
||||
`log_time` datetime NOT NULL,
|
||||
PRIMARY KEY (`log_id`) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
|
||||
|
||||
|
||||
INSERT INTO `sys_menu` VALUES (2081, '定时任务', 2, 2, 'task', 'monitor/task/index', NULL, 'N', 'Y', 'C', 'Y', '0', NULL, 'job', 'admin', '2023-05-06 19:11:08', '', NULL, '');
|
||||
|
||||
INSERT INTO `sys_i18n_dict` VALUES (239, 'zh-CN', 'MENU.NAME.2081', '定时任务');
|
||||
INSERT INTO `sys_i18n_dict` VALUES (240, 'en', 'MENU.NAME.2081', 'Scheduled Task');
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
ALTER TABLE cms_video MODIFY file_size BIGINT NULL;
|
||||
ALTER TABLE cms_video MODIFY format VARCHAR(20) NULL;
|
||||
ALTER TABLE cms_video MODIFY duration BIGINT NULL;
|
||||
ALTER TABLE cms_video MODIFY width INT NULL;
|
||||
ALTER TABLE cms_video MODIFY height INT NULL;
|
||||
ALTER TABLE cms_video MODIFY bit_rate INT NULL;
|
||||
ALTER TABLE cms_video MODIFY frame_rate INT NULL;
|
||||
ALTER TABLE cms_video MODIFY type VARCHAR(10) NULL;
|
||||
|
||||
ALTER TABLE cms_video_backup MODIFY file_size BIGINT NULL;
|
||||
ALTER TABLE cms_video_backup MODIFY format VARCHAR(20) NULL;
|
||||
ALTER TABLE cms_video_backup MODIFY duration BIGINT NULL;
|
||||
ALTER TABLE cms_video_backup MODIFY width INT NULL;
|
||||
ALTER TABLE cms_video_backup MODIFY height INT NULL;
|
||||
ALTER TABLE cms_video_backup MODIFY bit_rate INT NULL;
|
||||
ALTER TABLE cms_video_backup MODIFY frame_rate INT NULL;
|
||||
ALTER TABLE cms_video_backup MODIFY type VARCHAR(10) NULL;
|
||||
|
||||
ALTER TABLE cms_audio MODIFY file_size BIGINT NULL;
|
||||
ALTER TABLE cms_audio MODIFY duration BIGINT NULL;
|
||||
ALTER TABLE cms_audio MODIFY channels INT NULL;
|
||||
ALTER TABLE cms_audio MODIFY bit_rate INT NULL;
|
||||
ALTER TABLE cms_audio MODIFY sampling_rate INT NULL;
|
||||
ALTER TABLE cms_audio MODIFY type VARCHAR(10) NULL;
|
||||
ALTER TABLE cms_audio MODIFY format VARCHAR(20) NULL;
|
||||
|
||||
ALTER TABLE cms_audio_backup MODIFY file_size BIGINT NULL;
|
||||
ALTER TABLE cms_audio_backup MODIFY duration BIGINT NULL;
|
||||
ALTER TABLE cms_audio_backup MODIFY channels INT NULL;
|
||||
ALTER TABLE cms_audio_backup MODIFY bit_rate INT NULL;
|
||||
ALTER TABLE cms_audio_backup MODIFY sampling_rate INT NULL;
|
||||
ALTER TABLE cms_audio_backup MODIFY type VARCHAR(10) NULL;
|
||||
ALTER TABLE cms_audio_backup MODIFY format VARCHAR(20) NULL;
|
|
@ -0,0 +1,228 @@
|
|||
CREATE TABLE `cms_custom_form` (
|
||||
`form_id` bigint NOT NULL COMMENT 'ID',
|
||||
`site_id` bigint NOT NULL COMMENT '站点ID ',
|
||||
`model_id` bigint NOT NULL COMMENT '关联元数据模型ID',
|
||||
`name` varchar(100) NOT NULL COMMENT '表单名称',
|
||||
`code` varchar(50) NOT NULL COMMENT '同站点唯一编码',
|
||||
`status` int NOT NULL COMMENT '状态',
|
||||
`templates` varchar(100) DEFAULT NULL COMMENT '模板',
|
||||
`need_captcha` varchar(1) NOT NULL COMMENT '是否启用验证码',
|
||||
`need_login` varchar(1) NOT NULL COMMENT '是否需要会员登录',
|
||||
`rule_limit` varchar(1) NOT NULL COMMENT '唯一性规则限制(IP/浏览器指纹)',
|
||||
`create_by` varchar(64) NOT NULL COMMENT '创建者',
|
||||
`create_time` datetime NOT NULL COMMENT '创建时间',
|
||||
`update_by` varchar(64) DEFAULT '' COMMENT '更新者',
|
||||
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
|
||||
`remark` varchar(500) DEFAULT '' COMMENT '备注',
|
||||
PRIMARY KEY (`form_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
CREATE TABLE `cms_cfd_default` (
|
||||
`data_id` bigint NOT NULL COMMENT '数据主键ID',
|
||||
`model_id` bigint NOT NULL COMMENT '自定义表单ID(元数据模型ID)',
|
||||
`site_id` bigint NOT NULL COMMENT '所属站点ID',
|
||||
`client_ip` varchar(64) NOT NULL COMMENT 'IP',
|
||||
`uuid` varchar(128) NOT NULL COMMENT '用户唯一标识(浏览器指纹、会员ID)',
|
||||
`create_time` datetime NOT NULL COMMENT '创建时间',
|
||||
`short_text1` varchar(50) DEFAULT NULL,
|
||||
`short_text2` varchar(50) DEFAULT NULL,
|
||||
`short_text3` varchar(50) DEFAULT NULL,
|
||||
`short_text4` varchar(50) DEFAULT NULL,
|
||||
`short_text5` varchar(50) DEFAULT NULL,
|
||||
`short_text6` varchar(50) DEFAULT NULL,
|
||||
`short_text7` varchar(50) DEFAULT NULL,
|
||||
`short_text8` varchar(50) DEFAULT NULL,
|
||||
`short_text9` varchar(50) DEFAULT NULL,
|
||||
`short_text10` varchar(50) DEFAULT NULL,
|
||||
`short_text11` varchar(50) DEFAULT NULL,
|
||||
`short_text12` varchar(50) DEFAULT NULL,
|
||||
`short_text13` varchar(50) DEFAULT NULL,
|
||||
`short_text14` varchar(50) DEFAULT NULL,
|
||||
`short_text15` varchar(50) DEFAULT NULL,
|
||||
`short_text16` varchar(50) DEFAULT NULL,
|
||||
`short_text17` varchar(50) DEFAULT NULL,
|
||||
`short_text18` varchar(50) DEFAULT NULL,
|
||||
`short_text19` varchar(50) DEFAULT NULL,
|
||||
`short_text20` varchar(50) DEFAULT NULL,
|
||||
`short_text21` varchar(50) DEFAULT NULL,
|
||||
`short_text22` varchar(50) DEFAULT NULL,
|
||||
`short_text23` varchar(50) DEFAULT NULL,
|
||||
`short_text24` varchar(50) DEFAULT NULL,
|
||||
`short_text25` varchar(50) DEFAULT NULL,
|
||||
`medium_text1` varchar(200) DEFAULT NULL,
|
||||
`medium_text2` varchar(200) DEFAULT NULL,
|
||||
`medium_text3` varchar(200) DEFAULT NULL,
|
||||
`medium_text4` varchar(200) DEFAULT NULL,
|
||||
`medium_text5` varchar(200) DEFAULT NULL,
|
||||
`medium_text6` varchar(200) DEFAULT NULL,
|
||||
`medium_text7` varchar(200) DEFAULT NULL,
|
||||
`medium_text8` varchar(200) DEFAULT NULL,
|
||||
`medium_text9` varchar(200) DEFAULT NULL,
|
||||
`medium_text10` varchar(200) DEFAULT NULL,
|
||||
`medium_text11` varchar(200) DEFAULT NULL,
|
||||
`medium_text12` varchar(200) DEFAULT NULL,
|
||||
`medium_text13` varchar(200) DEFAULT NULL,
|
||||
`medium_text14` varchar(200) DEFAULT NULL,
|
||||
`medium_text15` varchar(200) DEFAULT NULL,
|
||||
`medium_text16` varchar(200) DEFAULT NULL,
|
||||
`medium_text17` varchar(200) DEFAULT NULL,
|
||||
`medium_text18` varchar(200) DEFAULT NULL,
|
||||
`medium_text19` varchar(200) DEFAULT NULL,
|
||||
`medium_text20` varchar(200) DEFAULT NULL,
|
||||
`medium_text21` varchar(200) DEFAULT NULL,
|
||||
`medium_text22` varchar(200) DEFAULT NULL,
|
||||
`medium_text23` varchar(200) DEFAULT NULL,
|
||||
`medium_text24` varchar(200) DEFAULT NULL,
|
||||
`medium_text25` varchar(200) DEFAULT NULL,
|
||||
`large_text1` varchar(2000) DEFAULT NULL,
|
||||
`large_text2` varchar(2000) DEFAULT NULL,
|
||||
`large_text3` varchar(2000) DEFAULT NULL,
|
||||
`large_text4` varchar(2000) DEFAULT NULL,
|
||||
`clob_text1` mediumtext,
|
||||
`long1` bigint DEFAULT NULL,
|
||||
`long2` bigint DEFAULT NULL,
|
||||
`long3` bigint DEFAULT NULL,
|
||||
`long4` bigint DEFAULT NULL,
|
||||
`long5` bigint DEFAULT NULL,
|
||||
`long6` bigint DEFAULT NULL,
|
||||
`long7` bigint DEFAULT NULL,
|
||||
`long8` bigint DEFAULT NULL,
|
||||
`long9` bigint DEFAULT NULL,
|
||||
`long10` bigint DEFAULT NULL,
|
||||
`double1` double(255,0) DEFAULT NULL,
|
||||
`double2` double(255,0) DEFAULT NULL,
|
||||
`double3` double(255,0) DEFAULT NULL,
|
||||
`double4` double(255,0) DEFAULT NULL,
|
||||
`double5` double(255,0) DEFAULT NULL,
|
||||
`double6` double(255,0) DEFAULT NULL,
|
||||
`double7` double(255,0) DEFAULT NULL,
|
||||
`double8` double(255,0) DEFAULT NULL,
|
||||
`double9` double(255,0) DEFAULT NULL,
|
||||
`double10` double(255,0) DEFAULT NULL,
|
||||
`date1` datetime DEFAULT NULL,
|
||||
`date2` datetime DEFAULT NULL,
|
||||
`date3` datetime DEFAULT NULL,
|
||||
`date4` datetime DEFAULT NULL,
|
||||
`date5` datetime DEFAULT NULL,
|
||||
`date6` datetime DEFAULT NULL,
|
||||
`date7` datetime DEFAULT NULL,
|
||||
`date8` datetime DEFAULT NULL,
|
||||
`date9` datetime DEFAULT NULL,
|
||||
`date10` datetime DEFAULT NULL,
|
||||
PRIMARY KEY (`data_id`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC;
|
||||
|
||||
CREATE TABLE `cms_exd_default` (
|
||||
`data_id` bigint NOT NULL COMMENT '关联数据ID',
|
||||
`data_type` varchar(20) NOT NULL COMMENT '关联数据类型',
|
||||
`model_id` bigint NOT NULL COMMENT '元数据模型ID',
|
||||
`short_text1` varchar(50) DEFAULT NULL,
|
||||
`short_text2` varchar(50) DEFAULT NULL,
|
||||
`short_text3` varchar(50) DEFAULT NULL,
|
||||
`short_text4` varchar(50) DEFAULT NULL,
|
||||
`short_text5` varchar(50) DEFAULT NULL,
|
||||
`short_text6` varchar(50) DEFAULT NULL,
|
||||
`short_text7` varchar(50) DEFAULT NULL,
|
||||
`short_text8` varchar(50) DEFAULT NULL,
|
||||
`short_text9` varchar(50) DEFAULT NULL,
|
||||
`short_text10` varchar(50) DEFAULT NULL,
|
||||
`short_text11` varchar(50) DEFAULT NULL,
|
||||
`short_text12` varchar(50) DEFAULT NULL,
|
||||
`short_text13` varchar(50) DEFAULT NULL,
|
||||
`short_text14` varchar(50) DEFAULT NULL,
|
||||
`short_text15` varchar(50) DEFAULT NULL,
|
||||
`short_text16` varchar(50) DEFAULT NULL,
|
||||
`short_text17` varchar(50) DEFAULT NULL,
|
||||
`short_text18` varchar(50) DEFAULT NULL,
|
||||
`short_text19` varchar(50) DEFAULT NULL,
|
||||
`short_text20` varchar(50) DEFAULT NULL,
|
||||
`short_text21` varchar(50) DEFAULT NULL,
|
||||
`short_text22` varchar(50) DEFAULT NULL,
|
||||
`short_text23` varchar(50) DEFAULT NULL,
|
||||
`short_text24` varchar(50) DEFAULT NULL,
|
||||
`short_text25` varchar(50) DEFAULT NULL,
|
||||
`medium_text1` varchar(200) DEFAULT NULL,
|
||||
`medium_text2` varchar(200) DEFAULT NULL,
|
||||
`medium_text3` varchar(200) DEFAULT NULL,
|
||||
`medium_text4` varchar(200) DEFAULT NULL,
|
||||
`medium_text5` varchar(200) DEFAULT NULL,
|
||||
`medium_text6` varchar(200) DEFAULT NULL,
|
||||
`medium_text7` varchar(200) DEFAULT NULL,
|
||||
`medium_text8` varchar(200) DEFAULT NULL,
|
||||
`medium_text9` varchar(200) DEFAULT NULL,
|
||||
`medium_text10` varchar(200) DEFAULT NULL,
|
||||
`medium_text11` varchar(200) DEFAULT NULL,
|
||||
`medium_text12` varchar(200) DEFAULT NULL,
|
||||
`medium_text13` varchar(200) DEFAULT NULL,
|
||||
`medium_text14` varchar(200) DEFAULT NULL,
|
||||
`medium_text15` varchar(200) DEFAULT NULL,
|
||||
`medium_text16` varchar(200) DEFAULT NULL,
|
||||
`medium_text17` varchar(200) DEFAULT NULL,
|
||||
`medium_text18` varchar(200) DEFAULT NULL,
|
||||
`medium_text19` varchar(200) DEFAULT NULL,
|
||||
`medium_text20` varchar(200) DEFAULT NULL,
|
||||
`medium_text21` varchar(200) DEFAULT NULL,
|
||||
`medium_text22` varchar(200) DEFAULT NULL,
|
||||
`medium_text23` varchar(200) DEFAULT NULL,
|
||||
`medium_text24` varchar(200) DEFAULT NULL,
|
||||
`medium_text25` varchar(200) DEFAULT NULL,
|
||||
`large_text1` varchar(2000) DEFAULT NULL,
|
||||
`large_text2` varchar(2000) DEFAULT NULL,
|
||||
`large_text3` varchar(2000) DEFAULT NULL,
|
||||
`large_text4` varchar(2000) DEFAULT NULL,
|
||||
`clob_text1` mediumtext,
|
||||
`long1` bigint DEFAULT NULL,
|
||||
`long2` bigint DEFAULT NULL,
|
||||
`long3` bigint DEFAULT NULL,
|
||||
`long4` bigint DEFAULT NULL,
|
||||
`long5` bigint DEFAULT NULL,
|
||||
`long6` bigint DEFAULT NULL,
|
||||
`long7` bigint DEFAULT NULL,
|
||||
`long8` bigint DEFAULT NULL,
|
||||
`long9` bigint DEFAULT NULL,
|
||||
`long10` bigint DEFAULT NULL,
|
||||
`double1` double(255,0) DEFAULT NULL,
|
||||
`double2` double(255,0) DEFAULT NULL,
|
||||
`double3` double(255,0) DEFAULT NULL,
|
||||
`double4` double(255,0) DEFAULT NULL,
|
||||
`double5` double(255,0) DEFAULT NULL,
|
||||
`double6` double(255,0) DEFAULT NULL,
|
||||
`double7` double(255,0) DEFAULT NULL,
|
||||
`double8` double(255,0) DEFAULT NULL,
|
||||
`double9` double(255,0) DEFAULT NULL,
|
||||
`double10` double(255,0) DEFAULT NULL,
|
||||
`date1` datetime DEFAULT NULL,
|
||||
`date2` datetime DEFAULT NULL,
|
||||
`date3` datetime DEFAULT NULL,
|
||||
`date4` datetime DEFAULT NULL,
|
||||
`date5` datetime DEFAULT NULL,
|
||||
`date6` datetime DEFAULT NULL,
|
||||
`date7` datetime DEFAULT NULL,
|
||||
`date8` datetime DEFAULT NULL,
|
||||
`date9` datetime DEFAULT NULL,
|
||||
`date10` datetime DEFAULT NULL,
|
||||
PRIMARY KEY (`data_id`,`data_type`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
INSERT INTO `sys_menu` VALUES (430649774219333, '自定义表单', 2035, 5, 'customform', 'cms/customform/index', NULL, 'N', 'Y', 'C', 'Y', '0', 'cms:custom:view', 'form', 'admin', '2023-06-20 10:12:08', 'admin', '2023-06-20 10:13:09', '');
|
||||
|
||||
INSERT INTO `sys_i18n_dict` VALUES (null, 'zh-CN', 'MENU.NAME.430649774219333', '自定义表单');
|
||||
INSERT INTO `sys_i18n_dict` VALUES (null, 'en', 'MENU.NAME.430649774219333', 'Custom Form');
|
||||
|
||||
ALTER TABLE cms_content add column deleted tinyint DEFAULT 0;
|
||||
ALTER TABLE cms_article_detail add column deleted tinyint DEFAULT 0;
|
||||
ALTER TABLE cms_image add column deleted tinyint DEFAULT 0;
|
||||
ALTER TABLE cms_audio add column deleted tinyint DEFAULT 0;
|
||||
ALTER TABLE cms_video add column deleted tinyint DEFAULT 0;
|
||||
|
||||
ALTER TABLE sys_config modify column config_id bigint;
|
||||
ALTER TABLE sys_notice modify column notice_id bigint;
|
||||
|
||||
DROP TABLE x_model_data;
|
||||
DROP TABLE cms_content_backup;
|
||||
DROP TABLE cms_article_detail_backup;
|
||||
DROP TABLE cms_image_backup;
|
||||
DROP TABLE cms_audio_backup;
|
||||
DROP TABLE cms_video_backup;
|
||||
|
||||
alter table cc_comment drop column del_flag;
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,21 @@
|
|||
ALTER TABLE cms_cfd_default CHANGE COLUMN date1 datetime1 datetime;
|
||||
ALTER TABLE cms_cfd_default CHANGE COLUMN date2 datetime2 datetime;
|
||||
ALTER TABLE cms_cfd_default CHANGE COLUMN date3 datetime3 datetime;
|
||||
ALTER TABLE cms_cfd_default CHANGE COLUMN date4 datetime4 datetime;
|
||||
ALTER TABLE cms_cfd_default CHANGE COLUMN date5 datetime5 datetime;
|
||||
ALTER TABLE cms_cfd_default CHANGE COLUMN date6 datetime6 datetime;
|
||||
ALTER TABLE cms_cfd_default CHANGE COLUMN date7 datetime7 datetime;
|
||||
ALTER TABLE cms_cfd_default CHANGE COLUMN date8 datetime8 datetime;
|
||||
ALTER TABLE cms_cfd_default CHANGE COLUMN date9 datetime9 datetime;
|
||||
ALTER TABLE cms_cfd_default CHANGE COLUMN date10 datetime10 datetime;
|
||||
|
||||
ALTER TABLE cms_exd_default CHANGE COLUMN date1 datetime1 datetime;
|
||||
ALTER TABLE cms_exd_default CHANGE COLUMN date2 datetime2 datetime;
|
||||
ALTER TABLE cms_exd_default CHANGE COLUMN date3 datetime3 datetime;
|
||||
ALTER TABLE cms_exd_default CHANGE COLUMN date4 datetime4 datetime;
|
||||
ALTER TABLE cms_exd_default CHANGE COLUMN date5 datetime5 datetime;
|
||||
ALTER TABLE cms_exd_default CHANGE COLUMN date6 datetime6 datetime;
|
||||
ALTER TABLE cms_exd_default CHANGE COLUMN date7 datetime7 datetime;
|
||||
ALTER TABLE cms_exd_default CHANGE COLUMN date8 datetime8 datetime;
|
||||
ALTER TABLE cms_exd_default CHANGE COLUMN date9 datetime9 datetime;
|
||||
ALTER TABLE cms_exd_default CHANGE COLUMN date10 datetime10 datetime;
|
|
@ -0,0 +1,22 @@
|
|||
ALTER TABLE cms_content ADD COLUMN like_count bigint default 0;
|
||||
ALTER TABLE cms_content ADD COLUMN comment_count bigint default 0;
|
||||
ALTER TABLE cms_content ADD COLUMN favorite_count bigint default 0;
|
||||
ALTER TABLE cms_content ADD COLUMN view_count bigint default 0;
|
||||
|
||||
CREATE TABLE `cms_member_favorites` (
|
||||
`log_id` bigint NOT NULL,
|
||||
`site_id` bigint NOT NULL COMMENT '站点ID',
|
||||
`content_id` bigint NOT NULL COMMENT '内容ID',
|
||||
`member_id` bigint NOT NULL COMMENT '会员ID',
|
||||
`create_time` datetime NOT NULL COMMENT '创建时间',
|
||||
PRIMARY KEY (`log_id`) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
|
||||
|
||||
CREATE TABLE `cms_member_like` (
|
||||
`log_id` bigint NOT NULL,
|
||||
`site_id` bigint NOT NULL COMMENT '站点ID',
|
||||
`content_id` bigint NOT NULL COMMENT '内容ID',
|
||||
`member_id` bigint NOT NULL COMMENT '会员ID',
|
||||
`create_time` datetime NOT NULL COMMENT '创建时间',
|
||||
PRIMARY KEY (`log_id`) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
|
|
@ -0,0 +1,58 @@
|
|||
ALTER TABLE cms_content ADD COLUMN contributor_id bigint default 0;
|
||||
|
||||
ALTER TABLE cc_member ADD COLUMN slogan varchar(255);
|
||||
ALTER TABLE cc_member ADD COLUMN description varchar(500);
|
||||
ALTER TABLE cc_member ADD COLUMN cover varchar(100);
|
||||
|
||||
DROP TABLE cms_member_favorites;
|
||||
DROP TABLE cms_member_like;
|
||||
|
||||
CREATE TABLE `cc_member_favorites` (
|
||||
`log_id` bigint NOT NULL,
|
||||
`member_id` bigint NOT NULL COMMENT '会员ID',
|
||||
`data_type` varchar(100) NOT NULL COMMENT '收藏数据类型',
|
||||
`data_id` bigint NOT NULL COMMENT '收藏数据ID',
|
||||
`create_time` datetime NOT NULL COMMENT '创建时间',
|
||||
PRIMARY KEY (`log_id`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT = Dynamic;
|
||||
|
||||
CREATE TABLE `cc_member_like` (
|
||||
`log_id` bigint NOT NULL,
|
||||
`member_id` bigint NOT NULL COMMENT '会员ID',
|
||||
`data_type` varchar(100) NOT NULL COMMENT '点赞数据类型',
|
||||
`data_id` bigint NOT NULL COMMENT '点赞数据ID',
|
||||
`create_time` datetime NOT NULL COMMENT '创建时间',
|
||||
PRIMARY KEY (`log_id`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT = Dynamic;
|
||||
|
||||
CREATE TABLE `cc_member_follow` (
|
||||
`log_id` bigint NOT NULL,
|
||||
`member_id` bigint NOT NULL COMMENT '会员ID',
|
||||
`follow_member_id` bigint NOT NULL COMMENT '关注的会员ID',
|
||||
PRIMARY KEY (`log_id`)
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
|
||||
|
||||
CREATE TABLE `cc_member_stat_data` (
|
||||
`member_id` bigint NOT NULL COMMENT '会员ID',
|
||||
`int_value1` int NOT NULL DEFAULT '0',
|
||||
`int_value2` int NOT NULL DEFAULT '0',
|
||||
`int_value3` int NOT NULL DEFAULT '0',
|
||||
`int_value4` int NOT NULL DEFAULT '0',
|
||||
`int_value5` int NOT NULL DEFAULT '0',
|
||||
`int_value6` int NOT NULL DEFAULT '0',
|
||||
`int_value7` int NOT NULL DEFAULT '0',
|
||||
`int_value8` int NOT NULL DEFAULT '0',
|
||||
`int_value9` int NOT NULL DEFAULT '0',
|
||||
`int_value10` int NOT NULL DEFAULT '0',
|
||||
`int_value11` int NOT NULL DEFAULT '0',
|
||||
`int_value12` int NOT NULL DEFAULT '0',
|
||||
`int_value13` int NOT NULL DEFAULT '0',
|
||||
`int_value14` int NOT NULL DEFAULT '0',
|
||||
`int_value15` int NOT NULL DEFAULT '0',
|
||||
`int_value16` int NOT NULL DEFAULT '0',
|
||||
`int_value17` int NOT NULL DEFAULT '0',
|
||||
`int_value18` int NOT NULL DEFAULT '0',
|
||||
`int_value19` int NOT NULL DEFAULT '0',
|
||||
`int_value20` int NOT NULL DEFAULT '0',
|
||||
PRIMARY KEY (`member_id`)
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE cms_video ADD COLUMN cover varchar(255);
|
|
@ -0,0 +1,33 @@
|
|||
ALTER TABLE cms_exd_default DROP PRIMARY KEY, ADD PRIMARY KEY(data_id, data_type, model_id);
|
||||
|
||||
ALTER TABLE cc_comment CHANGE COLUMN deleted del_flag int;
|
||||
|
||||
ALTER TABLE cc_member CHANGE COLUMN phonenumber phone_number varchar(20);
|
||||
|
||||
ALTER TABLE sys_user CHANGE COLUMN phonenumber phone_number varchar(20);
|
||||
|
||||
ALTER TABLE x_model_field ADD COLUMN sort_flag bigint DEFAULT '0' COMMENT '排序字段';
|
||||
|
||||
CREATE TABLE `cms_catalog_content_stat` (
|
||||
`catalog_id` bigint NOT NULL COMMENT '栏目ID',
|
||||
`site_id` bigint NOT NULL COMMENT '站点ID',
|
||||
`draft_total` int NOT NULL DEFAULT '0' COMMENT '初稿内容数',
|
||||
`to_publish_total` int NOT NULL DEFAULT '0' COMMENT '待发布内容数',
|
||||
`published_total` int NOT NULL DEFAULT '0' COMMENT '已发布内容数',
|
||||
`offline_total` int NOT NULL DEFAULT '0' COMMENT '已下线内容数',
|
||||
`editing_total` int NOT NULL DEFAULT '0' COMMENT '重新编辑内容数',
|
||||
PRIMARY KEY (`catalog_id`)
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
|
||||
|
||||
CREATE TABLE `cms_user_content_stat` (
|
||||
`id` varchar(100) NOT NULL COMMENT 'ID',
|
||||
`site_id` bigint NOT NULL COMMENT '站点ID',
|
||||
`user_id` bigint NOT NULL COMMENT '用户ID',
|
||||
`user_name` varchar(100) NOT NULL COMMENT '用户名',
|
||||
`draft_total` int NOT NULL DEFAULT '0' COMMENT '初稿内容数',
|
||||
`to_publish_total` int NOT NULL DEFAULT '0' COMMENT '待发布内容数',
|
||||
`published_total` int NOT NULL DEFAULT '0' COMMENT '已发布内容数',
|
||||
`offline_total` int NOT NULL DEFAULT '0' COMMENT '已下线内容数',
|
||||
`editing_total` int NOT NULL DEFAULT '0' COMMENT '重新编辑内容数',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE x_model_field ADD COLUMN validations varchar(500) DEFAULT '' COMMENT '校验规则';
|
||||
ALTER TABLE x_model_field DROP COLUMN mandatory_flag;
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE cc_comment add column deleted tinyint DEFAULT 0;
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE search_log add column location varchar(255);
|
|
@ -0,0 +1,13 @@
|
|||
#错误消息
|
||||
user.jcaptcha.error=验证码错误
|
||||
user.jcaptcha.expire=验证码已失效
|
||||
user.password.not.match=用户不存在/密码错误
|
||||
user.password.retry.limit.count=密码输入错误{0}次
|
||||
user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定{1}分钟
|
||||
|
||||
user.login.success=登录成功
|
||||
user.register.success=注册成功
|
||||
|
||||
##文件上传消息
|
||||
upload.exceed.maxSize=上传的文件大小超出限制的文件大小!<br/>允许的文件最大大小是:{0}MB!
|
||||
upload.filename.exceed.length=上传的文件名最长{0}个字符
|
|
@ -0,0 +1,13 @@
|
|||
#错误消息
|
||||
user.jcaptcha.error=Invalid captcha.
|
||||
user.jcaptcha.expire=Captcha expired.
|
||||
user.password.not.match=User not exists or password error.
|
||||
user.password.retry.limit.count=Password input error {0} times.
|
||||
user.password.retry.limit.exceed=Password input error {0} times, account locking {1} minutes.
|
||||
|
||||
user.login.success=Login success.
|
||||
user.register.success=Register success.
|
||||
|
||||
##文件上传消息
|
||||
upload.exceed.maxSize=Upload file size limit, max size is: {0}MB.
|
||||
upload.filename.exceed.length=Upload file name length limit ,max length is: {0}.
|
|
@ -0,0 +1,93 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<!-- 日志存放路径 -->
|
||||
<property name="log.path" value="logs" />
|
||||
<!-- 日志输出格式 -->
|
||||
<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
|
||||
|
||||
<!-- 控制台输出 -->
|
||||
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>${log.pattern}</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- 系统日志输出 -->
|
||||
<appender name="out" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${log.path}/out.log</file>
|
||||
<!-- 循环政策:基于时间创建日志文件 -->
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<!-- 日志文件名格式 -->
|
||||
<fileNamePattern>${log.path}/out.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
<!-- 日志最大的历史 60天 -->
|
||||
<maxHistory>60</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>${log.pattern}</pattern>
|
||||
</encoder>
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<!-- 过滤的级别 -->
|
||||
<level>INFO</level>
|
||||
<!-- 匹配时的操作:接收(记录) -->
|
||||
<onMatch>ACCEPT</onMatch>
|
||||
<!-- 不匹配时的操作:拒绝(不记录) -->
|
||||
<onMismatch>DENY</onMismatch>
|
||||
</filter>
|
||||
</appender>
|
||||
|
||||
<appender name="error" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${log.path}/error.log</file>
|
||||
<!-- 循环政策:基于时间创建日志文件 -->
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<!-- 日志文件名格式 -->
|
||||
<fileNamePattern>${log.path}/error.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
<!-- 日志最大的历史 60天 -->
|
||||
<maxHistory>60</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>${log.pattern}</pattern>
|
||||
</encoder>
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<!-- 过滤的级别 -->
|
||||
<level>ERROR</level>
|
||||
<!-- 匹配时的操作:接收(记录) -->
|
||||
<onMatch>ACCEPT</onMatch>
|
||||
<!-- 不匹配时的操作:拒绝(不记录) -->
|
||||
<onMismatch>DENY</onMismatch>
|
||||
</filter>
|
||||
</appender>
|
||||
|
||||
<!-- 定时任务输出 -->
|
||||
<appender name="cron" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${log.path}/cron.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<!-- 按天回滚 daily -->
|
||||
<fileNamePattern>${log.path}/cron.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
<!-- 日志最大的历史 60天 -->
|
||||
<maxHistory>60</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>${log.pattern}</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- 系统模块日志级别控制 -->
|
||||
<logger name="com.chestnut" level="info" />
|
||||
<!-- Spring日志级别控制 -->
|
||||
<logger name="org.springframework" level="warn" />
|
||||
<!--控制台日志-->
|
||||
<root level="info">
|
||||
<appender-ref ref="console" />
|
||||
</root>
|
||||
|
||||
<!--系统操作日志-->
|
||||
<root level="info">
|
||||
<appender-ref ref="out" />
|
||||
<appender-ref ref="error" />
|
||||
</root>
|
||||
|
||||
<!--系统定时任务日志-->
|
||||
<logger name="cron" level="info">
|
||||
<appender-ref ref="cron"/>
|
||||
</logger>
|
||||
</configuration>
|
|
@ -0,0 +1,28 @@
|
|||
package com.chestnut.member;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
import com.chestnut.member.domain.Member;
|
||||
import com.chestnut.member.service.IMemberExpConfigService;
|
||||
import com.chestnut.member.service.IMemberService;
|
||||
|
||||
@SpringBootTest
|
||||
public class MemberTest {
|
||||
|
||||
@Autowired
|
||||
private IMemberService memberService;
|
||||
|
||||
@Autowired
|
||||
private IMemberExpConfigService expConfigService;
|
||||
|
||||
@Test
|
||||
void testMemberSignIn() {
|
||||
Member member = this.memberService.getById(398339741712453L);
|
||||
|
||||
expConfigService.list().forEach(expConfig -> {
|
||||
|
||||
expConfigService.triggerExpOperation(expConfig.getOpType(), member.getMemberId());
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
<?xml version="1.0"?>
|
||||
<project
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-cms</artifactId>
|
||||
<version>1.3.21</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>chestnut-cms-advertisement</artifactId>
|
||||
<description>广告模块</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- 数据统计 -->
|
||||
<dependency>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-stat</artifactId>
|
||||
</dependency>
|
||||
<!-- 内容核心 -->
|
||||
<dependency>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-cms-contentcore</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
|
@ -0,0 +1,27 @@
|
|||
package com.chestnut.advertisement;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.chestnut.advertisement.domain.CmsAdvertisement;
|
||||
import com.chestnut.advertisement.service.IAdvertisementService;
|
||||
import com.chestnut.common.utils.SpringUtils;
|
||||
import com.chestnut.contentcore.core.AbstractPageWidget;
|
||||
|
||||
/**
|
||||
* 广告位
|
||||
*
|
||||
* @author 兮玥
|
||||
* @email 190785909@qq.com
|
||||
*/
|
||||
public class AdSpacePageWidget extends AbstractPageWidget {
|
||||
|
||||
private final IAdvertisementService advertisementService = SpringUtils.getBean(IAdvertisementService.class);
|
||||
|
||||
@Override
|
||||
public void delete() {
|
||||
super.delete();
|
||||
// 删除广告版位相关的广告
|
||||
this.advertisementService.remove(new LambdaQueryWrapper<CmsAdvertisement>()
|
||||
.eq(CmsAdvertisement::getAdSpaceId, this.getPageWidgetEntity().getPageWidgetId()));
|
||||
// TODO 删除广告统计数据
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
package com.chestnut.advertisement;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.chestnut.advertisement.pojo.AdSpaceProps;
|
||||
import com.chestnut.advertisement.pojo.vo.AdSpaceVO;
|
||||
import com.chestnut.common.utils.JacksonUtils;
|
||||
import com.chestnut.contentcore.core.IPageWidget;
|
||||
import com.chestnut.contentcore.core.IPageWidgetType;
|
||||
import com.chestnut.contentcore.domain.CmsPageWidget;
|
||||
import com.chestnut.contentcore.domain.vo.PageWidgetVO;
|
||||
|
||||
/**
|
||||
* 广告位页面部件
|
||||
*
|
||||
* @author 兮玥
|
||||
* @email 190785909@qq.com
|
||||
*/
|
||||
@Component(IPageWidgetType.BEAN_NAME_PREFIX + AdSpacePageWidgetType.ID)
|
||||
public class AdSpacePageWidgetType implements IPageWidgetType {
|
||||
|
||||
public final static String ID = "ads";
|
||||
public final static String NAME = "{CMS.CONTENCORE.PAGEWIDGET." + ID + "}";
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIcon() {
|
||||
return "el-icon-list";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRoute() {
|
||||
return "/cms/adspace/editor";
|
||||
}
|
||||
|
||||
@Override
|
||||
public IPageWidget loadPageWidget(CmsPageWidget cmsPageWdiget) {
|
||||
AdSpacePageWidget pw = new AdSpacePageWidget();
|
||||
pw.setPageWidgetEntity(cmsPageWdiget);
|
||||
return pw;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IPageWidget newInstance() {
|
||||
return new AdSpacePageWidget();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageWidgetVO getPageWidgetVO(CmsPageWidget pageWidget) {
|
||||
AdSpaceVO vo = new AdSpaceVO();
|
||||
BeanUtils.copyProperties(pageWidget, vo);
|
||||
vo.setContent(this.parseContent(pageWidget, null, true));
|
||||
return vo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdSpaceProps parseContent(CmsPageWidget pageWidget, String publishPipeCode, boolean isPreview) {
|
||||
return JacksonUtils.from(pageWidget.getContent(), AdSpaceProps.class);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package com.chestnut.advertisement;
|
||||
|
||||
/**
|
||||
* 广告素材类型
|
||||
*/
|
||||
public interface IAdvertisementType {
|
||||
|
||||
/**
|
||||
* Bean名称前缀
|
||||
*/
|
||||
public static final String BEAN_NAME_PREFIX = "AdvertisementType_";
|
||||
|
||||
public String getId();
|
||||
|
||||
public String getName();
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package com.chestnut.advertisement;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component(IAdvertisementType.BEAN_NAME_PREFIX + ImageAdvertisementType.ID)
|
||||
public class ImageAdvertisementType implements IAdvertisementType {
|
||||
|
||||
public static final String ID = "image";
|
||||
|
||||
public static final String NAME = "{ADVERTISEMENT.TYPE." + ID + "}";
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
package com.chestnut.advertisement.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.chestnut.advertisement.domain.CmsAdClickLog;
|
||||
import com.chestnut.advertisement.domain.CmsAdHourStat;
|
||||
import com.chestnut.advertisement.domain.CmsAdViewLog;
|
||||
import com.chestnut.advertisement.mapper.CmsAdClickLogMapper;
|
||||
import com.chestnut.advertisement.mapper.CmsAdHourStatMapper;
|
||||
import com.chestnut.advertisement.mapper.CmsAdViewLogMapper;
|
||||
import com.chestnut.advertisement.service.IAdvertisementService;
|
||||
import com.chestnut.common.domain.R;
|
||||
import com.chestnut.common.security.anno.Priv;
|
||||
import com.chestnut.common.security.web.BaseRestController;
|
||||
import com.chestnut.common.utils.DateUtils;
|
||||
import com.chestnut.common.utils.ServletUtils;
|
||||
import com.chestnut.contentcore.domain.CmsSite;
|
||||
import com.chestnut.contentcore.service.ISiteService;
|
||||
import com.chestnut.system.security.AdminUserType;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Priv(type = AdminUserType.TYPE)
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/cms/ad/stat")
|
||||
public class AdLogController extends BaseRestController {
|
||||
|
||||
private static final SimpleDateFormat FORMAT = new SimpleDateFormat("yyyyMMddHH");
|
||||
|
||||
private final ISiteService siteService;
|
||||
|
||||
private final IAdvertisementService advService;
|
||||
|
||||
private final CmsAdHourStatMapper advHourStatMapper;
|
||||
|
||||
private final CmsAdClickLogMapper adClickLogMapper;
|
||||
|
||||
private final CmsAdViewLogMapper adViewLogMapper;
|
||||
|
||||
@GetMapping
|
||||
public R<?> getAdStatSum(@RequestParam(required = false) Date beginTime,
|
||||
@RequestParam(required = false) Date endTime) {
|
||||
CmsSite site = this.siteService.getCurrentSite(ServletUtils.getRequest());
|
||||
String begin = Objects.isNull(beginTime) ? null : FORMAT.format(beginTime);
|
||||
String end = Objects.isNull(endTime) ? null : FORMAT.format(endTime);
|
||||
List<CmsAdHourStat> list = this.advHourStatMapper.selectGroupByAdvId(site.getSiteId(), begin, end);
|
||||
if (list.size() > 0) {
|
||||
Map<String, String> map = this.advService.getAdvertisementMap();
|
||||
list.forEach(l -> l.setAdName(map.get(l.getAdvertisementId().toString())));
|
||||
}
|
||||
return this.bindDataTable(list);
|
||||
}
|
||||
|
||||
@GetMapping("/chart")
|
||||
public R<?> getLineChartStatDatas(@RequestParam @Min(1) Long advertisementId, @RequestParam Date beginTime, @RequestParam Date endTime) {
|
||||
List<CmsAdHourStat> list = this.advHourStatMapper.selectHourStat(advertisementId, FORMAT.format(beginTime), FORMAT.format(endTime));
|
||||
if (list.size() > 0) {
|
||||
Map<String, String> map = this.advService.getAdvertisementMap();
|
||||
list.forEach(l -> l.setAdName(map.get(l.getAdvertisementId().toString())));
|
||||
}
|
||||
Map<String, CmsAdHourStat> collect = list.stream().collect(Collectors.toMap(CmsAdHourStat::getHour, s -> s));
|
||||
|
||||
List<String> xAxisDatas = new ArrayList<>();
|
||||
Map<String, List<Integer>> lineDatas = new HashMap<>();
|
||||
List<Integer> clickDatas = new ArrayList<>();
|
||||
List<Integer> viewDatas = new ArrayList<>();
|
||||
|
||||
while (!beginTime.after(endTime)) {
|
||||
String hourStr = DateUtils.parseDateToStr("yyyyMMddHH", beginTime);
|
||||
xAxisDatas.add(hourStr);
|
||||
|
||||
CmsAdHourStat stat = collect.get(hourStr);
|
||||
clickDatas.add(Objects.isNull(stat) ? 0 : stat.getClick());
|
||||
viewDatas.add(Objects.isNull(stat) ? 0 : stat.getView());
|
||||
|
||||
beginTime = DateUtils.addHours(beginTime, 1);
|
||||
}
|
||||
lineDatas.put("Click", clickDatas);
|
||||
lineDatas.put("View", viewDatas);
|
||||
|
||||
return R.ok(Map.of("xAxisDatas", xAxisDatas, "lineDatas", lineDatas));
|
||||
}
|
||||
|
||||
@GetMapping("/click")
|
||||
public R<?> listAdClickLogs() {
|
||||
PageRequest pr = getPageRequest();
|
||||
CmsSite site = this.siteService.getCurrentSite(ServletUtils.getRequest());
|
||||
LambdaQueryWrapper<CmsAdClickLog> q = new LambdaQueryWrapper<CmsAdClickLog>()
|
||||
.eq(CmsAdClickLog::getSiteId, site.getSiteId()).orderByDesc(CmsAdClickLog::getLogId);
|
||||
Page<CmsAdClickLog> page = adClickLogMapper.selectPage(new Page<>(pr.getPageNumber(), pr.getPageSize(), true),
|
||||
q);
|
||||
if (page.getRecords().size() > 0) {
|
||||
Map<String, String> map = this.advService.getAdvertisementMap();
|
||||
page.getRecords().forEach(l -> l.setAdName(map.get(l.getAdId().toString())));
|
||||
}
|
||||
return this.bindDataTable(page);
|
||||
}
|
||||
|
||||
@GetMapping("/view")
|
||||
public R<?> listAdViewLogs() {
|
||||
PageRequest pr = getPageRequest();
|
||||
CmsSite site = this.siteService.getCurrentSite(ServletUtils.getRequest());
|
||||
LambdaQueryWrapper<CmsAdViewLog> q = new LambdaQueryWrapper<CmsAdViewLog>()
|
||||
.eq(CmsAdViewLog::getSiteId, site.getSiteId()).orderByDesc(CmsAdViewLog::getLogId);
|
||||
Page<CmsAdViewLog> page = adViewLogMapper.selectPage(new Page<>(pr.getPageNumber(), pr.getPageSize(), true), q);
|
||||
if (page.getRecords().size() > 0) {
|
||||
Map<String, String> map = this.advService.getAdvertisementMap();
|
||||
page.getRecords().forEach(l -> l.setAdName(map.get(l.getAdId().toString())));
|
||||
}
|
||||
return this.bindDataTable(page);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
package com.chestnut.advertisement.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.chestnut.advertisement.AdSpacePageWidgetType;
|
||||
import com.chestnut.advertisement.pojo.vo.AdSpaceVO;
|
||||
import com.chestnut.common.domain.R;
|
||||
import com.chestnut.common.exception.CommonErrorCode;
|
||||
import com.chestnut.common.security.anno.Priv;
|
||||
import com.chestnut.common.security.domain.LoginUser;
|
||||
import com.chestnut.common.security.web.BaseRestController;
|
||||
import com.chestnut.common.utils.Assert;
|
||||
import com.chestnut.common.utils.JacksonUtils;
|
||||
import com.chestnut.common.utils.ServletUtils;
|
||||
import com.chestnut.common.utils.StringUtils;
|
||||
import com.chestnut.contentcore.core.IPageWidget;
|
||||
import com.chestnut.contentcore.domain.CmsCatalog;
|
||||
import com.chestnut.contentcore.domain.CmsPageWidget;
|
||||
import com.chestnut.contentcore.domain.CmsSite;
|
||||
import com.chestnut.contentcore.domain.dto.PageWidgetAddDTO;
|
||||
import com.chestnut.contentcore.domain.dto.PageWidgetEditDTO;
|
||||
import com.chestnut.contentcore.domain.vo.PageWidgetVO;
|
||||
import com.chestnut.contentcore.service.ICatalogService;
|
||||
import com.chestnut.contentcore.service.IPageWidgetService;
|
||||
import com.chestnut.contentcore.service.ISiteService;
|
||||
import com.chestnut.system.security.AdminUserType;
|
||||
import com.chestnut.system.security.StpAdminUtil;
|
||||
import freemarker.template.TemplateException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 广告位页面部件管理前端控制器
|
||||
* </p>
|
||||
*
|
||||
* @author 兮玥
|
||||
* @email 190785909@qq.com
|
||||
*/
|
||||
@Priv(type = AdminUserType.TYPE)
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/cms/adspace")
|
||||
public class AdSpaceController extends BaseRestController {
|
||||
|
||||
private final ISiteService siteService;
|
||||
|
||||
private final ICatalogService catalogService;
|
||||
|
||||
private final IPageWidgetService pageWidgetService;
|
||||
|
||||
private final AdSpacePageWidgetType pageWidgetType;
|
||||
|
||||
@GetMapping
|
||||
public R<?> listAdSpaces(@RequestParam(name = "catalogId", required = false) Long catalogId,
|
||||
@RequestParam(name = "name", required = false) String name,
|
||||
@RequestParam(name = "state", required = false) Integer state) {
|
||||
PageRequest pr = getPageRequest();
|
||||
CmsSite site = this.siteService.getCurrentSite(ServletUtils.getRequest());
|
||||
LambdaQueryWrapper<CmsPageWidget> q = new LambdaQueryWrapper<CmsPageWidget>()
|
||||
.eq(CmsPageWidget::getSiteId, site.getSiteId())
|
||||
.eq(catalogId != null && catalogId > 0, CmsPageWidget::getCatalogId, catalogId)
|
||||
.like(StringUtils.isNotEmpty(name), CmsPageWidget::getName, name)
|
||||
.eq(CmsPageWidget::getType, AdSpacePageWidgetType.ID)
|
||||
.eq(state != null && state > -1, CmsPageWidget::getState, state)
|
||||
.orderByDesc(CmsPageWidget::getCreateTime);
|
||||
Page<CmsPageWidget> page = pageWidgetService.page(new Page<>(pr.getPageNumber(), pr.getPageSize(), true), q);
|
||||
List<AdSpaceVO> list = new ArrayList<>();
|
||||
page.getRecords().forEach(pw -> {
|
||||
AdSpaceVO vo = (AdSpaceVO) pageWidgetType.getPageWidgetVO(pw);
|
||||
if (pw.getCatalogId() > 0) {
|
||||
CmsCatalog catalog = catalogService.getCatalog(pw.getCatalogId());
|
||||
vo.setCatalogName(catalog != null ? catalog.getName() : "未知");
|
||||
}
|
||||
list.add(vo);
|
||||
});
|
||||
return this.bindDataTable(list, (int) page.getTotal());
|
||||
}
|
||||
|
||||
@GetMapping("/{adSpaceId}")
|
||||
public R<PageWidgetVO> getAdSpaceInfo(@PathVariable("adSpaceId") Long adSpaceId) {
|
||||
CmsPageWidget pageWidget = this.pageWidgetService.getById(adSpaceId);
|
||||
if (pageWidget == null) {
|
||||
return R.fail("数据未找到:" + adSpaceId);
|
||||
}
|
||||
AdSpaceVO vo = (AdSpaceVO) pageWidgetType.getPageWidgetVO(pageWidget);
|
||||
CmsCatalog catalog = this.catalogService.getCatalog(pageWidget.getCatalogId());
|
||||
vo.setCatalogName(catalog != null ? catalog.getName() : "未知");
|
||||
return R.ok(vo);
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public R<?> addAdSpace(HttpServletRequest request) throws IOException {
|
||||
PageWidgetAddDTO dto = JacksonUtils.from(request.getInputStream(), PageWidgetAddDTO.class);
|
||||
dto.setType(pageWidgetType.getId());
|
||||
|
||||
CmsPageWidget cmsPageWdiget = new CmsPageWidget();
|
||||
BeanUtils.copyProperties(dto, cmsPageWdiget);
|
||||
IPageWidget pw = pageWidgetType.newInstance();
|
||||
pw.setPageWidgetEntity(cmsPageWdiget);
|
||||
pw.setOperator(StpAdminUtil.getLoginUser());
|
||||
CmsSite site = this.siteService.getCurrentSite(request);
|
||||
pw.getPageWidgetEntity().setSiteId(site.getSiteId());
|
||||
this.pageWidgetService.addPageWidget(pw);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
@PutMapping
|
||||
public R<?> editAdSpace(HttpServletRequest request) throws IOException {
|
||||
PageWidgetEditDTO dto = JacksonUtils.from(request.getInputStream(), PageWidgetEditDTO.class);
|
||||
CmsPageWidget cmsPageWdiget = new CmsPageWidget();
|
||||
BeanUtils.copyProperties(dto, cmsPageWdiget);
|
||||
IPageWidget pw = pageWidgetType.newInstance();
|
||||
pw.setPageWidgetEntity(cmsPageWdiget);
|
||||
pw.setOperator(StpAdminUtil.getLoginUser());
|
||||
this.pageWidgetService.savePageWidget(pw);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
@DeleteMapping
|
||||
public R<?> deleteAdSpaces(@RequestBody List<Long> adSpaceIds) {
|
||||
Assert.notEmpty(adSpaceIds, () -> CommonErrorCode.INVALID_REQUEST_ARG.exception("adSpaceIds"));
|
||||
this.pageWidgetService.deletePageWidgets(adSpaceIds, StpAdminUtil.getLoginUser());
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
@PostMapping("/publish")
|
||||
public R<?> publishPageWidgets(@RequestBody List<Long> adSpaceIds) throws TemplateException, IOException {
|
||||
Assert.notEmpty(adSpaceIds, () -> CommonErrorCode.INVALID_REQUEST_ARG.exception("adSpaceIds"));
|
||||
this.pageWidgetService.publishPageWidgets(adSpaceIds, StpAdminUtil.getLoginUser());
|
||||
return R.ok();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,137 @@
|
|||
package com.chestnut.advertisement.controller;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
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.PutMapping;
|
||||
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 com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.chestnut.advertisement.IAdvertisementType;
|
||||
import com.chestnut.advertisement.domain.CmsAdvertisement;
|
||||
import com.chestnut.advertisement.permission.CmsAdvertisementPriv;
|
||||
import com.chestnut.advertisement.pojo.dto.AdvertisementDTO;
|
||||
import com.chestnut.advertisement.pojo.vo.AdvertisementVO;
|
||||
import com.chestnut.advertisement.service.IAdvertisementService;
|
||||
import com.chestnut.common.domain.R;
|
||||
import com.chestnut.common.exception.CommonErrorCode;
|
||||
import com.chestnut.common.i18n.I18nUtils;
|
||||
import com.chestnut.common.log.annotation.Log;
|
||||
import com.chestnut.common.log.enums.BusinessType;
|
||||
import com.chestnut.common.security.anno.Priv;
|
||||
import com.chestnut.common.security.web.BaseRestController;
|
||||
import com.chestnut.common.utils.Assert;
|
||||
import com.chestnut.common.utils.StringUtils;
|
||||
import com.chestnut.system.security.AdminUserType;
|
||||
import com.chestnut.system.security.StpAdminUtil;
|
||||
|
||||
import jakarta.validation.constraints.Min;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 广告前端控制器
|
||||
* </p>
|
||||
*
|
||||
* @author 兮玥
|
||||
* @email 190785909@qq.com
|
||||
*/
|
||||
@Priv(type = AdminUserType.TYPE, value = CmsAdvertisementPriv.View)
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/cms/advertisement")
|
||||
public class AdvertisementController extends BaseRestController {
|
||||
|
||||
private final IAdvertisementService advertisementService;
|
||||
|
||||
@GetMapping("/types")
|
||||
public R<?> listAdvertisements() {
|
||||
List<Map<String, String>> list = advertisementService.getAdvertisementTypeList().stream()
|
||||
.map(t -> Map.of("id", t.getId(), "name", I18nUtils.get(t.getName()))).toList();
|
||||
return this.bindDataTable(list);
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public R<?> listAdvertisements(@RequestParam(name = "adSpaceId") @Min(1) Long adSpaceId,
|
||||
@RequestParam(name = "name", required = false) String name,
|
||||
@RequestParam(name = "state", required = false) Integer state) {
|
||||
PageRequest pr = getPageRequest();
|
||||
Page<CmsAdvertisement> page = this.advertisementService.lambdaQuery()
|
||||
.eq(CmsAdvertisement::getAdSpaceId, adSpaceId)
|
||||
.like(StringUtils.isNotEmpty(name), CmsAdvertisement::getName, name)
|
||||
.eq(state != null && state > -1, CmsAdvertisement::getState, state)
|
||||
.orderByDesc(CmsAdvertisement::getCreateTime).page(new Page<>(pr.getPageNumber(), pr.getPageSize(), true));
|
||||
page.getRecords().forEach(adv -> {
|
||||
IAdvertisementType advertisementType = this.advertisementService.getAdvertisementType(adv.getType());
|
||||
if (Objects.nonNull(advertisementType)) {
|
||||
adv.setTypeName(I18nUtils.get(advertisementType.getName()));
|
||||
}
|
||||
});
|
||||
return this.bindDataTable(page.getRecords(), (int) page.getTotal());
|
||||
}
|
||||
|
||||
@GetMapping("/{advertisementId}")
|
||||
public R<AdvertisementVO> getAdvertisementInfo(@PathVariable("advertisementId") @Min(1) Long advertisementId) {
|
||||
CmsAdvertisement ad = this.advertisementService.getById(advertisementId);
|
||||
Assert.notNull(ad, () -> CommonErrorCode.DATA_NOT_FOUND_BY_ID.exception("advertisementId", advertisementId));
|
||||
return R.ok(new AdvertisementVO(ad).dealPreviewResourcePath());
|
||||
}
|
||||
|
||||
@Log(title = "新增广告", businessType = BusinessType.INSERT)
|
||||
@PostMapping
|
||||
public R<?> addAdvertisement(@RequestBody AdvertisementDTO dto) throws IOException {
|
||||
dto.setOperator(StpAdminUtil.getLoginUser());
|
||||
this.advertisementService.addAdvertisement(dto);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
@Log(title = "编辑广告", businessType = BusinessType.UPDATE)
|
||||
@PutMapping
|
||||
public R<?> editAdvertisement(@RequestBody AdvertisementDTO dto) throws IOException {
|
||||
dto.setOperator(StpAdminUtil.getLoginUser());
|
||||
this.advertisementService.saveAdvertisement(dto);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
@Log(title = "删除广告", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping
|
||||
public R<?> deleteAdvertisements(@RequestBody List<Long> advertisementIds) {
|
||||
if (StringUtils.isEmpty(advertisementIds)) {
|
||||
return R.fail(StringUtils.messageFormat("参数[{0}]不能为空", "advertisementIds"));
|
||||
}
|
||||
this.advertisementService.deleteAdvertisement(advertisementIds);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
@Log(title = "启用广告", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/enable")
|
||||
public R<?> enableAdvertisements(@RequestBody List<Long> advertisementIds) {
|
||||
if (StringUtils.isEmpty(advertisementIds)) {
|
||||
return R.fail(StringUtils.messageFormat("参数[{0}]不能为空", "advertisementIds"));
|
||||
}
|
||||
this.advertisementService.enableAdvertisement(advertisementIds,
|
||||
StpAdminUtil.getLoginUser().getUsername());
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
@Log(title = "禁用广告", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/disable")
|
||||
public R<?> disableAdvertisements(@RequestBody List<Long> advertisementIds) {
|
||||
if (StringUtils.isEmpty(advertisementIds)) {
|
||||
return R.fail(StringUtils.messageFormat("参数[{0}]不能为空", "advertisementIds"));
|
||||
}
|
||||
this.advertisementService.disableAdvertisement(advertisementIds,
|
||||
StpAdminUtil.getLoginUser().getUsername());
|
||||
return R.ok();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
package com.chestnut.advertisement.controller.front;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import com.chestnut.common.utils.StringUtils;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.chestnut.advertisement.domain.CmsAdClickLog;
|
||||
import com.chestnut.advertisement.domain.CmsAdViewLog;
|
||||
import com.chestnut.advertisement.service.IAdvertisementStatService;
|
||||
import com.chestnut.common.security.web.BaseRestController;
|
||||
import com.chestnut.common.utils.ServletUtils;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
/**
|
||||
* 广告统计数据收集
|
||||
*
|
||||
* @author 兮玥
|
||||
* @email 190785909@qq.com
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping("/api/adv")
|
||||
public class AdApiController extends BaseRestController {
|
||||
|
||||
private final IAdvertisementStatService adStatService;
|
||||
|
||||
@GetMapping("/redirect")
|
||||
public void statAndRedirect(@RequestParam("sid") Long siteId,
|
||||
@RequestParam("aid") Long advertisementId,
|
||||
@RequestParam("url") String redirectUrl,
|
||||
HttpServletResponse response) throws IOException {
|
||||
this.adClick(siteId, advertisementId);
|
||||
response.sendRedirect(URLDecoder.decode(redirectUrl, StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
@GetMapping("/click")
|
||||
public void adClick(@RequestParam("sid") Long siteId, @RequestParam("aid") Long advertisementId) {
|
||||
try {
|
||||
CmsAdClickLog log = new CmsAdClickLog();
|
||||
log.fill(ServletUtils.getRequest());
|
||||
log.setSiteId(siteId);
|
||||
log.setAdId(advertisementId);
|
||||
log.setEvtTime(LocalDateTime.now());
|
||||
this.adStatService.adClick(log);
|
||||
} catch (Exception e) {
|
||||
log.error("Advertisement click stat failed: " + advertisementId, e);
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/view")
|
||||
public void adView(@RequestParam("sid") Long siteId, @RequestParam("aid") Long advertisementId) {
|
||||
try {
|
||||
CmsAdViewLog log = new CmsAdViewLog();
|
||||
log.fill(ServletUtils.getRequest());
|
||||
log.setSiteId(siteId);
|
||||
log.setAdId(advertisementId);
|
||||
log.setEvtTime(LocalDateTime.now());
|
||||
this.adStatService.adView(log);
|
||||
} catch (Exception e) {
|
||||
log.error("Advertisement view stat failed: " + advertisementId, e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package com.chestnut.advertisement.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.chestnut.stat.RequestEvent;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@TableName(CmsAdClickLog.TABLE_NAME)
|
||||
public class CmsAdClickLog extends RequestEvent implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public final static String TABLE_NAME = "cms_ad_click_log";
|
||||
|
||||
@TableId(value = "log_id", type = IdType.AUTO)
|
||||
private Long logId;
|
||||
|
||||
/**
|
||||
* 站点ID
|
||||
*/
|
||||
private Long siteId;
|
||||
|
||||
/**
|
||||
* 广告ID
|
||||
*/
|
||||
private Long adId;
|
||||
|
||||
/**
|
||||
* 广告名称
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private String adName;
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
package com.chestnut.advertisement.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* 广告小时点击/展现统计数据
|
||||
*
|
||||
* @author 兮玥
|
||||
* @email 190785909@qq.com
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@TableName(CmsAdHourStat.TABLE_NAME)
|
||||
public class CmsAdHourStat implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public final static String TABLE_NAME = "cms_ad_hour_stat";
|
||||
|
||||
@TableId(value = "stat_id", type = IdType.AUTO)
|
||||
private Long statId;
|
||||
|
||||
/**
|
||||
* 所属站点ID
|
||||
*/
|
||||
private Long siteId;
|
||||
|
||||
/**
|
||||
* 统计周期,格式:yyyyMMddHH
|
||||
*/
|
||||
private String hour;
|
||||
|
||||
/**
|
||||
* 广告ID
|
||||
*/
|
||||
private Long advertisementId;
|
||||
|
||||
/**
|
||||
* 点击数
|
||||
*/
|
||||
private Integer click;
|
||||
|
||||
/**
|
||||
* 展现数
|
||||
*/
|
||||
private Integer view;
|
||||
|
||||
/**
|
||||
* 广告名称
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private String adName;
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package com.chestnut.advertisement.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.chestnut.stat.RequestEvent;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@TableName(CmsAdViewLog.TABLE_NAME)
|
||||
public class CmsAdViewLog extends RequestEvent implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public final static String TABLE_NAME = "cms_ad_view_log";
|
||||
|
||||
@TableId(value = "log_id", type = IdType.AUTO)
|
||||
private Long logId;
|
||||
|
||||
/**
|
||||
* 站点ID
|
||||
*/
|
||||
private Long siteId;
|
||||
|
||||
/**
|
||||
* 广告ID
|
||||
*/
|
||||
private Long adId;
|
||||
|
||||
/**
|
||||
* 广告名称
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private String adName;
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
package com.chestnut.advertisement.domain;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.chestnut.common.db.domain.BaseEntity;
|
||||
import com.chestnut.system.fixed.dict.EnableOrDisable;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* 广告详情表对象 [cms_advertisement]
|
||||
*
|
||||
* @author 兮玥
|
||||
* @email 190785909@qq.com
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@TableName(CmsAdvertisement.TABLE_NAME)
|
||||
public class CmsAdvertisement extends BaseEntity {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public static final String TABLE_NAME = "cms_advertisement";
|
||||
|
||||
@TableId(value = "advertisement_id", type = IdType.INPUT)
|
||||
private Long advertisementId;
|
||||
|
||||
/**
|
||||
* 所属站点ID
|
||||
*/
|
||||
private Long siteId;
|
||||
|
||||
/**
|
||||
* 所属广告版位ID(等同页面部件ID)
|
||||
*/
|
||||
private Long adSpaceId;
|
||||
|
||||
/**
|
||||
* 类型
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 类型名称
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private String typeName;
|
||||
|
||||
/**
|
||||
* 名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 权重
|
||||
*/
|
||||
private Integer weight;
|
||||
|
||||
/**
|
||||
* 关键词
|
||||
*/
|
||||
private String keywords;
|
||||
|
||||
/**
|
||||
* 状态
|
||||
*
|
||||
* @see EnableOrDisable
|
||||
*/
|
||||
private String state;
|
||||
|
||||
/**
|
||||
* 上线时间
|
||||
*/
|
||||
private LocalDateTime onlineDate;
|
||||
|
||||
/**
|
||||
* 下线时间
|
||||
*/
|
||||
private LocalDateTime offlineDate;
|
||||
|
||||
/**
|
||||
* 跳转链接
|
||||
*/
|
||||
private String redirectUrl;
|
||||
|
||||
/**
|
||||
* 素材链接
|
||||
*/
|
||||
private String resourcePath;
|
||||
|
||||
public boolean isEnable() {
|
||||
return EnableOrDisable.isEnable(this.state);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
package com.chestnut.advertisement.job;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.chestnut.advertisement.AdSpacePageWidgetType;
|
||||
import com.chestnut.advertisement.domain.CmsAdvertisement;
|
||||
import com.chestnut.advertisement.service.IAdvertisementService;
|
||||
import com.chestnut.contentcore.core.IPageWidgetType;
|
||||
import com.chestnut.contentcore.domain.CmsPageWidget;
|
||||
import com.chestnut.contentcore.fixed.dict.PageWidgetStatus;
|
||||
import com.chestnut.contentcore.service.IPageWidgetService;
|
||||
import com.chestnut.contentcore.service.IPublishService;
|
||||
import com.chestnut.system.fixed.dict.EnableOrDisable;
|
||||
import com.chestnut.system.schedule.IScheduledHandler;
|
||||
import com.xxl.job.core.handler.IJobHandler;
|
||||
import com.xxl.job.core.handler.annotation.XxlJob;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 广告定时发布下线任务
|
||||
*
|
||||
* @author 兮玥
|
||||
* @email 190785909@qq.com
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Component(IScheduledHandler.BEAN_PREFIX + AdvertisementPublishJob.JOB_NAME)
|
||||
public class AdvertisementPublishJob extends IJobHandler implements IScheduledHandler {
|
||||
|
||||
static final String JOB_NAME = "AdvertisementPublishJob";
|
||||
|
||||
private final IPageWidgetService pageWidgetService;
|
||||
|
||||
private final IAdvertisementService advertisementService;
|
||||
|
||||
private final IPublishService publishService;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return JOB_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "{SCHEDULED_TASK." + JOB_NAME + "}";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exec() throws Exception {
|
||||
logger.info("Job start: {}", JOB_NAME);
|
||||
long s = System.currentTimeMillis();
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
List<CmsPageWidget> list = this.pageWidgetService.list(new LambdaQueryWrapper<CmsPageWidget>()
|
||||
.eq(CmsPageWidget::getState, PageWidgetStatus.PUBLISHED)
|
||||
.eq(CmsPageWidget::getType, AdSpacePageWidgetType.ID));
|
||||
for (CmsPageWidget adSpace : list) {
|
||||
boolean changed = false;
|
||||
List<CmsAdvertisement> toOnlineList = this.advertisementService.list(new LambdaQueryWrapper<CmsAdvertisement>()
|
||||
.eq(CmsAdvertisement::getState, EnableOrDisable.DISABLE)
|
||||
.eq(CmsAdvertisement::getAdSpaceId, adSpace.getPageWidgetId())
|
||||
.le(CmsAdvertisement::getOnlineDate, now)
|
||||
.ge(CmsAdvertisement::getOfflineDate, now));
|
||||
if (toOnlineList != null && toOnlineList.size() > 0) {
|
||||
changed = true;
|
||||
for (CmsAdvertisement ad : toOnlineList) {
|
||||
ad.setState(EnableOrDisable.ENABLE);
|
||||
}
|
||||
this.advertisementService.updateBatchById(toOnlineList);
|
||||
}
|
||||
// 下线时间小于当前时间的启用广告标记为停用
|
||||
List<CmsAdvertisement> toOfflineList = this.advertisementService.list(new LambdaQueryWrapper<CmsAdvertisement>()
|
||||
.eq(CmsAdvertisement::getState, EnableOrDisable.ENABLE)
|
||||
.eq(CmsAdvertisement::getAdSpaceId, adSpace.getPageWidgetId())
|
||||
.lt(CmsAdvertisement::getOfflineDate, now));
|
||||
if (toOfflineList != null && toOfflineList.size() > 0) {
|
||||
changed = true;
|
||||
for (CmsAdvertisement ad : toOfflineList) {
|
||||
ad.setState(EnableOrDisable.DISABLE);
|
||||
}
|
||||
this.advertisementService.updateBatchById(toOfflineList);
|
||||
}
|
||||
// 有变化重新发布广告版位
|
||||
if (changed) {
|
||||
IPageWidgetType pwt = this.pageWidgetService.getPageWidgetType(adSpace.getType());
|
||||
this.publishService.pageWidgetStaticize(pwt.loadPageWidget(adSpace));
|
||||
}
|
||||
}
|
||||
logger.info("Job '{}' completed, cost: {}ms", JOB_NAME, System.currentTimeMillis() - s);
|
||||
}
|
||||
|
||||
@Override
|
||||
@XxlJob(JOB_NAME)
|
||||
public void execute() throws Exception {
|
||||
this.exec();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
package com.chestnut.advertisement.job;
|
||||
|
||||
import com.chestnut.advertisement.domain.CmsAdHourStat;
|
||||
import com.chestnut.advertisement.domain.CmsAdvertisement;
|
||||
import com.chestnut.advertisement.service.IAdHourStatService;
|
||||
import com.chestnut.advertisement.service.IAdvertisementService;
|
||||
import com.chestnut.advertisement.service.impl.AdvertisementStatServiceImpl;
|
||||
import com.chestnut.common.redis.RedisCache;
|
||||
import com.chestnut.system.schedule.IScheduledHandler;
|
||||
import com.xxl.job.core.handler.IJobHandler;
|
||||
import com.xxl.job.core.handler.annotation.XxlJob;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 广告统计任务
|
||||
*
|
||||
* @author 兮玥
|
||||
* @email 190785909@qq.com
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Component(IScheduledHandler.BEAN_PREFIX + AdvertisementStatJob.JOB_NAME)
|
||||
public class AdvertisementStatJob extends IJobHandler implements IScheduledHandler {
|
||||
|
||||
static final String JOB_NAME = "AdvertisementStatJob";
|
||||
|
||||
private final IAdHourStatService adStatService;
|
||||
|
||||
private final IAdvertisementService advertisementService;
|
||||
|
||||
private final RedisCache redisCache;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return JOB_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "{SCHEDULED_TASK." + JOB_NAME + "}";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exec() throws Exception {
|
||||
logger.info("Job start: {}", JOB_NAME);
|
||||
long s = System.currentTimeMillis();
|
||||
try {
|
||||
// 数据更新
|
||||
String hour = LocalDateTime.now().format(AdvertisementStatServiceImpl.DATE_TIME_FORMAT);
|
||||
this.saveToDb(hour, false);
|
||||
// 尝试更新上一个小时数据并删除cache
|
||||
String yestoday = LocalDateTime.now().minusHours(1).format(AdvertisementStatServiceImpl.DATE_TIME_FORMAT);
|
||||
this.saveToDb(yestoday, true);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw e;
|
||||
}
|
||||
logger.info("Job '{}' completed, cost: {}ms", JOB_NAME, System.currentTimeMillis() - s);
|
||||
}
|
||||
|
||||
private void saveToDb(String hour, boolean deleteCache) {
|
||||
String clickCacheKey = AdvertisementStatServiceImpl.CLIC_CACHE_PREFIX + hour;
|
||||
String viewCacheKey = AdvertisementStatServiceImpl.VIEW_CACHE_PREFIX + hour;
|
||||
|
||||
Map<Long, CmsAdHourStat> stats = this.adStatService.lambdaQuery().eq(CmsAdHourStat::getHour, hour).list()
|
||||
.stream().collect(Collectors.toMap(CmsAdHourStat::getAdvertisementId, stat -> stat));
|
||||
|
||||
Map<Long, Long> advertisements = advertisementService.lambdaQuery()
|
||||
.select(List.of(CmsAdvertisement::getAdvertisementId, CmsAdvertisement::getSiteId)).list().stream()
|
||||
.collect(Collectors.toMap(CmsAdvertisement::getAdvertisementId, CmsAdvertisement::getSiteId));
|
||||
|
||||
List<Long> insertAdvIds = new ArrayList<>();
|
||||
for (Long advertisementId : advertisements.keySet()) {
|
||||
int click = this.redisCache.getZsetScore(clickCacheKey, advertisementId.toString()).intValue();
|
||||
int view = this.redisCache.getZsetScore(viewCacheKey, advertisementId.toString()).intValue();
|
||||
if (click > 0 || view > 0) {
|
||||
CmsAdHourStat stat = stats.get(advertisementId);
|
||||
if (Objects.isNull(stat)) {
|
||||
stat = new CmsAdHourStat();
|
||||
stat.setSiteId(advertisements.get(advertisementId));
|
||||
stat.setHour(hour);
|
||||
stat.setAdvertisementId(advertisementId);
|
||||
|
||||
stats.put(advertisementId, stat);
|
||||
insertAdvIds.add(advertisementId);
|
||||
}
|
||||
stat.setClick(Math.max(click, 0));
|
||||
stat.setView(Math.max(view, 0));
|
||||
}
|
||||
}
|
||||
// 更新数据库
|
||||
List<CmsAdHourStat> inserts = stats.values().stream()
|
||||
.filter(stat -> insertAdvIds.contains(stat.getAdvertisementId())).toList();
|
||||
this.adStatService.saveBatch(inserts);
|
||||
List<CmsAdHourStat> updates = stats.values().stream()
|
||||
.filter(stat -> !insertAdvIds.contains(stat.getAdvertisementId())).toList();
|
||||
this.adStatService.updateBatchById(updates);
|
||||
// 清理过期缓存
|
||||
if (deleteCache) {
|
||||
this.redisCache.deleteObject(clickCacheKey);
|
||||
this.redisCache.deleteObject(viewCacheKey);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@XxlJob(JOB_NAME)
|
||||
public void execute() throws Exception {
|
||||
this.exec();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package com.chestnut.advertisement.listener;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.chestnut.advertisement.domain.CmsAdvertisement;
|
||||
import com.chestnut.advertisement.service.IAdvertisementService;
|
||||
import com.chestnut.common.async.AsyncTaskManager;
|
||||
import com.chestnut.contentcore.domain.CmsSite;
|
||||
import com.chestnut.contentcore.listener.event.BeforeSiteDeleteEvent;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class AdvertisementListener {
|
||||
|
||||
private final IAdvertisementService advertisementService;
|
||||
|
||||
@EventListener
|
||||
public void beforeSiteDelete(BeforeSiteDeleteEvent event) {
|
||||
CmsSite site = event.getSite();
|
||||
int pageSize = 500;
|
||||
// 删除广告数据
|
||||
try {
|
||||
long total = this.advertisementService
|
||||
.count(new LambdaQueryWrapper<CmsAdvertisement>().eq(CmsAdvertisement::getSiteId, site.getSiteId()));
|
||||
for (int i = 0; i * pageSize < total; i++) {
|
||||
AsyncTaskManager.setTaskProgressInfo((int) (i * pageSize * 100 / total), "正在删除广告数据:" + (i * pageSize) + "/" + total);
|
||||
this.advertisementService.remove(new LambdaQueryWrapper<CmsAdvertisement>()
|
||||
.eq(CmsAdvertisement::getSiteId, site.getSiteId()).last("limit " + pageSize));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
AsyncTaskManager.addErrMessage("删除广告数据错误:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package com.chestnut.advertisement.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.chestnut.advertisement.domain.CmsAdClickLog;
|
||||
|
||||
public interface CmsAdClickLogMapper extends BaseMapper<CmsAdClickLog> {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package com.chestnut.advertisement.mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.chestnut.advertisement.domain.CmsAdHourStat;
|
||||
|
||||
public interface CmsAdHourStatMapper extends BaseMapper<CmsAdHourStat> {
|
||||
|
||||
@Select("""
|
||||
<script>
|
||||
SELECT * FROM `cms_ad_hour_stat`
|
||||
WHERE advertisement_id = #{advertisementId}
|
||||
<if test='begin != null'> and hour >= #{begin} </if>
|
||||
<if test='end != null'> and hour <= #{end} </if>
|
||||
ORDER BY hour ASC
|
||||
</script>
|
||||
""")
|
||||
public List<CmsAdHourStat> selectHourStat(@Param("advertisementId") Long advertisementId,
|
||||
@Param("begin") String begin, @Param("end") String end);
|
||||
|
||||
@Select("""
|
||||
<script>
|
||||
SELECT advertisement_id, sum(click) click, sum(view) view FROM `cms_ad_hour_stat`
|
||||
WHERE site_id = #{siteId}
|
||||
<if test='begin != null'> AND hour >= #{begin} </if>
|
||||
<if test='end != null'> AND hour <= #{end} </if>
|
||||
GROUP BY advertisement_id ORDER BY `click` DESC, `view` DESC
|
||||
</script>
|
||||
""")
|
||||
public List<CmsAdHourStat> selectGroupByAdvId(@Param("siteId") Long siteId, @Param("begin") String begin,
|
||||
@Param("end") String end);
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package com.chestnut.advertisement.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.chestnut.advertisement.domain.CmsAdViewLog;
|
||||
|
||||
public interface CmsAdViewLogMapper extends BaseMapper<CmsAdViewLog> {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.chestnut.advertisement.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.chestnut.advertisement.domain.CmsAdvertisement;
|
||||
|
||||
/**
|
||||
* 广告页面部件表 数据层
|
||||
*/
|
||||
public interface CmsAdvertisementMapper extends BaseMapper<CmsAdvertisement> {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.chestnut.advertisement.permission;
|
||||
|
||||
public interface CmsAdvertisementPriv {
|
||||
|
||||
public String View = "cms:advertisement:view";
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package com.chestnut.advertisement.pojo;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* 广告位自定义属性
|
||||
*
|
||||
* @author 兮玥
|
||||
* @email 190785909@qq.com
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class AdSpaceProps {
|
||||
|
||||
/**
|
||||
* 广告策略
|
||||
*/
|
||||
private String strategy;
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
package com.chestnut.advertisement.pojo.dto;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
import com.chestnut.common.security.domain.BaseDTO;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class AdvertisementDTO extends BaseDTO {
|
||||
|
||||
/**
|
||||
* 广告ID
|
||||
*/
|
||||
@NotNull
|
||||
private Long advertisementId;
|
||||
|
||||
/**
|
||||
* 广告位ID(等同页面部件ID)
|
||||
*/
|
||||
@NotNull
|
||||
private Long adSpaceId;
|
||||
|
||||
/**
|
||||
* 广告类型
|
||||
*/
|
||||
@NotNull
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 名称
|
||||
*/
|
||||
@NotNull
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 权重
|
||||
*/
|
||||
@NotNull
|
||||
private Integer weight;
|
||||
|
||||
/**
|
||||
* 关键词
|
||||
*/
|
||||
private String keywords;
|
||||
|
||||
/**
|
||||
* 上线时间
|
||||
*/
|
||||
@NotNull
|
||||
private LocalDateTime onlineDate;
|
||||
|
||||
/**
|
||||
* 下线时间
|
||||
*/
|
||||
@NotNull
|
||||
private LocalDateTime offlineDate;
|
||||
|
||||
/**
|
||||
* 跳转链接
|
||||
*/
|
||||
@NotNull
|
||||
private String redirectUrl;
|
||||
|
||||
/**
|
||||
* 素材链接
|
||||
*/
|
||||
@NotNull
|
||||
private String resourcePath;
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package com.chestnut.advertisement.pojo.vo;
|
||||
|
||||
import com.chestnut.advertisement.pojo.AdSpaceProps;
|
||||
import com.chestnut.contentcore.domain.vo.PageWidgetVO;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Accessors(chain = true)
|
||||
public class AdSpaceVO extends PageWidgetVO {
|
||||
|
||||
/**
|
||||
* 广告位自定义属性
|
||||
*/
|
||||
private AdSpaceProps content;
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
package com.chestnut.advertisement.pojo.vo;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
|
||||
import com.chestnut.advertisement.domain.CmsAdvertisement;
|
||||
import com.chestnut.contentcore.util.InternalUrlUtils;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* 广告数据VO
|
||||
*
|
||||
* @author 兮玥
|
||||
* @email 190785909@qq.com
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class AdvertisementVO {
|
||||
|
||||
/**
|
||||
* 广告ID
|
||||
*/
|
||||
private Long advertisementId;
|
||||
|
||||
/**
|
||||
* 所属广告位ID
|
||||
*/
|
||||
private Long adSpaceId;
|
||||
|
||||
/**
|
||||
* 类型
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 权重
|
||||
*/
|
||||
private Integer weight;
|
||||
|
||||
/**
|
||||
* 关键词
|
||||
*/
|
||||
private String keywords;
|
||||
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
private String state;
|
||||
|
||||
/**
|
||||
* 上线时间
|
||||
*/
|
||||
private LocalDateTime onlineDate;
|
||||
|
||||
/**
|
||||
* 下线时间
|
||||
*/
|
||||
private LocalDateTime offlineDate;
|
||||
|
||||
/**
|
||||
* 跳转链接
|
||||
*/
|
||||
private String redirectUrl;
|
||||
|
||||
/**
|
||||
* 跳转链接(可设置为中转地址)
|
||||
*/
|
||||
private String link;
|
||||
|
||||
/**
|
||||
* 素材链接
|
||||
*/
|
||||
private String resourcePath;
|
||||
|
||||
/**
|
||||
* 素材真实地址
|
||||
*/
|
||||
private String resourceSrc;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
private String createBy;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
|
||||
public AdvertisementVO(CmsAdvertisement ad) {
|
||||
this.advertisementId = ad.getAdvertisementId();
|
||||
this.adSpaceId = ad.getAdSpaceId();
|
||||
this.type = ad.getType();
|
||||
this.name = ad.getName();
|
||||
this.weight = ad.getWeight();
|
||||
this.keywords = ad.getKeywords();
|
||||
this.state = ad.getState();
|
||||
this.onlineDate = ad.getOnlineDate();
|
||||
this.offlineDate = ad.getOfflineDate();
|
||||
this.redirectUrl = ad.getRedirectUrl();
|
||||
this.resourcePath = ad.getResourcePath();
|
||||
this.createBy = ad.getCreateBy();
|
||||
this.createTime = ad.getCreateTime();
|
||||
}
|
||||
|
||||
public AdvertisementVO dealPreviewResourcePath() {
|
||||
return dealResourcePath(null, true);
|
||||
}
|
||||
|
||||
public AdvertisementVO dealResourcePath(String publishPipeCode, boolean isPreview) {
|
||||
if (StringUtils.isNotEmpty(this.getResourcePath())) {
|
||||
this.setResourceSrc(InternalUrlUtils.getActualUrl(this.getResourcePath(), publishPipeCode, isPreview));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package com.chestnut.advertisement.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.chestnut.advertisement.domain.CmsAdHourStat;
|
||||
|
||||
/**
|
||||
* 广告统计 服务接口
|
||||
*/
|
||||
public interface IAdHourStatService extends IService<CmsAdHourStat> {
|
||||
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
package com.chestnut.advertisement.service;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.chestnut.advertisement.IAdvertisementType;
|
||||
import com.chestnut.advertisement.domain.CmsAdvertisement;
|
||||
import com.chestnut.advertisement.pojo.dto.AdvertisementDTO;
|
||||
|
||||
/**
|
||||
* 广告数据管理Service
|
||||
*/
|
||||
public interface IAdvertisementService extends IService<CmsAdvertisement> {
|
||||
|
||||
/**
|
||||
* 广告<ID, NAME>缓存集合
|
||||
*
|
||||
* @return Map
|
||||
*/
|
||||
Map<String, String> getAdvertisementMap();
|
||||
|
||||
/**
|
||||
* 添加广告数据
|
||||
*
|
||||
* @param dto 广告数据DTO
|
||||
* @return CmsAdvertisement
|
||||
*/
|
||||
CmsAdvertisement addAdvertisement(AdvertisementDTO dto);
|
||||
|
||||
/**
|
||||
* 修改广告数据
|
||||
*
|
||||
* @param dto 广告数据DTO
|
||||
* @return CmsAdvertisement
|
||||
*/
|
||||
CmsAdvertisement saveAdvertisement(AdvertisementDTO dto);
|
||||
|
||||
/**
|
||||
* 删除广告数据
|
||||
*
|
||||
* @param advertisementIds 广告ID列表
|
||||
*/
|
||||
void deleteAdvertisement(List<Long> advertisementIds);
|
||||
|
||||
/**
|
||||
* 获取广告类型
|
||||
*
|
||||
* @param typeId 广告类型唯一标识
|
||||
* @return 广告类型实例
|
||||
*/
|
||||
IAdvertisementType getAdvertisementType(String typeId);
|
||||
|
||||
/**
|
||||
* 广告类型列表
|
||||
*
|
||||
* @return 广告类型实例列表
|
||||
*/
|
||||
List<IAdvertisementType> getAdvertisementTypeList();
|
||||
|
||||
/**
|
||||
* 启用广告
|
||||
*
|
||||
* @param advertisementIds 广告ID列表
|
||||
*/
|
||||
void enableAdvertisement(List<Long> advertisementIds, String operator);
|
||||
|
||||
/**
|
||||
* 停用广告
|
||||
*
|
||||
* @param advertisementIds 广告ID列表
|
||||
*/
|
||||
void disableAdvertisement(List<Long> advertisementIds, String operator);
|
||||
|
||||
/**
|
||||
* 获取广告点击统计地址
|
||||
*
|
||||
* @param adv 广告数据
|
||||
* @param publishPipeCode 发布通道编码
|
||||
* @return 广告点击统计地址
|
||||
*/
|
||||
String getAdvertisementStatLink(CmsAdvertisement adv, String publishPipeCode);
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package com.chestnut.advertisement.service;
|
||||
|
||||
import com.chestnut.advertisement.domain.CmsAdClickLog;
|
||||
import com.chestnut.advertisement.domain.CmsAdViewLog;
|
||||
|
||||
/**
|
||||
* 广告数据管理Service
|
||||
*/
|
||||
public interface IAdvertisementStatService {
|
||||
|
||||
/**
|
||||
* 广告点击
|
||||
*
|
||||
* @param advertisementIds
|
||||
* @return
|
||||
*/
|
||||
public void adClick(CmsAdClickLog log);
|
||||
|
||||
/**
|
||||
* 广告展现
|
||||
*
|
||||
* @param advertisementIds
|
||||
* @return
|
||||
*/
|
||||
public void adView(CmsAdViewLog log);
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package com.chestnut.advertisement.service.impl;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.chestnut.advertisement.domain.CmsAdHourStat;
|
||||
import com.chestnut.advertisement.mapper.CmsAdHourStatMapper;
|
||||
import com.chestnut.advertisement.service.IAdHourStatService;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 广告统计 服务实现类
|
||||
* </p>
|
||||
*
|
||||
* @author 兮玥
|
||||
* @email 190785909@qq.com
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class AdHourStatServiceImpl extends ServiceImpl<CmsAdHourStatMapper, CmsAdHourStat>
|
||||
implements IAdHourStatService {
|
||||
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
package com.chestnut.advertisement.service.impl;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.chestnut.contentcore.domain.CmsSite;
|
||||
import com.chestnut.contentcore.properties.SiteApiUrlProperty;
|
||||
import com.chestnut.contentcore.service.ISiteService;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.chestnut.advertisement.IAdvertisementType;
|
||||
import com.chestnut.advertisement.domain.CmsAdvertisement;
|
||||
import com.chestnut.advertisement.mapper.CmsAdvertisementMapper;
|
||||
import com.chestnut.advertisement.pojo.dto.AdvertisementDTO;
|
||||
import com.chestnut.advertisement.service.IAdvertisementService;
|
||||
import com.chestnut.common.exception.CommonErrorCode;
|
||||
import com.chestnut.common.redis.RedisCache;
|
||||
import com.chestnut.common.utils.Assert;
|
||||
import com.chestnut.common.utils.IdUtils;
|
||||
import com.chestnut.contentcore.config.CMSConfig;
|
||||
import com.chestnut.contentcore.domain.CmsPageWidget;
|
||||
import com.chestnut.contentcore.service.IPageWidgetService;
|
||||
import com.chestnut.system.fixed.dict.EnableOrDisable;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 广告数据服务实现类
|
||||
* </p>
|
||||
*
|
||||
* @author 兮玥
|
||||
* @email 190785909@qq.com
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class AdvertisementServiceImpl extends ServiceImpl<CmsAdvertisementMapper, CmsAdvertisement>
|
||||
implements IAdvertisementService {
|
||||
|
||||
private static final String CACHE_KEY_ADV_IDS = CMSConfig.CachePrefix + "adv-ids";
|
||||
|
||||
private final RedisCache redisCache;
|
||||
|
||||
private final Map<String, IAdvertisementType> advertisementTypes;
|
||||
|
||||
private final IPageWidgetService pageWidgetService;
|
||||
|
||||
private final ISiteService siteService;
|
||||
|
||||
@Override
|
||||
public IAdvertisementType getAdvertisementType(String typeId) {
|
||||
return this.advertisementTypes.get(IAdvertisementType.BEAN_NAME_PREFIX + typeId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IAdvertisementType> getAdvertisementTypeList() {
|
||||
return this.advertisementTypes.values().stream().toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getAdvertisementMap() {
|
||||
return this.redisCache.getCacheMap(CACHE_KEY_ADV_IDS,
|
||||
() -> this.lambdaQuery().select(List.of(CmsAdvertisement::getAdvertisementId, CmsAdvertisement::getName)).list()
|
||||
.stream().collect(
|
||||
Collectors.toMap(ad -> ad.getAdvertisementId().toString(), CmsAdvertisement::getName)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CmsAdvertisement addAdvertisement(AdvertisementDTO dto) {
|
||||
CmsPageWidget pageWidget = this.pageWidgetService.getById(dto.getAdSpaceId());
|
||||
Assert.notNull(pageWidget,
|
||||
() -> CommonErrorCode.DATA_NOT_FOUND_BY_ID.exception("adSpaceId", dto.getAdSpaceId()));
|
||||
|
||||
CmsAdvertisement advertisement = new CmsAdvertisement();
|
||||
BeanUtils.copyProperties(dto, advertisement);
|
||||
advertisement.setAdvertisementId(IdUtils.getSnowflakeId());
|
||||
advertisement.setSiteId(pageWidget.getSiteId());
|
||||
advertisement.setState(EnableOrDisable.ENABLE);
|
||||
advertisement.createBy(dto.getOperator().getUsername());
|
||||
this.save(advertisement);
|
||||
|
||||
this.redisCache.deleteObject(CACHE_KEY_ADV_IDS);
|
||||
return advertisement;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CmsAdvertisement saveAdvertisement(AdvertisementDTO dto) {
|
||||
CmsAdvertisement advertisement = this.getById(dto.getAdvertisementId());
|
||||
Assert.notNull(advertisement,
|
||||
() -> CommonErrorCode.DATA_NOT_FOUND_BY_ID.exception("advertisementId", dto.getAdvertisementId()));
|
||||
|
||||
BeanUtils.copyProperties(dto, advertisement, "adSpaceId");
|
||||
advertisement.updateBy(dto.getOperator().getUsername());
|
||||
this.updateById(advertisement);
|
||||
return advertisement;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteAdvertisement(List<Long> advertisementIds) {
|
||||
this.removeByIds(advertisementIds);
|
||||
this.redisCache.deleteObject(CACHE_KEY_ADV_IDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableAdvertisement(List<Long> advertisementIds, String operator) {
|
||||
List<CmsAdvertisement> list = this.listByIds(advertisementIds);
|
||||
for (CmsAdvertisement ad : list) {
|
||||
if (!ad.isEnable()) {
|
||||
ad.setState(EnableOrDisable.ENABLE);
|
||||
ad.updateBy(operator);
|
||||
}
|
||||
}
|
||||
this.updateBatchById(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableAdvertisement(List<Long> advertisementIds, String operator) {
|
||||
List<CmsAdvertisement> list = this.listByIds(advertisementIds);
|
||||
for (CmsAdvertisement ad : list) {
|
||||
if (ad.isEnable()) {
|
||||
ad.setState(EnableOrDisable.DISABLE);
|
||||
ad.updateBy(operator);
|
||||
}
|
||||
}
|
||||
this.updateBatchById(list);
|
||||
// todo 重新发布
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAdvertisementStatLink(CmsAdvertisement adv, String publishPipeCode) {
|
||||
CmsSite site = this.siteService.getSite(adv.getSiteId());
|
||||
String apiUrl = SiteApiUrlProperty.getValue(site, publishPipeCode);
|
||||
return apiUrl + "api/adv/redirect?sid=" + adv.getSiteId() + "&aid=" + adv.getAdvertisementId()
|
||||
+ "&url=" + URLEncoder.encode(adv.getRedirectUrl(), StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
package com.chestnut.advertisement.service.impl;
|
||||
|
||||
import com.chestnut.advertisement.domain.CmsAdClickLog;
|
||||
import com.chestnut.advertisement.domain.CmsAdViewLog;
|
||||
import com.chestnut.advertisement.mapper.CmsAdClickLogMapper;
|
||||
import com.chestnut.advertisement.mapper.CmsAdViewLogMapper;
|
||||
import com.chestnut.advertisement.service.IAdvertisementService;
|
||||
import com.chestnut.advertisement.service.IAdvertisementStatService;
|
||||
import com.chestnut.common.async.AsyncTaskManager;
|
||||
import com.chestnut.common.redis.RedisCache;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class AdvertisementStatServiceImpl implements IAdvertisementStatService {
|
||||
|
||||
public static final DateTimeFormatter DATE_TIME_FORMAT = DateTimeFormatter.ofPattern("yyyyMMddHH");
|
||||
|
||||
public static final String CLIC_CACHE_PREFIX = "adv:stat-click:";
|
||||
|
||||
public static final String VIEW_CACHE_PREFIX = "adv:stat-view:";
|
||||
|
||||
private final CmsAdClickLogMapper clickLogMapper;
|
||||
|
||||
private final CmsAdViewLogMapper viewLogMapper;
|
||||
|
||||
private final RedisCache redisCache;
|
||||
|
||||
private final IAdvertisementService advService;
|
||||
|
||||
private final AsyncTaskManager asyncTaskManager;
|
||||
|
||||
@Override
|
||||
public void adClick(CmsAdClickLog clickLog) {
|
||||
Map<String, String> advMap = advService.getAdvertisementMap();
|
||||
if (Objects.isNull(advMap) || Objects.isNull(clickLog.getAdId())
|
||||
|| !advMap.containsKey(clickLog.getAdId().toString())) {
|
||||
log.warn("Cms adv click log err, invalid id: " + clickLog.getAdId());
|
||||
return;
|
||||
}
|
||||
this.asyncTaskManager.execute(() -> {
|
||||
// redis 广告小时点击数+1
|
||||
String cacheKey = CLIC_CACHE_PREFIX + clickLog.getEvtTime().format(DATE_TIME_FORMAT);
|
||||
redisCache.zsetIncr(cacheKey, clickLog.getAdId().toString(), 1);
|
||||
// 记录点击日志
|
||||
this.clickLogMapper.insert(clickLog);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void adView(CmsAdViewLog viewLog) {
|
||||
Map<String, String> advMap = advService.getAdvertisementMap();
|
||||
if (Objects.isNull(advMap) || Objects.isNull(viewLog.getAdId())
|
||||
|| !advMap.containsKey(viewLog.getAdId().toString())) {
|
||||
log.warn("Cms adv view log err, invalid id: " + viewLog.getAdId());
|
||||
return;
|
||||
}
|
||||
this.asyncTaskManager.execute(() -> {
|
||||
// redis 广告日展现数+1
|
||||
String cacheKey = VIEW_CACHE_PREFIX + viewLog.getEvtTime().format(DATE_TIME_FORMAT);
|
||||
redisCache.zsetIncr(cacheKey, viewLog.getAdId().toString(), 1);
|
||||
// 记录展现日志
|
||||
this.viewLogMapper.insert(viewLog);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package com.chestnut.advertisement.stat;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.chestnut.stat.IStatType;
|
||||
import com.chestnut.stat.StatMenu;
|
||||
|
||||
@Component
|
||||
public class AdvertisementStatType implements IStatType {
|
||||
|
||||
private final static List<StatMenu> STAT_MENU = List.of(new StatMenu("CmsAdv", "", "{STAT.MENU.CmsAdv}", 2),
|
||||
new StatMenu("CmsAdStat", "CmsAdv", "{STAT.MENU.CmsAdStat}", 1),
|
||||
new StatMenu("CmsAdClickLog", "CmsAdv", "{STAT.MENU.CmsAdClickLog}", 2),
|
||||
new StatMenu("CmsAdViewLog", "CmsAdv", "{STAT.MENU.CmsAdViewLog}", 3));
|
||||
|
||||
@Override
|
||||
public List<StatMenu> getStatMenus() {
|
||||
return STAT_MENU;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
package com.chestnut.advertisement.template.tag;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.chestnut.common.staticize.tag.TagAttrOption;
|
||||
import com.chestnut.contentcore.fixed.config.SiteApiUrl;
|
||||
import com.chestnut.contentcore.properties.SiteApiUrlProperty;
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.chestnut.advertisement.domain.CmsAdvertisement;
|
||||
import com.chestnut.advertisement.pojo.vo.AdvertisementVO;
|
||||
import com.chestnut.advertisement.service.IAdvertisementService;
|
||||
import com.chestnut.common.staticize.FreeMarkerUtils;
|
||||
import com.chestnut.common.staticize.core.TemplateContext;
|
||||
import com.chestnut.common.staticize.enums.TagAttrDataType;
|
||||
import com.chestnut.common.staticize.tag.AbstractListTag;
|
||||
import com.chestnut.common.staticize.tag.TagAttr;
|
||||
import com.chestnut.common.utils.StringUtils;
|
||||
import com.chestnut.contentcore.domain.CmsPageWidget;
|
||||
import com.chestnut.contentcore.service.IPageWidgetService;
|
||||
import com.chestnut.system.fixed.dict.EnableOrDisable;
|
||||
|
||||
import freemarker.core.Environment;
|
||||
import freemarker.template.TemplateException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Component
|
||||
public class CmsAdvertisementTag extends AbstractListTag {
|
||||
|
||||
public final static String TAG_NAME = "cms_advertisement";
|
||||
public final static String NAME = "{FREEMARKER.TAG.NAME." + TAG_NAME + "}";
|
||||
public final static String DESC = "{FREEMARKER.TAG.DESC." + TAG_NAME + "}";
|
||||
|
||||
final static String TagAttr_Code = "code";
|
||||
final static String TagAttr_RedirectType = "type";
|
||||
|
||||
private final IAdvertisementService advertisementService;
|
||||
|
||||
private final IPageWidgetService pageWidgetService;
|
||||
|
||||
@Override
|
||||
public List<TagAttr> getTagAttrs() {
|
||||
List<TagAttr> tagAttrs = super.getTagAttrs();
|
||||
tagAttrs.add(new TagAttr(TagAttr_Code, true, TagAttrDataType.STRING, "广告位编码"));
|
||||
tagAttrs.add(new TagAttr(TagAttr_RedirectType, false, TagAttrDataType.STRING, "广告跳转方式",
|
||||
RedirectType.toTagAttrOptions(), RedirectType.None.name()));
|
||||
return tagAttrs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TagPageData prepareData(Environment env, Map<String, String> attrs, boolean page, int size, int pageIndex) throws TemplateException {
|
||||
String code = MapUtils.getString(attrs, TagAttr_Code);
|
||||
String redirectType = MapUtils.getString(attrs, TagAttr_RedirectType, RedirectType.None.name());
|
||||
|
||||
Long siteId = FreeMarkerUtils.evalLongVariable(env, "Site.siteId");
|
||||
CmsPageWidget adSpace = this.pageWidgetService.getOne(new LambdaQueryWrapper<CmsPageWidget>()
|
||||
.eq(CmsPageWidget::getSiteId, siteId)
|
||||
.eq(CmsPageWidget::getCode, code));
|
||||
if (adSpace == null) {
|
||||
throw new TemplateException(StringUtils.messageFormat("<@{0}>AD place `{1}` not exists.", this.getTagName(), code), env) ;
|
||||
}
|
||||
String condition = MapUtils.getString(attrs, TagAttr.AttrName_Condition);
|
||||
|
||||
LambdaQueryWrapper<CmsAdvertisement> q = new LambdaQueryWrapper<CmsAdvertisement>()
|
||||
.eq(CmsAdvertisement::getAdSpaceId, adSpace.getPageWidgetId())
|
||||
.eq(CmsAdvertisement::getState, EnableOrDisable.ENABLE);
|
||||
q.apply(StringUtils.isNotEmpty(condition), condition);
|
||||
Page<CmsAdvertisement> pageResult = this.advertisementService.page(new Page<>(pageIndex, size, page), q);
|
||||
if (pageIndex > 1 & pageResult.getRecords().size() == 0) {
|
||||
throw new TemplateException(StringUtils.messageFormat("Page data empty: pageIndex = {0}", pageIndex), env) ;
|
||||
}
|
||||
TemplateContext context = FreeMarkerUtils.getTemplateContext(env);
|
||||
List<AdvertisementVO> list = pageResult.getRecords().stream().map(ad ->{
|
||||
AdvertisementVO vo = new AdvertisementVO(ad);
|
||||
if (RedirectType.isStat(redirectType)) {
|
||||
vo.setLink(this.advertisementService.getAdvertisementStatLink(ad, context.getPublishPipeCode()));
|
||||
} else {
|
||||
vo.setLink(vo.getRedirectUrl());
|
||||
}
|
||||
return vo;
|
||||
}).toList();
|
||||
return TagPageData.of(list, pageResult.getTotal());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTagName() {
|
||||
return TAG_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return DESC;
|
||||
}
|
||||
|
||||
private enum RedirectType {
|
||||
None("原始链接"),
|
||||
Stat("统计链接");
|
||||
|
||||
private final String desc;
|
||||
|
||||
RedirectType(String desc) {
|
||||
this.desc = desc;
|
||||
}
|
||||
|
||||
static boolean isStat(String level) {
|
||||
return Stat.name().equalsIgnoreCase(level);
|
||||
}
|
||||
|
||||
static List<TagAttrOption> toTagAttrOptions() {
|
||||
return List.of(
|
||||
new TagAttrOption(None.name(), None.desc),
|
||||
new TagAttrOption(Stat.name(), Stat.desc)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
# 页面部件类型
|
||||
CMS.CONTENCORE.PAGEWIDGET.ads=广告位
|
||||
|
||||
# 广告类型
|
||||
ADVERTISEMENT.TYPE.image=图片
|
||||
|
||||
# 模板freemarker
|
||||
FREEMARKER.TAG.NAME.cms_advertisement=广告列表标签
|
||||
FREEMARKER.TAG.DESC.cms_advertisement=获取广告数据列表,内嵌<#list DataList as ad>${ad.name}</#list>遍历数据
|
||||
|
||||
# 统计菜单
|
||||
STAT.MENU.CmsAdv=广告数据统计
|
||||
STAT.MENU.CmsAdStat=广告综合统计
|
||||
STAT.MENU.CmsAdClickLog=广告点击日志
|
||||
STAT.MENU.CmsAdViewLog=广告展现日志
|
||||
|
||||
# 定时任务
|
||||
SCHEDULED_TASK.AdvertisementStatJob=广告统计任务
|
||||
SCHEDULED_TASK.AdvertisementPublishJob=广告定时发布下线任务
|
|
@ -0,0 +1,19 @@
|
|||
# 页面部件类型
|
||||
CMS.CONTENCORE.PAGEWIDGET.ads=AD
|
||||
|
||||
# 广告类型
|
||||
ADVERTISEMENT.TYPE.image=Image
|
||||
|
||||
# 模板freemarker
|
||||
FREEMARKER.TAG.NAME.cms_advertisement=Advertisement list tag
|
||||
FREEMARKER.TAG.DESC.cms_advertisement=Fetch advertising data list, use <#list> in tag like "<#list DataList as ad>${ad.name}</#list>" to walk through the list of ad.
|
||||
|
||||
# 统计菜单
|
||||
STAT.MENU.CmsAdv=Advertising Statistics
|
||||
STAT.MENU.CmsAdStat=Overview
|
||||
STAT.MENU.CmsAdClickLog=Click Logs
|
||||
STAT.MENU.CmsAdViewLog=View Logs
|
||||
|
||||
# 定时任务
|
||||
SCHEDULED_TASK.AdvertisementStatJob=AD Statistics Task
|
||||
SCHEDULED_TASK.AdvertisementPublishJob=AD Publish/Offline Task
|
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0"?>
|
||||
<project
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-cms</artifactId>
|
||||
<version>1.3.21</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>chestnut-cms-article</artifactId>
|
||||
<description>文章内容类型扩展模块</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- 内容核心 -->
|
||||
<dependency>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-cms-contentcore</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
|
@ -0,0 +1,5 @@
|
|||
package com.chestnut.article;
|
||||
|
||||
public class ArticleConsts {
|
||||
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
package com.chestnut.article;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
|
||||
import com.chestnut.article.domain.CmsArticleDetail;
|
||||
import com.chestnut.article.properties.AutoArticleLogo;
|
||||
import com.chestnut.article.service.IArticleService;
|
||||
import com.chestnut.article.service.impl.ArticleServiceImpl;
|
||||
import com.chestnut.common.async.AsyncTaskManager;
|
||||
import com.chestnut.common.utils.HtmlUtils;
|
||||
import com.chestnut.common.utils.SpringUtils;
|
||||
import com.chestnut.common.utils.StringUtils;
|
||||
import com.chestnut.contentcore.core.AbstractContent;
|
||||
import com.chestnut.contentcore.domain.CmsCatalog;
|
||||
import com.chestnut.system.fixed.dict.YesOrNo;
|
||||
|
||||
public class ArticleContent extends AbstractContent<CmsArticleDetail> {
|
||||
|
||||
private IArticleService articleService;
|
||||
|
||||
@Override
|
||||
public Long add() {
|
||||
super.add();
|
||||
if (!this.hasExtendEntity()) {
|
||||
this.getContentService().save(this.getContentEntity());
|
||||
return this.getContentEntity().getContentId();
|
||||
}
|
||||
CmsArticleDetail articleDetail = this.getExtendEntity();
|
||||
articleDetail.setContentId(this.getContentEntity().getContentId());
|
||||
articleDetail.setSiteId(this.getContentEntity().getSiteId());
|
||||
// 处理内部链接
|
||||
String contentHtml = this.getArticleService().saveInternalUrl(articleDetail.getContentHtml());
|
||||
// 处理文章正文远程图片
|
||||
if (YesOrNo.isYes(articleDetail.getDownloadRemoteImage())) {
|
||||
AsyncTaskManager.setTaskPercent(90);
|
||||
contentHtml = this.getArticleService().downloadRemoteImages(contentHtml, this.getSite(),
|
||||
this.getOperator().getUsername());
|
||||
}
|
||||
articleDetail.setContentHtml(contentHtml);
|
||||
// 正文首图作为logo
|
||||
if (StringUtils.isEmpty(this.getContentEntity().getLogo())
|
||||
&& AutoArticleLogo.getValue(this.getSite().getConfigProps())) {
|
||||
this.getContentEntity().setLogo(this.getFirstImage(articleDetail.getContentHtml()));
|
||||
}
|
||||
this.getContentService().save(this.getContentEntity());
|
||||
this.getArticleService().save(articleDetail);
|
||||
return this.getContentEntity().getContentId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long save() {
|
||||
super.save();
|
||||
// 非映射内容或标题内容修改文章详情
|
||||
if (!this.hasExtendEntity()) {
|
||||
this.getContentService().updateById(this.getContentEntity());
|
||||
return this.getContentEntity().getContentId();
|
||||
}
|
||||
CmsArticleDetail articleDetail = this.getExtendEntity();
|
||||
// 处理内部链接
|
||||
String contentHtml = this.getArticleService().saveInternalUrl(articleDetail.getContentHtml());
|
||||
// 处理文章正文远程图片
|
||||
if (YesOrNo.isYes(articleDetail.getDownloadRemoteImage())) {
|
||||
AsyncTaskManager.setTaskPercent(90);
|
||||
contentHtml = this.getArticleService().downloadRemoteImages(contentHtml, this.getSite(),
|
||||
this.getOperator().getUsername());
|
||||
}
|
||||
articleDetail.setContentHtml(contentHtml);
|
||||
// 正文首图作为logo
|
||||
if (StringUtils.isEmpty(this.getContentEntity().getLogo())
|
||||
&& AutoArticleLogo.getValue(this.getSite().getConfigProps())) {
|
||||
this.getContentEntity().setLogo(this.getFirstImage(articleDetail.getContentHtml()));
|
||||
}
|
||||
this.getContentService().updateById(this.getContentEntity());
|
||||
this.getArticleService().updateById(articleDetail);
|
||||
return this.getContentEntity().getContentId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文章正文第一张图片地址
|
||||
*/
|
||||
private String getFirstImage(String contentHtml) {
|
||||
if (StringUtils.isEmpty(contentHtml)) {
|
||||
return contentHtml;
|
||||
}
|
||||
Matcher matcher = ArticleServiceImpl.ImgHtmlTagPattern.matcher(contentHtml);
|
||||
if (matcher.find()) {
|
||||
String imgSrc = matcher.group(1);
|
||||
if (StringUtils.isNotEmpty(imgSrc)) {
|
||||
return imgSrc;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete() {
|
||||
super.delete();
|
||||
if (this.hasExtendEntity()) {
|
||||
this.getArticleService().removeById(this.getContentEntity().getContentId());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyTo(CmsCatalog toCatalog, Integer copyType) {
|
||||
super.copyTo(toCatalog, copyType);
|
||||
if (this.hasExtendEntity()) {
|
||||
Long newContentId = (Long) this.getParams().get("NewContentId");
|
||||
CmsArticleDetail newArticleDetail = new CmsArticleDetail();
|
||||
BeanUtils.copyProperties(this.getExtendEntity(), newArticleDetail, "contentId");
|
||||
newArticleDetail.setContentId(newContentId);
|
||||
this.getArticleService().save(newArticleDetail);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFullText() {
|
||||
return super.getFullText() + StringUtils.SPACE + HtmlUtils.clean(this.getExtendEntity().getContentHtml());
|
||||
}
|
||||
|
||||
public IArticleService getArticleService() {
|
||||
if (this.articleService == null) {
|
||||
this.articleService = SpringUtils.getBean(IArticleService.class);
|
||||
}
|
||||
return this.articleService;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,163 @@
|
|||
package com.chestnut.article;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.chestnut.article.domain.CmsArticleDetail;
|
||||
import com.chestnut.article.domain.dto.ArticleDTO;
|
||||
import com.chestnut.article.domain.vo.ArticleVO;
|
||||
import com.chestnut.article.mapper.CmsArticleDetailMapper;
|
||||
import com.chestnut.common.exception.CommonErrorCode;
|
||||
import com.chestnut.common.utils.Assert;
|
||||
import com.chestnut.common.utils.IdUtils;
|
||||
import com.chestnut.common.utils.JacksonUtils;
|
||||
import com.chestnut.common.utils.StringUtils;
|
||||
import com.chestnut.contentcore.core.IContent;
|
||||
import com.chestnut.contentcore.core.IContentType;
|
||||
import com.chestnut.contentcore.core.IPublishPipeProp.PublishPipePropUseType;
|
||||
import com.chestnut.contentcore.domain.CmsCatalog;
|
||||
import com.chestnut.contentcore.domain.CmsContent;
|
||||
import com.chestnut.contentcore.domain.CmsPublishPipe;
|
||||
import com.chestnut.contentcore.domain.dto.PublishPipeProp;
|
||||
import com.chestnut.contentcore.domain.vo.ContentVO;
|
||||
import com.chestnut.contentcore.enums.ContentOpType;
|
||||
import com.chestnut.contentcore.fixed.dict.ContentAttribute;
|
||||
import com.chestnut.contentcore.mapper.CmsContentMapper;
|
||||
import com.chestnut.contentcore.service.ICatalogService;
|
||||
import com.chestnut.contentcore.service.IPublishPipeService;
|
||||
import com.chestnut.contentcore.util.InternalUrlUtils;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@Component(IContentType.BEAN_NAME_PREFIX + ArticleContentType.ID)
|
||||
@RequiredArgsConstructor
|
||||
public class ArticleContentType implements IContentType {
|
||||
|
||||
public final static String ID = "article";
|
||||
|
||||
private final static String NAME = "{CMS.CONTENTCORE.CONTENT_TYPE." + ID + "}";
|
||||
|
||||
private final CmsContentMapper contentMapper;
|
||||
|
||||
private final CmsArticleDetailMapper articleMapper;
|
||||
|
||||
private final ICatalogService catalogService;
|
||||
|
||||
private final IPublishPipeService publishPipeService;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getComponent() {
|
||||
return "cms/article/editor";
|
||||
}
|
||||
|
||||
@Override
|
||||
public IContent<?> newContent() {
|
||||
return new ArticleContent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IContent<?> loadContent(CmsContent xContent) {
|
||||
ArticleContent articleContent = new ArticleContent();
|
||||
articleContent.setContentEntity(xContent);
|
||||
CmsArticleDetail articleDetail = this.articleMapper.selectById(xContent.getContentId());
|
||||
articleContent.setExtendEntity(articleDetail);
|
||||
return articleContent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IContent<?> readRequest(HttpServletRequest request) throws IOException {
|
||||
ArticleDTO dto = JacksonUtils.from(request.getInputStream(), ArticleDTO.class);
|
||||
|
||||
CmsContent contentEntity;
|
||||
if (dto.getOpType() == ContentOpType.UPDATE) {
|
||||
contentEntity = this.contentMapper.selectById(dto.getContentId());
|
||||
Assert.notNull(contentEntity,
|
||||
() -> CommonErrorCode.DATA_NOT_FOUND_BY_ID.exception("contentId", dto.getContentId()));
|
||||
} else {
|
||||
contentEntity = new CmsContent();
|
||||
}
|
||||
BeanUtils.copyProperties(dto, contentEntity);
|
||||
CmsCatalog catalog = this.catalogService.getCatalog(dto.getCatalogId());
|
||||
contentEntity.setSiteId(catalog.getSiteId());
|
||||
contentEntity.setAttributes(ContentAttribute.convertInt(dto.getAttributes()));
|
||||
// 发布通道配置
|
||||
Map<String, Map<String, Object>> publishPipProps = new HashMap<>();
|
||||
dto.getPublishPipeProps().forEach(prop -> {
|
||||
publishPipProps.put(prop.getPipeCode(), prop.getProps());
|
||||
});
|
||||
contentEntity.setPublishPipeProps(publishPipProps);
|
||||
|
||||
CmsArticleDetail extendEntity = new CmsArticleDetail();
|
||||
BeanUtils.copyProperties(dto, extendEntity);
|
||||
|
||||
ArticleContent content = new ArticleContent();
|
||||
content.setContentEntity(contentEntity);
|
||||
content.setExtendEntity(extendEntity);
|
||||
content.setParams(dto.getParams());
|
||||
if (content.hasExtendEntity() && StringUtils.isEmpty(extendEntity.getContentHtml())) {
|
||||
throw CommonErrorCode.NOT_EMPTY.exception("contentHtml");
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContentVO initEditor(Long catalogId, Long contentId) {
|
||||
CmsCatalog catalog = this.catalogService.getCatalog(catalogId);
|
||||
Assert.notNull(catalog, () -> CommonErrorCode.DATA_NOT_FOUND_BY_ID.exception("catalogId", catalogId));
|
||||
List<CmsPublishPipe> publishPipes = this.publishPipeService.getPublishPipes(catalog.getSiteId());
|
||||
ArticleVO vo;
|
||||
if (IdUtils.validate(contentId)) {
|
||||
CmsContent contentEntity = this.contentMapper.selectById(contentId);
|
||||
Assert.notNull(contentEntity, () -> CommonErrorCode.DATA_NOT_FOUND_BY_ID.exception("contentId", contentId));
|
||||
|
||||
CmsArticleDetail extendEntity = this.articleMapper.selectById(contentId);
|
||||
vo = ArticleVO.newInstance(contentEntity, extendEntity);
|
||||
if (StringUtils.isNotEmpty(vo.getLogo())) {
|
||||
vo.setLogoSrc(InternalUrlUtils.getActualPreviewUrl(vo.getLogo()));
|
||||
}
|
||||
// 发布通道模板数据
|
||||
List<PublishPipeProp> publishPipeProps = this.publishPipeService.getPublishPipeProps(catalog.getSiteId(),
|
||||
PublishPipePropUseType.Content, contentEntity.getPublishPipeProps());
|
||||
vo.setPublishPipeProps(publishPipeProps);
|
||||
} else {
|
||||
vo = new ArticleVO();
|
||||
vo.setContentId(IdUtils.getSnowflakeId());
|
||||
vo.setCatalogId(catalog.getCatalogId());
|
||||
vo.setContentType(ID);
|
||||
// 发布通道初始数据
|
||||
vo.setPublishPipe(publishPipes.stream().map(CmsPublishPipe::getCode).toArray(String[]::new));
|
||||
// 发布通道模板数据
|
||||
List<PublishPipeProp> publishPipeProps = this.publishPipeService.getPublishPipeProps(catalog.getSiteId(),
|
||||
PublishPipePropUseType.Content, null);
|
||||
vo.setPublishPipeProps(publishPipeProps);
|
||||
}
|
||||
vo.setCatalogName(catalog.getName());
|
||||
return vo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recover(Long contentId) {
|
||||
this.articleMapper.recoverById(contentId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteBackups(Long contentId) {
|
||||
this.articleMapper.deleteLogicDeletedById(contentId);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
package com.chestnut.article;
|
||||
|
||||
import com.chestnut.common.async.AsyncTaskManager;
|
||||
import com.chestnut.common.utils.SpringUtils;
|
||||
import com.chestnut.common.utils.StringUtils;
|
||||
import com.chestnut.contentcore.core.IInternalDataType;
|
||||
import com.chestnut.contentcore.core.impl.InternalDataType_PageWidget;
|
||||
import com.chestnut.contentcore.domain.CmsCatalog;
|
||||
import com.chestnut.contentcore.domain.CmsContent;
|
||||
import com.chestnut.contentcore.domain.CmsPageWidget;
|
||||
import com.chestnut.contentcore.domain.CmsSite;
|
||||
import com.chestnut.contentcore.properties.EnableSSIProperty;
|
||||
import com.chestnut.contentcore.service.*;
|
||||
import com.chestnut.contentcore.template.tag.CmsIncludeTag;
|
||||
import com.chestnut.contentcore.util.ContentCoreUtils;
|
||||
import com.chestnut.contentcore.util.ContentUtils;
|
||||
import com.chestnut.contentcore.util.PageWidgetUtils;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class ArticleUtils {
|
||||
|
||||
private static final ISiteService siteService = SpringUtils.getBean(ISiteService.class);
|
||||
|
||||
private static final ICatalogService catalogService = SpringUtils.getBean(ICatalogService.class);
|
||||
|
||||
private static final IContentService contentService = SpringUtils.getBean(IContentService.class);
|
||||
|
||||
private static final IPageWidgetService pageWidgetService = SpringUtils.getBean(IPageWidgetService.class);
|
||||
|
||||
private static final IPublishService publishService = SpringUtils.getBean(IPublishService.class);
|
||||
|
||||
|
||||
/**
|
||||
* 内容扩展模板内容占位符标签匹配
|
||||
*/
|
||||
public static final Pattern ContentExPlaceholderTagPattern = Pattern.compile("<img[^>]+ex_cid\\s*=\\s*['\"]([^'\"]+)['\"][^>]*>",
|
||||
Pattern.CASE_INSENSITIVE | Pattern.DOTALL | Pattern.MULTILINE);
|
||||
|
||||
/**
|
||||
* 替换文章正文内容扩展模板占位符,需要在处理图片链接前处理
|
||||
*
|
||||
* 占位符:<img src="/UEditorPlus/themes/default/images/spacer.gif" ex_cid="{content.id}" title="{content.title}" class="img_group_placeholder" />
|
||||
* 替换为ssi引用标签
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static String dealContentEx(String articleBody, String publishPipeCode, boolean isPreview) {
|
||||
if (StringUtils.isBlank(articleBody)) {
|
||||
return articleBody;
|
||||
}
|
||||
Matcher matcher = ContentExPlaceholderTagPattern.matcher(articleBody);
|
||||
int lastEndIndex = 0;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
while (matcher.find(lastEndIndex)) {
|
||||
int s = matcher.start();
|
||||
sb.append(articleBody.substring(lastEndIndex, s));
|
||||
|
||||
String placeholderImgTag = matcher.group();
|
||||
String contentId = matcher.group(1);
|
||||
try {
|
||||
CmsContent _content = contentService.getById(contentId);
|
||||
if (_content != null) {
|
||||
CmsSite site = siteService.getSite(_content.getSiteId());
|
||||
CmsCatalog catalog = catalogService.getCatalog(_content.getCatalogId());
|
||||
if (isPreview) {
|
||||
// 获取预览内容
|
||||
placeholderImgTag = publishService.getContentExPageData(_content, publishPipeCode, true);
|
||||
} else {
|
||||
boolean ssiEnabled = EnableSSIProperty.getValue(site.getConfigProps());
|
||||
if (catalog.isStaticize() && ssiEnabled) {
|
||||
String staticFilePath = ContentUtils.getContentExPath(site, catalog, _content, publishPipeCode);
|
||||
placeholderImgTag = StringUtils.messageFormat(CmsIncludeTag.SSI_INCLUDE_TAG, "/" + staticFilePath);
|
||||
} else {
|
||||
// 获取浏览内容
|
||||
placeholderImgTag = publishService.getContentExPageData(_content, publishPipeCode, false);;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
placeholderImgTag = StringUtils.EMPTY;
|
||||
}
|
||||
} catch (Exception e1) {
|
||||
AsyncTaskManager.addErrMessage("Replace content-ex placeholder failed: " + contentId);
|
||||
}
|
||||
sb.append(placeholderImgTag);
|
||||
lastEndIndex = matcher.end();
|
||||
}
|
||||
sb.append(articleBody.substring(lastEndIndex));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 页面部件占位符标签匹配
|
||||
*/
|
||||
public static final Pattern PageWidgetImgHtmlTagPattern = Pattern.compile("<img[^>]+pw_code\\s*=\\s*['\"]([^'\"]+)['\"][^>]*>",
|
||||
Pattern.CASE_INSENSITIVE | Pattern.DOTALL | Pattern.MULTILINE);
|
||||
|
||||
/**
|
||||
* 替换文章正文页面部件占位符,需要在处理图片链接前处理
|
||||
*
|
||||
* 占位符:<img src="/UEditorPlus/themes/default/images/spacer.gif" pw_code="{pageWidget.code}" title="{pageWidget.name}" class="pw_placeholder" />
|
||||
* 替换为ssi引用标签
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static String dealPageWidget(CmsContent content, String articleBody, boolean isPreview) {
|
||||
if (StringUtils.isBlank(articleBody)) {
|
||||
return articleBody;
|
||||
}
|
||||
CmsSite site = siteService.getSite(content.getSiteId());
|
||||
CmsCatalog catalog = catalogService.getCatalog(content.getCatalogId());
|
||||
Matcher matcher = PageWidgetImgHtmlTagPattern.matcher(articleBody);
|
||||
int lastEndIndex = 0;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
while (matcher.find(lastEndIndex)) {
|
||||
int s = matcher.start();
|
||||
sb.append(articleBody.substring(lastEndIndex, s));
|
||||
|
||||
String placeholderImgTag = matcher.group();
|
||||
String pageWidgetCode = matcher.group(1);
|
||||
try {
|
||||
CmsPageWidget pw = pageWidgetService.getPageWidget(site.getSiteId(), pageWidgetCode);
|
||||
if (pw != null) {
|
||||
if (isPreview) {
|
||||
IInternalDataType internalDataType = ContentCoreUtils.getInternalDataType(InternalDataType_PageWidget.ID);
|
||||
String pageData = internalDataType.getPageData(new IInternalDataType.RequestData(pw.getPageWidgetId(),
|
||||
1, pw.getPublishPipeCode(), true, null));
|
||||
placeholderImgTag = pageData;
|
||||
} else {
|
||||
boolean ssiEnabled = EnableSSIProperty.getValue(site.getConfigProps());
|
||||
if (catalog.isStaticize() && ssiEnabled) {
|
||||
String staticFileName = PageWidgetUtils.getStaticFileName(pw, site.getStaticSuffix(pw.getPublishPipeCode()));
|
||||
String staticFilePath = pw.getPath() + staticFileName;
|
||||
placeholderImgTag = StringUtils.messageFormat(CmsIncludeTag.SSI_INCLUDE_TAG, "/" + staticFilePath);
|
||||
} else {
|
||||
IInternalDataType internalDataType = ContentCoreUtils.getInternalDataType(InternalDataType_PageWidget.ID);
|
||||
String pageData = internalDataType.getPageData(new IInternalDataType.RequestData(pw.getPageWidgetId(),
|
||||
1, pw.getPublishPipeCode(), false, null));
|
||||
placeholderImgTag = pageData;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e1) {
|
||||
AsyncTaskManager.addErrMessage("Replace page widget placeholder failed: " + pageWidgetCode);
|
||||
}
|
||||
sb.append(placeholderImgTag);
|
||||
lastEndIndex = matcher.end();
|
||||
}
|
||||
sb.append(articleBody.substring(lastEndIndex));
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package com.chestnut.article;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.chestnut.contentcore.core.IPublishPipeProp;
|
||||
|
||||
@Component
|
||||
public class PublishPipeProp_ArticleDetailTemplate implements IPublishPipeProp {
|
||||
|
||||
public static final String KEY = DetailTemplatePropPrefix + ArticleContentType.ID;
|
||||
|
||||
@Override
|
||||
public String getKey() {
|
||||
return KEY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "文章详情页模板";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PublishPipePropUseType> getUseTypes() {
|
||||
return List.of(PublishPipePropUseType.Catalog);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package com.chestnut.article;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.chestnut.contentcore.core.IPublishPipeProp;
|
||||
|
||||
@Component
|
||||
public class PublishPipeProp_DefaultArticleDetailTemplate implements IPublishPipeProp {
|
||||
|
||||
public static final String KEY = DefaultDetailTemplatePropPrefix + ArticleContentType.ID;
|
||||
|
||||
@Override
|
||||
public String getKey() {
|
||||
return KEY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "文章详情页默认模板";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PublishPipePropUseType> getUseTypes() {
|
||||
return List.of(PublishPipePropUseType.Site);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package com.chestnut.article.controller;
|
||||
|
||||
|
||||
import com.chestnut.common.security.anno.Priv;
|
||||
import com.chestnut.common.security.web.BaseRestController;
|
||||
import com.chestnut.system.security.AdminUserType;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 前端控制器
|
||||
* </p>
|
||||
*
|
||||
* @author 兮玥
|
||||
* @email 190785909@qq.com
|
||||
*/
|
||||
@Priv(type = AdminUserType.TYPE)
|
||||
@RestController
|
||||
@RequestMapping("/cms/article")
|
||||
public class ArticleController extends BaseRestController {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
package com.chestnut.article.controller.front;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.chestnut.article.domain.CmsArticleDetail;
|
||||
import com.chestnut.article.domain.vo.ArticleApiVO;
|
||||
import com.chestnut.article.service.IArticleService;
|
||||
import com.chestnut.common.domain.R;
|
||||
import com.chestnut.common.security.web.BaseRestController;
|
||||
import com.chestnut.common.utils.IdUtils;
|
||||
import com.chestnut.common.utils.StringUtils;
|
||||
import com.chestnut.contentcore.domain.CmsCatalog;
|
||||
import com.chestnut.contentcore.domain.CmsContent;
|
||||
import com.chestnut.contentcore.domain.vo.ContentApiVO;
|
||||
import com.chestnut.contentcore.domain.vo.ContentDynamicDataVO;
|
||||
import com.chestnut.contentcore.domain.vo.ContentVO;
|
||||
import com.chestnut.contentcore.fixed.dict.ContentAttribute;
|
||||
import com.chestnut.contentcore.fixed.dict.ContentStatus;
|
||||
import com.chestnut.contentcore.service.ICatalogService;
|
||||
import com.chestnut.contentcore.service.IContentService;
|
||||
import com.chestnut.contentcore.service.impl.ContentDynamicDataService;
|
||||
import com.chestnut.contentcore.util.CatalogUtils;
|
||||
import com.chestnut.contentcore.util.InternalUrlUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* 内容数据API接口
|
||||
*
|
||||
* @author 兮玥
|
||||
* @email 190785909@qq.com
|
||||
*/
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping("/api/cms/article")
|
||||
public class ArticleApiController extends BaseRestController {
|
||||
|
||||
private final ICatalogService catalogService;
|
||||
|
||||
private final IContentService contentService;
|
||||
|
||||
private final IArticleService articleService;
|
||||
|
||||
@GetMapping("/list")
|
||||
public R<List<ArticleApiVO>> getContentList(
|
||||
@RequestParam("sid") Long siteId,
|
||||
@RequestParam(value = "cid", required = false, defaultValue = "0") Long catalogId,
|
||||
@RequestParam(value = "lv", required = false, defaultValue = "Root") String level,
|
||||
@RequestParam(value = "attrs", required = false) String hasAttributes,
|
||||
@RequestParam(value = "no_attrs", required = false) String noAttributes,
|
||||
@RequestParam(value = "st", required = false, defaultValue = "Recent") String sortType,
|
||||
@RequestParam(value = "ps", required = false, defaultValue = "16") Integer pageSize,
|
||||
@RequestParam(value = "pn", required = false, defaultValue = "1") Long pageNumber,
|
||||
@RequestParam(value = "pp") String publishPipeCode,
|
||||
@RequestParam(value = "preview", required = false, defaultValue = "false") Boolean preview,
|
||||
@RequestParam(value = "text", required = false, defaultValue = "false") Boolean text
|
||||
) {
|
||||
if (!"Root".equalsIgnoreCase(level) && !IdUtils.validate(catalogId)) {
|
||||
return R.fail("The parameter cid is required where lv is `Root`.");
|
||||
}
|
||||
LambdaQueryWrapper<CmsContent> q = new LambdaQueryWrapper<>();
|
||||
q.eq(CmsContent::getSiteId, siteId).eq(CmsContent::getStatus, ContentStatus.PUBLISHED);
|
||||
if (!"Root".equalsIgnoreCase(level)) {
|
||||
CmsCatalog catalog = this.catalogService.getCatalog(catalogId);
|
||||
if (Objects.isNull(catalog)) {
|
||||
return R.fail("Catalog not found: " + catalogId);
|
||||
}
|
||||
if ("Current".equalsIgnoreCase(level)) {
|
||||
q.eq(CmsContent::getCatalogId, catalog.getCatalogId());
|
||||
} else if ("Child".equalsIgnoreCase(level)) {
|
||||
q.likeRight(CmsContent::getCatalogAncestors, catalog.getAncestors() + CatalogUtils.ANCESTORS_SPLITER);
|
||||
} else if ("CurrentAndChild".equalsIgnoreCase(level)) {
|
||||
q.likeRight(CmsContent::getCatalogAncestors, catalog.getAncestors());
|
||||
}
|
||||
}
|
||||
if (StringUtils.isNotEmpty(hasAttributes)) {
|
||||
int attrTotal = ContentAttribute.convertInt(hasAttributes.split(","));
|
||||
q.apply(attrTotal > 0, "attributes&{0}={1}", attrTotal, attrTotal);
|
||||
}
|
||||
if (StringUtils.isNotEmpty(noAttributes)) {
|
||||
String[] contentAttrs = noAttributes.split(",");
|
||||
int attrTotal = ContentAttribute.convertInt(contentAttrs);
|
||||
for (String attr : contentAttrs) {
|
||||
int bit = ContentAttribute.bit(attr);
|
||||
q.apply(bit > 0, "attributes&{0}<>{1}", attrTotal, bit);
|
||||
}
|
||||
}
|
||||
if ("Recent".equalsIgnoreCase(sortType)) {
|
||||
q.orderByDesc(CmsContent::getPublishDate);
|
||||
} else {
|
||||
q.orderByDesc(Arrays.asList(CmsContent::getTopFlag, CmsContent::getSortFlag));
|
||||
}
|
||||
|
||||
Page<CmsContent> pageResult = this.contentService.page(new Page<>(pageNumber, pageSize, false), q);
|
||||
if (pageResult.getRecords().isEmpty()) {
|
||||
return R.ok(List.of());
|
||||
}
|
||||
List<Long> contentIds = pageResult.getRecords().stream().map(CmsContent::getContentId).toList();
|
||||
Map<Long, CmsArticleDetail> articleDetails = this.articleService.listByIds(contentIds)
|
||||
.stream().collect(Collectors.toMap(c -> c.getContentId(), c -> c));
|
||||
|
||||
Map<Long, CmsCatalog> loadedCatalogs = new HashMap<>();
|
||||
List<ArticleApiVO> list = new ArrayList<>();
|
||||
pageResult.getRecords().forEach(c -> {
|
||||
ArticleApiVO dto = ArticleApiVO.newInstance(c);
|
||||
CmsCatalog catalog = loadedCatalogs.get(c.getCatalogId());
|
||||
if (Objects.isNull(catalog)) {
|
||||
catalog = this.catalogService.getCatalog(c.getCatalogId());
|
||||
loadedCatalogs.put(catalog.getCatalogId(), catalog);
|
||||
}
|
||||
dto.setCatalogName(catalog.getName());
|
||||
dto.setCatalogLink(catalogService.getCatalogLink(catalog, 1, publishPipeCode, preview));
|
||||
dto.setLink(this.contentService.getContentLink(c, 1, publishPipeCode, preview));
|
||||
dto.setLogoSrc(InternalUrlUtils.getActualUrl(c.getLogo(), publishPipeCode, preview));
|
||||
CmsArticleDetail articleDetail = articleDetails.get(c.getContentId());
|
||||
if (Objects.nonNull(articleDetail)) {
|
||||
dto.setContentHtml(articleDetail.getContentHtml());
|
||||
}
|
||||
list.add(dto);
|
||||
});
|
||||
return R.ok(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按浏览量排序获取数据
|
||||
*/
|
||||
public R<ContentVO> getHotContentList() {
|
||||
|
||||
return R.ok();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
package com.chestnut.article.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.chestnut.common.db.domain.LogicDeleteEntity;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* 文章详情表对象 [cms_article_detail]
|
||||
*
|
||||
* @author 兮玥
|
||||
* @email 190785909@qq.com
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@TableName(CmsArticleDetail.TABLE_NAME)
|
||||
public class CmsArticleDetail extends LogicDeleteEntity {
|
||||
|
||||
public static final String TABLE_NAME = "cms_article_detail";
|
||||
|
||||
/**
|
||||
* 内容ID
|
||||
*/
|
||||
@TableId(value = "content_id", type = IdType.INPUT)
|
||||
private Long contentId;
|
||||
|
||||
/**
|
||||
* 站点ID
|
||||
*/
|
||||
private Long siteId;
|
||||
|
||||
/**
|
||||
* 正文详情(json)
|
||||
*/
|
||||
private String contentJson;
|
||||
|
||||
/**
|
||||
* 正文详情(html)
|
||||
*/
|
||||
private String contentHtml;
|
||||
|
||||
/**
|
||||
* 分页标题
|
||||
*/
|
||||
private String pageTitles;
|
||||
|
||||
/**
|
||||
* 是否下载远程图片
|
||||
*/
|
||||
private String downloadRemoteImage;
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package com.chestnut.article.domain.dto;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
|
||||
import com.chestnut.article.domain.CmsArticleDetail;
|
||||
import com.chestnut.common.utils.StringUtils;
|
||||
import com.chestnut.contentcore.domain.CmsContent;
|
||||
import com.chestnut.contentcore.domain.dto.ContentDTO;
|
||||
import com.chestnut.contentcore.fixed.dict.ContentAttribute;
|
||||
import com.chestnut.contentcore.util.InternalUrlUtils;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class ArticleDTO extends ContentDTO {
|
||||
|
||||
/**
|
||||
* 文章正文(html格式)
|
||||
*/
|
||||
private String contentHtml;
|
||||
|
||||
/**
|
||||
* 是否下载远程图片
|
||||
*/
|
||||
private String downloadRemoteImage;
|
||||
|
||||
/**
|
||||
* 分页标题
|
||||
*/
|
||||
private String pageTitles;
|
||||
|
||||
public static ArticleDTO newInstance(CmsContent content, CmsArticleDetail articleDetail) {
|
||||
ArticleDTO dto = new ArticleDTO();
|
||||
BeanUtils.copyProperties(content, dto);
|
||||
dto.setAttributes(ContentAttribute.convertStr(content.getAttributes()));
|
||||
if (StringUtils.isNotEmpty(dto.getLogo())) {
|
||||
dto.setLogoSrc(InternalUrlUtils.getActualPreviewUrl(dto.getLogo()));
|
||||
}
|
||||
BeanUtils.copyProperties(articleDetail, dto);
|
||||
return dto;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
package com.chestnut.article.domain.vo;
|
||||
|
||||
import com.chestnut.contentcore.domain.CmsContent;
|
||||
import com.chestnut.contentcore.domain.vo.ContentApiVO;
|
||||
import com.chestnut.contentcore.fixed.dict.ContentAttribute;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.time.ZoneOffset;
|
||||
|
||||
/**
|
||||
* <TODO description class purpose>
|
||||
*
|
||||
* @author 兮玥
|
||||
* @email 190785909@qq.com
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class ArticleApiVO extends ContentApiVO {
|
||||
|
||||
private String contentHtml;
|
||||
|
||||
public static ArticleApiVO newInstance(CmsContent cmsContent) {
|
||||
ArticleApiVO dto = new ArticleApiVO();
|
||||
dto.setAuthor(cmsContent.getAuthor());
|
||||
dto.setCatalogId(cmsContent.getCatalogId());
|
||||
dto.setContentId(cmsContent.getContentId());
|
||||
dto.setContentType(cmsContent.getContentType());
|
||||
dto.setEditor(cmsContent.getEditor());
|
||||
dto.setKeywords(cmsContent.getKeywords());
|
||||
dto.setLogo(cmsContent.getLogo());
|
||||
dto.setOriginal(cmsContent.getOriginal());
|
||||
dto.setPublishDate(cmsContent.getPublishDate().toInstant(ZoneOffset.UTC).toEpochMilli());
|
||||
dto.setShortTitle(cmsContent.getShortTitle());
|
||||
dto.setSubTitle(cmsContent.getSubTitle());
|
||||
dto.setTitle(cmsContent.getTitle());
|
||||
dto.setSource(cmsContent.getSource());
|
||||
dto.setSourceUrl(cmsContent.getSourceUrl());
|
||||
dto.setSummary(cmsContent.getSummary());
|
||||
dto.setTags(cmsContent.getTags());
|
||||
dto.setTitleStyle(cmsContent.getTitleStyle());
|
||||
dto.setTopFlag(cmsContent.getTopFlag());
|
||||
dto.setAttributes(ContentAttribute.convertStr(cmsContent.getAttributes()));
|
||||
dto.setViewCount(cmsContent.getViewCount());
|
||||
dto.setLikeCount(cmsContent.getLikeCount());
|
||||
dto.setCommentCount(cmsContent.getCommentCount());
|
||||
dto.setFavoriteCount(cmsContent.getFavoriteCount());
|
||||
return dto;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
package com.chestnut.article.domain.vo;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
|
||||
import com.chestnut.article.domain.CmsArticleDetail;
|
||||
import com.chestnut.common.utils.StringUtils;
|
||||
import com.chestnut.contentcore.domain.CmsContent;
|
||||
import com.chestnut.contentcore.domain.vo.ContentVO;
|
||||
import com.chestnut.contentcore.fixed.dict.ContentAttribute;
|
||||
import com.chestnut.contentcore.util.InternalUrlUtils;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class ArticleVO extends ContentVO {
|
||||
|
||||
/**
|
||||
* 文章正文(html格式)
|
||||
*/
|
||||
private String contentHtml;
|
||||
|
||||
/**
|
||||
* 是否下载远程图片
|
||||
*/
|
||||
private String downloadRemoteImage;
|
||||
|
||||
/**
|
||||
* 分页标题
|
||||
*/
|
||||
private String pageTitles;
|
||||
|
||||
public static ArticleVO newInstance(CmsContent content, CmsArticleDetail articleDetail) {
|
||||
ArticleVO dto = new ArticleVO();
|
||||
BeanUtils.copyProperties(content, dto);
|
||||
dto.setAttributes(ContentAttribute.convertStr(content.getAttributes()));
|
||||
if (StringUtils.isNotEmpty(dto.getLogo())) {
|
||||
dto.setLogoSrc(InternalUrlUtils.getActualPreviewUrl(dto.getLogo()));
|
||||
}
|
||||
if (Objects.nonNull(articleDetail)) {
|
||||
BeanUtils.copyProperties(articleDetail, dto);
|
||||
}
|
||||
return dto;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package com.chestnut.article.listener;
|
||||
|
||||
import com.chestnut.article.domain.vo.ArticleVO;
|
||||
import com.chestnut.article.mapper.CmsArticleDetailMapper;
|
||||
import com.chestnut.common.async.AsyncTaskManager;
|
||||
import com.chestnut.contentcore.domain.CmsSite;
|
||||
import com.chestnut.contentcore.listener.event.AfterContentEditorInitEvent;
|
||||
import com.chestnut.contentcore.listener.event.BeforeSiteDeleteEvent;
|
||||
import com.chestnut.contentcore.util.InternalUrlUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class ArticleListener {
|
||||
|
||||
private final CmsArticleDetailMapper articleMapper;
|
||||
|
||||
@EventListener
|
||||
public void beforeSiteDelete(BeforeSiteDeleteEvent event) {
|
||||
CmsSite site = event.getSite();
|
||||
int pageSize = 500;
|
||||
// 删除文章数据
|
||||
try {
|
||||
long total = this.articleMapper.selectCountBySiteIdIgnoreLogicDel(site.getSiteId());
|
||||
for (int i = 0; i * pageSize < total; i++) {
|
||||
AsyncTaskManager.setTaskProgressInfo((int) (i * pageSize * 100 / total), "正在删除文章详情备份数据:" + (i * pageSize) + "/" + total);
|
||||
this.articleMapper.deleteBySiteIdIgnoreLogicDel(site.getSiteId(), pageSize);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
AsyncTaskManager.addErrMessage("删除文章详情错误:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@EventListener
|
||||
public void afterContentEditorInit(AfterContentEditorInitEvent event) {
|
||||
if (event.getContentVO() instanceof ArticleVO vo) {
|
||||
vo.setContentHtml(InternalUrlUtils.dealResourceInternalUrl(vo.getContentHtml()));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package com.chestnut.article.mapper;
|
||||
|
||||
import com.chestnut.common.db.DBConstants;
|
||||
import org.apache.ibatis.annotations.Delete;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.chestnut.article.domain.CmsArticleDetail;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 文章Mapper 接口
|
||||
* </p>
|
||||
*
|
||||
* @author 兮玥
|
||||
* @email 190785909@qq.com
|
||||
*/
|
||||
public interface CmsArticleDetailMapper extends BaseMapper<CmsArticleDetail> {
|
||||
|
||||
/**
|
||||
* 删除逻辑删除标识deleted=1的数据
|
||||
*
|
||||
* @param contentId
|
||||
* @return
|
||||
*/
|
||||
@Delete("DELETE FROM cms_article_detail WHERE deleted = 1 AND content_id = #{contentId}")
|
||||
Long deleteLogicDeletedById(@Param("contentId") Long contentId);
|
||||
|
||||
/**
|
||||
* 删除站点内容,忽略逻辑删除标识影响
|
||||
*
|
||||
* @param siteId
|
||||
* @param limit
|
||||
* @return
|
||||
*/
|
||||
@Delete("DELETE FROM cms_article_detail WHERE site_id = #{siteId} limit ${limit}")
|
||||
Long deleteBySiteIdIgnoreLogicDel(@Param("siteId") Long siteId, @Param("limit") Integer limit);
|
||||
|
||||
/**
|
||||
* 查询站点内容,忽略逻辑删除标识影响
|
||||
*
|
||||
* @param siteId
|
||||
* @return
|
||||
*/
|
||||
@Select("SELECT count(*) FROM cms_article_detail WHERE site_id = #{siteId}")
|
||||
Long selectCountBySiteIdIgnoreLogicDel(@Param("siteId") Long siteId);
|
||||
|
||||
/**
|
||||
* 设置cms_article_detail的deleted逻辑删除标识值为0
|
||||
*
|
||||
* @param contentId
|
||||
* @return
|
||||
*/
|
||||
@Update("UPDATE cms_article_detail SET deleted = " + DBConstants.DELETED_NO + " WHERE content_id = #{contentId}")
|
||||
Long recoverById(@Param("contentId") Long contentId);
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
package com.chestnut.article.properties;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
import org.apache.commons.lang3.math.NumberUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.chestnut.contentcore.core.IProperty;
|
||||
import com.chestnut.contentcore.util.ConfigPropertyUtils;
|
||||
|
||||
/**
|
||||
* 文章正文图片高度,默认:600
|
||||
*/
|
||||
@Component(IProperty.BEAN_NAME_PREFIX + ArticleImageHeight.ID)
|
||||
public class ArticleImageHeight implements IProperty {
|
||||
|
||||
public final static String ID = "ArticleImageHeight";
|
||||
|
||||
static UseType[] UseTypes = new UseType[] { UseType.Site, UseType.Catalog };
|
||||
|
||||
@Override
|
||||
public UseType[] getUseTypes() {
|
||||
return UseTypes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "文章正文图片高度";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validate(String value) {
|
||||
return Objects.nonNull(value) && NumberUtils.isDigits(value.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer defaultValue() {
|
||||
return 600;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getPropValue(Map<String, String> configProps) {
|
||||
return MapUtils.getInteger(configProps, getId(), defaultValue());
|
||||
}
|
||||
|
||||
public static int getValue(Map<String, String> firstProps, Map<String, String> secondProps) {
|
||||
int value = ConfigPropertyUtils.getIntValue(ID, firstProps, secondProps);
|
||||
return value <= 0 ? 600 : value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package com.chestnut.article.properties;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
import org.apache.commons.lang3.math.NumberUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.chestnut.contentcore.core.IProperty;
|
||||
import com.chestnut.contentcore.util.ConfigPropertyUtils;
|
||||
|
||||
/**
|
||||
* 文章正文图片宽度,默认:600
|
||||
*/
|
||||
@Component(IProperty.BEAN_NAME_PREFIX + ArticleImageWidth.ID)
|
||||
public class ArticleImageWidth implements IProperty {
|
||||
|
||||
public final static String ID = "ArticleImageWidth";
|
||||
|
||||
static UseType[] UseTypes = new UseType[] { UseType.Site, UseType.Catalog };
|
||||
|
||||
@Override
|
||||
public UseType[] getUseTypes() {
|
||||
return UseTypes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "文章正文图片宽度";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validate(String value) {
|
||||
return NumberUtils.isDigits(value.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer defaultValue() {
|
||||
return 600;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getPropValue(Map<String, String> configProps) {
|
||||
return MapUtils.getInteger(configProps, getId(), defaultValue());
|
||||
}
|
||||
|
||||
public static int getValue(Map<String, String> firstProps, Map<String, String> secondProps) {
|
||||
int value = ConfigPropertyUtils.getIntValue(ID, firstProps, secondProps);
|
||||
return value <= 0 ? 600 : value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package com.chestnut.article.properties;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.chestnut.contentcore.core.IProperty;
|
||||
import com.chestnut.contentcore.util.ConfigPropertyUtils;
|
||||
import com.chestnut.system.fixed.dict.YesOrNo;
|
||||
|
||||
/**
|
||||
* 自动提取文章正文首图作为Logo
|
||||
*/
|
||||
@Component(IProperty.BEAN_NAME_PREFIX + AutoArticleLogo.ID)
|
||||
public class AutoArticleLogo implements IProperty {
|
||||
|
||||
public final static String ID = "AutoArticleLogo";
|
||||
|
||||
static UseType[] UseTypes = new UseType[] { UseType.Site };
|
||||
|
||||
@Override
|
||||
public UseType[] getUseTypes() {
|
||||
return UseTypes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "自动提取文章正文首图作为Logo";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String defaultValue() {
|
||||
return YesOrNo.NO;
|
||||
}
|
||||
|
||||
public static boolean getValue(Map<String, String> props) {
|
||||
return YesOrNo.isYes(ConfigPropertyUtils.getStringValue(ID, props));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package com.chestnut.article.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.chestnut.article.domain.CmsArticleDetail;
|
||||
import com.chestnut.contentcore.domain.CmsSite;
|
||||
|
||||
/**
|
||||
* 文章服务类
|
||||
*
|
||||
* @author 兮玥
|
||||
* @email 190785909@qq.com
|
||||
*/
|
||||
public interface IArticleService extends IService<CmsArticleDetail> {
|
||||
|
||||
/**
|
||||
* 保存内部链接
|
||||
*
|
||||
* 查找内容中所有带iurl属性的标签,如果标签内含有src/href属性则替换成iurl地址,并移除标签iurl属性
|
||||
*
|
||||
* @param content
|
||||
* @return
|
||||
*/
|
||||
String saveInternalUrl(String content);
|
||||
|
||||
/**
|
||||
* 下载远程图片
|
||||
*
|
||||
* @param content
|
||||
* @param site
|
||||
* @param operator
|
||||
* @return
|
||||
*/
|
||||
String downloadRemoteImages(String content, CmsSite site, String operator);
|
||||
}
|
|
@ -0,0 +1,145 @@
|
|||
package com.chestnut.article.service.impl;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import com.chestnut.contentcore.domain.*;
|
||||
import com.chestnut.contentcore.service.*;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.chestnut.article.domain.CmsArticleDetail;
|
||||
import com.chestnut.article.mapper.CmsArticleDetailMapper;
|
||||
import com.chestnut.article.service.IArticleService;
|
||||
import com.chestnut.common.async.AsyncTaskManager;
|
||||
import com.chestnut.common.utils.ServletUtils;
|
||||
import com.chestnut.common.utils.StringUtils;
|
||||
import com.chestnut.contentcore.util.InternalUrlUtils;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ArticleServiceImpl extends ServiceImpl<CmsArticleDetailMapper, CmsArticleDetail>
|
||||
implements IArticleService {
|
||||
|
||||
/**
|
||||
* 带src属性的图片标签匹配
|
||||
*/
|
||||
public static final Pattern ImgHtmlTagPattern = Pattern.compile("<img[^>]+src\\s*=\\s*['\"]([^'\"]+)['\"][^>]*>",
|
||||
Pattern.CASE_INSENSITIVE | Pattern.DOTALL | Pattern.MULTILINE);
|
||||
|
||||
/**
|
||||
* 带iurl属性的任意标签匹配
|
||||
*/
|
||||
private static final Pattern IUrlTagPattern = Pattern.compile("<[^>]+iurl\\s*=\\s*['\"]([^'\"]+)['\"][^>]*>",
|
||||
Pattern.CASE_INSENSITIVE | Pattern.DOTALL | Pattern.MULTILINE);
|
||||
|
||||
private static final Pattern TagAttrSrcPattern = Pattern.compile("src\\s*=\\s*['\"]([^'\"]+)['\"]",
|
||||
Pattern.CASE_INSENSITIVE | Pattern.DOTALL | Pattern.MULTILINE);
|
||||
|
||||
private static final Pattern TagAttrHrefPattern = Pattern.compile("href\\s*=\\s*['\"]([^'\"]+)['\"]",
|
||||
Pattern.CASE_INSENSITIVE | Pattern.DOTALL | Pattern.MULTILINE);
|
||||
|
||||
private static final Pattern TagAttrIUrlPattern = Pattern.compile("iurl\\s*=\\s*['\"]([^'\"]+)['\"]",
|
||||
Pattern.CASE_INSENSITIVE | Pattern.DOTALL | Pattern.MULTILINE);
|
||||
|
||||
private final IResourceService resourceService;
|
||||
|
||||
private final IPageWidgetService pageWidgetService;
|
||||
|
||||
private final ISiteService siteService;
|
||||
|
||||
private final ICatalogService catalogService;
|
||||
|
||||
private final IContentService contentService;
|
||||
|
||||
@Override
|
||||
public String saveInternalUrl(String content) {
|
||||
if (StringUtils.isBlank(content)) {
|
||||
return content;
|
||||
}
|
||||
Matcher matcher = IUrlTagPattern.matcher(content);
|
||||
int lastEndIndex = 0;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
while (matcher.find(lastEndIndex)) {
|
||||
int s = matcher.start();
|
||||
sb.append(content.substring(lastEndIndex, s));
|
||||
|
||||
String tagStr = matcher.group();
|
||||
String iurl = matcher.group(1);
|
||||
// begin
|
||||
try {
|
||||
// 移除iurl属性
|
||||
tagStr = TagAttrIUrlPattern.matcher(tagStr).replaceAll("");
|
||||
// 查找src属性,替换成iurl
|
||||
Matcher matcherSrc = TagAttrSrcPattern.matcher(tagStr);
|
||||
if (matcherSrc.find()) {
|
||||
tagStr = tagStr.substring(0, matcherSrc.start()) + "src=\"" + iurl + "\""
|
||||
+ tagStr.substring(matcherSrc.end());
|
||||
} else {
|
||||
// 无src属性则继续查找href属性
|
||||
Matcher matcherHref = TagAttrHrefPattern.matcher(tagStr);
|
||||
if (matcherHref.find()) {
|
||||
tagStr = tagStr.substring(0, matcherHref.start()) + "href=\"" + iurl + "\""
|
||||
+ tagStr.substring(matcherHref.end());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("InternalUrl parse failed: " + iurl, e);
|
||||
}
|
||||
// end
|
||||
sb.append(tagStr.replace("\\s+", " "));
|
||||
|
||||
lastEndIndex = matcher.end();
|
||||
}
|
||||
sb.append(content.substring(lastEndIndex));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载远程图片前要先调用saveInternalUrl方法处理所有标签的iurl
|
||||
*/
|
||||
@Override
|
||||
public String downloadRemoteImages(String content, CmsSite site, String operator) {
|
||||
if (StringUtils.isBlank(content)) {
|
||||
return content;
|
||||
}
|
||||
Matcher matcher = ImgHtmlTagPattern.matcher(content);
|
||||
int lastEndIndex = 0;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
while (matcher.find(lastEndIndex)) {
|
||||
int s = matcher.start();
|
||||
sb.append(content.substring(lastEndIndex, s));
|
||||
|
||||
String imgTag = matcher.group();
|
||||
String src = matcher.group(1);
|
||||
try {
|
||||
if (StringUtils.startsWithIgnoreCase(src, "data:image/")) {
|
||||
// base64图片保存到资源库
|
||||
CmsResource resource = resourceService.addBase64Image(site, operator, src);
|
||||
if (Objects.nonNull(resource)) {
|
||||
imgTag = imgTag.replaceFirst("data:image/([^'\"]+)", src);
|
||||
}
|
||||
} else if (!InternalUrlUtils.isInternalUrl(src) && ServletUtils.isHttpUrl(src)) {
|
||||
// 非iurl的http链接则下载图片
|
||||
CmsResource resource = resourceService.downloadImageFromUrl(src, site.getSiteId(), operator);
|
||||
if (Objects.nonNull(resource)) {
|
||||
imgTag = StringUtils.replaceEx(imgTag, src, resource.getInternalUrl());
|
||||
}
|
||||
}
|
||||
} catch (Exception e1) {
|
||||
String imgSrc = (src.startsWith("data:image/") ? src.substring(0, 20) : src);
|
||||
log.warn("Save image failed: " + imgSrc);
|
||||
AsyncTaskManager.addErrMessage("Download remote image failed: " + imgSrc);
|
||||
}
|
||||
sb.append(imgTag);
|
||||
lastEndIndex = matcher.end();
|
||||
}
|
||||
sb.append(content.substring(lastEndIndex));
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package com.chestnut.article.template.func;
|
||||
|
||||
import com.chestnut.article.ArticleUtils;
|
||||
import com.chestnut.article.service.IArticleService;
|
||||
import com.chestnut.common.staticize.FreeMarkerUtils;
|
||||
import com.chestnut.common.staticize.core.TemplateContext;
|
||||
import com.chestnut.common.staticize.func.AbstractFunc;
|
||||
import com.chestnut.common.utils.StringUtils;
|
||||
import com.chestnut.contentcore.util.InternalUrlUtils;
|
||||
import freemarker.core.Environment;
|
||||
import freemarker.template.SimpleScalar;
|
||||
import freemarker.template.TemplateModelException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Freemarker模板自定义函数:处理Html文本内容中的内部链接
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class dealArticleBodyFunction extends AbstractFunc {
|
||||
|
||||
static final String FUNC_NAME = "dealArticleBody";
|
||||
|
||||
private static final String DESC = "{FREEMARKER.FUNC.DESC." + FUNC_NAME + "}";
|
||||
|
||||
private final IArticleService articleService;
|
||||
|
||||
@Override
|
||||
public String getFuncName() {
|
||||
return FUNC_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDesc() {
|
||||
return DESC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object exec0(Object... args) throws TemplateModelException {
|
||||
if (args.length < 1) {
|
||||
return StringUtils.EMPTY;
|
||||
}
|
||||
TemplateContext context = FreeMarkerUtils.getTemplateContext(Environment.getCurrentEnvironment());
|
||||
SimpleScalar simpleScalar = (SimpleScalar) args[0];
|
||||
String contentHtml = simpleScalar.getAsString();
|
||||
// 处理内容扩展模板占位符
|
||||
contentHtml = ArticleUtils.dealContentEx(contentHtml, context.getPublishPipeCode(), context.isPreview());
|
||||
// 处理正文内部链接
|
||||
contentHtml = InternalUrlUtils.dealInternalUrl(contentHtml, context.getPublishPipeCode(), context.isPreview());
|
||||
return contentHtml;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FuncArg> getFuncArgs() {
|
||||
return List.of(new FuncArg("HTML文章正文内容", FuncArgType.String, true, null));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
package com.chestnut.article.template.tag;
|
||||
|
||||
import com.chestnut.article.domain.CmsArticleDetail;
|
||||
import com.chestnut.article.mapper.CmsArticleDetailMapper;
|
||||
import com.chestnut.common.staticize.FreeMarkerUtils;
|
||||
import com.chestnut.common.staticize.StaticizeConstants;
|
||||
import com.chestnut.common.staticize.core.TemplateContext;
|
||||
import com.chestnut.common.staticize.enums.TagAttrDataType;
|
||||
import com.chestnut.common.staticize.tag.AbstractTag;
|
||||
import com.chestnut.common.staticize.tag.TagAttr;
|
||||
import com.chestnut.common.utils.StringUtils;
|
||||
import com.chestnut.contentcore.domain.CmsContent;
|
||||
import com.chestnut.contentcore.enums.ContentCopyType;
|
||||
import com.chestnut.contentcore.mapper.CmsContentMapper;
|
||||
import freemarker.core.Environment;
|
||||
import freemarker.template.TemplateException;
|
||||
import freemarker.template.TemplateModel;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Component
|
||||
public class CmsArticleTag extends AbstractTag {
|
||||
|
||||
public static final String TAG_NAME = "cms_article";
|
||||
public final static String NAME = "{FREEMARKER.TAG.NAME." + TAG_NAME + "}";
|
||||
public final static String DESC = "{FREEMARKER.TAG.DESC." + TAG_NAME + "}";
|
||||
|
||||
public static final String TagAttr_ContentId = "contentId";
|
||||
|
||||
public static final String TagAttr_Page = "page";
|
||||
|
||||
public static final String TemplateVariable_ArticleContent = "ArticleContent";
|
||||
|
||||
// CKEditor5: <div class="page-break" style="page-break-after:always;"><span style="display:none;"> </span></div>
|
||||
// private static final String PAGE_BREAK_SPLITER = "<div[^>]+class=['\"]page-break['\"].*?</div>";
|
||||
private static final String PAGE_BREAK_SPLITER = "__XY_UEDITOR_PAGE_BREAK__";
|
||||
|
||||
|
||||
private final CmsContentMapper contentMapper;
|
||||
|
||||
private final CmsArticleDetailMapper articleMapper;
|
||||
|
||||
@Override
|
||||
public List<TagAttr> getTagAttrs() {
|
||||
List<TagAttr> tagAttrs = new ArrayList<>();
|
||||
tagAttrs.add(new TagAttr(TagAttr_ContentId, true, TagAttrDataType.INTEGER, "文章内容ID"));
|
||||
tagAttrs.add(new TagAttr(TagAttr_Page, false, TagAttrDataType.BOOLEAN, "是否分页,默认false"));
|
||||
return tagAttrs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, TemplateModel> execute0(Environment env, Map<String, String> attrs)
|
||||
throws TemplateException, IOException {
|
||||
String contentHtml = null;
|
||||
long contentId = MapUtils.getLongValue(attrs, TagAttr_ContentId, 0);
|
||||
if (contentId <= 0) {
|
||||
throw new TemplateException("Invalid contentId: " + contentId, env);
|
||||
}
|
||||
CmsContent content = this.contentMapper.selectById(contentId);
|
||||
if (content.isLinkContent()) {
|
||||
return Map.of(TemplateVariable_ArticleContent, this.wrap(env, StringUtils.EMPTY));
|
||||
}
|
||||
if (ContentCopyType.isMapping(content.getCopyType())) {
|
||||
contentId = content.getCopyId();
|
||||
}
|
||||
CmsArticleDetail articleDetail = this.articleMapper.selectById(contentId);
|
||||
if (Objects.isNull(articleDetail)) {
|
||||
throw new TemplateException("Article details not found: " + contentId, env);
|
||||
}
|
||||
contentHtml = articleDetail.getContentHtml();
|
||||
TemplateContext context = FreeMarkerUtils.getTemplateContext(env);
|
||||
boolean page = MapUtils.getBooleanValue(attrs, TagAttr_Page, false);
|
||||
if (page) {
|
||||
if (context.isPaged()) {
|
||||
throw new TemplateException("分页标识已被其他标签激活", env);
|
||||
}
|
||||
context.setPaged(true);
|
||||
|
||||
String[] pageContents = contentHtml.split(PAGE_BREAK_SPLITER);
|
||||
if (context.getPageIndex() > pageContents.length) {
|
||||
throw new TemplateException(StringUtils.messageFormat("文章内容分页越界:{0}, 最大页码:{1}。", context.getPageIndex(),
|
||||
pageContents.length), env);
|
||||
}
|
||||
context.setPageTotal(pageContents.length);
|
||||
env.setGlobalVariable(StaticizeConstants.TemplateVariable_PageTotal,
|
||||
this.wrap(env, context.getPageTotal()));
|
||||
contentHtml = pageContents[context.getPageIndex() - 1];
|
||||
}
|
||||
return Map.of(TemplateVariable_ArticleContent, this.wrap(env, contentHtml));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTagName() {
|
||||
return TAG_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return DESC;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
# 内容类型
|
||||
CMS.CONTENTCORE.CONTENT_TYPE.article=文章
|
||||
|
||||
# 模板freemarker
|
||||
FREEMARKER.TAG.NAME.cms_article=文章正文数据标签
|
||||
FREEMARKER.TAG.DESC.cms_article=获取文章正文数据,标签内使用${ArticleContent}获取正文详情
|
||||
FREEMARKER.FUNC.DESC.dealArticleBody=文章正文处理函数,主要用来处理文章内容中的内部链接和扩展模板占位符
|
|
@ -0,0 +1,7 @@
|
|||
# 内容类型
|
||||
CMS.CONTENTCORE.CONTENT_TYPE.article=Article
|
||||
|
||||
# 模板freemarker
|
||||
FREEMARKER.TAG.NAME.cms_article=Article body tag
|
||||
FREEMARKER.TAG.DESC.cms_article=Fetch article body details, use "${ArticleContent}" in tag to get body text.
|
||||
FREEMARKER.FUNC.DESC.dealArticleBody=Article body deal function
|
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0"?>
|
||||
<project
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-cms</artifactId>
|
||||
<version>1.3.21</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>chestnut-cms-block</artifactId>
|
||||
<description>区块页面部件扩展模块</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- 内容核心 -->
|
||||
<dependency>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-cms-contentcore</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
|
@ -0,0 +1,44 @@
|
|||
package com.chestnut.block;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import com.chestnut.block.ManualPageWidgetType.RowData;
|
||||
import com.chestnut.common.utils.JacksonUtils;
|
||||
import com.chestnut.contentcore.core.AbstractPageWidget;
|
||||
import com.chestnut.contentcore.domain.CmsPageWidget;
|
||||
|
||||
/**
|
||||
* 手动控制区块
|
||||
*
|
||||
* @author 兮玥
|
||||
* @email 190785909@qq.com
|
||||
*/
|
||||
public class ManualPageWidget extends AbstractPageWidget {
|
||||
|
||||
@Override
|
||||
public void add() {
|
||||
this.dealContentIngoreFields();
|
||||
super.add();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save() {
|
||||
this.dealContentIngoreFields();
|
||||
super.save();
|
||||
}
|
||||
|
||||
private void dealContentIngoreFields() {
|
||||
CmsPageWidget pageWidgetEntity = this.getPageWidgetEntity();
|
||||
List<RowData> rows = JacksonUtils.fromList(pageWidgetEntity.getContent(), RowData.class);
|
||||
if (Objects.nonNull(rows)) {
|
||||
rows.forEach(row -> {
|
||||
row.getItems().forEach(item -> item.setLogoSrc(null));
|
||||
});
|
||||
} else {
|
||||
rows = Collections.emptyList();
|
||||
}
|
||||
pageWidgetEntity.setContent(JacksonUtils.to(rows));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
package com.chestnut.block;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.chestnut.block.domain.vo.ManualPageWidgetVO;
|
||||
import com.chestnut.common.utils.JacksonUtils;
|
||||
import com.chestnut.common.utils.StringUtils;
|
||||
import com.chestnut.contentcore.core.IPageWidget;
|
||||
import com.chestnut.contentcore.core.IPageWidgetType;
|
||||
import com.chestnut.contentcore.domain.CmsPageWidget;
|
||||
import com.chestnut.contentcore.domain.vo.PageWidgetVO;
|
||||
import com.chestnut.contentcore.util.InternalUrlUtils;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* 手动控制区块页面部件<br/>
|
||||
* 此区块内容支持多行多列自定义控制
|
||||
*
|
||||
* @author 兮玥
|
||||
* @email 190785909@qq.com
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Component(IPageWidgetType.BEAN_NAME_PREFIX + ManualPageWidgetType.ID)
|
||||
public class ManualPageWidgetType implements IPageWidgetType {
|
||||
|
||||
public final static String ID = "manual";
|
||||
public final static String NAME = "{CMS.CONTENCORE.PAGEWIDGET." + ID + "}";
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIcon() {
|
||||
return "el-icon-list";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRoute() {
|
||||
return "/cms/block/manual/editor";
|
||||
}
|
||||
|
||||
@Override
|
||||
public IPageWidget loadPageWidget(CmsPageWidget cmsPageWdiget) {
|
||||
ManualPageWidget pw = new ManualPageWidget();
|
||||
pw.setPageWidgetEntity(cmsPageWdiget);
|
||||
return pw;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IPageWidget newInstance() {
|
||||
return new ManualPageWidget();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageWidgetVO getPageWidgetVO(CmsPageWidget pageWidget) {
|
||||
ManualPageWidgetVO vo = new ManualPageWidgetVO();
|
||||
BeanUtils.copyProperties(pageWidget, vo);
|
||||
vo.setContent(this.parseContent(pageWidget, null, true));
|
||||
return vo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RowData> parseContent(CmsPageWidget pageWidget, String publishPipeCode, boolean isPreview) {
|
||||
List<RowData> list = null;
|
||||
if (StringUtils.isNotEmpty(pageWidget.getContent())) {
|
||||
list = JacksonUtils.fromList(pageWidget.getContent(), RowData.class);
|
||||
}
|
||||
if (list == null) {
|
||||
list = List.of();
|
||||
}
|
||||
list.forEach(rd -> rd.getItems().forEach(item -> item.setLogoSrc(InternalUrlUtils.getActualPreviewUrl(item.logo))));
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public static class RowData {
|
||||
|
||||
private List<ItemData> items;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public static class ItemData {
|
||||
|
||||
private String title;
|
||||
|
||||
private String titleStyle;
|
||||
|
||||
private String summary;
|
||||
|
||||
private String url;
|
||||
|
||||
private String logo;
|
||||
|
||||
private String logoSrc;
|
||||
|
||||
private LocalDateTime publishDate;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package com.chestnut.block.domain.vo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.chestnut.block.ManualPageWidgetType.RowData;
|
||||
import com.chestnut.contentcore.domain.vo.PageWidgetVO;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* 动态区块页面部件展示对象
|
||||
*
|
||||
* @author 兮玥
|
||||
* @email 190785909@qq.com
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@Accessors(chain = true)
|
||||
public class ManualPageWidgetVO extends PageWidgetVO {
|
||||
|
||||
/**
|
||||
* 区块内容
|
||||
*/
|
||||
private List<RowData> content;
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package com.chestnut.block.listener;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class BlockListener {
|
||||
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
# 页面部件类型
|
||||
CMS.CONTENCORE.PAGEWIDGET.manual=自定义区块
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue