SpringBoot 后端 i18n 国际化实现

定义表结构

  • language.sql
CREATE TABLE "pig"."language" (
  "id" int8 NOT NULL DEFAULT nextval('"pig".language_id_seq'::regclass),
  "language" varchar(20) COLLATE "pg_catalog"."default" NOT NULL,
  "language_key" varchar(255) COLLATE "pg_catalog"."default",
  "language_value" varchar(255) COLLATE "pg_catalog"."default" NOT NULL,
  "status" int2 NOT NULL DEFAULT 1,
  "create_time" timestamp(6) DEFAULT CURRENT_TIMESTAMP,
  CONSTRAINT "language_pkey" PRIMARY KEY ("id"),
  CONSTRAINT "idx_language" UNIQUE ("language", "language_key")
)
;

ALTER TABLE "pig"."language" 
  OWNER TO "cloud_dev_pg";

COMMENT ON COLUMN "pig"."language"."language" IS '语言类型';

COMMENT ON COLUMN "pig"."language"."language_key" IS '词条';

COMMENT ON COLUMN "pig"."language"."language_value" IS '对应语言的文字内容';

COMMENT ON COLUMN "pig"."language"."status" IS '状态【 0:无效、1:有效】 ';

COMMENT ON TABLE "pig"."language" IS '国际化语言表';
  • LanguageController

@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/language")
@Tag(name = "国际化信息模块")
@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)
public class LanguageController {
	private final LanguageService languageService;

	private final int status = 1;

	@PostMapping("/add")
	public R<Boolean> addLanguageMess(@RequestBody LanguageDTO languageDTO) {
		languageDTO.setStatus(status);
		languageDTO.setCreateTime(new Date());
		return languageService.addLanguageMess(languageDTO);
	}

	@DeleteMapping("/delete")
	public R<Boolean> deleteLanguageMess(@RequestParam Long id) {
		LanguageDTO languageDTO = new LanguageDTO();
		languageDTO.setId(id);
		return languageService.deleteLanguageMess(languageDTO);
	}

	@PutMapping("/edit")
	public R<Boolean> editLanguageMess(@RequestBody LanguageDTO languageDTO) {
		return languageService.editLanguageMess(languageDTO);
	}

	@PostMapping("/select")
	public R<Page<LanguageVO>> selectLanguageMessByPage(@RequestParam int current, @RequestParam int size, @RequestBody LanguageDTO languageDTO) {
		Page<LanguageVO> pageR = languageService.selectLanguageMessByPage(current, size, languageDTO);
		return R.ok(pageR);
	}

	@SysLog(module = LogMoudleConstants.LANGUAGE_MANAGEMENT, value = "清除国际化缓存")
	@DeleteMapping("/cache")
	public R clearLanguageCache() {
		languageService.clearLanguageCache();
		return R.ok();
	}

	@Inner
	@GetMapping("/selectByLanguageAndKey")
	public R<Language> selectByLanguageAndKey(@RequestParam(value = "language") String language, @RequestParam(value = "key") String key) {
		Language languageVo = languageService.selectByLanguageAndKey(language, key);
		if (languageVo != null){
			return R.ok(languageVo);
		}
		return R.failed();
	}

	@Inner
	@GetMapping("/selectByLanguageAndValue")
	public R<Language> selectByLanguageAndValue(@RequestParam(value = "language") String language, @RequestParam(value = "value") String value) {
		Language languageVo = languageService.selectByLanguageAndValue(language, value);
		if (languageVo != null){
			return R.ok(languageVo);
		}
		return R.failed();
	}


}
  • LanguageServiceImpl.java

@Slf4j
@Service
@RequiredArgsConstructor
public class LanguageServiceImpl extends ServiceImpl<LanguageMapper, Language> implements LanguageService {
	private final LanguageMapper languageMapper;

	//添加
	@Override
	public R<Boolean> addLanguageMess(LanguageDTO languageDTO) {
		int count = languageMapper.selectCount(languageDTO);
		if (count > 0) {
			throw new AioException(SystemErrorEnum.EXIST_I18N_MESSAGE);
		}
		Boolean addBoolean = languageMapper.addLanguageMess(languageDTO);
		return R.ok(addBoolean);
	}

