diff --git a/pom.xml b/pom.xml index fb63efb..30e9a8c 100644 --- a/pom.xml +++ b/pom.xml @@ -190,25 +190,6 @@ - - - org.codehaus.mojo - exec-maven-plugin - 3.1.0 - - - run-code-generator - compile - - java - - - com.cool.core.plugin.I18nGenerator - compile - - - - diff --git a/src/main/java/com/cool/core/base/BaseController.java b/src/main/java/com/cool/core/base/BaseController.java index 29dae1b..c16dfd5 100644 --- a/src/main/java/com/cool/core/base/BaseController.java +++ b/src/main/java/com/cool/core/base/BaseController.java @@ -51,9 +51,11 @@ public abstract class BaseController, T extends BaseEnt protected final String COOL_PAGE_OP = "COOL_PAGE_OP"; protected final String COOL_LIST_OP = "COOL_LIST_OP"; + protected final String COOL_INFO_OP = "COOL_INFO_OP"; private final ThreadLocal> pageOption = new ThreadLocal<>(); private final ThreadLocal> listOption = new ThreadLocal<>(); + private final ThreadLocal> infoOption = new ThreadLocal<>(); private final ThreadLocal requestParams = new ThreadLocal<>(); @ModelAttribute @@ -61,16 +63,20 @@ public abstract class BaseController, T extends BaseEnt @RequestAttribute JSONObject requestParams) { String requestPath = ((ServletRequestAttributes) Objects.requireNonNull( RequestContextHolder.getRequestAttributes())).getRequest().getRequestURI(); - if (!requestPath.endsWith("/page") && !requestPath.endsWith("/list")) { + if (!requestPath.endsWith("/page") && !requestPath.endsWith("/list") + && !requestPath.endsWith("/info")) { // 非page或list不执行 return; } this.pageOption.set(new CrudOption<>(requestParams)); this.listOption.set(new CrudOption<>(requestParams)); + this.infoOption.set(new CrudOption<>(requestParams)); this.requestParams.set(requestParams); init(request, requestParams); request.setAttribute(COOL_PAGE_OP, this.pageOption.get()); request.setAttribute(COOL_LIST_OP, this.listOption.get()); + request.setAttribute(COOL_INFO_OP, this.infoOption.get()); + removeThreadLocal(); } @@ -87,6 +93,10 @@ public abstract class BaseController, T extends BaseEnt return new CrudOption<>(this.requestParams.get()); } + public void setInfoOption(CrudOption infoOption) { + this.infoOption.set(infoOption); + } + public void setListOption(CrudOption listOption) { this.listOption.set(listOption); } @@ -153,8 +163,11 @@ public abstract class BaseController, T extends BaseEnt @Operation(summary = "信息", description = "根据ID查询单个信息") @GetMapping("/info") protected R info(@RequestAttribute() JSONObject requestParams, - @RequestParam() Long id) { - return R.ok((T) service.info(requestParams, id)); + @RequestParam() Long id, + @RequestAttribute(COOL_INFO_OP) CrudOption option) { + T info = (T) service.info(requestParams, id); + invokerTransform(option, info); + return R.ok(info); } /** @@ -219,7 +232,7 @@ public abstract class BaseController, T extends BaseEnt * @param page 分页返回数据 */ protected PageResult pageResult(Page page) { - return PageResult.of( page ); + return PageResult.of(page); } public Class currentEntityClass() { diff --git a/src/main/java/com/cool/core/exception/CoolPreconditions.java b/src/main/java/com/cool/core/exception/CoolPreconditions.java index fc6d298..8779f3a 100644 --- a/src/main/java/com/cool/core/exception/CoolPreconditions.java +++ b/src/main/java/com/cool/core/exception/CoolPreconditions.java @@ -1,6 +1,7 @@ package com.cool.core.exception; import cn.hutool.core.util.ObjectUtil; +import com.cool.core.util.I18nUtil; import java.util.Arrays; import java.util.Optional; import lombok.Getter; @@ -69,6 +70,7 @@ public class CoolPreconditions { } private static String formatMessage(String messagePattern, Object... arguments) { + messagePattern = I18nUtil.getI18nMsg(messagePattern); StringBuilder sb = new StringBuilder(); int argumentIndex = 0; int placeholderIndex = messagePattern.indexOf("{}"); diff --git a/src/main/java/com/cool/core/i18n/I18nGenerator.java b/src/main/java/com/cool/core/i18n/I18nGenerator.java new file mode 100644 index 0000000..40741a5 --- /dev/null +++ b/src/main/java/com/cool/core/i18n/I18nGenerator.java @@ -0,0 +1,219 @@ +package com.cool.core.i18n; + +import static com.cool.core.util.I18nUtil.*; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.ObjUtil; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.cool.core.lock.CoolLock; +import com.cool.core.util.CoolPluginInvokers; +import com.cool.core.util.I18nUtil; +import com.cool.core.util.PathUtils; +import com.cool.modules.base.entity.sys.BaseSysMenuEntity; +import com.cool.modules.base.service.sys.BaseSysMenuService; +import com.cool.modules.dict.entity.DictInfoEntity; +import com.cool.modules.dict.entity.DictTypeEntity; +import com.cool.modules.dict.service.DictInfoService; +import com.cool.modules.dict.service.DictTypeService; +import com.mybatisflex.core.query.QueryWrapper; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.Duration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +public class I18nGenerator { + private final BaseSysMenuService baseSysMenuService; + private final DictTypeService dictTypeService; + private final DictInfoService dictInfoService; + private final CoolLock coolLock; + private final I18nUtil i18nUtil; + + private List languages; + private static final Duration DURATION = Duration.ofSeconds(30); + public void run(Map map) { + log.info("国际化 翻译..."); + languages = (List) map.getOrDefault("languages", List.of("zh-cn", "zh-tw", "en")); + path = (String) map.getOrDefault("path", "assets/i18n"); + init(); + log.info("✅国际化 翻译 成功!!!"); + enable = true; + } + + public void init() { + // 四个任务并发执行 + CompletableFuture futureMsg = CompletableFuture.runAsync(this::genBaseMsg); + CompletableFuture futureMenu = CompletableFuture.runAsync(this::genBaseMenu); + CompletableFuture futureDictInfo = CompletableFuture.runAsync(this::genBaseDictInfo); + CompletableFuture futureDictType = CompletableFuture.runAsync(this::genBaseDictType); + + // 等待全部执行完成 + CompletableFuture.allOf(futureMsg, futureMenu, futureDictInfo, futureDictType).join(); + } + + private void genBaseMsg() { + try { + Map msgMap = new HashMap<>(); + // 从idea本地启动时,从项目目录中读取 + Files.walk(Paths.get(System.getProperty("user.dir"))) + .filter(Files::isRegularFile) + .filter(path -> path.toString().endsWith(".java")) + .filter(path -> !path.toString().contains("/target/") && !path.toString().contains("/.git/")) + .forEach(path -> msgMap.putAll(processFile(path))); + if (ObjUtil.isNotEmpty(msgMap)) { + // 系统异常信息,输出到resources/i18n 文件夹下,只有本地运行会生成 + File msgfile = FileUtil.file(PathUtils.getUserDir(), + "src", "main", "resources", "cool", "i18n", "msg", "template.json"); + // 确保父目录存在 + FileUtil.mkParentDirs(msgfile); + // 写入内容 + FileUtil.writeUtf8String(JSONUtil.toJsonStr(msgMap), msgfile); + } else { + try { + // jar启动时,从jar包中读取 + PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); + Resource resource = resolver.getResource("classpath:cool/i18n/msg/template.json"); + String content = FileUtil.readUtf8String(resource.getFile()); + msgMap.putAll(JSONUtil.toBean(content, Map.class)); + } catch (Exception e) { + log.error("获取系统异常信息失败", e); + } + } + extracted(MSG_PREFIX, msgMap); + } catch (Exception e) { + log.error("国际化系统异常信息失败", e); + } + } + + /** + * 生成菜单信息国际化 + */ + @Async + public void asyncGenBaseMenu() { + if (coolLock.tryLock(MENU_PREFIX, DURATION)) { + genBaseMenu(); + coolLock.unlock(MENU_PREFIX); + } + } + private void genBaseMenu() { + try { + Map menuMap = baseSysMenuService.list(QueryWrapper.create().select(BaseSysMenuEntity::getName)) + .stream() + .collect(Collectors.toMap( + BaseSysMenuEntity::getName, + BaseSysMenuEntity::getName, + (oldValue, newValue) -> oldValue + )); + extracted(MENU_PREFIX, menuMap); + } catch (Exception e) { + log.error("国际化菜单信息失败", e); + } + } + @Async + public void asyncGenBaseDictType() { + if (coolLock.tryLock(DICT_TYPE_PREFIX, DURATION)) { + genBaseDictType(); + coolLock.unlock(DICT_TYPE_PREFIX); + } + } + private void genBaseDictType() { + try { + Map dataMap = dictTypeService.list(QueryWrapper.create().select(DictTypeEntity::getName)) + .stream() + .collect(Collectors.toMap( + DictTypeEntity::getName, + DictTypeEntity::getName, + (oldValue, newValue) -> oldValue + )); + extracted(DICT_TYPE_PREFIX, dataMap); + } catch (Exception e) { + log.error("国际化字段类型信息失败", e); + } + } + @Async + public void asyncGenBaseDictInfo() { + if (coolLock.tryLock(DICT_INFO_PREFIX, DURATION)) { + genBaseDictInfo(); + coolLock.unlock(DICT_INFO_PREFIX); + } + } + private void genBaseDictInfo() { + try { + Map dataMap = dictInfoService.list(QueryWrapper.create().select(DictInfoEntity::getName)) + .stream() + .collect(Collectors.toMap( + DictInfoEntity::getName, + DictInfoEntity::getName, + (oldValue, newValue) -> oldValue + )); + extracted(DICT_INFO_PREFIX, dataMap); + } catch (Exception e) { + log.error("国际化字段类型信息失败", e); + } + } + + private void extracted(String prefix, Map dataMap) { + languages.forEach(language -> { + String key = prefix + language; + if (!i18nUtil.exist(key)) { + JSONObject jsonObject = invokeTranslate(dataMap, language); + if (ObjUtil.isNotNull(jsonObject)) { + i18nUtil.update(key, jsonObject); + } + } + }); + } + + private JSONObject invokeTranslate(Map map, String language) { + return (JSONObject) CoolPluginInvokers.invoke("i18n", "invokeTranslate", map, language); + } + + // 匹配 CoolPreconditions 抛异常语句中的中文字符串 + private static final Pattern EXCEPTION_PATTERN = Pattern.compile( + "CoolPreconditions\\.(\\w+)\\s*\\([^;]*?\"([^\"]*[\u4e00-\u9fa5]+[^\"]*)\"", Pattern.MULTILINE + ); + + private static Map processFile(Path path) { + Map map = new HashMap<>(); + try { + String content = Files.readString(path, StandardCharsets.UTF_8); + // 去掉注释 + content = removeComments(content); + + // 仅查找方法体内的 CoolPreconditions 调用 + Matcher matcher = EXCEPTION_PATTERN.matcher(content); + while (matcher.find()) { + String chineseText = matcher.group(2).trim(); + map.put(chineseText, chineseText); + } + } catch (IOException e) { + e.printStackTrace(); + } + return map; + } + + // 移除注释(单行与多行) + private static String removeComments(String code) { + String noMultiLine = code.replaceAll("/\\*.*?\\*/", ""); // 多行注释 + return noMultiLine.replaceAll("//.*", ""); // 单行注释 + } +} \ No newline at end of file diff --git a/src/main/java/com/cool/core/plugin/I18nGenerator.java b/src/main/java/com/cool/core/plugin/I18nGenerator.java deleted file mode 100644 index aeca1db..0000000 --- a/src/main/java/com/cool/core/plugin/I18nGenerator.java +++ /dev/null @@ -1,157 +0,0 @@ -package com.cool.core.plugin; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.io.IoUtil; -import cn.hutool.http.HttpUtil; -import cn.hutool.json.JSONArray; -import cn.hutool.json.JSONObject; -import cn.hutool.json.JSONUtil; -import org.springframework.core.io.Resource; -import org.springframework.core.io.support.PathMatchingResourcePatternResolver; - -import java.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class I18nGenerator { - public static void main(String[] args) { - new I18nGenerator().run(); - } - public void run() { - System.out.println("i18n translate ..."); - // 要生成的文件路径 - File msgfile = FileUtil.file(System.getProperty("user.dir"), - "src", "main", "resources", "cool", "i18n", "msg", "en.json"); - if (!msgfile.exists()) { - JSONObject jsonObject = genExceptionMsg(); - - // 确保父目录存在 - FileUtil.mkParentDirs(msgfile); - // 写入内容 - FileUtil.writeUtf8String(JSONUtil.toJsonStr(jsonObject), msgfile); - } - // 要生成的文件路径 - File menufile = FileUtil.file(System.getProperty("user.dir"), - "src", "main", "resources", "cool", "i18n", "menu", "en.json"); - if (!menufile.exists()) { - JSONObject jsonObject = genBaseMenu(); - // 确保父目录存在 - FileUtil.mkParentDirs(menufile); - // 写入内容 - FileUtil.writeUtf8String(JSONUtil.toJsonStr(jsonObject), menufile); - } - System.out.println("✅i18n translate success !!!"); - } - - /** - * 生成菜单信息国际化 - */ - private JSONObject genBaseMenu() { - try { - PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); - Resource[] resources = resolver.getResources("classpath:cool/data/menu/*.json"); - Map map = new HashMap<>(); - List list = new ArrayList<>(); - // 遍历所有.json文件 - for (Resource resource : resources) { - String jsonStr = IoUtil.read(resource.getInputStream(), StandardCharsets.UTF_8); - // 使用 解析 JSON 字符串 - JSONArray jsonArray = JSONUtil.parseArray(jsonStr); - // 遍历 JSON 数组 - for (Object obj : jsonArray) { - JSONObject jsonObj = (JSONObject) obj; - parseMenu(jsonObj, list); - } - } - for (String value : list) { - map.put(value, value); - } - return invokeTranslate(map, "en"); - } catch (IOException e) { - e.printStackTrace(); - } - return null; - } - - private void parseMenu(JSONObject jsonObj, List list) { - list.add(jsonObj.getStr("name")); - // 递归处理子菜单 - JSONArray childMenus = jsonObj.getJSONArray("childMenus"); - if (childMenus != null) { - for (Object obj : childMenus) { - JSONObject childObj = (JSONObject) obj; - parseMenu(childObj, list); - } - } - } - - /** - * 生成异常信息国际化 - */ - private JSONObject genExceptionMsg() { - Path rootPath = Paths.get(System.getProperty("user.dir")); - try { - Map map = new HashMap<>(); - Files.walk(rootPath) - .filter(Files::isRegularFile) - .filter(path -> path.toString().endsWith(".java")) - .filter(path -> !path.toString().contains("/target/") && !path.toString().contains("/.git/")) - .forEach(path -> map.putAll(processFile(path))); - return invokeTranslate(map, "en"); - } catch (Exception e) { - e.printStackTrace(); - } - return null; - } - private JSONObject invokeTranslate(Map map, String language) { - if (map.isEmpty()) { - return new JSONObject(); - } - Map data = new HashMap<>(); - data.put("label", "i18n-node"); - data.put("params", Map.of("text", JSONUtil.toJsonStr(map), "language", language)); - data.put("stream", false); - String res = HttpUtil.post("https://service.cool-js.com/api/open/flow/run/invoke", JSONUtil.toJsonStr(data)); - JSONObject jsonObject = JSONUtil.parseObj(res); - return jsonObject.getJSONObject("data").getJSONObject("result").getJSONObject("data"); - } - - // 匹配 CoolPreconditions 抛异常语句中的中文字符串 - private static final Pattern EXCEPTION_PATTERN = Pattern.compile( - "CoolPreconditions\\.(\\w+)\\s*\\([^;]*?\"([^\"]*[\u4e00-\u9fa5]+[^\"]*)\"", Pattern.MULTILINE - ); - - private static Map processFile(Path path) { - Map map = new HashMap<>(); - try { - String content = Files.readString(path, StandardCharsets.UTF_8); - // 去掉注释 - content = removeComments(content); - - // 仅查找方法体内的 CoolPreconditions 调用 - Matcher matcher = EXCEPTION_PATTERN.matcher(content); - while (matcher.find()) { - String chineseText = matcher.group(2).trim(); - map.put(chineseText, chineseText); - } - } catch (IOException e) { - e.printStackTrace(); - } - return map; - } - - // 移除注释(单行与多行) - private static String removeComments(String code) { - String noMultiLine = code.replaceAll("/\\*.*?\\*/", ""); // 多行注释 - return noMultiLine.replaceAll("//.*", ""); // 单行注释 - } -} diff --git a/src/main/java/com/cool/core/plugin/consts/PluginConsts.java b/src/main/java/com/cool/core/plugin/consts/PluginConsts.java index eb07fee..1d80f37 100644 --- a/src/main/java/com/cool/core/plugin/consts/PluginConsts.java +++ b/src/main/java/com/cool/core/plugin/consts/PluginConsts.java @@ -4,7 +4,10 @@ package com.cool.core.plugin.consts; * 常量工具 */ public interface PluginConsts { - + /** + * 国际化插件 + */ + String i18n = "i18n"; /** * 上传文件hook */ diff --git a/src/main/java/com/cool/core/plugin/event/PluginActionEnum.java b/src/main/java/com/cool/core/plugin/event/PluginActionEnum.java new file mode 100644 index 0000000..064b807 --- /dev/null +++ b/src/main/java/com/cool/core/plugin/event/PluginActionEnum.java @@ -0,0 +1,7 @@ +package com.cool.core.plugin.event; + +public enum PluginActionEnum { + INSTALL, + UNINSTALL, + UPDATE, +} diff --git a/src/main/java/com/cool/core/plugin/event/PluginEvent.java b/src/main/java/com/cool/core/plugin/event/PluginEvent.java new file mode 100644 index 0000000..d4726c0 --- /dev/null +++ b/src/main/java/com/cool/core/plugin/event/PluginEvent.java @@ -0,0 +1,26 @@ +package com.cool.core.plugin.event; + +import com.cool.modules.plugin.entity.PluginInfoEntity; +import java.time.Clock; +import lombok.Getter; +import org.springframework.context.ApplicationEvent; + +public class PluginEvent extends ApplicationEvent { + @Getter + private String key; + @Getter + private PluginActionEnum actionEnum; + @Getter + private PluginInfoEntity pluginInfoEntity; + public PluginEvent(Object source, String key, PluginActionEnum actionEnum, PluginInfoEntity data) { + super(source); + this.key = key; + this.actionEnum = actionEnum; + this.pluginInfoEntity = data; + } + + public PluginEvent(Object source, Clock clock) { + super(source, clock); + } +} + diff --git a/src/main/java/com/cool/core/plugin/event/PluginEventListener.java b/src/main/java/com/cool/core/plugin/event/PluginEventListener.java new file mode 100644 index 0000000..8c56577 --- /dev/null +++ b/src/main/java/com/cool/core/plugin/event/PluginEventListener.java @@ -0,0 +1,46 @@ +package com.cool.core.plugin.event; + +import static com.cool.core.plugin.consts.PluginConsts.i18n; + +import cn.hutool.core.util.ObjUtil; +import com.cool.core.i18n.I18nGenerator; +import com.cool.core.util.I18nUtil; +import com.cool.modules.plugin.entity.PluginInfoEntity; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class PluginEventListener implements ApplicationListener { + private final I18nGenerator i18nGenerator; + private final I18nUtil i18nUtil; + @Async + @Override + public void onApplicationEvent(PluginEvent event) { + if (ObjUtil.equals(event.getKey(), i18n)) { + // 国际化插件变更 + PluginActionEnum actionEnum = event.getActionEnum(); + PluginInfoEntity pluginInfoEntity = event.getPluginInfoEntity(); + + if (ObjUtil.equals(actionEnum, PluginActionEnum.INSTALL) && ObjUtil.equals(pluginInfoEntity.getStatus(), 1)) { + // 安装插件后,如果插件状态为启用,则生成国际化文件 + i18nGenerator.run((Map) pluginInfoEntity.getConfig()); + } else if (ObjUtil.equals(actionEnum, PluginActionEnum.UPDATE)) { + if (ObjUtil.equals(pluginInfoEntity.getStatus(), 1)) { + // 更新插件配置 + i18nGenerator.run((Map) pluginInfoEntity.getConfig()); + } else { + // 停用 + I18nUtil.enable = false; + } + } else if (ObjUtil.equals(actionEnum, PluginActionEnum.UNINSTALL)) { + // 卸载国际化插件,则删除国际化文件 + i18nUtil.clear(); + } + } + } +} + diff --git a/src/main/java/com/cool/core/plugin/event/PluginEventPublisher.java b/src/main/java/com/cool/core/plugin/event/PluginEventPublisher.java new file mode 100644 index 0000000..78d9474 --- /dev/null +++ b/src/main/java/com/cool/core/plugin/event/PluginEventPublisher.java @@ -0,0 +1,19 @@ +package com.cool.core.plugin.event; + +import com.cool.modules.plugin.entity.PluginInfoEntity; +import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class PluginEventPublisher { + private final ApplicationEventPublisher applicationEventPublisher; + + public void publish(String key, PluginActionEnum actionEnum, PluginInfoEntity data) { + PluginEvent event = new PluginEvent(this, key, actionEnum, data); + applicationEventPublisher.publishEvent(event); + } + + +} diff --git a/src/main/java/com/cool/core/plugin/service/CoolPluginService.java b/src/main/java/com/cool/core/plugin/service/CoolPluginService.java index 16b57d4..34f6bd0 100644 --- a/src/main/java/com/cool/core/plugin/service/CoolPluginService.java +++ b/src/main/java/com/cool/core/plugin/service/CoolPluginService.java @@ -11,6 +11,8 @@ import com.cool.core.config.PluginJson; import com.cool.core.exception.CoolException; import com.cool.core.exception.CoolPreconditions; import com.cool.core.plugin.config.DynamicJarClassLoader; +import com.cool.core.plugin.event.PluginActionEnum; +import com.cool.core.plugin.event.PluginEventPublisher; import com.cool.core.util.CoolPluginInvokers; import com.cool.core.util.MapExtUtil; import com.cool.core.util.PathUtils; @@ -24,6 +26,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; +import java.util.Map; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.exceptions.PersistenceException; @@ -43,14 +46,15 @@ public class CoolPluginService { final private PluginInfoService pluginInfoService; + final private PluginEventPublisher pluginEventPublisher; + @Value("${cool.plugin.path}") private String pluginPath; public void init() { List list = pluginInfoService .list(QueryWrapper - .create().select(PluginInfoEntity::getId, PluginInfoEntity::getPluginJson, - PluginInfoEntity::getKey, PluginInfoEntity::getName) + .create() .eq(PluginInfoEntity::getStatus, 1)); if (ObjUtil.isEmpty(list)) { log.info("没有可初始化的插件"); @@ -77,6 +81,7 @@ public class CoolPluginService { dynamicJarLoaderService.install(pluginJson.getJarPath(), true); // 设置配置 CoolPluginInvokers.setPluginJson(entity.getKey(), entity); + pluginEventPublisher.publish(entity.getKey(), PluginActionEnum.INSTALL, entity); } catch (Exception e) { log.error("初始化{}插件失败", entity.getName(), e); } finally { @@ -100,9 +105,10 @@ public class CoolPluginService { PluginJson pluginJson = dynamicJarLoaderService.install(jarFilePath, force); key = pluginJson.getKey(); // 保存插件信息入库 - savePluginInfo(pluginJson, jarFilePath, jarFile, force); + PluginInfoEntity pluginInfoEntity = savePluginInfo(pluginJson, jarFilePath, jarFile, force); // 把 ApplicationContext 对象传递打插件类中,使其在插件中也能正常使用spring bean对象 CoolPluginInvokers.setApplicationContext(pluginJson.getKey()); + pluginEventPublisher.publish(pluginJson.getKey(), PluginActionEnum.INSTALL, pluginInfoEntity); } catch (PersistenceException persistenceException) { extractedAfterErr(jarFile, key); if (persistenceException.getMessage().contains("Duplicate entry")) { @@ -165,13 +171,14 @@ public class CoolPluginService { boolean flag = pluginInfoEntity.removeById(); if (flag) { FileUtil.del(pluginInfoEntity.getPluginJson().getJarPath()); + pluginEventPublisher.publish(pluginInfoEntity.getKey(), PluginActionEnum.UNINSTALL, null); } } /** * 保存插件信息 */ - private void savePluginInfo(PluginJson pluginJson, String jarFilePath , + private PluginInfoEntity savePluginInfo(PluginJson pluginJson, String jarFilePath , File jarFile, boolean force) { CoolPreconditions.checkEmpty(pluginJson, "插件安装失败"); @@ -186,9 +193,11 @@ public class CoolPluginService { closeSameNamePlugin(pluginJson); // 覆盖插件 coverPlugin(pluginJson, pluginInfo); - return; + return pluginInfo; } + pluginInfo.setStatus(1); pluginInfo.save(); + return pluginInfo; } /** @@ -201,7 +210,7 @@ public class CoolPluginService { String oldJarPath = one.getPluginJson().getJarPath(); // 重新加载配置不更新 pluginInfo.setConfig(one.getConfig()); - pluginInfo.getPluginJson().setConfig(one.getConfig()); + pluginInfo.getPluginJson().setConfig((Map) one.getConfig()); // 设置插件配置 CoolPluginInvokers.setPluginJson(pluginInfo.getKey(), pluginInfo); CopyOptions options = CopyOptions.create().setIgnoreNullValue(true); @@ -274,10 +283,11 @@ public class CoolPluginService { entity.getId()); // 调用插件更新配置标识 boolean invokePluginConfig = false; - if (!MapExtUtil.compareMaps(entity.getConfig(), dbPluginInfoEntity.getConfig())) { + if (!MapExtUtil.compareMaps((Map) entity.getConfig(), + (Map) dbPluginInfoEntity.getConfig())) { // 不一致,说明更新了配置 entity.setPluginJson(dbPluginInfoEntity.getPluginJson()); - entity.getPluginJson().setConfig(entity.getConfig()); + entity.getPluginJson().setConfig((Map) entity.getConfig()); // 更新了配置, 且插件是开启状态 invokePluginConfig = ObjUtil.equals(dbPluginInfoEntity.getStatus(), 1); } @@ -290,6 +300,8 @@ public class CoolPluginService { CoolPluginInvokers.setPluginJson(dbPluginInfoEntity.getKey(), entity); } pluginInfoService.update(entity); + pluginEventPublisher.publish(dbPluginInfoEntity.getKey(), PluginActionEnum.UPDATE, pluginInfoService.getPluginInfoEntityById( + entity.getId())); } /** diff --git a/src/main/java/com/cool/core/request/RequestParamsFilter.java b/src/main/java/com/cool/core/request/RequestParamsFilter.java index 257e197..2ac9923 100644 --- a/src/main/java/com/cool/core/request/RequestParamsFilter.java +++ b/src/main/java/com/cool/core/request/RequestParamsFilter.java @@ -31,12 +31,14 @@ public class RequestParamsFilter implements Filter { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) - throws IOException, ServletException { + throws IOException, ServletException { // 防止流读取一次后就没有了, 所以需要将流继续写出去 HttpServletRequest request = (HttpServletRequest) servletRequest; JSONObject requestParams = new JSONObject(); + String language = request.getHeader("language"); if (StrUtil.isNotEmpty(request.getContentType()) && request.getContentType().contains("multipart/form-data")) { servletRequest.setAttribute("requestParams", requestParams); + servletRequest.setAttribute("cool-language", language); filterChain.doFilter(servletRequest, servletResponse); } else { BodyReaderHttpServletRequestWrapper requestWrapper = new BodyReaderHttpServletRequestWrapper(request); @@ -57,7 +59,7 @@ public class RequestParamsFilter implements Filter { requestParams.set("tokenInfo", ((JWT) jwtObj).getPayload().getClaimsJson()); } requestWrapper.setAttribute("requestParams", requestParams); - + requestWrapper.setAttribute("cool-language", language); filterChain.doFilter(requestWrapper, servletResponse); } } diff --git a/src/main/java/com/cool/core/util/I18nUtil.java b/src/main/java/com/cool/core/util/I18nUtil.java new file mode 100644 index 0000000..91b92fb --- /dev/null +++ b/src/main/java/com/cool/core/util/I18nUtil.java @@ -0,0 +1,136 @@ +package com.cool.core.util; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import jakarta.annotation.PostConstruct; +import java.io.File; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicReference; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; + +@Slf4j +@Component +public class I18nUtil { + + public static final String MSG_PREFIX = "msg_"; + public static final String MENU_PREFIX = "menu_"; + public static final String DICT_INFO_PREFIX = "dictInfo_"; + public static final String DICT_TYPE_PREFIX = "dictType_"; + + public static boolean enable = false; + + public static String path; + public static String getLanguage() { + RequestAttributes attributes = RequestContextHolder.getRequestAttributes(); + if (attributes == null) { + return null; + } + return (String) attributes.getAttribute("cool-language", RequestAttributes.SCOPE_REQUEST); + } + + private static final Map data = new ConcurrentHashMap<>(); + + @PostConstruct + public void init() { + if (!enable) { + return; + } + try { + // 获取该目录下所有的 .json 文件 + List jsonFiles = FileUtil.loopFiles(getPath(), file -> + file.isFile() && file.getName().endsWith(".json") + ); + jsonFiles.forEach(file -> { + String parentName = file.getParentFile().getName(); + String content = FileUtil.readUtf8String(file); + String key = parentName + "_" + file.getName().replace(".json", ""); + data.put(key, JSONUtil.parseObj(content)); + }); + } catch (Exception e) { + log.error("读取国际化文件失败", e); + } + } + + public boolean exist(String name) { + // 获取该目录下所有的 .json 文件 + List jsonFiles = FileUtil.loopFiles(getPath(), file -> + file.isFile() && file.getName().endsWith(".json") + ); + AtomicReference flag = new AtomicReference<>(false); + jsonFiles.forEach(file -> { + String parentName = file.getParentFile().getName(); + String key = parentName + "_" + file.getName().replace(".json", ""); + if (key.equals(name)) { + flag.set(true); + } + }); + return flag.get(); + } + + public static String getI18nMenu(String name) { + return getI18n(name, MENU_PREFIX); + } + + public static String getI18nMsg(String name) { + return getI18n(name, MSG_PREFIX); + } + + public static String getI18nDictInfo(String name) { + return getI18n(name, DICT_INFO_PREFIX); + } + public static String getI18nDictType(String name) { + return getI18n(name, DICT_TYPE_PREFIX); + } + private static String getI18n(String name, String prefix) { + if (!enable) { + return name; + } + String language = I18nUtil.getLanguage(); + if (language == null) { + return name; + } + JSONObject jsonObject = data.get(prefix + language); + if (jsonObject == null) { + return name; + } + String str = jsonObject.getStr(name); + if (str == null) { + return name; + } + return str; + } + + public void update(String key, JSONObject object) { + data.put(key, object); + String[] split = key.split("_"); + String absolutePath = getPath(); + File file = FileUtil.file(absolutePath, split[0], split[1] + ".json"); + // 确保父目录存在 + FileUtil.mkParentDirs(file); + // 写入内容 + FileUtil.writeUtf8String(JSONUtil.toJsonStr(object), file); + } + + private String getPath() { + String absolutePath = path; + if (!PathUtils.isAbsolutePath(absolutePath)) { + absolutePath = PathUtils.getUserDir() + File.separator + absolutePath; + } + return absolutePath; + } + + public void clear() { + data.clear(); + List jsonFiles = FileUtil.loopFiles(getPath(), file -> + file.isFile() && file.getName().endsWith(".json") + ); + jsonFiles.forEach(File::delete); + enable = false; + } +} \ No newline at end of file diff --git a/src/main/java/com/cool/modules/base/controller/admin/AdminBaseCommController.java b/src/main/java/com/cool/modules/base/controller/admin/AdminBaseCommController.java index 8e1fc59..451ab3c 100644 --- a/src/main/java/com/cool/modules/base/controller/admin/AdminBaseCommController.java +++ b/src/main/java/com/cool/modules/base/controller/admin/AdminBaseCommController.java @@ -6,6 +6,8 @@ import com.cool.core.annotation.TokenIgnore; import com.cool.core.eps.CoolEps; import com.cool.core.file.FileUploadStrategyFactory; import com.cool.core.request.R; +import com.cool.core.util.I18nUtil; +import com.cool.modules.base.entity.sys.BaseSysMenuEntity; import com.cool.modules.base.entity.sys.BaseSysUserEntity; import com.cool.modules.base.service.sys.BaseSysLoginService; import com.cool.modules.base.service.sys.BaseSysPermsService; @@ -14,6 +16,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.GetMapping; @@ -67,7 +70,10 @@ public class AdminBaseCommController { @Operation(summary = "权限与菜单") @GetMapping("/permmenu") public R permmenu(@RequestAttribute() Long adminUserId) { - return R.ok(baseSysPermsService.permmenu(adminUserId)); + Dict permmenu = baseSysPermsService.permmenu(adminUserId); + List list = (List) permmenu.getObj("menus"); + list.forEach(o -> o.setName(I18nUtil.getI18nMenu(o.getName()))); + return R.ok(permmenu); } @Operation(summary = "文件上传") diff --git a/src/main/java/com/cool/modules/base/controller/admin/sys/AdminBaseSysMenuController.java b/src/main/java/com/cool/modules/base/controller/admin/sys/AdminBaseSysMenuController.java index d1abeee..fbe96e2 100644 --- a/src/main/java/com/cool/modules/base/controller/admin/sys/AdminBaseSysMenuController.java +++ b/src/main/java/com/cool/modules/base/controller/admin/sys/AdminBaseSysMenuController.java @@ -4,7 +4,9 @@ import cn.hutool.json.JSONObject; import com.cool.core.annotation.CoolRestController; import com.cool.core.base.BaseController; import com.cool.core.exception.CoolPreconditions; +import com.cool.core.request.CrudOption; import com.cool.core.request.R; +import com.cool.core.util.I18nUtil; import com.cool.modules.base.entity.sys.BaseSysMenuEntity; import com.cool.modules.base.service.sys.BaseSysMenuService; import io.swagger.v3.oas.annotations.Operation; @@ -25,6 +27,14 @@ public class AdminBaseSysMenuController extends @Override protected void init(HttpServletRequest request, JSONObject requestParams) { + CrudOption transform = createOp() + .transform(o -> { + BaseSysMenuEntity entity = (BaseSysMenuEntity) o; + entity.setName(I18nUtil.getI18nMenu(entity.getName())); + }); + setPageOption(transform); + setListOption(transform); + setInfoOption(transform); } @Operation(summary = "创建代码", description = "创建代码") diff --git a/src/main/java/com/cool/modules/base/service/sys/BaseSysPermsService.java b/src/main/java/com/cool/modules/base/service/sys/BaseSysPermsService.java index 2333b66..0e364db 100644 --- a/src/main/java/com/cool/modules/base/service/sys/BaseSysPermsService.java +++ b/src/main/java/com/cool/modules/base/service/sys/BaseSysPermsService.java @@ -1,8 +1,8 @@ package com.cool.modules.base.service.sys; +import cn.hutool.core.lang.Dict; import com.cool.modules.base.entity.sys.BaseSysMenuEntity; import com.cool.modules.base.entity.sys.BaseSysUserEntity; - import java.util.List; /** @@ -117,7 +117,7 @@ public interface BaseSysPermsService { * @param adminUserId 登录的用户 * @return 权限菜单 */ - Object permmenu(Long adminUserId); + Dict permmenu(Long adminUserId); /** * 更新角色权限 diff --git a/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysMenuServiceImpl.java b/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysMenuServiceImpl.java index d02bd00..0e3d930 100644 --- a/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysMenuServiceImpl.java +++ b/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysMenuServiceImpl.java @@ -3,11 +3,13 @@ package com.cool.modules.base.service.sys.impl; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjUtil; +import cn.hutool.extra.spring.SpringUtil; import cn.hutool.json.JSONObject; import com.cool.CoolApplication; import com.cool.core.base.BaseServiceImpl; import com.cool.core.base.ModifyEnum; import com.cool.core.eps.CoolEps; +import com.cool.core.i18n.I18nGenerator; import com.cool.core.util.CompilerUtils; import com.cool.core.util.CoolSecurityUtil; import com.cool.core.util.PathUtils; @@ -62,6 +64,9 @@ public class BaseSysMenuServiceImpl extends BaseServiceImpl menus) { menus.forEach(this::importMenu); + SpringUtil.getBean(I18nGenerator.class).asyncGenBaseMenu(); return true; } diff --git a/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysPermsServiceImpl.java b/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysPermsServiceImpl.java index bcc6194..42919a5 100644 --- a/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysPermsServiceImpl.java +++ b/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysPermsServiceImpl.java @@ -168,7 +168,7 @@ public class BaseSysPermsServiceImpl implements BaseSysPermsService { } @Override - public Object permmenu(Long adminUserId) { + public Dict permmenu(Long adminUserId) { return Dict.create().set("menus", getMenus(adminUserId)).set("perms", getPerms(adminUserId)); } diff --git a/src/main/java/com/cool/modules/dict/controller/admin/AdminDictInfoController.java b/src/main/java/com/cool/modules/dict/controller/admin/AdminDictInfoController.java index 2ec84b1..a9288d0 100644 --- a/src/main/java/com/cool/modules/dict/controller/admin/AdminDictInfoController.java +++ b/src/main/java/com/cool/modules/dict/controller/admin/AdminDictInfoController.java @@ -5,7 +5,9 @@ import cn.hutool.json.JSONObject; import com.cool.core.annotation.CoolRestController; import com.cool.core.annotation.TokenIgnore; import com.cool.core.base.BaseController; +import com.cool.core.request.CrudOption; import com.cool.core.request.R; +import com.cool.core.util.I18nUtil; import com.cool.modules.dict.entity.DictInfoEntity; import com.cool.modules.dict.entity.table.DictInfoEntityTableDef; import com.cool.modules.dict.service.DictInfoService; @@ -26,8 +28,18 @@ public class AdminDictInfoController extends BaseController { + DictInfoEntity entity = (DictInfoEntity) o; + entity.setName(I18nUtil.getI18nDictInfo(entity.getName())); + })); + CrudOption transform = createOp().transform(o -> { + DictInfoEntity entity = (DictInfoEntity) o; + entity.setName(I18nUtil.getI18nDictInfo(entity.getName())); + }); + setPageOption(transform); + setInfoOption(transform); } @Operation(summary = "获得字典数据", description = "获得字典数据信息") diff --git a/src/main/java/com/cool/modules/dict/controller/admin/AdminDictTypeController.java b/src/main/java/com/cool/modules/dict/controller/admin/AdminDictTypeController.java index f94d5bb..fb0ff5f 100644 --- a/src/main/java/com/cool/modules/dict/controller/admin/AdminDictTypeController.java +++ b/src/main/java/com/cool/modules/dict/controller/admin/AdminDictTypeController.java @@ -1,15 +1,17 @@ package com.cool.modules.dict.controller.admin; +import static com.cool.modules.dict.entity.table.DictTypeEntityTableDef.DICT_TYPE_ENTITY; + import cn.hutool.json.JSONObject; import com.cool.core.annotation.CoolRestController; import com.cool.core.base.BaseController; +import com.cool.core.request.CrudOption; +import com.cool.core.util.I18nUtil; import com.cool.modules.dict.entity.DictTypeEntity; import com.cool.modules.dict.service.DictTypeService; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; -import static com.cool.modules.dict.entity.table.DictTypeEntityTableDef.DICT_TYPE_ENTITY; - /** * 字典类型 */ @@ -20,6 +22,15 @@ public class AdminDictTypeController extends BaseController { + DictTypeEntity entity = (DictTypeEntity) o; + entity.setName(I18nUtil.getI18nDictType(entity.getName())); + })); + CrudOption transform = createOp().transform(o -> { + DictTypeEntity entity = (DictTypeEntity) o; + entity.setName(I18nUtil.getI18nDictType(entity.getName())); + }); + setPageOption(transform); + setInfoOption(transform); } } diff --git a/src/main/java/com/cool/modules/dict/service/impl/DictInfoServiceImpl.java b/src/main/java/com/cool/modules/dict/service/impl/DictInfoServiceImpl.java index 7235875..51e6506 100644 --- a/src/main/java/com/cool/modules/dict/service/impl/DictInfoServiceImpl.java +++ b/src/main/java/com/cool/modules/dict/service/impl/DictInfoServiceImpl.java @@ -8,7 +8,11 @@ import cn.hutool.core.lang.Dict; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.spring.SpringUtil; +import cn.hutool.json.JSONObject; import com.cool.core.base.BaseServiceImpl; +import com.cool.core.base.ModifyEnum; +import com.cool.core.i18n.I18nGenerator; import com.cool.modules.dict.entity.DictInfoEntity; import com.cool.modules.dict.entity.DictTypeEntity; import com.cool.modules.dict.mapper.DictInfoMapper; @@ -121,4 +125,10 @@ public class DictInfoServiceImpl extends BaseServiceImpl 0; } + + @Override + public void modifyBefore(JSONObject requestParams, DictTypeEntity t, ModifyEnum type) { + if (ModifyEnum.ADD.equals(type) || ModifyEnum.UPDATE.equals(type)) { + SpringUtil.getBean(I18nGenerator.class).asyncGenBaseDictType(); + } + } } \ No newline at end of file diff --git a/src/main/java/com/cool/modules/plugin/controller/admin/AdminPluginInfoController.java b/src/main/java/com/cool/modules/plugin/controller/admin/AdminPluginInfoController.java index 93d28e2..40f20a2 100644 --- a/src/main/java/com/cool/modules/plugin/controller/admin/AdminPluginInfoController.java +++ b/src/main/java/com/cool/modules/plugin/controller/admin/AdminPluginInfoController.java @@ -3,7 +3,9 @@ package com.cool.modules.plugin.controller.admin; import static com.cool.modules.plugin.entity.table.PluginInfoEntityTableDef.PLUGIN_INFO_ENTITY; import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.ObjUtil; import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; import com.cool.core.annotation.CoolRestController; import com.cool.core.annotation.IgnoreRecycleData; import com.cool.core.base.BaseController; @@ -16,6 +18,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; +import java.util.HashMap; import java.util.Map; import lombok.RequiredArgsConstructor; import org.springframework.http.MediaType; @@ -45,6 +48,11 @@ public class AdminPluginInfoController extends BaseController()); + } coolPluginService.updatePlugin(t); return R.ok(); } diff --git a/src/main/java/com/cool/modules/plugin/entity/PluginInfoEntity.java b/src/main/java/com/cool/modules/plugin/entity/PluginInfoEntity.java index 1905432..f38da3e 100644 --- a/src/main/java/com/cool/modules/plugin/entity/PluginInfoEntity.java +++ b/src/main/java/com/cool/modules/plugin/entity/PluginInfoEntity.java @@ -8,7 +8,6 @@ import com.mybatisflex.core.handler.Fastjson2TypeHandler; import com.mybatisflex.core.handler.JacksonTypeHandler; import com.tangzc.mybatisflex.autotable.annotation.ColumnDefine; import com.tangzc.mybatisflex.autotable.annotation.UniIndex; -import java.util.Map; import lombok.Getter; import lombok.Setter; import org.dromara.autotable.annotation.Ignore; @@ -54,7 +53,7 @@ public class PluginInfoEntity extends BaseEntity { @ColumnDefine(comment = "配置", type = "json") @Column(typeHandler = JacksonTypeHandler.class) - private Map config; + private Object config; @Ignore @Column(ignore = true)