diff --git a/README.md b/README.md index 4b5ebd9a..862c007a 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/chestnut-admin/pom.xml b/chestnut-admin/pom.xml index 7f086032..05da9247 100644 --- a/chestnut-admin/pom.xml +++ b/chestnut-admin/pom.xml @@ -3,7 +3,7 @@ chestnut com.chestnut - 1.5.4 + 1.5.5 4.0.0 jar diff --git a/chestnut-admin/src/main/java/com/chestnut/ChestnutApplication.java b/chestnut-admin/src/main/java/com/chestnut/ChestnutApplication.java index b553bb39..08d26204 100644 --- a/chestnut-admin/src/main/java/com/chestnut/ChestnutApplication.java +++ b/chestnut-admin/src/main/java/com/chestnut/ChestnutApplication.java @@ -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"); } diff --git a/chestnut-admin/src/main/resources/application-dev.yml b/chestnut-admin/src/main/resources/application-dev.yml index f444e9d9..f7a52c77 100644 --- a/chestnut-admin/src/main/resources/application-dev.yml +++ b/chestnut-admin/src/main/resources/application-dev.yml @@ -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: diff --git a/chestnut-admin/src/main/resources/application-prod.yml b/chestnut-admin/src/main/resources/application-prod.yml index 24a87810..ddf221c6 100644 --- a/chestnut-admin/src/main/resources/application-prod.yml +++ b/chestnut-admin/src/main/resources/application-prod.yml @@ -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: diff --git a/chestnut-admin/src/main/resources/application-test.yml b/chestnut-admin/src/main/resources/application-test.yml index 75976507..1580193b 100644 --- a/chestnut-admin/src/main/resources/application-test.yml +++ b/chestnut-admin/src/main/resources/application-test.yml @@ -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: diff --git a/chestnut-admin/src/main/resources/db/migration/mysql/V1.5.5__update.sql b/chestnut-admin/src/main/resources/db/migration/mysql/V1.5.5__update.sql new file mode 100644 index 00000000..04235ad8 --- /dev/null +++ b/chestnut-admin/src/main/resources/db/migration/mysql/V1.5.5__update.sql @@ -0,0 +1,3 @@ +ALTER TABLE cms_cfd_default ADD COLUMN `uid` bigint; +ALTER TABLE cms_custom_form MODIFY COLUMN `rule_limit` VARCHAR(20); + diff --git a/chestnut-admin/src/main/resources/i18n/messages.properties b/chestnut-admin/src/main/resources/i18n/messages.properties index 80ac2472..e69de29b 100644 --- a/chestnut-admin/src/main/resources/i18n/messages.properties +++ b/chestnut-admin/src/main/resources/i18n/messages.properties @@ -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=上传的文件大小超出限制的文件大小!
允许的文件最大大小是:{0}MB! -upload.filename.exceed.length=上传的文件名最长{0}个字符 \ No newline at end of file diff --git a/chestnut-admin/src/main/resources/i18n/messages_en.properties b/chestnut-admin/src/main/resources/i18n/messages_en.properties index 62118996..e69de29b 100644 --- a/chestnut-admin/src/main/resources/i18n/messages_en.properties +++ b/chestnut-admin/src/main/resources/i18n/messages_en.properties @@ -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}. \ No newline at end of file diff --git a/chestnut-admin/src/main/resources/i18n/messages_zh_TW.properties b/chestnut-admin/src/main/resources/i18n/messages_zh_TW.properties index 058d0e15..e69de29b 100644 --- a/chestnut-admin/src/main/resources/i18n/messages_zh_TW.properties +++ b/chestnut-admin/src/main/resources/i18n/messages_zh_TW.properties @@ -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=上傳的檔案大小超出限制的檔案大小!
允許的檔案最大大小是:{0}MB! -upload.filename.exceed.length=上傳的檔案名最長{0}個字元 diff --git a/chestnut-admin/src/main/resources/logback-dev.xml b/chestnut-admin/src/main/resources/logback-dev.xml new file mode 100644 index 00000000..7335051a --- /dev/null +++ b/chestnut-admin/src/main/resources/logback-dev.xml @@ -0,0 +1,96 @@ + + + + + + + + + + + 262144 + + 0 + + true + + + + + + + ${log.pattern} + + + + + ${log.pattern} + + + + + + ${log.path}/out.log + + + + ${log.path}/out-%d{yyyy-MM-dd}-${LOCAL_IP}.log + + 5 + + + ${log.pattern} + + + false + + 8192 + + + + + ${log.path}/cron.log + + + ${log.path}/cron-%d{yyyy-MM-dd}-${LOCAL_IP}.log + + 5 + + + ${log.pattern} + + + + + + ${log.path}/publish.log + + + ${log.path}/publish-%d{yyyy-MM-dd}-${LOCAL_IP}.log + + 5 + + + ${log.pattern} + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/chestnut-admin/src/main/resources/logback-prod.xml b/chestnut-admin/src/main/resources/logback-prod.xml new file mode 100644 index 00000000..69c78a45 --- /dev/null +++ b/chestnut-admin/src/main/resources/logback-prod.xml @@ -0,0 +1,112 @@ + + + + + + + + + + + ${log.pattern} + + + + + + 262144 + + 0 + + true + + + + + + ${log.path}/out.log + + + + ${log.path}/out-%d{yyyy-MM-dd}-${LOCAL_IP}.log + + 5 + + + ${log.pattern} + + + false + + 8192 + + + + ${log.path}/error.log + + + + ${log.path}/error-%d{yyyy-MM-dd}-${LOCAL_IP}.log + + 5 + + + ${log.pattern} + + + + WARN + + ACCEPT + + DENY + + + + + + ${log.path}/cron.log + + + ${log.path}/cron-%d{yyyy-MM-dd}-${LOCAL_IP}.log + + 5 + + + ${log.pattern} + + + + + + ${log.path}/publish.log + + + ${log.path}/publish-%d{yyyy-MM-dd}-${LOCAL_IP}.log + + 5 + + + ${log.pattern} + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/chestnut-admin/src/main/resources/logback.xml b/chestnut-admin/src/main/resources/logback.xml deleted file mode 100644 index 8ab3cf70..00000000 --- a/chestnut-admin/src/main/resources/logback.xml +++ /dev/null @@ -1,112 +0,0 @@ - - - - - - - - - - - ${log.pattern} - - - - - - ${log.path}/out.log - - - - ${log.path}/out.%d{yyyy-MM-dd}.log - - 60 - - - ${log.pattern} - - - - INFO - - ACCEPT - - DENY - - - - - ${log.path}/error.log - - - - ${log.path}/error.%d{yyyy-MM-dd}.log - - 60 - - - ${log.pattern} - - - - ERROR - - ACCEPT - - DENY - - - - - - ${log.path}/cron.log - - - ${log.path}/cron.%d{yyyy-MM-dd}.log - - 60 - - - ${log.pattern} - - - - - - ${log.path}/publish.log - - - ${log.path}/publish.%d{yyyy-MM-dd}.log - - 60 - - - ${log.pattern} - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/chestnut-cms/chestnut-cms-advertisement/pom.xml b/chestnut-cms/chestnut-cms-advertisement/pom.xml index fa9e647a..1302f909 100644 --- a/chestnut-cms/chestnut-cms-advertisement/pom.xml +++ b/chestnut-cms/chestnut-cms-advertisement/pom.xml @@ -7,7 +7,7 @@ com.chestnut chestnut-cms - 1.5.4 + 1.5.5 chestnut-cms-advertisement diff --git a/chestnut-cms/chestnut-cms-advertisement/src/main/java/com/chestnut/advertisement/cache/AdMonitoredCache.java b/chestnut-cms/chestnut-cms-advertisement/src/main/java/com/chestnut/advertisement/cache/AdNameMonitoredCache.java similarity index 69% rename from chestnut-cms/chestnut-cms-advertisement/src/main/java/com/chestnut/advertisement/cache/AdMonitoredCache.java rename to chestnut-cms/chestnut-cms-advertisement/src/main/java/com/chestnut/advertisement/cache/AdNameMonitoredCache.java index b3f5cd39..95cb4af0 100644 --- a/chestnut-cms/chestnut-cms-advertisement/src/main/java/com/chestnut/advertisement/cache/AdMonitoredCache.java +++ b/chestnut-cms/chestnut-cms-advertisement/src/main/java/com/chestnut/advertisement/cache/AdNameMonitoredCache.java @@ -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> { +public class AdNameMonitoredCache implements IMonitoredCache> { - 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> { @Override public String getCacheName() { - return "{MONITORED.CACHE.AD}"; + return NAME; } @Override @@ -64,7 +66,15 @@ public class AdMonitoredCache implements IMonitoredCache> { 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()); } } diff --git a/chestnut-cms/chestnut-cms-advertisement/src/main/java/com/chestnut/advertisement/cache/AdRedirectUrlMonitoredCache.java b/chestnut-cms/chestnut-cms-advertisement/src/main/java/com/chestnut/advertisement/cache/AdRedirectUrlMonitoredCache.java new file mode 100644 index 00000000..03125667 --- /dev/null +++ b/chestnut-cms/chestnut-cms-advertisement/src/main/java/com/chestnut/advertisement/cache/AdRedirectUrlMonitoredCache.java @@ -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 { + + 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()); + } +} diff --git a/chestnut-cms/chestnut-cms-advertisement/src/main/java/com/chestnut/advertisement/controller/front/AdApiController.java b/chestnut-cms/chestnut-cms-advertisement/src/main/java/com/chestnut/advertisement/controller/front/AdApiController.java index b124bb9f..4ef1d7fd 100644 --- a/chestnut-cms/chestnut-cms-advertisement/src/main/java/com/chestnut/advertisement/controller/front/AdApiController.java +++ b/chestnut-cms/chestnut-cms-advertisement/src/main/java/com/chestnut/advertisement/controller/front/AdApiController.java @@ -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(); diff --git a/chestnut-cms/chestnut-cms-advertisement/src/main/java/com/chestnut/advertisement/service/IAdvertisementService.java b/chestnut-cms/chestnut-cms-advertisement/src/main/java/com/chestnut/advertisement/service/IAdvertisementService.java index 8d7dca51..b9fc60af 100644 --- a/chestnut-cms/chestnut-cms-advertisement/src/main/java/com/chestnut/advertisement/service/IAdvertisementService.java +++ b/chestnut-cms/chestnut-cms-advertisement/src/main/java/com/chestnut/advertisement/service/IAdvertisementService.java @@ -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 */ @@ -35,7 +34,16 @@ public interface IAdvertisementService extends IService { * @return Map */ Map getAdvertisementMap(); - + + /** + * 获取广告跳转地址 + * + * @param siteId + * @param advertisementId + * @return + */ + String getRedirectUrlByAdId(Long siteId, Long advertisementId); + /** * 添加广告数据 * diff --git a/chestnut-cms/chestnut-cms-advertisement/src/main/java/com/chestnut/advertisement/service/impl/AdvertisementServiceImpl.java b/chestnut-cms/chestnut-cms-advertisement/src/main/java/com/chestnut/advertisement/service/impl/AdvertisementServiceImpl.java index 7cc16d1b..9f9ebec8 100644 --- a/chestnut-cms/chestnut-cms-advertisement/src/main/java/com/chestnut/advertisement/service/impl/AdvertisementServiceImpl.java +++ b/chestnut-cms/chestnut-cms-advertisement/src/main/java/com/chestnut/advertisement/service/impl/AdvertisementServiceImpl.java @@ -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 - implements IAdvertisementService { + implements IAdvertisementService, CommandLineRunner { - private final AdMonitoredCache adCache; + private final AdNameMonitoredCache adNameCache; + + private final AdRedirectUrlMonitoredCache adRedirectUrlCache; private final Map advertisementTypes; @@ -85,7 +84,7 @@ public class AdvertisementServiceImpl extends ServiceImpl 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 advertisementIds) { - this.removeByIds(advertisementIds); - this.adCache.clear(); + List 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 com.chestnut chestnut-cms - 1.5.4 + 1.5.5 chestnut-cms-block diff --git a/chestnut-cms/chestnut-cms-block/src/main/java/com/chestnut/block/ManualPageWidgetType.java b/chestnut-cms/chestnut-cms-block/src/main/java/com/chestnut/block/ManualPageWidgetType.java index 49ebf425..cd4eeb24 100644 --- a/chestnut-cms/chestnut-cms-block/src/main/java/com/chestnut/block/ManualPageWidgetType.java +++ b/chestnut-cms/chestnut-cms-block/src/main/java/com/chestnut/block/ManualPageWidgetType.java @@ -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; diff --git a/chestnut-cms/chestnut-cms-comment/pom.xml b/chestnut-cms/chestnut-cms-comment/pom.xml index 87fa82ab..2ab1f173 100644 --- a/chestnut-cms/chestnut-cms-comment/pom.xml +++ b/chestnut-cms/chestnut-cms-comment/pom.xml @@ -6,7 +6,7 @@ com.chestnut chestnut-cms - 1.5.4 + 1.5.5 chestnut-cms-comment diff --git a/chestnut-cms/chestnut-cms-contentcore/pom.xml b/chestnut-cms/chestnut-cms-contentcore/pom.xml index f6576b1c..fac036f1 100644 --- a/chestnut-cms/chestnut-cms-contentcore/pom.xml +++ b/chestnut-cms/chestnut-cms-contentcore/pom.xml @@ -7,7 +7,7 @@ com.chestnut chestnut-cms - 1.5.4 + 1.5.5 chestnut-cms-contentcore diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/config/properties/CMSProperties.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/config/properties/CMSProperties.java index 8fb0a219..4a0fa719 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/config/properties/CMSProperties.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/config/properties/CMSProperties.java @@ -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; } diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/CoreController.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/CoreController.java index c185aa63..f782af79 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/CoreController.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/CoreController.java @@ -89,10 +89,14 @@ public class CoreController extends BaseRestController { IInternalDataType internalDataType = ContentCoreUtils.getInternalDataType(dataType); Assert.notNull(internalDataType, () -> ContentCoreErrorCode.UNSUPPORTED_INTERNAL_DATA_TYPE.exception(dataType)); - IInternalDataType.RequestData data = new IInternalDataType.RequestData(dataId, pageIndex, publishPipe, - true, ServletUtils.getParamMap(ServletUtils.getRequest())); - String pageData = internalDataType.getPageData(data); - response.getWriter().write(pageData); + 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)); - IInternalDataType.RequestData data = new IInternalDataType.RequestData(dataId, pageIndex, publishPipe, - false, ServletUtils.getParamMap(ServletUtils.getRequest())); - String pageData = internalDataType.getPageData(data); - response.getWriter().write(pageData); + 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/") diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/ResourceController.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/ResourceController.java index 617c8b6d..7564b51f 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/ResourceController.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/ResourceController.java @@ -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 q = new LambdaQueryWrapper() .eq(CmsResource::getSiteId, site.getSiteId()) .like(StringUtils.isNotEmpty(name), CmsResource::getFileName, name) diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/TemplateController.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/TemplateController.java index 4055df70..a0fc5d36 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/TemplateController.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/controller/TemplateController.java @@ -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 page = this.templateService.lambdaQuery().eq(CmsTemplate::getSiteId, site.getSiteId()) .eq(StringUtils.isNotEmpty(publishPipeCode), CmsTemplate::getPublishPipeCode, publishPipeCode) diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/ISiteService.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/ISiteService.java index 9940e971..54728976 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/ISiteService.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/ISiteService.java @@ -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 { /** @@ -38,7 +36,16 @@ public interface ISiteService extends IService { */ boolean checkSiteUnique(String siteName, String sitePath, Long siteId); - /** + /** + * 获取指定id站点数据,如果不存在则返回当前站点数据 + * + * @param siteId + * @param request + * @return + */ + CmsSite getSiteOrCurrent(Long siteId, HttpServletRequest request); + + /** * 获取当前站点,保存在token中 */ CmsSite getCurrentSite(HttpServletRequest request); diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/impl/PublishServiceImpl.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/impl/PublishServiceImpl.java index ef08e5ad..5f8f83cf 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/impl/PublishServiceImpl.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/impl/PublishServiceImpl.java @@ -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(); } diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/impl/ResourceServiceImpl.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/impl/ResourceServiceImpl.java index 92e3d26a..7e899562 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/impl/ResourceServiceImpl.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/impl/ResourceServiceImpl.java @@ -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 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(); diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/impl/SiteThemeService.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/impl/SiteThemeService.java index 112f099b..fa454b4b 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/impl/SiteThemeService.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/service/impl/SiteThemeService.java @@ -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 contentCoreHandlers; + private final Map 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."); diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/util/TemplateUtils.java b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/util/TemplateUtils.java index a9874fca..7e56e730 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/util/TemplateUtils.java +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/java/com/chestnut/contentcore/util/TemplateUtils.java @@ -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; + } + /** * 添加站点数据到模板上线文变量中 * diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/resources/i18n/messages.properties b/chestnut-cms/chestnut-cms-contentcore/src/main/resources/i18n/messages.properties index 3cdfb8e1..832655e8 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/resources/i18n/messages.properties +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/resources/i18n/messages.properties @@ -205,6 +205,7 @@ SCHEDULED_TASK.SitePublishJobHandler=定时发布任务 SCHEDULED_TASK.ContentTopCancelJobHandler=内容置顶取消任务 SCHEDULED_TASK.UpdateDynamicDataJobHandler=保存内容动态数据任务 SCHEDULED_TASK.ContentOfflineJobHandler=内容定时下线任务 +SCHEDULED_TASK.ResourceChunkClearJobHandler=资源上传分片文件过期删除任务 # 缓存监控 MONITORED.CACHE.SITE=站点 diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/resources/i18n/messages_en.properties b/chestnut-cms/chestnut-cms-contentcore/src/main/resources/i18n/messages_en.properties index 15a16550..ae0fbd7d 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/resources/i18n/messages_en.properties +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/resources/i18n/messages_en.properties @@ -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/ diff --git a/chestnut-cms/chestnut-cms-contentcore/src/main/resources/i18n/messages_zh_TW.properties b/chestnut-cms/chestnut-cms-contentcore/src/main/resources/i18n/messages_zh_TW.properties index 8a598d3d..f2b0b0c8 100644 --- a/chestnut-cms/chestnut-cms-contentcore/src/main/resources/i18n/messages_zh_TW.properties +++ b/chestnut-cms/chestnut-cms-contentcore/src/main/resources/i18n/messages_zh_TW.properties @@ -205,6 +205,7 @@ SCHEDULED_TASK.SitePublishJobHandler=定時發布任務 SCHEDULED_TASK.ContentTopCancelJobHandler=內容置頂取消任務 SCHEDULED_TASK.UpdateDynamicDataJobHandler=保存內容動態數據任務 SCHEDULED_TASK.ContentOfflineJobHandler=內容定時下線任務 +SCHEDULED_TASK.ResourceChunkClearJobHandler=資源上傳分片文件過期刪除任務 # 缓存监控 MONITORED.CACHE.SITE=站點 diff --git a/chestnut-cms/chestnut-cms-customform/pom.xml b/chestnut-cms/chestnut-cms-customform/pom.xml index 9eaf9587..e8a59746 100644 --- a/chestnut-cms/chestnut-cms-customform/pom.xml +++ b/chestnut-cms/chestnut-cms-customform/pom.xml @@ -7,7 +7,7 @@ com.chestnut chestnut-cms - 1.5.4 + 1.5.5 chestnut-cms-customform diff --git a/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/CmsCustomFormMetaModelType.java b/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/CmsCustomFormMetaModelType.java index 684ea89f..8afaaeaf 100644 --- a/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/CmsCustomFormMetaModelType.java +++ b/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/CmsCustomFormMetaModelType.java @@ -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); } diff --git a/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/CustomFormConsts.java b/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/CustomFormConsts.java index 7629bd4e..c6c65263 100644 --- a/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/CustomFormConsts.java +++ b/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/CustomFormConsts.java @@ -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; + } } diff --git a/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/cache/CustomFormCaptchaMonitoredCache.java b/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/cache/CustomFormCaptchaMonitoredCache.java new file mode 100644 index 00000000..4ff8d8ca --- /dev/null +++ b/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/cache/CustomFormCaptchaMonitoredCache.java @@ -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 { + + 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); + } +} diff --git a/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/controller/CustomFormController.java b/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/controller/CustomFormController.java index 054b362e..96ddc68b 100644 --- a/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/controller/CustomFormController.java +++ b/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/controller/CustomFormController.java @@ -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 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, diff --git a/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/controller/front/CustomFormApiController.java b/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/controller/front/CustomFormApiController.java index 1b89f3ac..a290f6d4 100644 --- a/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/controller/front/CustomFormApiController.java +++ b/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/controller/front/CustomFormApiController.java @@ -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; /** *