	//删除
	@Override
	public R<Boolean> deleteLanguageMess(LanguageDTO languageDTO) {
		Language language = languageMapper.selectById(languageDTO.getId());
		if (language != null) {
			Boolean deleteBoolean = languageMapper.deleteLanguageMess(languageDTO);
			return R.ok(deleteBoolean);
		}
		throw new AioException(SystemErrorEnum.NOT_EXIST_I18N_MESSAGE);
	}

	//编辑
	@Override
	public R<Boolean> editLanguageMess(LanguageDTO languageDTO) {
		if (languageDTO.getId() != null) {
			Language language = languageMapper.selectById(languageDTO.getId());
			if (language != null) {
				int count = languageMapper.selectCount(languageDTO);
				if (count > 0) {
					throw new AioException(SystemErrorEnum.EXIST_I18N_MESSAGE);
				}
				Boolean editBoolean = languageMapper.editLanguageMess(languageDTO);
				return R.ok(editBoolean);
			}
			throw new AioException(SystemErrorEnum.NOT_EXIST_I18N_MESSAGE);
		} else {
			throw new AioException(SystemErrorEnum.NOT_EXIST_I18N_MESSAGE);
		}
	}

	//分页查询
	@Override
	public Page<LanguageVO> selectLanguageMessByPage(int current, int size, LanguageDTO languageDTO) {
		Page<Language> languagePage = new Page<>(current, size);

		LambdaQueryWrapper<Language> languageDTOLambdaQueryWrapper = Wrappers.lambdaQuery(Language.class);

		languageDTOLambdaQueryWrapper.like(StringUtils.isNotEmpty(languageDTO.getKeyword()), Language::getLanguageKey, languageDTO.getKeyword()).or()
				.like(StringUtils.isNotEmpty(languageDTO.getKeyword()), Language::getLanguageValue, languageDTO.getKeyword()).orderByDesc(Language::getCreateTime);

		Page<Language> page = languageMapper.selectPage(languagePage, languageDTOLambdaQueryWrapper);

		List<LanguageVO> records = page.getRecords()
				.stream()
				.map(data -> BeanUtil.copyProperties(data, LanguageVO.class))
				.collect(Collectors.toList());

		Page<LanguageVO> languageVOPage = new Page<>();
		languageVOPage.setRecords(records);
		languageVOPage.setSize(page.getSize());
		languageVOPage.setCurrent(page.getCurrent());
		languageVOPage.setTotal(page.getTotal());

		return languageVOPage;
	}

	@Override
	public Language selectByLanguageAndValue(String language, String value) {
		LambdaQueryWrapper<Language> queryWrapper = Wrappers.lambdaQuery(Language.class);
		queryWrapper.eq(Language::getLanguage, language)
				.eq(Language::getLanguageValue, value);
		List<Language> languages = baseMapper.selectList(queryWrapper);
		if (CollectionUtil.isNotEmpty(languages)){
			return languages.get(0);
		}
		return null;
	}

	@Override
	public Language selectByLanguageAndKey(String language, String key) {
		LambdaQueryWrapper<Language> queryWrapper = Wrappers.lambdaQuery(Language.class);
		queryWrapper.eq(Language::getLanguage, language)
				.eq(Language::getLanguageKey, key);
		List<Language> languages = baseMapper.selectList(queryWrapper);
		if (CollectionUtil.isNotEmpty(languages)){
			return languages.get(0);
		}
		return null;
	}

	//清除缓存
	@Override
	@CacheEvict(value = {CacheConstants.LANGUAGE}, allEntries = true)
	public void clearLanguageCache() {

	}
}

定义系统错误码

  • SystemCodeContant.java
public interface SystemCodeContant {

	// 业务平台
	String AIO_CLOUD = "10";

	// 系统管理
	String SYSTEM = AIO_CLOUD + "01";
	// 其他
	String OTHER = AIO_CLOUD + "99";
}

  • EventErrorEnum.java
