1.调整导出逻辑
2.增加导出导入日志管理页面 3.增加配置表
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
package com.njcn.relational.config;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.DbType;
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class MybatisPlusConfig {
|
||||
|
||||
/**
|
||||
* MyBatis-Plus 分页插件配置
|
||||
* 必须配置,否则分页不生效
|
||||
*/
|
||||
@Bean
|
||||
public MybatisPlusInterceptor mybatisPlusInterceptor() {
|
||||
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
|
||||
// 添加分页插件(根据数据库类型选择)
|
||||
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.ORACLE);
|
||||
// 或 DbType.MYSQL / DbType.POSTGRE_SQL 等
|
||||
|
||||
// 设置最大单页限制(默认500,-1表示不限制)
|
||||
paginationInnerInterceptor.setMaxLimit(1000L);
|
||||
// 设置溢出总页数后是否跳转到第一页(默认false)
|
||||
paginationInnerInterceptor.setOverflow(false);
|
||||
|
||||
interceptor.addInnerInterceptor(paginationInnerInterceptor);
|
||||
return interceptor;
|
||||
}
|
||||
}
|
||||
@@ -16,4 +16,5 @@ public class Security extends WebSecurityConfigurerAdapter {
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -3,22 +3,21 @@ package com.njcn.relational.controller;
|
||||
|
||||
import cn.hutool.core.date.DatePattern;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.njcn.relational.service.SyncTableConfigService;
|
||||
import com.njcn.relational.service.SyncTableParseService;
|
||||
import com.njcn.relational.service.SyncTwoDmExportService;
|
||||
import com.njcn.relational.service.SyncThreeDmParseService;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -34,15 +33,19 @@ import java.util.Map;
|
||||
@Slf4j
|
||||
public class DataTransportController {
|
||||
|
||||
private final SyncTableConfigService syncTableConfigService;
|
||||
private final SyncTwoDmExportService syncTwoDmExportService;
|
||||
|
||||
private final SyncTableParseService syncTableParseService;
|
||||
private final SyncThreeDmParseService syncThreeDmParseService;
|
||||
|
||||
@Value("${spring.profiles.active:query}")
|
||||
private String serverType;
|
||||
|
||||
@GetMapping("export")
|
||||
public void export(@RequestParam(required = false)String date){
|
||||
if(!serverType.contains("query")){
|
||||
return;
|
||||
}
|
||||
|
||||
if(StrUtil.isBlank(date)){
|
||||
date = LocalDate.now().minusDays(1).format(DateTimeFormatter.ofPattern(DatePattern.PURE_DATE_PATTERN));
|
||||
}else {
|
||||
@@ -51,11 +54,14 @@ public class DataTransportController {
|
||||
return;
|
||||
}
|
||||
}
|
||||
syncTableConfigService.syncAllFullTables(date);
|
||||
syncTwoDmExportService.syncAllFullTables(date);
|
||||
}
|
||||
|
||||
@GetMapping("import")
|
||||
public void importData(@RequestParam(required = false)String date){
|
||||
if(!serverType.contains("insert")){
|
||||
return;
|
||||
}
|
||||
if(StrUtil.isBlank(date)){
|
||||
date = LocalDate.now().minusDays(1).format(DateTimeFormatter.ofPattern(DatePattern.PURE_DATE_PATTERN));
|
||||
}else {
|
||||
@@ -65,7 +71,7 @@ public class DataTransportController {
|
||||
}
|
||||
}
|
||||
try {
|
||||
syncTableParseService.syncFullTables(date);
|
||||
syncThreeDmParseService.syncFullTables(date);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
@@ -73,18 +79,24 @@ public class DataTransportController {
|
||||
|
||||
@GetMapping("test")
|
||||
public void test(){
|
||||
syncTableConfigService.test();
|
||||
if(!serverType.contains("query")){
|
||||
return;
|
||||
}
|
||||
syncTwoDmExportService.test();
|
||||
}
|
||||
|
||||
@GetMapping("testR")
|
||||
public void testR(){
|
||||
syncTableParseService.testR();
|
||||
if(!serverType.contains("insert")){
|
||||
return;
|
||||
}
|
||||
syncThreeDmParseService.testR();
|
||||
}
|
||||
|
||||
@GetMapping("getRemoteTwoAll")
|
||||
public List<String> getRemoteTwoAll(){
|
||||
try {
|
||||
return syncTableConfigService.getPendingFiles();
|
||||
return syncTwoDmExportService.getPendingFiles();
|
||||
} catch (Exception e) {
|
||||
log.error("最终捕获二区异常",e);
|
||||
throw new RuntimeException(e);
|
||||
@@ -94,17 +106,24 @@ public class DataTransportController {
|
||||
@GetMapping("getRemoteAll")
|
||||
public List<String> getAll(){
|
||||
try {
|
||||
return syncTableParseService.getPendingFiles();
|
||||
if("query_up".equals(serverType)){
|
||||
return syncTwoDmExportService.getPendingFiles();
|
||||
}else if("insert_up".equals(serverType)){
|
||||
return syncThreeDmParseService.getPendingFiles();
|
||||
}else {
|
||||
return new ArrayList<>();
|
||||
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("最终捕获异常",e);
|
||||
throw new RuntimeException(e);
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("removeTwo")
|
||||
public String removeTwo(@RequestParam Integer a){
|
||||
try {
|
||||
syncTableConfigService.cleanLocalFiles(a);
|
||||
syncTwoDmExportService.cleanLocalFiles(a);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@@ -115,7 +134,7 @@ public class DataTransportController {
|
||||
@GetMapping("removeThree")
|
||||
public String removeThree(@RequestParam Integer a){
|
||||
try {
|
||||
syncTableParseService.cleanLocalFiles(a);
|
||||
syncThreeDmParseService.cleanLocalFiles(a);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.njcn.relational.controller;
|
||||
|
||||
import com.njcn.relational.pojo.dto.DcloudSyncQuery;
|
||||
import com.njcn.relational.service.DcloudBusService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
@@ -18,8 +19,27 @@ public class DcloudBusController {
|
||||
|
||||
private final DcloudBusService dcloudBusService;
|
||||
|
||||
@GetMapping("/sync")
|
||||
@GetMapping("/sync1")
|
||||
public void syncData() {
|
||||
dcloudBusService.syncDataToDb();
|
||||
DcloudSyncQuery query = DcloudSyncQuery.buildPlantDefault(null, null, null, null);
|
||||
dcloudBusService.syncDataToDb(query);
|
||||
}
|
||||
|
||||
@GetMapping("/sync2")
|
||||
public void syncData2() {
|
||||
DcloudSyncQuery query = DcloudSyncQuery.buildPlantChangZhan(null, null, null, null);
|
||||
dcloudBusService.syncDataToDb(query);
|
||||
}
|
||||
|
||||
@GetMapping("/sync3")
|
||||
public void syncData3() {
|
||||
DcloudSyncQuery query = DcloudSyncQuery.buildPlantBdB(null, null, null, null);
|
||||
dcloudBusService.syncDataToDb(query);
|
||||
}
|
||||
|
||||
@GetMapping("/sync4")
|
||||
public void syncData4() {
|
||||
DcloudSyncQuery query = DcloudSyncQuery.buildPlantBdC(null, null, null, null);
|
||||
dcloudBusService.syncDataToDb(query);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.njcn.relational.controller;
|
||||
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
/**
|
||||
* @Author: cdf
|
||||
* @CreateTime: 2026-06-28
|
||||
* @Description:
|
||||
*/
|
||||
@Controller
|
||||
@Slf4j
|
||||
public class IndexController {
|
||||
|
||||
@GetMapping("/")
|
||||
public String index() {
|
||||
log.info("访问根路径,跳转到首页");
|
||||
return "forward:/pages/index.html";
|
||||
}
|
||||
|
||||
@GetMapping("/index")
|
||||
public String indexPage() {
|
||||
log.info("访问 /index,跳转到首页");
|
||||
return "forward:/pages/index.html";
|
||||
}
|
||||
|
||||
@GetMapping("/config")
|
||||
public String configPage() {
|
||||
return "forward:/pages/config.html";
|
||||
}
|
||||
|
||||
@GetMapping("/logs")
|
||||
public String logs() {
|
||||
return "forward:/pages/logs.html";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.njcn.relational.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.njcn.relational.pojo.po.SGConPlantB;
|
||||
import com.njcn.relational.service.SGConPlantBService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/plant")
|
||||
@RequiredArgsConstructor
|
||||
public class SGConPlantBController {
|
||||
|
||||
private final SGConPlantBService sgConPlantBService;
|
||||
|
||||
/**
|
||||
* 新增场站
|
||||
*/
|
||||
@PostMapping("/add")
|
||||
public Boolean add(@RequestBody SGConPlantB entity) {
|
||||
return sgConPlantBService.save(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID删除
|
||||
*/
|
||||
@DeleteMapping("/remove/{id}")
|
||||
public Boolean remove(@PathVariable String id) {
|
||||
return sgConPlantBService.removeById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
*/
|
||||
@DeleteMapping("/batch")
|
||||
public Boolean batchRemove(@RequestBody List<String> idList) {
|
||||
return sgConPlantBService.removeByIds(idList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改场站
|
||||
*/
|
||||
@PutMapping("/update")
|
||||
public Boolean update(@RequestBody SGConPlantB entity) {
|
||||
return sgConPlantBService.updateById(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID查询单条
|
||||
*/
|
||||
@GetMapping("/get/{id}")
|
||||
public SGConPlantB getById(@PathVariable String id) {
|
||||
return sgConPlantBService.getById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
*/
|
||||
@GetMapping("/page")
|
||||
public IPage<SGConPlantB> page(
|
||||
@RequestParam(defaultValue = "1") long pageNum,
|
||||
@RequestParam(defaultValue = "10") long pageSize,
|
||||
SGConPlantB query
|
||||
) {
|
||||
Page<SGConPlantB> page = new Page<>(pageNum, pageSize);
|
||||
LambdaQueryWrapper<SGConPlantB> wrapper = new LambdaQueryWrapper<>();
|
||||
// 可根据业务自行拼接模糊/等值条件示例
|
||||
wrapper.like(query.getName() != null, SGConPlantB::getName, query.getName());
|
||||
wrapper.eq(query.getPlantType() != null, SGConPlantB::getPlantType, query.getPlantType());
|
||||
wrapper.eq(query.getConnectivePgId() != null, SGConPlantB::getConnectivePgId, query.getConnectivePgId());
|
||||
return sgConPlantBService.page(page, wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询全部列表(不分页)
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
public List<SGConPlantB> list(SGConPlantB query) {
|
||||
LambdaQueryWrapper<SGConPlantB> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.like(query.getName() != null, SGConPlantB::getName, query.getName());
|
||||
return sgConPlantBService.list(wrapper);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package com.njcn.relational.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.njcn.relational.pojo.po.SGConSubstationB;
|
||||
import com.njcn.relational.service.SGConSubstationBService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/substation")
|
||||
@RequiredArgsConstructor
|
||||
public class SGConSubstationBController {
|
||||
|
||||
private final SGConSubstationBService substationBService;
|
||||
|
||||
/**
|
||||
* 新增变电站
|
||||
*/
|
||||
@PostMapping("/add")
|
||||
public Boolean add(@RequestBody SGConSubstationB entity) {
|
||||
return substationBService.save(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID删除
|
||||
*/
|
||||
@DeleteMapping("/remove/{id}")
|
||||
public Boolean remove(@PathVariable String id) {
|
||||
return substationBService.removeById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
*/
|
||||
@DeleteMapping("/batch")
|
||||
public Boolean batchRemove(@RequestBody List<String> idList) {
|
||||
return substationBService.removeByIds(idList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改变电站
|
||||
*/
|
||||
@PutMapping("/update")
|
||||
public Boolean update(@RequestBody SGConSubstationB entity) {
|
||||
return substationBService.updateById(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID单条查询
|
||||
*/
|
||||
@GetMapping("/get/{id}")
|
||||
public SGConSubstationB getById(@PathVariable String id) {
|
||||
return substationBService.getById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
*/
|
||||
@GetMapping("/page")
|
||||
public IPage<SGConSubstationB> page(
|
||||
@RequestParam(defaultValue = "1") long pageNum,
|
||||
@RequestParam(defaultValue = "10") long pageSize,
|
||||
SGConSubstationB query
|
||||
) {
|
||||
Page<SGConSubstationB> page = new Page<>(pageNum, pageSize);
|
||||
LambdaQueryWrapper<SGConSubstationB> wrapper = new LambdaQueryWrapper<>();
|
||||
|
||||
// 常用查询条件示例
|
||||
wrapper.like(query.getName() != null, SGConSubstationB::getName, query.getName());
|
||||
wrapper.eq(query.getDccId() != null, SGConSubstationB::getDccId, query.getDccId());
|
||||
wrapper.eq(query.getPgId() != null, SGConSubstationB::getPgId, query.getPgId());
|
||||
wrapper.eq(query.getTopAcVoltageType() != null, SGConSubstationB::getTopAcVoltageType, query.getTopAcVoltageType());
|
||||
wrapper.eq(query.getOperateState() != null, SGConSubstationB::getOperateState, query.getOperateState());
|
||||
|
||||
return substationBService.page(page, wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 不分页全量列表
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
public List<SGConSubstationB> list(SGConSubstationB query) {
|
||||
LambdaQueryWrapper<SGConSubstationB> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.like(query.getName() != null, SGConSubstationB::getName, query.getName());
|
||||
wrapper.eq(query.getDccId() != null, SGConSubstationB::getDccId, query.getDccId());
|
||||
return substationBService.list(wrapper);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.njcn.relational.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.njcn.relational.pojo.po.SGConSubstationBind;
|
||||
import com.njcn.relational.service.impl.SGConSubstationCServiceImpl;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/substation/map")
|
||||
@RequiredArgsConstructor
|
||||
public class SGConSubstationCController {
|
||||
|
||||
private final SGConSubstationCServiceImpl substationCService;
|
||||
|
||||
/**
|
||||
* 新增多系统映射记录
|
||||
*/
|
||||
@PostMapping("/add")
|
||||
public Boolean add(@RequestBody SGConSubstationBind entity) {
|
||||
// 新增默认未删除
|
||||
entity.setIsDelete(0);
|
||||
return substationCService.save(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据联合主键查询
|
||||
*/
|
||||
@GetMapping("/get")
|
||||
public SGConSubstationBind get(
|
||||
@RequestParam String dccId,
|
||||
@RequestParam String dcloudId
|
||||
) {
|
||||
return substationCService.getByUnionKey(dccId, dcloudId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改映射记录
|
||||
*/
|
||||
@PutMapping("/update")
|
||||
public Boolean update(@RequestBody SGConSubstationBind entity) {
|
||||
return substationCService.updateById(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 逻辑删除(软删)
|
||||
*/
|
||||
@DeleteMapping("/logicRemove")
|
||||
public Boolean logicRemove(
|
||||
@RequestParam String dccId,
|
||||
@RequestParam String dcloudId
|
||||
) {
|
||||
return substationCService.logicRemoveByUnionKey(dccId, dcloudId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询映射关系
|
||||
*/
|
||||
@GetMapping("/page")
|
||||
public IPage<SGConSubstationBind> page(
|
||||
@RequestParam(defaultValue = "1") long pageNum,
|
||||
@RequestParam(defaultValue = "10") long pageSize,
|
||||
SGConSubstationBind query
|
||||
) {
|
||||
Page<SGConSubstationBind> page = new Page<>(pageNum, pageSize);
|
||||
LambdaQueryWrapper<SGConSubstationBind> wrapper = new LambdaQueryWrapper<>();
|
||||
// 默认只查正常未删除数据
|
||||
wrapper.eq(SGConSubstationBind::getIsDelete, 0);
|
||||
|
||||
wrapper.eq(query.getDccId() != null, SGConSubstationBind::getDccId, query.getDccId());
|
||||
wrapper.eq(query.getDcloudId() != null, SGConSubstationBind::getDcloudId, query.getDcloudId());
|
||||
wrapper.like(query.getDcloudName() != null, SGConSubstationBind::getDcloudName, query.getDcloudName());
|
||||
wrapper.eq(query.getDcloudVoltagelevel() != null, SGConSubstationBind::getDcloudVoltagelevel, query.getDcloudVoltagelevel());
|
||||
wrapper.eq(query.getD5000Id() != null, SGConSubstationBind::getD5000Id, query.getD5000Id());
|
||||
wrapper.eq(query.getOmsId() != null, SGConSubstationBind::getOmsId, query.getOmsId());
|
||||
wrapper.eq(query.getStatus() != null, SGConSubstationBind::getStatus, query.getStatus());
|
||||
|
||||
return substationCService.page(page, wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 不分页列表
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
public List<SGConSubstationBind> list(SGConSubstationBind query) {
|
||||
LambdaQueryWrapper<SGConSubstationBind> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(SGConSubstationBind::getIsDelete, 0);
|
||||
wrapper.eq(query.getDccId() != null, SGConSubstationBind::getDccId, query.getDccId());
|
||||
return substationCService.list(wrapper);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package com.njcn.relational.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.njcn.relational.pojo.po.SyncTableConfig;
|
||||
import com.njcn.relational.pojo.vo.HttpResult;
|
||||
import com.njcn.relational.service.SyncTableConfigService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 同步表配置控制器
|
||||
*
|
||||
* @author cdf
|
||||
* @date 2026/6/28
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/sync-table-config")
|
||||
@RequiredArgsConstructor
|
||||
public class SyncTableConfigController {
|
||||
|
||||
|
||||
private final SyncTableConfigService syncTableConfigService;
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
*/
|
||||
@GetMapping("/page")
|
||||
public HttpResult<IPage<SyncTableConfig>> page(
|
||||
@RequestParam(defaultValue = "1") Integer pageNum,
|
||||
@RequestParam(defaultValue = "10") Integer pageSize,
|
||||
@RequestParam(required = false) String tableName,
|
||||
@RequestParam(required = false) String syncMode,
|
||||
@RequestParam(required = false) Integer enabled) {
|
||||
|
||||
Page<SyncTableConfig> page = new Page<>(pageNum, pageSize);
|
||||
LambdaQueryWrapper<SyncTableConfig> wrapper = new LambdaQueryWrapper<>();
|
||||
|
||||
if (tableName != null && !tableName.isEmpty()) {
|
||||
wrapper.like(SyncTableConfig::getTableName, tableName);
|
||||
}
|
||||
if (syncMode != null && !syncMode.isEmpty()) {
|
||||
wrapper.eq(SyncTableConfig::getSyncMode, syncMode);
|
||||
}
|
||||
if (enabled != null) {
|
||||
wrapper.eq(SyncTableConfig::getEnabled, enabled);
|
||||
}
|
||||
|
||||
wrapper.orderByAsc(SyncTableConfig::getSortOrder);
|
||||
|
||||
IPage<SyncTableConfig> result = syncTableConfigService.page(page, wrapper);
|
||||
return HttpResult.success(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询所有启用的配置
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
public HttpResult<List<SyncTableConfig>> list() {
|
||||
LambdaQueryWrapper<SyncTableConfig> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(SyncTableConfig::getEnabled, 1)
|
||||
.orderByAsc(SyncTableConfig::getSortOrder);
|
||||
return HttpResult.success(syncTableConfigService.list(wrapper));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID查询
|
||||
*/
|
||||
@GetMapping("/{tableName}")
|
||||
public HttpResult<SyncTableConfig> getById(@PathVariable String tableName) {
|
||||
return HttpResult.success(syncTableConfigService.getById(tableName));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增
|
||||
*/
|
||||
@PostMapping
|
||||
public HttpResult<Boolean> add(@RequestBody SyncTableConfig config) {
|
||||
return HttpResult.success(syncTableConfigService.save(config));
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新
|
||||
*/
|
||||
@PutMapping
|
||||
public HttpResult<Boolean> update(@RequestBody SyncTableConfig config) {
|
||||
return HttpResult.success(syncTableConfigService.updateById(config));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
@DeleteMapping("/{tableName}")
|
||||
public HttpResult<Boolean> delete(@PathVariable String tableName) {
|
||||
return HttpResult.success(syncTableConfigService.removeById(tableName));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
*/
|
||||
@DeleteMapping("/batch")
|
||||
public HttpResult<Boolean> deleteBatch(@RequestBody List<String> tableNames) {
|
||||
return HttpResult.success(syncTableConfigService.removeByIds(tableNames));
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新启用状态
|
||||
*/
|
||||
@PutMapping("/status/{tableName}")
|
||||
public HttpResult<Boolean> updateStatus(@PathVariable String tableName, @RequestParam Integer enabled) {
|
||||
SyncTableConfig config = new SyncTableConfig();
|
||||
config.setTableName(tableName);
|
||||
config.setEnabled(enabled);
|
||||
return HttpResult.success(syncTableConfigService.updateById(config));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
package com.njcn.relational.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.njcn.relational.pojo.po.SyncTableLogs;
|
||||
import com.njcn.relational.pojo.vo.HttpResult;
|
||||
import com.njcn.relational.service.SyncTableLogsService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 二三区数据转移日志 Controller
|
||||
*
|
||||
* @author cdf
|
||||
* @date 2026-06-28
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/sync-logs")
|
||||
@RequiredArgsConstructor
|
||||
public class SyncTableLogsController {
|
||||
|
||||
private final SyncTableLogsService syncTableLogsService;
|
||||
|
||||
// ========== 查询 ==========
|
||||
|
||||
/**
|
||||
* 分页查询日志
|
||||
* GET /api/sync-logs/page?pageNum=1&pageSize=10&timeId=2026-06-28&result=1
|
||||
*/
|
||||
@GetMapping("/page")
|
||||
public HttpResult<IPage<SyncTableLogs>> page(
|
||||
@RequestParam(defaultValue = "1") Integer pageNum,
|
||||
@RequestParam(defaultValue = "10") Integer pageSize,
|
||||
@RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate timeId,
|
||||
@RequestParam(required = false) Integer result) {
|
||||
|
||||
IPage<SyncTableLogs> page = syncTableLogsService.pageQuery(pageNum, pageSize, timeId, result);
|
||||
return HttpResult.success(page);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据日期查询日志列表
|
||||
* GET /api/sync-logs/list?timeId=2026-06-28
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
public HttpResult<List<SyncTableLogs>> listByTimeId(
|
||||
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate timeId) {
|
||||
|
||||
List<SyncTableLogs> list = syncTableLogsService.listByTimeId(timeId);
|
||||
return HttpResult.success(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据日期和结果查询
|
||||
* GET /api/sync-logs/list/condition?timeId=2026-06-28&result=1
|
||||
*/
|
||||
@GetMapping("/list/condition")
|
||||
public HttpResult<List<SyncTableLogs>> listByTimeIdAndResult(
|
||||
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate timeId,
|
||||
@RequestParam Integer result) {
|
||||
|
||||
List<SyncTableLogs> list = syncTableLogsService.listByTimeIdAndResult(timeId, result);
|
||||
return HttpResult.success(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID查询单条记录
|
||||
* GET /api/sync-logs/2026-06-28/1
|
||||
*/
|
||||
@GetMapping("/{timeId}/{result}")
|
||||
public HttpResult<SyncTableLogs> getById(
|
||||
@PathVariable @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate timeId,
|
||||
@PathVariable Integer result) {
|
||||
|
||||
LambdaQueryWrapper<SyncTableLogs> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(SyncTableLogs::getTimeId, timeId)
|
||||
.eq(SyncTableLogs::getResult, result);
|
||||
|
||||
SyncTableLogs log = syncTableLogsService.getOne(wrapper);
|
||||
return HttpResult.success(log);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查某日期是否存在记录
|
||||
* GET /api/sync-logs/exists/2026-06-28
|
||||
*/
|
||||
@GetMapping("/exists/{timeId}")
|
||||
public HttpResult<Boolean> existsByTimeId(
|
||||
@PathVariable @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate timeId) {
|
||||
|
||||
boolean exists = syncTableLogsService.existsByTimeId(timeId);
|
||||
return HttpResult.success(exists);
|
||||
}
|
||||
|
||||
/**
|
||||
* 统计某日期的成功/失败数量
|
||||
* GET /api/sync-logs/count/2026-06-28/1
|
||||
*/
|
||||
@GetMapping("/count/{timeId}/{result}")
|
||||
public HttpResult<Integer> countByTimeIdAndResult(
|
||||
@PathVariable @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate timeId,
|
||||
@PathVariable Integer result) {
|
||||
|
||||
int count = syncTableLogsService.countByTimeIdAndResult(timeId, result);
|
||||
return HttpResult.success(count);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询所有日志
|
||||
* GET /api/sync-logs/all
|
||||
*/
|
||||
@GetMapping("/all")
|
||||
public HttpResult<List<SyncTableLogs>> listAll() {
|
||||
List<SyncTableLogs> list = syncTableLogsService.list();
|
||||
return HttpResult.success(list);
|
||||
}
|
||||
|
||||
// ========== 新增 ==========
|
||||
|
||||
/**
|
||||
* 新增日志
|
||||
* POST /api/sync-logs
|
||||
*/
|
||||
@PostMapping
|
||||
public HttpResult<Boolean> add(@RequestBody SyncTableLogs log) {
|
||||
// 设置创建时间
|
||||
if (log.getCreateTime() == null) {
|
||||
log.setCreateTime(LocalDateTime.now());
|
||||
}
|
||||
boolean success = syncTableLogsService.save(log);
|
||||
return success ? HttpResult.success(true) : HttpResult.error("新增日志失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量新增日志
|
||||
* POST /api/sync-logs/batch
|
||||
*/
|
||||
@PostMapping("/batch")
|
||||
public HttpResult<Boolean> batchAdd(@RequestBody List<SyncTableLogs> logs) {
|
||||
boolean success = syncTableLogsService.batchInsert(logs);
|
||||
return success ? HttpResult.success(true) : HttpResult.error("批量新增失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存或更新(存在则更新,不存在则插入)
|
||||
* POST /api/sync-logs/saveOrUpdate
|
||||
*/
|
||||
@PostMapping("/saveOrUpdate")
|
||||
public HttpResult<Boolean> saveOrUpdate(@RequestBody SyncTableLogs log) {
|
||||
boolean success = syncTableLogsService.saveOrUpdateLog(log);
|
||||
return success ? HttpResult.success(true) : HttpResult.error("保存或更新失败");
|
||||
}
|
||||
|
||||
// ========== 更新 ==========
|
||||
|
||||
/**
|
||||
* 更新日志
|
||||
* PUT /api/sync-logs
|
||||
* 注意:由于 timeId 和 result 是复合主键,更新时不能修改这两个字段
|
||||
*/
|
||||
@PutMapping
|
||||
public HttpResult<Boolean> update(@RequestBody SyncTableLogs log) {
|
||||
// 使用条件更新
|
||||
LambdaQueryWrapper<SyncTableLogs> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(SyncTableLogs::getTimeId, log.getTimeId())
|
||||
.eq(SyncTableLogs::getResult, log.getResult());
|
||||
|
||||
// 只更新 localResult 字段(如果有其他字段需要更新,在这里设置)
|
||||
SyncTableLogs update = new SyncTableLogs();
|
||||
update.setLocalResult(log.getLocalResult());
|
||||
|
||||
boolean success = syncTableLogsService.update(update, wrapper);
|
||||
return success ? HttpResult.success(true) : HttpResult.error("更新失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新本地处理结果
|
||||
* PUT /api/sync-logs/localResult/2026-06-28/1?localResult=1
|
||||
*/
|
||||
@PutMapping("/localResult/{timeId}/{result}")
|
||||
public HttpResult<Boolean> updateLocalResult(
|
||||
@PathVariable @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate timeId,
|
||||
@PathVariable Integer result,
|
||||
@RequestParam Integer localResult) {
|
||||
|
||||
LambdaQueryWrapper<SyncTableLogs> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(SyncTableLogs::getTimeId, timeId)
|
||||
.eq(SyncTableLogs::getResult, result);
|
||||
|
||||
SyncTableLogs update = new SyncTableLogs();
|
||||
update.setLocalResult(localResult);
|
||||
|
||||
boolean success = syncTableLogsService.update(update, wrapper);
|
||||
return success ? HttpResult.success(true) : HttpResult.error("更新本地处理结果失败");
|
||||
}
|
||||
|
||||
// ========== 删除 ==========
|
||||
|
||||
/**
|
||||
* 根据日期和结果删除
|
||||
* DELETE /api/sync-logs/2026-06-28/1
|
||||
*/
|
||||
@DeleteMapping("/{timeId}/{result}")
|
||||
public HttpResult<Boolean> delete(
|
||||
@PathVariable @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate timeId,
|
||||
@PathVariable Integer result) {
|
||||
|
||||
boolean success = syncTableLogsService.deleteByTimeIdAndResult(timeId, result);
|
||||
return success ? HttpResult.success(true) : HttpResult.error("删除失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据日期删除所有日志
|
||||
* DELETE /api/sync-logs/timeId/2026-06-28
|
||||
*/
|
||||
@DeleteMapping("/timeId/{timeId}")
|
||||
public HttpResult<Boolean> deleteByTimeId(
|
||||
@PathVariable @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate timeId) {
|
||||
|
||||
boolean success = syncTableLogsService.deleteByTimeId(timeId);
|
||||
return success ? HttpResult.success(true) : HttpResult.error("删除失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
* DELETE /api/sync-logs/batch
|
||||
*/
|
||||
@DeleteMapping("/batch")
|
||||
public HttpResult<Boolean> deleteBatch(@RequestBody List<SyncTableLogs> logs) {
|
||||
if (logs == null || logs.isEmpty()) {
|
||||
return HttpResult.error("请选择要删除的记录");
|
||||
}
|
||||
|
||||
// 构建删除条件
|
||||
LambdaQueryWrapper<SyncTableLogs> wrapper = new LambdaQueryWrapper<>();
|
||||
for (SyncTableLogs log : logs) {
|
||||
wrapper.or(w -> w.eq(SyncTableLogs::getTimeId, log.getTimeId())
|
||||
.eq(SyncTableLogs::getResult, log.getResult()));
|
||||
}
|
||||
|
||||
boolean success = syncTableLogsService.remove(wrapper);
|
||||
return success ? HttpResult.success(true) : HttpResult.error("批量删除失败");
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,9 @@
|
||||
package com.njcn.relational.job;
|
||||
|
||||
import cn.hutool.core.date.DatePattern;
|
||||
import com.njcn.relational.service.SyncTableConfigService;
|
||||
import com.njcn.relational.service.SyncTableParseService;
|
||||
import com.njcn.relational.service.SyncThreeDmParseService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -24,7 +22,7 @@ import java.time.format.DateTimeFormatter;
|
||||
public class importJob {
|
||||
|
||||
|
||||
private final SyncTableParseService syncTableParseService;
|
||||
private final SyncThreeDmParseService syncThreeDmParseService;
|
||||
|
||||
|
||||
|
||||
@@ -38,7 +36,7 @@ public class importJob {
|
||||
@Scheduled(cron = "0 55 4 * * ?")
|
||||
public void threeImport(){
|
||||
String date = LocalDate.now().minusDays(1).format(DateTimeFormatter.ofPattern(DatePattern.PURE_DATE_PATTERN));
|
||||
syncTableParseService.syncFullTables(date);
|
||||
syncThreeDmParseService.syncFullTables(date);
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +50,7 @@ public class importJob {
|
||||
@Scheduled(cron = "0 44 5 * * ?")
|
||||
@Profile("insert_up")
|
||||
public void removeThreeLocal(){
|
||||
syncTableParseService.cleanLocalFiles(7);
|
||||
syncThreeDmParseService.cleanLocalFiles(7);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
package com.njcn.relational.job;
|
||||
|
||||
import cn.hutool.core.date.DatePattern;
|
||||
import com.njcn.relational.service.SyncTableConfigService;
|
||||
import com.njcn.relational.service.SyncTableParseService;
|
||||
import com.njcn.relational.service.SyncTwoDmExportService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -23,7 +21,7 @@ import java.time.format.DateTimeFormatter;
|
||||
@Profile("query_up")
|
||||
public class reportJob {
|
||||
|
||||
private final SyncTableConfigService syncTableConfigService;
|
||||
private final SyncTwoDmExportService syncTwoDmExportService;
|
||||
|
||||
|
||||
/**
|
||||
@@ -35,7 +33,7 @@ public class reportJob {
|
||||
@Scheduled(cron = "0 40 4 * * ?")
|
||||
public void twoExport(){
|
||||
String date = LocalDate.now().minusDays(1).format(DateTimeFormatter.ofPattern(DatePattern.PURE_DATE_PATTERN));
|
||||
syncTableConfigService.syncAllFullTables(date);
|
||||
syncTwoDmExportService.syncAllFullTables(date);
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +46,7 @@ public class reportJob {
|
||||
*/
|
||||
@Scheduled(cron = "0 33 5 * * ?")
|
||||
public void removeTwoLocal(){
|
||||
syncTableConfigService.cleanLocalFiles(7);
|
||||
syncTwoDmExportService.cleanLocalFiles(7);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.njcn.relational.mapper;
|
||||
|
||||
|
||||
import com.github.jeffreyning.mybatisplus.base.MppBaseMapper;
|
||||
import com.njcn.relational.pojo.po.RStatDataFlickerD;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Mapper 接口
|
||||
* </p>
|
||||
*/
|
||||
public interface RStatDataFlickerRelationMapper extends MppBaseMapper<RStatDataFlickerD> {
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.njcn.relational.mapper;
|
||||
|
||||
import com.github.jeffreyning.mybatisplus.base.MppBaseMapper;
|
||||
import com.njcn.relational.pojo.po.RStatDataFlucD;
|
||||
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Mapper 接口
|
||||
* </p>
|
||||
*/
|
||||
public interface RStatDataFlucRelationMapper extends MppBaseMapper<RStatDataFlucD> {
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.njcn.relational.mapper;
|
||||
|
||||
import com.github.jeffreyning.mybatisplus.base.MppBaseMapper;
|
||||
import com.njcn.relational.pojo.po.RStatDataHarmPhasicID;
|
||||
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Mapper 接口
|
||||
* </p>
|
||||
*/
|
||||
public interface RStatDataHarmPhasicIRelationMapper extends MppBaseMapper<RStatDataHarmPhasicID> {
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.njcn.relational.mapper;
|
||||
|
||||
import com.github.jeffreyning.mybatisplus.base.MppBaseMapper;
|
||||
import com.njcn.relational.pojo.po.RStatDataHarmPhasicVD;
|
||||
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Mapper 接口
|
||||
* </p>
|
||||
*/
|
||||
public interface RStatDataHarmPhasicVRelationMapper extends MppBaseMapper<RStatDataHarmPhasicVD> {
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.njcn.relational.mapper;
|
||||
|
||||
import com.github.jeffreyning.mybatisplus.base.MppBaseMapper;
|
||||
import com.njcn.relational.pojo.po.RStatDataHarmPowerPD;
|
||||
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Mapper 接口
|
||||
* </p>
|
||||
*/
|
||||
public interface RStatDataHarmPowerPRelationMapper extends MppBaseMapper<RStatDataHarmPowerPD> {
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.njcn.relational.mapper;
|
||||
|
||||
import com.github.jeffreyning.mybatisplus.base.MppBaseMapper;
|
||||
import com.njcn.relational.pojo.po.RStatDataHarmPowerQD;
|
||||
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Mapper 接口
|
||||
* </p>
|
||||
*/
|
||||
public interface RStatDataHarmPowerQRelationMapper extends MppBaseMapper<RStatDataHarmPowerQD> {
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.njcn.relational.mapper;
|
||||
|
||||
import com.github.jeffreyning.mybatisplus.base.MppBaseMapper;
|
||||
import com.njcn.relational.pojo.po.RStatDataHarmPowerSD;
|
||||
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Mapper 接口
|
||||
* </p>
|
||||
*/
|
||||
public interface RStatDataHarmPowerSRelationMapper extends MppBaseMapper<RStatDataHarmPowerSD> {
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.njcn.relational.mapper;
|
||||
|
||||
import com.github.jeffreyning.mybatisplus.base.MppBaseMapper;
|
||||
import com.njcn.relational.pojo.po.RStatDataHarmRateID;
|
||||
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Mapper 接口
|
||||
* </p>
|
||||
*/
|
||||
public interface RStatDataHarmRateIRelationMapper extends MppBaseMapper<RStatDataHarmRateID> {
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.njcn.relational.mapper;
|
||||
|
||||
import com.github.jeffreyning.mybatisplus.base.MppBaseMapper;
|
||||
import com.njcn.relational.pojo.po.RStatDataHarmRateVD;
|
||||
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Mapper 接口
|
||||
* </p>
|
||||
*/
|
||||
public interface RStatDataHarmRateVRelationMapper extends MppBaseMapper<RStatDataHarmRateVD> {
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.njcn.relational.mapper;
|
||||
|
||||
import com.github.jeffreyning.mybatisplus.base.MppBaseMapper;
|
||||
import com.njcn.relational.pojo.po.RStatDataID;
|
||||
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Mapper 接口
|
||||
* </p>
|
||||
*/
|
||||
public interface RStatDataIRelationMapper extends MppBaseMapper<RStatDataID> {
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.njcn.relational.mapper;
|
||||
|
||||
import com.github.jeffreyning.mybatisplus.base.MppBaseMapper;
|
||||
import com.njcn.relational.pojo.po.RStatDataInHarmID;
|
||||
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Mapper 接口
|
||||
* </p>
|
||||
*/
|
||||
public interface RStatDataInHarmIRelationMapper extends MppBaseMapper<RStatDataInHarmID> {
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.njcn.relational.mapper;
|
||||
|
||||
import com.github.jeffreyning.mybatisplus.base.MppBaseMapper;
|
||||
import com.njcn.relational.pojo.po.RStatDataInHarmVD;
|
||||
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Mapper 接口
|
||||
* </p>
|
||||
*/
|
||||
public interface RStatDataInHarmVRelationMapper extends MppBaseMapper<RStatDataInHarmVD> {
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.njcn.relational.mapper;
|
||||
|
||||
import com.github.jeffreyning.mybatisplus.base.MppBaseMapper;
|
||||
import com.njcn.relational.pojo.po.RStatDataPltD;
|
||||
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Mapper 接口
|
||||
* </p>
|
||||
*/
|
||||
public interface RStatDataPltRelationMapper extends MppBaseMapper<RStatDataPltD> {
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.njcn.relational.mapper;
|
||||
|
||||
import com.github.jeffreyning.mybatisplus.base.MppBaseMapper;
|
||||
import com.njcn.relational.pojo.po.RStatDataVD;
|
||||
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Mapper 接口
|
||||
* </p>
|
||||
*/
|
||||
public interface RStatDataVRelationMapper extends MppBaseMapper<RStatDataVD> {
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -4,6 +4,7 @@ package com.njcn.relational.service;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.njcn.relational.pojo.dto.DcloudSyncQuery;
|
||||
import dcloud.common.InnerServiceBus.ServiceBus;
|
||||
import dcloud.model.common.ConModel;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -13,6 +14,6 @@ import org.springframework.stereotype.Service;
|
||||
|
||||
public interface DcloudBusService {
|
||||
|
||||
boolean syncDataToDb();
|
||||
boolean syncDataToDb(DcloudSyncQuery query);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
package com.njcn.relational.service;
|
||||
|
||||
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Description:
|
||||
* Date: 2024/11/18 11:17【需求编号】
|
||||
*
|
||||
* @author clam
|
||||
* @version V1.0.0
|
||||
*/
|
||||
public interface InsertIDataFlicker {
|
||||
|
||||
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package com.njcn.relational.service;
|
||||
|
||||
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Description:
|
||||
* Date: 2024/11/18 11:17【需求编号】
|
||||
*
|
||||
* @author clam
|
||||
* @version V1.0.0
|
||||
*/
|
||||
public interface InsertIDataFluc {
|
||||
|
||||
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package com.njcn.relational.service;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author xy
|
||||
*/
|
||||
public interface InsertIDataHarmRateI {
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package com.njcn.relational.service;
|
||||
|
||||
|
||||
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author xy
|
||||
*/
|
||||
public interface InsertIDataHarmRateV {
|
||||
|
||||
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package com.njcn.relational.service;
|
||||
|
||||
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Description:
|
||||
* Date: 2024/11/18 11:17【需求编号】
|
||||
*
|
||||
* @author clam
|
||||
* @version V1.0.0
|
||||
*/
|
||||
public interface InsertIDataHarmphasicI {
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package com.njcn.relational.service;
|
||||
|
||||
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Description:
|
||||
* Date: 2024/11/18 11:17【需求编号】
|
||||
*
|
||||
* @author clam
|
||||
* @version V1.0.0
|
||||
*/
|
||||
public interface InsertIDataHarmphasicV {
|
||||
|
||||
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package com.njcn.relational.service;
|
||||
|
||||
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Description:
|
||||
* Date: 2024/11/18 11:17【需求编号】
|
||||
*
|
||||
* @author clam
|
||||
* @version V1.0.0
|
||||
*/
|
||||
public interface InsertIDataHarmpowerP {
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package com.njcn.relational.service;
|
||||
|
||||
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Description:
|
||||
* Date: 2024/11/18 11:17【需求编号】
|
||||
*
|
||||
* @author clam
|
||||
* @version V1.0.0
|
||||
*/
|
||||
public interface InsertIDataHarmpowerQ {
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package com.njcn.relational.service;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Description:
|
||||
* Date: 2024/11/18 11:17【需求编号】
|
||||
*
|
||||
* @author clam
|
||||
* @version V1.0.0
|
||||
*/
|
||||
public interface InsertIDataHarmpowerS {
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package com.njcn.relational.service;
|
||||
|
||||
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Description:
|
||||
* Date: 2024/11/18 11:17【需求编号】
|
||||
*
|
||||
* @author clam
|
||||
* @version V1.0.0
|
||||
*/
|
||||
public interface InsertIDataI {
|
||||
|
||||
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package com.njcn.relational.service;
|
||||
|
||||
|
||||
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Description:
|
||||
* Date: 2024/11/18 13:27【需求编号】
|
||||
*
|
||||
* @author clam
|
||||
* @version V1.0.0
|
||||
*/
|
||||
public interface InsertIDataInharmI {
|
||||
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package com.njcn.relational.service;
|
||||
|
||||
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Description:
|
||||
* Date: 2024/11/18 13:27【需求编号】
|
||||
*
|
||||
* @author clam
|
||||
* @version V1.0.0
|
||||
*/
|
||||
public interface InsertIDataInharmV {
|
||||
|
||||
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package com.njcn.relational.service;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Description:
|
||||
* Date: 2024/11/18 11:17【需求编号】
|
||||
*
|
||||
* @author clam
|
||||
* @version V1.0.0
|
||||
*/
|
||||
public interface InsertIDataPlt {
|
||||
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package com.njcn.relational.service;
|
||||
|
||||
|
||||
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.njcn.relational.pojo.po.RStatDataID;
|
||||
import com.njcn.relational.pojo.po.RStatDataVD;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author hongawen
|
||||
* @version 1.0
|
||||
* @data 2024/11/7 10:54
|
||||
*/
|
||||
public interface InsertIDataV extends IService<RStatDataID> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.njcn.relational.service;
|
||||
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.njcn.relational.pojo.po.SGConPlantB;
|
||||
|
||||
public interface SGConPlantBService extends IService<SGConPlantB> {
|
||||
|
||||
/**
|
||||
* 云平台批量同步场站数据入库
|
||||
* @param dataArr 云平台分页返回JSON数组
|
||||
* @return 同步结果
|
||||
*/
|
||||
boolean batchSyncFromDcloud(JSONArray dataArr);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.njcn.relational.service;
|
||||
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.njcn.relational.pojo.po.SGConSubstationB;
|
||||
|
||||
public interface SGConSubstationBService extends IService<SGConSubstationB> {
|
||||
|
||||
/**
|
||||
* 云平台批量同步场站数据入库
|
||||
* @param dataArr 云平台分页返回JSON数组
|
||||
* @return 同步结果
|
||||
*/
|
||||
boolean batchSyncFromDcloud(JSONArray dataArr) throws Exception;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.njcn.relational.service;
|
||||
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.njcn.relational.pojo.po.SGConSubstationBind;
|
||||
|
||||
public interface SGConSubstationCService extends IService<SGConSubstationBind> {
|
||||
|
||||
/**
|
||||
* 云平台批量同步场站数据入库
|
||||
* @param dataArr 云平台分页返回JSON数组
|
||||
* @return 同步结果
|
||||
*/
|
||||
boolean batchSyncFromDcloud(JSONArray dataArr) throws Exception;
|
||||
}
|
||||
@@ -1,490 +1,7 @@
|
||||
package com.njcn.relational.service;
|
||||
|
||||
import cn.hutool.core.date.DatePattern;
|
||||
import cn.hutool.core.date.DateTime;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import com.njcn.relational.mapper.DynamicSyncMapper;
|
||||
import com.njcn.relational.mapper.SyncTableConfigMapper;
|
||||
import com.njcn.relational.pojo.bo.UploadResult;
|
||||
import com.njcn.relational.pojo.po.SyncTableConfig;
|
||||
import com.njcn.relational.serializer.BlobSerializer;
|
||||
import com.njcn.relational.serializer.ClobSerializer;
|
||||
import com.njcn.relational.utils.SftpUploadUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.io.*;
|
||||
import java.sql.Blob;
|
||||
import java.sql.Clob;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class SyncTableConfigService extends ServiceImpl<SyncTableConfigMapper, SyncTableConfig> {
|
||||
|
||||
@Autowired
|
||||
private DynamicSyncMapper dynamicSyncMapper;
|
||||
|
||||
@Value("${sync.export.remotePath}")
|
||||
private String remotePath;
|
||||
|
||||
@Value("${sync.export.localPath}")
|
||||
private String localPath;
|
||||
|
||||
@Value("${sync.ip}")
|
||||
private String ip;
|
||||
|
||||
@Value("${sync.port:22}")
|
||||
private Integer port;
|
||||
|
||||
@Value("${sync.username}")
|
||||
private String username;
|
||||
|
||||
@Value("${sync.password}")
|
||||
private String password;
|
||||
|
||||
// 新增:表名 -> 时间字段 缓存
|
||||
private Map<String, String> timeColumnCache = new HashMap<>();
|
||||
|
||||
private static final int BATCH_SIZE = 10000;
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||
private static final DateTimeFormatter FILE_DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
|
||||
|
||||
// JSON序列化工具(自动处理时间、换行符等)
|
||||
private final ObjectMapper objectMapper = new ObjectMapper()
|
||||
.registerModule(new JavaTimeModule())
|
||||
.registerModule(new SimpleModule() {{
|
||||
addSerializer(Blob.class, new BlobSerializer());
|
||||
addSerializer(Clob.class, new ClobSerializer());
|
||||
}})
|
||||
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
|
||||
.enable(SerializationFeature.INDENT_OUTPUT)
|
||||
.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));; // 格式化输出,可读性好
|
||||
|
||||
// 缓存当前Schema
|
||||
private String currentSchema;
|
||||
|
||||
// 记录本次生成的所有文件
|
||||
private final List<String> generatedFiles = new ArrayList<>();
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
currentSchema = dynamicSyncMapper.getCurrentUser();
|
||||
log.info("当前数据库Schema: {}", currentSchema);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有启用的全量表(从配置表)
|
||||
*/
|
||||
public List<SyncTableConfig> getEnabledFullTables() {
|
||||
LambdaQueryWrapper<SyncTableConfig> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(SyncTableConfig::getEnabled, 1)
|
||||
.eq(SyncTableConfig::getSyncMode, "FULL")
|
||||
.orderByAsc(SyncTableConfig::getSortOrder);
|
||||
return this.list(wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有启用的增量表(从配置表)
|
||||
*/
|
||||
public List<SyncTableConfig> getEnabledIncrementTables() {
|
||||
LambdaQueryWrapper<SyncTableConfig> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(SyncTableConfig::getEnabled, 1)
|
||||
.eq(SyncTableConfig::getSyncMode, "INC")
|
||||
.orderByAsc(SyncTableConfig::getSortOrder);
|
||||
return this.list(wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保本地目录存在
|
||||
*/
|
||||
private void ensureLocalDirectory() {
|
||||
File dir = new File(localPath);
|
||||
if (!dir.exists()) {
|
||||
dir.mkdirs();
|
||||
log.info("创建目录: {}", localPath);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 全量同步所有表
|
||||
*/
|
||||
public void syncAllFullTables(String dateStr) {
|
||||
List<SyncTableConfig> tables = getEnabledFullTables();
|
||||
log.info("========== 全量同步开始,共 {} 张表 ==========", tables.size());
|
||||
|
||||
// 确保目录存在
|
||||
ensureLocalDirectory();
|
||||
generatedFiles.clear();
|
||||
|
||||
for (SyncTableConfig config : tables) {
|
||||
String tableName = config.getTableName();
|
||||
try {
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
/*IService<?> service = registry.getServiceByTableName(tableName);
|
||||
if (service != null) {
|
||||
String filePath = exportFullTableWithEntity(service, tableName, dateStr);
|
||||
log.info("表 {} 使用实体方式导出完成", tableName);
|
||||
} else {*/
|
||||
String filePath = exportFullTableToJson(tableName, dateStr);
|
||||
log.info("表 {} 使用Map方式导出完成", tableName);
|
||||
// }
|
||||
|
||||
long duration = System.currentTimeMillis() - startTime;
|
||||
log.info("表 {} 全量导出完成,耗时 {} ms", tableName, duration);
|
||||
} catch (Exception e) {
|
||||
log.error("表 {} 全量导出失败", tableName, e);
|
||||
}
|
||||
}
|
||||
syncAllIncrementTables(dateStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* 增量同步所有表
|
||||
*/
|
||||
public void syncAllIncrementTables(String dateStr) {
|
||||
List<SyncTableConfig> tables = getEnabledIncrementTables();
|
||||
timeColumnCache = tables.stream().filter(it-> StrUtil.isNotBlank(it.getTimeColumn())).collect(Collectors.toMap(SyncTableConfig::getTableName,SyncTableConfig::getTimeColumn));
|
||||
log.info("========== 增量同步开始,共 {} 张表 ==========", tables.size());
|
||||
for (SyncTableConfig config : tables) {
|
||||
String tableName = config.getTableName();
|
||||
try {
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
/*IService<?> service = registry.getServiceByTableName(tableName);
|
||||
if (service != null) {
|
||||
String filePath = exportIncrementWithEntity(service, tableName, dateStr);
|
||||
log.info("表 {} 使用实体方式导出完成", tableName);
|
||||
} else {*/
|
||||
String filePath = exportTodayDataToJson(tableName, dateStr);
|
||||
log.info("表 {} 使用Map方式导出完成", tableName);
|
||||
// }
|
||||
|
||||
long duration = System.currentTimeMillis() - startTime;
|
||||
log.info("表 {} 增量导出完成,耗时 {} ms", tableName, duration);
|
||||
} catch (Exception e) {
|
||||
log.error("表 {} 增量同步失败", tableName, e);
|
||||
}
|
||||
}
|
||||
|
||||
// 推送所有文件到横向隔离设备
|
||||
if (!generatedFiles.isEmpty()) {
|
||||
uploadAllFiles();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传所有生成的文件(批量上传)
|
||||
*/
|
||||
private void uploadAllFiles() {
|
||||
if (generatedFiles.isEmpty()) {
|
||||
log.info("没有文件需要上传");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
List<UploadResult> resultList = SftpUploadUtil.batchUploadFiles(
|
||||
ip, port, username, password, generatedFiles, remotePath
|
||||
);
|
||||
|
||||
// 统计上传结果
|
||||
long successCount = resultList.stream().filter(UploadResult::isSuccess).count();
|
||||
long failCount = resultList.size() - successCount;
|
||||
|
||||
log.info("批量上传完成 - 成功: {}, 失败: {}, 总计: {}", successCount, failCount, resultList.size());
|
||||
// 记录失败的文件
|
||||
for (UploadResult result : resultList) {
|
||||
if (!result.isSuccess()) {
|
||||
log.error("上传失败文件: {}, 原因: {}", result.getLocalFilePath(), result.getErrorMessage());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("批量上传失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map方式全量导出为JSON(保存为.txt文件)
|
||||
*/
|
||||
@Transactional
|
||||
public String exportFullTableToJson(String tableName, String dateStr) throws IOException {
|
||||
String fileName = String.format("%s_FULL_%s.txt", tableName, dateStr);
|
||||
String filePath = localPath + fileName;
|
||||
|
||||
log.info("开始导出全量表: {} -> {}", tableName, filePath);
|
||||
|
||||
List<Map<String, Object>> allData = new ArrayList<>();
|
||||
int offset = 0;
|
||||
|
||||
while (true) {
|
||||
List<Map<String, Object>> batchData = dynamicSyncMapper.selectPage(
|
||||
tableName, BATCH_SIZE, offset);
|
||||
|
||||
if (batchData.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
allData.addAll(batchData);
|
||||
log.info("表 {} 已读取 {} 条记录", tableName, allData.size());
|
||||
|
||||
if (batchData.size() < BATCH_SIZE) {
|
||||
break;
|
||||
}
|
||||
offset += BATCH_SIZE;
|
||||
}
|
||||
|
||||
// 写入JSON格式到.txt文件
|
||||
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath))) {
|
||||
objectMapper.writeValue(bos, allData);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 记录生成的文件
|
||||
generatedFiles.add(filePath);
|
||||
|
||||
log.info("表 {} 全量导出完成,共 {} 条记录,文件: {}", tableName, allData.size(), filePath);
|
||||
return filePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map方式增量导出为JSON(保存为.txt文件)
|
||||
*/
|
||||
@Transactional
|
||||
public String exportTodayDataToJson(String tableName, String dateStr) throws IOException {
|
||||
|
||||
// 1. 判断表属于哪种周期:D/M/Q/Y
|
||||
String periodType = getTablePeriodType(tableName);
|
||||
if (periodType == null) {
|
||||
log.warn("表 {} 无法识别周期类型,跳过增量导出", tableName);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 2. 根据周期构建 开始/结束 时间
|
||||
Map<String, String> timeRange = buildPeriodTimeRange(periodType, dateStr);
|
||||
String dateStart = timeRange.get("start");
|
||||
String dateEnd = timeRange.get("end");
|
||||
|
||||
log.info("导出表 {} 数据: {} 至 {}", tableName, dateStart, dateEnd);
|
||||
|
||||
String timeColumn = getTimeColumn(tableName);
|
||||
|
||||
List<Map<String, Object>> queryData = dynamicSyncMapper.selectTodayData(
|
||||
tableName, timeColumn, dateStart, dateEnd);
|
||||
|
||||
if (queryData.isEmpty()) {
|
||||
log.info("表 {} {} - {}无数据", tableName,dateStart,dateEnd);
|
||||
return null;
|
||||
}
|
||||
|
||||
String fileName = String.format("%s_INC_%s.txt", tableName, dateStr);
|
||||
String filePath = localPath + fileName;
|
||||
|
||||
// 写入JSON格式到.txt文件
|
||||
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath))) {
|
||||
objectMapper.writeValue(bos, queryData);
|
||||
}
|
||||
|
||||
// 记录生成的文件
|
||||
generatedFiles.add(filePath);
|
||||
|
||||
log.info("表 {} {}数据导出完成,共 {} 条记录,文件: {}", tableName,dateStr, queryData.size(), filePath);
|
||||
return filePath;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 从表名识别周期类型
|
||||
* D=天, M=月, Q=季, Y=年
|
||||
*/
|
||||
private String getTablePeriodType(String tableName) {
|
||||
|
||||
if (tableName.endsWith("_D") || tableName.equalsIgnoreCase("R_MP_V_THD")) return "D";
|
||||
if (tableName.endsWith("_M")) return "M";
|
||||
if (tableName.endsWith("_Q")) return "Q";
|
||||
if (tableName.endsWith("_Y")) return "Y";
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* 根据周期 & 入参日期,计算【当前周期】时间范围(JDK8 兼容)
|
||||
*/
|
||||
private Map<String, String> buildPeriodTimeRange(String periodType, String dateStr) {
|
||||
DateTime baseDate = DateUtil.parse(dateStr, DatePattern.PURE_DATE_PATTERN);
|
||||
String start;
|
||||
String end;
|
||||
|
||||
// JDK8 标准 switch
|
||||
switch (periodType) {
|
||||
case "D":
|
||||
// 入参日期 当天
|
||||
start = DateUtil.format(DateUtil.beginOfDay(baseDate), DatePattern.NORM_DATETIME_PATTERN);
|
||||
end = DateUtil.format(DateUtil.endOfDay(baseDate), DatePattern.NORM_DATETIME_PATTERN);
|
||||
break;
|
||||
case "M":
|
||||
// 入参日期 所在整月
|
||||
start = DateUtil.format(DateUtil.beginOfMonth(baseDate), DatePattern.NORM_DATETIME_PATTERN);
|
||||
end = DateUtil.format(DateUtil.endOfMonth(baseDate), DatePattern.NORM_DATETIME_PATTERN);
|
||||
break;
|
||||
case "Q":
|
||||
// 入参日期 所在整季度
|
||||
start = DateUtil.format(DateUtil.beginOfQuarter(baseDate), DatePattern.NORM_DATETIME_PATTERN);
|
||||
end = DateUtil.format(DateUtil.endOfQuarter(baseDate), DatePattern.NORM_DATETIME_PATTERN);
|
||||
break;
|
||||
case "Y":
|
||||
// 入参日期 所在整年
|
||||
start = DateUtil.format(DateUtil.beginOfYear(baseDate), DatePattern.NORM_DATETIME_PATTERN);
|
||||
end = DateUtil.format(DateUtil.endOfYear(baseDate), DatePattern.NORM_DATETIME_PATTERN);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("不支持的周期类型:" + periodType);
|
||||
}
|
||||
|
||||
Map<String, String> map = new HashMap<>(2);
|
||||
map.put("start", start);
|
||||
map.put("end", end);
|
||||
return map;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取时间字段
|
||||
*/
|
||||
private String getTimeColumn(String tableName) {
|
||||
String createTitle = timeColumnCache.get(tableName);
|
||||
if(Objects.isNull(createTitle)){
|
||||
switch (tableName) {
|
||||
case "R_STAT_DATA_FLICKER_D":
|
||||
case "R_STAT_DATA_FLUC_D":
|
||||
case "R_STAT_DATA_HARMPHASIC_I_D":
|
||||
case "R_STAT_DATA_HARMPHASIC_V_D":
|
||||
case "R_STAT_DATA_HARMPOWER_P_D":
|
||||
case "R_STAT_DATA_HARMPOWER_Q_D":
|
||||
case "R_STAT_DATA_HARMPOWER_S_D":
|
||||
case "R_STAT_DATA_HARMRATE_I_D":
|
||||
case "R_STAT_DATA_HARMRATE_V_D":
|
||||
case "R_STAT_DATA_INHARM_I_D":
|
||||
case "R_STAT_DATA_INHARM_V_D":
|
||||
case "R_STAT_DATA_I_D":
|
||||
case "R_STAT_DATA_PLT_D":
|
||||
case "R_STAT_DATA_V_D":
|
||||
return "TIME";
|
||||
// 新增表
|
||||
case "R_STAT_COMASSES_D":
|
||||
case "R_STAT_ASSES_D":
|
||||
case "R_STAT_LIMIT_RATE_D":
|
||||
case "R_STAT_ONLINERATE_D":
|
||||
case "R_STAT_INTEGRITY_D":
|
||||
case "R_STAT_ORG_INTEGRITY_D":
|
||||
case "R_STAT_LIMIT_RATE_DETAIL_D":
|
||||
case "R_STAT_LIMIT_TARGET_D":
|
||||
return "TIME_ID";
|
||||
case "R_MP_POLLUTION_D":
|
||||
case "R_MP_V_THD":
|
||||
case "R_STAT_POLLUTION_ORG_D":
|
||||
case "R_STAT_POLLUTION_ORG_M":
|
||||
case "R_STAT_POLLUTION_ORG_Q":
|
||||
case "R_STAT_POLLUTION_ORG_Y":
|
||||
case "R_STAT_POLLUTION_SUBSTATION_D":
|
||||
case "R_STAT_POLLUTION_SUBSTATION_M":
|
||||
case "R_STAT_POLLUTION_SUBSTATION_Q":
|
||||
case "R_STAT_POLLUTION_SUBSTATION_Y":
|
||||
return "DATA_DATE";
|
||||
default:
|
||||
log.warn("表 {} 未配置时间字段,跳过", tableName);
|
||||
return "CREATE_TIME";
|
||||
}
|
||||
}else {
|
||||
return createTitle;
|
||||
}
|
||||
}
|
||||
|
||||
public void test(){
|
||||
try {
|
||||
// 确保目录存在
|
||||
File localDir = new File(localPath);
|
||||
if (!localDir.exists()) {
|
||||
localDir.mkdirs();
|
||||
}
|
||||
|
||||
// 生成txt文件
|
||||
String filePath = localPath + "test.txt";
|
||||
try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath))) {
|
||||
writer.write("test");
|
||||
}
|
||||
|
||||
// 上传到SFTP服务器
|
||||
SftpUploadUtil.uploadFile(ip, port, username, password, filePath, remotePath);
|
||||
|
||||
log.info("test.txt文件生成并上传成功");
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
public List<String> getPendingFiles() {
|
||||
List<String> matchedFiles = new ArrayList<>();
|
||||
Vector<?> files;
|
||||
try {
|
||||
// 列目录也纳入异常捕获
|
||||
files = SftpUploadUtil.listFiles(ip, port, username, password, remotePath);
|
||||
for (Object obj : files) {
|
||||
if (obj instanceof com.jcraft.jsch.ChannelSftp.LsEntry) {
|
||||
com.jcraft.jsch.ChannelSftp.LsEntry entry = (com.jcraft.jsch.ChannelSftp.LsEntry) obj;
|
||||
String fileName = entry.getFilename();
|
||||
matchedFiles.add(fileName);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("获取待处理文件列表失败,远端目录:{}", remotePath, e);
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return matchedFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理本地已处理过的文件
|
||||
*/
|
||||
public void cleanLocalFiles(int daysToKeep) {
|
||||
File localDir = new File(localPath);
|
||||
if (!localDir.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
long cutoffTime = System.currentTimeMillis() - (daysToKeep * 24L * 60 * 60 * 1000);
|
||||
File[] files = localDir.listFiles();
|
||||
|
||||
if (files != null) {
|
||||
int deletedCount = 0;
|
||||
for (File file : files) {
|
||||
if (file.isFile() && file.lastModified() < cutoffTime) {
|
||||
file.delete();
|
||||
deletedCount++;
|
||||
}
|
||||
}
|
||||
log.info("清理了10.11.7.5二区服务器 {} 个七天前的本地txt文件", deletedCount);
|
||||
}
|
||||
}
|
||||
public interface SyncTableConfigService extends IService<SyncTableConfig> {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.njcn.relational.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.njcn.relational.pojo.po.SyncTableLogs;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
public interface SyncTableLogsService extends IService<SyncTableLogs> {
|
||||
|
||||
|
||||
/**
|
||||
* 分页查询日志
|
||||
*/
|
||||
IPage<SyncTableLogs> pageQuery(Integer pageNum, Integer pageSize, LocalDate timeId, Integer result);
|
||||
|
||||
/**
|
||||
* 根据日期查询日志列表
|
||||
*/
|
||||
List<SyncTableLogs> listByTimeId(LocalDate timeId);
|
||||
|
||||
/**
|
||||
* 根据日期和结果查询
|
||||
*/
|
||||
List<SyncTableLogs> listByTimeIdAndResult(LocalDate timeId, Integer result);
|
||||
|
||||
/**
|
||||
* 查询某日期是否存在记录
|
||||
*/
|
||||
boolean existsByTimeId(LocalDate timeId);
|
||||
|
||||
/**
|
||||
* 批量插入日志
|
||||
*/
|
||||
boolean batchInsert(List<SyncTableLogs> logs);
|
||||
|
||||
/**
|
||||
* 根据日期删除日志
|
||||
*/
|
||||
boolean deleteByTimeId(LocalDate timeId);
|
||||
|
||||
/**
|
||||
* 根据日期和结果删除
|
||||
*/
|
||||
boolean deleteByTimeIdAndResult(LocalDate timeId, Integer result);
|
||||
|
||||
/**
|
||||
* 统计某日期的成功/失败数量
|
||||
*/
|
||||
int countByTimeIdAndResult(LocalDate timeId, Integer result);
|
||||
|
||||
/**
|
||||
* 保存或更新日志(如果存在则更新,不存在则插入)
|
||||
*/
|
||||
boolean saveOrUpdateLog(SyncTableLogs log);
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.njcn.relational.service;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@@ -9,6 +10,7 @@ import com.njcn.relational.mapper.DynamicSyncMapper;
|
||||
import com.njcn.relational.mapper.SyncTableConfigMapper;
|
||||
import com.njcn.relational.pojo.bo.DownloadResult;
|
||||
import com.njcn.relational.pojo.po.SyncTableConfig;
|
||||
import com.njcn.relational.pojo.po.SyncTableLogs;
|
||||
import com.njcn.relational.utils.SftpUploadUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -20,18 +22,23 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.io.*;
|
||||
import java.nio.file.*;
|
||||
import java.sql.Clob;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.Timestamp;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class SyncTableParseService {
|
||||
public class SyncThreeDmParseService {
|
||||
|
||||
@Autowired
|
||||
private DynamicSyncMapper dynamicSyncMapper;
|
||||
@@ -39,6 +46,9 @@ public class SyncTableParseService {
|
||||
@Autowired
|
||||
private SyncTableConfigMapper syncTableConfigMapper;
|
||||
|
||||
@Autowired
|
||||
private SyncTableLogsService syncTableLogsService;
|
||||
|
||||
// 缓存当前Schema
|
||||
private String currentSchema;
|
||||
|
||||
@@ -78,16 +88,29 @@ public class SyncTableParseService {
|
||||
log.info("当前数据库Schema: {}", currentSchema);
|
||||
}
|
||||
|
||||
private Map<String, String> syncData(){
|
||||
Map<String, String> tableBlobCache = new ConcurrentHashMap<>();
|
||||
Map<String, String> tableClobCache = new ConcurrentHashMap<>();
|
||||
private void syncData(){
|
||||
// 加载表配置到缓存
|
||||
Map<String, String> tableConfigCache = new HashMap<>();
|
||||
tableBlobCache.clear();
|
||||
tableClobCache.clear();
|
||||
|
||||
LambdaQueryWrapper<SyncTableConfig> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(SyncTableConfig::getEnabled, 1).isNotNull(SyncTableConfig::getBlobColumns);
|
||||
wrapper.eq(SyncTableConfig::getEnabled, 1);
|
||||
List<SyncTableConfig> configs = syncTableConfigMapper.selectList(wrapper);
|
||||
for (SyncTableConfig config : configs) {
|
||||
tableConfigCache.put(config.getTableName().toUpperCase(), config.getBlobColumns());
|
||||
String tableUpper = config.getTableName().toUpperCase();
|
||||
if(StrUtil.isNotBlank(config.getBlobColumns())) {
|
||||
tableBlobCache.put(tableUpper, config.getBlobColumns());
|
||||
}
|
||||
if(StrUtil.isNotBlank(config.getClobColumns())){
|
||||
tableClobCache.put(tableUpper, config.getClobColumns());
|
||||
}
|
||||
}
|
||||
return tableConfigCache;
|
||||
log.info("加载表配置BLOB缓存完成,共 {} 张表", tableBlobCache.size());
|
||||
|
||||
log.info("加载表配置CLOB缓存完成,共 {} 张表", tableClobCache.size());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -97,12 +120,14 @@ public class SyncTableParseService {
|
||||
log.info("========== 开始批量同步数据{} ==========",LocalDateTime.now());
|
||||
|
||||
// 缓存表配置
|
||||
Map<String, String> tableConfigCache = syncData();
|
||||
log.info("加载表配置缓存完成,共 {} 张表", tableConfigCache.size());
|
||||
syncData();
|
||||
|
||||
// 确保本地目录存在
|
||||
ensureLocalDirectory();
|
||||
|
||||
boolean localFlag =true;
|
||||
boolean resultFlag = true;
|
||||
|
||||
try {
|
||||
// 1. 获取待下载的文件列表
|
||||
List<String> remoteFilePaths = getRemoteFilePaths(dataStr);
|
||||
@@ -116,8 +141,7 @@ public class SyncTableParseService {
|
||||
|
||||
// 2. 批量下载并删除远程文件
|
||||
List<DownloadResult> downloadResults = SftpUploadUtil.batchDownloadAndDelete(
|
||||
ip, port, username, password,
|
||||
remoteFilePaths, localPath, true // true = 下载后删除远程文件
|
||||
ip, port, username, password, remoteFilePaths, localPath, true // true = 下载后删除远程文件
|
||||
);
|
||||
|
||||
// 3. 统计下载结果
|
||||
@@ -128,6 +152,7 @@ public class SyncTableParseService {
|
||||
// 记录下载失败的文件
|
||||
for (DownloadResult result : downloadResults) {
|
||||
if (!result.isSuccess()) {
|
||||
localFlag = false;
|
||||
log.error("下载失败: {}, 原因: {}", result.getRemoteFilePath(), result.getErrorMessage());
|
||||
}
|
||||
}
|
||||
@@ -138,7 +163,8 @@ public class SyncTableParseService {
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (successfulDownloads.isEmpty()) {
|
||||
log.warn("没有成功下载的文件,停止处理");
|
||||
log.error("没有成功下载的文件,停止处理");
|
||||
localFlag = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -152,7 +178,7 @@ public class SyncTableParseService {
|
||||
String localFilePath = downloadResult.getLocalFilePath();
|
||||
|
||||
log.info("开始导入表: {}, 文件: {}", tableName, localFilePath);
|
||||
importJsonToTable(tableName, localFilePath,tableConfigCache);
|
||||
importJsonToTable(tableName, localFilePath);
|
||||
|
||||
importSuccessCount++;
|
||||
log.info("表 {} 导入成功", tableName);
|
||||
@@ -161,6 +187,7 @@ public class SyncTableParseService {
|
||||
deleteLocalFile(localFilePath);
|
||||
|
||||
} catch (Exception e) {
|
||||
resultFlag = false;
|
||||
importFailCount++;
|
||||
log.error("导入失败: {}", downloadResult.getRemoteFilePath(), e);
|
||||
}
|
||||
@@ -171,7 +198,15 @@ public class SyncTableParseService {
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("批量同步失败", e);
|
||||
resultFlag = false;
|
||||
}
|
||||
|
||||
SyncTableLogs syncTableLogs = new SyncTableLogs();
|
||||
syncTableLogs.setLocalResult(localFlag?1:0);
|
||||
syncTableLogs.setResult((resultFlag&&localFlag) ? 1:0);
|
||||
syncTableLogs.setTimeId(LocalDate.parse(dataStr, DateTimeFormatter.ofPattern("yyyyMMdd")));
|
||||
syncTableLogs.setCreateTime(LocalDateTime.now());
|
||||
syncTableLogsService.saveOrUpdate(syncTableLogs);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -319,8 +354,8 @@ public class SyncTableParseService {
|
||||
/**
|
||||
* 从配置缓存中获取BLOB字段列表
|
||||
*/
|
||||
private List<String> getBlobColumnsFromCache(String tableName, Map<String, String> tableConfigCache) {
|
||||
String blobColumnsStr = tableConfigCache.get(tableName.toUpperCase());
|
||||
private List<String> getBlobColumnsFromCache(String tableName) {
|
||||
String blobColumnsStr = tableBlobCache.get(tableName.toUpperCase());
|
||||
if (blobColumnsStr == null || blobColumnsStr.trim().isEmpty()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
@@ -342,7 +377,7 @@ public class SyncTableParseService {
|
||||
* 导入JSON数据到表(支持BLOB字段)
|
||||
*/
|
||||
@Transactional
|
||||
public void importJsonToTable(String tableName, String filePath, Map<String, String> tableConfigCache) throws Exception {
|
||||
public void importJsonToTable(String tableName, String filePath) throws Exception {
|
||||
log.info("开始导入表: {}, 文件: {}", tableName, filePath);
|
||||
|
||||
File dataFile = new File(filePath);
|
||||
@@ -384,7 +419,7 @@ public class SyncTableParseService {
|
||||
List<String> timeColumns = getTimeColumns(tableName.toUpperCase());
|
||||
|
||||
// 【从缓存配置中获取BLOB字段列表】
|
||||
List<String> blobColumns = getBlobColumnsFromCache(tableName, tableConfigCache);
|
||||
List<String> blobColumns = getBlobColumnsFromCache(tableName);
|
||||
|
||||
// 转换数据:Map key转大写,并将时间字段转换为Timestamp,BLOB字段转换为byte[]
|
||||
List<Map<String, Object>> convertedData = new ArrayList<>();
|
||||
@@ -428,6 +463,19 @@ public class SyncTableParseService {
|
||||
convertedData.add(convertedRow);
|
||||
}
|
||||
|
||||
// 仅CLOB配置表 + 增量文件 才走先删当日再插入;全量保留原有truncate逻辑
|
||||
String tableUpper = tableName.toUpperCase();
|
||||
if (tableClobCache.containsKey(tableUpper) && !isFull) {
|
||||
log.info("表 {} 专用先删后插流程开始执行", tableName);
|
||||
String syncDate = extractDateFromFileName(fileName);
|
||||
// 从缓存拿当前表配置的timeColumn
|
||||
SyncTableConfig cfg = getTableConfig(tableName);
|
||||
String delTimeCol = cfg.getTimeColumn();
|
||||
deleteDayThenInsert(tableName, columns, convertedData, syncDate, delTimeCol);
|
||||
log.info("表 {} 专用先删后插流程执行完毕,删除时间字段:{},直接返回", tableName, delTimeCol);
|
||||
return;
|
||||
}
|
||||
|
||||
// ==============================================
|
||||
// 核心修改:增量使用MERGE INTO(存在更新/不存在插入)
|
||||
// ==============================================
|
||||
@@ -473,6 +521,16 @@ public class SyncTableParseService {
|
||||
log.info("表 {} 导入完成,共 {} 条记录", tableName, totalCount);
|
||||
}
|
||||
|
||||
private SyncTableConfig getTableConfig(String tableName) {
|
||||
LambdaQueryWrapper<SyncTableConfig> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(SyncTableConfig::getTableName, tableName.toUpperCase());
|
||||
SyncTableConfig config = syncTableConfigMapper.selectOne(wrapper);
|
||||
if (config == null || StrUtil.isBlank(config.getTimeColumn())) {
|
||||
throw new RuntimeException("表[" + tableName + "]未配置timeColumn,无法执行按日期删除增量逻辑");
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将Base64字符串转换为byte[]
|
||||
*/
|
||||
@@ -518,6 +576,10 @@ public class SyncTableParseService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建达梦MERGE INTO SQL(存在更新,不存在插入)
|
||||
* 根据表配置CLOB_COLUMN动态使用TO_CLOB(?),不再硬编码后缀_OVERTIME
|
||||
*/
|
||||
/**
|
||||
* 构建达梦MERGE INTO SQL(存在更新,不存在插入)
|
||||
*/
|
||||
@@ -558,7 +620,6 @@ public class SyncTableParseService {
|
||||
sb.append(")");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量执行MERGE
|
||||
*/
|
||||
@@ -581,7 +642,7 @@ public class SyncTableParseService {
|
||||
private List<String> getTimeColumns(String tableName) {
|
||||
String sql = "SELECT COLUMN_NAME FROM ALL_TAB_COLUMNS " +
|
||||
"WHERE TABLE_NAME = ? AND OWNER = ? " +
|
||||
"AND DATA_TYPE IN ('DATE', 'TIMESTAMP')";
|
||||
"AND DATA_TYPE IN ('DATE', 'TIMESTAMP','DATETIME')";
|
||||
try {
|
||||
List<String> timeColumns = targetJdbcTemplate.queryForList(sql, String.class, tableName, currentSchema);
|
||||
log.debug("表 {} 的时间字段: {}", tableName, timeColumns);
|
||||
@@ -596,59 +657,63 @@ public class SyncTableParseService {
|
||||
* 将字符串转换为 java.sql.Timestamp(增强版)
|
||||
*/
|
||||
private Timestamp convertToTimestamp(String timeStr) {
|
||||
if (timeStr == null || timeStr.isEmpty()) {
|
||||
return null;
|
||||
if (timeStr == null || StrUtil.isBlank(timeStr)) {
|
||||
log.warn("时间字符串为空,使用当日零点兜底");
|
||||
return Timestamp.valueOf(LocalDate.now(java.time.ZoneId.of("Asia/Shanghai")).atStartOfDay());
|
||||
}
|
||||
timeStr = timeStr.trim();
|
||||
if (timeStr.startsWith("\"") && timeStr.endsWith("\"")) {
|
||||
timeStr = timeStr.substring(1, timeStr.length() - 1);
|
||||
}
|
||||
|
||||
// 1. 带时区 ISO 2026-06-10T14:53:23.853+08:00
|
||||
try {
|
||||
// 去除可能的引号
|
||||
timeStr = timeStr.trim();
|
||||
if (timeStr.startsWith("\"") && timeStr.endsWith("\"")) {
|
||||
timeStr = timeStr.substring(1, timeStr.length() - 1);
|
||||
}
|
||||
java.time.OffsetDateTime offsetDateTime = java.time.OffsetDateTime.parse(timeStr);
|
||||
LocalDateTime ldt = offsetDateTime.atZoneSameInstant(java.time.ZoneId.of("Asia/Shanghai")).toLocalDateTime();
|
||||
return Timestamp.valueOf(ldt);
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
// 尝试多种解析器
|
||||
// 1. ISO_OFFSET_DATE_TIME (支持 2025-04-17T03:42:04.000+00:00)
|
||||
try {
|
||||
java.time.OffsetDateTime offsetDateTime = java.time.OffsetDateTime.parse(timeStr);
|
||||
return Timestamp.valueOf(offsetDateTime.toLocalDateTime());
|
||||
} catch (Exception e) {
|
||||
// 继续尝试其他格式
|
||||
}
|
||||
// 2. 本地带T 2026-06-10T14:53:23.853
|
||||
try {
|
||||
java.time.LocalDateTime localDateTime = java.time.LocalDateTime.parse(timeStr);
|
||||
return Timestamp.valueOf(localDateTime);
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
// 2. ISO_LOCAL_DATE_TIME (支持 2025-04-17T03:42:04)
|
||||
try {
|
||||
java.time.LocalDateTime localDateTime = java.time.LocalDateTime.parse(timeStr);
|
||||
return Timestamp.valueOf(localDateTime);
|
||||
} catch (Exception e) {
|
||||
// 继续尝试其他格式
|
||||
}
|
||||
// 3. yyyy-MM-dd HH:mm:ss.SSS
|
||||
try {
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
|
||||
LocalDateTime ldt = LocalDateTime.parse(timeStr, formatter);
|
||||
return Timestamp.valueOf(ldt);
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
// 3. 自定义格式: yyyy-MM-dd HH:mm:ss
|
||||
try {
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
LocalDateTime ldt = LocalDateTime.parse(timeStr, formatter);
|
||||
return Timestamp.valueOf(ldt);
|
||||
} catch (Exception e) {
|
||||
// 继续尝试其他格式
|
||||
}
|
||||
// 4. yyyy-MM-dd HH:mm:ss
|
||||
try {
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
LocalDateTime ldt = LocalDateTime.parse(timeStr, formatter);
|
||||
return Timestamp.valueOf(ldt);
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
// 4. 日期格式: yyyy-MM-dd
|
||||
try {
|
||||
LocalDate ld = LocalDate.parse(timeStr, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
|
||||
return Timestamp.valueOf(ld.atStartOfDay());
|
||||
} catch (Exception e) {
|
||||
// 继续尝试其他格式
|
||||
}
|
||||
|
||||
// 5. 最后尝试直接转换
|
||||
return Timestamp.valueOf(timeStr);
|
||||
// 5. 纯日期 yyyy-MM-dd
|
||||
try {
|
||||
LocalDate ld = LocalDate.parse(timeStr, DateTimeFormatter.ISO_LOCAL_DATE);
|
||||
return Timestamp.valueOf(ld.atStartOfDay());
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
// 6. 纯时分秒 HH:mm:ss(解决MINS_TIME数组内时间)
|
||||
try {
|
||||
DateTimeFormatter timeFmt = DateTimeFormatter.ofPattern("HH:mm:ss");
|
||||
LocalTime lt = LocalTime.parse(timeStr, timeFmt);
|
||||
LocalDate today = LocalDate.now(java.time.ZoneId.of("Asia/Shanghai"));
|
||||
LocalDateTime ldt = LocalDateTime.of(today, lt);
|
||||
return Timestamp.valueOf(ldt);
|
||||
} catch (Exception e) {
|
||||
log.warn("时间转换失败: {}, 返回null", timeStr);
|
||||
return null;
|
||||
log.error("所有格式均无法解析时间字符串:[{}],使用当日零点兜底", timeStr, e);
|
||||
// 兜底返回当日零点,防止NOT NULL字段插入null报错
|
||||
return Timestamp.valueOf(LocalDate.now(java.time.ZoneId.of("Asia/Shanghai")).atStartOfDay());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 构建INSERT语句
|
||||
*/
|
||||
@@ -668,6 +733,7 @@ public class SyncTableParseService {
|
||||
return sql.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 执行批量插入
|
||||
*/
|
||||
@@ -684,6 +750,105 @@ public class SyncTableParseService {
|
||||
return Arrays.stream(results).sum();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 专用:带动态时间字段,先删当日再批量插入
|
||||
* @param tableName 表名
|
||||
* @param columns 全表字段
|
||||
* @param dataList 待入库数据
|
||||
* @param fileDate 文件名内日期 yyyyMMdd
|
||||
* @param timeColumn 配置的时间字段(如TIME_ID)
|
||||
*/
|
||||
private void deleteDayThenInsert(String tableName, List<String> columns, List<Map<String, Object>> dataList, String fileDate, String timeColumn) throws Exception {
|
||||
// 读取本表CLOB字段配置
|
||||
String clobText = tableClobCache.get(tableName.toUpperCase());
|
||||
List<String> clobFields = Arrays.stream(clobText.split(","))
|
||||
.map(String::trim)
|
||||
.map(String::toUpperCase)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 动态拼接删除SQL,使用配置的timeColumn
|
||||
String deleteSql = String.format("DELETE FROM %s WHERE TRUNC(%s) = TO_DATE(?, 'yyyyMMdd')", tableName, timeColumn);
|
||||
|
||||
// 2、INSERT SQL 纯?占位符,无TO_CLOB
|
||||
StringBuilder colSb = new StringBuilder();
|
||||
StringBuilder valSb = new StringBuilder();
|
||||
for (String col : columns) {
|
||||
if (colSb.length() > 0) {
|
||||
colSb.append(",");
|
||||
valSb.append(",");
|
||||
}
|
||||
colSb.append(col);
|
||||
valSb.append("?");
|
||||
}
|
||||
String insertSql = "INSERT INTO " + tableName + "(" + colSb + ") VALUES(" + valSb + ")";
|
||||
|
||||
Connection conn = targetJdbcTemplate.getDataSource().getConnection();
|
||||
conn.setAutoCommit(false);
|
||||
PreparedStatement delStmt = null;
|
||||
PreparedStatement insStmt = null;
|
||||
final int BATCH_COMMIT = 1000;
|
||||
int batchCnt = 0;
|
||||
|
||||
try {
|
||||
// 执行删除当日数据
|
||||
delStmt = conn.prepareStatement(deleteSql);
|
||||
delStmt.setString(1, fileDate);
|
||||
int delRows = delStmt.executeUpdate();
|
||||
log.info("专用入库:根据字段{}删除{}日原有{}条记录", timeColumn, fileDate, delRows);
|
||||
|
||||
insStmt = conn.prepareStatement(insertSql);
|
||||
for (Map<String, Object> row : dataList) {
|
||||
for (int i = 0; i < columns.size(); i++) {
|
||||
String col = columns.get(i);
|
||||
Object val = row.get(col);
|
||||
int paramIndex = i + 1;
|
||||
// CLOB文本使用原生Clob完整存储,不截断
|
||||
if (clobFields.contains(col) && val instanceof String) {
|
||||
Clob clob = conn.createClob();
|
||||
clob.setString(1, (String) val);
|
||||
insStmt.setClob(paramIndex, clob);
|
||||
} else {
|
||||
insStmt.setObject(paramIndex, val);
|
||||
}
|
||||
}
|
||||
insStmt.addBatch();
|
||||
batchCnt++;
|
||||
// 分段提交
|
||||
if (batchCnt >= BATCH_COMMIT) {
|
||||
insStmt.executeBatch();
|
||||
batchCnt = 0;
|
||||
}
|
||||
}
|
||||
// 提交剩余批次
|
||||
if (batchCnt > 0) {
|
||||
insStmt.executeBatch();
|
||||
}
|
||||
conn.commit();
|
||||
log.info("专用入库完成,共插入{}条数据", dataList.size());
|
||||
} catch (Exception e) {
|
||||
conn.rollback();
|
||||
log.error("{} 入库失败,事务回滚", tableName, e);
|
||||
throw new Exception("入库异常", e);
|
||||
} finally {
|
||||
if (delStmt != null) delStmt.close();
|
||||
if (insStmt != null) insStmt.close();
|
||||
if (conn != null) conn.close();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 从文件名提取同步日期 yyyyMMdd
|
||||
*/
|
||||
private String extractDateFromFileName(String fileName) {
|
||||
Pattern pattern = Pattern.compile("_(FULL|INC)_(\\d{8})\\.txt");
|
||||
Matcher matcher = pattern.matcher(fileName);
|
||||
if (matcher.find()) {
|
||||
return matcher.group(2);
|
||||
}
|
||||
throw new RuntimeException("文件无法提取同步日期:" + fileName);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 手动导入指定的远程文件(单个文件处理)
|
||||
*/
|
||||
@@ -708,13 +873,11 @@ public class SyncTableParseService {
|
||||
String tableName = extractTableNameFromFile(remoteFileName);
|
||||
|
||||
try {
|
||||
|
||||
// 缓存表配置
|
||||
Map<String, String> tableConfigCache = syncData();
|
||||
log.info("加载表配置缓存完成,共 {} 张表", tableConfigCache.size());
|
||||
syncData();
|
||||
|
||||
// 导入数据
|
||||
importJsonToTable(tableName, result.getLocalFilePath(),tableConfigCache);
|
||||
importJsonToTable(tableName, result.getLocalFilePath());
|
||||
|
||||
// 导入成功后删除本地文件
|
||||
deleteLocalFile(result.getLocalFilePath());
|
||||
@@ -773,6 +936,26 @@ public class SyncTableParseService {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 判断当前字段是否配置为CLOB大文本字段
|
||||
* @param col 字段名(大写)
|
||||
* @param tableName 表名(大写)
|
||||
* @param tableConfigCache 表配置缓存 key=表名大写 value=逗号分隔CLOB字段大写
|
||||
*/
|
||||
private boolean isClobColumn(String col, String tableName, Map<String, String> tableConfigCache) {
|
||||
String clobColStr = tableConfigCache.get(tableName);
|
||||
if (StrUtil.isBlank(clobColStr)) {
|
||||
return false;
|
||||
}
|
||||
// 分割配置的CLOB字段列表,统一大写匹配
|
||||
List<String> clobCols = Arrays.stream(clobColStr.split(","))
|
||||
.map(String::trim)
|
||||
.map(String::toUpperCase)
|
||||
.collect(Collectors.toList());
|
||||
return clobCols.contains(col.toUpperCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试方法
|
||||
*/
|
||||
@@ -0,0 +1,523 @@
|
||||
package com.njcn.relational.service;
|
||||
|
||||
import cn.hutool.core.date.DatePattern;
|
||||
import cn.hutool.core.date.DateTime;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import com.njcn.relational.mapper.DynamicSyncMapper;
|
||||
import com.njcn.relational.mapper.SyncTableConfigMapper;
|
||||
import com.njcn.relational.pojo.bo.UploadResult;
|
||||
import com.njcn.relational.pojo.po.SyncTableConfig;
|
||||
import com.njcn.relational.pojo.po.SyncTableLogs;
|
||||
import com.njcn.relational.serializer.BlobSerializer;
|
||||
import com.njcn.relational.serializer.ClobSerializer;
|
||||
import com.njcn.relational.utils.SftpUploadUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.io.*;
|
||||
import java.sql.Blob;
|
||||
import java.sql.Clob;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class SyncTwoDmExportService extends ServiceImpl<SyncTableConfigMapper, SyncTableConfig> {
|
||||
|
||||
@Autowired
|
||||
private SyncTableLogsService syncTableLogsService;
|
||||
|
||||
@Autowired
|
||||
private DynamicSyncMapper dynamicSyncMapper;
|
||||
|
||||
@Value("${sync.export.remotePath}")
|
||||
private String remotePath;
|
||||
|
||||
@Value("${sync.export.localPath}")
|
||||
private String localPath;
|
||||
|
||||
@Value("${sync.ip}")
|
||||
private String ip;
|
||||
|
||||
@Value("${sync.port:22}")
|
||||
private Integer port;
|
||||
|
||||
@Value("${sync.username}")
|
||||
private String username;
|
||||
|
||||
@Value("${sync.password}")
|
||||
private String password;
|
||||
|
||||
// 新增:表名 -> 时间字段 缓存
|
||||
private Map<String, String> timeColumnCache = new HashMap<>();
|
||||
private Map<String, String> dataTimeTypeCache = new HashMap<>();
|
||||
|
||||
|
||||
private static final int BATCH_SIZE = 10000;
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||
private static final DateTimeFormatter FILE_DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
|
||||
|
||||
// JSON序列化工具(自动处理时间、换行符等)
|
||||
private final ObjectMapper objectMapper = new ObjectMapper()
|
||||
.registerModule(new JavaTimeModule())
|
||||
.registerModule(new SimpleModule() {{
|
||||
addSerializer(Blob.class, new BlobSerializer());
|
||||
addSerializer(Clob.class, new ClobSerializer());
|
||||
}})
|
||||
.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"))
|
||||
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
|
||||
.enable(SerializationFeature.INDENT_OUTPUT)
|
||||
.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
|
||||
; // 格式化输出,可读性好
|
||||
|
||||
// 缓存当前Schema
|
||||
private String currentSchema;
|
||||
|
||||
// 记录本次生成的所有文件
|
||||
private final List<String> generatedFiles = new ArrayList<>();
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
currentSchema = dynamicSyncMapper.getCurrentUser();
|
||||
log.info("当前数据库Schema: {}", currentSchema);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有启用的全量表(从配置表)
|
||||
*/
|
||||
public List<SyncTableConfig> getEnabledFullTables() {
|
||||
LambdaQueryWrapper<SyncTableConfig> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(SyncTableConfig::getEnabled, 1)
|
||||
.eq(SyncTableConfig::getSyncMode, "FULL")
|
||||
.orderByAsc(SyncTableConfig::getSortOrder);
|
||||
return this.list(wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有启用的增量表(从配置表)
|
||||
*/
|
||||
public List<SyncTableConfig> getEnabledIncrementTables() {
|
||||
LambdaQueryWrapper<SyncTableConfig> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(SyncTableConfig::getEnabled, 1)
|
||||
.eq(SyncTableConfig::getSyncMode, "INC")
|
||||
.orderByAsc(SyncTableConfig::getSortOrder);
|
||||
return this.list(wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保本地目录存在
|
||||
*/
|
||||
private void ensureLocalDirectory() {
|
||||
File dir = new File(localPath);
|
||||
if (!dir.exists()) {
|
||||
dir.mkdirs();
|
||||
log.info("创建目录: {}", localPath);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 全量同步所有表
|
||||
*/
|
||||
public void syncAllFullTables(String dateStr) {
|
||||
List<SyncTableConfig> tables = getEnabledFullTables();
|
||||
log.info("========== 全量同步开始,共 {} 张表 ==========", tables.size());
|
||||
|
||||
// 确保目录存在
|
||||
ensureLocalDirectory();
|
||||
generatedFiles.clear();
|
||||
|
||||
for (SyncTableConfig config : tables) {
|
||||
String tableName = config.getTableName();
|
||||
try {
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
/*IService<?> service = registry.getServiceByTableName(tableName);
|
||||
if (service != null) {
|
||||
String filePath = exportFullTableWithEntity(service, tableName, dateStr);
|
||||
log.info("表 {} 使用实体方式导出完成", tableName);
|
||||
} else {*/
|
||||
String filePath = exportFullTableToJson(tableName, dateStr);
|
||||
log.info("表 {} 使用Map方式导出完成", tableName);
|
||||
// }
|
||||
|
||||
long duration = System.currentTimeMillis() - startTime;
|
||||
log.info("表 {} 全量导出完成,耗时 {} ms", tableName, duration);
|
||||
} catch (Exception e) {
|
||||
log.error("表 {} 全量导出失败", tableName, e);
|
||||
}
|
||||
}
|
||||
syncAllIncrementTables(dateStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* 增量同步所有表
|
||||
*/
|
||||
public void syncAllIncrementTables(String dateStr) {
|
||||
List<SyncTableConfig> tables = getEnabledIncrementTables();
|
||||
timeColumnCache = tables.stream().filter(it -> StrUtil.isNotBlank(it.getTimeColumn())).collect(Collectors.toMap(SyncTableConfig::getTableName, SyncTableConfig::getTimeColumn));
|
||||
dataTimeTypeCache = tables.stream().filter(it -> StrUtil.isNotBlank(it.getDataTimeType())).collect(Collectors.toMap(SyncTableConfig::getTableName, SyncTableConfig::getDataTimeType));
|
||||
|
||||
|
||||
log.info("========== 增量同步开始,共 {} 张表 ==========", tables.size());
|
||||
boolean localFlag = true;
|
||||
boolean resultFlag = true;
|
||||
for (SyncTableConfig config : tables) {
|
||||
String tableName = config.getTableName();
|
||||
try {
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
String filePath = exportTodayDataToJson(tableName, dateStr);
|
||||
log.info("表 {} 使用Map方式导出完成", tableName);
|
||||
|
||||
long duration = System.currentTimeMillis() - startTime;
|
||||
log.info("表 {} 增量导出完成,耗时 {} ms", tableName, duration);
|
||||
} catch (Exception e) {
|
||||
log.error("表 {} 增量同步失败", tableName, e);
|
||||
localFlag = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 推送所有文件到横向隔离设备
|
||||
if (!generatedFiles.isEmpty()) {
|
||||
try {
|
||||
uploadAllFiles();
|
||||
} catch (Exception e) {
|
||||
log.error("批量上传失败", e);
|
||||
resultFlag = false;
|
||||
}
|
||||
} else {
|
||||
resultFlag = false;
|
||||
}
|
||||
|
||||
SyncTableLogs syncTableLogs = new SyncTableLogs();
|
||||
syncTableLogs.setLocalResult(localFlag?1:0);
|
||||
syncTableLogs.setResult((resultFlag&&localFlag) ? 1:0);
|
||||
syncTableLogs.setTimeId(LocalDate.parse(dateStr, DateTimeFormatter.ofPattern("yyyyMMdd")));
|
||||
syncTableLogs.setCreateTime(LocalDateTime.now());
|
||||
syncTableLogsService.saveOrUpdate(syncTableLogs);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传所有生成的文件(批量上传)
|
||||
*/
|
||||
private void uploadAllFiles() throws Exception {
|
||||
if (generatedFiles.isEmpty()) {
|
||||
log.info("没有文件需要上传");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
List<UploadResult> resultList = SftpUploadUtil.batchUploadFiles(
|
||||
ip, port, username, password, generatedFiles, remotePath
|
||||
);
|
||||
|
||||
// 统计上传结果
|
||||
long successCount = resultList.stream().filter(UploadResult::isSuccess).count();
|
||||
long failCount = resultList.size() - successCount;
|
||||
|
||||
log.info("批量上传完成 - 成功: {}, 失败: {}, 总计: {}", successCount, failCount, resultList.size());
|
||||
// 记录失败的文件
|
||||
for (UploadResult result : resultList) {
|
||||
if (!result.isSuccess()) {
|
||||
log.error("上传失败文件: {}, 原因: {}", result.getLocalFilePath(), result.getErrorMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Map方式全量导出为JSON(保存为.txt文件)
|
||||
*/
|
||||
@Transactional
|
||||
public String exportFullTableToJson(String tableName, String dateStr) throws IOException {
|
||||
String fileName = String.format("%s_FULL_%s.txt", tableName, dateStr);
|
||||
String filePath = localPath + fileName;
|
||||
|
||||
log.info("开始导出全量表: {} -> {}", tableName, filePath);
|
||||
|
||||
List<Map<String, Object>> allData = new ArrayList<>();
|
||||
int offset = 0;
|
||||
|
||||
while (true) {
|
||||
List<Map<String, Object>> batchData = dynamicSyncMapper.selectPage(
|
||||
tableName, BATCH_SIZE, offset);
|
||||
|
||||
if (batchData.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
allData.addAll(batchData);
|
||||
log.info("表 {} 已读取 {} 条记录", tableName, allData.size());
|
||||
|
||||
if (batchData.size() < BATCH_SIZE) {
|
||||
break;
|
||||
}
|
||||
offset += BATCH_SIZE;
|
||||
}
|
||||
|
||||
// 写入JSON格式到.txt文件
|
||||
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath))) {
|
||||
objectMapper.writeValue(bos, allData);
|
||||
}
|
||||
|
||||
|
||||
// 记录生成的文件
|
||||
generatedFiles.add(filePath);
|
||||
|
||||
log.info("表 {} 全量导出完成,共 {} 条记录,文件: {}", tableName, allData.size(), filePath);
|
||||
return filePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map方式增量导出为JSON(保存为.txt文件)
|
||||
*/
|
||||
@Transactional
|
||||
public String exportTodayDataToJson(String tableName, String dateStr) throws IOException {
|
||||
|
||||
// 1. 判断表属于哪种周期:D/M/Q/Y
|
||||
String periodType = getTablePeriodType(tableName);
|
||||
if (periodType == null) {
|
||||
log.error("表 {} 无法识别周期类型,跳过增量导出", tableName);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 2. 根据周期构建 开始/结束 时间
|
||||
Map<String, String> timeRange = buildPeriodTimeRange(periodType, dateStr);
|
||||
String dateStart = timeRange.get("start");
|
||||
String dateEnd = timeRange.get("end");
|
||||
|
||||
log.info("导出表 {} 数据: {} 至 {}", tableName, dateStart, dateEnd);
|
||||
|
||||
String timeColumn = getTimeColumn(tableName);
|
||||
|
||||
List<Map<String, Object>> queryData = dynamicSyncMapper.selectTodayData(
|
||||
tableName, timeColumn, dateStart, dateEnd);
|
||||
|
||||
if (queryData.isEmpty()) {
|
||||
log.info("表 {} {} - {}无数据", tableName, dateStart, dateEnd);
|
||||
return null;
|
||||
}
|
||||
|
||||
String fileName = String.format("%s_INC_%s.txt", tableName, dateStr);
|
||||
String filePath = localPath + fileName;
|
||||
|
||||
// 写入JSON格式到.txt文件
|
||||
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath))) {
|
||||
objectMapper.writeValue(bos, queryData);
|
||||
}
|
||||
|
||||
// 记录生成的文件
|
||||
generatedFiles.add(filePath);
|
||||
|
||||
log.info("表 {} {}数据导出完成,共 {} 条记录,文件: {}", tableName, dateStr, queryData.size(), filePath);
|
||||
return filePath;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 从表名识别周期类型
|
||||
* D=天, M=月, Q=季, Y=年
|
||||
*/
|
||||
private String getTablePeriodType(String tableName) {
|
||||
String dTypeCreateTitle = dataTimeTypeCache.get(tableName);
|
||||
if (dTypeCreateTitle != null) {
|
||||
if ("D".equals(dTypeCreateTitle) || "M".equalsIgnoreCase(tableName) || "Q".equals(tableName) || "Y".equals(tableName)) {
|
||||
return dTypeCreateTitle;
|
||||
}
|
||||
} else {
|
||||
if (tableName.endsWith("_D")) {
|
||||
return "D";
|
||||
}
|
||||
if (tableName.endsWith("_M")) {
|
||||
return "M";
|
||||
}
|
||||
if (tableName.endsWith("_Q")) {
|
||||
return "Q";
|
||||
}
|
||||
if (tableName.endsWith("_Y")) {
|
||||
return "Y";
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据周期 & 入参日期,计算【当前周期】时间范围(JDK8 兼容)
|
||||
*/
|
||||
private Map<String, String> buildPeriodTimeRange(String periodType, String dateStr) {
|
||||
DateTime baseDate = DateUtil.parse(dateStr, DatePattern.PURE_DATE_PATTERN);
|
||||
String start;
|
||||
String end;
|
||||
|
||||
// JDK8 标准 switch
|
||||
switch (periodType) {
|
||||
case "D":
|
||||
// 入参日期 当天
|
||||
start = DateUtil.format(DateUtil.beginOfDay(baseDate), DatePattern.NORM_DATETIME_PATTERN);
|
||||
end = DateUtil.format(DateUtil.endOfDay(baseDate), DatePattern.NORM_DATETIME_PATTERN);
|
||||
break;
|
||||
case "M":
|
||||
// 入参日期 所在整月
|
||||
start = DateUtil.format(DateUtil.beginOfMonth(baseDate), DatePattern.NORM_DATETIME_PATTERN);
|
||||
end = DateUtil.format(DateUtil.endOfMonth(baseDate), DatePattern.NORM_DATETIME_PATTERN);
|
||||
break;
|
||||
case "Q":
|
||||
// 入参日期 所在整季度
|
||||
start = DateUtil.format(DateUtil.beginOfQuarter(baseDate), DatePattern.NORM_DATETIME_PATTERN);
|
||||
end = DateUtil.format(DateUtil.endOfQuarter(baseDate), DatePattern.NORM_DATETIME_PATTERN);
|
||||
break;
|
||||
case "Y":
|
||||
// 入参日期 所在整年
|
||||
start = DateUtil.format(DateUtil.beginOfYear(baseDate), DatePattern.NORM_DATETIME_PATTERN);
|
||||
end = DateUtil.format(DateUtil.endOfYear(baseDate), DatePattern.NORM_DATETIME_PATTERN);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("不支持的周期类型:" + periodType);
|
||||
}
|
||||
|
||||
Map<String, String> map = new HashMap<>(2);
|
||||
map.put("start", start);
|
||||
map.put("end", end);
|
||||
return map;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取时间字段
|
||||
*/
|
||||
private String getTimeColumn(String tableName) {
|
||||
String createTitle = timeColumnCache.get(tableName);
|
||||
if (Objects.isNull(createTitle)) {
|
||||
switch (tableName) {
|
||||
case "R_STAT_DATA_FLICKER_D":
|
||||
case "R_STAT_DATA_FLUC_D":
|
||||
case "R_STAT_DATA_HARMPHASIC_I_D":
|
||||
case "R_STAT_DATA_HARMPHASIC_V_D":
|
||||
case "R_STAT_DATA_HARMPOWER_P_D":
|
||||
case "R_STAT_DATA_HARMPOWER_Q_D":
|
||||
case "R_STAT_DATA_HARMPOWER_S_D":
|
||||
case "R_STAT_DATA_HARMRATE_I_D":
|
||||
case "R_STAT_DATA_HARMRATE_V_D":
|
||||
case "R_STAT_DATA_INHARM_I_D":
|
||||
case "R_STAT_DATA_INHARM_V_D":
|
||||
case "R_STAT_DATA_I_D":
|
||||
case "R_STAT_DATA_PLT_D":
|
||||
case "R_STAT_DATA_V_D":
|
||||
return "TIME";
|
||||
// 新增表
|
||||
case "R_STAT_COMASSES_D":
|
||||
case "R_STAT_ASSES_D":
|
||||
case "R_STAT_LIMIT_RATE_D":
|
||||
case "R_STAT_ONLINERATE_D":
|
||||
case "R_STAT_INTEGRITY_D":
|
||||
case "R_STAT_ORG_INTEGRITY_D":
|
||||
case "R_STAT_LIMIT_RATE_DETAIL_D":
|
||||
case "R_STAT_LIMIT_TARGET_D":
|
||||
return "TIME_ID";
|
||||
case "R_MP_POLLUTION_D":
|
||||
case "R_MP_V_THD":
|
||||
case "R_STAT_POLLUTION_ORG_D":
|
||||
case "R_STAT_POLLUTION_ORG_M":
|
||||
case "R_STAT_POLLUTION_ORG_Q":
|
||||
case "R_STAT_POLLUTION_ORG_Y":
|
||||
case "R_STAT_POLLUTION_SUBSTATION_D":
|
||||
case "R_STAT_POLLUTION_SUBSTATION_M":
|
||||
case "R_STAT_POLLUTION_SUBSTATION_Q":
|
||||
case "R_STAT_POLLUTION_SUBSTATION_Y":
|
||||
return "DATA_DATE";
|
||||
default:
|
||||
log.warn("表 {} 未配置时间字段,跳过", tableName);
|
||||
return "CREATE_TIME";
|
||||
}
|
||||
} else {
|
||||
return createTitle;
|
||||
}
|
||||
}
|
||||
|
||||
public void test() {
|
||||
try {
|
||||
// 确保目录存在
|
||||
File localDir = new File(localPath);
|
||||
if (!localDir.exists()) {
|
||||
localDir.mkdirs();
|
||||
}
|
||||
|
||||
// 生成txt文件
|
||||
String filePath = localPath + "test.txt";
|
||||
try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath))) {
|
||||
writer.write("test");
|
||||
}
|
||||
|
||||
// 上传到SFTP服务器
|
||||
SftpUploadUtil.uploadFile(ip, port, username, password, filePath, remotePath);
|
||||
|
||||
log.info("test.txt文件生成并上传成功");
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
public List<String> getPendingFiles() {
|
||||
List<String> matchedFiles = new ArrayList<>();
|
||||
Vector<?> files;
|
||||
try {
|
||||
// 列目录也纳入异常捕获
|
||||
files = SftpUploadUtil.listFiles(ip, port, username, password, remotePath);
|
||||
for (Object obj : files) {
|
||||
if (obj instanceof com.jcraft.jsch.ChannelSftp.LsEntry) {
|
||||
com.jcraft.jsch.ChannelSftp.LsEntry entry = (com.jcraft.jsch.ChannelSftp.LsEntry) obj;
|
||||
String fileName = entry.getFilename();
|
||||
matchedFiles.add(fileName);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("获取待处理文件列表失败,远端目录:{}", remotePath, e);
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return matchedFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理本地已处理过的文件
|
||||
*/
|
||||
public void cleanLocalFiles(int daysToKeep) {
|
||||
File localDir = new File(localPath);
|
||||
if (!localDir.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
long cutoffTime = System.currentTimeMillis() - (daysToKeep * 24L * 60 * 60 * 1000);
|
||||
File[] files = localDir.listFiles();
|
||||
|
||||
if (files != null) {
|
||||
int deletedCount = 0;
|
||||
for (File file : files) {
|
||||
if (file.isFile() && file.lastModified() < cutoffTime) {
|
||||
file.delete();
|
||||
deletedCount++;
|
||||
}
|
||||
}
|
||||
log.info("清理了10.11.7.5二区服务器 {} 个七天前的本地txt文件", deletedCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,7 @@ import cn.hutool.core.util.StrUtil;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.njcn.relational.pojo.dto.DcloudSyncQuery;
|
||||
import com.njcn.relational.service.DcloudBusService;
|
||||
import com.njcn.relational.service.SgConPlantCService;
|
||||
import com.njcn.relational.service.*;
|
||||
import dcloud.common.InnerServiceBus.ServiceBus;
|
||||
import dcloud.model.common.ConModel;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -28,6 +27,37 @@ public class DcloudBusServiceImpl implements DcloudBusService {
|
||||
|
||||
private final SgConPlantCService sgConPlantCService;
|
||||
|
||||
private final SGConPlantBService sgConPlantBService;
|
||||
|
||||
private final SGConSubstationBService sgConSubstationBService;
|
||||
|
||||
private final SGConSubstationCService sgConSubstationCService;
|
||||
|
||||
|
||||
/**
|
||||
* 数据入库入口
|
||||
*/
|
||||
@Override
|
||||
public boolean syncDataToDb(DcloudSyncQuery query) {
|
||||
log.info("开始从云平台同步厂站数据到本地数据库");
|
||||
|
||||
ServiceBus serviceBus = initServiceBus();
|
||||
if (serviceBus == null) {
|
||||
log.error("ServiceBus初始化失败,终止同步");
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
handleConModelSync(serviceBus, query);
|
||||
log.info("厂站数据同步完成");
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("厂站数据同步失败", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 统一同步入口
|
||||
*/
|
||||
@@ -175,7 +205,20 @@ public class DcloudBusServiceImpl implements DcloudBusService {
|
||||
log.info("[{}] 第{}页查询到 {} 条数据,开始入库", tableName, pageIndex, pageData.size());
|
||||
|
||||
// 分批入库(每页数据直接入库)
|
||||
boolean batchResult = sgConPlantCService.batchSyncFromDcloud(pageData);
|
||||
boolean batchResult = false;
|
||||
if("SG_CON_PLANT_C".equals(tableName)) {
|
||||
log.info("【全量同步入口】执行同步厂站基础数据---------");
|
||||
batchResult = sgConPlantCService.batchSyncFromDcloud(pageData);
|
||||
}else if("SG_CON_PLANT_B".equals(tableName)){
|
||||
log.info("【全量同步入口】执行同步厂站绑定数据1111111111");
|
||||
batchResult = sgConPlantBService.batchSyncFromDcloud(pageData);
|
||||
}else if("SG_CON_SUBSTATION_B".equals(tableName)){
|
||||
log.info("【全量同步入口】执行变电站站基础数据---------");
|
||||
batchResult = sgConSubstationBService.batchSyncFromDcloud(pageData);
|
||||
}else if("SG_CON_SUBSTATION_C".equals(tableName)){
|
||||
log.info("【全量同步入口】执行变电站站绑定数据---------");
|
||||
batchResult = sgConSubstationCService.batchSyncFromDcloud(pageData);
|
||||
}
|
||||
if (batchResult) {
|
||||
successCount += pageData.size();
|
||||
log.info("[{}] 第{}页入库成功,当前累计成功 {} 条", tableName, pageIndex, successCount);
|
||||
@@ -192,30 +235,6 @@ public class DcloudBusServiceImpl implements DcloudBusService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据入库入口
|
||||
*/
|
||||
@Override
|
||||
public boolean syncDataToDb() {
|
||||
log.info("开始从云平台同步厂站数据到本地数据库");
|
||||
|
||||
DcloudSyncQuery query = DcloudSyncQuery.buildPlantDefault(null, null, 1, DEFAULT_PAGE_SIZE);
|
||||
|
||||
ServiceBus serviceBus = initServiceBus();
|
||||
if (serviceBus == null) {
|
||||
log.error("ServiceBus初始化失败,终止同步");
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
handleConModelSync(serviceBus, query);
|
||||
log.info("厂站数据同步完成");
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("厂站数据同步失败", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ===================== 便捷方法 =====================
|
||||
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
package com.njcn.relational.service.impl;
|
||||
|
||||
|
||||
|
||||
import com.njcn.relational.service.InsertIDataFlicker;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
|
||||
/**
|
||||
* Description:
|
||||
* Date: 2024/11/18 11:17【需求编号】
|
||||
*
|
||||
* @author clam
|
||||
* @version V1.0.0
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class InsertDataFlickerImpl implements InsertIDataFlicker {
|
||||
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package com.njcn.relational.service.impl;
|
||||
|
||||
|
||||
|
||||
import com.njcn.relational.service.InsertIDataFluc;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* Description:
|
||||
* Date: 2024/11/18 11:17【需求编号】
|
||||
*
|
||||
* @author clam
|
||||
* @version V1.0.0
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class InsertDataFlucImpl implements InsertIDataFluc {
|
||||
|
||||
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package com.njcn.relational.service.impl;
|
||||
|
||||
|
||||
|
||||
import com.njcn.relational.service.InsertIDataHarmRateI;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
|
||||
/**
|
||||
* @author xy
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class InsertDataHarmRateIImpl implements InsertIDataHarmRateI {
|
||||
|
||||
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package com.njcn.relational.service.impl;
|
||||
|
||||
|
||||
|
||||
import com.njcn.relational.service.InsertIDataHarmRateV;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
|
||||
/**
|
||||
* @author xy
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class InsertDataHarmRateVImpl implements InsertIDataHarmRateV {
|
||||
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package com.njcn.relational.service.impl;
|
||||
|
||||
|
||||
|
||||
import com.njcn.relational.service.InsertIDataHarmphasicI;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
|
||||
/**
|
||||
* Description:
|
||||
* Date: 2024/11/18 11:17【需求编号】
|
||||
*
|
||||
* @author clam
|
||||
* @version V1.0.0
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class InsertDataHarmphasicIImpl implements InsertIDataHarmphasicI {
|
||||
|
||||
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package com.njcn.relational.service.impl;
|
||||
|
||||
|
||||
|
||||
import com.njcn.relational.service.InsertIDataHarmphasicV;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* Description:
|
||||
* Date: 2024/11/18 11:17【需求编号】
|
||||
*
|
||||
* @author clam
|
||||
* @version V1.0.0
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class InsertDataHarmphasicVImpl implements InsertIDataHarmphasicV {
|
||||
|
||||
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package com.njcn.relational.service.impl;
|
||||
|
||||
|
||||
|
||||
import com.njcn.relational.service.InsertIDataHarmpowerP;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
|
||||
/**
|
||||
* Description:
|
||||
* Date: 2024/11/18 11:17【需求编号】
|
||||
*
|
||||
* @author clam
|
||||
* @version V1.0.0
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class InsertDataHarmpowerPImpl implements InsertIDataHarmpowerP {
|
||||
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package com.njcn.relational.service.impl;
|
||||
|
||||
|
||||
|
||||
import com.njcn.relational.service.InsertIDataHarmpowerQ;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
|
||||
/**
|
||||
* Description:
|
||||
* Date: 2024/11/18 11:17【需求编号】
|
||||
*
|
||||
* @author clam
|
||||
* @version V1.0.0
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class InsertDataHarmpowerQImpl implements InsertIDataHarmpowerQ {
|
||||
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package com.njcn.relational.service.impl;
|
||||
|
||||
|
||||
|
||||
import com.njcn.relational.service.InsertIDataHarmpowerS;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
|
||||
/**
|
||||
* Description:
|
||||
* Date: 2024/11/18 11:17【需求编号】
|
||||
*
|
||||
* @author clam
|
||||
* @version V1.0.0
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class InsertDataHarmpowerSImpl implements InsertIDataHarmpowerS {
|
||||
|
||||
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package com.njcn.relational.service.impl;
|
||||
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.date.DatePattern;
|
||||
import cn.hutool.core.date.LocalDateTimeUtil;
|
||||
import com.njcn.relational.service.InsertIDataI;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Description:
|
||||
* Date: 2024/11/18 11:17【需求编号】
|
||||
*
|
||||
* @author clam
|
||||
* @version V1.0.0
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class InsertDataIImpl implements InsertIDataI {
|
||||
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package com.njcn.relational.service.impl;
|
||||
|
||||
|
||||
|
||||
import com.njcn.relational.service.InsertIDataInharmI;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
|
||||
/**
|
||||
* Description:
|
||||
* Date: 2024/11/18 13:27【需求编号】
|
||||
*
|
||||
* @author clam
|
||||
* @version V1.0.0
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class InsertDataInharmIImpl implements InsertIDataInharmI {
|
||||
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package com.njcn.relational.service.impl;
|
||||
|
||||
|
||||
|
||||
import com.njcn.relational.service.InsertIDataInharmV;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
|
||||
/**
|
||||
* Description:
|
||||
* Date: 2024/11/18 13:27【需求编号】
|
||||
*
|
||||
* @author clam
|
||||
* @version V1.0.0
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class InsertDataInharmVImpl implements InsertIDataInharmV {
|
||||
|
||||
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package com.njcn.relational.service.impl;
|
||||
|
||||
|
||||
|
||||
import com.njcn.relational.service.InsertIDataPlt;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* Description:
|
||||
* Date: 2024/11/18 11:17【需求编号】
|
||||
*
|
||||
* @author clam
|
||||
* @version V1.0.0
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class InsertDataPltImpl implements InsertIDataPlt {
|
||||
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package com.njcn.relational.service.impl;
|
||||
|
||||
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.njcn.relational.mapper.RStatDataVRelationMapper;
|
||||
import com.njcn.relational.pojo.po.RStatDataVD;
|
||||
import com.njcn.relational.service.InsertIDataI;
|
||||
import com.njcn.relational.service.InsertIDataV;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
|
||||
/**
|
||||
* @author hongawen
|
||||
* @version 1.0
|
||||
* @data 2024/11/7 10:54
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class InsertDataVImpl extends ServiceImpl<RStatDataVRelationMapper, RStatDataVD> implements InsertIDataI {
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.njcn.relational.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.njcn.relational.mapper.SGConPlantBMapper;
|
||||
import com.njcn.relational.pojo.po.SGConPlantB;
|
||||
import com.njcn.relational.service.SGConPlantBService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class SGConPlantBServiceImpl extends ServiceImpl<SGConPlantBMapper, SGConPlantB>
|
||||
implements SGConPlantBService {
|
||||
|
||||
private static final int BATCH_SIZE = 500;
|
||||
|
||||
@Override
|
||||
public boolean batchSyncFromDcloud(JSONArray dataArr) {
|
||||
if (CollUtil.isEmpty(dataArr)) {
|
||||
return true;
|
||||
}
|
||||
int batchSize = 500;
|
||||
List<SGConPlantB> entityList = new ArrayList<>(dataArr.size());
|
||||
for (Object obj : dataArr) {
|
||||
JSONObject json = (JSONObject) obj;
|
||||
SGConPlantB plant = new SGConPlantB();
|
||||
|
||||
// 主键必存在
|
||||
if (json.containsKey("ID")) {
|
||||
plant.setId(json.getString("ID"));
|
||||
}
|
||||
|
||||
// 字符串字段,存在则赋值
|
||||
if (json.containsKey("ADDRESS")) plant.setAddress(json.getString("ADDRESS"));
|
||||
if (json.containsKey("CHECK_CODE")) plant.setCheckCode(json.getString("CHECK_CODE"));
|
||||
if (json.containsKey("COMM_TAG")) plant.setCommTag(json.getString("COMM_TAG"));
|
||||
if (json.containsKey("COMPANY_ID")) plant.setCompanyId(json.getString("COMPANY_ID"));
|
||||
if (json.containsKey("CONNECTIVE_PG_ID")) plant.setConnectivePgId(json.getString("CONNECTIVE_PG_ID"));
|
||||
if (json.containsKey("DCC_ID")) plant.setDccId(json.getString("DCC_ID"));
|
||||
if (json.containsKey("FAX_NO")) plant.setFaxNo(json.getString("FAX_NO"));
|
||||
if (json.containsKey("GENCOMPANY_ID")) plant.setGencompanyId(json.getString("GENCOMPANY_ID"));
|
||||
if (json.containsKey("NAME")) plant.setName(json.getString("NAME"));
|
||||
if (json.containsKey("NAME_ABBREVIATION")) plant.setNameAbbreviation(json.getString("NAME_ABBREVIATION"));
|
||||
if (json.containsKey("OWNER")) plant.setOwner(json.getString("OWNER"));
|
||||
if (json.containsKey("PHONE_NO")) plant.setPhoneNo(json.getString("PHONE_NO"));
|
||||
if (json.containsKey("POSTCODE")) plant.setPostcode(json.getString("POSTCODE"));
|
||||
if (json.containsKey("REGISTER_NAME")) plant.setRegisterName(json.getString("REGISTER_NAME"));
|
||||
if (json.containsKey("REMARKS")) plant.setRemarks(json.getString("REMARKS"));
|
||||
if (json.containsKey("SCS_TAG")) plant.setScsTag(json.getString("SCS_TAG"));
|
||||
if (json.containsKey("STAMP")) plant.setStamp(json.getString("STAMP"));
|
||||
if (json.containsKey("STATE_CODE")) plant.setStateCode(json.getString("STATE_CODE"));
|
||||
if (json.containsKey("SYS_FLAG")) plant.setSysFlag(json.getString("SYS_FLAG"));
|
||||
if (json.containsKey("TAG")) plant.setTag(json.getString("TAG"));
|
||||
|
||||
// Int数字字段
|
||||
if (json.containsKey("ASSETS_OWNERSHIP")) plant.setAssetsOwnership(json.getInteger("ASSETS_OWNERSHIP"));
|
||||
if (json.containsKey("LANDFORM")) plant.setLandform(json.getInteger("LANDFORM"));
|
||||
if (json.containsKey("MAX_VOLTAGE_TYPE")) plant.setMaxVoltageType(json.getInteger("MAX_VOLTAGE_TYPE"));
|
||||
if (json.containsKey("OPERATE_STATE")) plant.setOperateState(json.getInteger("OPERATE_STATE"));
|
||||
if (json.containsKey("PLANT_DETAIL_TYPE")) plant.setPlantDetailType(json.getInteger("PLANT_DETAIL_TYPE"));
|
||||
if (json.containsKey("PLANT_TYPE")) plant.setPlantType(json.getInteger("PLANT_TYPE"));
|
||||
if (json.containsKey("REGION")) plant.setRegion(json.getInteger("REGION"));
|
||||
|
||||
// BigDecimal 浮点字段
|
||||
if (json.containsKey("ALTITUDE")) plant.setAltitude(json.getBigDecimal("ALTITUDE"));
|
||||
if (json.containsKey("LATITUDE")) plant.setLatitude(json.getBigDecimal("LATITUDE"));
|
||||
if (json.containsKey("LONGITUDE")) plant.setLongitude(json.getBigDecimal("LONGITUDE"));
|
||||
|
||||
// 日期字段 字符串转Date
|
||||
if (json.containsKey("EXPIRY_DATE")) {
|
||||
String dateStr = json.getString("EXPIRY_DATE");
|
||||
if (StrUtil.isNotBlank(dateStr)) {
|
||||
plant.setExpiryDate(DateUtil.parseDate(dateStr));
|
||||
}
|
||||
}
|
||||
if (json.containsKey("OPERATE_DATE")) {
|
||||
String dateStr = json.getString("OPERATE_DATE");
|
||||
if (StrUtil.isNotBlank(dateStr)) {
|
||||
plant.setOperateDate(DateUtil.parseDate(dateStr));
|
||||
}
|
||||
}
|
||||
|
||||
entityList.add(plant);
|
||||
}
|
||||
// 根据主键ID存在更新,不存在新增
|
||||
return saveOrUpdateBatch(entityList, batchSize);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package com.njcn.relational.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.njcn.relational.mapper.SGConSubstationBMapper;
|
||||
import com.njcn.relational.pojo.po.SGConSubstationB;
|
||||
import com.njcn.relational.service.SGConSubstationBService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class SGConSubstationBServiceImpl extends ServiceImpl<SGConSubstationBMapper, SGConSubstationB>
|
||||
implements SGConSubstationBService {
|
||||
|
||||
private static final int BATCH_SIZE = 500;
|
||||
|
||||
@Override
|
||||
public boolean batchSyncFromDcloud(JSONArray dataArr) throws Exception{
|
||||
if (CollUtil.isEmpty(dataArr)) {
|
||||
return true;
|
||||
}
|
||||
List<SGConSubstationB> entityList = new ArrayList<>(dataArr.size());
|
||||
for (Object obj : dataArr) {
|
||||
JSONObject json = (JSONObject) obj;
|
||||
SGConSubstationB sub = new SGConSubstationB();
|
||||
|
||||
// 主键ID必传
|
||||
if (json.containsKey("ID")) {
|
||||
sub.setId(json.getString("ID"));
|
||||
}
|
||||
|
||||
// 字符串字段,存在才赋值
|
||||
if (json.containsKey("ASSETS_OWNERSHIP_COM_ID")) {
|
||||
sub.setAssetsOwnershipComId(json.getString("ASSETS_OWNERSHIP_COM_ID"));
|
||||
}
|
||||
if (json.containsKey("CHECK_CODE")) {
|
||||
sub.setCheckCode(json.getString("CHECK_CODE"));
|
||||
}
|
||||
if (json.containsKey("COMM_TAG")) {
|
||||
sub.setCommTag(json.getString("COMM_TAG"));
|
||||
}
|
||||
if (json.containsKey("DCC_ID")) {
|
||||
sub.setDccId(json.getString("DCC_ID"));
|
||||
}
|
||||
if (json.containsKey("MANAGE_DEPT_ID")) {
|
||||
sub.setManageDeptId(json.getString("MANAGE_DEPT_ID"));
|
||||
}
|
||||
if (json.containsKey("NAME")) {
|
||||
sub.setName(json.getString("NAME"));
|
||||
}
|
||||
if (json.containsKey("OWNER")) {
|
||||
sub.setOwner(json.getString("OWNER"));
|
||||
}
|
||||
if (json.containsKey("PG_ID")) {
|
||||
sub.setPgId(json.getString("PG_ID"));
|
||||
}
|
||||
if (json.containsKey("SCS_TAG")) {
|
||||
sub.setScsTag(json.getString("SCS_TAG"));
|
||||
}
|
||||
if (json.containsKey("STATE_CODE")) {
|
||||
sub.setStateCode(json.getString("STATE_CODE"));
|
||||
}
|
||||
if (json.containsKey("SYS_FLAG")) {
|
||||
sub.setSysFlag(json.getString("SYS_FLAG"));
|
||||
}
|
||||
if (json.containsKey("VIDEOSCENEID")) {
|
||||
sub.setVideosceneid(json.getString("VIDEOSCENEID"));
|
||||
}
|
||||
|
||||
// 整型数字字段
|
||||
if (json.containsKey("ASSETS_OWNERSHIP")) {
|
||||
sub.setAssetsOwnership(json.getInteger("ASSETS_OWNERSHIP"));
|
||||
}
|
||||
if (json.containsKey("DC_VOLTAGE_TYPE")) {
|
||||
sub.setDcVoltageType(json.getInteger("DC_VOLTAGE_TYPE"));
|
||||
}
|
||||
if (json.containsKey("OPERATE_STATE")) {
|
||||
sub.setOperateState(json.getInteger("OPERATE_STATE"));
|
||||
}
|
||||
if (json.containsKey("REGION")) {
|
||||
sub.setRegion(json.getInteger("REGION"));
|
||||
}
|
||||
if (json.containsKey("TOP_AC_VOLTAGE_TYPE")) {
|
||||
sub.setTopAcVoltageType(json.getInteger("TOP_AC_VOLTAGE_TYPE"));
|
||||
}
|
||||
if (json.containsKey("TYPE")) {
|
||||
sub.setType(json.getInteger("TYPE"));
|
||||
}
|
||||
|
||||
// 高精度小数
|
||||
if (json.containsKey("ALTITUDE")) {
|
||||
sub.setAltitude(json.getBigDecimal("ALTITUDE"));
|
||||
}
|
||||
if (json.containsKey("LATITUDE")) {
|
||||
sub.setLatitude(json.getBigDecimal("LATITUDE"));
|
||||
}
|
||||
if (json.containsKey("LONGITUDE")) {
|
||||
sub.setLongitude(json.getBigDecimal("LONGITUDE"));
|
||||
}
|
||||
|
||||
// 日期字段 字符串转Date
|
||||
if (json.containsKey("EXPIRY_DATE")) {
|
||||
String dateStr = json.getString("EXPIRY_DATE");
|
||||
if (StrUtil.isNotBlank(dateStr)) {
|
||||
sub.setExpiryDate(DateUtil.parseDate(dateStr));
|
||||
}
|
||||
}
|
||||
if (json.containsKey("OPERATE_DATE")) {
|
||||
String dateStr = json.getString("OPERATE_DATE");
|
||||
if (StrUtil.isNotBlank(dateStr)) {
|
||||
sub.setOperateDate(DateUtil.parseDate(dateStr));
|
||||
}
|
||||
}
|
||||
|
||||
entityList.add(sub);
|
||||
}
|
||||
// 根据主键ID判断新增/更新
|
||||
return saveOrUpdateBatch(entityList, BATCH_SIZE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package com.njcn.relational.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.njcn.relational.mapper.SGConSubstationCMapper;
|
||||
import com.njcn.relational.pojo.po.SGConSubstationBind;
|
||||
import com.njcn.relational.service.SGConSubstationCService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class SGConSubstationCServiceImpl extends ServiceImpl<SGConSubstationCMapper, SGConSubstationBind>
|
||||
implements SGConSubstationCService {
|
||||
|
||||
/**
|
||||
* 根据联合主键(DCC_ID + DCLOUD_ID)查询单条映射
|
||||
*/
|
||||
public SGConSubstationBind getByUnionKey(String dccId, String dcloudId) {
|
||||
LambdaQueryWrapper<SGConSubstationBind> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(SGConSubstationBind::getDccId, dccId)
|
||||
.eq(SGConSubstationBind::getDcloudId, dcloudId)
|
||||
.eq(SGConSubstationBind::getIsDelete, 0);
|
||||
return getOne(wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 逻辑删除:根据联合主键软删
|
||||
*/
|
||||
public boolean logicRemoveByUnionKey(String dccId, String dcloudId) {
|
||||
LambdaUpdateWrapper<SGConSubstationBind> updateWrapper = new LambdaUpdateWrapper<>();
|
||||
updateWrapper.set(SGConSubstationBind::getIsDelete, 1)
|
||||
.eq(SGConSubstationBind::getDccId, dccId)
|
||||
.eq(SGConSubstationBind::getDcloudId, dcloudId);
|
||||
return update(updateWrapper);
|
||||
}
|
||||
|
||||
private static final int BATCH_SIZE = 500;
|
||||
|
||||
@Override
|
||||
public boolean batchSyncFromDcloud(JSONArray dataArr) {
|
||||
if (CollUtil.isEmpty(dataArr)) {
|
||||
return true;
|
||||
}
|
||||
List<SGConSubstationBind> entityList = new ArrayList<>(dataArr.size());
|
||||
for (Object obj : dataArr) {
|
||||
JSONObject json = (JSONObject) obj;
|
||||
SGConSubstationBind bind = new SGConSubstationBind();
|
||||
|
||||
// 联合主键两字段必存在
|
||||
if (json.containsKey("DCC_ID")) {
|
||||
bind.setDccId(json.getString("DCC_ID"));
|
||||
}
|
||||
if (json.containsKey("DCLOUD_ID")) {
|
||||
bind.setDcloudId(json.getString("DCLOUD_ID"));
|
||||
}
|
||||
|
||||
// 字符串字段,存在key才赋值
|
||||
if (json.containsKey("D5000_ID")) bind.setD5000Id(json.getString("D5000_ID"));
|
||||
if (json.containsKey("D5000_NAME")) bind.setD5000Name(json.getString("D5000_NAME"));
|
||||
if (json.containsKey("DCLOUD_NAME")) bind.setDcloudName(json.getString("DCLOUD_NAME"));
|
||||
if (json.containsKey("DCLOUD_VOLTAGELEVEL")) bind.setDcloudVoltagelevel(json.getString("DCLOUD_VOLTAGELEVEL"));
|
||||
if (json.containsKey("OMS_ID")) bind.setOmsId(json.getString("OMS_ID"));
|
||||
if (json.containsKey("OMS_NAME")) bind.setOmsName(json.getString("OMS_NAME"));
|
||||
if (json.containsKey("OWNER")) bind.setOwner(json.getString("OWNER"));
|
||||
if (json.containsKey("PMS_ID")) bind.setPmsId(json.getString("PMS_ID"));
|
||||
if (json.containsKey("PSDB_DEV_TYPE")) bind.setPsdbDevType(json.getString("PSDB_DEV_TYPE"));
|
||||
if (json.containsKey("PSDB_ID")) bind.setPsdbId(json.getString("PSDB_ID"));
|
||||
if (json.containsKey("PSDB_NAME")) bind.setPsdbName(json.getString("PSDB_NAME"));
|
||||
if (json.containsKey("SETTING_ID")) bind.setSettingId(json.getString("SETTING_ID"));
|
||||
if (json.containsKey("SETTING_NAME")) bind.setSettingName(json.getString("SETTING_NAME"));
|
||||
if (json.containsKey("STATUS")) bind.setStatus(json.getString("STATUS"));
|
||||
|
||||
// 数字字段
|
||||
if (json.containsKey("IS_DELETE")) bind.setIsDelete(json.getInteger("IS_DELETE"));
|
||||
|
||||
entityList.add(bind);
|
||||
}
|
||||
// 根据主键ID判断新增/更新
|
||||
return saveOrUpdateBatch(entityList, BATCH_SIZE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.njcn.relational.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.njcn.relational.mapper.SyncTableConfigMapper;
|
||||
import com.njcn.relational.pojo.po.SyncTableConfig;
|
||||
import com.njcn.relational.service.SyncTableConfigService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* @Author: cdf
|
||||
* @CreateTime: 2026-06-28
|
||||
* @Description: 二区达梦数据导出服务实现类
|
||||
*/
|
||||
@Service
|
||||
public class SyncTableConfigServiceImpl extends ServiceImpl<SyncTableConfigMapper, SyncTableConfig> implements SyncTableConfigService{
|
||||
}
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
package com.njcn.relational.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.njcn.relational.mapper.SyncTableLogsMapper;
|
||||
import com.njcn.relational.pojo.po.SyncTableLogs;
|
||||
import com.njcn.relational.service.SyncTableLogsService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class SyncTableLogsServiceImpl extends ServiceImpl<SyncTableLogsMapper, SyncTableLogs>
|
||||
implements SyncTableLogsService {
|
||||
|
||||
@Override
|
||||
public IPage<SyncTableLogs> pageQuery(Integer pageNum, Integer pageSize, LocalDate timeId, Integer result) {
|
||||
Page<SyncTableLogs> page = new Page<>(pageNum, pageSize);
|
||||
LambdaQueryWrapper<SyncTableLogs> wrapper = new LambdaQueryWrapper<>();
|
||||
|
||||
if (timeId != null) {
|
||||
wrapper.eq(SyncTableLogs::getTimeId, timeId);
|
||||
}
|
||||
if (result != null) {
|
||||
wrapper.eq(SyncTableLogs::getResult, result);
|
||||
}
|
||||
|
||||
// 按创建时间倒序
|
||||
wrapper.orderByDesc(SyncTableLogs::getCreateTime);
|
||||
|
||||
return this.page(page, wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SyncTableLogs> listByTimeId(LocalDate timeId) {
|
||||
LambdaQueryWrapper<SyncTableLogs> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(SyncTableLogs::getTimeId, timeId)
|
||||
.orderByDesc(SyncTableLogs::getCreateTime);
|
||||
return this.list(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SyncTableLogs> listByTimeIdAndResult(LocalDate timeId, Integer result) {
|
||||
LambdaQueryWrapper<SyncTableLogs> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(SyncTableLogs::getTimeId, timeId)
|
||||
.eq(SyncTableLogs::getResult, result)
|
||||
.orderByDesc(SyncTableLogs::getCreateTime);
|
||||
return this.list(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean existsByTimeId(LocalDate timeId) {
|
||||
LambdaQueryWrapper<SyncTableLogs> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(SyncTableLogs::getTimeId, timeId);
|
||||
return this.count(wrapper) > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean batchInsert(List<SyncTableLogs> logs) {
|
||||
if (logs == null || logs.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
// 设置创建时间
|
||||
logs.forEach(log -> {
|
||||
if (log.getCreateTime() == null) {
|
||||
log.setCreateTime(LocalDateTime.now());
|
||||
}
|
||||
});
|
||||
return this.saveBatch(logs);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean deleteByTimeId(LocalDate timeId) {
|
||||
LambdaQueryWrapper<SyncTableLogs> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(SyncTableLogs::getTimeId, timeId);
|
||||
return this.remove(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean deleteByTimeIdAndResult(LocalDate timeId, Integer result) {
|
||||
LambdaQueryWrapper<SyncTableLogs> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(SyncTableLogs::getTimeId, timeId)
|
||||
.eq(SyncTableLogs::getResult, result);
|
||||
return this.remove(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int countByTimeIdAndResult(LocalDate timeId, Integer result) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean saveOrUpdateLog(SyncTableLogs log) {
|
||||
if (log == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 先查询是否存在
|
||||
LambdaQueryWrapper<SyncTableLogs> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(SyncTableLogs::getTimeId, log.getTimeId())
|
||||
.eq(SyncTableLogs::getResult, log.getResult());
|
||||
|
||||
SyncTableLogs exist = this.getOne(wrapper);
|
||||
|
||||
if (exist == null) {
|
||||
// 不存在则新增
|
||||
if (log.getCreateTime() == null) {
|
||||
log.setCreateTime(LocalDateTime.now());
|
||||
}
|
||||
return this.save(log);
|
||||
} else {
|
||||
// 存在则更新(保留原创建时间)
|
||||
log.setCreateTime(exist.getCreateTime());
|
||||
return this.update(log, wrapper);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.njcn.relational.utils;
|
||||
|
||||
/**
|
||||
* @Author: cdf
|
||||
* @CreateTime: 2026-06-26
|
||||
* @Description:
|
||||
*/
|
||||
public class LnBusUtils {
|
||||
}
|
||||
@@ -3,8 +3,8 @@ spring:
|
||||
datasource:
|
||||
druid:
|
||||
driver-class-name: dm.jdbc.driver.DmDriver
|
||||
url: jdbc:dm://127.0.0.1:5236/PQSADMIN?useUnicode=true&characterEncoding=utf-8
|
||||
username: PQSADMIN
|
||||
url: jdbc:dm://127.0.0.1:5236/PQSADMIN_LN?useUnicode=true&characterEncoding=utf-8
|
||||
username: PQSADMIN_LN
|
||||
password: Pqsadmin123
|
||||
#初始化建立物理连接的个数、最小、最大连接数
|
||||
initial-size: 5
|
||||
@@ -29,7 +29,7 @@ sync:
|
||||
password: dnzl@#001
|
||||
import:
|
||||
localPath: D:/data/import/
|
||||
remotePath: /home/export/
|
||||
remotePath: D:/data/export/
|
||||
export:
|
||||
localPath: D:/data/export/
|
||||
remotePath: /home/export/
|
||||
|
||||
@@ -3,7 +3,7 @@ spring:
|
||||
datasource:
|
||||
druid:
|
||||
driver-class-name: dm.jdbc.driver.DmDriver
|
||||
url: jdbc:dm://192.168.1.21:5236/PQSADMIN?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=CTT
|
||||
url: jdbc:dm://127.0.0.1:5236/PQSADMIN?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=CTT
|
||||
username: PQSADMIN
|
||||
password: Pqsadmin123
|
||||
#初始化建立物理连接的个数、最小、最大连接数
|
||||
@@ -29,7 +29,7 @@ sync:
|
||||
password: dnzl@#001
|
||||
export:
|
||||
localPath: D:/data/export/
|
||||
remotePath: /home/export/
|
||||
remotePath: D:/home/export/
|
||||
import:
|
||||
localPath: D:/data/export/
|
||||
remotePath: /home/export/
|
||||
|
||||
@@ -9,7 +9,7 @@ spring:
|
||||
application:
|
||||
name: dmTransport
|
||||
profiles:
|
||||
#active: query_up
|
||||
#active: 二区:query_up 三区:insert_up
|
||||
active: insert_up
|
||||
security:
|
||||
user:
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
<!-- 同步表配置对话框 -->
|
||||
<el-dialog
|
||||
:title="dialogTitle"
|
||||
:visible.sync="dialogVisible"
|
||||
width="600px"
|
||||
@close="handleDialogClose"
|
||||
>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="120px"
|
||||
size="small"
|
||||
>
|
||||
<el-form-item label="表名" prop="tableName">
|
||||
<el-input v-model="formData.tableName" placeholder="请输入表名" :disabled="isEdit"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="同步模式" prop="syncMode">
|
||||
<el-select v-model="formData.syncMode" placeholder="请选择同步模式" style="width: 100%">
|
||||
<el-option label="全量同步" value="FULL"></el-option>
|
||||
<el-option label="增量同步" value="INCREMENTAL"></el-option>
|
||||
<el-option label="实时同步" value="REAL_TIME"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="时间字段" prop="timeColumn">
|
||||
<el-input v-model="formData.timeColumn" placeholder="请输入时间字段名(如:create_time)"></el-input>
|
||||
<span class="form-tip">用于增量同步的时间查询字段</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="BLOB字段" prop="blobColumns">
|
||||
<el-input v-model="formData.blobColumns" placeholder="多个字段用逗号分隔,如:content,data"></el-input>
|
||||
<span class="form-tip">多个字段请用英文逗号分隔</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="排序" prop="sortOrder">
|
||||
<el-input-number v-model="formData.sortOrder" :min="0" :max="9999" style="width: 100%"></el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item label="启用状态" prop="enabled">
|
||||
<el-radio-group v-model="formData.enabled">
|
||||
<el-radio :label="1">启用</el-radio>
|
||||
<el-radio :label="0">禁用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="请输入备注"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button size="small" @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" size="small" :loading="submitLoading" @click="handleSubmit">确定</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
@@ -0,0 +1,10 @@
|
||||
<!-- 头部组件 -->
|
||||
<div class="header">
|
||||
<h1>
|
||||
🗄️ 数据同步管理系统
|
||||
<span :class="['environment-badge', isSourceServer ? 'badge-source' : 'badge-target']">
|
||||
{{ serverTypeName }}
|
||||
</span>
|
||||
</h1>
|
||||
<p> relational_migration - 数据导入导出管理平台 | 同步表配置管理</p>
|
||||
</div>
|
||||
@@ -0,0 +1,15 @@
|
||||
<!-- 日志组件 -->
|
||||
<div class="card">
|
||||
<div class="card-title">📋 操作日志</div>
|
||||
<div class="log-area">
|
||||
<div v-for="(log, index) in logs" :key="index" :class="['log-item', log.type]">
|
||||
[{{ log.time }}] {{ log.message }}
|
||||
</div>
|
||||
<div v-if="logs.length === 0" class="log-info">
|
||||
暂无操作日志
|
||||
</div>
|
||||
</div>
|
||||
<el-button size="small" @click="clearLogs" style="margin-top: 10px;">
|
||||
清空日志
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -0,0 +1,19 @@
|
||||
<!-- 统计信息组件 -->
|
||||
<div class="stats">
|
||||
<div class="stat-item">
|
||||
<div class="stat-value">{{ fileCount }}</div>
|
||||
<div class="stat-label">远程文件数量</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-value">{{ operationCount }}</div>
|
||||
<div class="stat-label">操作次数</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-value">{{ tableCount }}</div>
|
||||
<div class="stat-label">同步表配置数</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-value">{{ lastOperationTime || '--' }}</div>
|
||||
<div class="stat-label">最后操作时间</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,189 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* 头部样式 */
|
||||
.header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 30px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 30px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 28px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.header p {
|
||||
font-size: 14px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.environment-badge {
|
||||
display: inline-block;
|
||||
padding: 5px 15px;
|
||||
border-radius: 20px;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.badge-source {
|
||||
background: #e6a23c;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.badge-target {
|
||||
background: #409eff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* 卡片样式 */
|
||||
.card {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 25px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid #e4e7ed;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
/* 统计信息 */
|
||||
.stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 15px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* 按钮组 */
|
||||
.button-group {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* 日期输入 */
|
||||
.date-input {
|
||||
width: 200px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
/* 文件列表 */
|
||||
.file-list {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* 日志区域 */
|
||||
.log-area {
|
||||
background: #f5f7fa;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
padding: 15px;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 12px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.log-item {
|
||||
margin-bottom: 5px;
|
||||
padding: 5px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.log-success {
|
||||
background: #f0f9ff;
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.log-error {
|
||||
background: #fef0f0;
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.log-info {
|
||||
background: #f4f4f5;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
/* 表单提示 */
|
||||
.form-tip {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
/* BLOB标签 */
|
||||
.blob-tag {
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
/* 分页 */
|
||||
.pagination-container {
|
||||
margin-top: 20px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* 搜索卡片 */
|
||||
.search-card {
|
||||
background: #fafafa;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.batch-delete-btn {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
/* Tab内容 */
|
||||
.tab-content {
|
||||
padding-top: 20px;
|
||||
}
|
||||
@@ -19,7 +19,6 @@
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
// API接口定义
|
||||
var API = {
|
||||
// ========== 系统配置 ==========
|
||||
getConfig: function() {
|
||||
return axios.get('source/getConfig');
|
||||
},
|
||||
|
||||
// ========== 数据同步相关 ==========
|
||||
exportData: function(date) {
|
||||
var params = date ? { date: date } : {};
|
||||
return axios.get('source/export', { params: params });
|
||||
},
|
||||
|
||||
importData: function(date) {
|
||||
var params = date ? { date: date } : {};
|
||||
return axios.get('source/import', { params: params });
|
||||
},
|
||||
|
||||
testSync: function() {
|
||||
return axios.get('source/test');
|
||||
},
|
||||
|
||||
testSyncR: function() {
|
||||
return axios.get('source/testR');
|
||||
},
|
||||
|
||||
getRemoteFiles: function() {
|
||||
return axios.get('source/getRemoteAll');
|
||||
},
|
||||
|
||||
cleanTwo: function(days) {
|
||||
return axios.get('source/removeTwo', { params: { a: days } });
|
||||
},
|
||||
|
||||
cleanThree: function(days) {
|
||||
return axios.get('source/removeThree', { params: { a: days } });
|
||||
},
|
||||
|
||||
// ========== 同步表配置相关 ==========
|
||||
getTablePage: function(params) {
|
||||
return axios.get('/api/sync-table-config/page', { params: params });
|
||||
},
|
||||
|
||||
getTableList: function() {
|
||||
return axios.get('/api/sync-table-config/list');
|
||||
},
|
||||
|
||||
getTableById: function(tableName) {
|
||||
return axios.get('/api/sync-table-config/' + tableName);
|
||||
},
|
||||
|
||||
addTable: function(data) {
|
||||
return axios.post('/api/sync-table-config', data);
|
||||
},
|
||||
|
||||
updateTable: function(data) {
|
||||
return axios.put('/api/sync-table-config', data);
|
||||
},
|
||||
|
||||
deleteTable: function(tableName) {
|
||||
return axios.delete('/api/sync-table-config/' + tableName);
|
||||
},
|
||||
|
||||
deleteTables: function(tableNames) {
|
||||
return axios.delete('/api/sync-table-config/batch', { data: tableNames });
|
||||
},
|
||||
|
||||
updateTableStatus: function(tableName, enabled) {
|
||||
return axios.put('/api/sync-table-config/status/' + tableName, null, {
|
||||
params: { enabled: enabled }
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
// 全局配置
|
||||
var APP_CONFIG = {
|
||||
// API基础路径
|
||||
baseURL: '',
|
||||
// 环境配置
|
||||
serverType: 'source',
|
||||
serverTypeName: '源服务器',
|
||||
// 默认保留天数
|
||||
defaultCleanDays: 7,
|
||||
// 日志最大条数
|
||||
maxLogs: 100,
|
||||
// 分页配置
|
||||
pageSizeOptions: [10, 20, 50, 100],
|
||||
defaultPageSize: 10
|
||||
};
|
||||
|
||||
// 同步模式映射
|
||||
var SYNC_MODE = {
|
||||
FULL: { label: '全量同步', type: 'info' },
|
||||
INCREMENTAL: { label: '增量同步', type: 'warning' },
|
||||
REAL_TIME: { label: '实时同步', type: 'danger' }
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
// router.js
|
||||
const routes = [
|
||||
{ path: '/', component: MainPage },
|
||||
{ path: '/config', component: ConfigPage }
|
||||
];
|
||||
|
||||
const router = new VueRouter({
|
||||
routes
|
||||
});
|
||||
|
||||
// app.js
|
||||
new Vue({
|
||||
router,
|
||||
data: {
|
||||
// 全局共享数据
|
||||
operationCount: 0,
|
||||
lastOperationTime: '',
|
||||
logs: []
|
||||
},
|
||||
methods: {
|
||||
addLog(message, type) {
|
||||
// 全局日志方法
|
||||
}
|
||||
}
|
||||
}).$mount('#app');
|
||||
@@ -0,0 +1,65 @@
|
||||
// 工具函数
|
||||
var Utils = {
|
||||
// 获取昨天的日期
|
||||
getYesterdayDate: function() {
|
||||
var date = new Date();
|
||||
date.setDate(date.getDate() - 1);
|
||||
var y = date.getFullYear();
|
||||
var m = (date.getMonth() + 1).toString().padStart(2, '0');
|
||||
var d = date.getDate().toString().padStart(2, '0');
|
||||
return y + m + d;
|
||||
},
|
||||
|
||||
// 格式化时间
|
||||
formatTime: function(date) {
|
||||
if (!date) return '--';
|
||||
return date.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
});
|
||||
},
|
||||
|
||||
// 解析BLOB字段
|
||||
parseBlobColumns: function(columns) {
|
||||
if (!columns) return [];
|
||||
return columns.split(',').map(function(s) {
|
||||
return s.trim();
|
||||
}).filter(function(s) {
|
||||
return s !== '';
|
||||
});
|
||||
},
|
||||
|
||||
// 获取同步模式标签
|
||||
getSyncModeLabel: function(mode) {
|
||||
var map = {
|
||||
'FULL': '全量同步',
|
||||
'INCREMENTAL': '增量同步',
|
||||
'REAL_TIME': '实时同步'
|
||||
};
|
||||
return map[mode] || mode;
|
||||
},
|
||||
|
||||
// 获取同步模式类型
|
||||
getSyncModeType: function(mode) {
|
||||
var map = {
|
||||
'FULL': 'info',
|
||||
'INCREMENTAL': 'warning',
|
||||
'REAL_TIME': 'danger'
|
||||
};
|
||||
return map[mode] || '';
|
||||
},
|
||||
|
||||
// 深拷贝对象
|
||||
deepClone: function(obj) {
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
},
|
||||
|
||||
// 生成唯一ID
|
||||
generateId: function() {
|
||||
return Date.now() + '_' + Math.random().toString(36).substr(2, 9);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,621 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>传输配置管理 - 同步表配置</title>
|
||||
<!-- Element UI 样式 -->
|
||||
<link rel="stylesheet" href="js/element-ui.css">
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
background-color: #f0f2f5;
|
||||
padding: 20px;
|
||||
}
|
||||
.container {
|
||||
margin: 0 auto;
|
||||
}
|
||||
/* 头部 */
|
||||
.header {
|
||||
background: linear-gradient(135deg, #409eff 0%, #36a3f7 100%);
|
||||
color: white;
|
||||
padding: 20px 30px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 24px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.header h1 {
|
||||
font-size: 24px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.header .sub {
|
||||
font-size: 14px;
|
||||
opacity: 0.85;
|
||||
margin-top: 4px;
|
||||
}
|
||||
.header-actions .el-button {
|
||||
border-color: rgba(255,255,255,0.4);
|
||||
color: white;
|
||||
background: rgba(255,255,255,0.1);
|
||||
}
|
||||
.header-actions .el-button:hover {
|
||||
background: rgba(255,255,255,0.25);
|
||||
border-color: rgba(255,255,255,0.6);
|
||||
}
|
||||
/* 卡片 */
|
||||
.card {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 20px 24px;
|
||||
box-shadow: 0 1px 4px rgba(0,0,0,0.08);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.card-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
}
|
||||
.search-bar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.search-bar .el-form-item {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.table-wrap {
|
||||
margin-top: 8px;
|
||||
}
|
||||
.blob-tag {
|
||||
background: #ecf5ff;
|
||||
color: #409eff;
|
||||
border-radius: 4px;
|
||||
padding: 0 8px;
|
||||
font-size: 12px;
|
||||
line-height: 22px;
|
||||
display: inline-block;
|
||||
margin: 2px 4px 2px 0;
|
||||
}
|
||||
.status-tag {
|
||||
font-weight: 500;
|
||||
}
|
||||
.dialog-footer {
|
||||
text-align: right;
|
||||
padding-top: 12px;
|
||||
}
|
||||
.empty-tip {
|
||||
color: #909399;
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
padding: 30px 0;
|
||||
}
|
||||
.back-link {
|
||||
cursor: pointer;
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
.back-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.el-table th {
|
||||
background-color: #f5f7fa !important;
|
||||
}
|
||||
.el-dialog .el-form-item {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
/* 分页样式优化 */
|
||||
.pagination-wrap {
|
||||
margin-top: 16px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<div class="container">
|
||||
<!-- 头部 -->
|
||||
<div class="header">
|
||||
<div>
|
||||
<h1>⚙️ 传输配置管理 <span style="font-size: 14px; margin-left: 12px; font-weight: 400;">SYNC_TABLE_CONFIG</span></h1>
|
||||
<div class="sub">管理同步表配置 · 支持BLOB字段、时间列、启用状态等</div>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<el-button type="text" icon="el-icon-back" @click="goBack" style="color: white; font-size: 14px;">
|
||||
返回主页面
|
||||
</el-button>
|
||||
<el-button type="text" icon="el-icon-refresh" @click="fetchPage" style="color: white; font-size: 14px;">
|
||||
刷新
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 搜索 & 操作栏 -->
|
||||
<div class="card">
|
||||
<div class="card-title">🔍 查询 & 操作</div>
|
||||
<div class="search-bar">
|
||||
<el-form :inline="true" size="small" @submit.native.prevent>
|
||||
<el-form-item label="表名">
|
||||
<el-input v-model="search.tableName" placeholder="模糊搜索" clearable style="width:180px;"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="同步模式">
|
||||
<el-select v-model="search.syncMode" placeholder="全部" clearable style="width:140px;">
|
||||
<el-option label="全量" value="FULL"></el-option>
|
||||
<el-option label="增量" value="INCREMENTAL"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="search.enabled" placeholder="全部" clearable style="width:120px;">
|
||||
<el-option label="启用" :value="1"></el-option>
|
||||
<el-option label="停用" :value="0"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" @click="fetchPage">查询</el-button>
|
||||
<el-button icon="el-icon-refresh" @click="resetSearch">重置</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item style="margin-left: auto;">
|
||||
<el-button type="success" icon="el-icon-plus" @click="openAddDialog">新增配置</el-button>
|
||||
<el-button type="danger" icon="el-icon-delete" plain :disabled="selectedRows.length===0" @click="batchDelete">批量删除</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 表格 -->
|
||||
<div class="card">
|
||||
<div class="card-title">📋 配置列表 <span style="font-size:13px; font-weight:400; color:#909399; margin-left:12px;">共 {{ total }} 条</span></div>
|
||||
<div class="table-wrap">
|
||||
<el-table
|
||||
:data="tableData"
|
||||
stripe
|
||||
border
|
||||
v-loading="loading"
|
||||
style="width:100%;"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="50" align="center"></el-table-column>
|
||||
<el-table-column prop="tableName" label="表名" width="200" fixed></el-table-column>
|
||||
<el-table-column prop="syncMode" label="同步模式" width="90" align="center">
|
||||
<template slot-scope="{ row }">
|
||||
<el-tag :type="row.syncMode === 'FULL' ? 'warning' : 'success'" size="small">
|
||||
{{ row.syncMode === 'FULL' ? '全量' : '增量' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="timeColumn" label="时间查询列" min-width="130" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="blobColumns" label="BLOB列" min-width="120">
|
||||
<template slot-scope="{ row }">
|
||||
<span v-if="row.blobColumns">
|
||||
<span v-for="col in row.blobColumns.split(',')" :key="col" class="blob-tag">{{ col.trim() }}</span>
|
||||
</span>
|
||||
<span v-else style="color:#c0c4cc;">-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="dataTimeType" label="增量数据时间类型" min-width="110" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="enabled" label="状态" width="90" align="center">
|
||||
<template slot-scope="{ row }">
|
||||
<el-switch
|
||||
v-model="row.enabled"
|
||||
:active-value="1"
|
||||
:inactive-value="0"
|
||||
active-color="#13ce66"
|
||||
inactive-color="#ff4949"
|
||||
@change="handleStatusChange(row)"
|
||||
></el-switch>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="sortOrder" label="排序" width="70" align="center"></el-table-column>
|
||||
<el-table-column prop="remark" label="备注" min-width="120" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" width="140" sortable>
|
||||
<template slot-scope="{ row }">{{ row.createTime ? formatDate(row.createTime) : '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="150" fixed="right">
|
||||
<template slot-scope="{ row }">
|
||||
<el-button size="mini" type="primary" plain @click="openEditDialog(row)">编辑</el-button>
|
||||
<el-button size="mini" type="danger" plain @click="deleteSingle(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-wrap">
|
||||
<el-pagination
|
||||
background
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
:current-page="pageNum"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
:page-size="pageSize"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total">
|
||||
</el-pagination>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========== 新增/编辑对话框 ========== -->
|
||||
<el-dialog
|
||||
:title="dialogTitle"
|
||||
:visible.sync="dialogVisible"
|
||||
width="620px"
|
||||
:close-on-click-modal="false"
|
||||
@closed="resetForm"
|
||||
>
|
||||
<el-form :model="form" :rules="rules" ref="configForm" label-width="120px" size="small">
|
||||
<el-form-item label="表名" prop="tableName">
|
||||
<el-input v-model="form.tableName" placeholder="请输入表名 (主键)" :disabled="isEdit"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="同步模式" prop="syncMode">
|
||||
<el-select v-model="form.syncMode" placeholder="请选择" style="width:100%;">
|
||||
<el-option label="全量" value="FULL"></el-option>
|
||||
<el-option label="增量" value="INCREMENTAL"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="启用状态" prop="enabled">
|
||||
<el-switch v-model="form.enabled" :active-value="1" :inactive-value="0" active-color="#13ce66" inactive-color="#ff4949"></el-switch>
|
||||
<span style="margin-left:10px; color:#909399; font-size:13px;">{{ form.enabled === 1 ? '启用' : '停用' }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="排序值" prop="sortOrder">
|
||||
<el-input-number v-model="form.sortOrder" :min="0" :max="9999" style="width:100%;"></el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item label="时间查询列" prop="timeColumn">
|
||||
<el-input v-model="form.timeColumn" placeholder="例如: create_time, update_time"></el-input>
|
||||
<span style="color:#909399; font-size:12px;">用于增量同步的时间字段</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="BLOB列" prop="blobColumns">
|
||||
<el-input v-model="form.blobColumns" placeholder="多个逗号分隔,如: content, data"></el-input>
|
||||
<span style="color:#909399; font-size:12px;">需要特殊处理的BLOB字段</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="增量数据时间类型" prop="dataTimeType">
|
||||
<el-select v-model="form.dataTimeType" placeholder="请选择" style="width:100%;">
|
||||
<el-option label="日" value="D"></el-option>
|
||||
<el-option label="月" value="M"></el-option>
|
||||
<el-option label="季" value="Q"></el-option>
|
||||
<el-option label="年" value="Y"></el-option>
|
||||
</el-select>
|
||||
<span style="color:#909399; font-size:12px;">该字段用于配置日表、月表、年表、季表</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" rows="2" placeholder="可选备注"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm" :loading="submitLoading">确定</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</div>
|
||||
|
||||
<!-- 引入 Vue & Element UI & Axios -->
|
||||
<script src="js/vue.min.js"></script>
|
||||
<script src="js/element-ui-index.js"></script>
|
||||
<script src="js/axios.min.js"></script>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: '#app',
|
||||
data() {
|
||||
return {
|
||||
// 分页参数
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
// 搜索条件
|
||||
search: {
|
||||
tableName: '',
|
||||
syncMode: '',
|
||||
enabled: null
|
||||
},
|
||||
// 表格数据
|
||||
tableData: [],
|
||||
loading: false,
|
||||
selectedRows: [],
|
||||
// 对话框
|
||||
dialogVisible: false,
|
||||
dialogTitle: '新增配置',
|
||||
isEdit: false,
|
||||
submitLoading: false,
|
||||
form: {
|
||||
tableName: '',
|
||||
syncMode: 'FULL',
|
||||
enabled: 1,
|
||||
sortOrder: 0,
|
||||
timeColumn: '',
|
||||
blobColumns: '',
|
||||
dataTimeType: '',
|
||||
remark: ''
|
||||
},
|
||||
// 表单校验
|
||||
rules: {
|
||||
tableName: [
|
||||
{ required: true, message: '请输入表名', trigger: 'blur' },
|
||||
{ pattern: /^[a-zA-Z_][a-zA-Z0-9_]*$/, message: '表名格式不正确', trigger: 'blur' }
|
||||
],
|
||||
syncMode: [
|
||||
{ required: true, message: '请选择同步模式', trigger: 'change' }
|
||||
],
|
||||
sortOrder: [
|
||||
{ required: true, message: '请输入排序值', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.fetchPage();
|
||||
},
|
||||
methods: {
|
||||
// ---------- 数据请求 ----------
|
||||
fetchPage() {
|
||||
this.loading = true;
|
||||
const params = {
|
||||
pageNum: this.pageNum,
|
||||
pageSize: this.pageSize,
|
||||
tableName: this.search.tableName || undefined,
|
||||
syncMode: this.search.syncMode || undefined,
|
||||
enabled: this.search.enabled !== null && this.search.enabled !== '' ? this.search.enabled : undefined
|
||||
};
|
||||
axios.get('sync-table-config/page', { params })
|
||||
.then(res => {
|
||||
// 兼容不同的返回结构
|
||||
let responseData = res.data;
|
||||
|
||||
// 如果data是HttpResult包装的
|
||||
if (responseData.code !== undefined) {
|
||||
if (responseData.code === 200 || responseData.code === 0) {
|
||||
responseData = responseData.data;
|
||||
} else {
|
||||
this.$message.error(responseData.msg || '获取列表失败');
|
||||
this.tableData = [];
|
||||
this.total = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理分页数据 - 兼容多种结构
|
||||
if (responseData) {
|
||||
// 结构1: { records: [], total: 0 }
|
||||
if (responseData.records !== undefined) {
|
||||
this.tableData = responseData.records || [];
|
||||
this.total = responseData.total || 0;
|
||||
}
|
||||
// 结构2: { list: [], total: 0 }
|
||||
else if (responseData.list !== undefined) {
|
||||
this.tableData = responseData.list || [];
|
||||
this.total = responseData.total || 0;
|
||||
}
|
||||
// 结构3: 直接是数组,total从其他地方获取
|
||||
else if (Array.isArray(responseData)) {
|
||||
this.tableData = responseData;
|
||||
this.total = responseData.length;
|
||||
}
|
||||
// 结构4: 嵌套在 data 中
|
||||
else if (responseData.data && responseData.data.records) {
|
||||
this.tableData = responseData.data.records || [];
|
||||
this.total = responseData.data.total || 0;
|
||||
}
|
||||
else {
|
||||
this.tableData = [];
|
||||
this.total = 0;
|
||||
}
|
||||
} else {
|
||||
this.tableData = [];
|
||||
this.total = 0;
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
this.$message.error('请求失败: ' + (err.response?.data?.msg || err.message));
|
||||
this.tableData = [];
|
||||
this.total = 0;
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
|
||||
resetSearch() {
|
||||
this.search.tableName = '';
|
||||
this.search.syncMode = '';
|
||||
this.search.enabled = null;
|
||||
this.pageNum = 1;
|
||||
this.fetchPage();
|
||||
},
|
||||
|
||||
// ---------- 分页 ----------
|
||||
handleSizeChange(val) {
|
||||
this.pageSize = val;
|
||||
this.pageNum = 1;
|
||||
this.fetchPage();
|
||||
},
|
||||
handleCurrentChange(val) {
|
||||
this.pageNum = val;
|
||||
this.fetchPage();
|
||||
},
|
||||
|
||||
// ---------- 选中 ----------
|
||||
handleSelectionChange(rows) {
|
||||
this.selectedRows = rows;
|
||||
},
|
||||
|
||||
// ---------- 状态切换 ----------
|
||||
handleStatusChange(row) {
|
||||
axios.put(`sync-table-config/status/${row.tableName}?enabled=${row.enabled}`)
|
||||
.then(res => {
|
||||
let data = res.data;
|
||||
if (data.code === 200 || data.code === 0 || data.success) {
|
||||
this.$message.success(`状态已${row.enabled === 1 ? '启用' : '停用'}`);
|
||||
} else {
|
||||
// 回退
|
||||
row.enabled = row.enabled === 1 ? 0 : 1;
|
||||
this.$message.error(data.msg || '状态更新失败');
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
row.enabled = row.enabled === 1 ? 0 : 1;
|
||||
this.$message.error('状态更新失败');
|
||||
});
|
||||
},
|
||||
|
||||
// ---------- 新增 ----------
|
||||
openAddDialog() {
|
||||
this.dialogTitle = '新增配置';
|
||||
this.isEdit = false;
|
||||
this.form = {
|
||||
tableName: '',
|
||||
syncMode: 'FULL',
|
||||
enabled: 1,
|
||||
sortOrder: 0,
|
||||
timeColumn: '',
|
||||
blobColumns: '',
|
||||
remark: ''
|
||||
};
|
||||
this.dialogVisible = true;
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.configForm) this.$refs.configForm.clearValidate();
|
||||
});
|
||||
},
|
||||
|
||||
// ---------- 编辑 ----------
|
||||
openEditDialog(row) {
|
||||
this.dialogTitle = '编辑配置';
|
||||
this.isEdit = true;
|
||||
// 深拷贝
|
||||
this.form = {
|
||||
tableName: row.tableName,
|
||||
syncMode: row.syncMode || 'FULL',
|
||||
enabled: row.enabled,
|
||||
sortOrder: row.sortOrder || 0,
|
||||
timeColumn: row.timeColumn || '',
|
||||
blobColumns: row.blobColumns || '',
|
||||
remark: row.remark || ''
|
||||
};
|
||||
this.dialogVisible = true;
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.configForm) this.$refs.configForm.clearValidate();
|
||||
});
|
||||
},
|
||||
|
||||
// ---------- 提交表单 ----------
|
||||
submitForm() {
|
||||
this.$refs.configForm.validate((valid) => {
|
||||
if (!valid) return;
|
||||
this.submitLoading = true;
|
||||
const url = 'sync-table-config';
|
||||
const method = this.isEdit ? 'put' : 'post';
|
||||
axios[method](url, this.form)
|
||||
.then(res => {
|
||||
let data = res.data;
|
||||
if (data.code === 200 || data.code === 0 || data.success) {
|
||||
this.$message.success(this.isEdit ? '更新成功' : '新增成功');
|
||||
this.dialogVisible = false;
|
||||
this.fetchPage();
|
||||
} else {
|
||||
this.$message.error(data.msg || '操作失败');
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
this.$message.error('请求失败: ' + (err.response?.data?.msg || err.message));
|
||||
})
|
||||
.finally(() => {
|
||||
this.submitLoading = false;
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
resetForm() {
|
||||
// 关闭时重置
|
||||
this.form = {
|
||||
tableName: '',
|
||||
syncMode: 'FULL',
|
||||
enabled: 1,
|
||||
sortOrder: 0,
|
||||
timeColumn: '',
|
||||
blobColumns: '',
|
||||
remark: ''
|
||||
};
|
||||
this.isEdit = false;
|
||||
},
|
||||
|
||||
// ---------- 删除单个 ----------
|
||||
deleteSingle(row) {
|
||||
this.$confirm(`确定删除表 "${row.tableName}" 的配置吗?`, '提示', {
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
axios.delete(`sync-table-config/${row.tableName}`)
|
||||
.then(res => {
|
||||
let data = res.data;
|
||||
if (data.code === 200 || data.code === 0 || data.success) {
|
||||
this.$message.success('删除成功');
|
||||
this.fetchPage();
|
||||
} else {
|
||||
this.$message.error(data.msg || '删除失败');
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
this.$message.error('删除失败: ' + (err.response?.data?.msg || err.message));
|
||||
});
|
||||
}).catch(() => {});
|
||||
},
|
||||
|
||||
// ---------- 批量删除 ----------
|
||||
batchDelete() {
|
||||
if (this.selectedRows.length === 0) {
|
||||
this.$message.warning('请先选择要删除的配置');
|
||||
return;
|
||||
}
|
||||
const names = this.selectedRows.map(r => r.tableName);
|
||||
this.$confirm(`确定删除选中的 ${names.length} 个配置吗?`, '批量删除', {
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
axios.delete('sync-table-config/batch', { data: names })
|
||||
.then(res => {
|
||||
let data = res.data;
|
||||
if (data.code === 200 || data.code === 0 || data.success) {
|
||||
this.$message.success('批量删除成功');
|
||||
this.fetchPage();
|
||||
} else {
|
||||
this.$message.error(data.msg || '批量删除失败');
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
this.$message.error('批量删除失败: ' + (err.response?.data?.msg || err.message));
|
||||
});
|
||||
}).catch(() => {});
|
||||
},
|
||||
|
||||
// ---------- 返回主页面 ----------
|
||||
goBack() {
|
||||
window.location.href = 'index';
|
||||
},
|
||||
|
||||
// ---------- 工具函数 ----------
|
||||
formatDate(dateStr) {
|
||||
if (!dateStr) return '-';
|
||||
const d = new Date(dateStr);
|
||||
if (isNaN(d)) return dateStr;
|
||||
const pad = n => String(n).padStart(2, '0');
|
||||
return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,622 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>数据同步管理系统</title>
|
||||
<!-- 引入 Element UI 样式 -->
|
||||
<link rel="stylesheet" href="js/element-ui.css">
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
.container {
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 10px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.header-left h1 {
|
||||
font-size: 28px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.header-left p {
|
||||
font-size: 14px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-actions .el-button {
|
||||
border-color: rgba(255,255,255,0.4);
|
||||
color: white;
|
||||
background: rgba(255,255,255,0.1);
|
||||
}
|
||||
|
||||
.header-actions .el-button:hover {
|
||||
background: rgba(255,255,255,0.25);
|
||||
border-color: rgba(255,255,255,0.6);
|
||||
}
|
||||
|
||||
.card {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 25px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid #e4e7ed;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.button-item {
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.date-input {
|
||||
width: 200px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.file-list {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.log-area {
|
||||
background: #f5f7fa;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
padding: 15px;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 12px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.log-item {
|
||||
margin-bottom: 5px;
|
||||
padding: 5px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.log-success {
|
||||
background: #f0f9ff;
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.log-error {
|
||||
background: #fef0f0;
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.log-info {
|
||||
background: #f4f4f5;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 15px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.environment-badge {
|
||||
display: inline-block;
|
||||
padding: 5px 15px;
|
||||
border-radius: 20px;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.badge-source {
|
||||
background: #e6a23c;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.badge-target {
|
||||
background: #409eff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* 管理配置页面(独立跳转,此处仅为样式预留) */
|
||||
.config-page-link {
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
}
|
||||
.config-page-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<div class="container">
|
||||
<!-- 头部:增加右侧按钮组 -->
|
||||
<div class="header">
|
||||
<div class="header-left">
|
||||
<h1>
|
||||
🗄️ 数据同步管理系统
|
||||
<span style="font-size: large" :class="['environment-badge', isSourceServer ? 'badge-source' : 'badge-target']">
|
||||
{{ serverTypeName }}
|
||||
</span>
|
||||
</h1>
|
||||
<p> relational_migration - 数据导入导出管理平台</p>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<!-- 新增:传输配置按钮,点击跳转至管理配置页面 -->
|
||||
<el-button type="text" icon="el-icon-setting" @click="goToLogsPage" style="color: white; font-size: 16px;">
|
||||
传输日志
|
||||
</el-button>
|
||||
<el-button type="text" icon="el-icon-setting" @click="goToConfigPage" style="color: white; font-size: 16px;">
|
||||
传输配置
|
||||
</el-button>
|
||||
<el-button type="text" icon="el-icon-refresh" @click="refreshAll" style="color: white; font-size: 14px;">
|
||||
刷新
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 统计信息 -->
|
||||
<div class="stats">
|
||||
<div class="stat-item">
|
||||
<div class="stat-value">{{ fileCount }}</div>
|
||||
<div class="stat-label">远程文件数量</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-value">{{ operationCount }}</div>
|
||||
<div class="stat-label">操作次数</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-value">{{ lastOperationTime || '--' }}</div>
|
||||
<div class="stat-label">最后操作时间</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 环境配置卡片(可选显示) -->
|
||||
<div class="card" v-if="showEnvSwitch">
|
||||
<div class="card-title">⚙️ 环境切换(调试用)</div>
|
||||
<el-alert
|
||||
title="当前环境由配置文件控制,如需切换请修改 application.yml 中的 app.server-type 配置"
|
||||
type="warning"
|
||||
:closable="false"
|
||||
show-icon
|
||||
style="margin-bottom: 15px;">
|
||||
</el-alert>
|
||||
<el-form :inline="true">
|
||||
<el-form-item label="当前配置">
|
||||
<el-tag :type="isSourceServer ? 'warning' : 'primary'" size="medium">
|
||||
{{ serverTypeName }}
|
||||
</el-tag>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- 数据操作卡片 -->
|
||||
<div class="card">
|
||||
<div class="card-title">📊 数据操作</div>
|
||||
<el-form :inline="true" class="demo-form-inline">
|
||||
<el-form-item label="日期">
|
||||
<el-date-picker
|
||||
v-model="selectedDate"
|
||||
type="date"
|
||||
placeholder="选择日期"
|
||||
format="yyyyMMdd"
|
||||
value-format="yyyyMMdd"
|
||||
class="date-input">
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<div class="button-group">
|
||||
<el-button
|
||||
v-if="isSourceServer"
|
||||
type="primary"
|
||||
icon="el-icon-download"
|
||||
@click="handleExport"
|
||||
:loading="loading.export">
|
||||
从二区库导出数据并上传横向隔离设备
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="isTargetServer"
|
||||
type="success"
|
||||
icon="el-icon-upload2"
|
||||
@click="handleImport"
|
||||
:loading="loading.import">
|
||||
从横向隔离设备拉取数据并入三区库
|
||||
</el-button>
|
||||
<el-button v-if="isSourceServer" type="warning" icon="el-icon-refresh" @click="handleTest" :loading="loading.test">
|
||||
测试二区发送数据至横向隔离设备
|
||||
</el-button>
|
||||
<el-button v-if="isTargetServer" type="info" icon="el-icon-refresh-right" @click="handleTestR" :loading="loading.testR">
|
||||
测试三区从横向隔离设备消费数据
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- 文件管理卡片 -->
|
||||
<div class="card">
|
||||
<div class="card-title">📁 远程文件管理</div>
|
||||
<el-button type="primary" icon="el-icon-search" @click="getRemoteFiles" :loading="loading.getRemoteAll" style="margin-bottom: 15px;">
|
||||
获取横向隔离设备远程文件列表
|
||||
</el-button>
|
||||
|
||||
<div class="file-list" v-if="remoteFiles.length > 0">
|
||||
<el-table :data="remoteFiles" stripe style="width: 100%">
|
||||
<el-table-column type="index" label="序号" width="60"></el-table-column>
|
||||
<el-table-column prop="name" label="文件名"></el-table-column>
|
||||
<el-table-column label="操作" width="200">
|
||||
<template slot-scope="scope">
|
||||
<!-- <el-button size="mini" type="danger" @click="removeFile(scope.row)">
|
||||
删除
|
||||
</el-button>-->
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<el-empty v-else description="暂无远程文件" :image-size="100"></el-empty>
|
||||
</div>
|
||||
|
||||
<!-- 清理文件卡片 -->
|
||||
<div class="card">
|
||||
<div class="card-title">🗑️ 本地文件清理</div>
|
||||
<el-form :inline="true">
|
||||
<el-form-item label="保留天数">
|
||||
<el-input-number v-model="cleanDays" :min="1" :max="365" label="请输入保留天数"></el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button v-if="isSourceServer" type="danger" icon="el-icon-delete" @click="cleanTwo" :loading="loading.removeTwo">
|
||||
清理二区服务器上生成的数据
|
||||
</el-button>
|
||||
<el-button v-if="isTargetServer" type="danger" icon="el-icon-delete" @click="cleanThree" :loading="loading.removeThree">
|
||||
清理三区服务器保留的数据
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- 操作日志卡片 -->
|
||||
<div class="card">
|
||||
<div class="card-title">📋 操作日志</div>
|
||||
<div class="log-area">
|
||||
<div v-for="(log, index) in logs" :key="index" :class="['log-item', log.type]">
|
||||
[{{ log.time }}] {{ log.message }}
|
||||
</div>
|
||||
<div v-if="logs.length === 0" class="log-info">
|
||||
暂无操作日志
|
||||
</div>
|
||||
</div>
|
||||
<el-button size="small" @click="clearLogs" style="margin-top: 10px;">
|
||||
清空日志
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 引入 Vue -->
|
||||
<script src="js/vue.min.js"></script>
|
||||
<!-- 引入 Element UI -->
|
||||
<script src="js/element-ui-index.js"></script>
|
||||
<!-- 引入 Axios -->
|
||||
<script src="js/axios.min.js"></script>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: '#app',
|
||||
data() {
|
||||
return {
|
||||
selectedDate: '',
|
||||
cleanDays: 7,
|
||||
remoteFiles: [],
|
||||
logs: [],
|
||||
loading: {
|
||||
export: false,
|
||||
import: false,
|
||||
test: false,
|
||||
testR: false,
|
||||
getRemoteAll: false,
|
||||
removeTwo: false,
|
||||
removeThree: false
|
||||
},
|
||||
fileCount: 0,
|
||||
operationCount: 0,
|
||||
lastOperationTime: '',
|
||||
serverType: 'source',
|
||||
serverTypeName: '源服务器',
|
||||
showEnvSwitch: false,
|
||||
// 默认选中 昨天
|
||||
selectedDate: this.getYesterdayDate()
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isSourceServer() {
|
||||
return this.serverType === 'source';
|
||||
},
|
||||
isTargetServer() {
|
||||
return this.serverType === 'target';
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadServerConfig();
|
||||
},
|
||||
methods: {
|
||||
// 获取昨天的日期,格式 yyyyMMdd
|
||||
getYesterdayDate() {
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() - 1); // 减去一天 = 昨天
|
||||
|
||||
const y = date.getFullYear();
|
||||
const m = (date.getMonth() + 1).toString().padStart(2, '0');
|
||||
const d = date.getDate().toString().padStart(2, '0');
|
||||
|
||||
return `${y}${m}${d}`; // 输出 20250613 这种格式
|
||||
},
|
||||
// 加载服务器配置
|
||||
async loadServerConfig() {
|
||||
try {
|
||||
const response = await axios.get('source/getConfig');
|
||||
this.serverType = response.data.serverType;
|
||||
this.serverTypeName = response.data.serverTypeName;
|
||||
this.addLog(`系统初始化完成,当前环境:${this.serverTypeName}`, 'success');
|
||||
this.getRemoteFiles();
|
||||
} catch (error) {
|
||||
console.error('加载配置失败:', error);
|
||||
this.addLog('加载配置失败,使用默认配置', 'error');
|
||||
this.getRemoteFiles();
|
||||
}
|
||||
},
|
||||
|
||||
// 添加日志
|
||||
addLog(message, type = 'info') {
|
||||
const now = new Date();
|
||||
const timeStr = now.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
});
|
||||
this.logs.unshift({
|
||||
time: timeStr,
|
||||
message: message,
|
||||
type: type
|
||||
});
|
||||
// 保持最多100条日志
|
||||
if (this.logs.length > 100) {
|
||||
this.logs.pop();
|
||||
}
|
||||
},
|
||||
|
||||
// 清空日志
|
||||
clearLogs() {
|
||||
this.logs = [];
|
||||
this.$message.info('日志已清空');
|
||||
},
|
||||
|
||||
// 导出数据
|
||||
async handleExport() {
|
||||
this.loading.export = true;
|
||||
try {
|
||||
const params = this.selectedDate ? { date: this.selectedDate } : {};
|
||||
await axios.get('source/export', { params });
|
||||
this.$message.success('二区数据导出上传成功');
|
||||
this.addLog('二区数据导出上传成功', 'success');
|
||||
this.operationCount++;
|
||||
this.lastOperationTime = new Date().toLocaleTimeString();
|
||||
} catch (error) {
|
||||
this.$message.error('数据导出失败: ' + (error.response?.data || error.message));
|
||||
this.addLog('数据导出失败: ' + error.message, 'error');
|
||||
} finally {
|
||||
this.loading.export = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 导入数据
|
||||
async handleImport() {
|
||||
this.loading.import = true;
|
||||
try {
|
||||
const params = this.selectedDate ? { date: this.selectedDate } : {};
|
||||
await axios.get('source/import', { params });
|
||||
this.$message.success('三区数据拉取导入成功');
|
||||
this.addLog('三区数据拉取导入成功', 'success');
|
||||
this.operationCount++;
|
||||
this.lastOperationTime = new Date().toLocaleTimeString();
|
||||
} catch (error) {
|
||||
this.$message.error('数据导入失败: ' + (error.response?.data || error.message));
|
||||
this.addLog('数据导入失败: ' + error.message, 'error');
|
||||
} finally {
|
||||
this.loading.import = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 测试连接
|
||||
async handleTest() {
|
||||
this.loading.test = true;
|
||||
try {
|
||||
await axios.get('source/test');
|
||||
this.$message.success('测试二区数据推送横向隔离设备成功');
|
||||
this.addLog('测试二区数据推送横向隔离设备成功', 'success');
|
||||
this.operationCount++;
|
||||
this.lastOperationTime = new Date().toLocaleTimeString();
|
||||
} catch (error) {
|
||||
this.$message.error('测试二区数据推送横向隔离设备失败: ' + (error.response?.data || error.message));
|
||||
this.addLog('测试二区数据推送横向隔离设备失败: ' + error.message, 'error');
|
||||
} finally {
|
||||
this.loading.test = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 测试远程
|
||||
async handleTestR() {
|
||||
this.loading.testR = true;
|
||||
try {
|
||||
await axios.get('source/testR');
|
||||
this.$message.success('三区拉取测试数据成功');
|
||||
this.addLog('三区拉取测试数据成功', 'success');
|
||||
this.operationCount++;
|
||||
this.lastOperationTime = new Date().toLocaleTimeString();
|
||||
} catch (error) {
|
||||
this.$message.error('三区拉取测试数据失败: ' + (error.response?.data || error.message));
|
||||
this.addLog('三区拉取测试数据失败: ' + error.message, 'error');
|
||||
} finally {
|
||||
this.loading.testR = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 获取远程文件列表
|
||||
async getRemoteFiles() {
|
||||
this.loading.getRemoteAll = true;
|
||||
try {
|
||||
const response = await axios.get('source/getRemoteAll');
|
||||
this.remoteFiles = response.data.map((name, index) => ({
|
||||
id: index,
|
||||
name: name
|
||||
}));
|
||||
this.fileCount = this.remoteFiles.length;
|
||||
this.$message.success(`获取到 ${this.fileCount} 个远程文件`);
|
||||
this.addLog(`获取远程文件列表成功,共 ${this.fileCount} 个文件`, 'success');
|
||||
} catch (error) {
|
||||
this.$message.error('获取远程文件失败: ' + (error.response?.data || error.message));
|
||||
this.addLog('获取远程文件失败: ' + error.message, 'error');
|
||||
} finally {
|
||||
this.loading.getRemoteAll = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 清理二区服务器历史导出数据
|
||||
async cleanTwo() {
|
||||
this.loading.removeTwo = true;
|
||||
try {
|
||||
const response = await axios.get('source/removeTwo', {
|
||||
params: { a: this.cleanDays }
|
||||
});
|
||||
this.$message.success(response.data);
|
||||
this.addLog(`清理二区服务器历史导出数据成功,保留${this.cleanDays}天内的文件`, 'success');
|
||||
this.operationCount++;
|
||||
this.lastOperationTime = new Date().toLocaleTimeString();
|
||||
// 刷新文件列表
|
||||
//this.getRemoteFiles();
|
||||
} catch (error) {
|
||||
this.$message.error('清理失败: ' + (error.response?.data || error.message));
|
||||
this.addLog('清理二区服务器历史导出数据失败: ' + error.message, 'error');
|
||||
} finally {
|
||||
this.loading.removeTwo = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 清理三区拉取数据
|
||||
async cleanThree() {
|
||||
this.loading.removeThree = true;
|
||||
try {
|
||||
const response = await axios.get('source/removeThree', {
|
||||
params: { a: this.cleanDays }
|
||||
});
|
||||
this.$message.success(response.data);
|
||||
this.addLog(`清理三区拉取数据成功,保留${this.cleanDays}天内的文件`, 'success');
|
||||
this.operationCount++;
|
||||
this.lastOperationTime = new Date().toLocaleTimeString();
|
||||
// 刷新文件列表
|
||||
//this.getRemoteFiles();
|
||||
} catch (error) {
|
||||
this.$message.error('清理失败: ' + (error.response?.data || error.message));
|
||||
this.addLog('清理三区拉取数据失败: ' + error.message, 'error');
|
||||
} finally {
|
||||
this.loading.removeThree = false;
|
||||
}
|
||||
},
|
||||
|
||||
// ========== 新增:跳转至管理配置页面 ==========
|
||||
goToConfigPage() {
|
||||
// 方式1:当前窗口跳转(独立页面)
|
||||
window.location.href = 'config';
|
||||
// 方式2:新窗口打开(可选)
|
||||
// window.open('config.html', '_blank');
|
||||
},
|
||||
|
||||
goToLogsPage() {
|
||||
// 方式1:当前窗口跳转(独立页面)
|
||||
window.location.href = 'logs';
|
||||
// 方式2:新窗口打开(可选)
|
||||
// window.open('config.html', '_blank');
|
||||
},
|
||||
|
||||
// 刷新当前页面数据
|
||||
refreshAll() {
|
||||
this.getRemoteFiles();
|
||||
this.addLog('手动刷新', 'info');
|
||||
this.$message.success('已刷新');
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,588 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>数据同步日志管理</title>
|
||||
<!-- Element UI 样式 -->
|
||||
<link rel="stylesheet" href="js/element-ui.css">
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
background-color: #f0f2f5;
|
||||
padding: 20px;
|
||||
}
|
||||
.container {
|
||||
margin: 0 auto;
|
||||
}
|
||||
/* 头部 */
|
||||
.header {
|
||||
background: linear-gradient(135deg, #67c23a 0%, #5daf34 100%);
|
||||
color: white;
|
||||
padding: 20px 30px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 24px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.header h1 {
|
||||
font-size: 24px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.header .sub {
|
||||
font-size: 14px;
|
||||
opacity: 0.85;
|
||||
margin-top: 4px;
|
||||
}
|
||||
.header-actions .el-button {
|
||||
border-color: rgba(255,255,255,0.4);
|
||||
color: white;
|
||||
background: rgba(255,255,255,0.1);
|
||||
}
|
||||
.header-actions .el-button:hover {
|
||||
background: rgba(255,255,255,0.25);
|
||||
border-color: rgba(255,255,255,0.6);
|
||||
}
|
||||
/* 卡片 */
|
||||
.card {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 20px 24px;
|
||||
box-shadow: 0 1px 4px rgba(0,0,0,0.08);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.card-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
}
|
||||
.search-bar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.search-bar .el-form-item {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.table-wrap {
|
||||
margin-top: 8px;
|
||||
}
|
||||
.status-tag {
|
||||
font-weight: 500;
|
||||
}
|
||||
.dialog-footer {
|
||||
text-align: right;
|
||||
padding-top: 12px;
|
||||
}
|
||||
.el-table th {
|
||||
background-color: #f5f7fa !important;
|
||||
}
|
||||
.el-dialog .el-form-item {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.pagination-wrap {
|
||||
margin-top: 16px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.result-success {
|
||||
color: #67c23a;
|
||||
}
|
||||
.result-fail {
|
||||
color: #f56c6c;
|
||||
}
|
||||
.stat-summary {
|
||||
display: flex;
|
||||
gap: 30px;
|
||||
flex-wrap: wrap;
|
||||
padding: 10px 0;
|
||||
}
|
||||
.stat-summary .stat-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.stat-summary .stat-item .label {
|
||||
color: #909399;
|
||||
}
|
||||
.stat-summary .stat-item .value {
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
}
|
||||
.stat-summary .stat-item .value.success {
|
||||
color: #67c23a;
|
||||
}
|
||||
.stat-summary .stat-item .value.fail {
|
||||
color: #f56c6c;
|
||||
}
|
||||
.stat-summary .stat-item .value.total {
|
||||
color: #409eff;
|
||||
}
|
||||
.back-link {
|
||||
cursor: pointer;
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
.back-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.local-result-tag {
|
||||
font-size: 12px;
|
||||
padding: 2px 10px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
.retry-btn {
|
||||
margin-left: 4px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<div class="container">
|
||||
<!-- 头部 -->
|
||||
<div class="header">
|
||||
<div>
|
||||
<h1>📋 数据同步日志管理 <span style="font-size: 14px; margin-left: 12px; font-weight: 400;">SYNC_TABLE_LOGS</span></h1>
|
||||
<div class="sub">二三区数据转移日志 · 查看同步记录与状态</div>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<el-button type="text" icon="el-icon-back" @click="goBack" style="color: white; font-size: 14px;">
|
||||
返回主页面
|
||||
</el-button>
|
||||
<el-button type="text" icon="el-icon-refresh" @click="fetchPage" style="color: white; font-size: 14px;">
|
||||
刷新
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 搜索 & 统计 -->
|
||||
<div class="card">
|
||||
<div class="card-title">🔍 查询 & 统计</div>
|
||||
<div class="search-bar">
|
||||
<el-form :inline="true" size="small" @submit.native.prevent>
|
||||
<el-form-item label="日期">
|
||||
<el-date-picker
|
||||
v-model="search.timeId"
|
||||
type="date"
|
||||
placeholder="选择日期"
|
||||
format="yyyy-MM-dd"
|
||||
value-format="yyyy-MM-dd"
|
||||
clearable
|
||||
style="width:160px;">
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="同步结果">
|
||||
<el-select v-model="search.result" placeholder="全部" clearable style="width:120px;">
|
||||
<el-option label="成功" :value="1"></el-option>
|
||||
<el-option label="失败" :value="0"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="本地处理">
|
||||
<el-select v-model="search.localResult" placeholder="全部" clearable style="width:120px;">
|
||||
<el-option label="成功" :value="1"></el-option>
|
||||
<el-option label="失败" :value="0"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" @click="fetchPage">查询</el-button>
|
||||
<el-button icon="el-icon-refresh" @click="resetSearch">重置</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item style="margin-left: auto;">
|
||||
<el-button type="danger" icon="el-icon-delete" plain :disabled="selectedRows.length===0" @click="batchDelete">批量删除</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<!-- 统计信息 -->
|
||||
<div class="stat-summary" v-if="total > 0">
|
||||
<div class="stat-item">
|
||||
<span class="label">📊 总记录数:</span>
|
||||
<span class="value total">{{ total }}</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="label">✅ 同步成功:</span>
|
||||
<span class="value success">{{ successCount }}</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="label">❌ 同步失败:</span>
|
||||
<span class="value fail">{{ failCount }}</span>
|
||||
</div>
|
||||
<div class="stat-item" v-if="search.timeId">
|
||||
<span class="label">📅 日期:</span>
|
||||
<span>{{ search.timeId }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 表格 -->
|
||||
<div class="card">
|
||||
<div class="card-title">📋 日志列表 <span style="font-size:13px; font-weight:400; color:#909399; margin-left:12px;">共 {{ total }} 条</span></div>
|
||||
<div class="table-wrap">
|
||||
<el-table
|
||||
:data="tableData"
|
||||
stripe
|
||||
border
|
||||
v-loading="loading"
|
||||
style="width:100%;"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="90" align="center"></el-table-column>
|
||||
<el-table-column prop="timeId" label="日期" width="130" sortable>
|
||||
<template slot-scope="{ row }">
|
||||
{{ row.timeId || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="result" label="同步结果" width="110" align="center">
|
||||
<template slot-scope="{ row }">
|
||||
<el-tag :type="row.result === 1 ? 'success' : 'danger'" size="small">
|
||||
{{ row.result === 1 ? '✅ 成功' : '❌ 失败' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="localResult" label="本地处理" width="130" align="center">
|
||||
<template slot-scope="{ row }">
|
||||
<el-tag :type="row.localResult === 1 ? 'success' : 'danger'" size="small" v-if="row.localResult !== undefined && row.localResult !== null">
|
||||
{{ row.localResult === 1 ? '✅ 成功' : '❌ 失败' }}
|
||||
</el-tag>
|
||||
<span v-else style="color:#c0c4cc;">-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" width="170" sortable>
|
||||
<template slot-scope="{ row }">{{ row.createTime ? formatDate(row.createTime) : '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" fixed="right">
|
||||
<template slot-scope="{ row }">
|
||||
<el-button size="mini" type="primary" plain @click="openDetailDialog(row)">详情</el-button>
|
||||
<!-- 重试按钮:只有 result=0 且 localResult=0 时才显示 -->
|
||||
<el-button
|
||||
v-if="row.result === 0 && row.localResult === 0"
|
||||
size="mini"
|
||||
type="warning"
|
||||
plain
|
||||
class="retry-btn"
|
||||
@click="handleRetry(row)"
|
||||
:loading="retryLoading[row.timeId + '_' + row.result]"
|
||||
>
|
||||
重试
|
||||
</el-button>
|
||||
<el-button size="mini" type="danger" plain @click="deleteSingle(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-wrap">
|
||||
<el-pagination
|
||||
background
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
:current-page="pageNum"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
:page-size="pageSize"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total">
|
||||
</el-pagination>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========== 详情对话框 ========== -->
|
||||
<el-dialog
|
||||
title="📄 日志详情"
|
||||
:visible.sync="detailVisible"
|
||||
width="500px"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-descriptions :column="1" border>
|
||||
<el-descriptions-item label="日期">
|
||||
{{ detailData.timeId || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="同步结果">
|
||||
<el-tag :type="detailData.result === 1 ? 'success' : 'danger'" size="small">
|
||||
{{ detailData.result === 1 ? '✅ 成功' : '❌ 失败' }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="本地处理结果">
|
||||
<el-tag :type="detailData.localResult === 1 ? 'success' : 'danger'" size="small" v-if="detailData.localResult !== undefined && detailData.localResult !== null">
|
||||
{{ detailData.localResult === 1 ? '✅ 成功' : '❌ 失败' }}
|
||||
</el-tag>
|
||||
<span v-else style="color:#c0c4cc;">未处理</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">
|
||||
{{ detailData.createTime ? formatDate(detailData.createTime) : '-' }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="detailVisible = false">关闭</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</div>
|
||||
|
||||
<!-- 引入 Vue & Element UI & Axios -->
|
||||
<script src="js/vue.min.js"></script>
|
||||
<script src="js/element-ui-index.js"></script>
|
||||
<script src="js/axios.min.js"></script>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: '#app',
|
||||
data() {
|
||||
return {
|
||||
// 分页参数
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
// 搜索条件
|
||||
search: {
|
||||
timeId: '',
|
||||
result: null,
|
||||
localResult: null
|
||||
},
|
||||
// 表格数据
|
||||
tableData: [],
|
||||
loading: false,
|
||||
selectedRows: [],
|
||||
// 统计
|
||||
successCount: 0,
|
||||
failCount: 0,
|
||||
// 详情对话框
|
||||
detailVisible: false,
|
||||
detailData: {},
|
||||
serverType: '',
|
||||
// 重试加载状态
|
||||
retryLoading: {}
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.loadServerConfig();
|
||||
this.fetchPage();
|
||||
},
|
||||
methods: {
|
||||
// ---------- 数据请求 ----------
|
||||
fetchPage() {
|
||||
this.loading = true;
|
||||
const params = {
|
||||
pageNum: this.pageNum,
|
||||
pageSize: this.pageSize,
|
||||
timeId: this.search.timeId || undefined,
|
||||
result: this.search.result !== null && this.search.result !== '' ? this.search.result : undefined,
|
||||
localResult: this.search.localResult !== null && this.search.localResult !== '' ? this.search.localResult : undefined
|
||||
};
|
||||
axios.get('sync-logs/page', { params })
|
||||
.then(res => {
|
||||
let responseData = res.data;
|
||||
|
||||
if (responseData.code !== undefined) {
|
||||
if (responseData.code === 200 || responseData.code === 0) {
|
||||
responseData = responseData.data;
|
||||
} else {
|
||||
this.$message.error(responseData.msg || '获取列表失败');
|
||||
this.tableData = [];
|
||||
this.total = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (responseData) {
|
||||
if (responseData.records !== undefined) {
|
||||
this.tableData = responseData.records || [];
|
||||
this.total = responseData.total || 0;
|
||||
} else if (responseData.list !== undefined) {
|
||||
this.tableData = responseData.list || [];
|
||||
this.total = responseData.total || 0;
|
||||
} else if (Array.isArray(responseData)) {
|
||||
this.tableData = responseData;
|
||||
this.total = responseData.length;
|
||||
} else if (responseData.data && responseData.data.records) {
|
||||
this.tableData = responseData.data.records || [];
|
||||
this.total = responseData.data.total || 0;
|
||||
} else {
|
||||
this.tableData = [];
|
||||
this.total = 0;
|
||||
}
|
||||
} else {
|
||||
this.tableData = [];
|
||||
this.total = 0;
|
||||
}
|
||||
|
||||
// 统计成功/失败数量
|
||||
this.calcStatistics();
|
||||
})
|
||||
.catch(err => {
|
||||
this.$message.error('请求失败: ' + (err.response?.data?.msg || err.message));
|
||||
this.tableData = [];
|
||||
this.total = 0;
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
|
||||
calcStatistics() {
|
||||
this.successCount = this.tableData.filter(item => item.result === 1).length;
|
||||
this.failCount = this.tableData.filter(item => item.result === 0).length;
|
||||
},
|
||||
|
||||
resetSearch() {
|
||||
this.search.timeId = '';
|
||||
this.search.result = null;
|
||||
this.search.localResult = null;
|
||||
this.pageNum = 1;
|
||||
this.fetchPage();
|
||||
},
|
||||
|
||||
// ---------- 分页 ----------
|
||||
handleSizeChange(val) {
|
||||
this.pageSize = val;
|
||||
this.pageNum = 1;
|
||||
this.fetchPage();
|
||||
},
|
||||
handleCurrentChange(val) {
|
||||
this.pageNum = val;
|
||||
this.fetchPage();
|
||||
},
|
||||
|
||||
// ---------- 选中 ----------
|
||||
handleSelectionChange(rows) {
|
||||
this.selectedRows = rows;
|
||||
},
|
||||
|
||||
// ---------- 详情 ----------
|
||||
openDetailDialog(row) {
|
||||
this.detailData = { ...row };
|
||||
this.detailVisible = true;
|
||||
},
|
||||
|
||||
// ---------- 重试 ----------
|
||||
async handleRetry(row) {
|
||||
const key = row.timeId + '_' + row.result;
|
||||
// 设置加载状态
|
||||
this.$set(this.retryLoading, key, true);
|
||||
|
||||
try {
|
||||
// 根据 serverType 调用不同接口
|
||||
// source: 调用 source/import (二区导入)
|
||||
// target: 调用 source/export (三区导出)
|
||||
let url = '';
|
||||
if (this.serverType === 'source') {
|
||||
url = 'source/export';
|
||||
} else if (this.serverType === 'target') {
|
||||
url = 'source/import';
|
||||
} else {
|
||||
this.$message.error('未知的服务器类型: ' + this.serverType);
|
||||
return;
|
||||
}
|
||||
|
||||
// 调用接口,传递日期参数
|
||||
|
||||
const params = {
|
||||
date: row.timeId.replace(/-/g, "")
|
||||
};
|
||||
await axios.get(url, { params });
|
||||
|
||||
this.$message.success(`重试成功 (${this.serverType === 'source' ? '二区导入' : '三区导出'})`);
|
||||
// 刷新列表
|
||||
this.fetchPage();
|
||||
} catch (error) {
|
||||
this.$message.error('重试失败: ' + (error.response?.data || error.message));
|
||||
} finally {
|
||||
// 清除加载状态
|
||||
this.$set(this.retryLoading, key, false);
|
||||
}
|
||||
},
|
||||
|
||||
// ---------- 删除单个 ----------
|
||||
deleteSingle(row) {
|
||||
this.$confirm(`确定删除日期 "${row.timeId}" 的日志吗?`, '提示', {
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
axios.delete(`sync-logs/${row.timeId}/${row.result}`)
|
||||
.then(res => {
|
||||
let data = res.data;
|
||||
if (data.code === 200 || data.code === 0 || data.success) {
|
||||
this.$message.success('删除成功');
|
||||
this.fetchPage();
|
||||
} else {
|
||||
this.$message.error(data.msg || '删除失败');
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
this.$message.error('删除失败: ' + (err.response?.data?.msg || err.message));
|
||||
});
|
||||
}).catch(() => {});
|
||||
},
|
||||
|
||||
// ---------- 批量删除 ----------
|
||||
batchDelete() {
|
||||
if (this.selectedRows.length === 0) {
|
||||
this.$message.warning('请先选择要删除的日志');
|
||||
return;
|
||||
}
|
||||
const logs = this.selectedRows.map(r => ({
|
||||
timeId: r.timeId,
|
||||
result: r.result
|
||||
}));
|
||||
this.$confirm(`确定删除选中的 ${logs.length} 条日志吗?`, '批量删除', {
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
axios.delete('sync-logs/batch', { data: logs })
|
||||
.then(res => {
|
||||
let data = res.data;
|
||||
if (data.code === 200 || data.code === 0 || data.success) {
|
||||
this.$message.success('批量删除成功');
|
||||
this.fetchPage();
|
||||
} else {
|
||||
this.$message.error(data.msg || '批量删除失败');
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
this.$message.error('批量删除失败: ' + (err.response?.data?.msg || err.message));
|
||||
});
|
||||
}).catch(() => {});
|
||||
},
|
||||
|
||||
// ---------- 返回主页面 ----------
|
||||
goBack() {
|
||||
window.location.href = 'index';
|
||||
},
|
||||
|
||||
// ---------- 工具函数 ----------
|
||||
formatDate(dateStr) {
|
||||
if (!dateStr) return '-';
|
||||
const d = new Date(dateStr);
|
||||
if (isNaN(d)) return dateStr;
|
||||
const pad = n => String(n).padStart(2, '0');
|
||||
return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
||||
},
|
||||
|
||||
// ---------- 加载服务器配置 ----------
|
||||
async loadServerConfig() {
|
||||
try {
|
||||
const response = await axios.get('source/getConfig');
|
||||
this.serverType = response.data.serverType;
|
||||
} catch (error) {
|
||||
console.error('加载配置失败:', error);
|
||||
// 默认设为 source
|
||||
this.serverType = 'source';
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user