mirror of
http://192.168.1.205:9980/cf_devdept2/cf_imes_server.git
synced 2026-08-13 21:32:07 +08:00
1、新增菜单翻译接口、获取菜单接口新增菜单名国际化能力;2、新增字典数据标签翻译接口、字典获取接口修改为一次性返回所有语言数据、新增字典数据国际化版本查询/更新接口、新增字典数据国际化缓存;
This commit is contained in:
+5
@@ -22,4 +22,9 @@ public class RedisKeyConstants {
|
||||
* 软件购买
|
||||
*/
|
||||
public static final String PRODUCT_PURCHASE_LOCK_KEY = "product_purchase:%s%s";
|
||||
|
||||
/**
|
||||
* 字典数据i18n缓存刷新
|
||||
*/
|
||||
public static final String DICT_DATA_I18N_CACHE_REFRESH = "DICT_DATA_I18N_CACHE_REFRESH";
|
||||
}
|
||||
|
||||
+2
-7
@@ -26,6 +26,7 @@ public class ErrorCodeConstants {
|
||||
public static final ErrorCode MENU_EXISTS_CHILDREN = new ErrorCode(1_002_001_004, "存在子菜单,无法删除");
|
||||
public static final ErrorCode MENU_PARENT_NOT_DIR_OR_MENU = new ErrorCode(1_002_001_005, "父菜单的类型必须是目录或者菜单");
|
||||
public static final ErrorCode MANAGEMENT_MENU_OPERATION_PERMISSION_ERROR = new ErrorCode(1_002_001_006, "管理端菜单操作权限不足");
|
||||
public static final ErrorCode MENU_TRANS_ERROR = new ErrorCode(1_002_001_007, "菜单翻译异常,error_code :{}");
|
||||
|
||||
// ========== 角色模块 1-002-002-000 ==========
|
||||
public static final ErrorCode ROLE_NOT_EXISTS = new ErrorCode(1_002_002_000, "角色不存在");
|
||||
@@ -75,13 +76,6 @@ public class ErrorCodeConstants {
|
||||
public static final ErrorCode DEPT_NOT_ALLOWED_LOGIN = new ErrorCode(1_002_004_011, "部门({})已被禁用,无法登录");
|
||||
public static final ErrorCode PARENT_DEPT_DISABLE = new ErrorCode(1_002_004_012, "上级部门({})已被禁用,请开启后操作或选择其他上级部门");
|
||||
|
||||
|
||||
// ========== 岗位模块 1-002-005-000 ==========
|
||||
public static final ErrorCode POST_NOT_FOUND = new ErrorCode(1_002_005_000, "当前岗位不存在");
|
||||
public static final ErrorCode POST_NOT_ENABLE = new ErrorCode(1_002_005_001, "岗位({}) 不处于开启状态,不允许选择");
|
||||
public static final ErrorCode POST_NAME_DUPLICATE = new ErrorCode(1_002_005_002, "已经存在该名字的岗位");
|
||||
public static final ErrorCode POST_CODE_DUPLICATE = new ErrorCode(1_002_005_003, "已经存在该编码的岗位");
|
||||
|
||||
// ========== 字典类型 1-002-006-000 ==========
|
||||
public static final ErrorCode DICT_TYPE_NOT_EXISTS = new ErrorCode(1_002_006_001, "当前字典类型不存在");
|
||||
public static final ErrorCode DICT_TYPE_NOT_ENABLE = new ErrorCode(1_002_006_002, "字典类型处于关闭状态,不允许新增或编辑");
|
||||
@@ -93,6 +87,7 @@ public class ErrorCodeConstants {
|
||||
public static final ErrorCode DICT_DATA_NOT_EXISTS = new ErrorCode(1_002_007_001, "当前字典数据不存在");
|
||||
public static final ErrorCode DICT_DATA_NOT_ENABLE = new ErrorCode(1_002_007_002, "字典数据({})不处于开启状态,不允许选择");
|
||||
public static final ErrorCode DICT_DATA_VALUE_DUPLICATE = new ErrorCode(1_002_007_003, "已经存在该值的字典数据");
|
||||
public static final ErrorCode DICT_DATA_TRANS_ERROR = new ErrorCode(1_002_007_004, "字典数据标签翻译异常,error_code :{}");
|
||||
|
||||
// ========== 通知公告 1-002-008-000 ==========
|
||||
public static final ErrorCode NOTICE_NOT_FOUND = new ErrorCode(1_002_008_001, "当前通知公告不存在");
|
||||
|
||||
+23
-5
@@ -1,6 +1,5 @@
|
||||
package com.cf.imes.module.system.controller.admin.dict;
|
||||
|
||||
import com.cf.imes.framework.common.enums.CommonStatusEnum;
|
||||
import com.cf.imes.framework.common.pojo.CommonResult;
|
||||
import com.cf.imes.framework.common.pojo.PageParam;
|
||||
import com.cf.imes.framework.common.pojo.PageResult;
|
||||
@@ -11,6 +10,7 @@ import com.cf.imes.module.system.controller.admin.dict.vo.data.DictDataPageReqVO
|
||||
import com.cf.imes.module.system.controller.admin.dict.vo.data.DictDataRespVO;
|
||||
import com.cf.imes.module.system.controller.admin.dict.vo.data.DictDataSaveReqVO;
|
||||
import com.cf.imes.module.system.controller.admin.dict.vo.data.DictDataSimpleRespVO;
|
||||
import com.cf.imes.module.system.controller.admin.dict.vo.data.DictDataTransReqVO;
|
||||
import com.cf.imes.module.system.dal.dataobject.dict.DictDataDO;
|
||||
import com.cf.imes.module.system.service.dict.DictDataService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
@@ -25,6 +25,7 @@ import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.Valid;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.cf.imes.framework.common.pojo.CommonResult.success;
|
||||
import static com.cf.imes.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
|
||||
@@ -66,10 +67,8 @@ public class DictDataController {
|
||||
@GetMapping(value = {"/list-all-simple", "simple-list"})
|
||||
@Operation(summary = "获得全部字典数据列表", description = "一般用于管理后台缓存字典数据在本地")
|
||||
// 无需添加权限认证,因为前端全局都需要
|
||||
public CommonResult<List<DictDataSimpleRespVO>> getSimpleDictDataList() {
|
||||
List<DictDataDO> list = dictDataService.getDictDataList(
|
||||
CommonStatusEnum.ENABLE.getStatus(), null);
|
||||
return success(BeanUtils.toBean(list, DictDataSimpleRespVO.class));
|
||||
public CommonResult<Map<String, List<DictDataSimpleRespVO>>> getSimpleDictDataList() {
|
||||
return success(dictDataService.getAllLanguageDictData());
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@@ -101,4 +100,23 @@ public class DictDataController {
|
||||
BeanUtils.toBean(list, DictDataRespVO.class));
|
||||
}
|
||||
|
||||
@PostMapping("/trans")
|
||||
@Operation(summary = "字典数据标签对应语种翻译")
|
||||
public CommonResult<Boolean> trans(@RequestBody DictDataTransReqVO reqVO){
|
||||
dictDataService.trans(reqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/i18n/version")
|
||||
@Operation(summary = "获取字典数据国际化版本号")
|
||||
public CommonResult<Integer> getVersion() {
|
||||
return success(dictDataService.getI18nVersion());
|
||||
}
|
||||
|
||||
@PostMapping("/i18n/version")
|
||||
@Operation(summary = "更新字典数据国际化版本号")
|
||||
public CommonResult<Boolean> updateVersion() {
|
||||
dictDataService.updateI18nVersion();
|
||||
return success(true);
|
||||
}
|
||||
}
|
||||
|
||||
+2
@@ -1,10 +1,12 @@
|
||||
package com.cf.imes.module.system.controller.admin.dict.vo.data;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - 数据字典精简 Response VO")
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public class DictDataSimpleRespVO {
|
||||
|
||||
@Schema(description = "字典类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "gender")
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package com.cf.imes.module.system.controller.admin.dict.vo.data;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Gqr
|
||||
* @since 2025/11/11 10:56
|
||||
*/
|
||||
@Schema(description = "管理后台 - 字典数据标签 Request VO")
|
||||
@Data
|
||||
public class DictDataTransReqVO {
|
||||
@Schema(description = "目标语种", example = "en")
|
||||
@NotNull(message = "目标语种不能为空")
|
||||
private String targetLanguage;
|
||||
}
|
||||
+7
@@ -11,6 +11,7 @@ import com.cf.imes.module.system.controller.admin.permission.vo.menu.MenuListReq
|
||||
import com.cf.imes.module.system.controller.admin.permission.vo.menu.MenuRespVO;
|
||||
import com.cf.imes.module.system.controller.admin.permission.vo.menu.MenuSaveVO;
|
||||
import com.cf.imes.module.system.controller.admin.permission.vo.menu.MenuSimpleRespVO;
|
||||
import com.cf.imes.module.system.controller.admin.permission.vo.menu.MenuTransReqVO;
|
||||
import com.cf.imes.module.system.dal.dataobject.permission.MenuDO;
|
||||
import com.cf.imes.module.system.dal.dataobject.permission.RoleDO;
|
||||
import com.cf.imes.module.system.dal.dataobject.user.AdminUserDO;
|
||||
@@ -159,4 +160,10 @@ public class MenuController {
|
||||
return success(BeanUtils.toBean(menu, MenuRespVO.class));
|
||||
}
|
||||
|
||||
@PostMapping("/trans")
|
||||
@Operation(summary = "菜单对应语种翻译")
|
||||
public CommonResult<Boolean> trans(@RequestBody MenuTransReqVO reqVO) {
|
||||
menuService.trans(reqVO);
|
||||
return success(true);
|
||||
}
|
||||
}
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package com.cf.imes.module.system.controller.admin.permission.vo.menu;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Gqr
|
||||
* @since 2025/11/11 10:56
|
||||
*/
|
||||
@Schema(description = "管理后台 - 菜单翻译 Request VO")
|
||||
@Data
|
||||
public class MenuTransReqVO {
|
||||
@Schema(description = "目标语种", example = "en")
|
||||
@NotNull(message = "目标语种不能为空")
|
||||
private String targetLanguage;
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package com.cf.imes.module.system.dal.dataobject.dict;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 字典数据标签国际化表
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@TableName("system_dict_data_i18n")
|
||||
@KeySequence("system_dict_data_i18n_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||
@Data
|
||||
public class DictDatai18nDO {
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 字典数据主键
|
||||
*/
|
||||
private Long dictDataId;
|
||||
|
||||
/**
|
||||
* 语种
|
||||
*/
|
||||
private String locale;
|
||||
|
||||
/**
|
||||
* 字典标签
|
||||
*/
|
||||
private String label;
|
||||
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package com.cf.imes.module.system.dal.dataobject.dict;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 字典数据标签国际化版本号表
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@TableName("system_dict_data_i18n_version")
|
||||
@KeySequence("system_dict_data_i18n_version_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||
@Data
|
||||
public class DictDatai18nVersionDO {
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
|
||||
private Integer version;
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package com.cf.imes.module.system.dal.dataobject.permission;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 菜单国际化DO
|
||||
* @author Gqr
|
||||
* @since 2025/11/11 11:08
|
||||
*/
|
||||
@TableName("system_menu_i18n")
|
||||
@KeySequence("system_menu_i18n_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||
@Data
|
||||
public class MenuI18nDO {
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 菜单id
|
||||
*/
|
||||
private Long menuId;
|
||||
|
||||
/**
|
||||
* 语种
|
||||
*/
|
||||
private String locale;
|
||||
|
||||
/**
|
||||
* 菜单名称
|
||||
*/
|
||||
private String menuName;
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package com.cf.imes.module.system.dal.mysql.dict;
|
||||
|
||||
import com.cf.imes.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import com.cf.imes.module.system.dal.dataobject.dict.DictDataDO;
|
||||
import com.cf.imes.module.system.dal.dataobject.dict.DictDatai18nDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 字典数据标签国际化 Mapper
|
||||
*
|
||||
* @author Gqr
|
||||
* @since 2025/11/11 17:10
|
||||
*/
|
||||
@Mapper
|
||||
public interface DictDataI18nMapper extends BaseMapperX<DictDatai18nDO> {
|
||||
/**
|
||||
* 查询指定语种下的字典数据
|
||||
*
|
||||
* @param ids
|
||||
* @param locale
|
||||
* @return
|
||||
*/
|
||||
List<DictDataDO> getLocaleDictData(@Param("ids") Collection<Long> ids, @Param("locale") String locale);
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package com.cf.imes.module.system.dal.mysql.dict;
|
||||
|
||||
import com.cf.imes.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import com.cf.imes.module.system.dal.dataobject.dict.DictDatai18nVersionDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 字典数据标签国际化版本号 Mapper
|
||||
*
|
||||
* @author Gqr
|
||||
* @since 2025/11/11 17:10
|
||||
*/
|
||||
@Mapper
|
||||
public interface DictDataI18nVersionMapper extends BaseMapperX<DictDatai18nVersionDO> {
|
||||
/**
|
||||
* 更新版本号
|
||||
*/
|
||||
void updateVersion();
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package com.cf.imes.module.system.dal.mysql.permission;
|
||||
|
||||
import com.cf.imes.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import com.cf.imes.module.system.dal.dataobject.permission.MenuI18nDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 菜单国际化 Mapper
|
||||
* @author Gqr
|
||||
* @since 2025/11/11 11:10
|
||||
*/
|
||||
@Mapper
|
||||
public interface MenuI18nMapper extends BaseMapperX<MenuI18nDO> {
|
||||
}
|
||||
+11
@@ -5,7 +5,9 @@ import com.cf.imes.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import com.cf.imes.module.system.controller.admin.permission.vo.menu.MenuListReqVO;
|
||||
import com.cf.imes.module.system.dal.dataobject.permission.MenuDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
@@ -30,4 +32,13 @@ public interface MenuMapper extends BaseMapperX<MenuDO> {
|
||||
default List<MenuDO> selectListByPermission(String permission) {
|
||||
return selectList(MenuDO::getPermission, permission);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询对应范围的菜单
|
||||
*
|
||||
* @param ids 菜单编号集合
|
||||
* @param isManage 是否是管理后台
|
||||
* @return
|
||||
*/
|
||||
List<MenuDO> selectListIn(@Param("ids") Collection<Long> ids, @Param("isManage") boolean isManage, @Param("language") String language);
|
||||
}
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package com.cf.imes.module.system.dal.redis.listener;
|
||||
|
||||
import com.cf.imes.framework.mq.redis.core.pubsub.AbstractRedisSimpleMessageListener;
|
||||
import com.cf.imes.framework.redis.constants.RedisKeyConstants;
|
||||
import com.cf.imes.module.system.util.locale.DictDataI18nCacheUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.connection.Message;
|
||||
import org.springframework.data.redis.listener.ChannelTopic;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* system DICT_DATA_I18N_CACHE_REFRESH redis监听器
|
||||
*
|
||||
* @author Gqr
|
||||
* @since 2024/6/13 10:32
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class SystemDictDataI18nCacheRefreshRedisListener extends AbstractRedisSimpleMessageListener {
|
||||
|
||||
|
||||
public SystemDictDataI18nCacheRefreshRedisListener() {
|
||||
super.setTopic(new ChannelTopic(RedisKeyConstants.DICT_DATA_I18N_CACHE_REFRESH));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(Message message, byte[] bytes) {
|
||||
log.info("[SystemDictDataI18nCacheRefreshRedisListener][onMessage][{}收到刷新本地字典数据国际化缓存]", message);
|
||||
DictDataI18nCacheUtils.clearCache();
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package com.cf.imes.module.system.enums.common;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 语种枚举
|
||||
*
|
||||
* @author Gqr
|
||||
* @since 2025/11/12 11:30
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum LanguageEnum {
|
||||
ZH("zh"),
|
||||
EN("en"),
|
||||
CHT("cht");
|
||||
|
||||
private String language;
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
package com.cf.imes.module.system.framework.trans.config;
|
||||
|
||||
import cn.hutool.core.text.UnicodeUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.cf.imes.module.system.framework.trans.core.HttpGet;
|
||||
import com.cf.imes.module.system.framework.trans.core.MD5;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Gqr
|
||||
* @since 2025/11/11 9:34
|
||||
*/
|
||||
public class TransApi {
|
||||
|
||||
private String appid;
|
||||
private String securityKey;
|
||||
private String apiHost;
|
||||
|
||||
public TransApi(String appid, String securityKey, String apiHost) {
|
||||
this.appid = appid;
|
||||
this.securityKey = securityKey;
|
||||
this.apiHost = apiHost;
|
||||
}
|
||||
|
||||
public String getTransResult(String query, String from, String to) {
|
||||
Map<String, String> params = buildParams(query, from, to);
|
||||
return HttpGet.get(apiHost, params);
|
||||
}
|
||||
|
||||
private Map<String, String> buildParams(String query, String from, String to) {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("q", query);
|
||||
params.put("from", from);
|
||||
params.put("to", to);
|
||||
|
||||
params.put("appid", appid);
|
||||
|
||||
// 随机数
|
||||
String salt = String.valueOf(System.currentTimeMillis());
|
||||
params.put("salt", salt);
|
||||
|
||||
// 签名
|
||||
String src = appid + query + salt + securityKey; // 加密前的原文
|
||||
params.put("sign", MD5.md5(src));
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
TransApi api = new TransApi("20251110002494620", "_CUXY5Hk4WLBnYRQBcWw","https://fanyi-api.baidu.com/api/trans/vip/translate");
|
||||
String query = "测试翻译机\n" +
|
||||
"语言是什么";
|
||||
String json = api.getTransResult(query, "auto", "cht");
|
||||
|
||||
JSONObject obj = JSON.parseObject(json);
|
||||
JSONArray arr = obj.getJSONArray("trans_result");
|
||||
|
||||
for (int i = 0; i < arr.size(); i++) {
|
||||
JSONObject item = arr.getJSONObject(i);
|
||||
String src = item.getString("src");
|
||||
String dst = item.getString("dst");
|
||||
System.out.println(src + " => " + UnicodeUtil.toString(dst));
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package com.cf.imes.module.system.framework.trans.config;
|
||||
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableConfigurationProperties(TransProperties.class)
|
||||
public class TransConfiguration {
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package com.cf.imes.module.system.framework.trans.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* 翻译api配置
|
||||
*
|
||||
* @author Gqr
|
||||
* @since 2025/11/11 9:23
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "trans.config")
|
||||
@Data
|
||||
public class TransProperties {
|
||||
private String appId;
|
||||
|
||||
private String securityKey;
|
||||
|
||||
private String apiHost;
|
||||
}
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
package com.cf.imes.module.system.framework.trans.core;
|
||||
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @author Gqr
|
||||
* @since 2025/11/11 9:37
|
||||
*/
|
||||
public class HttpGet {
|
||||
protected static final int SOCKET_TIMEOUT = 10000; // 10S
|
||||
protected static final String GET = "GET";
|
||||
|
||||
public static String get(String host, Map<String, String> params) {
|
||||
try {
|
||||
// 设置SSLContext
|
||||
SSLContext sslcontext = SSLContext.getInstance("TLS");
|
||||
sslcontext.init(null, null, null);
|
||||
|
||||
String sendUrl = getUrlWithQueryString(host, params);
|
||||
|
||||
|
||||
URL uri = new URL(sendUrl); // 创建URL对象
|
||||
HttpURLConnection conn = (HttpURLConnection) uri.openConnection();
|
||||
if (conn instanceof HttpsURLConnection) {
|
||||
((HttpsURLConnection) conn).setSSLSocketFactory(sslcontext.getSocketFactory());
|
||||
}
|
||||
|
||||
conn.setConnectTimeout(SOCKET_TIMEOUT); // 设置相应超时
|
||||
conn.setRequestMethod(GET);
|
||||
int statusCode = conn.getResponseCode();
|
||||
if (statusCode != HttpURLConnection.HTTP_OK) {
|
||||
System.out.println("Http错误码:" + statusCode);
|
||||
}
|
||||
|
||||
// 读取服务器的数据
|
||||
InputStream is = conn.getInputStream();
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(is));
|
||||
StringBuilder builder = new StringBuilder();
|
||||
String line = null;
|
||||
while ((line = br.readLine()) != null) {
|
||||
builder.append(line);
|
||||
}
|
||||
|
||||
String text = builder.toString();
|
||||
|
||||
close(br); // 关闭数据流
|
||||
close(is); // 关闭数据流
|
||||
conn.disconnect(); // 断开连接
|
||||
|
||||
return text;
|
||||
} catch (MalformedURLException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} catch (KeyManagementException e) {
|
||||
e.printStackTrace();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String getUrlWithQueryString(String url, Map<String, String> params) {
|
||||
if (params == null) {
|
||||
return url;
|
||||
}
|
||||
|
||||
StringBuilder builder = new StringBuilder(url);
|
||||
if (url.contains("?")) {
|
||||
builder.append("&");
|
||||
} else {
|
||||
builder.append("?");
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
for (String key : params.keySet()) {
|
||||
String value = params.get(key);
|
||||
if (value == null) { // 过滤空的key
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i != 0) {
|
||||
builder.append('&');
|
||||
}
|
||||
|
||||
builder.append(key);
|
||||
builder.append('=');
|
||||
builder.append(encode(value));
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
protected static void close(Closeable closeable) {
|
||||
if (closeable != null) {
|
||||
try {
|
||||
closeable.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 对输入的字符串进行URL编码, 即转换为%20这种形式
|
||||
*
|
||||
* @param input 原文
|
||||
* @return URL编码. 如果编码失败, 则返回原文
|
||||
*/
|
||||
public static String encode(String input) {
|
||||
if (input == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
try {
|
||||
return URLEncoder.encode(input, "utf-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
package com.cf.imes.module.system.framework.trans.core;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @author Gqr
|
||||
* @since 2025/11/11 9:36
|
||||
*/
|
||||
public class MD5 {
|
||||
// 首先初始化一个字符数组,用来存放每个16进制字符
|
||||
private static final char[] hexDigits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd',
|
||||
'e', 'f' };
|
||||
|
||||
/**
|
||||
* 获得一个字符串的MD5值
|
||||
*
|
||||
* @param input 输入的字符串
|
||||
* @return 输入字符串的MD5值
|
||||
*
|
||||
*/
|
||||
public static String md5(String input) {
|
||||
if (input == null)
|
||||
return null;
|
||||
|
||||
try {
|
||||
try {
|
||||
|
||||
// 拿到一个MD5转换器(如果想要SHA1参数换成”SHA1”)
|
||||
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
|
||||
// 输入的字符串转换成字节数组
|
||||
byte[] inputByteArray = input.getBytes("utf-8");
|
||||
// inputByteArray是输入字符串转换得到的字节数组
|
||||
messageDigest.update(inputByteArray);
|
||||
// 转换并返回结果,也是字节数组,包含16个元素
|
||||
byte[] resultByteArray = messageDigest.digest();
|
||||
// 字符数组转换成字符串返回
|
||||
return byteArrayToHex(resultByteArray);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
return null;
|
||||
}
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件的MD5值
|
||||
*
|
||||
* @param file
|
||||
* @return
|
||||
*/
|
||||
public static String md5(File file) {
|
||||
try {
|
||||
if (!file.isFile()) {
|
||||
System.err.println("文件" + file.getAbsolutePath() + "不存在或者不是文件");
|
||||
return null;
|
||||
}
|
||||
|
||||
FileInputStream in = new FileInputStream(file);
|
||||
|
||||
String result = md5(in);
|
||||
|
||||
in.close();
|
||||
|
||||
return result;
|
||||
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String md5(InputStream in) {
|
||||
|
||||
try {
|
||||
MessageDigest messagedigest = MessageDigest.getInstance("MD5");
|
||||
|
||||
byte[] buffer = new byte[1024];
|
||||
int read = 0;
|
||||
while ((read = in.read(buffer)) != -1) {
|
||||
messagedigest.update(buffer, 0, read);
|
||||
}
|
||||
|
||||
in.close();
|
||||
|
||||
String result = byteArrayToHex(messagedigest.digest());
|
||||
|
||||
return result;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
e.printStackTrace();
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String byteArrayToHex(byte[] byteArray) {
|
||||
// new一个字符数组,这个就是用来组成结果字符串的(解释一下:一个byte是八位二进制,也就是2位十六进制字符(2的8次方等于16的2次方))
|
||||
char[] resultCharArray = new char[byteArray.length * 2];
|
||||
// 遍历字节数组,通过位运算(位运算效率高),转换成字符放到字符数组中去
|
||||
int index = 0;
|
||||
for (byte b : byteArray) {
|
||||
resultCharArray[index++] = hexDigits[b >>> 4 & 0xf];
|
||||
resultCharArray[index++] = hexDigits[b & 0xf];
|
||||
}
|
||||
|
||||
// 字符数组组合成字符串返回
|
||||
return new String(resultCharArray);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* 属于 system 模块的 framework.trans 封装
|
||||
*
|
||||
* @author 晨丰科技
|
||||
*/
|
||||
package com.cf.imes.module.system.framework.trans;
|
||||
+28
@@ -3,11 +3,14 @@ package com.cf.imes.module.system.service.dict;
|
||||
import com.cf.imes.framework.common.pojo.PageResult;
|
||||
import com.cf.imes.module.system.controller.admin.dict.vo.data.DictDataPageReqVO;
|
||||
import com.cf.imes.module.system.controller.admin.dict.vo.data.DictDataSaveReqVO;
|
||||
import com.cf.imes.module.system.controller.admin.dict.vo.data.DictDataSimpleRespVO;
|
||||
import com.cf.imes.module.system.controller.admin.dict.vo.data.DictDataTransReqVO;
|
||||
import com.cf.imes.module.system.dal.dataobject.dict.DictDataDO;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 字典数据 Service 接口
|
||||
@@ -99,4 +102,29 @@ public interface DictDataService {
|
||||
*/
|
||||
DictDataDO parseDictData(String dictType, String label);
|
||||
|
||||
/**
|
||||
* 字典数据标签对应语种翻译
|
||||
*
|
||||
* @param reqVO
|
||||
*/
|
||||
void trans(DictDataTransReqVO reqVO);
|
||||
|
||||
/**
|
||||
* 获取字典数据国际化版本号
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Integer getI18nVersion();
|
||||
|
||||
/**
|
||||
* 更新字典数据国际化版本号
|
||||
*/
|
||||
void updateI18nVersion();
|
||||
|
||||
/**
|
||||
* 获取所有语种的字典数据
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Map<String, List<DictDataSimpleRespVO>> getAllLanguageDictData();
|
||||
}
|
||||
|
||||
+150
-1
@@ -1,6 +1,12 @@
|
||||
package com.cf.imes.module.system.service.dict;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.text.UnicodeUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.cf.imes.framework.common.enums.CommonStatusEnum;
|
||||
import com.cf.imes.framework.common.exception.util.ServiceExceptionUtil;
|
||||
import com.cf.imes.framework.common.pojo.PageResult;
|
||||
@@ -8,21 +14,41 @@ import com.cf.imes.framework.common.util.collection.CollectionUtils;
|
||||
import com.cf.imes.framework.common.util.object.BeanUtils;
|
||||
import com.cf.imes.module.system.controller.admin.dict.vo.data.DictDataPageReqVO;
|
||||
import com.cf.imes.module.system.controller.admin.dict.vo.data.DictDataSaveReqVO;
|
||||
import com.cf.imes.module.system.controller.admin.dict.vo.data.DictDataSimpleRespVO;
|
||||
import com.cf.imes.module.system.controller.admin.dict.vo.data.DictDataTransReqVO;
|
||||
import com.cf.imes.module.system.dal.dataobject.dict.DictDataDO;
|
||||
import com.cf.imes.module.system.dal.dataobject.dict.DictDatai18nDO;
|
||||
import com.cf.imes.module.system.dal.dataobject.dict.DictDatai18nVersionDO;
|
||||
import com.cf.imes.module.system.dal.dataobject.dict.DictTypeDO;
|
||||
import com.cf.imes.module.system.dal.mysql.dict.DictDataI18nMapper;
|
||||
import com.cf.imes.module.system.dal.mysql.dict.DictDataI18nVersionMapper;
|
||||
import com.cf.imes.module.system.dal.mysql.dict.DictDataMapper;
|
||||
import com.cf.imes.module.system.enums.ErrorCodeConstants;
|
||||
import com.cf.imes.module.system.enums.common.LanguageEnum;
|
||||
import com.cf.imes.module.system.framework.trans.config.TransApi;
|
||||
import com.cf.imes.module.system.framework.trans.config.TransProperties;
|
||||
import com.cf.imes.module.system.util.locale.DictDataI18nCacheUtils;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import static com.cf.imes.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static com.cf.imes.module.system.enums.ErrorCodeConstants.DICT_DATA_TRANS_ERROR;
|
||||
|
||||
/**
|
||||
* 字典数据 Service 实现类
|
||||
@@ -46,6 +72,18 @@ public class DictDataServiceImpl implements DictDataService {
|
||||
@Resource
|
||||
private DictDataMapper dictDataMapper;
|
||||
|
||||
@Resource
|
||||
private DictDataI18nMapper dictDatai18nMapper;
|
||||
|
||||
@Resource
|
||||
private TransProperties transProperties;
|
||||
|
||||
@Resource
|
||||
private DictDataI18nVersionMapper dictDataI18nVersionMapper;
|
||||
|
||||
@Resource
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
@Override
|
||||
public List<DictDataDO> getDictDataList(Integer status, String dictType) {
|
||||
List<DictDataDO> list = dictDataMapper.selectListByStatusAndDictType(status, dictType);
|
||||
@@ -170,4 +208,115 @@ public class DictDataServiceImpl implements DictDataService {
|
||||
return dictDataMapper.selectByDictTypeAndLabel(dictType, label);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void trans(DictDataTransReqVO reqVO) {
|
||||
String to = reqVO.getTargetLanguage();
|
||||
// 清理对应语种下的历史记录
|
||||
int delete = dictDatai18nMapper.delete(new LambdaQueryWrapper<DictDatai18nDO>()
|
||||
.eq(DictDatai18nDO::getLocale, to));
|
||||
log.info("========== 【字典数据标签国际化】{}清理历史记录,删除 {} 条 =====", to, delete);
|
||||
|
||||
// 查询所有有效的菜单类型 菜单
|
||||
List<DictDataDO> dictDataDOS = dictDataMapper.selectList(new LambdaQueryWrapper<DictDataDO>()
|
||||
.eq(DictDataDO::getDeleted, false)
|
||||
.eq(DictDataDO::getStatus, CommonStatusEnum.ENABLE.getStatus()));
|
||||
|
||||
// 转成有序 map(key = 序号, value = DictDataDO)
|
||||
Map<Integer, DictDataDO> dataMap = IntStream.range(0, dictDataDOS.size())
|
||||
.boxed()
|
||||
.collect(Collectors.toMap(
|
||||
i -> i,
|
||||
dictDataDOS::get,
|
||||
(v1, v2) -> v1,
|
||||
LinkedHashMap::new
|
||||
));
|
||||
|
||||
// 拼接 menu name,用换行分隔
|
||||
String namesForQuery = dataMap.values().stream()
|
||||
.map(DictDataDO::getLabel)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.joining("\n"));
|
||||
|
||||
// 请求翻译api
|
||||
TransApi transApi = new TransApi(transProperties.getAppId(), transProperties.getSecurityKey(), transProperties.getApiHost());
|
||||
String transResult = transApi.getTransResult(namesForQuery, "zh", to);
|
||||
|
||||
JSONObject obj = JSON.parseObject(transResult);
|
||||
String errorCode = obj.getString("error_code");
|
||||
if (StringUtils.isNotEmpty(errorCode)) {
|
||||
throw ServiceExceptionUtil.exception(DICT_DATA_TRANS_ERROR, errorCode);
|
||||
}
|
||||
JSONArray arr = obj.getJSONArray("trans_result");
|
||||
|
||||
List<DictDatai18nDO> i18nList = new ArrayList<>();
|
||||
for (int i = 0; i < arr.size(); i++) {
|
||||
DictDataDO dictDataDO = dataMap.get(i);
|
||||
JSONObject item = arr.getJSONObject(i);
|
||||
String src = item.getString("src");
|
||||
String dst = item.getString("dst");
|
||||
|
||||
if (dictDataDO == null || !dictDataDO.getLabel().equals(UnicodeUtil.toString(src))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String name = UnicodeUtil.toString(dst);
|
||||
DictDatai18nDO i18n = new DictDatai18nDO();
|
||||
i18n.setDictDataId(dictDataDO.getId());
|
||||
i18n.setLocale(to);
|
||||
i18n.setLabel(name);
|
||||
|
||||
i18nList.add(i18n);
|
||||
}
|
||||
|
||||
if (CollUtil.isNotEmpty(i18nList)) {
|
||||
dictDatai18nMapper.insertBatch(i18nList);
|
||||
log.info("========== 【字典数据标签国际化】{}成功,插入 {} 条 =====", to, i18nList.size());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getI18nVersion() {
|
||||
List<DictDatai18nVersionDO> dictDatai18nVersionDOS = dictDataI18nVersionMapper.selectList();
|
||||
if (CollUtil.isNotEmpty(dictDatai18nVersionDOS)) {
|
||||
return dictDatai18nVersionDOS.get(0).getVersion();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateI18nVersion() {
|
||||
// 更新版本号
|
||||
dictDataI18nVersionMapper.updateVersion();
|
||||
|
||||
// 通知清理cache
|
||||
stringRedisTemplate.convertAndSend(com.cf.imes.framework.redis.constants.RedisKeyConstants.DICT_DATA_I18N_CACHE_REFRESH, "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<DictDataSimpleRespVO>> getAllLanguageDictData() {
|
||||
Map<String, List<DictDataSimpleRespVO>> cache = DictDataI18nCacheUtils.getCache();
|
||||
if (MapUtil.isNotEmpty(cache)) {
|
||||
return cache;
|
||||
}
|
||||
// dictData数据作为中文字典
|
||||
List<DictDataDO> list = dictDataMapper.selectListByStatusAndDictType(CommonStatusEnum.ENABLE.getStatus(), null);
|
||||
list.sort(COMPARATOR_TYPE_AND_SORT);
|
||||
|
||||
List<Long> ids = list.stream().map(DictDataDO::getId).toList();
|
||||
|
||||
Map<String, List<DictDataSimpleRespVO>> resultMap = new ConcurrentHashMap<>();
|
||||
// 获取每种语言的字典数据
|
||||
for (LanguageEnum languageEnum : LanguageEnum.values()) {
|
||||
if (LanguageEnum.ZH.equals(languageEnum)) {
|
||||
resultMap.put(languageEnum.getLanguage(), BeanUtils.toBean(list, DictDataSimpleRespVO.class));
|
||||
} else {
|
||||
resultMap.put(languageEnum.getLanguage(), BeanUtils.toBean(dictDatai18nMapper.getLocaleDictData(ids, languageEnum.getLanguage()), DictDataSimpleRespVO.class));
|
||||
}
|
||||
}
|
||||
|
||||
// 缓存
|
||||
DictDataI18nCacheUtils.putCache(resultMap);
|
||||
return resultMap;
|
||||
}
|
||||
}
|
||||
|
||||
+8
-1
@@ -2,11 +2,11 @@ package com.cf.imes.module.system.service.permission;
|
||||
|
||||
import com.cf.imes.module.system.controller.admin.permission.vo.menu.MenuListReqVO;
|
||||
import com.cf.imes.module.system.controller.admin.permission.vo.menu.MenuSaveVO;
|
||||
import com.cf.imes.module.system.controller.admin.permission.vo.menu.MenuTransReqVO;
|
||||
import com.cf.imes.module.system.dal.dataobject.permission.MenuDO;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 菜单 Service 接口
|
||||
@@ -102,4 +102,11 @@ public interface MenuService {
|
||||
* @return
|
||||
*/
|
||||
List<MenuDO> getCustomEndPointMenuList(Collection<Long> ids);
|
||||
|
||||
/**
|
||||
* 菜单目标语种翻译
|
||||
*
|
||||
* @param reqVO
|
||||
*/
|
||||
void trans(MenuTransReqVO reqVO);
|
||||
}
|
||||
|
||||
+93
-9
@@ -1,22 +1,34 @@
|
||||
package com.cf.imes.module.system.service.permission;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.text.UnicodeUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.cf.imes.framework.common.exception.ServiceException;
|
||||
import com.cf.imes.framework.common.exception.util.ServiceExceptionUtil;
|
||||
import com.cf.imes.framework.common.util.object.BeanUtils;
|
||||
import com.cf.imes.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import com.cf.imes.framework.organ.core.aop.OrganIgnore;
|
||||
import com.cf.imes.framework.security.core.util.SecurityFrameworkUtils;
|
||||
import com.cf.imes.module.system.controller.admin.permission.vo.menu.MenuListReqVO;
|
||||
import com.cf.imes.module.system.controller.admin.permission.vo.menu.MenuSaveVO;
|
||||
import com.cf.imes.module.system.controller.admin.permission.vo.menu.MenuTransReqVO;
|
||||
import com.cf.imes.module.system.dal.dataobject.permission.MenuDO;
|
||||
import com.cf.imes.module.system.dal.dataobject.permission.MenuI18nDO;
|
||||
import com.cf.imes.module.system.dal.mysql.permission.MenuMapper;
|
||||
import com.cf.imes.module.system.dal.mysql.permission.MenuI18nMapper;
|
||||
import com.cf.imes.module.system.dal.redis.RedisKeyConstants;
|
||||
import com.cf.imes.module.system.enums.permission.MenuSourceTypeEnum;
|
||||
import com.cf.imes.module.system.enums.permission.MenuTypeEnum;
|
||||
import com.cf.imes.module.system.framework.trans.config.TransApi;
|
||||
import com.cf.imes.module.system.framework.trans.config.TransProperties;
|
||||
import com.cf.imes.module.system.service.organ.OrganService;
|
||||
import com.cf.imes.module.system.util.locale.LocaleUtils;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
@@ -30,8 +42,13 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import jakarta.annotation.Resource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import static com.cf.imes.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static com.cf.imes.framework.common.util.collection.CollectionUtils.convertList;
|
||||
@@ -57,6 +74,12 @@ public class MenuServiceImpl implements MenuService {
|
||||
@Autowired
|
||||
private RedisTemplate redisTemplate;
|
||||
|
||||
@Resource
|
||||
private MenuI18nMapper menui18nMapper;
|
||||
|
||||
@Resource
|
||||
private TransProperties transProperties;
|
||||
|
||||
|
||||
private static final String FIELD_CHARACTER = ":*:%s";
|
||||
|
||||
@@ -327,18 +350,79 @@ public class MenuServiceImpl implements MenuService {
|
||||
@Override
|
||||
@OrganIgnore
|
||||
public List<MenuDO> getManageEndPointMenuList(Collection<Long> ids) {
|
||||
return menuMapper.selectList(new LambdaQueryWrapperX<MenuDO>()
|
||||
.inIfPresent(MenuDO::getId, ids)
|
||||
.ne(MenuDO::getSourceType, MenuSourceTypeEnum.PRODUCTION.getType()));
|
||||
return menuMapper.selectListIn(ids, true, LocaleUtils.getLocale());
|
||||
}
|
||||
|
||||
@Override
|
||||
@OrganIgnore
|
||||
public List<MenuDO> getCustomEndPointMenuList(Collection<Long> ids) {
|
||||
return menuMapper.selectList(new LambdaQueryWrapperX<MenuDO>()
|
||||
.inIfPresent(MenuDO::getId, ids)
|
||||
.and(wr -> wr.or(wrapper -> wrapper
|
||||
.isNull(MenuDO::getSourceType))
|
||||
.or(wrapper -> wrapper.ne(MenuDO::getSourceType, MenuSourceTypeEnum.MANAGEMENT.getType()))));
|
||||
return menuMapper.selectListIn(ids, false, LocaleUtils.getLocale());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void trans(MenuTransReqVO reqVO) {
|
||||
String to = reqVO.getTargetLanguage();
|
||||
// 清理对应语种下的历史记录
|
||||
int delete = menui18nMapper.delete(new LambdaQueryWrapper<MenuI18nDO>()
|
||||
.eq(MenuI18nDO::getLocale, to));
|
||||
log.info("========== 【菜单国际化】{}清理历史记录,删除 {} 条 =====", to, delete);
|
||||
|
||||
// 查询所有有效的菜单类型 菜单
|
||||
List<MenuDO> menuDOS = menuMapper.selectList(new LambdaQueryWrapper<MenuDO>()
|
||||
.eq(MenuDO::getDeleted, false)
|
||||
.eq(MenuDO::getType, MenuTypeEnum.MENU.getType()));
|
||||
|
||||
// 转成有序 map(key = 序号, value = MenuDO)
|
||||
Map<Integer, MenuDO> menuMap = IntStream.range(0, menuDOS.size())
|
||||
.boxed()
|
||||
.collect(Collectors.toMap(
|
||||
i -> i,
|
||||
menuDOS::get,
|
||||
(v1, v2) -> v1,
|
||||
LinkedHashMap::new
|
||||
));
|
||||
|
||||
// 拼接 menu name,用换行分隔
|
||||
String namesForQuery = menuMap.values().stream()
|
||||
.map(MenuDO::getName)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.joining("\n"));
|
||||
|
||||
// 请求翻译api
|
||||
TransApi transApi = new TransApi(transProperties.getAppId(), transProperties.getSecurityKey(), transProperties.getApiHost());
|
||||
String transResult = transApi.getTransResult(namesForQuery, "zh", to);
|
||||
|
||||
JSONObject obj = JSON.parseObject(transResult);
|
||||
String errorCode = obj.getString("error_code");
|
||||
if (StringUtils.isNotEmpty(errorCode)) {
|
||||
throw ServiceExceptionUtil.exception(MENU_TRANS_ERROR, errorCode);
|
||||
}
|
||||
JSONArray arr = obj.getJSONArray("trans_result");
|
||||
|
||||
List<MenuI18nDO> i18nList = new ArrayList<>();
|
||||
for (int i = 0; i < arr.size(); i++) {
|
||||
MenuDO menuDO = menuMap.get(i);
|
||||
JSONObject item = arr.getJSONObject(i);
|
||||
String src = item.getString("src");
|
||||
String dst = item.getString("dst");
|
||||
|
||||
if (menuDO == null || !menuDO.getName().equals(UnicodeUtil.toString(src))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String name = UnicodeUtil.toString(dst);
|
||||
MenuI18nDO i18n = new MenuI18nDO();
|
||||
i18n.setMenuId(menuDO.getId());
|
||||
i18n.setLocale(to);
|
||||
i18n.setMenuName(name);
|
||||
|
||||
i18nList.add(i18n);
|
||||
}
|
||||
|
||||
if (CollUtil.isNotEmpty(i18nList)) {
|
||||
menui18nMapper.insertBatch(i18nList);
|
||||
log.info("========== 【菜单国际化】{}成功,插入 {} 条 =====", to, i18nList.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package com.cf.imes.module.system.util.locale;
|
||||
|
||||
import com.cf.imes.module.system.controller.admin.dict.vo.data.DictDataSimpleRespVO;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 字典数据标签国际化缓存工具类
|
||||
*
|
||||
* @author Gqr
|
||||
* @since 2025/11/12 13:38
|
||||
*/
|
||||
public class DictDataI18nCacheUtils {
|
||||
private static final Map<String, List<DictDataSimpleRespVO>> CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 获取缓存
|
||||
*/
|
||||
public static Map<String, List<DictDataSimpleRespVO>> getCache() {
|
||||
return Collections.unmodifiableMap(CACHE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存
|
||||
* @param map
|
||||
*/
|
||||
public static void putCache(Map<String, List<DictDataSimpleRespVO>> map) {
|
||||
CACHE.putAll(map);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除缓存
|
||||
*/
|
||||
public static void clearCache() {
|
||||
CACHE.clear();
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package com.cf.imes.module.system.util.locale;
|
||||
|
||||
import org.springframework.context.i18n.LocaleContextHolder;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* 国际化utils
|
||||
*
|
||||
* @author Gqr
|
||||
* @since 2025/11/11 15:15
|
||||
*/
|
||||
public class LocaleUtils {
|
||||
|
||||
/**
|
||||
* 获取当前语言,中文就默认null
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static String getLocale() {
|
||||
Locale locale = LocaleContextHolder.getLocale();
|
||||
|
||||
if (locale == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Locale.CHINA.equals(locale)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return locale.getLanguage();
|
||||
}
|
||||
}
|
||||
@@ -98,4 +98,11 @@ spring:
|
||||
repositories:
|
||||
enabled: false # 项目未使用到 Spring Data Redis 的 Repository,所以直接禁用,保证启动速度
|
||||
|
||||
trans:
|
||||
config:
|
||||
appId: 20251110002494620
|
||||
securityKey: _CUXY5Hk4WLBnYRQBcWw
|
||||
apiHost: https://fanyi-api.baidu.com/api/trans/vip/translate
|
||||
|
||||
|
||||
debug: false
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
|
||||
<mapper namespace="com.cf.imes.module.system.dal.mysql.dict.DictDataI18nMapper">
|
||||
<select id="getLocaleDictData" resultType="com.cf.imes.module.system.dal.dataobject.dict.DictDataDO">
|
||||
select
|
||||
d.dict_type,
|
||||
i.label,
|
||||
d.value,
|
||||
d.color_type,
|
||||
d.css_class
|
||||
from system_dict_data d
|
||||
left join system_dict_data_i18n i
|
||||
on d.id = i.dict_data_id and i.locale = #{locale}
|
||||
where d.deleted = false
|
||||
and d.status = '${@com.cf.imes.framework.common.enums.CommonStatusEnum@ENABLE.status}'
|
||||
<if test="ids != null and !ids.isEmpty()">
|
||||
and d.id IN
|
||||
<foreach collection="ids" item="id" open="(" separator="," close=")">
|
||||
#{id}
|
||||
</foreach>
|
||||
</if>
|
||||
</select>
|
||||
</mapper>
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
|
||||
<mapper namespace="com.cf.imes.module.system.dal.mysql.dict.DictDataI18nVersionMapper">
|
||||
|
||||
<update id="updateVersion">
|
||||
update system_dict_data_i18n_version set version = version + 1;
|
||||
</update>
|
||||
</mapper>
|
||||
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
|
||||
<mapper namespace="com.cf.imes.module.system.dal.mysql.permission.MenuMapper">
|
||||
<select id="selectListIn" resultType="com.cf.imes.module.system.dal.dataobject.permission.MenuDO">
|
||||
SELECT
|
||||
m.id,
|
||||
<if test="language != null">
|
||||
i.menu_name AS name,
|
||||
</if>
|
||||
<if test="language == null">
|
||||
m.name,
|
||||
</if>
|
||||
m.permission,
|
||||
m.type,
|
||||
m.sort,
|
||||
m.parent_id AS parentId,
|
||||
m.path,
|
||||
m.icon,
|
||||
m.component,
|
||||
m.component_name AS componentName,
|
||||
m.status,
|
||||
m.visible,
|
||||
m.keep_alive AS keepAlive,
|
||||
m.always_show AS alwaysShow,
|
||||
m.description,
|
||||
m.source_type AS sourceType
|
||||
FROM system_menu m
|
||||
<if test="language != null">
|
||||
LEFT JOIN system_menu_i18n i
|
||||
ON m.id = i.menu_id
|
||||
AND i.locale = #{language}
|
||||
</if>
|
||||
WHERE 1=1
|
||||
|
||||
<if test="ids != null and !ids.isEmpty()">
|
||||
AND m.id IN
|
||||
<foreach collection="ids" item="id" open="(" separator="," close=")">
|
||||
#{id}
|
||||
</foreach>
|
||||
</if>
|
||||
|
||||
<if test="isManage">
|
||||
AND m.source_type != '${@com.cf.imes.module.system.enums.permission.MenuSourceTypeEnum@PRODUCTION.type}'
|
||||
</if>
|
||||
<if test="!isManage">
|
||||
AND (
|
||||
m.source_type IS NULL
|
||||
OR m.source_type != '${@com.cf.imes.module.system.enums.permission.MenuSourceTypeEnum@MANAGEMENT.type}'
|
||||
)
|
||||
</if>
|
||||
</select>
|
||||
</mapper>
|
||||
Reference in New Issue
Block a user