feat: 新增数据管理模块(区域管理)及修复前端已知问题
后端(bms-back):
- 新增区域管理完整后端:Domain/Mapper/Service/Controller + MyBatis XML
- 新增 RestTemplateConfig 配置类,用于第三方API调用
- 新增 countries.json 本地国家数据源(105个国家)
- RuoYiApplication 加 @EnableAsync 支持异步任务
- application-druid.yml 修复 Druid 连接池字符集配置(SET NAMES utf8mb4)
- logback.xml 日志路径改为 ${user.dir}/logs 避免沙箱写入受限
- pom.xml 增加 JVM 编码参数(-Dfile.encoding=UTF-8)
- 新增 sql/bms_region.sql 建表脚本+菜单权限
前端(bms-front):
- 新增区域管理页面(列表/编辑/状态切换/同步国家数据/导出)
- 新增同步日志功能(内嵌为区域管理弹窗,移除独立菜单)
- 新增 API 层: region.js / region-log.js
- 修复 BasicForm/index.vue 缺失 import FormControl 导致搜索栏控件不渲染
- 修复 user/role/region 三个页面 el-switch 初始化时弹 undefined 提示
This commit is contained in:
@@ -0,0 +1,270 @@
|
||||
package com.ruoyi.system.domain;
|
||||
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
import org.apache.commons.lang3.builder.ToStringStyle;
|
||||
import com.ruoyi.common.annotation.Excel;
|
||||
import com.ruoyi.common.core.domain.BaseEntity;
|
||||
|
||||
/**
|
||||
* 国家/区域 bms_region
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
public class BmsRegion extends BaseEntity
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 区域ID */
|
||||
@Excel(name = "区域ID")
|
||||
private Long regionId;
|
||||
|
||||
/** 中文名 */
|
||||
@Excel(name = "中文名")
|
||||
private String countryNameCn;
|
||||
|
||||
/** 英文名 */
|
||||
@Excel(name = "英文名")
|
||||
private String countryNameEn;
|
||||
|
||||
/** ISO两位码 */
|
||||
@Excel(name = "国家代码")
|
||||
private String countryCode2;
|
||||
|
||||
/** ISO三位码 */
|
||||
@Excel(name = "ISO三位码")
|
||||
private String countryCode3;
|
||||
|
||||
/** 洲/大区 */
|
||||
@Excel(name = "洲/大区")
|
||||
private String continent;
|
||||
|
||||
/** 子区域 */
|
||||
@Excel(name = "子区域")
|
||||
private String subRegion;
|
||||
|
||||
/** 货币代码 */
|
||||
@Excel(name = "货币代码")
|
||||
private String currencyCode;
|
||||
|
||||
/** 货币名称 */
|
||||
@Excel(name = "货币名称")
|
||||
private String currencyName;
|
||||
|
||||
/** 货币符号 */
|
||||
private String currencySymbol;
|
||||
|
||||
/** 电话前缀 */
|
||||
@Excel(name = "电话前缀")
|
||||
private String phonePrefix;
|
||||
|
||||
/** 国旗emoji */
|
||||
private String flagEmoji;
|
||||
|
||||
/** 国旗图片URL */
|
||||
private String flagUrl;
|
||||
|
||||
/** 是否独立(1是 0否) */
|
||||
@Excel(name = "是否独立", readConverterExp = "1=是,0=否")
|
||||
private String isIndependent;
|
||||
|
||||
/** 是否欧盟成员(1是 0否) */
|
||||
@Excel(name = "是否欧盟", readConverterExp = "1=是,0=否")
|
||||
private String isEu;
|
||||
|
||||
/** 状态(0正常 1停用) */
|
||||
@Excel(name = "状态", readConverterExp = "0=正常,1=停用")
|
||||
private String status;
|
||||
|
||||
/** 删除标志(0存在 2删除) */
|
||||
private String delFlag;
|
||||
|
||||
public Long getRegionId()
|
||||
{
|
||||
return regionId;
|
||||
}
|
||||
|
||||
public void setRegionId(Long regionId)
|
||||
{
|
||||
this.regionId = regionId;
|
||||
}
|
||||
|
||||
public String getCountryNameCn()
|
||||
{
|
||||
return countryNameCn;
|
||||
}
|
||||
|
||||
public void setCountryNameCn(String countryNameCn)
|
||||
{
|
||||
this.countryNameCn = countryNameCn;
|
||||
}
|
||||
|
||||
public String getCountryNameEn()
|
||||
{
|
||||
return countryNameEn;
|
||||
}
|
||||
|
||||
public void setCountryNameEn(String countryNameEn)
|
||||
{
|
||||
this.countryNameEn = countryNameEn;
|
||||
}
|
||||
|
||||
public String getCountryCode2()
|
||||
{
|
||||
return countryCode2;
|
||||
}
|
||||
|
||||
public void setCountryCode2(String countryCode2)
|
||||
{
|
||||
this.countryCode2 = countryCode2;
|
||||
}
|
||||
|
||||
public String getCountryCode3()
|
||||
{
|
||||
return countryCode3;
|
||||
}
|
||||
|
||||
public void setCountryCode3(String countryCode3)
|
||||
{
|
||||
this.countryCode3 = countryCode3;
|
||||
}
|
||||
|
||||
public String getContinent()
|
||||
{
|
||||
return continent;
|
||||
}
|
||||
|
||||
public void setContinent(String continent)
|
||||
{
|
||||
this.continent = continent;
|
||||
}
|
||||
|
||||
public String getSubRegion()
|
||||
{
|
||||
return subRegion;
|
||||
}
|
||||
|
||||
public void setSubRegion(String subRegion)
|
||||
{
|
||||
this.subRegion = subRegion;
|
||||
}
|
||||
|
||||
public String getCurrencyCode()
|
||||
{
|
||||
return currencyCode;
|
||||
}
|
||||
|
||||
public void setCurrencyCode(String currencyCode)
|
||||
{
|
||||
this.currencyCode = currencyCode;
|
||||
}
|
||||
|
||||
public String getCurrencyName()
|
||||
{
|
||||
return currencyName;
|
||||
}
|
||||
|
||||
public void setCurrencyName(String currencyName)
|
||||
{
|
||||
this.currencyName = currencyName;
|
||||
}
|
||||
|
||||
public String getCurrencySymbol()
|
||||
{
|
||||
return currencySymbol;
|
||||
}
|
||||
|
||||
public void setCurrencySymbol(String currencySymbol)
|
||||
{
|
||||
this.currencySymbol = currencySymbol;
|
||||
}
|
||||
|
||||
public String getPhonePrefix()
|
||||
{
|
||||
return phonePrefix;
|
||||
}
|
||||
|
||||
public void setPhonePrefix(String phonePrefix)
|
||||
{
|
||||
this.phonePrefix = phonePrefix;
|
||||
}
|
||||
|
||||
public String getFlagEmoji()
|
||||
{
|
||||
return flagEmoji;
|
||||
}
|
||||
|
||||
public void setFlagEmoji(String flagEmoji)
|
||||
{
|
||||
this.flagEmoji = flagEmoji;
|
||||
}
|
||||
|
||||
public String getFlagUrl()
|
||||
{
|
||||
return flagUrl;
|
||||
}
|
||||
|
||||
public void setFlagUrl(String flagUrl)
|
||||
{
|
||||
this.flagUrl = flagUrl;
|
||||
}
|
||||
|
||||
public String getIsIndependent()
|
||||
{
|
||||
return isIndependent;
|
||||
}
|
||||
|
||||
public void setIsIndependent(String isIndependent)
|
||||
{
|
||||
this.isIndependent = isIndependent;
|
||||
}
|
||||
|
||||
public String getIsEu()
|
||||
{
|
||||
return isEu;
|
||||
}
|
||||
|
||||
public void setIsEu(String isEu)
|
||||
{
|
||||
this.isEu = isEu;
|
||||
}
|
||||
|
||||
public String getStatus()
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status)
|
||||
{
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getDelFlag()
|
||||
{
|
||||
return delFlag;
|
||||
}
|
||||
|
||||
public void setDelFlag(String delFlag)
|
||||
{
|
||||
this.delFlag = delFlag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
|
||||
.append("regionId", getRegionId())
|
||||
.append("countryNameCn", getCountryNameCn())
|
||||
.append("countryNameEn", getCountryNameEn())
|
||||
.append("countryCode2", getCountryCode2())
|
||||
.append("countryCode3", getCountryCode3())
|
||||
.append("continent", getContinent())
|
||||
.append("subRegion", getSubRegion())
|
||||
.append("status", getStatus())
|
||||
.append("createBy", getCreateBy())
|
||||
.append("createTime", getCreateTime())
|
||||
.append("updateBy", getUpdateBy())
|
||||
.append("updateTime", getUpdateTime())
|
||||
.append("remark", getRemark())
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
package com.ruoyi.system.domain;
|
||||
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
import org.apache.commons.lang3.builder.ToStringStyle;
|
||||
import com.ruoyi.common.annotation.Excel;
|
||||
import com.ruoyi.common.core.domain.BaseEntity;
|
||||
|
||||
/**
|
||||
* 区域数据同步日志 bms_region_sync_log
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
public class BmsRegionSyncLog extends BaseEntity
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 日志ID */
|
||||
@Excel(name = "日志ID")
|
||||
private Long logId;
|
||||
|
||||
/** 同步类型 */
|
||||
@Excel(name = "同步类型")
|
||||
private String syncType;
|
||||
|
||||
/** 状态 */
|
||||
@Excel(name = "状态", readConverterExp = "PENDING=待处理,PROCESSING=处理中,SUCCESS=成功,FAILED=失败")
|
||||
private String status;
|
||||
|
||||
/** API返回总数 */
|
||||
@Excel(name = "总数")
|
||||
private Integer totalCount;
|
||||
|
||||
/** 新增数 */
|
||||
@Excel(name = "新增数")
|
||||
private Integer insertCount;
|
||||
|
||||
/** 更新数 */
|
||||
@Excel(name = "更新数")
|
||||
private Integer updateCount;
|
||||
|
||||
/** 失败数 */
|
||||
@Excel(name = "失败数")
|
||||
private Integer failCount;
|
||||
|
||||
/** 耗时(毫秒) */
|
||||
@Excel(name = "耗时(ms)")
|
||||
private Long durationMs;
|
||||
|
||||
/** 错误信息 */
|
||||
private String errorMsg;
|
||||
|
||||
public Long getLogId()
|
||||
{
|
||||
return logId;
|
||||
}
|
||||
|
||||
public void setLogId(Long logId)
|
||||
{
|
||||
this.logId = logId;
|
||||
}
|
||||
|
||||
public String getSyncType()
|
||||
{
|
||||
return syncType;
|
||||
}
|
||||
|
||||
public void setSyncType(String syncType)
|
||||
{
|
||||
this.syncType = syncType;
|
||||
}
|
||||
|
||||
public String getStatus()
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status)
|
||||
{
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public Integer getTotalCount()
|
||||
{
|
||||
return totalCount;
|
||||
}
|
||||
|
||||
public void setTotalCount(Integer totalCount)
|
||||
{
|
||||
this.totalCount = totalCount;
|
||||
}
|
||||
|
||||
public Integer getInsertCount()
|
||||
{
|
||||
return insertCount;
|
||||
}
|
||||
|
||||
public void setInsertCount(Integer insertCount)
|
||||
{
|
||||
this.insertCount = insertCount;
|
||||
}
|
||||
|
||||
public Integer getUpdateCount()
|
||||
{
|
||||
return updateCount;
|
||||
}
|
||||
|
||||
public void setUpdateCount(Integer updateCount)
|
||||
{
|
||||
this.updateCount = updateCount;
|
||||
}
|
||||
|
||||
public Integer getFailCount()
|
||||
{
|
||||
return failCount;
|
||||
}
|
||||
|
||||
public void setFailCount(Integer failCount)
|
||||
{
|
||||
this.failCount = failCount;
|
||||
}
|
||||
|
||||
public Long getDurationMs()
|
||||
{
|
||||
return durationMs;
|
||||
}
|
||||
|
||||
public void setDurationMs(Long durationMs)
|
||||
{
|
||||
this.durationMs = durationMs;
|
||||
}
|
||||
|
||||
public String getErrorMsg()
|
||||
{
|
||||
return errorMsg;
|
||||
}
|
||||
|
||||
public void setErrorMsg(String errorMsg)
|
||||
{
|
||||
this.errorMsg = errorMsg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
|
||||
.append("logId", getLogId())
|
||||
.append("syncType", getSyncType())
|
||||
.append("status", getStatus())
|
||||
.append("totalCount", getTotalCount())
|
||||
.append("insertCount", getInsertCount())
|
||||
.append("updateCount", getUpdateCount())
|
||||
.append("failCount", getFailCount())
|
||||
.append("durationMs", getDurationMs())
|
||||
.append("errorMsg", getErrorMsg())
|
||||
.append("createBy", getCreateBy())
|
||||
.append("createTime", getCreateTime())
|
||||
.append("updateTime", getUpdateTime())
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.ruoyi.system.mapper;
|
||||
|
||||
import java.util.List;
|
||||
import com.ruoyi.system.domain.BmsRegion;
|
||||
|
||||
/**
|
||||
* 国家/区域 数据层
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
public interface BmsRegionMapper
|
||||
{
|
||||
/**
|
||||
* 查询区域信息
|
||||
*
|
||||
* @param regionId 区域ID
|
||||
* @return 区域信息
|
||||
*/
|
||||
public BmsRegion selectRegionById(Long regionId);
|
||||
|
||||
/**
|
||||
* 查询区域列表
|
||||
*
|
||||
* @param region 区域信息
|
||||
* @return 区域集合
|
||||
*/
|
||||
public List<BmsRegion> selectRegionList(BmsRegion region);
|
||||
|
||||
/**
|
||||
* 根据ISO两位码查询区域
|
||||
*
|
||||
* @param countryCode2 ISO两位码
|
||||
* @return 区域信息
|
||||
*/
|
||||
public BmsRegion selectRegionByCode2(String countryCode2);
|
||||
|
||||
/**
|
||||
* 新增区域
|
||||
*
|
||||
* @param region 区域信息
|
||||
* @return 结果
|
||||
*/
|
||||
public int insertRegion(BmsRegion region);
|
||||
|
||||
/**
|
||||
* 修改区域
|
||||
*
|
||||
* @param region 区域信息
|
||||
* @return 结果
|
||||
*/
|
||||
public int updateRegion(BmsRegion region);
|
||||
|
||||
/**
|
||||
* 批量删除区域
|
||||
*
|
||||
* @param regionIds 需要删除的区域ID
|
||||
* @return 结果
|
||||
*/
|
||||
public int deleteRegionByIds(Long[] regionIds);
|
||||
|
||||
/**
|
||||
* 根据ISO两位码做upsert(存在则更新,不存在则新增)
|
||||
*
|
||||
* @param region 区域信息
|
||||
* @return 结果
|
||||
*/
|
||||
public int upsertByCode2(BmsRegion region);
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
package com.ruoyi.system.mapper;
|
||||
|
||||
import java.util.List;
|
||||
import com.ruoyi.system.domain.BmsRegionSyncLog;
|
||||
|
||||
/**
|
||||
* 区域数据同步日志 数据层
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
public interface BmsRegionSyncLogMapper
|
||||
{
|
||||
/**
|
||||
* 查询同步日志
|
||||
*
|
||||
* @param logId 日志ID
|
||||
* @return 同步日志
|
||||
*/
|
||||
public BmsRegionSyncLog selectSyncLogById(Long logId);
|
||||
|
||||
/**
|
||||
* 查询同步日志列表
|
||||
*
|
||||
* @param syncLog 同步日志
|
||||
* @return 同步日志集合
|
||||
*/
|
||||
public List<BmsRegionSyncLog> selectSyncLogList(BmsRegionSyncLog syncLog);
|
||||
|
||||
/**
|
||||
* 新增同步日志
|
||||
*
|
||||
* @param syncLog 同步日志
|
||||
* @return 结果
|
||||
*/
|
||||
public int insertSyncLog(BmsRegionSyncLog syncLog);
|
||||
|
||||
/**
|
||||
* 修改同步日志
|
||||
*
|
||||
* @param syncLog 同步日志
|
||||
* @return 结果
|
||||
*/
|
||||
public int updateSyncLog(BmsRegionSyncLog syncLog);
|
||||
|
||||
/**
|
||||
* 批量删除同步日志
|
||||
*
|
||||
* @param logIds 需要删除的日志ID
|
||||
* @return 结果
|
||||
*/
|
||||
public int deleteSyncLogByIds(Long[] logIds);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.ruoyi.system.service;
|
||||
|
||||
import java.util.List;
|
||||
import com.ruoyi.system.domain.BmsRegion;
|
||||
|
||||
/**
|
||||
* 国家/区域 服务层
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
public interface IBmsRegionService
|
||||
{
|
||||
/**
|
||||
* 查询区域信息
|
||||
*
|
||||
* @param regionId 区域ID
|
||||
* @return 区域信息
|
||||
*/
|
||||
public BmsRegion selectRegionById(Long regionId);
|
||||
|
||||
/**
|
||||
* 查询区域列表
|
||||
*
|
||||
* @param region 区域信息
|
||||
* @return 区域集合
|
||||
*/
|
||||
public List<BmsRegion> selectRegionList(BmsRegion region);
|
||||
|
||||
/**
|
||||
* 新增区域
|
||||
*
|
||||
* @param region 区域信息
|
||||
* @return 结果
|
||||
*/
|
||||
public int insertRegion(BmsRegion region);
|
||||
|
||||
/**
|
||||
* 修改区域
|
||||
*
|
||||
* @param region 区域信息
|
||||
* @return 结果
|
||||
*/
|
||||
public int updateRegion(BmsRegion region);
|
||||
|
||||
/**
|
||||
* 批量删除区域
|
||||
*
|
||||
* @param regionIds 需要删除的区域ID
|
||||
* @return 结果
|
||||
*/
|
||||
public int deleteRegionByIds(Long[] regionIds);
|
||||
|
||||
/**
|
||||
* 修改区域状态
|
||||
*
|
||||
* @param region 区域信息
|
||||
* @return 结果
|
||||
*/
|
||||
public int updateRegionStatus(BmsRegion region);
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package com.ruoyi.system.service;
|
||||
|
||||
/**
|
||||
* 区域数据同步 服务层
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
public interface IBmsRegionSyncService
|
||||
{
|
||||
/**
|
||||
* 触发同步国家数据(异步)
|
||||
*
|
||||
* @return 日志ID
|
||||
*/
|
||||
public Long syncRegion();
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
package com.ruoyi.system.service.impl;
|
||||
|
||||
import java.util.List;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.system.domain.BmsRegion;
|
||||
import com.ruoyi.system.mapper.BmsRegionMapper;
|
||||
import com.ruoyi.system.service.IBmsRegionService;
|
||||
|
||||
/**
|
||||
* 国家/区域 服务层实现
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Service
|
||||
public class BmsRegionServiceImpl implements IBmsRegionService
|
||||
{
|
||||
@Autowired
|
||||
private BmsRegionMapper regionMapper;
|
||||
|
||||
/**
|
||||
* 查询区域信息
|
||||
*
|
||||
* @param regionId 区域ID
|
||||
* @return 区域信息
|
||||
*/
|
||||
@Override
|
||||
public BmsRegion selectRegionById(Long regionId)
|
||||
{
|
||||
return regionMapper.selectRegionById(regionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询区域列表
|
||||
*
|
||||
* @param region 区域信息
|
||||
* @return 区域集合
|
||||
*/
|
||||
@Override
|
||||
public List<BmsRegion> selectRegionList(BmsRegion region)
|
||||
{
|
||||
return regionMapper.selectRegionList(region);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增区域
|
||||
*
|
||||
* @param region 区域信息
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public int insertRegion(BmsRegion region)
|
||||
{
|
||||
return regionMapper.insertRegion(region);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改区域
|
||||
*
|
||||
* @param region 区域信息
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public int updateRegion(BmsRegion region)
|
||||
{
|
||||
return regionMapper.updateRegion(region);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除区域
|
||||
*
|
||||
* @param regionIds 需要删除的区域ID
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public int deleteRegionByIds(Long[] regionIds)
|
||||
{
|
||||
return regionMapper.deleteRegionByIds(regionIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改区域状态
|
||||
*
|
||||
* @param region 区域信息
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public int updateRegionStatus(BmsRegion region)
|
||||
{
|
||||
return regionMapper.updateRegion(region);
|
||||
}
|
||||
}
|
||||
+249
@@ -0,0 +1,249 @@
|
||||
package com.ruoyi.system.service.impl;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.system.domain.BmsRegion;
|
||||
import com.ruoyi.system.domain.BmsRegionSyncLog;
|
||||
import com.ruoyi.system.mapper.BmsRegionMapper;
|
||||
import com.ruoyi.system.mapper.BmsRegionSyncLogMapper;
|
||||
import com.ruoyi.system.service.IBmsRegionSyncService;
|
||||
|
||||
/**
|
||||
* 区域数据同步 服务层实现
|
||||
* 从本地 countries.json 文件加载国家数据
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Service
|
||||
public class BmsRegionSyncServiceImpl implements IBmsRegionSyncService
|
||||
{
|
||||
private static final Logger log = LoggerFactory.getLogger(BmsRegionSyncServiceImpl.class);
|
||||
|
||||
private static final String DATA_FILE = "countries.json";
|
||||
|
||||
@Autowired
|
||||
private BmsRegionMapper regionMapper;
|
||||
|
||||
@Autowired
|
||||
private BmsRegionSyncLogMapper syncLogMapper;
|
||||
|
||||
/**
|
||||
* 触发同步国家数据(异步)
|
||||
*
|
||||
* @return 日志ID
|
||||
*/
|
||||
@Override
|
||||
public Long syncRegion()
|
||||
{
|
||||
// 创建同步日志记录
|
||||
BmsRegionSyncLog syncLog = new BmsRegionSyncLog();
|
||||
syncLog.setSyncType("ALL");
|
||||
syncLog.setStatus("PROCESSING");
|
||||
syncLog.setTotalCount(0);
|
||||
syncLog.setInsertCount(0);
|
||||
syncLog.setUpdateCount(0);
|
||||
syncLog.setFailCount(0);
|
||||
syncLog.setDurationMs(0L);
|
||||
syncLogMapper.insertSyncLog(syncLog);
|
||||
|
||||
// 异步执行同步
|
||||
asyncSync(syncLog.getLogId());
|
||||
|
||||
return syncLog.getLogId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步同步国家数据
|
||||
*
|
||||
* @param logId 日志ID
|
||||
*/
|
||||
@Async("threadPoolTaskExecutor")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void asyncSync(Long logId)
|
||||
{
|
||||
long startTime = System.currentTimeMillis();
|
||||
BmsRegionSyncLog syncLog = syncLogMapper.selectSyncLogById(logId);
|
||||
|
||||
try
|
||||
{
|
||||
log.info("开始同步国家数据,读取本地文件: {}", DATA_FILE);
|
||||
|
||||
// 从classpath读取国家数据文件
|
||||
ClassPathResource resource = new ClassPathResource(DATA_FILE);
|
||||
InputStream inputStream = resource.getInputStream();
|
||||
byte[] bytes = inputStream.readAllBytes();
|
||||
inputStream.close();
|
||||
String jsonData = new String(bytes, StandardCharsets.UTF_8);
|
||||
|
||||
if (StringUtils.isEmpty(jsonData))
|
||||
{
|
||||
throw new RuntimeException("国家数据文件为空");
|
||||
}
|
||||
|
||||
// 解析JSON
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
List<Object> countryList = objectMapper.readValue(jsonData, List.class);
|
||||
|
||||
int insertCount = 0;
|
||||
int updateCount = 0;
|
||||
int failCount = 0;
|
||||
|
||||
for (Object obj : countryList)
|
||||
{
|
||||
try
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> country = (Map<String, Object>) obj;
|
||||
BmsRegion region = parseCountry(country);
|
||||
|
||||
if (region == null || StringUtils.isEmpty(region.getCountryCode2()))
|
||||
{
|
||||
failCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检查是否已存在
|
||||
BmsRegion existing = regionMapper.selectRegionByCode2(region.getCountryCode2());
|
||||
if (existing != null)
|
||||
{
|
||||
region.setRegionId(existing.getRegionId());
|
||||
regionMapper.updateRegion(region);
|
||||
updateCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
regionMapper.insertRegion(region);
|
||||
insertCount++;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
failCount++;
|
||||
log.warn("解析国家数据失败: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 更新同步日志
|
||||
long duration = System.currentTimeMillis() - startTime;
|
||||
syncLog.setStatus("SUCCESS");
|
||||
syncLog.setTotalCount(countryList.size());
|
||||
syncLog.setInsertCount(insertCount);
|
||||
syncLog.setUpdateCount(updateCount);
|
||||
syncLog.setFailCount(failCount);
|
||||
syncLog.setDurationMs(duration);
|
||||
syncLogMapper.updateSyncLog(syncLog);
|
||||
|
||||
log.info("同步国家数据完成: 总数={}, 新增={}, 更新={}, 失败={}, 耗时={}ms",
|
||||
countryList.size(), insertCount, updateCount, failCount, duration);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
long duration = System.currentTimeMillis() - startTime;
|
||||
syncLog.setStatus("FAILED");
|
||||
syncLog.setDurationMs(duration);
|
||||
syncLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
|
||||
syncLogMapper.updateSyncLog(syncLog);
|
||||
|
||||
log.error("同步国家数据失败: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析国家数据
|
||||
*
|
||||
* @param country 国家Map
|
||||
* @return 区域实体
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private BmsRegion parseCountry(Map<String, Object> country)
|
||||
{
|
||||
BmsRegion region = new BmsRegion();
|
||||
region.setStatus("0");
|
||||
region.setIsIndependent("1");
|
||||
region.setIsEu("0");
|
||||
|
||||
// 英文名 name.common
|
||||
Map<String, Object> name = (Map<String, Object>) country.get("name");
|
||||
if (name != null)
|
||||
{
|
||||
region.setCountryNameEn((String) name.get("common"));
|
||||
}
|
||||
|
||||
// 中文名 translations.cmn.common,如无则用英文名
|
||||
Map<String, Object> translations = (Map<String, Object>) country.get("translations");
|
||||
if (translations != null)
|
||||
{
|
||||
Map<String, Object> cmn = (Map<String, Object>) translations.get("cmn");
|
||||
if (cmn != null)
|
||||
{
|
||||
region.setCountryNameCn((String) cmn.get("common"));
|
||||
}
|
||||
}
|
||||
if (StringUtils.isEmpty(region.getCountryNameCn()))
|
||||
{
|
||||
region.setCountryNameCn(region.getCountryNameEn());
|
||||
}
|
||||
|
||||
// ISO代码
|
||||
region.setCountryCode2((String) country.get("cca2"));
|
||||
region.setCountryCode3((String) country.get("cca3"));
|
||||
|
||||
// 大洲/子区域
|
||||
region.setContinent((String) country.get("region"));
|
||||
region.setSubRegion((String) country.get("subregion"));
|
||||
|
||||
// 货币
|
||||
Map<String, Object> currencies = (Map<String, Object>) country.get("currencies");
|
||||
if (currencies != null && !currencies.isEmpty())
|
||||
{
|
||||
Map.Entry<String, Object> entry = currencies.entrySet().iterator().next();
|
||||
region.setCurrencyCode(entry.getKey());
|
||||
Map<String, Object> currency = (Map<String, Object>) entry.getValue();
|
||||
if (currency != null)
|
||||
{
|
||||
region.setCurrencyName((String) currency.get("name"));
|
||||
region.setCurrencySymbol((String) currency.get("symbol"));
|
||||
}
|
||||
}
|
||||
|
||||
// 电话前缀 idd.root + idd.suffixes
|
||||
Map<String, Object> idd = (Map<String, Object>) country.get("idd");
|
||||
if (idd != null)
|
||||
{
|
||||
String root = (String) idd.get("root");
|
||||
List<String> suffixes = (List<String>) idd.get("suffixes");
|
||||
if (StringUtils.isNotEmpty(root) && suffixes != null && !suffixes.isEmpty())
|
||||
{
|
||||
region.setPhonePrefix(root + suffixes.get(0));
|
||||
}
|
||||
}
|
||||
|
||||
// 国旗
|
||||
region.setFlagEmoji((String) country.get("flag"));
|
||||
Map<String, Object> flags = (Map<String, Object>) country.get("flags");
|
||||
if (flags != null)
|
||||
{
|
||||
region.setFlagUrl((String) flags.get("png"));
|
||||
}
|
||||
|
||||
// 是否独立
|
||||
Boolean independent = (Boolean) country.get("independent");
|
||||
if (independent != null)
|
||||
{
|
||||
region.setIsIndependent(independent ? "1" : "0");
|
||||
}
|
||||
|
||||
return region;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
<?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.ruoyi.system.mapper.BmsRegionMapper">
|
||||
|
||||
<resultMap type="BmsRegion" id="BmsRegionResult">
|
||||
<id property="regionId" column="region_id" />
|
||||
<result property="countryNameCn" column="country_name_cn" />
|
||||
<result property="countryNameEn" column="country_name_en" />
|
||||
<result property="countryCode2" column="country_code_2" />
|
||||
<result property="countryCode3" column="country_code_3" />
|
||||
<result property="continent" column="continent" />
|
||||
<result property="subRegion" column="sub_region" />
|
||||
<result property="currencyCode" column="currency_code" />
|
||||
<result property="currencyName" column="currency_name" />
|
||||
<result property="currencySymbol" column="currency_symbol" />
|
||||
<result property="phonePrefix" column="phone_prefix" />
|
||||
<result property="flagEmoji" column="flag_emoji" />
|
||||
<result property="flagUrl" column="flag_url" />
|
||||
<result property="isIndependent" column="is_independent" />
|
||||
<result property="isEu" column="is_eu" />
|
||||
<result property="status" column="status" />
|
||||
<result property="delFlag" column="del_flag" />
|
||||
<result property="createBy" column="create_by" />
|
||||
<result property="createTime" column="create_time" />
|
||||
<result property="updateBy" column="update_by" />
|
||||
<result property="updateTime" column="update_time" />
|
||||
<result property="remark" column="remark" />
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectRegionVo">
|
||||
select region_id, country_name_cn, country_name_en, country_code_2, country_code_3,
|
||||
continent, sub_region, currency_code, currency_name, currency_symbol,
|
||||
phone_prefix, flag_emoji, flag_url, is_independent, is_eu,
|
||||
status, del_flag, create_by, create_time, update_by, update_time, remark
|
||||
from bms_region
|
||||
</sql>
|
||||
|
||||
<select id="selectRegionById" parameterType="Long" resultMap="BmsRegionResult">
|
||||
<include refid="selectRegionVo"/>
|
||||
where region_id = #{regionId}
|
||||
</select>
|
||||
|
||||
<select id="selectRegionByCode2" parameterType="String" resultMap="BmsRegionResult">
|
||||
<include refid="selectRegionVo"/>
|
||||
where country_code_2 = #{countryCode2} limit 1
|
||||
</select>
|
||||
|
||||
<select id="selectRegionList" parameterType="BmsRegion" resultMap="BmsRegionResult">
|
||||
<include refid="selectRegionVo"/>
|
||||
<where>
|
||||
del_flag = '0'
|
||||
<if test="countryNameCn != null and countryNameCn != ''">
|
||||
AND country_name_cn like concat('%', #{countryNameCn}, '%')
|
||||
</if>
|
||||
<if test="countryNameEn != null and countryNameEn != ''">
|
||||
AND country_name_en like concat('%', #{countryNameEn}, '%')
|
||||
</if>
|
||||
<if test="countryCode2 != null and countryCode2 != ''">
|
||||
AND country_code_2 like concat('%', #{countryCode2}, '%')
|
||||
</if>
|
||||
<if test="continent != null and continent != ''">
|
||||
AND continent = #{continent}
|
||||
</if>
|
||||
<if test="status != null and status != ''">
|
||||
AND status = #{status}
|
||||
</if>
|
||||
</where>
|
||||
order by region_id
|
||||
</select>
|
||||
|
||||
<insert id="insertRegion" parameterType="BmsRegion" useGeneratedKeys="true" keyProperty="regionId">
|
||||
insert into bms_region (
|
||||
<if test="countryNameCn != null">country_name_cn,</if>
|
||||
<if test="countryNameEn != null and countryNameEn != ''">country_name_en,</if>
|
||||
<if test="countryCode2 != null and countryCode2 != ''">country_code_2,</if>
|
||||
<if test="countryCode3 != null">country_code_3,</if>
|
||||
<if test="continent != null">continent,</if>
|
||||
<if test="subRegion != null">sub_region,</if>
|
||||
<if test="currencyCode != null">currency_code,</if>
|
||||
<if test="currencyName != null">currency_name,</if>
|
||||
<if test="currencySymbol != null">currency_symbol,</if>
|
||||
<if test="phonePrefix != null">phone_prefix,</if>
|
||||
<if test="flagEmoji != null">flag_emoji,</if>
|
||||
<if test="flagUrl != null">flag_url,</if>
|
||||
<if test="isIndependent != null and isIndependent != ''">is_independent,</if>
|
||||
<if test="isEu != null and isEu != ''">is_eu,</if>
|
||||
<if test="status != null and status != ''">status,</if>
|
||||
<if test="delFlag != null and delFlag != ''">del_flag,</if>
|
||||
<if test="createBy != null and createBy != ''">create_by,</if>
|
||||
<if test="remark != null">remark,</if>
|
||||
create_time
|
||||
) values (
|
||||
<if test="countryNameCn != null">#{countryNameCn},</if>
|
||||
<if test="countryNameEn != null and countryNameEn != ''">#{countryNameEn},</if>
|
||||
<if test="countryCode2 != null and countryCode2 != ''">#{countryCode2},</if>
|
||||
<if test="countryCode3 != null">#{countryCode3},</if>
|
||||
<if test="continent != null">#{continent},</if>
|
||||
<if test="subRegion != null">#{subRegion},</if>
|
||||
<if test="currencyCode != null">#{currencyCode},</if>
|
||||
<if test="currencyName != null">#{currencyName},</if>
|
||||
<if test="currencySymbol != null">#{currencySymbol},</if>
|
||||
<if test="phonePrefix != null">#{phonePrefix},</if>
|
||||
<if test="flagEmoji != null">#{flagEmoji},</if>
|
||||
<if test="flagUrl != null">#{flagUrl},</if>
|
||||
<if test="isIndependent != null and isIndependent != ''">#{isIndependent},</if>
|
||||
<if test="isEu != null and isEu != ''">#{isEu},</if>
|
||||
<if test="status != null and status != ''">#{status},</if>
|
||||
<if test="delFlag != null and delFlag != ''">#{delFlag},</if>
|
||||
<if test="createBy != null and createBy != ''">#{createBy},</if>
|
||||
<if test="remark != null">#{remark},</if>
|
||||
sysdate()
|
||||
)
|
||||
</insert>
|
||||
|
||||
<update id="updateRegion" parameterType="BmsRegion">
|
||||
update bms_region
|
||||
<set>
|
||||
<if test="countryNameCn != null">country_name_cn = #{countryNameCn},</if>
|
||||
<if test="countryNameEn != null and countryNameEn != ''">country_name_en = #{countryNameEn},</if>
|
||||
<if test="countryCode2 != null and countryCode2 != ''">country_code_2 = #{countryCode2},</if>
|
||||
<if test="countryCode3 != null">country_code_3 = #{countryCode3},</if>
|
||||
<if test="continent != null">continent = #{continent},</if>
|
||||
<if test="subRegion != null">sub_region = #{subRegion},</if>
|
||||
<if test="currencyCode != null">currency_code = #{currencyCode},</if>
|
||||
<if test="currencyName != null">currency_name = #{currencyName},</if>
|
||||
<if test="currencySymbol != null">currency_symbol = #{currencySymbol},</if>
|
||||
<if test="phonePrefix != null">phone_prefix = #{phonePrefix},</if>
|
||||
<if test="flagEmoji != null">flag_emoji = #{flagEmoji},</if>
|
||||
<if test="flagUrl != null">flag_url = #{flagUrl},</if>
|
||||
<if test="isIndependent != null and isIndependent != ''">is_independent = #{isIndependent},</if>
|
||||
<if test="isEu != null and isEu != ''">is_eu = #{isEu},</if>
|
||||
<if test="status != null and status != ''">status = #{status},</if>
|
||||
<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
|
||||
<if test="remark != null">remark = #{remark},</if>
|
||||
update_time = sysdate()
|
||||
</set>
|
||||
where region_id = #{regionId}
|
||||
</update>
|
||||
|
||||
<delete id="deleteRegionByIds" parameterType="Long">
|
||||
delete from bms_region where region_id in
|
||||
<foreach item="regionId" collection="array" open="(" separator="," close=")">
|
||||
#{regionId}
|
||||
</foreach>
|
||||
</delete>
|
||||
|
||||
<insert id="upsertByCode2" parameterType="BmsRegion">
|
||||
insert into bms_region (
|
||||
country_name_cn, country_name_en, country_code_2, country_code_3,
|
||||
continent, sub_region, currency_code, currency_name, currency_symbol,
|
||||
phone_prefix, flag_emoji, flag_url, is_independent, is_eu,
|
||||
status, del_flag, create_by, create_time
|
||||
) values (
|
||||
#{countryNameCn}, #{countryNameEn}, #{countryCode2}, #{countryCode3},
|
||||
#{continent}, #{subRegion}, #{currencyCode}, #{currencyName}, #{currencySymbol},
|
||||
#{phonePrefix}, #{flagEmoji}, #{flagUrl}, #{isIndependent}, #{isEu},
|
||||
#{status}, '0', #{createBy}, sysdate()
|
||||
)
|
||||
on duplicate key update
|
||||
country_name_cn = values(country_name_cn),
|
||||
country_name_en = values(country_name_en),
|
||||
country_code_3 = values(country_code_3),
|
||||
continent = values(continent),
|
||||
sub_region = values(sub_region),
|
||||
currency_code = values(currency_code),
|
||||
currency_name = values(currency_name),
|
||||
currency_symbol = values(currency_symbol),
|
||||
phone_prefix = values(phone_prefix),
|
||||
flag_emoji = values(flag_emoji),
|
||||
flag_url = values(flag_url),
|
||||
is_independent = values(is_independent),
|
||||
is_eu = values(is_eu),
|
||||
update_by = #{updateBy},
|
||||
update_time = sysdate()
|
||||
</insert>
|
||||
|
||||
</mapper>
|
||||
@@ -0,0 +1,101 @@
|
||||
<?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.ruoyi.system.mapper.BmsRegionSyncLogMapper">
|
||||
|
||||
<resultMap type="BmsRegionSyncLog" id="BmsRegionSyncLogResult">
|
||||
<id property="logId" column="log_id" />
|
||||
<result property="syncType" column="sync_type" />
|
||||
<result property="status" column="status" />
|
||||
<result property="totalCount" column="total_count" />
|
||||
<result property="insertCount" column="insert_count" />
|
||||
<result property="updateCount" column="update_count" />
|
||||
<result property="failCount" column="fail_count" />
|
||||
<result property="durationMs" column="duration_ms" />
|
||||
<result property="errorMsg" column="error_msg" />
|
||||
<result property="createBy" column="create_by" />
|
||||
<result property="createTime" column="create_time" />
|
||||
<result property="updateBy" column="update_by" />
|
||||
<result property="updateTime" column="update_time" />
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectSyncLogVo">
|
||||
select log_id, sync_type, status, total_count, insert_count, update_count,
|
||||
fail_count, duration_ms, error_msg, create_by, create_time, update_by, update_time
|
||||
from bms_region_sync_log
|
||||
</sql>
|
||||
|
||||
<select id="selectSyncLogById" parameterType="Long" resultMap="BmsRegionSyncLogResult">
|
||||
<include refid="selectSyncLogVo"/>
|
||||
where log_id = #{logId}
|
||||
</select>
|
||||
|
||||
<select id="selectSyncLogList" parameterType="BmsRegionSyncLog" resultMap="BmsRegionSyncLogResult">
|
||||
<include refid="selectSyncLogVo"/>
|
||||
<where>
|
||||
<if test="syncType != null and syncType != ''">
|
||||
AND sync_type = #{syncType}
|
||||
</if>
|
||||
<if test="status != null and status != ''">
|
||||
AND status = #{status}
|
||||
</if>
|
||||
<if test="params.beginTime != null and params.beginTime != ''">
|
||||
and date_format(create_time,'%Y%m%d') >= date_format(#{params.beginTime},'%Y%m%d')
|
||||
</if>
|
||||
<if test="params.endTime != null and params.endTime != ''">
|
||||
and date_format(create_time,'%Y%m%d') <= date_format(#{params.endTime},'%Y%m%d')
|
||||
</if>
|
||||
</where>
|
||||
order by log_id desc
|
||||
</select>
|
||||
|
||||
<insert id="insertSyncLog" parameterType="BmsRegionSyncLog" useGeneratedKeys="true" keyProperty="logId">
|
||||
insert into bms_region_sync_log (
|
||||
<if test="syncType != null and syncType != ''">sync_type,</if>
|
||||
<if test="status != null and status != ''">status,</if>
|
||||
<if test="totalCount != null">total_count,</if>
|
||||
<if test="insertCount != null">insert_count,</if>
|
||||
<if test="updateCount != null">update_count,</if>
|
||||
<if test="failCount != null">fail_count,</if>
|
||||
<if test="durationMs != null">duration_ms,</if>
|
||||
<if test="errorMsg != null">error_msg,</if>
|
||||
<if test="createBy != null and createBy != ''">create_by,</if>
|
||||
create_time
|
||||
) values (
|
||||
<if test="syncType != null and syncType != ''">#{syncType},</if>
|
||||
<if test="status != null and status != ''">#{status},</if>
|
||||
<if test="totalCount != null">#{totalCount},</if>
|
||||
<if test="insertCount != null">#{insertCount},</if>
|
||||
<if test="updateCount != null">#{updateCount},</if>
|
||||
<if test="failCount != null">#{failCount},</if>
|
||||
<if test="durationMs != null">#{durationMs},</if>
|
||||
<if test="errorMsg != null">#{errorMsg},</if>
|
||||
<if test="createBy != null and createBy != ''">#{createBy},</if>
|
||||
sysdate()
|
||||
)
|
||||
</insert>
|
||||
|
||||
<update id="updateSyncLog" parameterType="BmsRegionSyncLog">
|
||||
update bms_region_sync_log
|
||||
<set>
|
||||
<if test="status != null and status != ''">status = #{status},</if>
|
||||
<if test="totalCount != null">total_count = #{totalCount},</if>
|
||||
<if test="insertCount != null">insert_count = #{insertCount},</if>
|
||||
<if test="updateCount != null">update_count = #{updateCount},</if>
|
||||
<if test="failCount != null">fail_count = #{failCount},</if>
|
||||
<if test="durationMs != null">duration_ms = #{durationMs},</if>
|
||||
<if test="errorMsg != null">error_msg = #{errorMsg},</if>
|
||||
update_time = sysdate()
|
||||
</set>
|
||||
where log_id = #{logId}
|
||||
</update>
|
||||
|
||||
<delete id="deleteSyncLogByIds" parameterType="Long">
|
||||
delete from bms_region_sync_log where log_id in
|
||||
<foreach item="logId" collection="array" open="(" separator="," close=")">
|
||||
#{logId}
|
||||
</foreach>
|
||||
</delete>
|
||||
|
||||
</mapper>
|
||||
Reference in New Issue
Block a user