public enum EventErrorEnum implements IErrorCode {

	//名称已存在
	RULE_NAME_EXIST("000001"),
	//数据源不能为空
	ALARM_SOURCE_NOT_NULL("000002"),
	;


	EventErrorEnum(String code) {
		this.code = code;
	}

	private String code;


	@Override
	public String getCode() {
		return SystemCodeContant.SYSTEM + this.code;
	}

	@Override
	public String getI18nMessage(Object... args) {
		return MsgUtils.getDynamicMessage(getCode(), args);
	}

}

定义资源i18n

配置目录

src/main/resources/i18n/messages.properties
src/main/resources/i18n/messages_en.properties
src/main/resources/i18n/messages_zh_CN.properties
在这里插入图片描述

内容配置

messages.properties

1004000001=rule name exist!
1004000002=alarm source not null!
1004000003=device has already bound organization {0} with the name of the alarm rule {1}!!

messages_en.properties

1004000001=rule name exist!
1004000002=alarm source not null!
1004000003=device has already bound organization {0} with the name of the alarm rule {1}!!

messages_zh_CN.properties

1004000001=名称已经存在,不允许重复!
1004000002=类型不能为空!
1004000003=关联设备已经绑定{0}相应配置了,名称为{1}的配置了!

定义业务类

  • ILanguageService.java
public interface ILanguageService {

	/**
	 * @param language 语种
	 * @author: kele
	 * @desc:
	 */
	void cacheData(String language);

	/**
	 * @param key      词条
	 * @author: kele
	 * @desc: 根据词条和语言返回该语种的具体释义
	 */
	String getValueByKey(String key);


	/**
	 * @author: kele
	 * @desc:
	 */
	void cleanCache();
}
  • LanguageServiceImpl.java
@Slf4j
@RequiredArgsConstructor
public class LanguageServiceImpl implements ILanguageService {

	private static final String LANGUAGE_KEY = "language_key";
	private static final String LANGUAGE_VALUE = "language_value";
	private static final String LANGUAGE_SQL = String.format("select %s,%s from pig.language where language = ?  AND  status = 1", LANGUAGE_KEY, LANGUAGE_VALUE);

	private final DataSource dataSource;

	private final ValueOperations<String, String> valueOperations;

	private static ThreadLocal<JSONObject> localLanguage = new TransmittableThreadLocal<>();

	@Override
	public void cacheData(String language) {
		//缓存对象注入为空,直接查库
		if (valueOperations == null) {
			localLanguage.set(JSON.parseObject(JSON.toJSONString(selectI18nData(language))));
			return;
		}
		//查询缓存中是否已经有该语种的值
		String value = valueOperations.get(String.format(CacheConstants.LANGUAGE_REDIS_KEY, language));
		if (value != null) {
			localLanguage.set(JSON.parseObject(value));
			return;
		}
		//缓存中不存在该语种的值,去数据库查询
		JSONObject jsonObject = JSON.parseObject(JSON.toJSONString(selectI18nData(language)));
		valueOperations.set(String.format(CacheConstants.LANGUAGE_REDIS_KEY, language), jsonObject.toJSONString());
		localLanguage.set(jsonObject);
	}

	@Override
	public String getValueByKey(String key) {
		//缓存对象注入为空,直接查库
		return localLanguage.get().getString(key);

	}

	@Override
	public void cleanCache() {
		localLanguage.remove();
	}

	/**
	 * @param language 语种
	 * @author: kele
	 * @desc: 查询该语种的国际化数据
	 */
	private Map<String, String> selectI18nData(String language) {
		Map<String, String> result = new HashMap<>();
		if (StringUtils.isEmpty(language)) {
			return result;
		}
		Connection connection = null;
		try {
			//获取连接对象
			connection = dataSource.getConnection();
			PreparedStatement preparedStatement = connection.prepareStatement(LANGUAGE_SQL);
			//设置语种
			preparedStatement.setString(1, language);
			ResultSet executeQuery = preparedStatement.executeQuery();
			//循环取返回值
			while (executeQuery.next()) {
				result.put(executeQuery.getString(LANGUAGE_KEY), executeQuery.getString(LANGUAGE_VALUE));
			}

		} catch (Exception e) {
			log.error("查询数据库失败", e);
		} finally {
			if (connection != null) {
				try {
					boolean closed = connection.isClosed();
					if (!closed) {
						connection.close();
					}
				} catch (Exception e) {
					log.error("关闭连接异常", e);
				}
			}
		}
		return result;
	}
}