@@ -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 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 formData) { + public R submitForm(@RequestBody Map 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); - } - } - }); - - this.modelDataService.saveModelData(form.getModelId(), formData); + if (YesOrNo.isYes(form.getNeedLogin())) { + formData.put(CmsCustomFormMetaModelType.FIELD_UID.getCode(), StpMemberUtil.getLoginUser().getUserId()); + } + 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); + } } diff --git a/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/domain/CmsCustomFormData.java b/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/domain/CmsCustomFormData.java index 82b92197..aad3d40a 100644 --- a/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/domain/CmsCustomFormData.java +++ b/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/domain/CmsCustomFormData.java @@ -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,10 +36,11 @@ 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; - private static final long serialVersionUID=1L; - public static final String TABLE_NAME = "cms_cfd_default"; @TableId(value = "data_id", type = IdType.INPUT) diff --git a/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/exception/CustomFormErrorCode.java b/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/exception/CustomFormErrorCode.java new file mode 100644 index 00000000..e4c2f874 --- /dev/null +++ b/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/exception/CustomFormErrorCode.java @@ -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() + "}"; + } +} diff --git a/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/fixed/config/CustomFormCaptchaExpireSeconds.java b/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/fixed/config/CustomFormCaptchaExpireSeconds.java new file mode 100644 index 00000000..785bbcdf --- /dev/null +++ b/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/fixed/config/CustomFormCaptchaExpireSeconds.java @@ -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); + } +} diff --git a/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/rule/CustomFormLimitRule_IP.java b/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/rule/CustomFormLimitRule_IP.java new file mode 100644 index 00000000..7ec08964 --- /dev/null +++ b/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/rule/CustomFormLimitRule_IP.java @@ -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 dataMap) { + Object ipAddr = MapUtils.getString(dataMap, CmsCustomFormMetaModelType.FIELD_CLIENT_IP.getCode()); + Long count = customFormDataMapper.selectCount(new LambdaQueryWrapper() + .eq(CmsCustomFormData::getModelId, form.getFormId()) + .eq(CmsCustomFormData::getClientIp, ipAddr)); + return count == 0; + } + + @Override + public int getOrder() { + return 2; + } +} diff --git a/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/rule/CustomFormLimitRule_UUID.java b/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/rule/CustomFormLimitRule_UUID.java new file mode 100644 index 00000000..5ec2e1a7 --- /dev/null +++ b/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/rule/CustomFormLimitRule_UUID.java @@ -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 dataMap) { + Object uuid = MapUtils.getString(dataMap, CmsCustomFormMetaModelType.FIELD_UUID.getCode()); + Long count = customFormDataMapper.selectCount(new LambdaQueryWrapper() + .eq(CmsCustomFormData::getModelId, form.getFormId()) + .eq(CmsCustomFormData::getUuid, uuid)); + return count == 0; + } + + @Override + public int getOrder() { + return 3; + } +} diff --git a/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/rule/CustomFormLimitRule_Unlimited.java b/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/rule/CustomFormLimitRule_Unlimited.java new file mode 100644 index 00000000..6210638b --- /dev/null +++ b/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/rule/CustomFormLimitRule_Unlimited.java @@ -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 dataMap) { + return true; + } + + @Override + public int getOrder() { + return 1; + } +} diff --git a/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/rule/ICustomFormLimitRule.java b/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/rule/ICustomFormLimitRule.java new file mode 100644 index 00000000..caade3ac --- /dev/null +++ b/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/rule/ICustomFormLimitRule.java @@ -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 dataMap); +} diff --git a/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/service/ICustomFormApiService.java b/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/service/ICustomFormApiService.java new file mode 100644 index 00000000..fb69d074 --- /dev/null +++ b/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/service/ICustomFormApiService.java @@ -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 formData) throws IOException; +} diff --git a/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/service/ICustomFormService.java b/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/service/ICustomFormService.java index 014bc39b..1249ec0e 100644 --- a/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/service/ICustomFormService.java +++ b/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/service/ICustomFormService.java @@ -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 { + ICustomFormLimitRule getLimitRule(String ruleId); + /** * 添加自定义表单 * diff --git a/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/service/impl/CustomFormApiServiceImpl.java b/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/service/impl/CustomFormApiServiceImpl.java new file mode 100644 index 00000000..55bbe66b --- /dev/null +++ b/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/service/impl/CustomFormApiServiceImpl.java @@ -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 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 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(); + } + } +} diff --git a/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/service/impl/CustomFormServiceImpl.java b/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/service/impl/CustomFormServiceImpl.java index 0a2deb96..652394b1 100644 --- a/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/service/impl/CustomFormServiceImpl.java +++ b/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/service/impl/CustomFormServiceImpl.java @@ -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 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) { diff --git a/chestnut-cms/chestnut-cms-customform/src/main/resources/i18n/messages.properties b/chestnut-cms/chestnut-cms-customform/src/main/resources/i18n/messages.properties index 1d18bf58..a311744b 100644 --- a/chestnut-cms/chestnut-cms-customform/src/main/resources/i18n/messages.properties +++ b/chestnut-cms/chestnut-cms-customform/src/main/resources/i18n/messages.properties @@ -14,4 +14,15 @@ DICT.CustomFormStatus.20=已下线 DICT.CustomFormRule=自定义表单限制状态 DICT.CustomFormRule.0=无限制 DICT.CustomFormRule.1=IP -DICT.CustomFormRule.2=浏览器指纹 \ No newline at end of file +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=请勿重复提交 diff --git a/chestnut-cms/chestnut-cms-customform/src/main/resources/i18n/messages_en.properties b/chestnut-cms/chestnut-cms-customform/src/main/resources/i18n/messages_en.properties index db0e2d42..e8bad8d5 100644 --- a/chestnut-cms/chestnut-cms-customform/src/main/resources/i18n/messages_en.properties +++ b/chestnut-cms/chestnut-cms-customform/src/main/resources/i18n/messages_en.properties @@ -14,4 +14,15 @@ DICT.CustomFormStatus.20=Offline DICT.CustomFormRule=Custom Form Rule DICT.CustomFormRule.0=Unlimited DICT.CustomFormRule.1=IP -DICT.CustomFormRule.2=Browser Fingerprint \ No newline at end of file +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 \ No newline at end of file diff --git a/chestnut-cms/chestnut-cms-customform/src/main/resources/i18n/messages_zh_TW.properties b/chestnut-cms/chestnut-cms-customform/src/main/resources/i18n/messages_zh_TW.properties index 590e60ec..a83186d7 100644 --- a/chestnut-cms/chestnut-cms-customform/src/main/resources/i18n/messages_zh_TW.properties +++ b/chestnut-cms/chestnut-cms-customform/src/main/resources/i18n/messages_zh_TW.properties @@ -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=請勿重複提交 diff --git a/chestnut-cms/chestnut-cms-dynamic/pom.xml b/chestnut-cms/chestnut-cms-dynamic/pom.xml index a2624d9e..3b657b17 100644 --- a/chestnut-cms/chestnut-cms-dynamic/pom.xml +++ b/chestnut-cms/chestnut-cms-dynamic/pom.xml @@ -7,7 +7,7 @@ com.chestnut chestnut-cms - 1.5.4 + 1.5.5 chestnut-cms-dynamic diff --git a/chestnut-cms/chestnut-cms-exmodel/pom.xml b/chestnut-cms/chestnut-cms-exmodel/pom.xml index f858d76f..97ded06e 100644 --- a/chestnut-cms/chestnut-cms-exmodel/pom.xml +++ b/chestnut-cms/chestnut-cms-exmodel/pom.xml @@ -7,7 +7,7 @@ com.chestnut chestnut-cms - 1.5.4 + 1.5.5 chestnut-cms-exmodel diff --git a/chestnut-cms/chestnut-cms-image/pom.xml b/chestnut-cms/chestnut-cms-image/pom.xml index 0b466fcd..bd187024 100644 --- a/chestnut-cms/chestnut-cms-image/pom.xml +++ b/chestnut-cms/chestnut-cms-image/pom.xml @@ -7,7 +7,7 @@ com.chestnut chestnut-cms - 1.5.4 + 1.5.5 chestnut-cms-image diff --git a/chestnut-cms/chestnut-cms-link/pom.xml b/chestnut-cms/chestnut-cms-link/pom.xml index c9f09d09..0bf3e158 100644 --- a/chestnut-cms/chestnut-cms-link/pom.xml +++ b/chestnut-cms/chestnut-cms-link/pom.xml @@ -7,7 +7,7 @@ com.chestnut chestnut-cms - 1.5.4 + 1.5.5 chestnut-cms-link diff --git a/chestnut-cms/chestnut-cms-media/pom.xml b/chestnut-cms/chestnut-cms-media/pom.xml index 1ac6fa2a..166ebc7d 100644 --- a/chestnut-cms/chestnut-cms-media/pom.xml +++ b/chestnut-cms/chestnut-cms-media/pom.xml @@ -7,7 +7,7 @@ com.chestnut chestnut-cms - 1.5.4 + 1.5.5 chestnut-cms-media diff --git a/chestnut-cms/chestnut-cms-member/pom.xml b/chestnut-cms/chestnut-cms-member/pom.xml index 11199de9..d15ad194 100644 --- a/chestnut-cms/chestnut-cms-member/pom.xml +++ b/chestnut-cms/chestnut-cms-member/pom.xml @@ -6,7 +6,7 @@ com.chestnut chestnut-cms - 1.5.4 + 1.5.5 chestnut-cms-member diff --git a/chestnut-cms/chestnut-cms-search/pom.xml b/chestnut-cms/chestnut-cms-search/pom.xml index 9a66ef84..f531692c 100644 --- a/chestnut-cms/chestnut-cms-search/pom.xml +++ b/chestnut-cms/chestnut-cms-search/pom.xml @@ -7,7 +7,7 @@ com.chestnut chestnut-cms - 1.5.4 + 1.5.5 chestnut-cms-search diff --git a/chestnut-cms/chestnut-cms-seo/pom.xml b/chestnut-cms/chestnut-cms-seo/pom.xml index a86a9071..aa7a6de2 100644 --- a/chestnut-cms/chestnut-cms-seo/pom.xml +++ b/chestnut-cms/chestnut-cms-seo/pom.xml @@ -7,7 +7,7 @@ com.chestnut chestnut-cms - 1.5.4 + 1.5.5 chestnut-cms-seo diff --git a/chestnut-cms/chestnut-cms-stat/pom.xml b/chestnut-cms/chestnut-cms-stat/pom.xml index de05788f..79c08dcc 100644 --- a/chestnut-cms/chestnut-cms-stat/pom.xml +++ b/chestnut-cms/chestnut-cms-stat/pom.xml @@ -7,7 +7,7 @@ com.chestnut chestnut-cms - 1.5.4 + 1.5.5 chestnut-cms-stat diff --git a/chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/SiteStatType.java b/chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/SiteBaiduStatType.java similarity index 68% rename from chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/SiteStatType.java rename to chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/SiteBaiduStatType.java index caa284cf..e20967f0 100644 --- a/chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/SiteStatType.java +++ b/chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/SiteBaiduStatType.java @@ -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 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), diff --git a/chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/baidu/api/OverviewGetCommonTrackRptResponse.java b/chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/baidu/api/OverviewGetCommonTrackRptResponse.java index 2f547538..055dc679 100644 --- a/chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/baidu/api/OverviewGetCommonTrackRptResponse.java +++ b/chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/baidu/api/OverviewGetCommonTrackRptResponse.java @@ -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; diff --git a/chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/baidu/api/OverviewGetTimeTrendRptApi.java b/chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/baidu/api/OverviewGetTimeTrendRptApi.java index 7b1f6309..93bf5c59 100644 --- a/chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/baidu/api/OverviewGetTimeTrendRptApi.java +++ b/chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/baidu/api/OverviewGetTimeTrendRptApi.java @@ -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; diff --git a/chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/baidu/api/SiteListApi.java b/chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/baidu/api/SiteListApi.java index c1d22113..cdfc6c35 100644 --- a/chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/baidu/api/SiteListApi.java +++ b/chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/baidu/api/SiteListApi.java @@ -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; diff --git a/chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/domain/CmsSiteVisitLog.java b/chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/domain/CmsSiteVisitLog.java index 0ff6779b..41143b7e 100644 --- a/chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/domain/CmsSiteVisitLog.java +++ b/chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/domain/CmsSiteVisitLog.java @@ -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; + /** * 发生时间 */ diff --git a/chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/domain/vo/ContentStatByCatalogVO.java b/chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/domain/vo/ContentStatByCatalogVO.java index 1a30ee34..d97e6686 100644 --- a/chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/domain/vo/ContentStatByCatalogVO.java +++ b/chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/domain/vo/ContentStatByCatalogVO.java @@ -20,7 +20,6 @@ import com.chestnut.contentcore.domain.CmsCatalog; import lombok.Getter; import lombok.Setter; -import java.util.List; import java.util.Objects; /** diff --git a/chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/service/impl/CmsStatServiceImpl.java b/chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/service/impl/CmsStatServiceImpl.java index f4d05c14..a1e881c1 100644 --- a/chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/service/impl/CmsStatServiceImpl.java +++ b/chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/service/impl/CmsStatServiceImpl.java @@ -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; diff --git a/chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/template/tag/CmsStatTag.java b/chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/template/tag/CmsStatTag.java index dbfed4d7..cf021f46 100644 --- a/chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/template/tag/CmsStatTag.java +++ b/chestnut-cms/chestnut-cms-stat/src/main/java/com/chestnut/cms/stat/template/tag/CmsStatTag.java @@ -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"; diff --git a/chestnut-cms/chestnut-cms-stat/src/main/resources/i18n/messages.properties b/chestnut-cms/chestnut-cms-stat/src/main/resources/i18n/messages.properties index c8f4e8ab..652b42a6 100644 --- a/chestnut-cms/chestnut-cms-stat/src/main/resources/i18n/messages.properties +++ b/chestnut-cms/chestnut-cms-stat/src/main/resources/i18n/messages.properties @@ -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=访问来源 diff --git a/chestnut-cms/chestnut-cms-stat/src/main/resources/i18n/messages_en.properties b/chestnut-cms/chestnut-cms-stat/src/main/resources/i18n/messages_en.properties index f01106b2..98323887 100644 --- a/chestnut-cms/chestnut-cms-stat/src/main/resources/i18n/messages_en.properties +++ b/chestnut-cms/chestnut-cms-stat/src/main/resources/i18n/messages_en.properties @@ -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 diff --git a/chestnut-cms/chestnut-cms-stat/src/main/resources/i18n/messages_zh_TW.properties b/chestnut-cms/chestnut-cms-stat/src/main/resources/i18n/messages_zh_TW.properties index a11ed2aa..2af865e6 100644 --- a/chestnut-cms/chestnut-cms-stat/src/main/resources/i18n/messages_zh_TW.properties +++ b/chestnut-cms/chestnut-cms-stat/src/main/resources/i18n/messages_zh_TW.properties @@ -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=訪問來源 diff --git a/chestnut-cms/chestnut-cms-vote/pom.xml b/chestnut-cms/chestnut-cms-vote/pom.xml index 41704b7f..8b059070 100644 --- a/chestnut-cms/chestnut-cms-vote/pom.xml +++ b/chestnut-cms/chestnut-cms-vote/pom.xml @@ -7,7 +7,7 @@ com.chestnut chestnut-cms - 1.5.4 + 1.5.5 chestnut-cms-vote diff --git a/chestnut-cms/chestnut-cms-word/pom.xml b/chestnut-cms/chestnut-cms-word/pom.xml index bfc1c954..a97e692e 100644 --- a/chestnut-cms/chestnut-cms-word/pom.xml +++ b/chestnut-cms/chestnut-cms-word/pom.xml @@ -7,7 +7,7 @@ com.chestnut chestnut-cms - 1.5.4 + 1.5.5 chestnut-cms-word diff --git a/chestnut-cms/pom.xml b/chestnut-cms/pom.xml index c434c569..0b37f083 100644 --- a/chestnut-cms/pom.xml +++ b/chestnut-cms/pom.xml @@ -5,7 +5,7 @@ com.chestnut chestnut - 1.5.4 + 1.5.5 4.0.0 diff --git a/chestnut-common/chestnut-common-core/pom.xml b/chestnut-common/chestnut-common-core/pom.xml index 349acdb7..f420ba07 100644 --- a/chestnut-common/chestnut-common-core/pom.xml +++ b/chestnut-common/chestnut-common-core/pom.xml @@ -5,7 +5,7 @@ chestnut-common com.chestnut - 1.5.4 + 1.5.5 4.0.0 diff --git a/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/exception/CommonErrorCode.java b/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/exception/CommonErrorCode.java index c783926a..1d765c89 100644 --- a/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/exception/CommonErrorCode.java +++ b/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/exception/CommonErrorCode.java @@ -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() + "}"; } } diff --git a/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/i18n/I18nUtils.java b/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/i18n/I18nUtils.java index 985960ad..86e703ac 100644 --- a/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/i18n/I18nUtils.java +++ b/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/i18n/I18nUtils.java @@ -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; + } } diff --git a/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/ArrayUtils.java b/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/ArrayUtils.java index deca706a..000d5bcb 100644 --- a/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/ArrayUtils.java +++ b/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/ArrayUtils.java @@ -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 boolean isNotEmpty(T[] arr) { return !isEmpty(arr); } + + public static long sumLongValue(List list, Function 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 int sumIntValue(List list, Function 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; + } } diff --git a/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/HttpUtils.java b/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/HttpUtils.java index 45e452e6..8319557b 100644 --- a/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/HttpUtils.java +++ b/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/HttpUtils.java @@ -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 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 headers) throws Exception { if (Objects.isNull(body)) { body = StringUtils.EMPTY; diff --git a/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/IP2RegionUtils.java b/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/IP2RegionUtils.java index da62403e..8d63f39e 100644 --- a/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/IP2RegionUtils.java +++ b/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/IP2RegionUtils.java @@ -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); + } + } } diff --git a/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/ServletUtils.java b/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/ServletUtils.java index e253e64d..a7a582ea 100644 --- a/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/ServletUtils.java +++ b/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/ServletUtils.java @@ -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; } diff --git a/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/TimeUtils.java b/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/TimeUtils.java index 8a8d518e..39d39b73 100644 --- a/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/TimeUtils.java +++ b/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/TimeUtils.java @@ -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))); } diff --git a/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/image/ImageUtils.java b/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/image/ImageUtils.java index e6dafdc1..1465d229 100644 --- a/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/image/ImageUtils.java +++ b/chestnut-common/chestnut-common-core/src/main/java/com/chestnut/common/utils/image/ImageUtils.java @@ -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()); + } } \ No newline at end of file diff --git a/chestnut-common/chestnut-common-core/src/main/resources/i18n/messages.properties b/chestnut-common/chestnut-common-core/src/main/resources/i18n/messages.properties index 3d18be04..07f8b64c 100644 --- a/chestnut-common/chestnut-common-core/src/main/resources/i18n/messages.properties +++ b/chestnut-common/chestnut-common-core/src/main/resources/i18n/messages.properties @@ -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=任务执行成功 \ No newline at end of file diff --git a/chestnut-common/chestnut-common-core/src/main/resources/i18n/messages_en.properties b/chestnut-common/chestnut-common-core/src/main/resources/i18n/messages_en.properties index 2a1d01c4..4b982cb3 100644 --- a/chestnut-common/chestnut-common-core/src/main/resources/i18n/messages_en.properties +++ b/chestnut-common/chestnut-common-core/src/main/resources/i18n/messages_en.properties @@ -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. \ No newline at end of file diff --git a/chestnut-common/chestnut-common-core/src/main/resources/i18n/messages_zh_TW.properties b/chestnut-common/chestnut-common-core/src/main/resources/i18n/messages_zh_TW.properties index 9886d96b..a329315b 100644 --- a/chestnut-common/chestnut-common-core/src/main/resources/i18n/messages_zh_TW.properties +++ b/chestnut-common/chestnut-common-core/src/main/resources/i18n/messages_zh_TW.properties @@ -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=任務執行成功 diff --git a/chestnut-common/chestnut-common-core/src/main/resources/ip2region/ip2region.xdb b/chestnut-common/chestnut-common-core/src/main/resources/ip2region/ip2region.xdb index c78b7928..7052c057 100644 Binary files a/chestnut-common/chestnut-common-core/src/main/resources/ip2region/ip2region.xdb and b/chestnut-common/chestnut-common-core/src/main/resources/ip2region/ip2region.xdb differ diff --git a/chestnut-common/chestnut-common-datasource/pom.xml b/chestnut-common/chestnut-common-datasource/pom.xml index a2f5fe09..1e3980cb 100644 --- a/chestnut-common/chestnut-common-datasource/pom.xml +++ b/chestnut-common/chestnut-common-datasource/pom.xml @@ -5,7 +5,7 @@ chestnut-common com.chestnut - 1.5.4 + 1.5.5 4.0.0 diff --git a/chestnut-common/chestnut-common-extend/pom.xml b/chestnut-common/chestnut-common-extend/pom.xml index f9328178..98668ca6 100644 --- a/chestnut-common/chestnut-common-extend/pom.xml +++ b/chestnut-common/chestnut-common-extend/pom.xml @@ -5,7 +5,7 @@ chestnut-common com.chestnut - 1.5.4 + 1.5.5 4.0.0 diff --git a/chestnut-common/chestnut-common-log/pom.xml b/chestnut-common/chestnut-common-log/pom.xml index b1efcbc1..de5398f9 100644 --- a/chestnut-common/chestnut-common-log/pom.xml +++ b/chestnut-common/chestnut-common-log/pom.xml @@ -5,7 +5,7 @@ chestnut-common com.chestnut - 1.5.4 + 1.5.5 4.0.0 diff --git a/chestnut-common/chestnut-common-log/src/main/java/com/chestnut/common/log/ILogType.java b/chestnut-common/chestnut-common-log/src/main/java/com/chestnut/common/log/ILogType.java index 4312e832..96216fe8 100644 --- a/chestnut-common/chestnut-common-log/src/main/java/com/chestnut/common/log/ILogType.java +++ b/chestnut-common/chestnut-common-log/src/main/java/com/chestnut/common/log/ILogType.java @@ -15,11 +15,10 @@ */ package com.chestnut.common.log; -import java.time.LocalDateTime; - +import com.chestnut.common.log.annotation.Log; import org.aspectj.lang.ProceedingJoinPoint; -import com.chestnut.common.log.annotation.Log; +import java.time.LocalDateTime; /** * 日志类型接口,收集日志相关信息交给LogHandler处理。 @@ -43,7 +42,7 @@ public interface ILogType { * @param log * @param logTime */ - default public void beforeProceed(ProceedingJoinPoint joinPoint, Log log, LocalDateTime logTime) { + default void beforeProceed(ProceedingJoinPoint joinPoint, Log log, LocalDateTime logTime) { } @@ -56,7 +55,7 @@ public interface ILogType { * @param result * @param e */ - default public void afterProceed(ProceedingJoinPoint joinPoint, Log log, LocalDateTime logTime, Object result, Throwable e) { + default void afterProceed(ProceedingJoinPoint joinPoint, Log log, LocalDateTime logTime, Object result, Throwable e) { } } diff --git a/chestnut-common/chestnut-common-log/src/main/java/com/chestnut/common/log/annotation/Log.java b/chestnut-common/chestnut-common-log/src/main/java/com/chestnut/common/log/annotation/Log.java index 254f54fd..37aa2e0f 100644 --- a/chestnut-common/chestnut-common-log/src/main/java/com/chestnut/common/log/annotation/Log.java +++ b/chestnut-common/chestnut-common-log/src/main/java/com/chestnut/common/log/annotation/Log.java @@ -26,8 +26,12 @@ import com.chestnut.common.log.restful.RestfulLogType; /** * 日志记录注解 + * + * @author 兮玥 + * @email 190785909@qq.com + * @ */ -@Target({ ElementType.PARAMETER, ElementType.METHOD }) +@Target({ ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Log { @@ -35,25 +39,25 @@ public @interface Log { /** * 日志标题,一般使用功能模块名称 */ - public String title() default ""; + String title() default ""; /** * 日志类型 */ - public String type() default RestfulLogType.TYPE; + String type() default RestfulLogType.TYPE; /** * 功能类型 */ - public BusinessType businessType() default BusinessType.OTHER; + BusinessType businessType() default BusinessType.OTHER; /** * 是否保存请求的参数 */ - public boolean isSaveRequestData() default true; + boolean isSaveRequestData() default true; /** * 是否保存响应的参数 */ - public boolean isSaveResponseData() default true; + boolean isSaveResponseData() default true; } diff --git a/chestnut-common/chestnut-common-log/src/main/java/com/chestnut/common/log/aspectj/LogAspect.java b/chestnut-common/chestnut-common-log/src/main/java/com/chestnut/common/log/aspectj/LogAspect.java index a586a70c..24b76a77 100644 --- a/chestnut-common/chestnut-common-log/src/main/java/com/chestnut/common/log/aspectj/LogAspect.java +++ b/chestnut-common/chestnut-common-log/src/main/java/com/chestnut/common/log/aspectj/LogAspect.java @@ -35,6 +35,9 @@ import lombok.RequiredArgsConstructor; /** * 日志处理切面实现 + * + * @author 兮玥 + * @email 190785909@qq.com */ @Aspect @Component diff --git a/chestnut-common/chestnut-common-log/src/main/java/com/chestnut/common/log/restful/RestfulLogData.java b/chestnut-common/chestnut-common-log/src/main/java/com/chestnut/common/log/restful/RestfulLogData.java index 211bd70a..bbdbebc0 100644 --- a/chestnut-common/chestnut-common-log/src/main/java/com/chestnut/common/log/restful/RestfulLogData.java +++ b/chestnut-common/chestnut-common-log/src/main/java/com/chestnut/common/log/restful/RestfulLogData.java @@ -15,6 +15,7 @@ */ package com.chestnut.common.log.restful; +import java.io.Serial; import java.io.Serializable; import java.util.LinkedHashMap; @@ -24,6 +25,7 @@ import com.chestnut.common.utils.StringUtils; public class RestfulLogData extends LinkedHashMap implements Serializable { + @Serial private static final long serialVersionUID = 1L; /** diff --git a/chestnut-common/chestnut-common-redis/pom.xml b/chestnut-common/chestnut-common-redis/pom.xml index dae05e55..e72a74fd 100644 --- a/chestnut-common/chestnut-common-redis/pom.xml +++ b/chestnut-common/chestnut-common-redis/pom.xml @@ -5,7 +5,7 @@ chestnut-common com.chestnut - 1.5.4 + 1.5.5 4.0.0 diff --git a/chestnut-common/chestnut-common-redis/src/main/java/com/chestnut/common/redis/RedisCache.java b/chestnut-common/chestnut-common-redis/src/main/java/com/chestnut/common/redis/RedisCache.java index 767d6a05..4c8f461e 100644 --- a/chestnut-common/chestnut-common-redis/src/main/java/com/chestnut/common/redis/RedisCache.java +++ b/chestnut-common/chestnut-common-redis/src/main/java/com/chestnut/common/redis/RedisCache.java @@ -296,6 +296,10 @@ public class RedisCache { return objects; } + public Long getCacheSetSize(final String key) { + return Objects.requireNonNullElse(this.redisTemplate.opsForSet().size(key), 0L); + } + /** * Add element to set cache * @@ -595,4 +599,16 @@ public class RedisCache { public boolean getBit(String cacheKey, long offset) { return Boolean.TRUE.equals(this.redisTemplate.opsForValue().getBit(cacheKey, offset)); } + + public void addHyperLogLog(String cacheKey, Object... values) { + this.redisTemplate.opsForHyperLogLog().add(cacheKey, values); + } + + public Long getHyperLogLogSize(String cacheKey) { + return Objects.requireNonNullElse(this.redisTemplate.opsForHyperLogLog().size(cacheKey), 0L); + } + + public void removeHyperLogLog(String cacheKey) { + this.redisTemplate.opsForHyperLogLog().delete(cacheKey); + } } diff --git a/chestnut-common/chestnut-common-security/pom.xml b/chestnut-common/chestnut-common-security/pom.xml index 8a998027..da2ef4af 100644 --- a/chestnut-common/chestnut-common-security/pom.xml +++ b/chestnut-common/chestnut-common-security/pom.xml @@ -5,7 +5,7 @@ chestnut-common com.chestnut - 1.5.4 + 1.5.5 4.0.0 @@ -18,6 +18,16 @@ org.springframework.boot spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-tomcat + + + + + org.springframework.boot + spring-boot-starter-undertow diff --git a/chestnut-common/chestnut-common-security/src/main/java/com/chestnut/common/security/exception/handler/GlobalExceptionHandler.java b/chestnut-common/chestnut-common-security/src/main/java/com/chestnut/common/security/exception/handler/GlobalExceptionHandler.java index 64edc3ed..7c006501 100644 --- a/chestnut-common/chestnut-common-security/src/main/java/com/chestnut/common/security/exception/handler/GlobalExceptionHandler.java +++ b/chestnut-common/chestnut-common-security/src/main/java/com/chestnut/common/security/exception/handler/GlobalExceptionHandler.java @@ -115,9 +115,10 @@ public class GlobalExceptionHandler { */ @ExceptionHandler(BindException.class) public R handleBindException(BindException e) { + log.error("参数校验错误", e); String errMsg = e.getBindingResult().getAllErrors().stream() .map(DefaultMessageSourceResolvable::getDefaultMessage) .collect(Collectors.joining("\n")); - return R.fail(errMsg); + return R.fail(StringUtils.isEmpty(errMsg) ? e.getMessage() : errMsg); } } diff --git a/chestnut-common/chestnut-common-staticize/pom.xml b/chestnut-common/chestnut-common-staticize/pom.xml index 6ec15776..3e78555e 100644 --- a/chestnut-common/chestnut-common-staticize/pom.xml +++ b/chestnut-common/chestnut-common-staticize/pom.xml @@ -5,7 +5,7 @@ chestnut-common com.chestnut - 1.5.4 + 1.5.5 4.0.0 diff --git a/chestnut-common/chestnut-common-staticize/src/main/java/com/chestnut/common/staticize/config/FreeMarkerConfig.java b/chestnut-common/chestnut-common-staticize/src/main/java/com/chestnut/common/staticize/config/FreeMarkerConfig.java index a6f10e24..5ca32314 100644 --- a/chestnut-common/chestnut-common-staticize/src/main/java/com/chestnut/common/staticize/config/FreeMarkerConfig.java +++ b/chestnut-common/chestnut-common-staticize/src/main/java/com/chestnut/common/staticize/config/FreeMarkerConfig.java @@ -63,7 +63,11 @@ public class FreeMarkerConfig { MultiTemplateLoader multiTemplateLoader = new MultiTemplateLoader( templateLoaders.toArray(TemplateLoader[]::new)); cfg.setTemplateLoader(multiTemplateLoader); - cfg.setTemplateExceptionHandler(TemplateExceptionHandler.HTML_DEBUG_HANDLER); + if (SpringUtils.isProduction()) { + cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); + } else { + cfg.setTemplateExceptionHandler(TemplateExceptionHandler.HTML_DEBUG_HANDLER); + } // 默认模板缓存策略:Most recently use cache. // 缓存分两级,强引用->弱引用,强引用数达到上限则会将使用次数更少的转移到弱引用缓存,强引用不会被JVM释放,弱引用则相反。 // 默认设置:strongSizeLimit = 100,softSizeLimit = 1000 diff --git a/chestnut-common/chestnut-common-storage/pom.xml b/chestnut-common/chestnut-common-storage/pom.xml index 9613844c..033b612d 100644 --- a/chestnut-common/chestnut-common-storage/pom.xml +++ b/chestnut-common/chestnut-common-storage/pom.xml @@ -5,7 +5,7 @@ chestnut-common com.chestnut - 1.5.4 + 1.5.5 4.0.0 diff --git a/chestnut-common/pom.xml b/chestnut-common/pom.xml index cd02c775..be67aa81 100644 --- a/chestnut-common/pom.xml +++ b/chestnut-common/pom.xml @@ -5,7 +5,7 @@ com.chestnut chestnut - 1.5.4 + 1.5.5 4.0.0 diff --git a/chestnut-modules/chestnut-comment/pom.xml b/chestnut-modules/chestnut-comment/pom.xml index 4d08cd3d..43ebfff3 100644 --- a/chestnut-modules/chestnut-comment/pom.xml +++ b/chestnut-modules/chestnut-comment/pom.xml @@ -3,7 +3,7 @@ chestnut-modules com.chestnut - 1.5.4 + 1.5.5 4.0.0 diff --git a/chestnut-modules/chestnut-generator/pom.xml b/chestnut-modules/chestnut-generator/pom.xml index 7b4b1de8..973b10e9 100644 --- a/chestnut-modules/chestnut-generator/pom.xml +++ b/chestnut-modules/chestnut-generator/pom.xml @@ -5,7 +5,7 @@ chestnut-modules com.chestnut - 1.5.4 + 1.5.5 4.0.0 diff --git a/chestnut-modules/chestnut-member/pom.xml b/chestnut-modules/chestnut-member/pom.xml index c6c94d87..d0bb0ff1 100644 --- a/chestnut-modules/chestnut-member/pom.xml +++ b/chestnut-modules/chestnut-member/pom.xml @@ -3,7 +3,7 @@ chestnut-modules com.chestnut - 1.5.4 + 1.5.5 4.0.0 diff --git a/chestnut-modules/chestnut-member/src/main/java/com/chestnut/member/service/impl/MemberServiceImpl.java b/chestnut-modules/chestnut-member/src/main/java/com/chestnut/member/service/impl/MemberServiceImpl.java index 9924fd5f..91659da8 100644 --- a/chestnut-modules/chestnut-member/src/main/java/com/chestnut/member/service/impl/MemberServiceImpl.java +++ b/chestnut-modules/chestnut-member/src/main/java/com/chestnut/member/service/impl/MemberServiceImpl.java @@ -15,29 +15,18 @@ */ package com.chestnut.member.service.impl; -import java.io.File; -import java.io.IOException; -import java.lang.reflect.Field; -import java.time.LocalDateTime; -import java.util.Base64; -import java.util.List; -import java.util.Optional; -import java.util.stream.Stream; - -import com.chestnut.common.utils.*; -import com.chestnut.member.config.MemberConfig; -import com.chestnut.member.core.IMemberStatData; -import com.chestnut.member.domain.*; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.io.FileUtils; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.CommandLineRunner; -import org.springframework.stereotype.Service; - import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.chestnut.common.exception.CommonErrorCode; import com.chestnut.common.security.SecurityUtils; +import com.chestnut.common.utils.Assert; +import com.chestnut.common.utils.CDKeyUtil; +import com.chestnut.common.utils.IdUtils; +import com.chestnut.common.utils.StringUtils; +import com.chestnut.common.utils.image.ImageUtils; +import com.chestnut.member.config.MemberConfig; +import com.chestnut.member.core.IMemberStatData; +import com.chestnut.member.domain.*; import com.chestnut.member.domain.dto.MemberDTO; import com.chestnut.member.exception.MemberErrorCode; import com.chestnut.member.mapper.MemberLevelExpLogMapper; @@ -46,8 +35,20 @@ import com.chestnut.member.mapper.MemberMapper; import com.chestnut.member.mapper.MemberSignInLogMapper; import com.chestnut.member.service.IMemberService; import com.chestnut.system.service.ISecurityConfigService; - import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.FileUtils; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Service; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.time.LocalDateTime; +import java.util.Base64; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; @Slf4j @RequiredArgsConstructor @@ -158,7 +159,7 @@ public class MemberServiceImpl extends ServiceImpl impleme @Override public String uploadAvatarByBase64(Long memberId, String base64Data) throws IOException { - if (!base64Data.startsWith("data:image/")) { + if (!ImageUtils.isBase64Image(base64Data)) { return null; } String base64Str = StringUtils.substringAfter(base64Data, ","); diff --git a/chestnut-modules/chestnut-meta/pom.xml b/chestnut-modules/chestnut-meta/pom.xml index f8640d68..295891db 100644 --- a/chestnut-modules/chestnut-meta/pom.xml +++ b/chestnut-modules/chestnut-meta/pom.xml @@ -6,7 +6,7 @@ chestnut-modules com.chestnut - 1.5.4 + 1.5.5 4.0.0 diff --git a/chestnut-modules/chestnut-monitor/pom.xml b/chestnut-modules/chestnut-monitor/pom.xml index f3618849..3a321093 100644 --- a/chestnut-modules/chestnut-monitor/pom.xml +++ b/chestnut-modules/chestnut-monitor/pom.xml @@ -6,7 +6,7 @@ com.chestnut chestnut-modules - 1.5.4 + 1.5.5 chestnut-monitor diff --git a/chestnut-modules/chestnut-search/pom.xml b/chestnut-modules/chestnut-search/pom.xml index 6a54832e..43ff50c9 100644 --- a/chestnut-modules/chestnut-search/pom.xml +++ b/chestnut-modules/chestnut-search/pom.xml @@ -3,7 +3,7 @@ chestnut-modules com.chestnut - 1.5.4 + 1.5.5 4.0.0 diff --git a/chestnut-modules/chestnut-stat/pom.xml b/chestnut-modules/chestnut-stat/pom.xml index 53d01374..ec75ad7d 100644 --- a/chestnut-modules/chestnut-stat/pom.xml +++ b/chestnut-modules/chestnut-stat/pom.xml @@ -6,7 +6,7 @@ chestnut-modules com.chestnut - 1.5.4 + 1.5.5 4.0.0 diff --git a/chestnut-modules/chestnut-stat/src/main/java/com/chestnut/stat/core/IStatType.java b/chestnut-modules/chestnut-stat/src/main/java/com/chestnut/stat/core/IStatType.java index c35d9f0a..b4cacad3 100644 --- a/chestnut-modules/chestnut-stat/src/main/java/com/chestnut/stat/core/IStatType.java +++ b/chestnut-modules/chestnut-stat/src/main/java/com/chestnut/stat/core/IStatType.java @@ -15,8 +15,6 @@ */ package com.chestnut.stat.core; -import com.chestnut.stat.core.StatMenu; - import java.util.List; /** diff --git a/chestnut-modules/chestnut-stat/src/main/java/com/chestnut/stat/core/RequestEventData.java b/chestnut-modules/chestnut-stat/src/main/java/com/chestnut/stat/core/RequestEventData.java index 3db2ac6d..f0c75279 100644 --- a/chestnut-modules/chestnut-stat/src/main/java/com/chestnut/stat/core/RequestEventData.java +++ b/chestnut-modules/chestnut-stat/src/main/java/com/chestnut/stat/core/RequestEventData.java @@ -101,7 +101,7 @@ public class RequestEventData { this.setIp(ServletUtils.getIpAddr(request)); this.setAddress(IP2RegionUtils.ip2Region(this.getIp())); this.setReferer(ServletUtils.getReferer(request)); - this.setLocale(StringUtils.substringBefore(ServletUtils.getAcceptLanaguage(request), ",")); + this.setLocale(StringUtils.substringBefore(ServletUtils.getAcceptLanguage(request), ",")); this.setUserAgent(ServletUtils.getUserAgent(request)); UserAgent ua = ServletUtils.parseUserAgent(request); diff --git a/chestnut-modules/chestnut-system/pom.xml b/chestnut-modules/chestnut-system/pom.xml index 0f62b84a..42fbd9d8 100644 --- a/chestnut-modules/chestnut-system/pom.xml +++ b/chestnut-modules/chestnut-system/pom.xml @@ -5,7 +5,7 @@ chestnut-modules com.chestnut - 1.5.4 + 1.5.5 4.0.0 diff --git a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/config/SysI18nConfig.java b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/config/SysI18nConfig.java index 3fff949b..0367f9e2 100644 --- a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/config/SysI18nConfig.java +++ b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/config/SysI18nConfig.java @@ -53,12 +53,8 @@ public class SysI18nConfig { @Bean("messageSource") public MessageSource messageSource(MessageSourceProperties properties) { I18nMessageSource messageSource = new I18nMessageSource(this.redisCache); - if (StringUtils.isNotBlank(properties.getBasename())) { - messageSource.setBasename(properties.getBasename()); - } - if (properties.getEncoding() != null) { - messageSource.setEncoding(properties.getEncoding()); - } + messageSource.setBasename(properties.getBasename()); + messageSource.setEncoding(properties.getEncoding()); // messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale()); Duration cacheDuration = properties.getCacheDuration(); if (cacheDuration != null) { diff --git a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/controller/SysConsoleLogController.java b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/controller/SysConsoleLogController.java new file mode 100644 index 00000000..4f662c3e --- /dev/null +++ b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/controller/SysConsoleLogController.java @@ -0,0 +1,60 @@ +/* + * 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.system.controller; + +import com.chestnut.common.domain.R; +import com.chestnut.common.security.anno.Priv; +import com.chestnut.common.security.web.BaseRestController; +import com.chestnut.common.utils.Assert; +import com.chestnut.system.domain.vo.ConsoleLogsVO; +import com.chestnut.system.exception.SysErrorCode; +import com.chestnut.system.logs.CcConsoleAppender; +import com.chestnut.system.permission.SysMenuPriv; +import com.chestnut.system.security.AdminUserType; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * 控制台日志记录 + * + * @author 兮玥 + * @email 190785909@qq.com + */ +@Priv(type = AdminUserType.TYPE, value = SysMenuPriv.MonitorLogsView) +@RequiredArgsConstructor +@RestController +@RequestMapping("/monitor/console") +public class SysConsoleLogController extends BaseRestController { + + @GetMapping + public R list(@RequestParam int sinceIndex) { + CcConsoleAppender instance = CcConsoleAppender.getInstance(); + Assert.notNull(instance, SysErrorCode.MISSING_CONSOLE_APPENDER::exception); + + List logsSince = instance.getLogsSince(sinceIndex); + List logs = logsSince.stream().map(CcConsoleAppender.LogEntry::message).toList(); + ConsoleLogsVO vo = new ConsoleLogsVO( + logsSince.isEmpty() ? sinceIndex : logsSince.get(logsSince.size() - 1).index(), + logs + ); + return R.ok(vo); + } +} diff --git a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/controller/common/CaptchaController.java b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/controller/common/CaptchaController.java index 294e880f..d44dcad1 100644 --- a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/controller/common/CaptchaController.java +++ b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/controller/common/CaptchaController.java @@ -68,8 +68,9 @@ public class CaptchaController { String uuid = IdUtils.simpleUUID(); String verifyKey = SysConstants.CAPTCHA_CODE_KEY + uuid; - String capStr = null, code = null; - BufferedImage image = null; + 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())); @@ -89,14 +90,13 @@ public class CaptchaController { captchaCache.setCache(verifyKey, code, SysConstants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES); // 转换流信息写出 - FastByteArrayOutputStream os = new FastByteArrayOutputStream(); - try { + 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()); } - ImageCaptchaVO vo = ImageCaptchaVO.builder().captchaEnabled(captchaEnabled).uuid(uuid) - .img(Base64.getEncoder().encodeToString(os.toByteArray())).build(); - return R.ok(vo); } } diff --git a/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/rule/ICustomFormRule.java b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/vo/ConsoleLogsVO.java similarity index 75% rename from chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/rule/ICustomFormRule.java rename to chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/vo/ConsoleLogsVO.java index e3a0bbdf..818000f5 100644 --- a/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/rule/ICustomFormRule.java +++ b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/domain/vo/ConsoleLogsVO.java @@ -13,17 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.chestnut.customform.rule; +package com.chestnut.system.domain.vo; -import com.chestnut.customform.domain.CmsCustomForm; +import java.util.List; /** - * 自定义表单校验规则接口 + * ConsoleLogsVO * * @author 兮玥 * @email 190785909@qq.com */ -public interface ICustomFormRule { - - public boolean check(CmsCustomForm form); -} +public record ConsoleLogsVO(long index, List logs) {} diff --git a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/exception/SysErrorCode.java b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/exception/SysErrorCode.java index ad0acf7a..37539063 100644 --- a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/exception/SysErrorCode.java +++ b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/exception/SysErrorCode.java @@ -207,10 +207,15 @@ public enum SysErrorCode implements ErrorCode { /** * 不支持的定时任务触发器类型:{0} */ - SCHEDULED_TASK_UNSUPPORTED_TRIGGER; + SCHEDULED_TASK_UNSUPPORTED_TRIGGER, + + /** + * 未配置日志输出器 + */ + MISSING_CONSOLE_APPENDER; @Override public String value() { - return "{ERRCODE.SYS." + this.name() + "}"; + return "{ERR.SYS." + this.name() + "}"; } } diff --git a/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/fixed/dict/CustomFormRule.java b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/fixed/dict/TrueOrFalse.java similarity index 63% rename from chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/fixed/dict/CustomFormRule.java rename to chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/fixed/dict/TrueOrFalse.java index 63c4fc2f..4151f8fe 100644 --- a/chestnut-cms/chestnut-cms-customform/src/main/java/com/chestnut/customform/fixed/dict/CustomFormRule.java +++ b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/fixed/dict/TrueOrFalse.java @@ -13,8 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.chestnut.customform.fixed.dict; +package com.chestnut.system.fixed.dict; +import com.chestnut.common.utils.ArrayUtils; import com.chestnut.common.utils.SpringUtils; import com.chestnut.system.fixed.FixedDictType; import com.chestnut.system.service.ISysDictTypeService; @@ -25,30 +26,38 @@ import java.util.function.BiConsumer; import java.util.function.Function; /** - * 自定义表单提交限制规则 + * TRUE/FALSE */ -@Component(FixedDictType.BEAN_PREFIX + CustomFormRule.TYPE) -public class CustomFormRule extends FixedDictType { +@Component(FixedDictType.BEAN_PREFIX + TrueOrFalse.TYPE) +public class TrueOrFalse extends FixedDictType { - public static final String TYPE = "CustomFormRule"; + public static final String TYPE = "TrueOrFalse"; - public static final String Unlimited = "0"; // 无限制 - -// public static final String IP = "1"; // IP - -// public static final String BrowserFingerprint = "2"; // 浏览器指纹 + public static final String TRUE = "1"; + public static final String FALSE = "0"; private static final ISysDictTypeService dictTypeService = SpringUtils.getBean(ISysDictTypeService.class); - public CustomFormRule() { + public TrueOrFalse() { super(TYPE, "{DICT." + TYPE + "}"); - super.addDictData("{DICT." + TYPE + "." + Unlimited + "}", Unlimited, 1); -// super.addDictData("{DICT." + TYPE + "." + IP + "}", IP, 2); -// super.addDictData("{DICT." + TYPE + "." + BrowserFingerprint + "}", BrowserFingerprint, 3); + super.addDictData("{DICT." + TYPE + "." + TRUE + "}", TRUE, 1); + super.addDictData("{DICT." + TYPE + "." + FALSE + "}", FALSE, 2); + } + + public static boolean isTrue(String v) { + return TRUE.equals(v); + } + + public static boolean isFalse(String v) { + return !isTrue(v); } public static void decode(List list, Function getter, BiConsumer setter) { dictTypeService.decode(TYPE, list, getter, setter); } + + public static boolean valid(String v) { + return ArrayUtils.contains(v, TRUE, FALSE); + } } diff --git a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/logs/CcConsoleAppender.java b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/logs/CcConsoleAppender.java new file mode 100644 index 00000000..25cc5fdd --- /dev/null +++ b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/logs/CcConsoleAppender.java @@ -0,0 +1,90 @@ +/* + * 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.system.logs; + +import ch.qos.logback.core.AppenderBase; +import ch.qos.logback.core.Layout; +import ch.qos.logback.core.encoder.Encoder; +import ch.qos.logback.core.status.ErrorStatus; +import lombok.Getter; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; + +/** + * CcConsoleAppender + * + * @author 兮玥 + * @email 190785909@qq.com + */ +public class CcConsoleAppender extends AppenderBase { + + private static final int MAX_QUEUE_SIZE = 1000; + private final LogQueue logQueue = new LogQueue<>(MAX_QUEUE_SIZE); + private final AtomicLong indexCounter = new AtomicLong(0); + + @Getter + @Setter + protected Layout layout; + + @Getter + @Setter + protected Encoder encoder; + + @Getter + private static CcConsoleAppender instance; + + @Override + public void start() { + if (this.layout == null && encoder == null) { + this.addStatus(new ErrorStatus("No pattern set for the appender named \"" + this.name + "\".", this)); + } else { + super.start(); + instance = this; + } + } + + @Override + protected void append(E evt) { + long currentIndex = indexCounter.incrementAndGet(); + LogEntry entry = new LogEntry( + currentIndex, + encodeMessage(evt) + ); + logQueue.add(entry); + } + + private String encodeMessage(E logEvent) { + if (this.encoder != null) { + return new String(this.encoder.encode(logEvent)); + } + return this.layout.doLayout(logEvent); + } + + public List getLogsSince(int sinceIndex) { + List list = new ArrayList<>(); + for (LogEntry entry : logQueue) { + if (entry.index() > sinceIndex) { + list.add(entry); + } + } + return list; + } + + public record LogEntry(long index, String message) {} +} diff --git a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/logs/ILogMenu.java b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/logs/ILogMenu.java index e2a21030..ea180c7e 100644 --- a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/logs/ILogMenu.java +++ b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/logs/ILogMenu.java @@ -20,15 +20,15 @@ public interface ILogMenu { /** * 唯一标识 */ - public String getId(); + String getId(); /** * 名称 */ - public String getName(); + String getName(); /** * 路由地址 */ - public String getRouter(); + String getRouter(); } diff --git a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/logs/LogQueue.java b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/logs/LogQueue.java new file mode 100644 index 00000000..aab9362a --- /dev/null +++ b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/logs/LogQueue.java @@ -0,0 +1,155 @@ +/* + * 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.system.logs; + +import lombok.Getter; +import org.jetbrains.annotations.NotNull; + +import java.io.Serial; +import java.io.Serializable; +import java.util.AbstractQueue; +import java.util.Iterator; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * 固定长度FIFO队列,队列满直接移除最早添加的元素 + * + * @author 兮玥 + * @email 190785909@qq.com + */ +public class LogQueue extends AbstractQueue implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + + private final Object[] elements; + + private int head; + + private int tail; + + private int size; + + @Getter + private final int capacity; + + public LogQueue(int capacity) { + if (capacity <= 0) { + throw new IllegalArgumentException("Capacity must great than zero."); + } + this.capacity = capacity; + this.elements = new Object[this.capacity]; + this.head = 0; + this.tail = 0; + this.size = 0; + } + + @NotNull + @Override + public Iterator iterator() { + lock.readLock().lock(); + try { + Object[] snapshot = new Object[size]; + int current = head; + for (int i = 0; i < size; i++) { + snapshot[i] = elements[current]; + current = (current + 1) % capacity; + } + return new Iterator<>() { + private int index = 0; + + @Override + public boolean hasNext() { + return index < snapshot.length; + } + + @Override + public E next() { + //noinspection unchecked + return (E) snapshot[index++]; + } + }; + + } finally { + lock.readLock().unlock(); + } + } + + @Override + public int size() { + lock.readLock().lock(); + try { + return size; + } finally { + lock.readLock().unlock(); + } + } + + @Override + public boolean offer(E e) { + if (e == null) { + throw new NullPointerException("The queue entry cannot be null."); + } + lock.writeLock().lock(); + try { + if (size == capacity) { + head = (head + 1) % capacity; + size--; + } + elements[tail] = e; + tail = (tail + 1) % capacity; + size++; + return true; + } finally { + lock.writeLock().unlock(); + } + } + + @Override + public E poll() { + lock.writeLock().lock(); + try { + if (size == 0) { + return null; + } + //noinspection unchecked + E element = (E) elements[head]; + elements[head] = null; + head = (head + 1) % capacity; + size--; + return element; + } finally { + lock.writeLock().unlock(); + } + } + + @Override + public E peek() { + lock.readLock().lock(); + try { + if (size == 0) { + return null; + } + //noinspection unchecked + return (E) elements[head]; + } finally { + lock.readLock().unlock(); + } + } +} diff --git a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/logs/impl/ConsoleLogMenu.java b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/logs/impl/ConsoleLogMenu.java new file mode 100644 index 00000000..cf7d9b11 --- /dev/null +++ b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/logs/impl/ConsoleLogMenu.java @@ -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.system.logs.impl; + +import com.chestnut.common.i18n.I18nUtils; +import com.chestnut.system.logs.ILogMenu; + +//@Component +public class ConsoleLogMenu implements ILogMenu { + + @Override + public String getId() { + return "Console"; + } + + @Override + public String getName() { + return I18nUtils.get("{LOG.MENU." + getId() + "}"); + } + + @Override + public String getRouter() { + return "/monitor/logs/console"; + } +} diff --git a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/schedule/ScheduledTask.java b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/schedule/ScheduledTask.java index 9369d7ec..7b4a7a5d 100644 --- a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/schedule/ScheduledTask.java +++ b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/schedule/ScheduledTask.java @@ -19,10 +19,13 @@ import com.chestnut.common.exception.GlobalException; import com.chestnut.common.i18n.I18nUtils; import com.chestnut.common.utils.Assert; import com.chestnut.system.service.ISysScheduledTaskService; -import lombok.extern.slf4j.Slf4j; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.context.i18n.LocaleContextHolder; -import java.time.*; +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.ZoneOffset; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ScheduledFuture; @@ -31,9 +34,10 @@ import java.util.function.Consumer; /** * 异步任务构造器 */ -@Slf4j public abstract class ScheduledTask implements Runnable { + private final Logger logger = LoggerFactory.getLogger("cron"); + /** * 任务ID */ @@ -116,7 +120,7 @@ public abstract class ScheduledTask implements Runnable { this.addErrorMessage(err); } this.setPercent(100); - log.error("Scheduled task run failed.", e); + logger.error("Job failed.", e); } finally { this.onTaskEnded(); } @@ -130,7 +134,7 @@ public abstract class ScheduledTask implements Runnable { * 提交到线程池时执行 */ public void ready() { - log.debug("[{}]ScheduledTask ready: {}[{}]", Thread.currentThread().getName(), this.getType(), this.getTaskId()); + logger.info("Job ready: {}[{}]", this.getType(), this.getTaskId()); this.status = ScheduledTaskStatus.READY; this.readyTime = LocalDateTime.now(); } @@ -139,7 +143,7 @@ public abstract class ScheduledTask implements Runnable { * 任务开始执行 */ void start() { - log.debug("[{}]ScheduledTask start: {}[{}]", Thread.currentThread().getName(), this.getType(), this.getTaskId()); + logger.info("Job start: {}[{}]", this.getType(), this.getTaskId()); this.status = ScheduledTaskStatus.RUNNING; this.startTime = LocalDateTime.now(); } @@ -152,7 +156,7 @@ public abstract class ScheduledTask implements Runnable { return; } long cost = LocalDateTime.now().toInstant(ZoneOffset.UTC).toEpochMilli() - this.getStartTime().toInstant(ZoneOffset.UTC).toEpochMilli(); - log.debug("[{}]ScheduledTask completed: {}[{}], cost: {}ms", Thread.currentThread().getName(), this.getType(), this.getTaskId(), cost); + logger.info("Job completed: {}[{}], cost: {}ms", this.getType(), this.getTaskId(), cost); this.setStatus(ScheduledTaskStatus.SUCCESS); this.setEndTime(LocalDateTime.now()); this.setPercent(100); @@ -162,7 +166,7 @@ public abstract class ScheduledTask implements Runnable { * 执行中断,设置中断标识 */ public void interrupt() { - log.debug("[{}]ScheduledTask interrupted: {}[{}]", Thread.currentThread().getName(), this.getType(), this.getTaskId()); + logger.info("Job interrupted: {}[{}]", this.getType(), this.getTaskId()); if (this.interrupt) { return; } diff --git a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/service/impl/SysI18nDictServiceImpl.java b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/service/impl/SysI18nDictServiceImpl.java index 9ac982fd..768363d2 100644 --- a/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/service/impl/SysI18nDictServiceImpl.java +++ b/chestnut-modules/chestnut-system/src/main/java/com/chestnut/system/service/impl/SysI18nDictServiceImpl.java @@ -15,22 +15,10 @@ */ package com.chestnut.system.service.impl; -import java.io.IOException; -import java.io.InputStream; -import java.util.*; -import java.util.stream.Collectors; - -import com.chestnut.common.i18n.I18nUtils; -import org.apache.commons.io.IOUtils; -import org.springframework.boot.CommandLineRunner; -import org.springframework.core.io.Resource; -import org.springframework.core.io.support.PathMatchingResourcePatternResolver; -import org.springframework.stereotype.Service; -import org.springframework.util.CollectionUtils; - import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.chestnut.common.exception.CommonErrorCode; +import com.chestnut.common.i18n.I18nUtils; import com.chestnut.common.redis.RedisCache; import com.chestnut.common.utils.Assert; import com.chestnut.common.utils.IdUtils; @@ -39,9 +27,20 @@ import com.chestnut.system.config.I18nMessageSource; import com.chestnut.system.domain.SysI18nDict; import com.chestnut.system.mapper.SysI18nDictMapper; import com.chestnut.system.service.ISysI18nDictService; - import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.IOUtils; +import org.springframework.boot.CommandLineRunner; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.util.*; +import java.util.stream.Collectors; @Slf4j @Service @@ -85,6 +84,7 @@ public class SysI18nDictServiceImpl extends ServiceImpl dictIds) { List list = this.listByIds(dictIds); this.removeBatchByIds(list); @@ -103,6 +103,7 @@ public class SysI18nDictServiceImpl extends ServiceImpl dicts) { if (CollectionUtils.isEmpty(dicts)) { return; @@ -142,11 +143,11 @@ public class SysI18nDictServiceImpl extends ServiceImpl> map = this.list().stream().collect(Collectors.groupingBy( SysI18nDict::getLangTag, Collectors.toMap(SysI18nDict::getLangKey, SysI18nDict::getLangValue))); - map.entrySet().forEach(e -> { - e.getValue().entrySet().forEach(kv -> { - redisCache.setCacheMapValue(CACHE_PREFIX + e.getKey(), kv.getKey(), kv.getValue()); - }); - }); + map.forEach((k1, v1) -> + v1.forEach((k2, v2) -> + redisCache.setCacheMapValue(CACHE_PREFIX + k1, k2, v2) + ) + ); } private void loadMessagesFromResources(I18nMessageSource messageSource) throws IOException { @@ -158,7 +159,7 @@ public class SysI18nDictServiceImpl extends ServiceImpl 0) { + if (Objects.nonNull(resource.getFilename()) && resource.getFilename().contains("_")) { langTag = resource.getFilename().substring(resource.getFilename().indexOf("_") + 1, resource.getFilename().lastIndexOf(".")); langTag = StringUtils.replace(langTag, "_", "-"); diff --git a/chestnut-modules/chestnut-system/src/main/resources/i18n/messages.properties b/chestnut-modules/chestnut-system/src/main/resources/i18n/messages.properties index 39867c53..a241bb6d 100644 --- a/chestnut-modules/chestnut-system/src/main/resources/i18n/messages.properties +++ b/chestnut-modules/chestnut-system/src/main/resources/i18n/messages.properties @@ -5,43 +5,44 @@ VALIDATOR.SYSTEM.INVALID_LONG_ID=长整形ID参数值错误:{0} VALIDATOR.SYSTEM.SCRIPT_TEXT=Groovy脚本不能为空 #错误消息 -ERRCODE.SYS.UNAME_PWD_REQUIRED=账号/密码不能为空 -ERRCODE.SYS.USER_NOT_EXISTS=账号不存在 -ERRCODE.SYS.USER_LOCKED=账号被锁定,解锁时间:{0} -ERRCODE.SYS.USER_DISABLED=账号被封禁 -ERRCODE.SYS.PASSWORD_ERROR=密码错误 -ERRCODE.SYS.SECURITY_AUTH_FAIL=安全校验失败:{0} -ERRCODE.SYS.CAPTCHA_EXPIRED=验证码已失效 -ERRCODE.SYS.CAPTCHA_ERR=验证码错误 -ERRCODE.SYS.INSECURE_PASSWORD=密码不符合安全校验规则 -ERRCODE.SYS.ORG_DEL_ROOT=系统默认机构不可删除 -ERRCODE.SYS.ORG_DEL_CHILD=机构删失败:请先删除子机构 -ERRCODE.SYS.ORG_DEL_ROLE=机构删失败:请先移除机构关联角色 -ERRCODE.SYS.ORG_DEL_USER=机构删失败:请先移除机构关联用户 -ERRCODE.SYS.USER_ROLE_ORG_MATCH=用户关联角色与机构不匹配 -ERRCODE.SYS.SUPERADMIN_DELETE=不能删除超级管理员 -ERRCODE.SYS.DISBALE_DEPT_ADD_CHILD=部门停用,不允许新增 -ERRCODE.SYS.DELETE_DICT_DATA_FIRST=请先删除字典数据项 -ERRCODE.SYS.DICT_TYPE_CONFLICT=已存在的字典类型:{0} -ERRCODE.SYS.POST_USER_NOT_EMPTY=请先删除岗位“{0}”关联的用户 -ERRCODE.SYS.ROLE_USER_NOT_EMPTY=请先删除角色“{0}”关联的用户 -ERRCODE.SYS.REGIST_DISABELD=暂未开放账号注册 -ERRCODE.SYS.UPLOAD_FILE_SIZE_LIMIT=上传文件大小不能超过:{0} -ERRCODE.SYS.UPLOAD_FILE_TYPE_LIMIT=文件类型不支持上传 -ERRCODE.SYS.CAPTCHA_CONFIG_ERR=不支持的验证码类型:{0} -ERRCODE.SYS.MENU_DEL_CHILD_FIRST=请先删除子菜单 -ERRCODE.SYS.UNSUPPORTED_USER_PREFERENCE=不支持的用户偏好配置:{0} -ERRCODE.SYS.INVALID_USER_PREFERENCE=用户偏好配置“{0}”数据校验失败 -ERRCODE.SYS.ASYNC_TASK_NOT_FOUND=指定任务不存在:{0} -ERRCODE.SYS.UNSUPPORTED_PERMISSION_TYPE=不支持的权限类型:{0} -ERRCODE.SYS.SCHEDULED_TASK_UPDATE_ERR=启用状态定时任务不能修改 -ERRCODE.SYS.SCHEDULED_TASK_REMOVE_ERR=启用状态定时任务不能删除 -ERRCODE.SYS.SCHEDULED_TASK_EXISTS=定时任务已存在 -ERRCODE.SYS.SCHEDULED_TASK_EXEC_ERR=只能手动执行停用状态任务 -ERRCODE.SYS.SCHEDULED_TASK_UNSUPPORTED_HANDLER=不支持的定时任务类型:{0} -ERRCODE.SYS.SCHEDULED_TASK_TRIGGER_ERR=任务触发器`{0}`配置错误:{1} -ERRCODE.SYS.SCHEDULED_TASK_RUNNING=任务正在运行中 -ERRCODE.SYS.SCHEDULED_TASK_UNSUPPORTED_TRIGGER=不支持的定时任务触发器类型:{0} +ERR.SYS.UNAME_PWD_REQUIRED=账号/密码不能为空 +ERR.SYS.USER_NOT_EXISTS=账号不存在 +ERR.SYS.USER_LOCKED=账号被锁定,解锁时间:{0} +ERR.SYS.USER_DISABLED=账号被封禁 +ERR.SYS.PASSWORD_ERROR=密码错误 +ERR.SYS.SECURITY_AUTH_FAIL=安全校验失败:{0} +ERR.SYS.CAPTCHA_EXPIRED=验证码已失效 +ERR.SYS.CAPTCHA_ERR=验证码错误 +ERR.SYS.INSECURE_PASSWORD=密码不符合安全校验规则 +ERR.SYS.ORG_DEL_ROOT=系统默认机构不可删除 +ERR.SYS.ORG_DEL_CHILD=机构删失败:请先删除子机构 +ERR.SYS.ORG_DEL_ROLE=机构删失败:请先移除机构关联角色 +ERR.SYS.ORG_DEL_USER=机构删失败:请先移除机构关联用户 +ERR.SYS.USER_ROLE_ORG_MATCH=用户关联角色与机构不匹配 +ERR.SYS.SUPERADMIN_DELETE=不能删除超级管理员 +ERR.SYS.DISBALE_DEPT_ADD_CHILD=部门停用,不允许新增 +ERR.SYS.DELETE_DICT_DATA_FIRST=请先删除字典数据项 +ERR.SYS.DICT_TYPE_CONFLICT=已存在的字典类型:{0} +ERR.SYS.POST_USER_NOT_EMPTY=请先删除岗位“{0}”关联的用户 +ERR.SYS.ROLE_USER_NOT_EMPTY=请先删除角色“{0}”关联的用户 +ERR.SYS.REGIST_DISABELD=暂未开放账号注册 +ERR.SYS.UPLOAD_FILE_SIZE_LIMIT=上传文件大小不能超过:{0} +ERR.SYS.UPLOAD_FILE_TYPE_LIMIT=文件类型不支持上传 +ERR.SYS.CAPTCHA_CONFIG_ERR=不支持的验证码类型:{0} +ERR.SYS.MENU_DEL_CHILD_FIRST=请先删除子菜单 +ERR.SYS.UNSUPPORTED_USER_PREFERENCE=不支持的用户偏好配置:{0} +ERR.SYS.INVALID_USER_PREFERENCE=用户偏好配置“{0}”数据校验失败 +ERR.SYS.ASYNC_TASK_NOT_FOUND=指定任务不存在:{0} +ERR.SYS.UNSUPPORTED_PERMISSION_TYPE=不支持的权限类型:{0} +ERR.SYS.SCHEDULED_TASK_UPDATE_ERR=启用状态定时任务不能修改 +ERR.SYS.SCHEDULED_TASK_REMOVE_ERR=启用状态定时任务不能删除 +ERR.SYS.SCHEDULED_TASK_EXISTS=定时任务已存在 +ERR.SYS.SCHEDULED_TASK_EXEC_ERR=只能手动执行停用状态任务 +ERR.SYS.SCHEDULED_TASK_UNSUPPORTED_HANDLER=不支持的定时任务类型:{0} +ERR.SYS.SCHEDULED_TASK_TRIGGER_ERR=任务触发器`{0}`配置错误:{1} +ERR.SYS.SCHEDULED_TASK_RUNNING=任务正在运行中 +ERR.SYS.SCHEDULED_TASK_UNSUPPORTED_TRIGGER=不支持的定时任务触发器类型:{0} +ERR.SYS.MISSING_CONSOLE_APPENDER=未配置日志输出器 # 缓存监控 MONITORED.CACHE.REPEAT_SUBMIT=防重复提交 @@ -57,6 +58,9 @@ MONITORED.CACHE.POST=岗位信息 DICT.YesOrNo=是/否 DICT.YesOrNo.Y=是 DICT.YesOrNo.N=否 +DICT.TrueOrFalse=True/False +DICT.YesOrNo.1=是 +DICT.YesOrNo.0=否 DICT.SuccessOrFail=成功/失败 DICT.SuccessOrFail.0=成功 DICT.SuccessOrFail.1=失败 diff --git a/chestnut-modules/chestnut-system/src/main/resources/i18n/messages_en.properties b/chestnut-modules/chestnut-system/src/main/resources/i18n/messages_en.properties index 91f56df5..a95bb3d4 100644 --- a/chestnut-modules/chestnut-system/src/main/resources/i18n/messages_en.properties +++ b/chestnut-modules/chestnut-system/src/main/resources/i18n/messages_en.properties @@ -4,44 +4,45 @@ VALIDATOR.SYSTEM.INVALID_LONG_ID=Invalid long id value: {0} VALIDATOR.SYSTEM.SCRIPT_TEXT=Groovy script cannot be empty. #错误消息 -ERRCODE.SYS.UNAME_PWD_REQUIRED=Account/Password cannot be empty. -ERRCODE.SYS.USER_NOT_EXISTS=Account not exists. -ERRCODE.SYS.USER_LOCKED=User is locked, lock expire at: {0} -ERRCODE.SYS.USER_DISABLED=Account is disabled. -ERRCODE.SYS.PASSWORD_ERROR=Password error. -ERRCODE.SYS.SECURITY_AUTH_FAIL=Security verification fail: {0} -ERRCODE.SYS.CAPTCHA_EXPIRED=Captcha expired. -ERRCODE.SYS.CAPTCHA_ERR=Captcha error. -ERRCODE.SYS.INSECURE_PASSWORD=Insecure password. -ERRCODE.SYS.ORG_DEL_ROOT=The root department cannot be deleted. -ERRCODE.SYS.ORG_DEL_CHILD=Please delete children department first. -ERRCODE.SYS.ORG_DEL_ROLE=Please remove the associated role first. -ERRCODE.SYS.ORG_DEL_USER=Please remove the associated user first. -ERRCODE.SYS.USER_ROLE_ORG_MATCH=The user's associated role does not match the department. -ERRCODE.SYS.SUPERADMIN_DELETE=Super administrator cannot be deleted. -ERRCODE.SYS.STORAGE_CONFIG_UNEXISTS=Please configure the storage type first. -ERRCODE.SYS.DISBALE_DEPT_ADD_CHILD=Add department failed because the parent is disabled. -ERRCODE.SYS.DELETE_DICT_DATA_FIRST=Please delete dict items first. -ERRCODE.SYS.DICT_TYPE_CONFLICT=Conflict dict type: {0} -ERRCODE.SYS.POST_USER_NOT_EMPTY=Please remove the associated user first. -ERRCODE.SYS.ROLE_USER_NOT_EMPTY=Please remove the associated user first. -ERRCODE.SYS.REGIST_DISABELD=Account registration has not been opened yet. -ERRCODE.SYS.UPLOAD_FILE_SIZE_LIMIT=Uploaded file size cannot exceed {0}. -ERRCODE.SYS.UPLOAD_FILE_TYPE_LIMIT=Upload file type not supported. -ERRCODE.SYS.CAPTCHA_CONFIG_ERR=Unsupported captcha type: {0}. -ERRCODE.SYS.MENU_DEL_CHILD_FIRST=Please delete children first. -ERRCODE.SYS.UNSUPPORTED_USER_PREFERENCE=Unsupported user preference: {0}. -ERRCODE.SYS.INVALID_USER_PREFERENCE=The user preference "{0}" check failed. -ERRCODE.SYS.ASYNC_TASK_NOT_FOUND=The task "{0}" not exists. -ERRCODE.SYS.UNSUPPORTED_PERMISSION_TYPE=Unsupported permission type: {0}. -ERRCODE.SYS.SCHEDULED_TASK_UPDATE_ERR=Enable task cannot edit. -ERRCODE.SYS.SCHEDULED_TASK_REMOVE_ERR=Enable task cannot delete. -ERRCODE.SYS.SCHEDULED_TASK_EXISTS=The task exists. -ERRCODE.SYS.SCHEDULED_TASK_EXEC_ERR=Enable task cannot execute manually. -ERRCODE.SYS.SCHEDULED_TASK_UNSUPPORTED_HANDLER=Unsupported task handler:{0} -ERRCODE.SYS.SCHEDULED_TASK_TRIGGER_ERR=The task trigger `{0}` is invalid: {1} -ERRCODE.SYS.SCHEDULED_TASK_RUNNING=The task is running. -ERRCODE.SYS.SCHEDULED_TASK_UNSUPPORTED_TRIGGER=Unsupported task trigger: {0} +ERR.SYS.UNAME_PWD_REQUIRED=Account/Password cannot be empty. +ERR.SYS.USER_NOT_EXISTS=Account not exists. +ERR.SYS.USER_LOCKED=User is locked, lock expire at: {0} +ERR.SYS.USER_DISABLED=Account is disabled. +ERR.SYS.PASSWORD_ERROR=Password error. +ERR.SYS.SECURITY_AUTH_FAIL=Security verification fail: {0} +ERR.SYS.CAPTCHA_EXPIRED=Captcha expired. +ERR.SYS.CAPTCHA_ERR=Captcha error. +ERR.SYS.INSECURE_PASSWORD=Insecure password. +ERR.SYS.ORG_DEL_ROOT=The root department cannot be deleted. +ERR.SYS.ORG_DEL_CHILD=Please delete children department first. +ERR.SYS.ORG_DEL_ROLE=Please remove the associated role first. +ERR.SYS.ORG_DEL_USER=Please remove the associated user first. +ERR.SYS.USER_ROLE_ORG_MATCH=The user's associated role does not match the department. +ERR.SYS.SUPERADMIN_DELETE=Super administrator cannot be deleted. +ERR.SYS.STORAGE_CONFIG_UNEXISTS=Please configure the storage type first. +ERR.SYS.DISBALE_DEPT_ADD_CHILD=Add department failed because the parent is disabled. +ERR.SYS.DELETE_DICT_DATA_FIRST=Please delete dict items first. +ERR.SYS.DICT_TYPE_CONFLICT=Conflict dict type: {0} +ERR.SYS.POST_USER_NOT_EMPTY=Please remove the associated user first. +ERR.SYS.ROLE_USER_NOT_EMPTY=Please remove the associated user first. +ERR.SYS.REGIST_DISABELD=Account registration has not been opened yet. +ERR.SYS.UPLOAD_FILE_SIZE_LIMIT=Uploaded file size cannot exceed {0}. +ERR.SYS.UPLOAD_FILE_TYPE_LIMIT=Upload file type not supported. +ERR.SYS.CAPTCHA_CONFIG_ERR=Unsupported captcha type: {0}. +ERR.SYS.MENU_DEL_CHILD_FIRST=Please delete children first. +ERR.SYS.UNSUPPORTED_USER_PREFERENCE=Unsupported user preference: {0}. +ERR.SYS.INVALID_USER_PREFERENCE=The user preference "{0}" check failed. +ERR.SYS.ASYNC_TASK_NOT_FOUND=The task "{0}" not exists. +ERR.SYS.UNSUPPORTED_PERMISSION_TYPE=Unsupported permission type: {0}. +ERR.SYS.SCHEDULED_TASK_UPDATE_ERR=Enable task cannot edit. +ERR.SYS.SCHEDULED_TASK_REMOVE_ERR=Enable task cannot delete. +ERR.SYS.SCHEDULED_TASK_EXISTS=The task exists. +ERR.SYS.SCHEDULED_TASK_EXEC_ERR=Enable task cannot execute manually. +ERR.SYS.SCHEDULED_TASK_UNSUPPORTED_HANDLER=Unsupported task handler:{0} +ERR.SYS.SCHEDULED_TASK_TRIGGER_ERR=The task trigger `{0}` is invalid: {1} +ERR.SYS.SCHEDULED_TASK_RUNNING=The task is running. +ERR.SYS.SCHEDULED_TASK_UNSUPPORTED_TRIGGER=Unsupported task trigger: {0} +ERR.SYS.MISSING_CONSOLE_APPENDER=Missing log appender. # 缓存监控 MONITORED.CACHE.REPEAT_SUBMIT=RepeatSubmit @@ -57,6 +58,9 @@ MONITORED.CACHE.POST=Post DICT.YesOrNo=Yes/No DICT.YesOrNo.Y=Yes DICT.YesOrNo.N=No +DICT.TrueOrFalse=True/False +DICT.YesOrNo.1=True +DICT.YesOrNo.0=False DICT.SuccessOrFail=Success/Fail DICT.SuccessOrFail.0=Success DICT.SuccessOrFail.1=Fail diff --git a/chestnut-modules/chestnut-system/src/main/resources/i18n/messages_zh_TW.properties b/chestnut-modules/chestnut-system/src/main/resources/i18n/messages_zh_TW.properties index d64ec2cb..3afb9922 100644 --- a/chestnut-modules/chestnut-system/src/main/resources/i18n/messages_zh_TW.properties +++ b/chestnut-modules/chestnut-system/src/main/resources/i18n/messages_zh_TW.properties @@ -5,43 +5,44 @@ VALIDATOR.SYSTEM.INVALID_LONG_ID=長整形ID參數值錯誤:{0} VALIDATOR.SYSTEM.SCRIPT_TEXT=Groovy腳本不能為空 #錯誤消息 -ERRCODE.SYS.UNAME_PWD_REQUIRED=賬號/密碼不能為空 -ERRCODE.SYS.USER_NOT_EXISTS=賬號不存在 -ERRCODE.SYS.USER_LOCKED=賬號被鎖定,解鎖時間:{0} -ERRCODE.SYS.USER_DISABLED=賬號被封禁 -ERRCODE.SYS.PASSWORD_ERROR=密碼錯誤 -ERRCODE.SYS.SECURITY_AUTH_FAIL=安全校驗失敗:{0} -ERRCODE.SYS.CAPTCHA_EXPIRED=驗證碼已失效 -ERRCODE.SYS.CAPTCHA_ERR=驗證碼錯誤 -ERRCODE.SYS.INSECURE_PASSWORD=密碼不符合安全校驗規則 -ERRCODE.SYS.ORG_DEL_ROOT=系統預設機構不可刪除 -ERRCODE.SYS.ORG_DEL_CHILD=機構刪失敗:請先刪除子機構 -ERRCODE.SYS.ORG_DEL_ROLE=機構刪失敗:請先移除機構關聯角色 -ERRCODE.SYS.ORG_DEL_USER=機構刪失敗:請先移除機構關聯用戶 -ERRCODE.SYS.USER_ROLE_ORG_MATCH=用戶關聯角色與機構不匹配 -ERRCODE.SYS.SUPERADMIN_DELETE=不能刪除超級管理員 -ERRCODE.SYS.DISBALE_DEPT_ADD_CHILD=部門停用,不允許新增 -ERRCODE.SYS.DELETE_DICT_DATA_FIRST=請先刪除字典數據項 -ERRCODE.SYS.DICT_TYPE_CONFLICT=已存在的字典類型:{0} -ERRCODE.SYS.POST_USER_NOT_EMPTY=請先刪除崗位“{0}”關聯的用戶 -ERRCODE.SYS.ROLE_USER_NOT_EMPTY=請先刪除角色“{0}”關聯的用戶 -ERRCODE.SYS.REGIST_DISABELD=暫未開放賬號註冊 -ERRCODE.SYS.UPLOAD_FILE_SIZE_LIMIT=上傳檔案大小不能超過:{0} -ERRCODE.SYS.UPLOAD_FILE_TYPE_LIMIT=檔案類型不支援上傳 -ERRCODE.SYS.CAPTCHA_CONFIG_ERR=不支援的驗證碼類型:{0} -ERRCODE.SYS.MENU_DEL_CHILD_FIRST=請先刪除子菜單 -ERRCODE.SYS.UNSUPPORTED_USER_PREFERENCE=不支援的用戶偏好配置:{0} -ERRCODE.SYS.INVALID_USER_PREFERENCE=用戶偏好配置“{0}”數據校驗失敗 -ERRCODE.SYS.ASYNC_TASK_NOT_FOUND=指定任務不存在:{0} -ERRCODE.SYS.UNSUPPORTED_PERMISSION_TYPE=不支援的許可權類型:{0} -ERRCODE.SYS.SCHEDULED_TASK_UPDATE_ERR=啟用狀態定時任務不能修改 -ERRCODE.SYS.SCHEDULED_TASK_REMOVE_ERR=啟用狀態定時任務不能刪除 -ERRCODE.SYS.SCHEDULED_TASK_EXISTS=定時任務已存在 -ERRCODE.SYS.SCHEDULED_TASK_EXEC_ERR=只能手動執行停用狀態任務 -ERRCODE.SYS.SCHEDULED_TASK_UNSUPPORTED_HANDLER=不支援的定時任務類型:{0} -ERRCODE.SYS.SCHEDULED_TASK_TRIGGER_ERR=任務觸發器`{0}`配置錯誤:{1} -ERRCODE.SYS.SCHEDULED_TASK_RUNNING=任務正在運行中 -ERRCODE.SYS.SCHEDULED_TASK_UNSUPPORTED_TRIGGER=不支援的定時任務觸發器類型:{0} +ERR.SYS.UNAME_PWD_REQUIRED=賬號/密碼不能為空 +ERR.SYS.USER_NOT_EXISTS=賬號不存在 +ERR.SYS.USER_LOCKED=賬號被鎖定,解鎖時間:{0} +ERR.SYS.USER_DISABLED=賬號被封禁 +ERR.SYS.PASSWORD_ERROR=密碼錯誤 +ERR.SYS.SECURITY_AUTH_FAIL=安全校驗失敗:{0} +ERR.SYS.CAPTCHA_EXPIRED=驗證碼已失效 +ERR.SYS.CAPTCHA_ERR=驗證碼錯誤 +ERR.SYS.INSECURE_PASSWORD=密碼不符合安全校驗規則 +ERR.SYS.ORG_DEL_ROOT=系統預設機構不可刪除 +ERR.SYS.ORG_DEL_CHILD=機構刪失敗:請先刪除子機構 +ERR.SYS.ORG_DEL_ROLE=機構刪失敗:請先移除機構關聯角色 +ERR.SYS.ORG_DEL_USER=機構刪失敗:請先移除機構關聯用戶 +ERR.SYS.USER_ROLE_ORG_MATCH=用戶關聯角色與機構不匹配 +ERR.SYS.SUPERADMIN_DELETE=不能刪除超級管理員 +ERR.SYS.DISBALE_DEPT_ADD_CHILD=部門停用,不允許新增 +ERR.SYS.DELETE_DICT_DATA_FIRST=請先刪除字典數據項 +ERR.SYS.DICT_TYPE_CONFLICT=已存在的字典類型:{0} +ERR.SYS.POST_USER_NOT_EMPTY=請先刪除崗位“{0}”關聯的用戶 +ERR.SYS.ROLE_USER_NOT_EMPTY=請先刪除角色“{0}”關聯的用戶 +ERR.SYS.REGIST_DISABELD=暫未開放賬號註冊 +ERR.SYS.UPLOAD_FILE_SIZE_LIMIT=上傳檔案大小不能超過:{0} +ERR.SYS.UPLOAD_FILE_TYPE_LIMIT=檔案類型不支援上傳 +ERR.SYS.CAPTCHA_CONFIG_ERR=不支援的驗證碼類型:{0} +ERR.SYS.MENU_DEL_CHILD_FIRST=請先刪除子菜單 +ERR.SYS.UNSUPPORTED_USER_PREFERENCE=不支援的用戶偏好配置:{0} +ERR.SYS.INVALID_USER_PREFERENCE=用戶偏好配置“{0}”數據校驗失敗 +ERR.SYS.ASYNC_TASK_NOT_FOUND=指定任務不存在:{0} +ERR.SYS.UNSUPPORTED_PERMISSION_TYPE=不支援的許可權類型:{0} +ERR.SYS.SCHEDULED_TASK_UPDATE_ERR=啟用狀態定時任務不能修改 +ERR.SYS.SCHEDULED_TASK_REMOVE_ERR=啟用狀態定時任務不能刪除 +ERR.SYS.SCHEDULED_TASK_EXISTS=定時任務已存在 +ERR.SYS.SCHEDULED_TASK_EXEC_ERR=只能手動執行停用狀態任務 +ERR.SYS.SCHEDULED_TASK_UNSUPPORTED_HANDLER=不支援的定時任務類型:{0} +ERR.SYS.SCHEDULED_TASK_TRIGGER_ERR=任務觸發器`{0}`配置錯誤:{1} +ERR.SYS.SCHEDULED_TASK_RUNNING=任務正在運行中 +ERR.SYS.SCHEDULED_TASK_UNSUPPORTED_TRIGGER=不支援的定時任務觸發器類型:{0} +ERR.SYS.MISSING_CONSOLE_APPENDER=未配置日誌輸出器 # 緩存監控 MONITORED.CACHE.REPEAT_SUBMIT=防重複提交 @@ -57,6 +58,9 @@ MONITORED.CACHE.POST=崗位資訊 DICT.YesOrNo=是/否 DICT.YesOrNo.Y=是 DICT.YesOrNo.N=否 +DICT.TrueOrFalse=True/False +DICT.YesOrNo.1=是 +DICT.YesOrNo.0=否 DICT.SuccessOrFail=成功/失敗 DICT.SuccessOrFail.0=成功 DICT.SuccessOrFail.1=失敗 diff --git a/chestnut-modules/chestnut-vote/pom.xml b/chestnut-modules/chestnut-vote/pom.xml index b9312574..9ae9d78f 100644 --- a/chestnut-modules/chestnut-vote/pom.xml +++ b/chestnut-modules/chestnut-vote/pom.xml @@ -3,7 +3,7 @@ chestnut-modules com.chestnut - 1.5.4 + 1.5.5 4.0.0 diff --git a/chestnut-modules/chestnut-word/pom.xml b/chestnut-modules/chestnut-word/pom.xml index d0caec05..259a26ee 100644 --- a/chestnut-modules/chestnut-word/pom.xml +++ b/chestnut-modules/chestnut-word/pom.xml @@ -6,7 +6,7 @@ chestnut-modules com.chestnut - 1.5.4 + 1.5.5 4.0.0 diff --git a/chestnut-modules/pom.xml b/chestnut-modules/pom.xml index ef218118..92c6b93c 100644 --- a/chestnut-modules/pom.xml +++ b/chestnut-modules/pom.xml @@ -5,7 +5,7 @@ com.chestnut chestnut - 1.5.4 + 1.5.5 4.0.0 diff --git a/chestnut-ui/package.json b/chestnut-ui/package.json index 79148d6b..b453b433 100644 --- a/chestnut-ui/package.json +++ b/chestnut-ui/package.json @@ -1,6 +1,6 @@ { "name": "ChestnutCMS", - "version": "1.5.4", + "version": "1.5.5", "description": "ChestnutCMS[栗子内容管理系统]", "author": "兮玥 - 190785909@qq.com", "license": "Apache-2.0", diff --git a/chestnut-ui/src/api/customform/customform.js b/chestnut-ui/src/api/customform/customform.js index a688349d..51007f3a 100644 --- a/chestnut-ui/src/api/customform/customform.js +++ b/chestnut-ui/src/api/customform/customform.js @@ -1,5 +1,12 @@ import request from '@/utils/request' +export function getLimitRules() { + return request({ + url: '/cms/customform/limit_rules', + method: 'get', + }) +} + export function listCustomForms(params) { return request({ url: '/cms/customform', diff --git a/chestnut-ui/src/api/monitor/console.js b/chestnut-ui/src/api/monitor/console.js new file mode 100644 index 00000000..c6a630c1 --- /dev/null +++ b/chestnut-ui/src/api/monitor/console.js @@ -0,0 +1,8 @@ +import request from '@/utils/request' + +export function getConsoleLogs(sinceIndex) { + return request({ + url: '/monitor/console?sinceIndex=' + sinceIndex, + method: 'get' + }) +} \ No newline at end of file diff --git a/chestnut-ui/src/i18n/lang/en.js b/chestnut-ui/src/i18n/lang/en.js index 5bf89b17..76e9d59e 100644 --- a/chestnut-ui/src/i18n/lang/en.js +++ b/chestnut-ui/src/i18n/lang/en.js @@ -1,1968 +1,2032 @@ -export default { - APP: { - TITLE: 'ChestnutCMS' - }, - Common: { - Search: 'Search', - Reset: 'Reset', - More: 'More', - None: 'None', - OK: 'OK', - Confirm: 'Confirm', - Cancel: 'Cancel', - Open: 'Open', - Close: 'Close', - Submit: 'Submit', - GoBack: 'Go Back', - Success: 'Success', - Fail: 'Fail', - Normal: 'Normal', - OpSuccess: 'Success', - OpFail: 'Failed', - AddSuccess: 'Add Success', - SaveSuccess: "Save Success", - EditSuccess: 'Edit Success', - DeleteSuccess: 'Delete Success', - CopySuccess: 'Copy Success', - Yes: 'Yes', - No: 'No', - Enable: "Enable", - Disable: "Disable", - BeginDate: 'Begin Date', - EndDate: 'End Date', - BeginTime: 'Begin Time', - EndTime: 'End Time', - Add: 'Add', - BatchAdd: 'Batch Add', - Edit: 'Edit', - Save: 'Save', - Delete: 'Delete', - Remove: "Remove", - Import: 'Import', - Export: 'Export', - Refresh: 'Refresh', - RefreshCache: 'Refresh Cache', - Clean: "Clean", - View: "View", - Show: "Show", - Hide: "Hide", - Move: "Move", - Copy: "Copy", - Sort: "Sort", - Details: "Details", - Remark: 'Remark', - CreateTime: 'Create Time', - CreateBy: 'Create User', - UpdateTime: 'Update Time', - UpdateBy: 'Update User', - Operation: 'Operation', - Select: 'Select', - Upload: 'Upload', - RowNo: 'No.', - ExpandOrCollapse: 'Expand / Collapse', - Expand: 'Expand', - Collapse: 'Collapse', - CheckAll: 'Check All', - CheckInverse: 'Inverse', - TreeLinkage: 'Linkage', - ConfirmDelete: 'Are you sure to delete?', - LastWeek: 'Last week', - LastMonth: 'Last month', - LastThreeMonth: 'Last 3 month', - SessionExpired: 'Session has expired, stay on the current page or re-login.', - Tips: "Tips", - SystemTip: 'System Tip', - Relogin: 'Re-login', - InvalidSession: 'Invalid session or expired, please re-login.', - RepeatSubmit: 'The request is being processed, please do not submit repeatedly.', - ServerConnectFailed: 'Server connection failed.', - ServerConnectTimeout: 'Server connection timeout.', - ServerApiError: 'Server api error: {0}', - Loading: 'Loading...', - DownloadFailed: 'Download failed, please contact the administrator.', - InvalidFileSuffix: 'Unsupport file format, only "{0}" is supported.', - SelectFirst: 'Please select first.', - Width: "Width", - Height: "Height", - Downloading: "Downloading...", - Downloaded: "Download completed.", - DownloadTimeout: "Download timeout.", - Unit: { - Year: "yr", - Month: "mo", - Day: "d", - Hour: "h", - Minute: "min", - Second: "s" - }, - RuleTips: { - NotEmpty: "Cannot be empty.", - Email: "Invalid email.", - Url: "Invalid url, must starts with http(s)://.", - Code: "Only 'A-Za-z', '0-9' and '_' can be used.", - PhoneNumber: "Invalid phone number." - } - }, - Error: { - Err401: "401 Error!", - NoPermission: "Permission denied!", - NoPermissionTip: "Sorry, you do not have access rights. Please do not engage in illegal operations! You can return to the main page.", - GoHome: "Back Home", - Err404: "404 Error!", - Err404Tip: "Sorry, the page you are looking for does not exist. Try checking for errors in the URL, then press the refresh button on the browser or try to find other content in our application.", - PageNotFound: "Page not found!", - Unknown: 'Unknown system error, please notify then administrator!' - }, - Router: { - Home: 'Home', - AccountCenter: 'Account Center', - UserPreference: 'User Preference', - LayoutSetting: 'Layout Setting', - Logout: 'Logout' - }, - Login: { - Login: 'Login', - Logining: 'Logining...', - Register: 'Register', - Account: 'Account', - Password: 'Password', - RememberMe: 'Remember me', - Captcha: 'Captcha', - AccountRuleTip: "Account cannot be empty!", - PasswordRuleTip: "Password canot be empty!", - CaptchaRuleTip: "Captcha cannot be empty!", - Language: 'Language', - }, - AccountCenter: { - PersonInfo: 'Personal Information', - Account: 'Account', - PhoneNumber: 'Phone Number', - Email: 'Email', - Dept: 'Deptment', - Roles: 'Roles', - CreateTime: 'Create Time', - Basic: 'Basic', - ResetPassword: 'Reset Password', - NickName: 'Nick Name', - Gender: 'Gender', - GenderMale: 'Male', - GenderFemale: 'Female', - NickNameEmptyTip: 'Nick name cannot be empty.', - EmailEmptyTip: 'Email cannot be empty.', - EmailRuleTip: 'Invalid email.', - PhoneNumEmptyTip: 'Phone number cannot be empty.', - PhoneNumRuleTip: 'Invalid phone number.', - OldPwd: 'Old Password', - NewPwd: 'New Password', - ConfirmPwd: 'Confirm Password', - OldPwdPlaceHolder: 'Please input old password', - NewPwdPlaceHolder: 'Please input new password', - ConfirmPwdPlaceHolder: 'Please confirm new password', - OldPwdEmptyTip: 'Old password cannot be empty.', - NewPwdEmptyTip: 'New password cannot be empty.', - ConfirmPwdEmptyTip: 'Confirm new password cannot be empty.', - NewPwdLenTip: 'Should be {0} - {1} characters in length.', - NewPwdSensitiveTip: 'Cannot contain user sensitive information.', - NewPwdTip: 'Insecure new password .', - NewPwdRuleLETTER_NUMBERTip: 'Must contain letters and numbers.', - NewPwdRuleUPPER_LOW_LETTER_NUMBERTip: 'Must contain uppercase and lowercase letters and numbers.', - NewPwdRuleLETTER_NUMBER_SPECIALTip: 'Must contain letters, numbers, special characters.', - NewPwdRuleUPPER_LOW_LETTER_NUMBER_SPECIALTip: 'Must contain uppercase and lowercase letters, numbers and special characters.', - ConfirmPwdErrTip: 'The two passwords were inconsistent.', - }, - Component: { - Progress: { - TaskIsRunning: "Task is running...", - Completed: " completed", - }, - CommonImageViewer: { - UploadSizeErr: "Uploaded file size cannot exceed {0}!", - Uploading: "Uploading...", - Tips: "Support: jpg/png, Size limit: 2MB" - }, - Crontab: { - Second: "Second", - Minute: "Minute", - Hour: "Hour", - Day: "Day", - Month: "Month", - Week: "Week", - Year: "Year", - TimeExpression: "Time Expression", - CronExpression: "Cron Expression", - Next5RunTime: "Next 5 run time", - Waiting: "Waiting..." - }, - IconSelect: { - Placeholder: "Input icon name" - }, - RightToolbar: { - Show: "Show", - Hide: "Hide", - ShowOrHide: "Show / Hide", - HideSearch: "Hide Search", - ShowSearch: "Show Search", - ShowTableColumn: "Show/Hide Table Columns" - }, - Screenfull: { - UnSupported: "Your browser is not support full-screen mode." - }, - TopNav: { - MoreMenu: "More+" - }, - Layout: { - Settings: { - ThemeStyle: 'Theme Style', - ThemeColor: 'Theme Color', - SystemLayoutConfig: 'Layout Configuration', - EnableTopNav: 'Enable TopNav', - EnableTagsViews: 'Enable Tags-Views', - EnableFixedHeader: 'Fixed Header', - ShowLogo: 'Show Logo', - EnableDynamicTitle: 'Enable Dynamic Title', - }, - Navbar: { - FullScreen: 'Full Screen', - LayoutSize: 'Layout Size', - Language: 'Language', - ConfirmLogoutTip: 'Confirm to logout?', - PublishTask: "Publish Task", - ClearPublishTask: "Clear All Tasks" - }, - TagViews: { - RefreshPage: "Refresh", - CloseCurrent: "Close Current", - CloseOther: "Close Others", - CloseLeft: "Close Lefts", - CloseRight: "Close Rights", - CloseAll: "Close All" - } - } - }, - Home: { - Welcome: "Welcome back", - LastLoginTime: "Last login time", - LastLoginIP: "Last login ip" - }, - System: { - User: { - UserId: "User ID", - UserName: "User Name", - NickName: "Nick Name", - Dept: "Dept", - PhoneNumber: "Phone Num", - Email: "Email", - Password: "Password", - Gender: "Gender", - Status: "Status", - Post: "Posts", - Role: "Roles", - ResetPwd: "Reset Password", - RoleSetting: "Role Setting", - Enable: "enable", - Disable: "disable", - ConfirmChangeStatus: 'Are you sure to {0} "{1}"?', - ConfirmDelete: 'Are you sure to delete user with ids: {0}?', - InputNewPwd: 'Please input new password for user "{0}"', - ChangePwdSucc: 'Reset success, new password is: {0}', - ImportResult: "Import Result", - ImportTip1: "Drag and drop your file here, or click to upload.", - ImportUpdate: "Update existing user?", - DownloadTemplate: "Download Template", - EditAvatar: 'Edit Avatar', - UploadAvatar: 'Click to upload avatar', - SelectUser: "Select User", - IsAllocatedRole: "Allocated", - Dialog: { - Add: "Add User", - Edit: "Edit User", - Import: "Import Uesrs" - }, - Placeholder: { - DeptName: "Input deptment name.", - UserName: "Input user name.", - NickName: "Input nick name", - PhoneNumber: "Input phone number.", - Status: "User Status", - Dept: "Select deptment", - Email: "Input email", - Password: "Input password", - Gender: "Select gender", - Post: "Select posts", - Role: "Select roles" - }, - RuleTips: { - UserName1: "User name cannot be empty.", - UserName2: "User name length must be between 2 and 20", - DeptId: "User department cannot be empty.", - NickName: "Nickname cannot be empty", - Password1: "Password cannot be empty", - Email: "Invalid email address", - PhoneNumber: "Invalid phone number" - } - }, - UserPreference: { - Shortcut: "Shortcut Menu", - StatIndex: "Statistics Default Menu", - IncludeChildContent: "Include Children In Content List", - OpenContentEditorW: "New Window For Content Editor", - ShowContentSubTitle: "Show Content Subtitle", - CatalogTreeExpandMode: "Catalog Tree Expand Mode", - CatalogTreeExpandMode_Normal: "Common", - CatalogTreeExpandMode_Accordion: "Accordion", - }, - UserRole: { - UserInfo: "User Information", - Roles: "User Roles", - AddUser: "Add User", - RemoveUser: "Remove User" - }, - Role: { - RoleId: "Role ID", - RoleName: "Role Name", - RoleKey: "Role Key", - RoleKeyTips: "Role key cannot be empty and only number, character and underline.", - Sort: "Sort", - Status: "Status", - UserSetting: "User Setting", - MenuPerms: "Menu Perms", - PermissionSetting: "Permission Setting", - Enable: "enable", - Disable: "disable", - ConfirmChangeStatus: 'Are you sure to {0} "{1}"?', - DataScope: "Data Permissions", - ConfirmRemoveUser: "Are you sure to remove users?", - Dialog: { - Add: "Add Role", - Edit: "Edit Role", - DataScope: "Grant Data Permissions" - }, - Placeholder: { - RoleName: "Input role name", - RoleKey: "Input role key", - Status: "Role status" - }, - RuleTips: { - RoleName: "Role name cannot be empty.", - RoleKey: "Role key cannot be empty.", - Sort: "Role sort cannot be empty." - } - }, - Permission: { - MenuPriv: "Meun", - SitePriv: "Site", - CatalogPriv: "Catalog", - ContentPriv: "Content", - PageWidgetPriv: "Page Widget" - }, - Menu: { - MenuName: "Menu Name", - MenuType: "Menu Type", - Icon: "Icon", - Status: "Status", - StatusTips: "Router will be invisible and inaccessible when router is disabled.", - Sort: "Sort", - Perms: "Permission Key", - Component: "Component", - ComponentTips: "Component path, eg: `system/user/index`, default directory is `views`.", - ParentMenu: "Parent Menu", - TypeDir: "Directory", - TypeMenu: "Menu", - TypeBtn: "Button", - IsExternalLink: "Is External Link", - IsExternalLinkTips: "Router must start whith `http(s):// for external link.", - RouterLink: "Router", - RouterLinkTips: "Router visit path, eg: `user`, Router must start whith `http(s):// for external link. ", - PermsTips: 'Permission Key is used for controller, eg: @Priv(type = "sys_user", value = "system:role:add")', - RouterParams: "Router Params", - RouterParamsTips: 'Router default parameters, eg: `{"id": 1, "name": "ry"}`', - IsCache: "Is Cache", - IsCacheTips: "Cached with `keep-alive`, need component's name same with path.", - EnableCache: "Cache", - DisableCache: "Non Cache", - Visible: "Is Visible", - VisibleTips: "Router is not show in sidebar but accessible when invisible.", - ConfirmDelete: 'Are you sure to delete menu "{0}"?', - Dialog: { - Add: "Add Menu", - Edit: "Edit Menu" - }, - Placeholder: { - MenuName: "Input menu name", - Status: "Menu Status", - ParentMenu: "Select parent menu", - Icon: "Click to choose icon", - RouterLink: "Input router link", - Component: "Input component path", - Perms: "Input permisison key", - RouterParams: "Input router parameters", - }, - RuleTips: { - MenuName: "Menu cannot be empty.", - Sort: "Menu sort cannot be empty.", - RouterLink: "Router link cannot be empty." - }, - Shortcut: "Shortcut" - }, - Dept: { - DeptName: "Dept Name", - Status: "Status", - ParentDept: "Parent Dept", - Sort: "Sort", - Leader: "Leader", - Phone: "Phone Num.", - Email: "Email", - ConfirmDelete: '是否确认删除名称为"{0}"的数据项?', - Dialog: { - Add: "Add Department", - Edit: "Edit Department" - }, - Placeholder: { - DeptName: "Input department name", - Status: "Department Status", - ParentDept: "Input parent department", - Leader: "Input department leader", - Phone: "Input phone number", - Email: "Input email" - }, - RuleTips: { - DeptName: "Department name cannot be empty.", - ParentDept: "Parent department cannot be empty.", - Sort: "Sort cannot be empty", - Email: "Invalid emial.", - Phone: "Invlaid phone number." - } - }, - Post: { - PostId: "Post ID", - PostCode: "Post Code", - PostName: "Post Name", - Status: "Status", - PostSort: "Sort", - ConfirmDelete: 'Are you sure to delete posts with ids: {0}?', - Dialog: { - Add: "Add Post", - Edit: "Edit Post" - }, - Placeholder: { - PostCode: "Input post code", - PostName: "Input post name", - Status: "Post Status" - }, - RuleTips: { - PostName: "Post name cannot be empty.", - PostCode: "Post code cannot be empty.", - PostSort: "Post sort cannot be empty." - } - }, - Dict: { - DictId: "ID", - DictName: "Dict Name", - DictType: "Dict Type", - DictCode: "Dict Code", - DictLabel: "Dict Label", - DictValue: "Dict Value", - DictSort: "Sort", - SystemFixed: "Fixed", - CssClass: "CSS Class", - ListClass: "List Class", - Status: "Status", - DataList: "Dictionary Datas", - ConfirmDelete: 'Are you sure to delete dict with ids: {0}?', - ConfirmDeleteDatas: 'Are you sure to delete dict datas with codes: {0}?', - Dialog: { - Add: "Add Dict", - Edit: "Edit Dict", - AddData: "Add Dict Data", - EditData: "Edit Dict Data" - }, - Placeholder: { - DictName: "Input dict name", - DictType: "Input dict type", - DictCode: "Input dict data code", - DictLabel: "Input dict data label", - DictValue: "Input dict data value", - CssClass: "Input dict data css class", - Status: "Input status" - }, - RuleTips: { - DictName: "Dict name cannot be empty.", - DictType: "Dict type cannot be empty.", - DictCode: "Dict data code cannot be empty.", - DictLabel: "Dict data label cannot be empty.", - DictValue: "Dict data value cannot be empty.", - DictSort: "Dict sort cannot be empty.", - } - }, - Config: { - ConfigId: "Config ID", - ConfigName: "Config Name", - ConfigKey: "Config Key", - ConfigValue: "Config Value", - ConfigType: "Fixed Config", - ConfirmDelete: 'Are you sure to delete config with ids: {0}?', - Dialog: { - Add: "Add Config", - Edit: "Edit Config" - }, - Placeholder: { - ConfigName: "Input config name", - ConfigKey: "Input config key", - ConfigValue: "Input config value", - ConfigType: "Fixed Config", - }, - RuleTips: { - ConfigName: "Config name cannot be empty.", - ConfigKey: "Config key cannot be empty.", - ConfigValue: "Config value cannot be empty.", - } - }, - Notice: { - NoticeTitle: "Notice Title", - NoticeType: "Notice Type", - NoticeContent: "Content", - Status: "Status", - ConfirmDelete: 'Are you sure to delete noitces with ids: {0}?', - Dialog: { - Add: "Add Notice", - Edit: "Edit Notice" - }, - RuleTips: { - NoticeTitle: "Notice title cannot be empty.", - NoticeType: "Notice type cannot be empty." - } - }, - I18n: { - LangTag: "Language Tag", - LangKey: "Language Key", - LangValue: "Language Value", - EditorTitle: "Edit i18n `{0}`", - Dialog: { - Add: "Add I18n", - Edit: "Edit I18n" - }, - RuleTips: { - LangTag: "Language tag cannot be empty.", - LangKey: "Language key cannot be empty.", - LangValue: "Language value cannot be empty.", - } - }, - IPRule: { - Type: "Type", - ExpireTime: "Expire Time", - CIDR: "CIDR", - Dialog: { - Add: "Add IP Rule", - Edit: "Edit IP Rule", - }, - RuleTips: { - IP: "IP address cannot be empty.", - Type: "Type cannot be empty.", - CIDR: "CIDR cannot be empty." - } - }, - OpLog: { - Title: "Title", - Operator: "Operator", - OpType: "Op Type", - ResponseCode: "Res Code", - OpTime: "Op Time", - Cost: "Op Cost", - LogId: "Log ID", - RequestMethod: "Request Method", - IP: "IP", - Location: "Location", - Details: "Details", - DetailsTitle: "Operation Details", - LoginInfo: "Information", - RequestUrl: "Request URL", - Method: "Method", - RequestParams: "Request Args", - Response: "Reponse Result", - ConfirmDelete: 'Are you sure to delete logs with ids: {0}?', - ConfirmClean: 'Are you sure to clean all logs?' - }, - LoginInfo: { - LogId: "Log ID", - IP: "IP", - UserType: 'User Type', - UserId: "UID", - UserName: "User Name", - Status: "Status", - LoginTime: "Login Time", - Unlock: "Unlock", - Location: "Location", - Browser: "Browser", - OS: "OS", - OpMsg: "Message", - ConfirmUnlock: 'Are you sure to unlock user: {0}?', - UnlockSuccess: 'Unlock user "{0}" success.' - }, - GenCode: { - EditGenConfig: 'Edit Gen Config' - }, - Security: { - PasswordLength: "Password Length", - PasswordRule: "Password Rule", - PasswordExpireSeconds: "Password Expiration Seconds", - PasswordRetryLimit: "Password Retry Day Limit", - Status: "Status", - PasswordConfig: "Password Configuration", - PasswordMinLength: "Pwd Min Length", - PasswordMaxLength: "Pwd Max Length", - PasswordSensitive: "Pwd Sensitive Char", - WeakPasswords: "Weak Password", - WeakPasswordsPlaceholder: "One weak password per line.", - PasswordExpireSecondsTip: "0 means never expires, with a maximum of 100 days.", - ForceModifyPwdAfterAdd: "Modify Pwd For First Login", - ForceModifyPwdAfterAddTip: "Only applicable to adding users in the backend.", - ForceModifyPwdAfterReset: "Modify Pwd For Reset", - ForceModifyPwdAfterResetTip: "Only applicable to reset in the backend.", - LoginConfig: "Login Configuration", - PasswordRetryLimitTip: "0 means unlimited.", - PasswordRetryStrategy: "Pwd Retry Strategy", - PasswordRetryLockSeconds: "Lock User Seconds", - PasswordRetryLockSecondsTip: "Maximum of 100 days.", - AddTitle: "Add Security Configuration", - EditTitle: "Edit Security Configuration", - ForceModifyPwd: "Password needs to be changed after first login or password reset!", - PwdExpired: "Your password has expired. For the security of your account, please change the password immediately!" - }, - WeChat: { - Backend: "Backend", - AddTitle: "Add WeChat Configuration", - EditTitle: "Edit WeChat Configuration", - } - }, - Monitor: { - Job: { - JobId: "Job ID", - JobName: "Job Name", - JobGroup: "Job Group", - Status: "Status", - InvokeTarget: "Invoke Target", - CronExpression: "Cron", - CronGenerator: "Cron Generator", - GenCron: "Generate Cron", - Concurrent: "Concurrent", - Allow: "Allow", - Forbid: "Forbid", - Enable: "Enable", - Disable: "Disable", - MisfirePolicy: "Policy", - DefaultPolicy: "Default", - RunNow: "Run Immediately", - RunOnce: "Run Once", - Manual: "Manual Run", - Details: "Job Details", - BeanExample: "Bean invoke eg: ryTask.ryParams('ry')", - ClassExample: "Class invoke eg: com.test.quartz.task.RyTask.ryParams('ry')", - ParamsTips: "Support Parameter: String, Boolean, Long, Float, Integer", - NextRunTime: "Next Run Time", - ConfirmDelete: 'Are you sure to delete jobs with ids: {0}?', - ConfirmChangeStatus: 'Are you sure to "{0}" job "{1}"?', - ConfirmRunOnce: 'Are you sure to run job "{0}" once immediately?', - Logs: "Job Logs", - ExecTime: "Execute Time", - JobLogId: "Log ID", - JobLogMessage: "Log Message", - LogDetails: "Details", - ExceptionInfo: "Exception Info", - ConfirmClean: 'Are you sure to clean all logs?', - Dialog: { - Add: "Add Job", - Edit: "Edit Job", - Log: "Log Details", - }, - Placeholder: { - InvokeTarget: "Input invoke target string" - }, - RuleTips: { - JobName: "Job name cannot be empty.", - InvokeTaget: "Invoke taget cannot be empty.", - CronExpression: "Cron expression cannot be empty." - } - }, - ScheduledTask: { - Type: "Type", - Name: "Name", - Status: "Status", - ReadyTime: "Ready Time", - ExecOnce: "Execute Once", - Logs: "Logs", - TriggerType: "Trigger", - CronExpression: "CRON Expression", - TriggerTypePeriodic: "Periodic", - FixedRate: "Fixed Rate", - FixedRateTipY: "Yes: The specified time interval between two task start times.", - FixedRateTipN: "No: Specify the duration between the end time of the previous task and the start time of the next task.", - PeriodicSeconds: "Period (s)", - PeriodicDelaySeconds: "Initial Delay (s)", - ExecResult: "Result", - ErrMsg: "Error", - AddTitle: "Add Scheduled Task", - EditTitle: "Edit Scheduled Task", - LogDialogTitle: "Scheduled Task Logs" - }, - Logs: { - LogId: "ID", - LogName: "Name" - }, - Online: { - LoginIP: "Login IP", - UserName: "User Name", - TokenID: "Token", - DeptName: "Dept Name", - LoginLocation: "Location", - Browser: "Browser", - OS: "OS", - LoginTime: "Login Time", - ForceExit: "Force Exit", - ConfirmForceExit: 'Are you sure to force exit user "{0}"?', - }, - Server: { - Loading: "Loading...", - Attribute: "Attribute", - Value: "Value", - ApplicationInfo: "Application Infomation", - AppName: "Name", - AppVersion: "Version", - LatestVersion: "Latest", - CPU: "CPU", - CPUCoreNum: "Core Number", - CPUUserUsage: "User Usage", - CPUSysUsage: "System Usage", - CPUIdle: "Idle", - Memory: "Memory", - JVM: "JVM", - TotalMemory: "Total", - UsedMemory: "Used", - LeftMemory: "Left", - MemoryUsage: "Percent", - ServerInfo: "Server", - ServerName: "Name", - OSName: "OS", - ServerIP: "IP", - OSArch: "OS Arch", - JVMInfo: "JVM", - JVMName: "Java Name", - JVMVersion: "Java Version", - JVMStartTime: "Start Time", - JVMRunTime: "Run Time", - JVMHome: "Java Home", - ProjectDir: "Project Path", - JVMArgs: "JVM Args", - Disk: "Disk", - DiskPath: "Path", - FileSystem: "File System", - DiskType: "Type", - DiskSize: "Size", - DiskLeftSize: "Left", - DiskUsedSize: "Used", - DiskUsedPercent: "Percent", - DbInfo: "Data Source List", - DbPoolName: "Data Source", - DbDriverClass: "Driver Class", - DbUrl: "URL", - DbUserName: "User Name" - }, - Cache: { - Basic: "Basic Infomation", - RedisVersion: "Redis Version", - RunMode: "Mode", - Port: "Port", - ClientNumber: "Client Number", - UpTimeInDays: "Up Time(days)", - Memory: "Memory Usage", - CPU: "CPU Usage", - MaxMemory: "Max Memory", - AOFStatus: "AOF", - RDBStatus: "RDB", - DBSize: "Key Size", - InputKbps: "Input(Kbps)", - CommandStat: "Command Stat", - MemoryInfo: "Memory Stat", - Command: "Command", - PeakValue: "Peak Value", - MemoryCost: "Memory Cost", - CacheList: "Cache List", - CacheName: "Cache Name", - CachePrefix: "Cache Key(Prefix)", - CacheKeyList: "Cache Keys", - CacheKey: "Cache Key", - CacheValue: "Cache Value", - ClearCache: "Clear All", - ExpireTime: "Expire Time In Seconds", - ClearSuccess: "Clear cache [{0}] success." - }, - Async: { - Type: "Type", - TaskID: "Task ID", - Status: "Status", - Percent: "Rate", - ErrMessage: "Err Message", - Stop: "Stop", - ReadyTime: "Ready Time" - } - }, - CMS: { - Dashboard: { - PublishStrategy: "Publish Strategy", - ResourceRoot: "Resource Root" - }, - ContentCore: { - ContentType: "Content Type", - CatalogType: "Catalog Type", - InternalDataType: "Internal Data Type", - ResourceType: "Resource Type", - InternalUrl: "Internal Url", - SelectCatalog: "Select Catalog", - SelectContent: "Select Content", - ToPublish: "To Publish", - Publish: "Publish", - Preview: "Preview", - Browse: "Browse", - ToPublishSuccess: "To Publish Success", - PublishSuccess: "Publish success.", - PublishProgressTitle: "Publish task", - ApplyToCatalog: "Apply to catalog", - SEOConfig: "SEO Config", - SEOTitle: "SEO Title", - SEOKeyword: "SEO Keyword", - SEODescription: "SEO Description", - PublishPipe: "Publish Pipe", - Route: { - EditSite: "Site Details", - EditTemplate: "Template Details", - EditFile: "File Details", - EditContent: "Content Details", - EditManualBlock: "Manual Block", - EditAdvSpace: "AD Space", - EditAdv: "Advertising", - }, - ClientType: { - PC: "Desktop", - Phone: "Phone", - Pad: "Pad" - } - }, - UEditor: { - InsertResource: "Insert Resource", - ResourceType: { - Image: "Image", - Audio: "Audio", - Video: "Video", - File: "File" - }, - InsertThirdVideo: "Insert third-party sharing video", - ThirdVideo: { - DialogTitle: "Insert third-party sharing video", - Code: "Video Sharing Code", - CodeTip: "Please use the `