mirror of
https://gitee.com/liweiyi/ChestnutCMS.git
synced 2025-12-06 16:38:24 +08:00
版本更新:V1.5.5
This commit is contained in:
parent
ffca051843
commit
8dee7904f8
@ -1,4 +1,4 @@
|
||||
# ChestnutCMS v1.5.4
|
||||
# ChestnutCMS v1.5.5
|
||||
|
||||
### 系统简介
|
||||
|
||||
@ -105,7 +105,7 @@ ChestnutCMS是前后端分离的企业级内容管理系统。项目基于[RuoYi
|
||||
|
||||
### QQ交流群
|
||||
|
||||
- [一群:568506424(满)](https://qm.qq.com/q/rOw3kwePg)
|
||||
- [一群:568506424](https://qm.qq.com/q/rOw3kwePg)
|
||||
|
||||
- [二群:643215654(满)](https://qm.qq.com/q/BEC38NokKY)
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<artifactId>chestnut</artifactId>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<version>1.5.4</version>
|
||||
<version>1.5.5</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
@ -17,7 +17,8 @@ package com.chestnut;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||
|
||||
import java.net.InetAddress;
|
||||
|
||||
/**
|
||||
* 启动程序
|
||||
@ -28,9 +29,10 @@ import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||
@SpringBootApplication
|
||||
public class ChestnutApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
public static void main(String[] args) throws Exception {
|
||||
long s = System.currentTimeMillis();
|
||||
System.setProperty("spring.devtools.restart.enabled", "false");
|
||||
System.setProperty("LOCAL_IP", InetAddress.getLocalHost().getHostAddress());
|
||||
SpringApplication.run(ChestnutApplication.class, args);
|
||||
System.out.println("ChestnutApplication startup, cost: " + (System.currentTimeMillis() - s) + "ms");
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@ chestnut:
|
||||
# 代号
|
||||
alias: ChestnutCMS
|
||||
# 版本
|
||||
version: 1.5.4
|
||||
version: 1.5.5
|
||||
# 版权年份
|
||||
copyrightYear: 2022-2024
|
||||
system:
|
||||
@ -46,9 +46,12 @@ server:
|
||||
|
||||
# 日志配置
|
||||
logging:
|
||||
config: classpath:logback-dev.xml
|
||||
level:
|
||||
com.chestnut: debug
|
||||
org.springframework: warn
|
||||
com.chestnut: debug
|
||||
cron: debug
|
||||
publish: debug
|
||||
|
||||
# Spring配置
|
||||
spring:
|
||||
|
||||
@ -5,7 +5,7 @@ chestnut:
|
||||
# 代号
|
||||
alias: ChestnutCMS
|
||||
# 版本
|
||||
version: 1.5.4
|
||||
version: 1.5.5
|
||||
# 版权年份
|
||||
copyrightYear: 2022-2024
|
||||
system:
|
||||
@ -46,9 +46,12 @@ server:
|
||||
|
||||
# 日志配置
|
||||
logging:
|
||||
config: classpath:logback-prod.xml
|
||||
level:
|
||||
com.chestnut: debug
|
||||
org.springframework: warn
|
||||
com.chestnut: debug
|
||||
cron: debug
|
||||
publish: debug
|
||||
|
||||
# Spring配置
|
||||
spring:
|
||||
|
||||
@ -5,7 +5,7 @@ chestnut:
|
||||
# 代号
|
||||
alias: ChestnutCMS
|
||||
# 版本
|
||||
version: 1.5.4
|
||||
version: 1.5.5
|
||||
# 版权年份
|
||||
copyrightYear: 2022-2024
|
||||
system:
|
||||
@ -40,9 +40,12 @@ server:
|
||||
|
||||
# 日志配置
|
||||
logging:
|
||||
config: classpath:logback-dev.xml
|
||||
level:
|
||||
com.chestnut: debug
|
||||
org.springframework: warn
|
||||
com.chestnut: debug
|
||||
cron: debug
|
||||
publish: debug
|
||||
|
||||
# Spring配置
|
||||
spring:
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
ALTER TABLE cms_cfd_default ADD COLUMN `uid` bigint;
|
||||
ALTER TABLE cms_custom_form MODIFY COLUMN `rule_limit` VARCHAR(20);
|
||||
|
||||
@ -1,13 +0,0 @@
|
||||
#错误消息
|
||||
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}个字符
|
||||
@ -1,13 +0,0 @@
|
||||
#错误消息
|
||||
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}.
|
||||
@ -1,13 +0,0 @@
|
||||
#錯誤消息
|
||||
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}個字元
|
||||
96
chestnut-admin/src/main/resources/logback-dev.xml
Normal file
96
chestnut-admin/src/main/resources/logback-dev.xml
Normal file
@ -0,0 +1,96 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<property name="log.app.name" value="ChestnutCMS" />
|
||||
<!-- 日志存放路径 -->
|
||||
<property name="log.path" value="logs" />
|
||||
<!-- 日志输出格式 -->
|
||||
<property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%t][%C#%method,%L]: %msg%n" />
|
||||
|
||||
<appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
|
||||
<!-- 设置队列的最大容量,默认:256 -->
|
||||
<queueSize>262144</queueSize>
|
||||
<!-- 在队列快满时(还剩20%容量),丢弃日志的水平,配置为 0 就是都不丢弃 -->
|
||||
<discardingThreshold>0</discardingThreshold>
|
||||
<!-- 设置是否在异步线程中包含调用者数据,默认:false,其实就是是否可以输出代码位置 -->
|
||||
<includeCallerData>true</includeCallerData>
|
||||
<appender-ref ref="out" />
|
||||
</appender>
|
||||
|
||||
<!-- 控制台输出 -->
|
||||
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>${log.pattern}</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
<appender name="CC_CONSOLE" class="com.chestnut.system.logs.CcConsoleAppender">
|
||||
<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}-${LOCAL_IP}.log</fileNamePattern>
|
||||
<!-- 日志最大的历史 5天 -->
|
||||
<maxHistory>5</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||
<pattern>${log.pattern}</pattern>
|
||||
</encoder>
|
||||
<!-- 默认:true,日志直接写入磁盘,设置为false先写入内存,buffer满后批量刷盘 -->
|
||||
<immediateFlush>false</immediateFlush>
|
||||
<!-- 日志写入内存容量上限 -->
|
||||
<bufferSize>8192</bufferSize>
|
||||
</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}-${LOCAL_IP}.log</fileNamePattern>
|
||||
<!-- 日志最大的历史 60天 -->
|
||||
<maxHistory>5</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>${log.pattern}</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- 发布日志输出 -->
|
||||
<appender name="publish" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${log.path}/publish.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<!-- 按天回滚 daily -->
|
||||
<fileNamePattern>${log.path}/publish-%d{yyyy-MM-dd}-${LOCAL_IP}.log</fileNamePattern>
|
||||
<!-- 日志最大的历史 60天 -->
|
||||
<maxHistory>5</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>${log.pattern}</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!--默认-->
|
||||
<root level="info">
|
||||
<appender-ref ref="console" />
|
||||
<appender-ref ref="CC_CONSOLE" />
|
||||
<appender-ref ref="ASYNC_FILE" />
|
||||
</root>
|
||||
<!-- Spring日志 -->
|
||||
<logger name="org.springframework" level="warn" />
|
||||
<!-- 系统模块日志 -->
|
||||
<logger name="com.chestnut" level="debug" />
|
||||
<!-- 系统定时任务日志-->
|
||||
<logger name="cron" level="debug">
|
||||
<appender-ref ref="cron"/>
|
||||
</logger>
|
||||
<!--系统发布日志-->
|
||||
<logger name="publish" level="debug">
|
||||
<appender-ref ref="publish"/>
|
||||
</logger>
|
||||
</configuration>
|
||||
112
chestnut-admin/src/main/resources/logback-prod.xml
Normal file
112
chestnut-admin/src/main/resources/logback-prod.xml
Normal file
@ -0,0 +1,112 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<property name="log.app.name" value="ChestnutCMS" />
|
||||
<!-- 日志存放路径 -->
|
||||
<property name="log.path" value="logs" />
|
||||
<!-- 日志输出格式 -->
|
||||
<property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%t][%C#%method,%L]: %msg%n" />
|
||||
|
||||
<appender name="CC_CONSOLE" class="com.chestnut.system.logs.CcConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>${log.pattern}</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
|
||||
<!-- 设置队列的最大容量,默认:256 -->
|
||||
<queueSize>262144</queueSize>
|
||||
<!-- 在队列快满时(还剩20%容量),丢弃日志的水平,配置为 0 就是都不丢弃 -->
|
||||
<discardingThreshold>0</discardingThreshold>
|
||||
<!-- 设置是否在异步线程中包含调用者数据,默认:false,其实就是是否可以输出代码位置 -->
|
||||
<includeCallerData>true</includeCallerData>
|
||||
<appender-ref ref="out" />
|
||||
</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}-${LOCAL_IP}.log</fileNamePattern>
|
||||
<!-- 日志最大的历史 5天 -->
|
||||
<maxHistory>5</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||
<pattern>${log.pattern}</pattern>
|
||||
</encoder>
|
||||
<!-- 默认:true,日志直接写入磁盘,设置为false先写入内存,buffer满后批量刷盘 -->
|
||||
<immediateFlush>false</immediateFlush>
|
||||
<!-- 日志写入内存容量上限 -->
|
||||
<bufferSize>8192</bufferSize>
|
||||
</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}-${LOCAL_IP}.log</fileNamePattern>
|
||||
<!-- 日志最大的历史 60天 -->
|
||||
<maxHistory>5</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>${log.pattern}</pattern>
|
||||
</encoder>
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<!-- 过滤的级别 -->
|
||||
<level>WARN</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}-${LOCAL_IP}.log</fileNamePattern>
|
||||
<!-- 日志最大的历史 60天 -->
|
||||
<maxHistory>5</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>${log.pattern}</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- 发布日志输出 -->
|
||||
<appender name="publish" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${log.path}/publish.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<!-- 按天回滚 daily -->
|
||||
<fileNamePattern>${log.path}/publish-%d{yyyy-MM-dd}-${LOCAL_IP}.log</fileNamePattern>
|
||||
<!-- 日志最大的历史 60天 -->
|
||||
<maxHistory>5</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>${log.pattern}</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!--默认-->
|
||||
<root level="info">
|
||||
<appender-ref ref="CC_CONSOLE" />
|
||||
<appender-ref ref="ASYNC_FILE" />
|
||||
<appender-ref ref="error"/>
|
||||
</root>
|
||||
<!-- Spring日志 -->
|
||||
<logger name="org.springframework" level="error" />
|
||||
<!-- 系统模块日志 -->
|
||||
<logger name="com.chestnut" level="info" />
|
||||
<!-- 系统定时任务日志-->
|
||||
<logger name="cron" level="info">
|
||||
<appender-ref ref="cron"/>
|
||||
</logger>
|
||||
<!--系统发布日志-->
|
||||
<logger name="publish" level="info">
|
||||
<appender-ref ref="publish"/>
|
||||
</logger>
|
||||
</configuration>
|
||||
@ -1,112 +0,0 @@
|
||||
<?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>
|
||||
|
||||
<!-- 发布日志输出 -->
|
||||
<appender name="publish" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${log.path}/publish.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<!-- 按天回滚 daily -->
|
||||
<fileNamePattern>${log.path}/publish.%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>
|
||||
|
||||
<!--系统发布日志-->
|
||||
<logger name="publish" level="debug">
|
||||
<appender-ref ref="publish"/>
|
||||
</logger>
|
||||
</configuration>
|
||||
@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-cms</artifactId>
|
||||
<version>1.5.4</version>
|
||||
<version>1.5.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>chestnut-cms-advertisement</artifactId>
|
||||
|
||||
@ -30,13 +30,15 @@ import java.util.function.Supplier;
|
||||
* @author 兮玥
|
||||
* @email 190785909@qq.com
|
||||
*/
|
||||
@Component(IMonitoredCache.BEAN_PREFIX + AdMonitoredCache.ID)
|
||||
@Component(IMonitoredCache.BEAN_PREFIX + AdNameMonitoredCache.ID)
|
||||
@RequiredArgsConstructor
|
||||
public class AdMonitoredCache implements IMonitoredCache<Map<String, String>> {
|
||||
public class AdNameMonitoredCache implements IMonitoredCache<Map<String, String>> {
|
||||
|
||||
public static final String ID = "AD";
|
||||
public static final String ID = "AD_ID2NAME";
|
||||
|
||||
private static final String CACHE_PREFIX = CMSConfig.CachePrefix + "adv-ids";
|
||||
static final String NAME = "{MONITORED.CACHE." + ID + "}";
|
||||
|
||||
private static final String CACHE_PREFIX = CMSConfig.CachePrefix + "adv-id2name";
|
||||
|
||||
private final RedisCache redisCache;
|
||||
|
||||
@ -47,7 +49,7 @@ public class AdMonitoredCache implements IMonitoredCache<Map<String, String>> {
|
||||
|
||||
@Override
|
||||
public String getCacheName() {
|
||||
return "{MONITORED.CACHE.AD}";
|
||||
return NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -64,7 +66,15 @@ public class AdMonitoredCache implements IMonitoredCache<Map<String, String>> {
|
||||
return redisCache.getCacheMap(CACHE_PREFIX, String.class, supplier);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
this.redisCache.deleteObject(CACHE_PREFIX);
|
||||
public String getCacheValue(Long adId) {
|
||||
return redisCache.getCacheMapValue(CACHE_PREFIX, adId.toString());
|
||||
}
|
||||
|
||||
public void update(Long advertisementId, String adName) {
|
||||
this.redisCache.setCacheMapValue(CACHE_PREFIX, advertisementId.toString(), adName);
|
||||
}
|
||||
|
||||
public void delete(Long advertisementId) {
|
||||
this.redisCache.deleteCacheMapValue(CACHE_PREFIX, advertisementId.toString());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2022-2025 兮玥(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.
|
||||
*/
|
||||
package com.chestnut.advertisement.cache;
|
||||
|
||||
import com.chestnut.common.redis.IMonitoredCache;
|
||||
import com.chestnut.common.redis.RedisCache;
|
||||
import com.chestnut.contentcore.config.CMSConfig;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* AdRedirectUrlMonitoredCache
|
||||
*
|
||||
* @author 兮玥
|
||||
* @email 190785909@qq.com
|
||||
*/
|
||||
@Component(IMonitoredCache.BEAN_PREFIX + AdRedirectUrlMonitoredCache.ID)
|
||||
@RequiredArgsConstructor
|
||||
public class AdRedirectUrlMonitoredCache implements IMonitoredCache<String> {
|
||||
|
||||
public static final String ID = "AD_ID2URL";
|
||||
|
||||
static final String NAME = "{MONITORED.CACHE." + ID + "}";
|
||||
|
||||
private static final String CACHE_PREFIX = CMSConfig.CachePrefix + "adv-id2url:";
|
||||
|
||||
private final RedisCache redisCache;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCacheName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCacheKey() {
|
||||
return CACHE_PREFIX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCache(String cacheKey) {
|
||||
return redisCache.getCacheObject(cacheKey, String.class);
|
||||
}
|
||||
|
||||
public String getCacheValue(Long siteId, Long advertisementId) {
|
||||
return redisCache.getCacheObject(CACHE_PREFIX + siteId + ":" + advertisementId.toString(), String.class);
|
||||
}
|
||||
|
||||
public void update(Long siteId, Long advertisementId, String redirectUrl) {
|
||||
this.redisCache.setCacheObject(CACHE_PREFIX + siteId + ":" + advertisementId.toString(), redirectUrl);
|
||||
}
|
||||
|
||||
public void delete(Long siteId, Long advertisementId) {
|
||||
this.redisCache.deleteObject(CACHE_PREFIX + siteId + ":" + advertisementId.toString());
|
||||
}
|
||||
}
|
||||
@ -15,12 +15,16 @@
|
||||
*/
|
||||
package com.chestnut.advertisement.controller.front;
|
||||
|
||||
import com.chestnut.advertisement.cache.AdNameMonitoredCache;
|
||||
import com.chestnut.advertisement.service.IAdvertisementService;
|
||||
import com.chestnut.advertisement.stat.AdClickStatEventHandler;
|
||||
import com.chestnut.advertisement.stat.AdViewStatEventHandler;
|
||||
import com.chestnut.common.redis.RedisCache;
|
||||
import com.chestnut.common.security.web.BaseRestController;
|
||||
import com.chestnut.common.utils.IdUtils;
|
||||
import com.chestnut.common.utils.JacksonUtils;
|
||||
import com.chestnut.common.utils.ServletUtils;
|
||||
import com.chestnut.common.utils.StringUtils;
|
||||
import com.chestnut.stat.core.StatEvent;
|
||||
import com.chestnut.stat.service.impl.StatEventService;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
@ -33,8 +37,6 @@ import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
@ -51,13 +53,28 @@ public class AdApiController extends BaseRestController {
|
||||
|
||||
private final StatEventService statEventService;
|
||||
|
||||
private final IAdvertisementService advertisementService;
|
||||
|
||||
private final AdNameMonitoredCache adNameMonitoredCache;
|
||||
|
||||
private final RedisCache redisCache;
|
||||
|
||||
@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));
|
||||
if (!IdUtils.validate(siteId) || !IdUtils.validate(advertisementId)) {
|
||||
log.warn("Invalid sid/aid: sid = {}, aid = {}", siteId, advertisementId);
|
||||
return;
|
||||
}
|
||||
String redirectUrl = advertisementService.getRedirectUrlByAdId(siteId, advertisementId);
|
||||
if (StringUtils.isEmpty(redirectUrl)) {
|
||||
// TODO 跳转公共错误页面
|
||||
response.getWriter().write("Invalid advertisement.");
|
||||
return;
|
||||
}
|
||||
dealAdClick(siteId, advertisementId);
|
||||
response.sendRedirect(redirectUrl);
|
||||
}
|
||||
|
||||
@GetMapping("/click")
|
||||
@ -66,6 +83,15 @@ public class AdApiController extends BaseRestController {
|
||||
log.warn("Invalid sid/aid: sid = {}, aid = {}", siteId, advertisementId);
|
||||
return;
|
||||
}
|
||||
boolean hasAd = redisCache.hasMapKey(adNameMonitoredCache.getCacheKey(), advertisementId.toString());
|
||||
if (!hasAd) {
|
||||
log.warn("Invalid advertisement id: {}", advertisementId);
|
||||
return;
|
||||
}
|
||||
dealAdClick(siteId, advertisementId);
|
||||
}
|
||||
|
||||
private void dealAdClick(Long siteId, Long advertisementId) {
|
||||
StatEvent evt = new StatEvent();
|
||||
evt.setType(AdClickStatEventHandler.TYPE);
|
||||
ObjectNode objectNode = JacksonUtils.objectNode();
|
||||
@ -83,6 +109,11 @@ public class AdApiController extends BaseRestController {
|
||||
log.warn("Invalid sid/aid: sid = {}, aid = {}", siteId, advertisementId);
|
||||
return;
|
||||
}
|
||||
boolean hasAd = redisCache.hasMapKey(adNameMonitoredCache.getCacheKey(), advertisementId.toString());
|
||||
if (!hasAd) {
|
||||
log.warn("Invalid advertisement id: {}", advertisementId);
|
||||
return;
|
||||
}
|
||||
StatEvent evt = new StatEvent();
|
||||
evt.setType(AdViewStatEventHandler.TYPE);
|
||||
ObjectNode objectNode = JacksonUtils.objectNode();
|
||||
|
||||
@ -15,15 +15,14 @@
|
||||
*/
|
||||
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;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 广告数据管理Service
|
||||
*/
|
||||
@ -36,6 +35,15 @@ public interface IAdvertisementService extends IService<CmsAdvertisement> {
|
||||
*/
|
||||
Map<String, String> getAdvertisementMap();
|
||||
|
||||
/**
|
||||
* 获取广告跳转地址
|
||||
*
|
||||
* @param siteId
|
||||
* @param advertisementId
|
||||
* @return
|
||||
*/
|
||||
String getRedirectUrlByAdId(Long siteId, Long advertisementId);
|
||||
|
||||
/**
|
||||
* 添加广告数据
|
||||
*
|
||||
|
||||
@ -18,7 +18,8 @@ package com.chestnut.advertisement.service.impl;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.chestnut.advertisement.AdSpacePageWidgetType;
|
||||
import com.chestnut.advertisement.IAdvertisementType;
|
||||
import com.chestnut.advertisement.cache.AdMonitoredCache;
|
||||
import com.chestnut.advertisement.cache.AdNameMonitoredCache;
|
||||
import com.chestnut.advertisement.cache.AdRedirectUrlMonitoredCache;
|
||||
import com.chestnut.advertisement.domain.CmsAdvertisement;
|
||||
import com.chestnut.advertisement.mapper.CmsAdvertisementMapper;
|
||||
import com.chestnut.advertisement.pojo.dto.AdvertisementDTO;
|
||||
@ -32,20 +33,16 @@ import com.chestnut.contentcore.core.IPageWidgetType;
|
||||
import com.chestnut.contentcore.domain.CmsPageWidget;
|
||||
import com.chestnut.contentcore.domain.CmsSite;
|
||||
import com.chestnut.contentcore.properties.SiteApiUrlProperty;
|
||||
import com.chestnut.contentcore.publish.IStaticizeType;
|
||||
import com.chestnut.contentcore.service.IPageWidgetService;
|
||||
import com.chestnut.contentcore.service.ISiteService;
|
||||
import com.chestnut.system.fixed.dict.EnableOrDisable;
|
||||
import com.chestnut.system.security.StpAdminUtil;
|
||||
import freemarker.template.TemplateException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
@ -61,9 +58,11 @@ import java.util.stream.Collectors;
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class AdvertisementServiceImpl extends ServiceImpl<CmsAdvertisementMapper, CmsAdvertisement>
|
||||
implements IAdvertisementService {
|
||||
implements IAdvertisementService, CommandLineRunner {
|
||||
|
||||
private final AdMonitoredCache adCache;
|
||||
private final AdNameMonitoredCache adNameCache;
|
||||
|
||||
private final AdRedirectUrlMonitoredCache adRedirectUrlCache;
|
||||
|
||||
private final Map<String, IAdvertisementType> advertisementTypes;
|
||||
|
||||
@ -85,7 +84,7 @@ public class AdvertisementServiceImpl extends ServiceImpl<CmsAdvertisementMapper
|
||||
|
||||
@Override
|
||||
public Map<String, String> getAdvertisementMap() {
|
||||
return adCache.getCache(() -> {
|
||||
return adNameCache.getCache(() -> {
|
||||
return this.lambdaQuery()
|
||||
.select(List.of(CmsAdvertisement::getAdvertisementId, CmsAdvertisement::getName))
|
||||
.list().stream()
|
||||
@ -93,6 +92,11 @@ public class AdvertisementServiceImpl extends ServiceImpl<CmsAdvertisementMapper
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRedirectUrlByAdId(Long siteId, Long advertisementId) {
|
||||
return adRedirectUrlCache.getCacheValue(siteId, advertisementId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CmsAdvertisement addAdvertisement(AdvertisementDTO dto) {
|
||||
CmsPageWidget pageWidget = this.pageWidgetService.getById(dto.getAdSpaceId());
|
||||
@ -106,8 +110,8 @@ public class AdvertisementServiceImpl extends ServiceImpl<CmsAdvertisementMapper
|
||||
advertisement.setState(EnableOrDisable.ENABLE);
|
||||
advertisement.createBy(dto.getOperator().getUsername());
|
||||
this.save(advertisement);
|
||||
|
||||
this.adCache.clear();
|
||||
// 更新缓存
|
||||
this.updateCache(advertisement);
|
||||
return advertisement;
|
||||
}
|
||||
|
||||
@ -120,14 +124,20 @@ public class AdvertisementServiceImpl extends ServiceImpl<CmsAdvertisementMapper
|
||||
BeanUtils.copyProperties(dto, advertisement, "adSpaceId");
|
||||
advertisement.updateBy(dto.getOperator().getUsername());
|
||||
this.updateById(advertisement);
|
||||
// 更新缓存
|
||||
this.updateCache(advertisement);
|
||||
return advertisement;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void deleteAdvertisement(List<Long> advertisementIds) {
|
||||
this.removeByIds(advertisementIds);
|
||||
this.adCache.clear();
|
||||
List<CmsAdvertisement> advertisements = this.listByIds(advertisementIds);
|
||||
this.removeByIds(advertisements);
|
||||
// 更新缓存
|
||||
for (CmsAdvertisement advertisement : advertisements) {
|
||||
this.deleteCache(advertisement.getSiteId(), advertisement.getAdvertisementId());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -168,7 +178,21 @@ public class AdvertisementServiceImpl extends ServiceImpl<CmsAdvertisementMapper
|
||||
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);
|
||||
return apiUrl + "api/adv/redirect?sid=" + adv.getSiteId() + "&aid=" + adv.getAdvertisementId();
|
||||
}
|
||||
|
||||
private void updateCache(CmsAdvertisement advertisement) {
|
||||
this.adNameCache.update(advertisement.getAdvertisementId(), advertisement.getName());
|
||||
this.adRedirectUrlCache.update(advertisement.getSiteId(), advertisement.getAdvertisementId(), advertisement.getRedirectUrl());
|
||||
}
|
||||
|
||||
private void deleteCache(Long siteId, Long advertisementId) {
|
||||
this.adNameCache.delete(advertisementId);
|
||||
this.adRedirectUrlCache.delete(siteId, advertisementId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(String... args) throws Exception {
|
||||
this.list().forEach(this::updateCache);
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,4 +22,5 @@ STAT.MENU.CmsAdViewLog=广告展现日志
|
||||
SCHEDULED_TASK.AdvertisementStatJob=广告统计任务
|
||||
SCHEDULED_TASK.AdvertisementPublishJob=广告定时发布下线任务
|
||||
|
||||
MONITORED.CACHE.AD=广告
|
||||
MONITORED.CACHE.AD_ID2NAME=广告名称
|
||||
MONITORED.CACHE.AD_ID2URL=广告跳转链接
|
||||
@ -22,4 +22,5 @@ STAT.MENU.CmsAdViewLog=View Logs
|
||||
SCHEDULED_TASK.AdvertisementStatJob=AD Statistics Task
|
||||
SCHEDULED_TASK.AdvertisementPublishJob=AD Publish/Offline Task
|
||||
|
||||
MONITORED.CACHE.AD=Advertisement
|
||||
MONITORED.CACHE.AD_ID2NAME=AD Name
|
||||
MONITORED.CACHE.AD_ID2URL=AD Redirect URL
|
||||
@ -22,4 +22,5 @@ STAT.MENU.CmsAdViewLog=廣告展現日誌
|
||||
SCHEDULED_TASK.AdvertisementStatJob=廣告統計任務
|
||||
SCHEDULED_TASK.AdvertisementPublishJob=廣告定時發布下線任務
|
||||
|
||||
MONITORED.CACHE.AD=廣告
|
||||
MONITORED.CACHE.AD_ID2NAME=廣告名稱
|
||||
MONITORED.CACHE.AD_ID2URL=廣告跳轉鏈接
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-cms</artifactId>
|
||||
<version>1.5.4</version>
|
||||
<version>1.5.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>chestnut-cms-article</artifactId>
|
||||
|
||||
@ -18,6 +18,7 @@ package com.chestnut.article;
|
||||
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.format.ArticleBodyFormat_RichText;
|
||||
import com.chestnut.article.service.IArticleService;
|
||||
import com.chestnut.common.async.AsyncTaskManager;
|
||||
import com.chestnut.common.utils.JacksonUtils;
|
||||
@ -32,6 +33,7 @@ import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
/**
|
||||
@ -47,6 +49,8 @@ public class ArticleCoreDataHandler implements ICoreDataHandler {
|
||||
|
||||
private final IArticleService articleService;
|
||||
|
||||
private final Map<String, IArticleBodyFormat> articleBodyFormatMap;
|
||||
|
||||
@Override
|
||||
public void onSiteExport(SiteExportContext context) {
|
||||
// cms_article_detail
|
||||
@ -101,6 +105,9 @@ public class ArticleCoreDataHandler implements ICoreDataHandler {
|
||||
}
|
||||
html.append(contentHtml.substring(index));
|
||||
data.setContentHtml(html.toString());
|
||||
if (!articleBodyFormatMap.containsKey(IArticleBodyFormat.BEAN_PREFIX + data.getFormat())) {
|
||||
data.setFormat(ArticleBodyFormat_RichText.ID); // 不支持的文章格式一律设置为富文本格式
|
||||
}
|
||||
articleService.dao().save(data);
|
||||
} catch (Exception e) {
|
||||
AsyncTaskManager.addErrMessage("导入文章数据`" + oldContentId + "`失败:" + e.getMessage());
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-cms</artifactId>
|
||||
<version>1.5.4</version>
|
||||
<version>1.5.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>chestnut-cms-block</artifactId>
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
package com.chestnut.block;
|
||||
|
||||
import com.chestnut.block.domain.vo.ManualPageWidgetVO;
|
||||
import com.chestnut.common.annotation.XComment;
|
||||
import com.chestnut.common.utils.JacksonUtils;
|
||||
import com.chestnut.common.utils.StringUtils;
|
||||
import com.chestnut.contentcore.core.IPageWidget;
|
||||
@ -120,13 +121,15 @@ public class ManualPageWidgetType implements IPageWidgetType {
|
||||
|
||||
private String summary;
|
||||
|
||||
@Deprecated
|
||||
private String url;
|
||||
|
||||
@XComment("与url字段同值,仅为习惯添加")
|
||||
private String link;
|
||||
|
||||
private String logo;
|
||||
|
||||
// TODO 下个大版本移除,在模板使用${iurl(logo)}
|
||||
@Deprecated(forRemoval = true)
|
||||
private String logoSrc;
|
||||
|
||||
private LocalDateTime publishDate;
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-cms</artifactId>
|
||||
<version>1.5.4</version>
|
||||
<version>1.5.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>chestnut-cms-comment</artifactId>
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-cms</artifactId>
|
||||
<version>1.5.4</version>
|
||||
<version>1.5.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>chestnut-cms-contentcore</artifactId>
|
||||
|
||||
@ -15,10 +15,9 @@
|
||||
*/
|
||||
package com.chestnut.contentcore.config.properties;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* CMS配置属性
|
||||
@ -44,5 +43,10 @@ public class CMSProperties {
|
||||
/**
|
||||
* 系统启动时是否清空cacheName前缀的所有缓存
|
||||
*/
|
||||
private Boolean resetCache = true;
|
||||
private Boolean resetCache = false;
|
||||
|
||||
/**
|
||||
* 资源分片文件过期时间,默认:24小时,单位:秒
|
||||
*/
|
||||
private long resourceChunkExpireSeconds = 24 * 60 * 60;
|
||||
}
|
||||
|
||||
@ -89,10 +89,14 @@ public class CoreController extends BaseRestController {
|
||||
IInternalDataType internalDataType = ContentCoreUtils.getInternalDataType(dataType);
|
||||
Assert.notNull(internalDataType, () -> ContentCoreErrorCode.UNSUPPORTED_INTERNAL_DATA_TYPE.exception(dataType));
|
||||
|
||||
try {
|
||||
IInternalDataType.RequestData data = new IInternalDataType.RequestData(dataId, pageIndex, publishPipe,
|
||||
true, ServletUtils.getParamMap(ServletUtils.getRequest()));
|
||||
String pageData = internalDataType.getPageData(data);
|
||||
response.getWriter().write(pageData);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace(response.getWriter());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -107,7 +111,7 @@ public class CoreController extends BaseRestController {
|
||||
public void browse(@PathVariable("dataType") String dataType, @PathVariable("dataId") Long dataId,
|
||||
@RequestParam(value = "pp") String publishPipe,
|
||||
@RequestParam(value = "pi", required = false, defaultValue = "1") Integer pageIndex)
|
||||
throws IOException, TemplateException {
|
||||
throws IOException {
|
||||
HttpServletResponse response = ServletUtils.getResponse();
|
||||
|
||||
response.setCharacterEncoding(Charset.defaultCharset().displayName());
|
||||
@ -115,10 +119,14 @@ public class CoreController extends BaseRestController {
|
||||
IInternalDataType internalDataType = ContentCoreUtils.getInternalDataType(dataType);
|
||||
Assert.notNull(internalDataType, () -> ContentCoreErrorCode.UNSUPPORTED_INTERNAL_DATA_TYPE.exception(dataType));
|
||||
|
||||
try {
|
||||
IInternalDataType.RequestData data = new IInternalDataType.RequestData(dataId, pageIndex, publishPipe,
|
||||
false, ServletUtils.getParamMap(ServletUtils.getRequest()));
|
||||
String pageData = internalDataType.getPageData(data);
|
||||
response.getWriter().write(pageData);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace(response.getWriter());
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/cms/ssi/virtual/")
|
||||
|
||||
@ -46,6 +46,7 @@ import com.chestnut.contentcore.util.InternalUrlUtils;
|
||||
import com.chestnut.system.security.AdminUserType;
|
||||
import com.chestnut.system.security.StpAdminUtil;
|
||||
import com.chestnut.system.validator.LongId;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@ -94,10 +95,12 @@ public class ResourceController extends BaseRestController {
|
||||
public R<?> listData(@RequestParam(value = "name", required = false) String name,
|
||||
@RequestParam(value = "resourceType", required = false) String resourceType,
|
||||
@RequestParam(value = "owner", required = false, defaultValue = "false") boolean owner,
|
||||
@RequestParam(value = "siteId", required = false, defaultValue = "0") Long siteId,
|
||||
@RequestParam(value = "beginTime", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date beginTime,
|
||||
@RequestParam(value = "endTime", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date endTime) {
|
||||
@RequestParam(value = "endTime", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date endTime,
|
||||
HttpServletRequest request) {
|
||||
PageRequest pr = this.getPageRequest();
|
||||
CmsSite site = this.siteService.getCurrentSite(ServletUtils.getRequest());
|
||||
CmsSite site = siteService.getSiteOrCurrent(siteId, request);
|
||||
LambdaQueryWrapper<CmsResource> q = new LambdaQueryWrapper<CmsResource>()
|
||||
.eq(CmsResource::getSiteId, site.getSiteId())
|
||||
.like(StringUtils.isNotEmpty(name), CmsResource::getFileName, name)
|
||||
|
||||
@ -45,6 +45,7 @@ import com.chestnut.contentcore.util.CmsPrivUtils;
|
||||
import com.chestnut.contentcore.util.SiteUtils;
|
||||
import com.chestnut.system.security.AdminUserType;
|
||||
import com.chestnut.system.security.StpAdminUtil;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
@ -83,9 +84,11 @@ public class TemplateController extends BaseRestController {
|
||||
)
|
||||
@GetMapping
|
||||
public R<?> getTemplateList(@RequestParam(value = "publishPipeCode", required = false) String publishPipeCode,
|
||||
@RequestParam(value = "filename", required = false) String filename) {
|
||||
@RequestParam(value = "siteId", required = false, defaultValue = "0") Long siteId,
|
||||
@RequestParam(value = "filename", required = false) String filename,
|
||||
HttpServletRequest request) {
|
||||
PageRequest pr = this.getPageRequest();
|
||||
CmsSite site = this.siteService.getCurrentSite(ServletUtils.getRequest());
|
||||
CmsSite site = siteService.getSiteOrCurrent(siteId, request);
|
||||
this.templateService.scanTemplates(site);
|
||||
Page<CmsTemplate> page = this.templateService.lambdaQuery().eq(CmsTemplate::getSiteId, site.getSiteId())
|
||||
.eq(StringUtils.isNotEmpty(publishPipeCode), CmsTemplate::getPublishPipeCode, publishPipeCode)
|
||||
|
||||
@ -15,17 +15,15 @@
|
||||
*/
|
||||
package com.chestnut.contentcore.service;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.chestnut.common.security.domain.LoginUser;
|
||||
import com.chestnut.contentcore.domain.CmsSite;
|
||||
import com.chestnut.contentcore.domain.dto.SiteDTO;
|
||||
import com.chestnut.contentcore.domain.dto.SiteDefaultTemplateDTO;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
public interface ISiteService extends IService<CmsSite> {
|
||||
|
||||
/**
|
||||
@ -38,6 +36,15 @@ public interface ISiteService extends IService<CmsSite> {
|
||||
*/
|
||||
boolean checkSiteUnique(String siteName, String sitePath, Long siteId);
|
||||
|
||||
/**
|
||||
* 获取指定id站点数据,如果不存在则返回当前站点数据
|
||||
*
|
||||
* @param siteId
|
||||
* @param request
|
||||
* @return
|
||||
*/
|
||||
CmsSite getSiteOrCurrent(Long siteId, HttpServletRequest request);
|
||||
|
||||
/**
|
||||
* 获取当前站点,保存在token中
|
||||
*/
|
||||
|
||||
@ -375,7 +375,7 @@ public class PublishServiceImpl implements IPublishService, ApplicationContextAw
|
||||
templateContext.setOtherFileName(contentLink + "&pi=" + TemplateContext.PlaceHolder_PageNo);
|
||||
// staticize
|
||||
this.staticizeService.process(templateContext, writer);
|
||||
logger.debug("[{}][{}]内容模板解析:{},耗时:{}", requestData.getPublishPipeCode(), contentType.getName(), content.getTitle(),
|
||||
logger.debug("[{}][{}]内容模板解析:{},耗时:{}", requestData.getPublishPipeCode(), contentType.getId(), content.getTitle(),
|
||||
System.currentTimeMillis() - s);
|
||||
return writer.toString();
|
||||
}
|
||||
@ -477,7 +477,7 @@ public class PublishServiceImpl implements IPublishService, ApplicationContextAw
|
||||
templateType.initTemplateData(content.getContentId(), templateContext);
|
||||
// staticize
|
||||
this.staticizeService.process(templateContext, writer);
|
||||
logger.debug("[{}][{}]内容扩展模板解析:{},耗时:{}", publishPipeCode, contentType.getName(), content.getTitle(),
|
||||
logger.debug("[{}][{}]内容扩展模板解析:{},耗时:{}", publishPipeCode, contentType.getId(), content.getTitle(),
|
||||
System.currentTimeMillis() - s);
|
||||
return writer.toString();
|
||||
}
|
||||
|
||||
@ -24,6 +24,7 @@ import com.chestnut.common.storage.StorageReadArgs.StorageReadArgsBuilder;
|
||||
import com.chestnut.common.storage.exception.StorageErrorCode;
|
||||
import com.chestnut.common.utils.*;
|
||||
import com.chestnut.common.utils.file.FileExUtils;
|
||||
import com.chestnut.common.utils.image.ImageUtils;
|
||||
import com.chestnut.contentcore.core.IResourceStat;
|
||||
import com.chestnut.contentcore.core.IResourceType;
|
||||
import com.chestnut.contentcore.core.impl.InternalDataType_Resource;
|
||||
@ -43,7 +44,7 @@ import com.chestnut.system.fixed.dict.EnableOrDisable;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.tomcat.util.http.fileupload.IOUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.File;
|
||||
@ -162,7 +163,7 @@ public class ResourceServiceImpl extends ServiceImpl<CmsResourceMapper, CmsResou
|
||||
|
||||
@Override
|
||||
public CmsResource addBase64Image(CmsSite site, String operator, String base64Data) throws IOException {
|
||||
if (!base64Data.startsWith("data:image/")) {
|
||||
if (!ImageUtils.isBase64Image(base64Data)) {
|
||||
return null;
|
||||
}
|
||||
String suffix = base64Data.substring(11, base64Data.indexOf(";"));
|
||||
@ -333,7 +334,7 @@ public class ResourceServiceImpl extends ServiceImpl<CmsResourceMapper, CmsResou
|
||||
String imgTag = matcher.group();
|
||||
String src = matcher.group(1);
|
||||
try {
|
||||
if (StringUtils.startsWithIgnoreCase(src, "data:image/")) {
|
||||
if (ImageUtils.isBase64Image(src)) {
|
||||
// base64图片保存到资源库
|
||||
CmsResource resource = addBase64Image(site, operator, src);
|
||||
if (Objects.nonNull(resource)) {
|
||||
@ -347,7 +348,7 @@ public class ResourceServiceImpl extends ServiceImpl<CmsResourceMapper, CmsResou
|
||||
}
|
||||
}
|
||||
} catch (Exception e1) {
|
||||
String imgSrc = (src.startsWith("data:image/") ? src.substring(0, 20) : src);
|
||||
String imgSrc = (src.startsWith("data:image/") ? "base64Img" : src);
|
||||
log.warn("Save image failed: " + imgSrc);
|
||||
AsyncTaskManager.addErrMessage("Download remote image failed: " + imgSrc);
|
||||
}
|
||||
|
||||
@ -84,6 +84,18 @@ public class SiteServiceImpl extends ServiceImpl<CmsSiteMapper, CmsSite> impleme
|
||||
return site;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CmsSite getSiteOrCurrent(Long siteId, HttpServletRequest request) {
|
||||
CmsSite site = null;
|
||||
if (IdUtils.validate(siteId)) {
|
||||
site = getSite(siteId);
|
||||
}
|
||||
if (Objects.isNull(site)) {
|
||||
site = getCurrentSite(request);
|
||||
}
|
||||
return site;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CmsSite getCurrentSite(HttpServletRequest request) {
|
||||
LoginUser loginUser = StpAdminUtil.getLoginUser();
|
||||
|
||||
@ -39,10 +39,7 @@ import org.apache.commons.io.FileUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 站点导入导出
|
||||
@ -75,6 +72,8 @@ public class SiteThemeService {
|
||||
|
||||
private final List<ICoreDataHandler> contentCoreHandlers;
|
||||
|
||||
private final Map<String, IContentType> contentTypes;
|
||||
|
||||
public AsyncTask importSiteTheme(CmsSite site, final File zipFile, LoginUser operator) {
|
||||
AsyncTask asyncTask = new AsyncTask() {
|
||||
|
||||
@ -232,6 +231,9 @@ public class SiteThemeService {
|
||||
list.forEach(content -> {
|
||||
Long sourceContentId = content.getContentId();
|
||||
try {
|
||||
if (!contentTypes.containsKey(IContentType.BEAN_NAME_PREFIX + content.getContentType())) {
|
||||
throw new RuntimeException("Unsupported content type: " + content.getContentType());
|
||||
}
|
||||
CmsCatalog catalog = catalogService.getCatalog(context.getCatalogIdMap().get(content.getCatalogId()));
|
||||
if (Objects.isNull(catalog)) {
|
||||
throw new RuntimeException("Catalog is missing.");
|
||||
|
||||
@ -28,7 +28,10 @@ import com.chestnut.contentcore.fixed.config.TemplateSuffix;
|
||||
import com.chestnut.contentcore.properties.SiteApiUrlProperty;
|
||||
import com.chestnut.system.security.StpAdminUtil;
|
||||
import freemarker.core.Environment;
|
||||
import freemarker.template.TemplateHashModel;
|
||||
import freemarker.template.TemplateModel;
|
||||
import freemarker.template.TemplateModelException;
|
||||
import freemarker.template.TemplateNumberModel;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@ -119,6 +122,14 @@ public class TemplateUtils {
|
||||
*/
|
||||
public final static String TemplateVariable_ClientType = "ClientType";
|
||||
|
||||
public static String evalPrefix(Environment env) throws TemplateModelException {
|
||||
return FreeMarkerUtils.evalStringVariable(env, TemplateVariable_Prefix);
|
||||
}
|
||||
|
||||
public static String evalApiPrefix(Environment env) throws TemplateModelException {
|
||||
return FreeMarkerUtils.evalStringVariable(env, TemplateVariable_ApiPrefix);
|
||||
}
|
||||
|
||||
public static Long evalSiteId(Environment env) throws TemplateModelException {
|
||||
return FreeMarkerUtils.evalLongVariable(env, "Site.siteId");
|
||||
}
|
||||
@ -127,10 +138,42 @@ public class TemplateUtils {
|
||||
return FreeMarkerUtils.evalLongVariable(env, "Catalog.catalogId");
|
||||
}
|
||||
|
||||
public static Long findCatalogId(Environment env) {
|
||||
try {
|
||||
TemplateModel model = env.getVariable("Catalog");
|
||||
if (!(model instanceof TemplateHashModel)) {
|
||||
return null;
|
||||
}
|
||||
model = ((TemplateHashModel) model).get("catalogId");
|
||||
if (model instanceof TemplateNumberModel m) {
|
||||
return m.getAsNumber().longValue();
|
||||
}
|
||||
} catch (TemplateModelException e) {
|
||||
// ignore
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Long evalContentId(Environment env) throws TemplateModelException {
|
||||
return FreeMarkerUtils.evalLongVariable(env, "Content.contentId");
|
||||
}
|
||||
|
||||
public static Long findContentId(Environment env) {
|
||||
try {
|
||||
TemplateModel model = env.getVariable("Content");
|
||||
if (!(model instanceof TemplateHashModel)) {
|
||||
return null;
|
||||
}
|
||||
model = ((TemplateHashModel) model).get("contentId");
|
||||
if (model instanceof TemplateNumberModel m) {
|
||||
return m.getAsNumber().longValue();
|
||||
}
|
||||
} catch (TemplateModelException e) {
|
||||
// ignore
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加站点数据到模板上线文变量中
|
||||
*
|
||||
|
||||
@ -205,6 +205,7 @@ SCHEDULED_TASK.SitePublishJobHandler=定时发布任务
|
||||
SCHEDULED_TASK.ContentTopCancelJobHandler=内容置顶取消任务
|
||||
SCHEDULED_TASK.UpdateDynamicDataJobHandler=保存内容动态数据任务
|
||||
SCHEDULED_TASK.ContentOfflineJobHandler=内容定时下线任务
|
||||
SCHEDULED_TASK.ResourceChunkClearJobHandler=资源上传分片文件过期删除任务
|
||||
|
||||
# 缓存监控
|
||||
MONITORED.CACHE.SITE=站点
|
||||
|
||||
@ -205,6 +205,7 @@ SCHEDULED_TASK.SitePublishJobHandler=Site Publish Task
|
||||
SCHEDULED_TASK.ContentTopCancelJobHandler=Content Top Cancel Task
|
||||
SCHEDULED_TASK.UpdateDynamicDataJobHandler=Save Content Dynamic Data Task
|
||||
SCHEDULED_TASK.ContentOfflineJobHandler=Content Offline Task
|
||||
SCHEDULED_TASK.ResourceChunkClearJobHandler=Remove Expired Resource Chunks Task
|
||||
|
||||
# 内容静态化子目录划分规则
|
||||
CONTENT_PATH_RULE.IdHash=/content id hash/
|
||||
|
||||
@ -205,6 +205,7 @@ SCHEDULED_TASK.SitePublishJobHandler=定時發布任務
|
||||
SCHEDULED_TASK.ContentTopCancelJobHandler=內容置頂取消任務
|
||||
SCHEDULED_TASK.UpdateDynamicDataJobHandler=保存內容動態數據任務
|
||||
SCHEDULED_TASK.ContentOfflineJobHandler=內容定時下線任務
|
||||
SCHEDULED_TASK.ResourceChunkClearJobHandler=資源上傳分片文件過期刪除任務
|
||||
|
||||
# 缓存监控
|
||||
MONITORED.CACHE.SITE=站點
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-cms</artifactId>
|
||||
<version>1.5.4</version>
|
||||
<version>1.5.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>chestnut-cms-customform</artifactId>
|
||||
|
||||
@ -16,7 +16,6 @@
|
||||
package com.chestnut.customform;
|
||||
|
||||
import com.chestnut.customform.domain.CmsCustomFormData;
|
||||
import com.chestnut.exmodel.domain.CmsExtendModelData;
|
||||
import com.chestnut.xmodel.core.IMetaModelType;
|
||||
import com.chestnut.xmodel.core.MetaModelField;
|
||||
import com.chestnut.xmodel.core.impl.MetaControlType_Input;
|
||||
@ -68,8 +67,12 @@ public class CmsCustomFormMetaModelType implements IMetaModelType {
|
||||
"site_id", false, MetaControlType_Input.TYPE, MetaFieldType.LONG);
|
||||
public static final MetaModelField FIELD_CLIENT_IP = new MetaModelField("IP", "clientIp",
|
||||
"client_ip", false, MetaControlType_Input.TYPE, MetaFieldType.SHORT_TEXT);
|
||||
// 用户唯一标识(未登录)
|
||||
public static final MetaModelField FIELD_UUID = new MetaModelField("UUID", "uuid",
|
||||
"uuid", false, MetaControlType_Input.TYPE, MetaFieldType.MEDIUM_TEXT);
|
||||
// 会员ID(已登录)
|
||||
public static final MetaModelField FIELD_UID = new MetaModelField("UID", "uid",
|
||||
"uid", false, MetaControlType_Input.TYPE, MetaFieldType.LONG);
|
||||
public static final MetaModelField FIELD_CREATE_TIME = new MetaModelField("创建时间", "createTime",
|
||||
"create_time", false, MetaControlType_Input.TYPE, MetaFieldType.DATETIME);
|
||||
}
|
||||
|
||||
@ -16,11 +16,13 @@
|
||||
package com.chestnut.customform;
|
||||
|
||||
import com.chestnut.common.utils.ReflectASMUtils;
|
||||
import com.chestnut.common.utils.ServletUtils;
|
||||
import com.chestnut.common.utils.StringUtils;
|
||||
import com.chestnut.contentcore.domain.CmsSite;
|
||||
import com.chestnut.contentcore.fixed.config.SiteApiUrl;
|
||||
import com.chestnut.contentcore.properties.SiteApiUrlProperty;
|
||||
import com.chestnut.contentcore.util.SiteUtils;
|
||||
import com.chestnut.customform.domain.CmsCustomForm;
|
||||
import com.chestnut.member.security.StpMemberUtil;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@ -36,6 +38,11 @@ public class CustomFormConsts {
|
||||
|
||||
public static final String TemplateVariable_CustomForm = "CustomForm";
|
||||
|
||||
/**
|
||||
* uuid请求参数/header参数
|
||||
*/
|
||||
public static final String PARAMETER_UUID = "_cc_uuid";
|
||||
|
||||
public static String getCustomFormActionUrl(CmsSite site, String publishPipeCode) {
|
||||
return SiteApiUrlProperty.getValue(site, publishPipeCode) + "api/customform/submit";
|
||||
}
|
||||
@ -45,4 +52,26 @@ public class CustomFormConsts {
|
||||
map.put("action", getCustomFormActionUrl(site, publishPipeCode));
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试获取uuid
|
||||
* 已登录用户取登录用户ID
|
||||
* 未登录用户尝试从请求参数/header参数/cookie参数获取
|
||||
*/
|
||||
public static String tryToGetUUID(HttpServletRequest request) {
|
||||
if (StpMemberUtil.isLogin()) {
|
||||
return StpMemberUtil.getLoginUser().getUserId().toString();
|
||||
}
|
||||
String uuid = request.getParameter("uuid"); // 兼容老版本
|
||||
if (StringUtils.isEmpty(uuid)) {
|
||||
uuid = request.getParameter(PARAMETER_UUID);
|
||||
if (StringUtils.isEmpty(uuid)) {
|
||||
uuid = request.getHeader(PARAMETER_UUID);
|
||||
if (StringUtils.isEmpty(uuid)) {
|
||||
ServletUtils.getCookieValue(request, PARAMETER_UUID);
|
||||
}
|
||||
}
|
||||
}
|
||||
return uuid;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2022-2025 兮玥(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.
|
||||
*/
|
||||
package com.chestnut.customform.cache;
|
||||
|
||||
import com.chestnut.common.redis.IMonitoredCache;
|
||||
import com.chestnut.common.redis.RedisCache;
|
||||
import com.chestnut.system.SysConstants;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* CustomFormCaptchaMonitoredCache
|
||||
*
|
||||
* @author 兮玥
|
||||
* @email 190785909@qq.com
|
||||
*/
|
||||
@Component(IMonitoredCache.BEAN_PREFIX + CustomFormCaptchaMonitoredCache.ID)
|
||||
@RequiredArgsConstructor
|
||||
public class CustomFormCaptchaMonitoredCache implements IMonitoredCache<String> {
|
||||
|
||||
public static final String ID = "CustomFormCaptcha";
|
||||
|
||||
public static final String CACHE_PREFIX = "cms:customform:captcha:";
|
||||
|
||||
private final RedisCache redisCache;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCacheName() {
|
||||
return "{MONITORED.CACHE.CUSTOM_FORM_CAPTCHA}";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCacheKey() {
|
||||
return CACHE_PREFIX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCache(String cacheKey) {
|
||||
return redisCache.getCacheObject(cacheKey, String.class);
|
||||
}
|
||||
|
||||
public void deleteCache(String cacheKey) {
|
||||
this.redisCache.deleteObject(cacheKey);
|
||||
}
|
||||
|
||||
public void setCache(String cacheKey, String code, Integer captchaExpiration, TimeUnit timeUnit) {
|
||||
this.redisCache.setCacheObject(cacheKey, code, captchaExpiration, timeUnit);
|
||||
}
|
||||
}
|
||||
@ -34,6 +34,7 @@ import com.chestnut.customform.domain.dto.CustomFormAddDTO;
|
||||
import com.chestnut.customform.domain.dto.CustomFormEditDTO;
|
||||
import com.chestnut.customform.domain.vo.CustomFormVO;
|
||||
import com.chestnut.customform.permission.CustomFormPriv;
|
||||
import com.chestnut.customform.rule.ICustomFormLimitRule;
|
||||
import com.chestnut.customform.service.ICustomFormService;
|
||||
import com.chestnut.system.security.AdminUserType;
|
||||
import com.chestnut.system.security.StpAdminUtil;
|
||||
@ -66,6 +67,14 @@ public class CustomFormController extends BaseRestController {
|
||||
|
||||
private final ICustomFormService customFormService;
|
||||
|
||||
private final List<ICustomFormLimitRule> limitRules;
|
||||
|
||||
@Priv(type = AdminUserType.TYPE)
|
||||
@GetMapping("/limit_rules")
|
||||
public R<?> getLimitRules() {
|
||||
return bindSelectOptions(this.limitRules, ICustomFormLimitRule::getId, ICustomFormLimitRule::getName);
|
||||
}
|
||||
|
||||
@Priv(type = AdminUserType.TYPE, value = CustomFormPriv.View)
|
||||
@GetMapping
|
||||
public R<?> getList(@RequestParam(value = "query", required = false) String query,
|
||||
|
||||
@ -15,36 +15,46 @@
|
||||
*/
|
||||
package com.chestnut.customform.controller.front;
|
||||
|
||||
import com.chestnut.common.captcha.CaptchaType;
|
||||
import com.chestnut.common.config.CaptchaConfig;
|
||||
import com.chestnut.common.domain.R;
|
||||
import com.chestnut.common.exception.CommonErrorCode;
|
||||
import com.chestnut.common.exception.GlobalException;
|
||||
import com.chestnut.common.security.web.BaseRestController;
|
||||
import com.chestnut.common.utils.Assert;
|
||||
import com.chestnut.common.utils.IdUtils;
|
||||
import com.chestnut.common.utils.ServletUtils;
|
||||
import com.chestnut.common.utils.StringUtils;
|
||||
import com.chestnut.contentcore.core.impl.InternalDataType_Resource;
|
||||
import com.chestnut.contentcore.domain.CmsResource;
|
||||
import com.chestnut.contentcore.domain.CmsSite;
|
||||
import com.chestnut.contentcore.service.IResourceService;
|
||||
import com.chestnut.contentcore.service.ISiteService;
|
||||
import com.chestnut.customform.CmsCustomFormMetaModelType;
|
||||
import com.chestnut.customform.CustomFormConsts;
|
||||
import com.chestnut.customform.cache.CustomFormCaptchaMonitoredCache;
|
||||
import com.chestnut.customform.domain.CmsCustomForm;
|
||||
import com.chestnut.customform.exception.CustomFormErrorCode;
|
||||
import com.chestnut.customform.fixed.config.CustomFormCaptchaExpireSeconds;
|
||||
import com.chestnut.customform.service.ICustomFormApiService;
|
||||
import com.chestnut.customform.service.ICustomFormService;
|
||||
import com.chestnut.member.security.StpMemberUtil;
|
||||
import com.chestnut.system.SysConstants;
|
||||
import com.chestnut.system.annotation.IgnoreDemoMode;
|
||||
import com.chestnut.system.config.properties.SysProperties;
|
||||
import com.chestnut.system.domain.vo.ImageCaptchaVO;
|
||||
import com.chestnut.system.exception.SysErrorCode;
|
||||
import com.chestnut.system.fixed.dict.YesOrNo;
|
||||
import com.chestnut.xmodel.service.IModelDataService;
|
||||
import com.chestnut.system.validator.LongId;
|
||||
import com.google.code.kaptcha.Producer;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.util.FastByteArrayOutputStream;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Base64;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@ -61,54 +71,110 @@ public class CustomFormApiController extends BaseRestController {
|
||||
|
||||
private final ICustomFormService customFormService;
|
||||
|
||||
private final IModelDataService modelDataService;
|
||||
private final ICustomFormApiService customFormApiService;
|
||||
|
||||
private final IResourceService resourceService;
|
||||
private final CustomFormCaptchaMonitoredCache captchaCache;
|
||||
|
||||
private final ISiteService siteService;
|
||||
private final Map<String, Producer> captchaProducers;
|
||||
|
||||
private final SysProperties properties;
|
||||
|
||||
@GetMapping("/captchaImage")
|
||||
public R<?> getCaptchaImage(@RequestParam @LongId Long formId, HttpServletRequest request) {
|
||||
CmsCustomForm form = this.customFormService.getById(formId);
|
||||
Assert.notNull(form, CustomFormErrorCode.FORM_NOT_FOUND::exception);
|
||||
// 是否需要验证码
|
||||
if (!YesOrNo.isYes(form.getNeedCaptcha())) {
|
||||
return R.ok(ImageCaptchaVO.builder().captchaEnabled(false).build());
|
||||
}
|
||||
// 是否登录
|
||||
if (YesOrNo.isYes(form.getNeedLogin()) && !StpMemberUtil.isLogin()) {
|
||||
throw CustomFormErrorCode.NOT_LOGIN.exception();
|
||||
}
|
||||
String uuid = CustomFormConsts.tryToGetUUID(request);
|
||||
Assert.notEmpty(uuid, CustomFormErrorCode.MISSING_UUID::exception);
|
||||
// 保存验证码信息
|
||||
String verifyKey = CustomFormCaptchaMonitoredCache.CACHE_PREFIX + uuid;
|
||||
|
||||
String capStr;
|
||||
String code;
|
||||
BufferedImage image;
|
||||
|
||||
Producer captchaProducer = captchaProducers.get(CaptchaConfig.BEAN_PREFIX + this.properties.getCaptchaType());
|
||||
Assert.notNull(captchaProducer, () -> SysErrorCode.CAPTCHA_CONFIG_ERR.exception(this.properties.getCaptchaType()));
|
||||
// 生成验证码
|
||||
String captchaType = properties.getCaptchaType();
|
||||
if (CaptchaType.MATH.equals(captchaType)) {
|
||||
String capText = captchaProducer.createText();
|
||||
capStr = capText.substring(0, capText.lastIndexOf("@"));
|
||||
code = capText.substring(capText.lastIndexOf("@") + 1);
|
||||
image = captchaProducer.createImage(capStr);
|
||||
} else if (CaptchaType.CHAR.equals(captchaType)) {
|
||||
capStr = code = captchaProducer.createText();
|
||||
image = captchaProducer.createImage(capStr);
|
||||
} else {
|
||||
throw new GlobalException("Unknown captcha type: " + captchaType);
|
||||
}
|
||||
|
||||
Integer expireSeconds = CustomFormCaptchaExpireSeconds.getSeconds();
|
||||
captchaCache.setCache(verifyKey, code, expireSeconds, TimeUnit.SECONDS);
|
||||
|
||||
try(FastByteArrayOutputStream os = new FastByteArrayOutputStream()) {
|
||||
ImageIO.write(image, "jpg", os);
|
||||
ImageCaptchaVO vo = ImageCaptchaVO.builder().captchaEnabled(true).uuid(uuid)
|
||||
.img(Base64.getEncoder().encodeToString(os.toByteArray())).build();
|
||||
return R.ok(vo);
|
||||
} catch (IOException e) {
|
||||
return R.fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@IgnoreDemoMode
|
||||
@PostMapping("/submit")
|
||||
public R<?> submitForm(@RequestBody @Validated Map<String, Object> formData) {
|
||||
public R<?> submitForm(@RequestBody Map<String, Object> formData, HttpServletRequest request) throws IOException {
|
||||
Long formId = MapUtils.getLong(formData, "formId");
|
||||
if (!IdUtils.validate(formId)) {
|
||||
return R.fail("Unknown form: " + formId);
|
||||
throw CommonErrorCode.INVALID_REQUEST_ARG.exception("formId");
|
||||
}
|
||||
CmsCustomForm form = this.customFormService.getById(formId);
|
||||
if (Objects.isNull(form)) {
|
||||
return R.fail("Unknown form: " + formId);
|
||||
}
|
||||
Assert.notNull(form, CustomFormErrorCode.FORM_NOT_FOUND::exception);
|
||||
// 判断登录
|
||||
if (YesOrNo.isYes(form.getNeedLogin()) && !StpMemberUtil.isLogin()) {
|
||||
return R.fail("Please login first.");
|
||||
throw CustomFormErrorCode.NOT_LOGIN.exception();
|
||||
}
|
||||
// TODO 限制规则校验:验证码,IP,浏览器指纹
|
||||
// 获取用户标识
|
||||
String uuid = CustomFormConsts.tryToGetUUID(request);
|
||||
Assert.notEmpty(uuid, CustomFormErrorCode.MISSING_UUID::exception);
|
||||
// 验证码
|
||||
if (YesOrNo.isYes(form.getNeedCaptcha())) {
|
||||
|
||||
String captcha = MapUtils.getString(formData, "captcha", StringUtils.EMPTY);
|
||||
this.validateCaptcha(captcha, uuid);
|
||||
}
|
||||
String uuid = MapUtils.getString(formData, "uuid", StringUtils.EMPTY);
|
||||
String clientIp = ServletUtils.getIpAddr(ServletUtils.getRequest());
|
||||
|
||||
String clientIp = ServletUtils.getIpAddr(request);
|
||||
formData.put(CmsCustomFormMetaModelType.FIELD_DATA_ID.getCode(), IdUtils.getSnowflakeId());
|
||||
formData.put(CmsCustomFormMetaModelType.FIELD_MODEL_ID.getCode(), form.getFormId());
|
||||
formData.put(CmsCustomFormMetaModelType.FIELD_SITE_ID.getCode(), form.getSiteId());
|
||||
formData.put(CmsCustomFormMetaModelType.FIELD_CLIENT_IP.getCode(), clientIp);
|
||||
formData.put(CmsCustomFormMetaModelType.FIELD_UUID.getCode(), uuid);
|
||||
formData.put(CmsCustomFormMetaModelType.FIELD_CREATE_TIME.getCode(), LocalDateTime.now());
|
||||
|
||||
CmsSite site = siteService.getSite(form.getSiteId());
|
||||
|
||||
formData.forEach((k, v) -> {
|
||||
if (Objects.nonNull(v) && v.toString().startsWith("data:image/png;base64,")) {
|
||||
try {
|
||||
CmsResource resource = resourceService.addBase64Image(site, SysConstants.SYS_OPERATOR, v.toString());
|
||||
formData.put(k, InternalDataType_Resource.getInternalUrl(resource));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
if (YesOrNo.isYes(form.getNeedLogin())) {
|
||||
formData.put(CmsCustomFormMetaModelType.FIELD_UID.getCode(), StpMemberUtil.getLoginUser().getUserId());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.modelDataService.saveModelData(form.getModelId(), formData);
|
||||
customFormApiService.submit(form, formData);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
private void validateCaptcha(String code, String uuid) {
|
||||
Assert.notEmpty(code, () -> CommonErrorCode.INVALID_REQUEST_ARG.exception("captcha"));
|
||||
|
||||
String cacheKey = CustomFormCaptchaMonitoredCache.CACHE_PREFIX + Objects.requireNonNullElse(uuid, StringUtils.EMPTY);
|
||||
String cacheValue = captchaCache.getCache(cacheKey);
|
||||
// 过期判断
|
||||
Assert.notNull(cacheValue, CommonErrorCode.CAPTCHA_EXPIRED::exception);
|
||||
// 未过期判断是否与输入验证码一致
|
||||
Assert.isTrue(StringUtils.equals(code, cacheValue), CommonErrorCode.INVALID_CAPTCHA::exception);
|
||||
// 移除缓存
|
||||
captchaCache.deleteCache(cacheKey);
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,6 +23,8 @@ import com.chestnut.xmodel.core.BaseModelData;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
@ -34,8 +36,9 @@ import java.time.LocalDateTime;
|
||||
@Getter
|
||||
@Setter
|
||||
@TableName(value = CmsCustomFormData.TABLE_NAME, autoResultMap = true)
|
||||
public class CmsCustomFormData extends BaseModelData {
|
||||
public class CmsCustomFormData extends BaseModelData implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID =1L;
|
||||
|
||||
public static final String TABLE_NAME = "cms_cfd_default";
|
||||
|
||||
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2022-2025 兮玥(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.
|
||||
*/
|
||||
package com.chestnut.customform.exception;
|
||||
|
||||
import com.chestnut.common.exception.ErrorCode;
|
||||
|
||||
public enum CustomFormErrorCode implements ErrorCode {
|
||||
|
||||
/**
|
||||
* 表单不存在
|
||||
*/
|
||||
FORM_NOT_FOUND,
|
||||
|
||||
/**
|
||||
* uuid不能为空
|
||||
*/
|
||||
MISSING_UUID,
|
||||
|
||||
/**
|
||||
* 未登录
|
||||
*/
|
||||
NOT_LOGIN,
|
||||
|
||||
/**
|
||||
* 不能重复提交
|
||||
*/
|
||||
CANNOT_RESUBMIT;
|
||||
|
||||
@Override
|
||||
public String value() {
|
||||
return "{ERR.CUSTOM_FORM." + this.name() + "}";
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2022-2025 兮玥(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.
|
||||
*/
|
||||
package com.chestnut.customform.fixed.config;
|
||||
|
||||
import com.chestnut.common.utils.ConvertUtils;
|
||||
import com.chestnut.common.utils.SpringUtils;
|
||||
import com.chestnut.system.fixed.FixedConfig;
|
||||
import com.chestnut.system.service.ISysConfigService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 自定义表单验证码过期时间(秒)
|
||||
*/
|
||||
@Component(FixedConfig.BEAN_PREFIX + CustomFormCaptchaExpireSeconds.ID)
|
||||
public class CustomFormCaptchaExpireSeconds extends FixedConfig {
|
||||
|
||||
public static final String ID = "CustomFormCaptchaExpireSeconds";
|
||||
|
||||
private static final ISysConfigService configService = SpringUtils.getBean(ISysConfigService.class);
|
||||
|
||||
/**
|
||||
* 默认:600秒
|
||||
*/
|
||||
private static final int DEFAULT_VALUE = 600;
|
||||
|
||||
public CustomFormCaptchaExpireSeconds() {
|
||||
super(ID, "{CONFIG." + ID + "}", String.valueOf(DEFAULT_VALUE), null);
|
||||
}
|
||||
|
||||
public static Integer getSeconds() {
|
||||
String configValue = configService.selectConfigByKey(ID);
|
||||
return ConvertUtils.toInteger(configValue, DEFAULT_VALUE);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2022-2025 兮玥(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.
|
||||
*/
|
||||
package com.chestnut.customform.rule;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.chestnut.customform.CmsCustomFormMetaModelType;
|
||||
import com.chestnut.customform.domain.CmsCustomForm;
|
||||
import com.chestnut.customform.domain.CmsCustomFormData;
|
||||
import com.chestnut.customform.mapper.CustomFormDataMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* CustomFormLimitRule_IP
|
||||
*
|
||||
* @author 兮玥
|
||||
* @email 190785909@qq.com
|
||||
*/
|
||||
@Order(2)
|
||||
@RequiredArgsConstructor
|
||||
@Component(ICustomFormLimitRule.BEAN_PREFIX + CustomFormLimitRule_IP.ID)
|
||||
public class CustomFormLimitRule_IP implements ICustomFormLimitRule {
|
||||
|
||||
public static final String ID = "IP";
|
||||
|
||||
private final CustomFormDataMapper customFormDataMapper;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "{CUSTOM_FORM.LIMIT_RULE.IP}";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean check(CmsCustomForm form, Map<String, Object> dataMap) {
|
||||
Object ipAddr = MapUtils.getString(dataMap, CmsCustomFormMetaModelType.FIELD_CLIENT_IP.getCode());
|
||||
Long count = customFormDataMapper.selectCount(new LambdaQueryWrapper<CmsCustomFormData>()
|
||||
.eq(CmsCustomFormData::getModelId, form.getFormId())
|
||||
.eq(CmsCustomFormData::getClientIp, ipAddr));
|
||||
return count == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2022-2025 兮玥(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.
|
||||
*/
|
||||
package com.chestnut.customform.rule;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.chestnut.customform.CmsCustomFormMetaModelType;
|
||||
import com.chestnut.customform.domain.CmsCustomForm;
|
||||
import com.chestnut.customform.domain.CmsCustomFormData;
|
||||
import com.chestnut.customform.mapper.CustomFormDataMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* CustomFormLimitRule_UUID
|
||||
*
|
||||
* @author 兮玥
|
||||
* @email 190785909@qq.com
|
||||
*/
|
||||
@Order(3)
|
||||
@RequiredArgsConstructor
|
||||
@Component(ICustomFormLimitRule.BEAN_PREFIX + CustomFormLimitRule_UUID.ID)
|
||||
public class CustomFormLimitRule_UUID implements ICustomFormLimitRule {
|
||||
|
||||
public static final String ID = "UUID";
|
||||
|
||||
private final CustomFormDataMapper customFormDataMapper;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "{CUSTOM_FORM.LIMIT_RULE.UUID}";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean check(CmsCustomForm form, Map<String, Object> dataMap) {
|
||||
Object uuid = MapUtils.getString(dataMap, CmsCustomFormMetaModelType.FIELD_UUID.getCode());
|
||||
Long count = customFormDataMapper.selectCount(new LambdaQueryWrapper<CmsCustomFormData>()
|
||||
.eq(CmsCustomFormData::getModelId, form.getFormId())
|
||||
.eq(CmsCustomFormData::getUuid, uuid));
|
||||
return count == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2022-2025 兮玥(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.
|
||||
*/
|
||||
package com.chestnut.customform.rule;
|
||||
|
||||
import com.chestnut.customform.domain.CmsCustomForm;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* CustomFormLimitRule_Unlimited
|
||||
*
|
||||
* @author 兮玥
|
||||
* @email 190785909@qq.com
|
||||
*/
|
||||
@Order(1)
|
||||
@RequiredArgsConstructor
|
||||
@Component(ICustomFormLimitRule.BEAN_PREFIX + CustomFormLimitRule_Unlimited.ID)
|
||||
public class CustomFormLimitRule_Unlimited implements ICustomFormLimitRule {
|
||||
|
||||
public static final String ID = "Unlimited";
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "{CUSTOM_FORM.LIMIT_RULE.UNLIMITED}";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean check(CmsCustomForm form, Map<String, Object> dataMap) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2022-2025 兮玥(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.
|
||||
*/
|
||||
package com.chestnut.customform.rule;
|
||||
|
||||
import com.chestnut.customform.domain.CmsCustomForm;
|
||||
import org.springframework.core.Ordered;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 自定义表单校验规则接口
|
||||
*
|
||||
* @author 兮玥
|
||||
* @email 190785909@qq.com
|
||||
*/
|
||||
public interface ICustomFormLimitRule extends Ordered {
|
||||
|
||||
String BEAN_PREFIX = "CustomFormLimitRule_";
|
||||
|
||||
String getId();
|
||||
|
||||
String getName();
|
||||
|
||||
boolean check(CmsCustomForm form, Map<String, Object> dataMap);
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2022-2025 兮玥(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.
|
||||
*/
|
||||
package com.chestnut.customform.service;
|
||||
|
||||
import com.chestnut.customform.domain.CmsCustomForm;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* ICustomFormApiService
|
||||
*
|
||||
* @author 兮玥
|
||||
* @email 190785909@qq.com
|
||||
*/
|
||||
public interface ICustomFormApiService {
|
||||
|
||||
/**
|
||||
* 提交表单数据
|
||||
*
|
||||
* @param form 表单定义
|
||||
* @param formData 表单数据
|
||||
* @throws IOException ex
|
||||
*/
|
||||
void submit(CmsCustomForm form, Map<String, Object> formData) throws IOException;
|
||||
}
|
||||
@ -19,6 +19,7 @@ import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.chestnut.customform.domain.CmsCustomForm;
|
||||
import com.chestnut.customform.domain.dto.CustomFormAddDTO;
|
||||
import com.chestnut.customform.domain.dto.CustomFormEditDTO;
|
||||
import com.chestnut.customform.rule.ICustomFormLimitRule;
|
||||
import com.chestnut.xmodel.domain.XModel;
|
||||
import com.chestnut.xmodel.dto.XModelDTO;
|
||||
|
||||
@ -27,6 +28,8 @@ import java.util.List;
|
||||
|
||||
public interface ICustomFormService extends IService<CmsCustomForm> {
|
||||
|
||||
ICustomFormLimitRule getLimitRule(String ruleId);
|
||||
|
||||
/**
|
||||
* 添加自定义表单
|
||||
*
|
||||
|
||||
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2022-2025 兮玥(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.
|
||||
*/
|
||||
package com.chestnut.customform.service.impl;
|
||||
|
||||
import com.chestnut.common.utils.image.ImageUtils;
|
||||
import com.chestnut.contentcore.core.impl.InternalDataType_Resource;
|
||||
import com.chestnut.contentcore.domain.CmsResource;
|
||||
import com.chestnut.contentcore.domain.CmsSite;
|
||||
import com.chestnut.contentcore.service.IResourceService;
|
||||
import com.chestnut.contentcore.service.ISiteService;
|
||||
import com.chestnut.customform.CmsCustomFormMetaModelType;
|
||||
import com.chestnut.customform.domain.CmsCustomForm;
|
||||
import com.chestnut.customform.exception.CustomFormErrorCode;
|
||||
import com.chestnut.customform.rule.ICustomFormLimitRule;
|
||||
import com.chestnut.customform.service.ICustomFormApiService;
|
||||
import com.chestnut.customform.service.ICustomFormService;
|
||||
import com.chestnut.system.SysConstants;
|
||||
import com.chestnut.xmodel.service.IModelDataService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
import org.redisson.api.RLock;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* CustomFormApiServiceImpl
|
||||
*
|
||||
* @author 兮玥
|
||||
* @email 190785909@qq.com
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class CustomFormApiServiceImpl implements ICustomFormApiService {
|
||||
|
||||
private final ICustomFormService customFormService;
|
||||
|
||||
private final IModelDataService modelDataService;
|
||||
|
||||
private final IResourceService resourceService;
|
||||
|
||||
private final ISiteService siteService;
|
||||
|
||||
private final RedissonClient redissonClient;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Throwable.class)
|
||||
public void submit(CmsCustomForm form, Map<String, Object> formData) throws IOException {
|
||||
String uuid = MapUtils.getString(formData, CmsCustomFormMetaModelType.FIELD_UUID.getCode());
|
||||
RLock lock = redissonClient.getLock("CustomFormSubmit-" + uuid);
|
||||
lock.lock();
|
||||
try {
|
||||
// 唯一提交限制校验
|
||||
ICustomFormLimitRule limitRule = customFormService.getLimitRule(form.getRuleLimit());
|
||||
if (Objects.nonNull(limitRule)) {
|
||||
if (!limitRule.check(form, formData)) {
|
||||
throw CustomFormErrorCode.CANNOT_RESUBMIT.exception();
|
||||
}
|
||||
}
|
||||
// base64图片保存到资源库
|
||||
CmsSite site = siteService.getSite(form.getSiteId());
|
||||
for (Map.Entry<String, Object> entry : formData.entrySet()) {
|
||||
if (ImageUtils.isBase64Image(entry.getValue())) {
|
||||
CmsResource resource = resourceService.addBase64Image(site, SysConstants.SYS_OPERATOR, entry.getValue().toString());
|
||||
entry.setValue(InternalDataType_Resource.getInternalUrl(resource));
|
||||
}
|
||||
}
|
||||
// 保存数据
|
||||
this.modelDataService.saveModelData(form.getModelId(), formData);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -22,11 +22,9 @@ import com.chestnut.common.staticize.StaticizeService;
|
||||
import com.chestnut.common.staticize.core.TemplateContext;
|
||||
import com.chestnut.common.utils.Assert;
|
||||
import com.chestnut.common.utils.IdUtils;
|
||||
import com.chestnut.common.utils.ReflectASMUtils;
|
||||
import com.chestnut.common.utils.StringUtils;
|
||||
import com.chestnut.contentcore.domain.CmsPublishPipe;
|
||||
import com.chestnut.contentcore.domain.CmsSite;
|
||||
import com.chestnut.contentcore.fixed.config.SiteApiUrl;
|
||||
import com.chestnut.contentcore.service.IPublishPipeService;
|
||||
import com.chestnut.contentcore.service.ISiteService;
|
||||
import com.chestnut.contentcore.service.ITemplateService;
|
||||
@ -42,6 +40,7 @@ import com.chestnut.customform.domain.dto.CustomFormEditDTO;
|
||||
import com.chestnut.customform.fixed.dict.CustomFormStatus;
|
||||
import com.chestnut.customform.mapper.CustomFormMapper;
|
||||
import com.chestnut.customform.publishpipe.PublishPipeProp_CustomFormTemplate;
|
||||
import com.chestnut.customform.rule.ICustomFormLimitRule;
|
||||
import com.chestnut.customform.service.ICustomFormService;
|
||||
import com.chestnut.xmodel.domain.XModel;
|
||||
import com.chestnut.xmodel.service.IModelService;
|
||||
@ -74,6 +73,13 @@ public class CustomFormServiceImpl extends ServiceImpl<CustomFormMapper, CmsCust
|
||||
|
||||
private final StaticizeService staticizeService;
|
||||
|
||||
private final Map<String, ICustomFormLimitRule> limitRuleMap;
|
||||
|
||||
@Override
|
||||
public ICustomFormLimitRule getLimitRule(String ruleId) {
|
||||
return this.limitRuleMap.get(ICustomFormLimitRule.BEAN_PREFIX + ruleId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void addCustomForm(CustomFormAddDTO dto) {
|
||||
|
||||
@ -15,3 +15,14 @@ DICT.CustomFormRule=自定义表单限制状态
|
||||
DICT.CustomFormRule.0=无限制
|
||||
DICT.CustomFormRule.1=IP
|
||||
DICT.CustomFormRule.2=浏览器指纹
|
||||
|
||||
MONITORED.CACHE.CUSTOM_FORM_CAPTCHA=自定义表单验证码
|
||||
|
||||
CUSTOM_FORM.LIMIT_RULE.UNLIMITED=无限制
|
||||
CUSTOM_FORM.LIMIT_RULE.IP=IP
|
||||
CUSTOM_FORM.LIMIT_RULE.UUID=浏览器指纹
|
||||
|
||||
ERR.CUSTOM_FORM.FORM_NOT_FOUND=表单数据不存在
|
||||
ERR.CUSTOM_FORM.MISSING_UUID=UUID参数不能为空
|
||||
ERR.CUSTOM_FORM.NOT_LOGIN=请先登录
|
||||
ERR.CUSTOM_FORM.CANNOT_RESUBMIT=请勿重复提交
|
||||
|
||||
@ -15,3 +15,14 @@ DICT.CustomFormRule=Custom Form Rule
|
||||
DICT.CustomFormRule.0=Unlimited
|
||||
DICT.CustomFormRule.1=IP
|
||||
DICT.CustomFormRule.2=Browser Fingerprint
|
||||
|
||||
MONITORED.CACHE.CUSTOM_FORM_CAPTCHA=Custom form captcha
|
||||
|
||||
CUSTOM_FORM.LIMIT_RULE.UNLIMITED=Unlimited
|
||||
CUSTOM_FORM.LIMIT_RULE.IP=IP
|
||||
CUSTOM_FORM.LIMIT_RULE.UUID=Finger Print
|
||||
|
||||
ERR.CUSTOM_FORM.FORM_NOT_FOUND=Form not found.
|
||||
ERR.CUSTOM_FORM.MISSING_UUID=Missing uuid.
|
||||
ERR.CUSTOM_FORM.NOT_LOGIN=Please login first.
|
||||
ERR.CUSTOM_FORM.CANNOT_RESUBMIT=Please do not resubmit
|
||||
@ -15,3 +15,14 @@ DICT.CustomFormRule=自定義表單限制狀態
|
||||
DICT.CustomFormRule.0=無限制
|
||||
DICT.CustomFormRule.1=IP
|
||||
DICT.CustomFormRule.2=瀏覽器指紋
|
||||
|
||||
MONITORED.CACHE.CUSTOM_FORM_CAPTCHA=自定義表單驗證碼
|
||||
|
||||
CUSTOM_FORM.LIMIT_RULE.UNLIMITED=無限制
|
||||
CUSTOM_FORM.LIMIT_RULE.IP=IP
|
||||
CUSTOM_FORM.LIMIT_RULE.UUID=瀏覽器指紋
|
||||
|
||||
ERR.CUSTOM_FORM.FORM_NOT_FOUND=表單數據不存在
|
||||
ERR.CUSTOM_FORM.MISSING_UUID=UUID參數不能為空
|
||||
ERR.CUSTOM_FORM.NOT_LOGIN=請先登錄
|
||||
ERR.CUSTOM_FORM.CANNOT_RESUBMIT=請勿重複提交
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-cms</artifactId>
|
||||
<version>1.5.4</version>
|
||||
<version>1.5.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>chestnut-cms-dynamic</artifactId>
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-cms</artifactId>
|
||||
<version>1.5.4</version>
|
||||
<version>1.5.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>chestnut-cms-exmodel</artifactId>
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-cms</artifactId>
|
||||
<version>1.5.4</version>
|
||||
<version>1.5.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>chestnut-cms-image</artifactId>
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-cms</artifactId>
|
||||
<version>1.5.4</version>
|
||||
<version>1.5.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>chestnut-cms-link</artifactId>
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-cms</artifactId>
|
||||
<version>1.5.4</version>
|
||||
<version>1.5.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>chestnut-cms-media</artifactId>
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-cms</artifactId>
|
||||
<version>1.5.4</version>
|
||||
<version>1.5.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>chestnut-cms-member</artifactId>
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-cms</artifactId>
|
||||
<version>1.5.4</version>
|
||||
<version>1.5.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>chestnut-cms-search</artifactId>
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-cms</artifactId>
|
||||
<version>1.5.4</version>
|
||||
<version>1.5.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>chestnut-cms-seo</artifactId>
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-cms</artifactId>
|
||||
<version>1.5.4</version>
|
||||
<version>1.5.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>chestnut-cms-stat</artifactId>
|
||||
|
||||
@ -23,15 +23,15 @@ import com.chestnut.stat.core.IStatType;
|
||||
import com.chestnut.stat.core.StatMenu;
|
||||
|
||||
@Component
|
||||
public class SiteStatType implements IStatType {
|
||||
public class SiteBaiduStatType implements IStatType {
|
||||
|
||||
private final static List<StatMenu> STAT_MENU = List.of(
|
||||
new StatMenu("CmsSiteStat", "", "{STAT.MENU.CmsSiteStat}", 1),
|
||||
new StatMenu("BdSiteTrendOverview", "CmsSiteStat", "{STAT.MENU.BdSiteTrendOverview}", 1),
|
||||
new StatMenu("BdSiteTimeTrend", "CmsSiteStat", "{STAT.MENU.BdSiteTimeTrend}", 2),
|
||||
new StatMenu("BdSiteVisitSource", "CmsSiteStat", "{STAT.MENU.BdSiteVisitSource}", 3),
|
||||
new StatMenu("BdSiteEngineSource", "CmsSiteStat", "{STAT.MENU.BdSiteEngineSource}", 4),
|
||||
new StatMenu("BdSiteSearchWordSource", "CmsSiteStat", "{STAT.MENU.BdSiteSearchWordSource}", 5),
|
||||
new StatMenu("CmsSiteBaiduStat", "", "{STAT.MENU.CmsSiteStat}", 1),
|
||||
new StatMenu("BdSiteTrendOverview", "CmsSiteBaiduStat", "{STAT.MENU.BdSiteTrendOverview}", 1),
|
||||
new StatMenu("BdSiteTimeTrend", "CmsSiteBaiduStat", "{STAT.MENU.BdSiteTimeTrend}", 2),
|
||||
new StatMenu("BdSiteVisitSource", "CmsSiteBaiduStat", "{STAT.MENU.BdSiteVisitSource}", 3),
|
||||
new StatMenu("BdSiteEngineSource", "CmsSiteBaiduStat", "{STAT.MENU.BdSiteEngineSource}", 4),
|
||||
new StatMenu("BdSiteSearchWordSource", "CmsSiteBaiduStat", "{STAT.MENU.BdSiteSearchWordSource}", 5),
|
||||
new StatMenu("CmsContentStat", "", "{STAT.MENU.CmsContentStat}", 2),
|
||||
new StatMenu("ContentDynamicStat", "CmsContentStat", "{STAT.MENU.ContentDynamicStat}", 1),
|
||||
new StatMenu("ContentStatByCatalog", "CmsContentStat", "{STAT.MENU.ContentStatByCatalog}", 2),
|
||||
@ -19,7 +19,6 @@ import com.chestnut.cms.stat.baidu.BaiduTjMetrics;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.apache.commons.text.StringEscapeUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@ -19,7 +19,6 @@ import com.chestnut.cms.stat.baidu.BaiduTjMetrics;
|
||||
import com.chestnut.common.utils.HttpUtils;
|
||||
import com.chestnut.common.utils.JacksonUtils;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
|
||||
@ -17,7 +17,6 @@ package com.chestnut.cms.stat.baidu.api;
|
||||
|
||||
import com.chestnut.common.utils.HttpUtils;
|
||||
import com.chestnut.common.utils.JacksonUtils;
|
||||
import lombok.Builder;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
@ -99,6 +99,41 @@ public class CmsSiteVisitLog implements Serializable {
|
||||
*/
|
||||
private String locale;
|
||||
|
||||
/**
|
||||
* 屏幕分辨率:宽
|
||||
*/
|
||||
private Integer screenWidth;
|
||||
|
||||
/**
|
||||
* 屏幕分辨率:高
|
||||
*/
|
||||
private Integer screenHeight;
|
||||
|
||||
/**
|
||||
* 色彩深度
|
||||
*/
|
||||
private Integer colorDepth;
|
||||
|
||||
/**
|
||||
* 是否允许cookie
|
||||
*/
|
||||
private Integer cookieEnabled;
|
||||
|
||||
/**
|
||||
* 是否允许java
|
||||
*/
|
||||
private Integer javaEnabled;
|
||||
|
||||
/**
|
||||
* 访问时长,单位:秒
|
||||
*/
|
||||
private Integer visitTime;
|
||||
|
||||
/**
|
||||
* 访客唯一标识
|
||||
*/
|
||||
private String uuid;
|
||||
|
||||
/**
|
||||
* 发生时间
|
||||
*/
|
||||
|
||||
@ -20,7 +20,6 @@ import com.chestnut.contentcore.domain.CmsCatalog;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
|
||||
@ -17,12 +17,10 @@ package com.chestnut.cms.stat.service.impl;
|
||||
|
||||
import com.chestnut.cms.stat.baidu.BaiduTongjiConfig;
|
||||
import com.chestnut.cms.stat.baidu.BaiduTongjiUtils;
|
||||
import com.chestnut.cms.stat.baidu.api.SiteListResponse;
|
||||
import com.chestnut.cms.stat.exception.CmsStatErrorCode;
|
||||
import com.chestnut.cms.stat.properties.BaiduTjAccessTokenProperty;
|
||||
import com.chestnut.cms.stat.properties.BaiduTjRefreshTokenProperty;
|
||||
import com.chestnut.cms.stat.service.ICmsStatService;
|
||||
import com.chestnut.common.domain.R;
|
||||
import com.chestnut.common.utils.Assert;
|
||||
import com.chestnut.contentcore.domain.CmsSite;
|
||||
import com.chestnut.contentcore.service.ISiteService;
|
||||
|
||||
@ -19,9 +19,11 @@ import com.chestnut.common.staticize.FreeMarkerUtils;
|
||||
import com.chestnut.common.staticize.tag.AbstractTag;
|
||||
import com.chestnut.common.utils.IdUtils;
|
||||
import com.chestnut.contentcore.util.TemplateUtils;
|
||||
import com.ulisesbocchio.jasyptspringboot.annotation.ConditionalOnMissingBean;
|
||||
import freemarker.core.Environment;
|
||||
import freemarker.template.TemplateException;
|
||||
import freemarker.template.TemplateModel;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
@ -29,6 +31,7 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
@Component
|
||||
@ConditionalOnMissingClass("com.chestnut.cms.statpro.template.tag.CmsStatTag")
|
||||
public class CmsStatTag extends AbstractTag {
|
||||
|
||||
public final static String TAG_NAME = "cms_stat";
|
||||
|
||||
@ -8,7 +8,7 @@ VALIDATOR.CMS.STAT.END_DATE_NOT_NULL=结束时间不能为空
|
||||
VALIDATOR.CMS.STAT.TIME_GRAN_NOT_NULL=时间粒度不能为空
|
||||
|
||||
# 统计菜单
|
||||
STAT.MENU.CmsSiteStat=网站访问统计
|
||||
STAT.MENU.CmsSiteStat=百度访问统计
|
||||
STAT.MENU.BdSiteTrendOverview=网站概况
|
||||
STAT.MENU.BdSiteTimeTrend=趋势分析
|
||||
STAT.MENU.BdSiteVisitSource=访问来源
|
||||
|
||||
@ -8,7 +8,7 @@ VALID.CMS.STAT.END_DATE_NOT_NULL=End date cannot be null.
|
||||
VALID.CMS.STAT.TIME_GRAN_NOT_NULL=Time granularity cannot be null.
|
||||
|
||||
# 统计菜单
|
||||
STAT.MENU.CmsSiteStat=Site Statistics
|
||||
STAT.MENU.CmsSiteStat=Baidu Statistics
|
||||
STAT.MENU.BdSiteTrendOverview=Overview
|
||||
STAT.MENU.BdSiteTimeTrend=Trend Analysis
|
||||
STAT.MENU.BdSiteVisitSource=Visit Source
|
||||
|
||||
@ -8,7 +8,7 @@ VALIDATOR.CMS.STAT.END_DATE_NOT_NULL=結束時間不能為空
|
||||
VALIDATOR.CMS.STAT.TIME_GRAN_NOT_NULL=時間粒度不能為空
|
||||
|
||||
# 統計菜單
|
||||
STAT.MENU.CmsSiteStat=網站訪問統計
|
||||
STAT.MENU.CmsSiteStat=百度訪問統計
|
||||
STAT.MENU.BdSiteTrendOverview=網站概況
|
||||
STAT.MENU.BdSiteTimeTrend=趨勢分析
|
||||
STAT.MENU.BdSiteVisitSource=訪問來源
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-cms</artifactId>
|
||||
<version>1.5.4</version>
|
||||
<version>1.5.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>chestnut-cms-vote</artifactId>
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut-cms</artifactId>
|
||||
<version>1.5.4</version>
|
||||
<version>1.5.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>chestnut-cms-word</artifactId>
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<artifactId>chestnut</artifactId>
|
||||
<version>1.5.4</version>
|
||||
<version>1.5.5</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>chestnut-common</artifactId>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<version>1.5.4</version>
|
||||
<version>1.5.5</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@ -90,10 +90,20 @@ public enum CommonErrorCode implements ErrorCode {
|
||||
/**
|
||||
* 上传文件不能为空
|
||||
*/
|
||||
UPLOAD_FILE_EMPTY;
|
||||
UPLOAD_FILE_EMPTY,
|
||||
|
||||
/**
|
||||
* 验证码错误
|
||||
*/
|
||||
INVALID_CAPTCHA,
|
||||
|
||||
/**
|
||||
* 验证码过期
|
||||
*/
|
||||
CAPTCHA_EXPIRED;
|
||||
|
||||
@Override
|
||||
public String value() {
|
||||
return "{ERRCODE.COMMON." + this.name() + "}";
|
||||
return "{ERR.COMMON." + this.name() + "}";
|
||||
}
|
||||
}
|
||||
|
||||
@ -129,4 +129,20 @@ public class I18nUtils {
|
||||
}
|
||||
return PlaceholderHelper.replacePlaceholders(str, langKey -> messageSource.getMessage(langKey, args, locale));
|
||||
}
|
||||
|
||||
public static boolean isLanguageTag(String s) {
|
||||
int len = s.length();
|
||||
return (len >= 2) && (len <= 8) && isAlphaString(s);
|
||||
}
|
||||
|
||||
static boolean isAlphaString(String s) {
|
||||
int len = s.length();
|
||||
for (int i = 0; i < len; i++) {
|
||||
char c = s.charAt(i);
|
||||
if (c != '-' && !((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,6 +51,28 @@ public class ArrayUtils {
|
||||
return indexOf(searchStr, arr) > -1;
|
||||
}
|
||||
|
||||
public static int indexOf(Integer searchStr, Integer... arr) {
|
||||
if (Objects.isNull(arr) || arr.length == 0) {
|
||||
return -1;
|
||||
}
|
||||
for (int i = 0; i < arr.length; i++) {
|
||||
if (searchStr == null) {
|
||||
if (arr[i] == null) {
|
||||
return i;
|
||||
}
|
||||
} else {
|
||||
if (searchStr.equals(arr[i])) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static boolean contains(Integer searchStr, Integer... arr) {
|
||||
return indexOf(searchStr, arr) > -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找指定列表中符合条件的第一个元素并返回,如果没有符合条件的元素直接抛出异常
|
||||
*/
|
||||
@ -142,4 +164,30 @@ public class ArrayUtils {
|
||||
public static <T> boolean isNotEmpty(T[] arr) {
|
||||
return !isEmpty(arr);
|
||||
}
|
||||
|
||||
public static <T> long sumLongValue(List<T> list, Function<T, Long> getter) {
|
||||
if (Objects.isNull(list)) {
|
||||
return 0;
|
||||
}
|
||||
long sum = 0L;
|
||||
for (T t : list) {
|
||||
Long v = getter.apply(t);
|
||||
if (Objects.nonNull(v)) {
|
||||
sum += v;
|
||||
}
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
public static <T> int sumIntValue(List<T> list, Function<T, Integer> getter) {
|
||||
if (Objects.isNull(list)) {
|
||||
return 0;
|
||||
}
|
||||
int sum = 0;
|
||||
for (T t : list) {
|
||||
Integer v = getter.apply(t);
|
||||
sum += Objects.requireNonNullElse(v, 0);
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,7 +51,7 @@ public class HttpUtils {
|
||||
return USER_AGENTS[index];
|
||||
}
|
||||
|
||||
private static SSLContext trustAllSSLContext() throws NoSuchAlgorithmException, KeyManagementException {
|
||||
public static SSLContext trustAllSSLContext() throws NoSuchAlgorithmException, KeyManagementException {
|
||||
TrustManager[] trustAllCerts = new TrustManager[] {
|
||||
new X509TrustManager() {
|
||||
public void checkClientTrusted(X509Certificate[] certs, String authType) {}
|
||||
@ -94,22 +94,25 @@ public class HttpUtils {
|
||||
* @param uri
|
||||
* @return
|
||||
*/
|
||||
public static String get(URI uri) {
|
||||
public static String get(URI uri, Map<String, String> headers) {
|
||||
try {
|
||||
HttpClient httpClient = HttpClient.newBuilder()
|
||||
.connectTimeout(Duration.ofSeconds(30))
|
||||
.followRedirects(Redirect.ALWAYS)
|
||||
.build();
|
||||
HttpRequest httpRequest = HttpRequest.newBuilder(uri)
|
||||
.header("User-Agent", USER_AGENTS[0])
|
||||
.GET()
|
||||
.build();
|
||||
HttpRequest.Builder builder = HttpRequest.newBuilder(uri);
|
||||
headers.forEach(builder::setHeader);
|
||||
HttpRequest httpRequest = builder.GET().build();
|
||||
return httpClient.send(httpRequest, BodyHandlers.ofString()).body();
|
||||
} catch (IOException | InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static String get(URI uri) {
|
||||
return get(uri, Map.of("User-Agent", USER_AGENTS[0]));
|
||||
}
|
||||
|
||||
public static String post(URI uri, String body, Map<String, String> headers) throws Exception {
|
||||
if (Objects.isNull(body)) {
|
||||
body = StringUtils.EMPTY;
|
||||
|
||||
@ -15,14 +15,14 @@
|
||||
*/
|
||||
package com.chestnut.common.utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.lionsoul.ip2region.xdb.Searcher;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* IP2Region工具类,内存查询
|
||||
*/
|
||||
@ -40,24 +40,38 @@ public class IP2RegionUtils {
|
||||
cBuff = FileCopyUtils.copyToByteArray(is);
|
||||
searcher = Searcher.newWithBuffer(cBuff);
|
||||
} catch (IOException e1) {
|
||||
logger.error("Load ip2region.xdb failed: {}", e1);
|
||||
logger.error("Load ip2region.xdb failed.", e1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ip转区域,格式:
|
||||
*
|
||||
* @param ip IPv4/IPv6
|
||||
* @return 国家|0|省份|城市|运营商
|
||||
*/
|
||||
public static String ip2Region(String ip) {
|
||||
try {
|
||||
if (ServletUtils.isUnknown(ip)) {
|
||||
return ServletUtils.UNKNOWN;
|
||||
}
|
||||
if (ServletUtils.internalIp(ip)) {
|
||||
return "内网";
|
||||
if (ServletUtils.isInternalIp(ip)) {
|
||||
return ServletUtils.INTERNAL_IP;
|
||||
}
|
||||
return searcher.search(ip);
|
||||
} catch (Exception e) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.error("Ip2region failed: {}", e);
|
||||
logger.error("Ip2region failed: {}", ip, e);
|
||||
}
|
||||
return ServletUtils.UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
public static void close() {
|
||||
try {
|
||||
searcher.close();
|
||||
} catch (IOException e) {
|
||||
logger.error("Close ip2region searcher failed.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -84,13 +84,15 @@ public class ServletUtils {
|
||||
*/
|
||||
public static final String HEADER_ACCEPT_LANGUAGE = "Accept-Language";
|
||||
|
||||
public static final String UNKNOWN = "unknown";
|
||||
public static final String UNKNOWN = "0";
|
||||
|
||||
public static final String INTERNAL_IP = "InternalIP";
|
||||
|
||||
public static boolean isHttpUrl(String url) {
|
||||
return StringUtils.startsWithIgnoreCase(url, HTTP) || StringUtils.startsWithIgnoreCase(url, HTTPS);
|
||||
}
|
||||
|
||||
public static String getAcceptLanaguage(HttpServletRequest request) {
|
||||
public static String getAcceptLanguage(HttpServletRequest request) {
|
||||
return getHeader(request, HEADER_ACCEPT_LANGUAGE);
|
||||
}
|
||||
|
||||
@ -560,9 +562,9 @@ public class ServletUtils {
|
||||
* @param ip IP地址
|
||||
* @return 结果
|
||||
*/
|
||||
public static boolean internalIp(String ip) {
|
||||
public static boolean isInternalIp(String ip) {
|
||||
byte[] addr = textToNumericFormatV4(ip);
|
||||
return internalIp(addr) || "127.0.0.1".equals(ip);
|
||||
return isInternalIp(addr) || "127.0.0.1".equals(ip);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -571,7 +573,7 @@ public class ServletUtils {
|
||||
* @param addr byte地址
|
||||
* @return 结果
|
||||
*/
|
||||
private static boolean internalIp(byte[] addr) {
|
||||
private static boolean isInternalIp(byte[] addr) {
|
||||
if (Objects.isNull(addr) || addr.length < 2) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -43,10 +43,26 @@ public class TimeUtils {
|
||||
return LocalDateTime.ofInstant(instant, ZONE_OFFSET);
|
||||
}
|
||||
|
||||
public static LocalDateTime epochSecondToLocalDateTime(long epochSecond) {
|
||||
return toLocalDateTime(Instant.ofEpochSecond(epochSecond));
|
||||
}
|
||||
|
||||
public static long epochSecond(LocalDateTime localDateTime) {
|
||||
return localDateTime.toEpochSecond(ZONE_OFFSET);
|
||||
}
|
||||
|
||||
public static String epochSecondFormat(long epochSecond, DateTimeFormatter formatter) {
|
||||
return formatter.format(toLocalDateTime(Instant.ofEpochSecond(epochSecond)));
|
||||
}
|
||||
|
||||
public static String localDateTimeFormat(long epochMilli) {
|
||||
return YYYY_MM_DD_HH_MM_SS.format(toLocalDateTime(Instant.ofEpochMilli(epochMilli)));
|
||||
}
|
||||
|
||||
public static String localDateTimeFormat(long epochMilli, DateTimeFormatter formatter) {
|
||||
return formatter.format(toLocalDateTime(Instant.ofEpochMilli(epochMilli)));
|
||||
}
|
||||
|
||||
public static String localDateFormat(long epochMilli) {
|
||||
return YYYY_MM_DD.format(toLocalDateTime(Instant.ofEpochMilli(epochMilli)));
|
||||
}
|
||||
|
||||
@ -153,4 +153,19 @@ public class ImageUtils {
|
||||
}
|
||||
throw new ImageException("Unsupported image format: " + imageFormat);
|
||||
}
|
||||
|
||||
public static boolean isBase64Image(String base64) {
|
||||
if (!StringUtils.startsWithIgnoreCase(base64, "data:image/")) {
|
||||
return false;
|
||||
}
|
||||
String encode = StringUtils.substringAfter(StringUtils.substringBefore(base64, ","), ";");
|
||||
return "base64".equalsIgnoreCase(encode);
|
||||
}
|
||||
|
||||
public static boolean isBase64Image(Object v) {
|
||||
if (Objects.isNull(v)) {
|
||||
return false;
|
||||
}
|
||||
return isBase64Image(v.toString());
|
||||
}
|
||||
}
|
||||
@ -1,20 +1,22 @@
|
||||
#错误消息
|
||||
ERRCODE.COMMON.NOT_NULL=参数[{0}]不能为NULL
|
||||
ERRCODE.COMMON.NOT_EMPTY=参数[{0}]不能为空
|
||||
ERRCODE.COMMON.DATA_NOT_FOUND=数据不存在
|
||||
ERRCODE.COMMON.DATA_NOT_FOUND_BY_ID=数据不存在:{0}={1}
|
||||
ERRCODE.COMMON.DATA_CONFLICT=数据[{0}]冲突
|
||||
ERRCODE.COMMON.INVALID_REQUEST_ARG=请求参数[{0}]不符合校验规则
|
||||
ERRCODE.COMMON.INVALID_REQUEST_METHOD=请求方法错误
|
||||
ERRCODE.COMMON.SYSTEM_ERROR=系统内部错误
|
||||
ERRCODE.COMMON.UNKNOWN_ERROR=未知错误
|
||||
ERRCODE.COMMON.DATABASE_FAIL=数据库操作失败
|
||||
ERRCODE.COMMON.REQUEST_FAILED=Http(s)请求失败:{0}
|
||||
ERRCODE.COMMON.FIXED_DICT=系统固定字典数据不允许删除或修改类型
|
||||
ERRCODE.COMMON.FIXED_DICT_NOT_ALLOW_ADD=此系统固定字典类型不能添加子项
|
||||
ERRCODE.COMMON.FIXED_CONFIG_DEL=系统固定配置参数[{0}]不能删除
|
||||
ERRCODE.COMMON.FIXED_CONFIG_UPDATE=系统固定配置参数[{0}]不能修改键名
|
||||
ERRCODE.COMMON.ASYNC_TASK_RUNNING=任务“{0}”正在运行中
|
||||
ERRCODE.COMMON.UPLOAD_FILE_EMPTY=上传文件不能为空
|
||||
ERR.COMMON.NOT_NULL=参数[{0}]不能为NULL
|
||||
ERR.COMMON.NOT_EMPTY=参数[{0}]不能为空
|
||||
ERR.COMMON.DATA_NOT_FOUND=数据不存在
|
||||
ERR.COMMON.DATA_NOT_FOUND_BY_ID=数据不存在:{0}={1}
|
||||
ERR.COMMON.DATA_CONFLICT=数据[{0}]冲突
|
||||
ERR.COMMON.INVALID_REQUEST_ARG=请求参数[{0}]不符合校验规则
|
||||
ERR.COMMON.INVALID_REQUEST_METHOD=请求方法错误
|
||||
ERR.COMMON.SYSTEM_ERROR=系统内部错误
|
||||
ERR.COMMON.UNKNOWN_ERROR=未知错误
|
||||
ERR.COMMON.DATABASE_FAIL=数据库操作失败
|
||||
ERR.COMMON.REQUEST_FAILED=Http(s)请求失败:{0}
|
||||
ERR.COMMON.FIXED_DICT=系统固定字典数据不允许删除或修改类型
|
||||
ERR.COMMON.FIXED_DICT_NOT_ALLOW_ADD=此系统固定字典类型不能添加子项
|
||||
ERR.COMMON.FIXED_CONFIG_DEL=系统固定配置参数[{0}]不能删除
|
||||
ERR.COMMON.FIXED_CONFIG_UPDATE=系统固定配置参数[{0}]不能修改键名
|
||||
ERR.COMMON.ASYNC_TASK_RUNNING=任务“{0}”正在运行中
|
||||
ERR.COMMON.UPLOAD_FILE_EMPTY=上传文件不能为空
|
||||
ERR.COMMON.INVALID_CAPTCHA=验证码错误
|
||||
ERR.COMMON.CAPTCHA_EXPIRED=验证码已过期
|
||||
|
||||
AsyncTask.SuccessMsg=任务执行成功
|
||||
@ -1,20 +1,22 @@
|
||||
#错误消息
|
||||
ERRCODE.COMMON.NOT_NULL=Parameter "{0}" cannot be null.
|
||||
ERRCODE.COMMON.NOT_EMPTY=Parameter "{0}" cannot be empty.
|
||||
ERRCODE.COMMON.DATA_NOT_FOUND=Data not found.
|
||||
ERRCODE.COMMON.DATA_NOT_FOUND_BY_ID=Data not found: {0}={1}
|
||||
ERRCODE.COMMON.DATA_CONFLICT=Data conflict: [{0}]
|
||||
ERRCODE.COMMON.INVALID_REQUEST_ARG=Invalid parameter: {0}
|
||||
ERRCODE.COMMON.INVALID_REQUEST_METHOD=Invalid request method.
|
||||
ERRCODE.COMMON.SYSTEM_ERROR=System error.
|
||||
ERRCODE.COMMON.UNKNOWN_ERROR=Unknown error.
|
||||
ERRCODE.COMMON.DATABASE_FAIL=Database error.
|
||||
ERRCODE.COMMON.REQUEST_FAILED=Http(s) request failed: {0}
|
||||
ERRCODE.COMMON.FIXED_DICT=The fixed dict cannot be delete or modify type.
|
||||
ERRCODE.COMMON.FIXED_DICT_NOT_ALLOW_ADD=The fixed dict not allow to add data items.
|
||||
ERRCODE.COMMON.FIXED_CONFIG_DEL=The fixed config "{0}" cannot be delete.
|
||||
ERRCODE.COMMON.FIXED_CONFIG_UPDATE=The fixed config "{0}" cannot modify key.
|
||||
ERRCODE.COMMON.ASYNC_TASK_RUNNING=The task "{0}" is running.
|
||||
ERRCODE.COMMON.UPLOAD_FILE_EMPTY=Upload file cannot be empty.
|
||||
ERR.COMMON.NOT_NULL=Parameter "{0}" cannot be null.
|
||||
ERR.COMMON.NOT_EMPTY=Parameter "{0}" cannot be empty.
|
||||
ERR.COMMON.DATA_NOT_FOUND=Data not found.
|
||||
ERR.COMMON.DATA_NOT_FOUND_BY_ID=Data not found: {0}={1}
|
||||
ERR.COMMON.DATA_CONFLICT=Data conflict: [{0}]
|
||||
ERR.COMMON.INVALID_REQUEST_ARG=Invalid parameter: {0}
|
||||
ERR.COMMON.INVALID_REQUEST_METHOD=Invalid request method.
|
||||
ERR.COMMON.SYSTEM_ERROR=System error.
|
||||
ERR.COMMON.UNKNOWN_ERROR=Unknown error.
|
||||
ERR.COMMON.DATABASE_FAIL=Database error.
|
||||
ERR.COMMON.REQUEST_FAILED=Http(s) request failed: {0}
|
||||
ERR.COMMON.FIXED_DICT=The fixed dict cannot be delete or modify type.
|
||||
ERR.COMMON.FIXED_DICT_NOT_ALLOW_ADD=The fixed dict not allow to add data items.
|
||||
ERR.COMMON.FIXED_CONFIG_DEL=The fixed config "{0}" cannot be delete.
|
||||
ERR.COMMON.FIXED_CONFIG_UPDATE=The fixed config "{0}" cannot modify key.
|
||||
ERR.COMMON.ASYNC_TASK_RUNNING=The task "{0}" is running.
|
||||
ERR.COMMON.UPLOAD_FILE_EMPTY=Upload file cannot be empty.
|
||||
ERR.COMMON.INVALID_CAPTCHA=Invalid captcha.
|
||||
ERR.COMMON.CAPTCHA_EXPIRED=The captcha is expired.
|
||||
|
||||
AsyncTask.SuccessMsg=Task completed.
|
||||
@ -1,20 +1,22 @@
|
||||
#錯誤消息
|
||||
ERRCODE.COMMON.NOT_NULL=參數[{0}]不能為NULL
|
||||
ERRCODE.COMMON.NOT_EMPTY=參數[{0}]不能為空
|
||||
ERRCODE.COMMON.DATA_NOT_FOUND=數據不存在
|
||||
ERRCODE.COMMON.DATA_NOT_FOUND_BY_ID=數據不存在:{0}={1}
|
||||
ERRCODE.COMMON.DATA_CONFLICT=數據[{0}]衝突
|
||||
ERRCODE.COMMON.INVALID_REQUEST_ARG=請求參數[{0}]不符合校驗規則
|
||||
ERRCODE.COMMON.INVALID_REQUEST_METHOD=請求方法錯誤
|
||||
ERRCODE.COMMON.SYSTEM_ERROR=系統內部錯誤
|
||||
ERRCODE.COMMON.UNKNOWN_ERROR=未知錯誤
|
||||
ERRCODE.COMMON.DATABASE_FAIL=資料庫操作失敗
|
||||
ERRCODE.COMMON.REQUEST_FAILED=Http(s)請求失敗:{0}
|
||||
ERRCODE.COMMON.FIXED_DICT=系統固定字典數據不允許刪除或修改類型
|
||||
ERRCODE.COMMON.FIXED_DICT_NOT_ALLOW_ADD=此系統固定字典類型不能添加子項
|
||||
ERRCODE.COMMON.FIXED_CONFIG_DEL=系統固定配置參數[{0}]不能刪除
|
||||
ERRCODE.COMMON.FIXED_CONFIG_UPDATE=系統固定配置參數[{0}]不能修改鍵名
|
||||
ERRCODE.COMMON.ASYNC_TASK_RUNNING=任務“{0}”正在運行中
|
||||
ERRCODE.COMMON.UPLOAD_FILE_EMPTY=上傳文件不能為空
|
||||
ERR.COMMON.NOT_NULL=參數[{0}]不能為NULL
|
||||
ERR.COMMON.NOT_EMPTY=參數[{0}]不能為空
|
||||
ERR.COMMON.DATA_NOT_FOUND=數據不存在
|
||||
ERR.COMMON.DATA_NOT_FOUND_BY_ID=數據不存在:{0}={1}
|
||||
ERR.COMMON.DATA_CONFLICT=數據[{0}]衝突
|
||||
ERR.COMMON.INVALID_REQUEST_ARG=請求參數[{0}]不符合校驗規則
|
||||
ERR.COMMON.INVALID_REQUEST_METHOD=請求方法錯誤
|
||||
ERR.COMMON.SYSTEM_ERROR=系統內部錯誤
|
||||
ERR.COMMON.UNKNOWN_ERROR=未知錯誤
|
||||
ERR.COMMON.DATABASE_FAIL=資料庫操作失敗
|
||||
ERR.COMMON.REQUEST_FAILED=Http(s)請求失敗:{0}
|
||||
ERR.COMMON.FIXED_DICT=系統固定字典數據不允許刪除或修改類型
|
||||
ERR.COMMON.FIXED_DICT_NOT_ALLOW_ADD=此系統固定字典類型不能添加子項
|
||||
ERR.COMMON.FIXED_CONFIG_DEL=系統固定配置參數[{0}]不能刪除
|
||||
ERR.COMMON.FIXED_CONFIG_UPDATE=系統固定配置參數[{0}]不能修改鍵名
|
||||
ERR.COMMON.ASYNC_TASK_RUNNING=任務“{0}”正在運行中
|
||||
ERR.COMMON.UPLOAD_FILE_EMPTY=上傳文件不能為空
|
||||
ERR.COMMON.INVALID_CAPTCHA=驗證碼錯誤
|
||||
ERR.COMMON.CAPTCHA_EXPIRED=驗證碼已過期
|
||||
|
||||
AsyncTask.SuccessMsg=任務執行成功
|
||||
|
||||
Binary file not shown.
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>chestnut-common</artifactId>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<version>1.5.4</version>
|
||||
<version>1.5.5</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>chestnut-common</artifactId>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<version>1.5.4</version>
|
||||
<version>1.5.5</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>chestnut-common</artifactId>
|
||||
<groupId>com.chestnut</groupId>
|
||||
<version>1.5.4</version>
|
||||
<version>1.5.5</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user