定义处理注解

  • DatabaseI18n.java
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DatabaseI18n {

	/**
	 * 是否AOP统一处理
	 * @return false, true
	 */
	boolean value() default true;


	/**
	 * @author: kele
	 * @desc: 有中文需要翻译
	 */
	boolean containsChinese() default false;
}

定义处理注解切面

  • DatabaseI18nAspect.java
/**
 * @author kele
 * @description 数据国际化统一处理
 */
@Slf4j
@Aspect
@RequiredArgsConstructor
public class DatabaseI18nAspect {

	private final ILanguageService iLanguageService;

	@Around("@within(databaseI18n) || @annotation(databaseI18n)")
	public Object around(ProceedingJoinPoint point, DatabaseI18n databaseI18n) throws Throwable {
		// 实际注入的databaseI18n实体由表达式后一个注解决定,即是方法上的@DatabaseI18n注解实体,若方法上无@DatabaseI18n注解,则获取类上的
		Object proceed = point.proceed();
		try {
			if (databaseI18n == null) {
				Class<?> clazz = point.getTarget().getClass();
				databaseI18n = AnnotationUtils.findAnnotation(clazz, DatabaseI18n.class);
			}
			//翻译业务类未注入,未获取到注解,返回值为空 直接返回不进行返回值解析
			if (iLanguageService == null || databaseI18n == null || !databaseI18n.value() || proceed == null) {
				return proceed;
			}
			//内部接口直接返回
			if (innerRequest()) {
				return proceed;
			}
			String requestLocalLanguage = RequestUtil.getRequestLocalLanguage();
			//没有请求域或请求域没有指定语言的不翻译
			if (requestLocalLanguage == null) {
				return proceed;
			}
			iLanguageService.cacheData(requestLocalLanguage);

			//判断是否是导出接口,导出接口数据必须保持原始的数据模型,不能转为JSON
			try {
				Class<?>[] parameterTypes = ((MethodSignature) point.getSignature()).getParameterTypes();
				Method method = point.getTarget().getClass().getDeclaredMethod(point.getSignature().getName(), parameterTypes);
				ResponseExcel responseExcel = AnnotationUtils.findAnnotation(method, ResponseExcel.class);
				if (responseExcel != null) {
					//导出的数据都是list 并且里面都是简单的数据模型
					for (Object listDatum : (List) proceed) {
						for (Field declaredField : listDatum.getClass().getDeclaredFields()) {
							declaredField.setAccessible(true);
							Object oldData = declaredField.get(listDatum);
							//只有字符串需要进行国际化
							Object i18nData = getI18nData(oldData, databaseI18n);
							declaredField.set(listDatum, i18nData);
						}
					}
					return proceed;
				}
			} catch (Exception e) {
				log.error("判断是否导出接口异常", e);
			}

			//返回对象是否进行了包装 ,如果包装了获取其data值
			try {
				if (proceed instanceof R) {
					R processR = (R) proceed;
					Object processData = handleProcessData(processR.getData(), databaseI18n);
					//由于返回值的模型不固定,统一进行了json替换,并非对象上进行修改,所以需要将新值赋予返回值对象
					processR.setData(processData);
					proceed = processR;
				} else {
					proceed = handleProcessData(proceed, databaseI18n);
				}
			} catch (Exception e) {
				log.error("数据国际化失败", e);
			}
			return proceed;
		} finally {
			if (iLanguageService != null) {
				iLanguageService.cleanCache();
			}
		}
	}

