upgrade:国际化多语言

This commit is contained in:
ruying408
2025-04-12 23:26:42 +08:00
parent f9767320d6
commit bf8c6d711e
24 changed files with 584 additions and 202 deletions

19
pom.xml
View File

@@ -190,25 +190,6 @@
</annotationProcessorPaths>
</configuration>
</plugin>
<!-- 编译执行 Java 脚本 -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>run-code-generator</id>
<phase>compile</phase>
<goals>
<goal>java</goal>
</goals>
<configuration>
<mainClass>com.cool.core.plugin.I18nGenerator</mainClass>
<classpathScope>compile</classpathScope>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

View File

@@ -51,9 +51,11 @@ public abstract class BaseController<S extends BaseService<T>, 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<CrudOption<T>> pageOption = new ThreadLocal<>();
private final ThreadLocal<CrudOption<T>> listOption = new ThreadLocal<>();
private final ThreadLocal<CrudOption<T>> infoOption = new ThreadLocal<>();
private final ThreadLocal<JSONObject> requestParams = new ThreadLocal<>();
@ModelAttribute
@@ -61,16 +63,20 @@ public abstract class BaseController<S extends BaseService<T>, 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<S extends BaseService<T>, T extends BaseEnt
return new CrudOption<>(this.requestParams.get());
}
public void setInfoOption(CrudOption<T> infoOption) {
this.infoOption.set(infoOption);
}
public void setListOption(CrudOption<T> listOption) {
this.listOption.set(listOption);
}
@@ -153,8 +163,11 @@ public abstract class BaseController<S extends BaseService<T>, T extends BaseEnt
@Operation(summary = "信息", description = "根据ID查询单个信息")
@GetMapping("/info")
protected R<T> info(@RequestAttribute() JSONObject requestParams,
@RequestParam() Long id) {
return R.ok((T) service.info(requestParams, id));
@RequestParam() Long id,
@RequestAttribute(COOL_INFO_OP) CrudOption<T> option) {
T info = (T) service.info(requestParams, id);
invokerTransform(option, info);
return R.ok(info);
}
/**
@@ -219,7 +232,7 @@ public abstract class BaseController<S extends BaseService<T>, T extends BaseEnt
* @param page 分页返回数据
*/
protected PageResult<T> pageResult(Page<T> page) {
return PageResult.of( page );
return PageResult.of(page);
}
public Class<T> currentEntityClass() {

View File

@@ -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("{}");

View File

@@ -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<String> languages;
private static final Duration DURATION = Duration.ofSeconds(30);
public void run(Map<String, Object> map) {
log.info("国际化 翻译...");
languages = (List<String>) 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<Void> futureMsg = CompletableFuture.runAsync(this::genBaseMsg);
CompletableFuture<Void> futureMenu = CompletableFuture.runAsync(this::genBaseMenu);
CompletableFuture<Void> futureDictInfo = CompletableFuture.runAsync(this::genBaseDictInfo);
CompletableFuture<Void> futureDictType = CompletableFuture.runAsync(this::genBaseDictType);
// 等待全部执行完成
CompletableFuture.allOf(futureMsg, futureMenu, futureDictInfo, futureDictType).join();
}
private void genBaseMsg() {
try {
Map<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> processFile(Path path) {
Map<String, String> 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("//.*", ""); // 单行注释
}
}

View File

@@ -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<String, String> map = new HashMap<>();
List<String> 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<String> 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<String, String> 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<String, String> map, String language) {
if (map.isEmpty()) {
return new JSONObject();
}
Map<String, Object> 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<String, String> processFile(Path path) {
Map<String, String> 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("//.*", ""); // 单行注释
}
}

View File

@@ -4,7 +4,10 @@ package com.cool.core.plugin.consts;
* 常量工具
*/
public interface PluginConsts {
/**
* 国际化插件
*/
String i18n = "i18n";
/**
* 上传文件hook
*/

View File

@@ -0,0 +1,7 @@
package com.cool.core.plugin.event;
public enum PluginActionEnum {
INSTALL,
UNINSTALL,
UPDATE,
}

View File

@@ -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);
}
}

View File

@@ -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<PluginEvent> {
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<String, Object>) pluginInfoEntity.getConfig());
} else if (ObjUtil.equals(actionEnum, PluginActionEnum.UPDATE)) {
if (ObjUtil.equals(pluginInfoEntity.getStatus(), 1)) {
// 更新插件配置
i18nGenerator.run((Map<String, Object>) pluginInfoEntity.getConfig());
} else {
// 停用
I18nUtil.enable = false;
}
} else if (ObjUtil.equals(actionEnum, PluginActionEnum.UNINSTALL)) {
// 卸载国际化插件,则删除国际化文件
i18nUtil.clear();
}
}
}
}

View File

@@ -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);
}
}

View File

@@ -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<PluginInfoEntity> 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<String, Object>) 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<String, Object>) entity.getConfig(),
(Map<String, Object>) dbPluginInfoEntity.getConfig())) {
// 不一致,说明更新了配置
entity.setPluginJson(dbPluginInfoEntity.getPluginJson());
entity.getPluginJson().setConfig(entity.getConfig());
entity.getPluginJson().setConfig((Map<String, Object>) 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()));
}
/**

View File

@@ -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);
}
}

View File

@@ -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<String, JSONObject> data = new ConcurrentHashMap<>();
@PostConstruct
public void init() {
if (!enable) {
return;
}
try {
// 获取该目录下所有的 .json 文件
List<File> 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<File> jsonFiles = FileUtil.loopFiles(getPath(), file ->
file.isFile() && file.getName().endsWith(".json")
);
AtomicReference<Boolean> 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<File> jsonFiles = FileUtil.loopFiles(getPath(), file ->
file.isFile() && file.getName().endsWith(".json")
);
jsonFiles.forEach(File::delete);
enable = false;
}
}

View File

@@ -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<BaseSysMenuEntity> list = (List<BaseSysMenuEntity>) permmenu.getObj("menus");
list.forEach(o -> o.setName(I18nUtil.getI18nMenu(o.getName())));
return R.ok(permmenu);
}
@Operation(summary = "文件上传")

View File

@@ -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<BaseSysMenuEntity> transform = createOp()
.transform(o -> {
BaseSysMenuEntity entity = (BaseSysMenuEntity) o;
entity.setName(I18nUtil.getI18nMenu(entity.getName()));
});
setPageOption(transform);
setListOption(transform);
setInfoOption(transform);
}
@Operation(summary = "创建代码", description = "创建代码")

View File

@@ -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);
/**
* 更新角色权限

View File

@@ -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<BaseSysMenuMapper, B
baseSysPermsService.refreshPermsByMenuId(id);
}
}
if (ModifyEnum.ADD.equals(type) || ModifyEnum.UPDATE.equals(type)) {
SpringUtil.getBean(I18nGenerator.class).asyncGenBaseMenu();
}
}
@Override
@@ -126,6 +131,7 @@ public class BaseSysMenuServiceImpl extends BaseServiceImpl<BaseSysMenuMapper, B
@Override
public boolean importMenu(List<BaseSysMenuEntity> menus) {
menus.forEach(this::importMenu);
SpringUtil.getBean(I18nGenerator.class).asyncGenBaseMenu();
return true;
}

View File

@@ -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));
}

View File

@@ -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<DictInfoService, Dic
@Override
protected void init(HttpServletRequest request, JSONObject requestParams) {
setListOption(createOp().fieldEq(DictInfoEntityTableDef.DICT_INFO_ENTITY.TYPE_ID)
.keyWordLikeFields(DictInfoEntityTableDef.DICT_INFO_ENTITY.NAME)
.queryWrapper(QueryWrapper.create().orderBy(DictInfoEntityTableDef.DICT_INFO_ENTITY.CREATE_TIME, false)));
.keyWordLikeFields(DictInfoEntityTableDef.DICT_INFO_ENTITY.NAME)
.queryWrapper(QueryWrapper.create().orderBy(DictInfoEntityTableDef.DICT_INFO_ENTITY.CREATE_TIME, false))
.transform(o -> {
DictInfoEntity entity = (DictInfoEntity) o;
entity.setName(I18nUtil.getI18nDictInfo(entity.getName()));
}));
CrudOption<DictInfoEntity> transform = createOp().transform(o -> {
DictInfoEntity entity = (DictInfoEntity) o;
entity.setName(I18nUtil.getI18nDictInfo(entity.getName()));
});
setPageOption(transform);
setInfoOption(transform);
}
@Operation(summary = "获得字典数据", description = "获得字典数据信息")

View File

@@ -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<DictTypeService, Dic
@Override
protected void init(HttpServletRequest request, JSONObject requestParams) {
setPageOption(
createOp().select(DICT_TYPE_ENTITY.ID, DICT_TYPE_ENTITY.KEY, DICT_TYPE_ENTITY.NAME));
createOp().select(DICT_TYPE_ENTITY.ID, DICT_TYPE_ENTITY.KEY, DICT_TYPE_ENTITY.NAME).transform(o -> {
DictTypeEntity entity = (DictTypeEntity) o;
entity.setName(I18nUtil.getI18nDictType(entity.getName()));
}));
CrudOption<DictTypeEntity> transform = createOp().transform(o -> {
DictTypeEntity entity = (DictTypeEntity) o;
entity.setName(I18nUtil.getI18nDictType(entity.getName()));
});
setPageOption(transform);
setInfoOption(transform);
}
}

View File

@@ -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<DictInfoMapper, DictInf
}
}
}
@Override
public void modifyAfter(JSONObject requestParams, DictInfoEntity entity, ModifyEnum type) {
if (ModifyEnum.ADD.equals(type) || ModifyEnum.UPDATE.equals(type)) {
SpringUtil.getBean(I18nGenerator.class).asyncGenBaseDictInfo();
}
}
}

View File

@@ -3,7 +3,11 @@ package com.cool.modules.dict.service.impl;
import static com.cool.modules.dict.entity.table.DictInfoEntityTableDef.DICT_INFO_ENTITY;
import static com.cool.modules.dict.entity.table.DictTypeEntityTableDef.DICT_TYPE_ENTITY;
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.DictTypeEntity;
import com.cool.modules.dict.mapper.DictInfoMapper;
import com.cool.modules.dict.mapper.DictTypeMapper;
@@ -36,4 +40,11 @@ public class DictTypeServiceImpl extends BaseServiceImpl<DictTypeMapper, DictTyp
return dictInfoMapper.deleteByQuery(
QueryWrapper.create().and(DICT_INFO_ENTITY.TYPE_ID.in((Object) ids))) > 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();
}
}
}

View File

@@ -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<PluginInfoService,
@PostMapping("/update")
protected R update(@RequestBody PluginInfoEntity t,
@RequestAttribute() JSONObject requestParams) {
if (ObjUtil.isNotEmpty(t.getConfig())) {
t.setConfig(JSONUtil.parseObj(t.getConfig()));
} else {
t.setConfig(new HashMap<>());
}
coolPluginService.updatePlugin(t);
return R.ok();
}

View File

@@ -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<PluginInfoEntity> {
@ColumnDefine(comment = "配置", type = "json")
@Column(typeHandler = JacksonTypeHandler.class)
private Map<String, Object> config;
private Object config;
@Ignore
@Column(ignore = true)