	private Object handleProcessData(Object proceed, DatabaseI18n databaseI18n) {
		if (proceed == null) return proceed;
		//简单类型直接返回
		if (ClassUtil.isSimpleValueType(proceed.getClass())) {
			return proceed;
		}
		//只处理数据记录
		if (proceed instanceof IPage) {
			IPage page = (IPage) proceed;
			List processData = (List)handleProcessData(page.getRecords(), databaseI18n);
			page.setRecords(processData);
			return page;
		}
		//需要进行类型判断  否则不知道数据为列表还是对象
		if (proceed instanceof List) {
			JSONArray jsonArray = JSON.parseArray(JSON.toJSONStringWithDateFormat(proceed, JSON.DEFFAULT_DATE_FORMAT));
			handleDataToI18n(jsonArray, databaseI18n);
			return jsonArray;
		} else {
			JSONObject jsonObject = JSON.parseObject(JSON.toJSONStringWithDateFormat(proceed, JSON.DEFFAULT_DATE_FORMAT));
			handleDataToI18n(jsonObject, databaseI18n);
			return jsonObject;
		}
	}


	private void handleDataToI18n(Object object, DatabaseI18n databaseI18n) {
		//数据为空直接返回
		if (object == null) return;
		//数据为列表递归调用
		if (object instanceof JSONArray) {
			JSONArray jsonArray = (JSONArray) object;
			jsonArray.forEach(data -> handleDataToI18n(data, databaseI18n));
			return;
		}
		if (object instanceof JSONObject) {
			JSONObject jsonObject = (JSONObject) object;
			for (String key : jsonObject.keySet()) {
				Object oldData = jsonObject.get(key);
				//对象里面的属性为列表,递归调用
				if (oldData instanceof JSONArray) {
					//如果列表里面存在简单类型,需要替换返回值
					long count = ((JSONArray) oldData).stream().filter(data -> ClassUtil.isSimpleValueType(data.getClass())).count();
					if (count > 0) {
						List<Object> list = ((JSONArray) oldData).stream().map(data -> {
							if (data instanceof String) {
								return getI18nData(data, databaseI18n);
							} else {
								handleDataToI18n(data, databaseI18n);
								return data;
							}
						}).collect(Collectors.toList());
						jsonObject.put(key, JSON.parseArray(JSON.toJSONString(list)));
					} else {
						handleDataToI18n(oldData, databaseI18n);
					}
					continue;
				}

				//属性值仍为对象,递归遍历
				if (oldData instanceof JSONObject) {
					handleDataToI18n(oldData, databaseI18n);
					continue;
				}
				//替换词条为国际化数据
				jsonObject.put(key, getI18nData(oldData, databaseI18n));
			}
		}

	}

	public Object getI18nData(Object oldData, DatabaseI18n databaseI18n) {
		//非字符串的数据不进行国际化
		if (!(oldData instanceof String)) {
			return oldData;
		}
		String oldDataStr = String.valueOf(oldData);
		String value = null;
		if (databaseI18n.containsChinese() || oldDataStr.contains(StrUtil.DOT)) {
			value = iLanguageService.getValueByKey(oldDataStr);
		}
		//根据词条和语言进行词条翻译
		return value == null ? oldData : value;
	}

	/**
	 * @author: kele
	 * @desc: 是否内部请求
	 */
	private boolean innerRequest() {
		HttpServletRequest request = RequestUtil.getRequest();
		if (request == null) return false;
		return StrUtil.isEmpty(request.getHeader(HttpHeaders.ACCEPT_LANGUAGE));
	}


}

定义处理注解切面使用

  • AioI18nAutoConfigration.java
/**
 * @author kele
 * @description
 */
public class AioI18nAutoConfigration {

	@Bean
	@ConditionalOnBean({DataSource.class})
	public ILanguageService iLanguageService(DataSource dataSource, @Autowired(required = false) ValueOperations valueOperations) {
		return new LanguageServiceImpl(dataSource, valueOperations);
	}


	@Bean
	@ConditionalOnBean(ILanguageService.class)
	public DatabaseI18nAspect databaseI18nAspect(ILanguageService iLanguageService) {
		SerializeConfig serializeConfig = SerializeConfig.globalInstance;
		serializeConfig.put(Long.class, ToStringSerializer.instance);
		return new DatabaseI18nAspect(iLanguageService);
	}

	@Bean
	@ConditionalOnBean(ILanguageService.class)
	public AioI18nHeaderHandler aioI18nHeaderHandler(MessageSource messageSource, ILanguageService iLanguageService) {
		return new AioI18nHeaderHandler(messageSource, iLanguageService);
	}

}

国际化使用

Controller生效

@DatabaseI18n
public class AlarmTypeNotifyTemplateController {}

Controller下的方法生效

@GetMapping("/key/{key}")
@DatabaseI18n
public R<List<SysDictItem>> getDictByKey(@PathVariable String key) {
	return R.ok();
}

Controller下的方法不生效

@GetMapping("/key/{key}")
@DatabaseI18n(value = false)
public R<List<SysDictItem>> getDictByKey(@PathVariable String key) {
	return R.ok();
}

方法类单个生效使用

  • 使用方式

@Resource
private ILanguageService iLanguageService;


GetMapping("/key/{key}")
@DatabaseI18n(value = false)
public R<List<SysDictItem>> getDictByKey(@PathVariable String key) {

	iLanguageService.cacheData(RequestUtil.getRequestLocalLanguage());

	String fileName = iLanguageService.getValueByKey(name);
	alarmRecordService.asyncExportData(alarmRecord, task, fileName, head);

	String taskStatusName = iLanguageService.getValueByKey(TaskStatusEnum.ABORNING.getName());
	iLanguageService.cleanCache();

	return R.ok();
}
  • 工具类RequestUtil.java

import com.alibaba.ttl.TransmittableThreadLocal;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

/**
 * @author kele
 * @description
 */
public class RequestUtil {

	private static ThreadLocal<ServletRequestAttributes> threadLocal = new TransmittableThreadLocal<>();

	/**
	 * @author: kele
	 * @desc: 获取请求域
	 */
	public static HttpServletRequest getRequest() {
		ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
		return servletRequestAttributes == null ? get() == null ? null : get().getRequest() : servletRequestAttributes.getRequest();
	}

	/**
	 * @author: kele
	 * @desc: 获取请求域
	 */
	public static String getRequestLocalLanguage() {
		HttpServletRequest request = getRequest();
		return request == null ? null : request.getLocale().getLanguage();
	}

	public static void set(ServletRequestAttributes servletRequestAttributes){
		threadLocal.set(servletRequestAttributes);
	}

	public static ServletRequestAttributes get(){
		return threadLocal.get();
	}

	public static void remove(){
		threadLocal.remove();
	}

	public static void transfer() {
		ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
		set(servletRequestAttributes);
	}
}

Excel导出国际处理

  • EasyExcelUtil.java
RequestUtil.transfer();
EasyExcelUtil.writeExcelLanguage(iLanguageService);

import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.annotation.ExcelProperty;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.dahua.alarm.api.model.vo.LocalDateTimeConverter;
import com.dahua.service.ILanguageService;
import com.dahua.util.RequestUtil;
import com.pig4cloud.plugin.excel.head.I18nHeaderCellWriteHandler;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.regex.Matcher;

@Slf4j
public class EasyExcelUtil {

	private static ILanguageService iLanguageService;

	public static void writeExcelLanguage(ILanguageService languageService) {
		iLanguageService = languageService;
	}

	public static <T> void writeExcel(File file, Class<T> head, Collection<T> data) {
		EasyExcel.write(file, head).sheet(0).doWrite(data);
	}


	/**
	 * 分页写入excel
	 * @param path
	 * @param head
	 * @param page
	 * @param i18nHeaderCellWriteHandler
	 * @param pageHandler
	 * @param <T>
	 */
	public static <T> File writeExcel(
			String path,
			Class<T> head,
			Page page,
			I18nHeaderCellWriteHandler i18nHeaderCellWriteHandler,
			PageHandler<T> pageHandler
	) {
		//构造文件、创建文件
		File file = new File(path);
		if (!file.getParentFile().exists()) {
			file.getParentFile().mkdirs();
		}
		if (!file.exists()) {
			try {
				file.createNewFile();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		ExcelWriter excelWriter = null;
		try {
			excelWriter = EasyExcel
					.write(file, head)
					.inMemory(false)
					.registerWriteHandler(i18nHeaderCellWriteHandler)
					.registerConverter(new LocalDateTimeConverter())
					.build();
			//当前页码
			while (true) {
				//分页读取数据
				List<T> data = pageHandler.page(page);
				if (data.isEmpty()) {
					//数据为0的时候也需要写入,防止excel文件异常
					excelWriter.write(data, EasyExcel.writerSheet("sheet1").head(head).build());
					break;
				}
				//读取的而数据写入excel
				setI18nData(data);
				excelWriter.write(data, EasyExcel.writerSheet("sheet1").head(head).build());
				//更新页码
				page.setCurrent(page.getCurrent() + 1);
			}
		} catch (Exception e) {
			e.printStackTrace();
			log.error("writeExcel -> errorMsg:", e);
		} finally {
			//数据写入完毕关闭资源
			if (excelWriter != null) {
				excelWriter.finish();
			}
			iLanguageService.cleanCache();
		}
		return file;
	}


	private static <T> void setI18nData(List<?> data) {
		iLanguageService.cacheData(RequestUtil.getRequestLocalLanguage());
		Map<String, Map<String, Object>> map = null;
		for (int i = 0; i < data.size(); i++) {
			Object obj = data.get(i);
			Class<?> clazz = obj.getClass();
			Field[] fields = clazz.getDeclaredFields();

			for (Field field : fields) {
				try {
					field.setAccessible(true);
					Object val = field.get(obj);
					//只有字符串需要进行国际化
					if (val instanceof String) {
						String value = (String)val;

						if (value.contains(StrUtil.DOT)) {
							value = iLanguageService.getValueByKey(value);
							field.set(obj, value);
						} else {
							ExcelProperty annotation = field.getAnnotation(ExcelProperty.class);
							if (Objects.nonNull(annotation)) {
								if (Objects.nonNull(annotation.converter())) {
									if (!annotation.converter().getName().contains("AutoConverter")) {
										if (null == map) {
											Class<?> clss = Class.forName(annotation.converter().getName());
											Object convert = clss.getDeclaredConstructor().newInstance();
											Method method = convert.getClass().getDeclaredMethod("getDictMap");
											map = (Map<String, Map<String, Object>>)method.invoke(convert);
										}
										//对类属性上注解@ExcelProperty的value进行处理 eg:export.device_status
										// String[] words = annotation.value()[0].split("\\.")[1].split("_");
										//String key = words[0] +  words[1].substring(0, 1).toUpperCase() + words[1].substring(1);
										String key = convertToCamelCase(annotation.value()[0].split("\\.")[1]);
										Map<String, Object> map0 = map.getOrDefault(key, new HashMap<>());
										String v = map0.getOrDefault(value, "").toString();
										value = v.contains(StrUtil.DOT) ? iLanguageService.getValueByKey(v) : v;
										field.set(obj, value);
									}
								}
							}
						}
					}
				} catch (IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException | ClassNotFoundException e) {
					log.error("writeExcel -> setI18nData -> errorMsg:", e);
				}
			}
		}
	}

	/**
	 * 下划线转驼峰
	 */
	public static String convertToCamelCase(String input) {
		StringBuilder camelCaseStr = new StringBuilder();
		boolean nextUpperCase = false;
		for (char c : input.toCharArray()) {
			if (c == '_') {
				nextUpperCase = true;
			} else {
				if (nextUpperCase) {
					camelCaseStr.append(Character.toUpperCase(c));
					nextUpperCase = false;
				} else {
					camelCaseStr.append(Character.toLowerCase(c));
				}
			}
		}
		return camelCaseStr.toString();
	}

}

国际化测试

在其controller或者方法添加注解@DatabaseI18n即可

方式实现

请添加图片描述

错误码演示

  • 错误码英文
    请添加图片描述

  • 错误码中文

在这里插入图片描述

词条业务演示

  • 词条英文
    在这里插入图片描述

  • 词条中文
    *在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值