Ver Fonte

feat: 实现车辆违章数据导入接口

weijianghai há 9 meses atrás
pai
commit
f9e0705559

+ 4 - 0
.mvn/maven.config

@@ -0,0 +1,4 @@
+-U
+-T1C
+-Dmaven.test.skip=true
+-Dmaven.compile.fork=true

+ 11 - 0
scripts/copy.sh

@@ -0,0 +1,11 @@
+#!/bin/bash
+
+export PGPASSWORD=$4
+host=$1
+port=$2
+username=$3
+dbname=$5
+table=$6
+filename=$7
+columns=$8
+psql -h "${host}" -p "${port}" -U "${username}" -d "${dbname}" -c "\\copy ${table} ${columns} from ${filename} with csv header;"

+ 91 - 0
src/main/java/com/nokia/financeapi/common/utils/psql/PsqlUtil.java

@@ -0,0 +1,91 @@
+package com.nokia.financeapi.common.utils.psql;
+
+import com.nokia.financeapi.common.exception.MyRuntimeException;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.exec.CommandLine;
+import org.apache.commons.exec.DefaultExecutor;
+import org.apache.commons.exec.ExecuteWatchdog;
+import org.apache.commons.exec.PumpStreamHandler;
+import org.springframework.util.StringUtils;
+
+import java.io.ByteArrayOutputStream;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * psql命令工具
+ */
+@Slf4j
+public class PsqlUtil {
+    /**
+     * 匹配psql copy成功结果
+     */
+    private static final Pattern PATTERN = Pattern.compile("^(COPY) (\\d+)$");
+
+    /**
+     * 导入csv
+     *
+     * @param script         脚本
+     * @param dbHost         数据库主机
+     * @param dbPort         数据库端口
+     * @param dbUsername     数据库用户名
+     * @param dbPassword     数据库密码
+     * @param dbName         数据库名字
+     * @param dbTable        数据库表
+     * @param csv            csv
+     * @param columns        字段
+     * @param timeout        超时ms
+     * @param minInsertCount 最小值插入数
+     */
+    public static void copyCsv(String script, String dbHost, String dbPort, String dbUsername, String dbPassword,
+                               String dbName, String dbTable, String csv, String columns, Long timeout,
+                               Long minInsertCount) {
+        String command = "sh " + script;
+        CommandLine commandLine = CommandLine.parse(command);
+        commandLine.addArgument(dbHost);
+        commandLine.addArgument(dbPort);
+        commandLine.addArgument(dbUsername);
+        commandLine.addArgument(dbPassword);
+        commandLine.addArgument(dbName);
+        commandLine.addArgument(dbTable);
+        commandLine.addArgument(csv);
+        commandLine.addArgument(columns);
+        log.info("command: {}", commandLine);
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        ByteArrayOutputStream err = new ByteArrayOutputStream();
+        DefaultExecutor executor = new DefaultExecutor();
+        ExecuteWatchdog watchdog = new ExecuteWatchdog(timeout);
+        executor.setWatchdog(watchdog);
+        PumpStreamHandler streamHandler = new PumpStreamHandler(out, err);
+        executor.setStreamHandler(streamHandler);
+        try {
+            int exitValue = executor.execute(commandLine);
+            log.info("exitValue: {}", exitValue);
+            String outString = out.toString();
+            Long count = null;
+            Matcher matcher = PATTERN.matcher(outString);
+            if (matcher.find()) {
+                count = Long.parseLong(matcher.group(2));
+            }
+            if (count == null) {
+                throw new MyRuntimeException("导入数据失败");
+            }
+            log.info("插入 {} 条数据", count);
+            if (minInsertCount != null && count < minInsertCount) {
+                throw new MyRuntimeException(csv + " 数据异常,少于 " + minInsertCount);
+            }
+        } catch (Exception e) {
+            if (watchdog.killedProcess()) {
+                throw new MyRuntimeException("执行超时", e);
+            }
+            throw new MyRuntimeException(e);
+        } finally {
+            String outString = out.toString();
+            String errString = err.toString();
+            log.info("out: {}", outString);
+            if (StringUtils.hasText(errString)) {
+                log.error("err: {}", errString);
+            }
+        }
+    }
+}

+ 47 - 0
src/main/java/com/nokia/financeapi/config/DataImportConfig.java

@@ -0,0 +1,47 @@
+package com.nokia.financeapi.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@Data
+@ConfigurationProperties("data-import")
+public class DataImportConfig {
+    /**
+     * copy脚本路径
+     */
+    private String copyScriptPath;
+    /**
+     * 导入的数据库ip
+     */
+    private String dbHost;
+    /**
+     * 数据库端口
+     */
+    private String dbPort;
+    /**
+     * 数据库账号
+     */
+    private String dbUsername;
+    /**
+     * 数据库密码
+     */
+    private String dbPassword;
+    /**
+     * 数据库名称
+     */
+    private String dbName;
+    /**
+     * 车辆超保数据保存路径
+     */
+    private String chaoBao;
+    /**
+     * 车辆过检数据保存路径
+     */
+    private String guoJian;
+    /**
+     * 车辆违章数据保存路径
+     */
+    private String weiZhang;
+}

+ 31 - 0
src/main/java/com/nokia/financeapi/controller/car/CarDataImportController.java

@@ -0,0 +1,31 @@
+package com.nokia.financeapi.controller.car;
+
+import com.nokia.financeapi.common.R;
+import com.nokia.financeapi.pojo.dto.CarDataImportDto;
+import com.nokia.financeapi.service.car.CarDataImportService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestPart;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.validation.Valid;
+
+@Tag(name = "车辆数据导入")
+@RestController
+@RequestMapping("/house-car/car/data-import/api")
+public class CarDataImportController {
+    private final CarDataImportService carDataImportService;
+
+    public CarDataImportController(CarDataImportService carDataImportService) {
+        this.carDataImportService = carDataImportService;
+    }
+
+    @Operation(summary = "导入数据")
+    @PostMapping("/dataImport")
+    public R<Object> dataImport(@Valid CarDataImportDto dto, @RequestPart("file") MultipartFile file) {
+        return carDataImportService.dataImport(dto, file);
+    }
+}

+ 100 - 0
src/main/java/com/nokia/financeapi/dao/car/CarWeiZhangDao.java

@@ -0,0 +1,100 @@
+package com.nokia.financeapi.dao.car;
+
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import org.apache.ibatis.annotations.Update;
+
+@Mapper
+public interface CarWeiZhangDao {
+    /**
+     * 判断是否有车辆违章数据
+     * @param endYearMonth 账期
+     */
+    @Select("""
+select exists (select 1 from car.car_wei_zhang where year_month = #{endYearMonth})
+""")
+    boolean hasCarWeiZhang(@Param("endYearMonth") Integer endYearMonth);
+
+    /**
+     * 插入违章长期未处理
+     * @param endYearMonth 账期
+     */
+    @Update("""
+insert
+    into
+    car.car_wei_zhang_chang_qi
+(
+year_month,
+    che_pai_hao,
+    raw_yi_ji,
+    raw_er_ji,
+    raw_san_ji,
+    wei_zhang_shi_jian,
+    wei_zhang_di_dian,
+    wei_zhang_xiang_qing,
+    kou_fen,
+    fa_kuan,
+    wei_zhang_wei_chu_li_shi_chang,
+    chu_li_zhuang_tai,
+    first_unit,
+    second_unit,
+    third_unit,
+    area_no,
+    area_name,
+    city_no,
+    city_name,
+    area_name2,
+    area_no2,
+    city_id,
+    city,
+    district_id,
+    district,
+    raw_che_pai_hao,
+    che_pai_fail,
+    wei_zhang_nian_yue,
+    year_no,
+    month_no,
+    source
+)
+select
+    year_month,
+    che_pai_hao,
+    raw_yi_ji,
+    raw_er_ji,
+    raw_san_ji,
+    wei_zhang_shi_jian,
+    wei_zhang_di_dian,
+    wei_zhang_xiang_qing,
+    kou_fen,
+    fa_kuan,
+    wei_zhang_wei_chu_li_shi_chang,
+    chu_li_zhuang_tai,
+    first_unit,
+    second_unit,
+    third_unit,
+    area_no,
+    area_name,
+    city_no,
+    city_name,
+    area_name2,
+    area_no2,
+    city_id,
+    city,
+    district_id,
+    district,
+    raw_che_pai_hao,
+    che_pai_fail,
+    wei_zhang_nian_yue,
+    year_no,
+    month_no,
+    source
+from
+    car.car_wei_zhang
+where
+    chu_li_zhuang_tai = '未处理'
+    and wei_zhang_wei_chu_li_shi_chang > 150
+    and year_month = #{endYearMonth}
+""")
+    int insertCarWeiZhangChangQi(@Param("endYearMonth") Integer endYearMonth);
+}

+ 52 - 0
src/main/java/com/nokia/financeapi/dao/gdc/GdcCarWeiZhangDao.java

@@ -0,0 +1,52 @@
+package com.nokia.financeapi.dao.gdc;
+
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Update;
+
+@Mapper
+public interface GdcCarWeiZhangDao {
+    /**
+     * 插入违章长期未处理
+     * @param endYearMonth 账期
+     */
+    @Update("""
+insert
+    into
+    car_theme.wz_f_violation_details
+(
+statistical_month,
+    card_num,
+    city,
+    dpt_sec,
+    grid,
+    violation_time,
+    violation_location,
+    violation_details,
+    deduction_points,
+    fine,
+    processing_time,
+    unprocessed_duration_of_violation,
+    offline_actual_processing_status
+)
+select
+    year_month,
+    che_pai_hao,
+    first_unit,
+    second_unit,
+    third_unit,
+    wei_zhang_shi_jian,
+    wei_zhang_di_dian,
+    wei_zhang_xiang_qing,
+    kou_fen,
+    fa_kuan,
+    chu_li_shi_jian,
+    wei_zhang_wei_chu_li_shi_chang,
+    chu_li_zhuang_tai
+from
+    car.car_wei_zhang
+where
+    year_month = #{endYearMonth}
+""")
+    int insertCarWeiZhang(@Param("endYearMonth") Integer endYearMonth);
+}

+ 17 - 0
src/main/java/com/nokia/financeapi/pojo/dto/CarDataImportDto.java

@@ -0,0 +1,17 @@
+package com.nokia.financeapi.pojo.dto;
+
+import com.nokia.financeapi.pojo.enums.CarDataImportEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+@Data
+public class CarDataImportDto {
+    @Schema(description = "模板id")
+    @NotNull(message = "id不能为空")
+    private CarDataImportEnum id;
+    @Schema(description = "账期", example = "202301")
+    @NotNull(message = "yearMonth不能为空")
+    private Integer yearMonth;
+}

+ 9 - 0
src/main/java/com/nokia/financeapi/pojo/enums/CarDataImportEnum.java

@@ -0,0 +1,9 @@
+package com.nokia.financeapi.pojo.enums;
+
+public enum CarDataImportEnum {
+    /**
+     * 违章
+     */
+    WEI_ZHANG,
+    ;
+}

+ 40 - 0
src/main/java/com/nokia/financeapi/service/car/CarDataImportService.java

@@ -0,0 +1,40 @@
+package com.nokia.financeapi.service.car;
+
+import com.nokia.financeapi.common.R;
+import com.nokia.financeapi.common.exception.BizException;
+import com.nokia.financeapi.pojo.dto.CarDataImportDto;
+import com.nokia.financeapi.pojo.enums.CarDataImportEnum;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+import org.springframework.web.multipart.MultipartFile;
+
+@Slf4j
+@Service
+public class CarDataImportService {
+    private final CarWeiZhangImportService carWeiZhangImportService;
+
+    public CarDataImportService(CarWeiZhangImportService carWeiZhangImportService) {
+        this.carWeiZhangImportService = carWeiZhangImportService;
+    }
+
+    public R<Object> dataImport(CarDataImportDto dto, MultipartFile file) {
+        try {
+            if (file.isEmpty()) {
+                return R.error("文件为空");
+            }
+            if (!StringUtils.endsWithIgnoreCase(file.getOriginalFilename(), ".xlsx")) {
+                return R.error("文件格式错误");
+            }
+            if (CarDataImportEnum.WEI_ZHANG.equals(dto.getId())) {
+                carWeiZhangImportService.dataImport(dto, file);
+            }
+            return R.ok();
+        } catch (BizException e) {
+            return R.error(e.getMessage());
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return R.error("导入失败");
+        }
+    }
+}

+ 535 - 0
src/main/java/com/nokia/financeapi/service/car/CarService.java

@@ -0,0 +1,535 @@
+package com.nokia.financeapi.service.car;
+
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import com.nokia.financeapi.pojo.po.common.AreaPo;
+import com.nokia.financeapi.pojo.po.common.OrganizationPo;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+@Slf4j
+@Service
+public class CarService {
+    /**
+     * 匹配车牌省份
+     */
+    static final Pattern HAS_CHE_PAI_PROVINCE_PATTERN = Pattern.compile("[" + Pattern.quote("京津晋冀蒙辽吉黑沪苏浙皖闽赣鲁豫鄂湘粤桂琼渝川贵云藏陕甘青宁国防") + "]");
+    /**
+     * 匹配非车牌字符
+     */
+    static final Pattern NOT_CHE_PAI_PATTERN = Pattern.compile("[^京津晋冀蒙辽吉黑沪苏浙皖闽赣鲁豫鄂湘粤桂琼渝川贵云藏陕甘青宁新港澳学挂领试超练警国防A-Z\\d]");
+    /**
+     * 车牌正则
+     */
+    static final Pattern CHE_PAI_PATTERN = Pattern.compile(
+            "([京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z][A-Z](([DF]((?![IO])[A-Z0-9](?![IO]))\\d{4})|(\\d{5}[DF]))|[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z][A-Z][A-Z0-9]{4}[A-Z0-9挂学警港澳])"
+    );
+
+    /**
+     * 一级单位的二级单位字典
+     */
+    static final Map<String, List<String>> ER_JI_MAP = new Gson().fromJson("""
+{
+    "石家庄": ["鹿泉", "藁城", "栾城", "井陉矿区", "井陉", "无极", "正定", "元氏", "新乐", "晋州", "平山", "灵寿", "赞皇", "赵县", "行唐", "高邑", "辛集", "深泽"],
+    "唐山": ["唐山高开区", "迁西", "海港", "开平", "丰南", "滦县", "乐亭", "丰润", "玉田", "古冶", "曹妃甸", "遵化", "滦南", "迁安"],
+    "秦皇岛": ["北戴河新区", "北戴河", "山海关", "昌黎", "卢龙", "青龙", "抚宁"],
+    "邯郸": ["曲周", "魏县", "馆陶", "磁县", "大名", "鸡泽", "成安", "涉县", "永年", "武安", "峰峰", "广平", "临漳", "邱县", "肥乡"],
+    "邢台": ["新河", "南宫", "隆尧", "内邱", "平乡", "宁晋", "广宗", "清河", "临西", "任县", "巨鹿", "沙河", "威县", "临城", "柏乡", "南和"],
+    "保定": ["涞水", "蠡县", "顺平", "博野", "安国", "涞源", "唐县", "定州", "高阳", "曲阳", "阜平", "清苑", "高碑店", "满城", "涿州", "易县", "望都", "徐水", "定兴", "白沟"],
+    "张家口": ["张北", "崇礼", "康保", "赤城", "阳原", "万全", "下花园", "尚义", "怀安", "怀来", "蔚县", "涿鹿", "沽源", "宣化"],
+    "承德": ["承德县", "兴隆", "宽城", "平泉", "营子", "隆化", "滦平", "围场", "丰宁", "双滦"],
+    "廊坊": ["文安", "霸州", "大城", "廊坊开发区", "三河", "香河", "永清", "胜芳", "燕郊", "固安", "大厂"],
+    "沧州": ["东光", "吴桥", "黄骅", "盐山", "孟村", "泊头", "献县", "南皮", "渤海新区", "海兴", "沧县", "河间", "青县", "任丘", "肃宁"],
+    "衡水": ["景县", "阜城", "枣强", "深州", "饶阳", "故城", "武强", "武邑", "冀州", "安平"],
+    "雄安": ["容城", "雄县", "安新"]
+}
+                    """,
+            new TypeToken<Map<String, List<String>>>() {}.getType());
+
+    /**
+     * 匹配车牌
+     * @param chePai 车牌
+     */
+    public String getChePai(String chePai) {
+        if (!StringUtils.hasText(chePai)) {
+            return "";
+        }
+        // 字母转大写
+        String upperCase = chePai.toUpperCase();
+        // 删除非车牌字符
+        String s = NOT_CHE_PAI_PATTERN.matcher(upperCase).replaceAll("");
+        Matcher m = CHE_PAI_PATTERN.matcher(s);
+        if (m.find()) {
+            return m.group(0);
+        }
+        if (HAS_CHE_PAI_PROVINCE_PATTERN.matcher(chePai).find()) {
+            log.warn("车牌匹配失败: {} -> {}", chePai, s);
+            return s;
+        }
+        log.warn("车牌匹配失败: {} -> {}", chePai, upperCase);
+        return upperCase;
+    }
+
+    /**
+     * 车牌是否匹配失败
+     * @param chePai 车牌
+     */
+    public String chePaiFail(String chePai) {
+        if (!StringUtils.hasText(chePai)) {
+            return "1";
+        }
+        // 字母转大写删除非车牌字符
+        String s = NOT_CHE_PAI_PATTERN.matcher(chePai.toUpperCase()).replaceAll("");
+        Matcher m = CHE_PAI_PATTERN.matcher(s);
+        if (m.find()) {
+            return "0";
+        }
+        return "1";
+    }
+
+    /**
+     * 车牌是否包含报废
+     * @param chePai 车牌
+     */
+    public String baoFei(String chePai) {
+        if (chePai.contains("报废")) {
+            return "1";
+        }
+        return "0";
+    }
+
+    /**
+     * 获取一级单位
+     * @param unit 单位
+     */
+    public String getFirstUnit(String unit) {
+        if (!StringUtils.hasText(unit)) {
+            return "";
+        }
+        if (unit.contains("机动通信局") || unit.contains("机动局")) {
+            return "机动局";
+        }
+        if (unit.contains("雄安基地建设部")) {
+            return "雄安基地建设部";
+        }
+        if (unit.contains("华北基地建设部")) {
+            return "华北基地建设部";
+        }
+        for (String yj : ER_JI_MAP.keySet()) {
+            if (unit.contains(yj)) {
+                return yj;
+            }
+        }
+        return "省公司本部";
+    }
+
+    /**
+     * 获取而二级单位
+     * @param unit 单位
+     * @param firstUnit 一级单位
+     */
+    public String getSecondUnit(String unit, String firstUnit) {
+        if (!StringUtils.hasText(unit)) {
+            return firstUnit;
+        }
+        if ("省公司本部".equals(firstUnit)) {
+            return firstUnit;
+        }
+        if ("机动局".equals(firstUnit)) {
+            for (String yj : ER_JI_MAP.keySet()) {
+                if (unit.contains(yj)) {
+                    return "机动局" + yj;
+                }
+            }
+            return "机动局本部";
+        }
+        if ("石家庄".equals(firstUnit)) {
+            if (unit.contains("开发区")) {
+                return "石家庄开发区";
+            }
+        }
+        if ("廊坊".equals(firstUnit)) {
+            if (unit.contains("开发区")) {
+                return "廊坊开发区";
+            }
+        }
+        if ("邢台".equals(firstUnit)) {
+            if (unit.contains("内丘")) {
+                return "内邱";
+            }
+            if (unit.contains("任泽")) {
+                return "任县";
+            }
+        }
+        if ("唐山".equals(firstUnit)) {
+            if (unit.contains("高开区")) {
+                return "唐山高开区";
+            }
+            if (unit.contains("滦州")) {
+                return "滦县";
+            }
+        }
+        List<String> ejs = ER_JI_MAP.get(firstUnit);
+        if (CollectionUtils.isEmpty(ejs)) {
+            return firstUnit;
+        }
+        if ("雄安".equals(firstUnit)) {
+            unit = unit.replace("雄安新区", "");
+        }
+        for (String ej : ejs) {
+            if (unit.contains(ej)) {
+                return ej;
+            }
+        }
+        return firstUnit + "本部";
+    }
+
+    /**
+     * 获取三级单位
+     * @param unit 单位
+     * @param secondUnit 二级单位
+     */
+    public String getThirdUnit(String unit, String secondUnit) {
+        if (!StringUtils.hasText(unit)) {
+            return secondUnit;
+        }
+        String[] a = unit.split("_");
+        if (a.length == 1) {
+            return unit;
+        }
+        if (a.length < 4) {
+            return secondUnit;
+        }
+        return a[3];
+    }
+
+    /**
+     * 获取二级组织机构编码
+     * @param secondOrgs 二级组织机构列表
+     * @param unit 单位
+     */
+    public String getAreaNo(List<OrganizationPo> secondOrgs, String unit) {
+        if (!StringUtils.hasText(unit)) {
+            return "";
+        }
+        if (unit.contains("机动通信局") || unit.contains("机动局") || unit.contains("传输局")) {
+            return "-11";
+        }
+        if (unit.contains("省公司本部") || unit.contains("雄安基地建设部") || unit.contains("华北基地建设部")) {
+            return "-12";
+        }
+        for (OrganizationPo secondOrg : secondOrgs) {
+            if (unit.contains(secondOrg.getName())) {
+                return secondOrg.getId();
+            }
+        }
+        return "-12";
+    }
+
+    /**
+     * 获取组织机构名称
+     * @param orgMap 组织机构字典
+     * @param orgNo 组织机构编码
+     */
+    public String getOrgName(Map<String, OrganizationPo> orgMap, String orgNo) {
+        if (!StringUtils.hasText(orgNo)) {
+            return "";
+        }
+        OrganizationPo po = orgMap.get(orgNo);
+        if (po != null) {
+            return po.getName();
+        }
+        return "";
+    }
+
+    /**
+     * 获取三级组织机构编码
+     * @param thirdOrgListMap 三级组织机构列表字典
+     * @param areaNo 二级组织机构编码
+     * @param areaName 二级组织机构名称
+     * @param unit 单位
+     */
+    public String getCityNo(Map<String, List<OrganizationPo>> thirdOrgListMap, String areaNo, String areaName,
+                            String unit) {
+        if (!StringUtils.hasText(areaNo) || !StringUtils.hasText(areaName)) {
+            return "";
+        }
+        if ("石家庄".equals(areaName)) {
+            if (unit.contains("井陉矿区")) {
+                return "D0130185";
+            }
+            if (unit.contains("井陉")) {
+                return "D0130121";
+            }
+        }
+        if ("秦皇岛".equals(areaName)) {
+            if (unit.contains("北戴河新区")) {
+                return "D0130325";
+            }
+            if (unit.contains("北戴河")) {
+                return "D0130304";
+            }
+        }
+        if ("邯郸".equals(areaName)) {
+            if (unit.contains("峰峰")) {
+                return "D0130406";
+            }
+        }
+        if ("邢台".equals(areaName)) {
+            if (unit.contains("内丘")) {
+                return "D0130523";
+            }
+            if (unit.contains("任泽")) {
+                return "D0130526";
+            }
+        }
+        if ("省机动局".equals(areaName)) {
+            if (unit.contains("沧州")) {
+                return "HECS180";
+            }
+            if (unit.contains("唐山")) {
+                return "HECS181";
+            }
+            if (unit.contains("秦皇岛")) {
+                return "HECS182";
+            }
+            if (unit.contains("廊坊")) {
+                return "HECS183";
+            }
+            if (unit.contains("张家口")) {
+                return "HECS184";
+            }
+            if (unit.contains("邢台")) {
+                return "HECS185";
+            }
+            if (unit.contains("邯郸")) {
+                return "HECS186";
+            }
+            if (unit.contains("保定")) {
+                return "HECS187";
+            }
+            if (unit.contains("石家庄")) {
+                return "HECS188";
+            }
+            if (unit.contains("承德")) {
+                return "HECS189";
+            }
+            if (unit.contains("衡水")) {
+                return "HECS720";
+            }
+            if (unit.contains("雄安")) {
+                return "HECS728";
+            }
+            return "HECS018";
+        }
+        if ("雄安".equals(areaName)) {
+            unit = unit.replace("雄安新区", "");
+        }
+        List<OrganizationPo> l3 = thirdOrgListMap.getOrDefault(areaNo, new ArrayList<>());
+        for (OrganizationPo organizationPo : l3) {
+            if (unit.contains(organizationPo.getName())) {
+                return organizationPo.getId();
+            }
+        }
+        // 市区
+        if ("沧州".equals(areaName)) {
+            return "D0130911";
+        }
+        if ("唐山".equals(areaName)) {
+            return "D0130202";
+        }
+        if ("秦皇岛".equals(areaName)) {
+            return "D0130302";
+        }
+        if ("廊坊".equals(areaName)) {
+            return "D0131000";
+        }
+        if ("张家口".equals(areaName)) {
+            return "D0130701";
+        }
+        if ("邢台".equals(areaName)) {
+            return "D0130502";
+        }
+        if ("邯郸".equals(areaName)) {
+            return "D0130402";
+        }
+        if ("保定".equals(areaName)) {
+            return "D0130601";
+        }
+        if ("石家庄".equals(areaName)) {
+            return "D0130186";
+        }
+        if ("承德".equals(areaName)) {
+            return "D0130801";
+        }
+        if ("衡水".equals(areaName)) {
+            return "D0133001";
+        }
+        if ("雄安".equals(areaName)) {
+            return "D0130830";
+        }
+        // 河北省分公司
+        return "HE001";
+    }
+
+    /**
+     * 获取地图匹配的二级组织机构编码,车效大屏
+     * @param areaName 二级组织机构名称
+     * @param cityName 三级组织机构名称
+     */
+    public String getAreaNo2(String areaName, String cityName) {
+        if (!StringUtils.hasText(areaName)) {
+            return "";
+        }
+        if ("省机动局".equals(areaName) && StringUtils.hasText(cityName)) {
+            if (cityName.contains("沧州")) {
+                return "180";
+            }
+            if (cityName.contains("唐山")) {
+                return "181";
+            }
+            if (cityName.contains("秦皇岛")) {
+                return "182";
+            }
+            if (cityName.contains("廊坊")) {
+                return "183";
+            }
+            if (cityName.contains("张家口")) {
+                return "184";
+            }
+            if (cityName.contains("邢台")) {
+                return "185";
+            }
+            if (cityName.contains("邯郸")) {
+                return "186";
+            }
+            if (cityName.contains("保定")) {
+                return "187";
+            }
+            if (cityName.contains("石家庄")) {
+                return "188";
+            }
+            if (cityName.contains("承德")) {
+                return "189";
+            }
+            if (cityName.contains("衡水")) {
+                return "720";
+            }
+            if (cityName.contains("雄安")) {
+                return "782";
+            }
+        }
+        if (areaName.contains("沧州")) {
+            return "180";
+        }
+        if (areaName.contains("唐山")) {
+            return "181";
+        }
+        if (areaName.contains("秦皇岛")) {
+            return "182";
+        }
+        if (areaName.contains("廊坊")) {
+            return "183";
+        }
+        if (areaName.contains("张家口")) {
+            return "184";
+        }
+        if (areaName.contains("邢台")) {
+            return "185";
+        }
+        if (areaName.contains("邯郸")) {
+            return "186";
+        }
+        if (areaName.contains("保定")) {
+            return "187";
+        }
+        if (areaName.contains("石家庄")) {
+            return "188";
+        }
+        if (areaName.contains("承德")) {
+            return "189";
+        }
+        if (areaName.contains("衡水")) {
+            return "720";
+        }
+        if (areaName.contains("雄安")) {
+            return "782";
+        }
+        return "";
+    }
+
+    /**
+     * 获取地市id
+     * @param cities 地市列表
+     * @param unit 单位
+     */
+    public String getCityId(List<AreaPo> cities, String unit) {
+        if (!StringUtils.hasText(unit)) {
+            return "";
+        }
+        for (AreaPo city : cities) {
+            if (unit.contains(city.getShortName())) {
+                return city.getAreaId();
+            }
+        }
+        return "";
+    }
+
+    /**
+     * 获取地区名称
+     * @param areaMap 地区map
+     * @param id 地区id
+     */
+    public String getAreaName(Map<String, AreaPo> areaMap, String id) {
+        if (!StringUtils.hasText(id)) {
+            return "";
+        }
+        AreaPo areaPo = areaMap.get(id);
+        if (null != areaPo) {
+            return areaPo.getAreaName();
+        }
+        return "";
+    }
+
+    /**
+     * 获取区县id
+     * @param districtListMap 区县列表map
+     * @param cityId 地市id
+     * @param cityName 地市名称
+     * @param unit 单位
+     */
+    public String getDistrictId(Map<String, List<AreaPo>> districtListMap, String cityId, String cityName, String unit) {
+        if (!StringUtils.hasText(cityId) || !StringUtils.hasText(cityName) || !StringUtils.hasText(unit)) {
+            return "";
+        }
+        if ("石家庄".equals(cityName)) {
+            if (unit.contains("井陉矿区")) {
+                return "130107";
+            }
+            if (unit.contains("井陉")) {
+                return "130121";
+            }
+        }
+        if ("雄安".equals(cityName)) {
+            unit = unit.replace("雄安新区", "");
+        }
+        List<AreaPo> districts = districtListMap.get(cityId);
+        if (CollectionUtils.isEmpty(districts)) {
+            return "";
+        }
+        for (AreaPo district : districts) {
+            if (unit.contains(district.getShortName())) {
+                return district.getAreaId();
+            }
+        }
+        return "";
+    }
+}

+ 305 - 0
src/main/java/com/nokia/financeapi/service/car/CarWeiZhangImportService.java

@@ -0,0 +1,305 @@
+package com.nokia.financeapi.service.car;
+
+import com.nokia.financeapi.common.exception.BizException;
+import com.nokia.financeapi.common.exception.MyRuntimeException;
+import com.nokia.financeapi.common.utils.psql.PsqlUtil;
+import com.nokia.financeapi.config.DataImportConfig;
+import com.nokia.financeapi.dao.car.CarWeiZhangDao;
+import com.nokia.financeapi.dao.gdc.GdcCarWeiZhangDao;
+import com.nokia.financeapi.pojo.dto.CarDataImportDto;
+import com.nokia.financeapi.pojo.po.common.AreaPo;
+import com.nokia.financeapi.pojo.po.common.OrganizationPo;
+import com.nokia.financeapi.service.common.AreaService;
+import com.nokia.financeapi.service.common.OrganizationService;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.csv.CSVFormat;
+import org.apache.commons.csv.CSVPrinter;
+import org.apache.commons.io.FileUtils;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.DateUtil;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.StringUtils;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStreamWriter;
+import java.math.BigDecimal;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Stream;
+
+@Slf4j
+@Service
+public class CarWeiZhangImportService {
+    private final DataImportConfig dataImportConfig;
+    private final CarService carService;
+    private final OrganizationService organizationService;
+    private final AreaService areaService;
+    private final CarWeiZhangDao carWeiZhangDao;
+    private final GdcCarWeiZhangDao gdcCarWeiZhangDao;
+
+    public CarWeiZhangImportService(DataImportConfig dataImportConfig, CarService carService,
+                                    OrganizationService organizationService, AreaService areaService,
+                                    CarWeiZhangDao carWeiZhangDao, GdcCarWeiZhangDao gdcCarWeiZhangDao) {
+        this.dataImportConfig = dataImportConfig;
+        this.carService = carService;
+        this.organizationService = organizationService;
+        this.areaService = areaService;
+        this.carWeiZhangDao = carWeiZhangDao;
+        this.gdcCarWeiZhangDao = gdcCarWeiZhangDao;
+    }
+
+    @Transactional(timeout = 60, rollbackFor = Exception.class)
+    public synchronized void dataImport(CarDataImportDto dto, MultipartFile file) throws IOException {
+        boolean flag = carWeiZhangDao.hasCarWeiZhang(dto.getYearMonth());
+        if (flag) {
+            throw new BizException("该账期数据已存在");
+        }
+        Files.createDirectories(Paths.get(dataImportConfig.getWeiZhang()));
+        File fileNew = new File(dataImportConfig.getWeiZhang() + dto.getYearMonth() + ".xlsx");
+        FileUtils.copyInputStreamToFile(file.getInputStream(), fileNew);
+        Path path = fileNew.toPath();
+        List<Map<String, String>> list = readFile(path);
+        List<Map<String, String>> distinctList = dataProcessing(path, list, dto);
+        Path csvPath = toCsv(path, distinctList);
+        copyCsv(csvPath);
+        procedure(dto);
+    }
+
+    /**
+     * 读取文件
+     *
+     * @param path 文件路径
+     */
+    public List<Map<String, String>> readFile(Path path) throws IOException {
+        log.info("读取: {}", path);
+        List<String> rawHeaders = Stream.of("账期", "车牌号", "单位", "二级单位", "三级单位", "违章时间", "违章地点",
+                "违章详情", "扣分", "罚款", "处理时间", "违章未处理时长(天)", "线下实际处理状态(最终状态)").toList();
+        List<String> headers = Stream.of("year_month", "che_pai_hao", "raw_yi_ji", "raw_er_ji", "raw_san_ji",
+                "wei_zhang_shi_jian", "wei_zhang_di_dian", "wei_zhang_xiang_qing", "kou_fen", "fa_kuan",
+                "chu_li_shi_jian", "wei_zhang_wei_chu_li_shi_chang", "chu_li_zhuang_tai").toList();
+        try (InputStream inputStream = Files.newInputStream(path);
+             Workbook workbook = new XSSFWorkbook(inputStream)
+        ) {
+            List<Map<String, String>> resultList = new ArrayList<>();
+            // 读取第一个工作表
+            Sheet sheet = workbook.getSheetAt(0);
+            // 表头行
+            Row headerRow = sheet.getRow(0);
+            // 列数
+            int columnCount = headerRow.getPhysicalNumberOfCells();
+            log.info("columnCount: {}", columnCount);
+            // 检查表头
+            if (headers.size() != columnCount) {
+                throw new BizException("列数错误");
+            }
+            for (int i = 0; i < columnCount; i++) {
+                Cell cell = headerRow.getCell(i);
+                if (cell == null || !rawHeaders.get(i).equals(cell.getStringCellValue())) {
+                    throw new BizException("表头错误");
+                }
+            }
+            // 最后行数
+            int lastRowNum = sheet.getLastRowNum();
+            log.info("lastRowNum: {}", lastRowNum);
+            if (lastRowNum == 0) {
+                throw new BizException("文件为空");
+            }
+            // 遍历行
+            for (int i = 1; i <= lastRowNum; i++) {
+                Row row = sheet.getRow(i);
+                if (row == null) {
+                    continue;
+                }
+                Map<String, String> rowMap = new LinkedHashMap<>();
+                // 遍历列
+                for (int j = 0; j < columnCount; j++) {
+                    String header = headers.get(j);
+                    String cellValue = "";
+                    rowMap.put(header, cellValue);
+                    Cell cell = row.getCell(j);
+                    if (cell == null) {
+                        continue;
+                    }
+                    switch (cell.getCellType()) {
+                        case STRING:
+                            boolean skipTrim = "wei_zhang_shi_jian".equals(header) || "chu_li_shi_jian".equals(header);
+                            // 删除字符串空白字符
+                            cellValue = skipTrim ? cell.getStringCellValue()
+                                    : StringUtils.trimAllWhitespace(cell.getStringCellValue());
+                            break;
+                        case NUMERIC:
+                            if (DateUtil.isCellDateFormatted(cell)) {
+                                cellValue = DateUtil.getLocalDateTime(cell.getNumericCellValue())
+                                        .format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss"));
+                                break;
+                            }
+                            cellValue = String.valueOf(cell.getNumericCellValue());
+                            break;
+                        case BOOLEAN:
+                            cellValue = String.valueOf(cell.getBooleanCellValue());
+                            break;
+                        default:
+                            break;
+                    }
+                    rowMap.put(header, cellValue);
+                }
+                resultList.add(rowMap);
+            }
+            return resultList;
+        }
+    }
+
+    /**
+     * 数据加工
+     *
+     * @param path 文件路径
+     * @param list 数据
+     * @param dto 请求参数
+     */
+    public List<Map<String, String>> dataProcessing(Path path, List<Map<String, String>> list, CarDataImportDto dto) {
+        String yearMonth = dto.getYearMonth().toString();
+        LocalDate localDate = LocalDate.parse(yearMonth + "01", DateTimeFormatter.ofPattern("yyyyMMdd"));
+        String year = String.valueOf(localDate.getYear());
+        String month = String.valueOf(localDate.getMonthValue());
+        List<OrganizationPo> secondOrgs = organizationService.getSecondOrgs();
+        List<OrganizationPo> thirdOrgs = organizationService.getThirdOrgs();
+        Map<String, OrganizationPo> orgMap = organizationService.getOrgMap(secondOrgs, thirdOrgs);
+        Map<String, List<OrganizationPo>> thirdOrganizationListMap =
+                organizationService.getThirdOrganizationListMap(secondOrgs, thirdOrgs);
+        List<AreaPo> cities = areaService.getCities();
+        List<AreaPo> districts = areaService.getDistricts();
+        Map<String, AreaPo> areaMap = areaService.getAreaMap(cities, districts);
+        Map<String, List<AreaPo>> districtListMap = areaService.getDistrictListMap(cities, districts);
+        for (Map<String, String> map : list) {
+            map.put("year_month", yearMonth);
+            map.put("year_no", year);
+            map.put("month_no", month);
+            String rawChePaiHao = map.get("che_pai_hao");
+            map.put("raw_che_pai_hao", rawChePaiHao);
+            String chePaiHao = carService.getChePai(rawChePaiHao);
+            map.put("che_pai_hao", chePaiHao);
+            String chePaiFail = carService.chePaiFail(rawChePaiHao);
+            map.put("che_pai_fail", chePaiFail);
+            String yj = map.get("raw_yi_ji");
+            String ej = map.get("raw_er_ji");
+            String sj = map.get("raw_san_ji");
+            String firstUnit = carService.getFirstUnit(yj);
+            map.put("first_unit", firstUnit);
+            String secondUnit = carService.getSecondUnit(ej, firstUnit);
+            map.put("second_unit", secondUnit);
+            String thirdUnit = carService.getThirdUnit(sj, secondUnit);
+            map.put("third_unit", thirdUnit);
+            String areaNo = carService.getAreaNo(secondOrgs, yj);
+            map.put("area_no", areaNo);
+            String areaName = carService.getOrgName(orgMap, areaNo);
+            map.put("area_name", areaName);
+            String cityNo = carService.getCityNo(thirdOrganizationListMap, areaNo, areaName, ej);
+            map.put("city_no", cityNo);
+            String cityName = carService.getOrgName(orgMap, cityNo);
+            map.put("city_name", cityName);
+            String areaNo2 = carService.getAreaNo2(areaName, cityName);
+            map.put("area_no2", areaNo2);
+            String areaName2 = carService.getOrgName(orgMap, areaNo2);
+            map.put("area_name2", areaName2);
+            String cityId = carService.getCityId(cities, yj);
+            map.put("city_id", cityId);
+            String city = carService.getAreaName(areaMap, cityId);
+            map.put("city", city);
+            String districtId = carService.getDistrictId(districtListMap, cityId, cityName, ej);
+            map.put("district_id", districtId);
+            String district = carService.getAreaName(areaMap, districtId);
+            map.put("district", district);
+            String weiZhangShiJian = map.get("wei_zhang_shi_jian");
+            LocalDateTime weiZhangShiJianLocalDateTime = LocalDateTime.parse(weiZhangShiJian,
+                    DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
+            String weiZhangNianYue = weiZhangShiJianLocalDateTime.format(DateTimeFormatter.ofPattern("yyyyMM"));
+            map.put("wei_zhang_nian_yue", weiZhangNianYue);
+            String chuLiShiJian = map.get("chu_li_shi_jian");
+            String chuLiNianYue = "";
+            if (StringUtils.hasText(chuLiShiJian)) {
+                LocalDateTime chuLiShiJianLocalDateTime = LocalDateTime.parse(chuLiShiJian,
+                        DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
+                chuLiNianYue = chuLiShiJianLocalDateTime.format(DateTimeFormatter.ofPattern("yyyyMM"));
+            }
+            map.put("chu_li_nian_yue", chuLiNianYue);
+            map.put("source", path.getFileName().toString());
+            String kouFen = map.get("kou_fen");
+            if (StringUtils.hasText(kouFen)) {
+                map.put("kou_fen", String.valueOf(new BigDecimal(kouFen).intValue()));
+            }
+            String weiZhangWeiChuLiShiChang = map.get("wei_zhang_wei_chu_li_shi_chang");
+            if (StringUtils.hasText(weiZhangWeiChuLiShiChang)) {
+                map.put("wei_zhang_wei_chu_li_shi_chang",
+                        String.valueOf(new BigDecimal(weiZhangWeiChuLiShiChang).intValue()));
+            }
+        }
+        return list;
+    }
+
+    /**
+     * 生成csv
+     *
+     * @param path 源文件路径
+     * @param list 数据
+     */
+    public Path toCsv(Path path, List<Map<String, String>> list) throws IOException {
+        log.info("条数:{}", list.size());
+        Path csvPath = Paths.get(path + ".csv");
+        try (OutputStreamWriter osw = new OutputStreamWriter(Files.newOutputStream(csvPath),
+                StandardCharsets.UTF_8);
+             CSVPrinter printer = new CSVPrinter(osw, CSVFormat.DEFAULT)) {
+            // 添加bom头避免excel乱码
+            osw.write('\ufeff');
+            Map<String, String> header = list.get(0);
+            // 表头
+            printer.printRecord(header.keySet());
+            for (Map<String, String> map : list) {
+                printer.printRecord(map.values());
+            }
+        }
+        return csvPath;
+    }
+
+    /**
+     * 导入数据库
+     *
+     * @param path 文件路径
+     */
+    public void copyCsv(Path path) {
+        String dbTable = "car.car_wei_zhang";
+        String csv = path.toString();
+        String columns = "(year_month,che_pai_hao,raw_yi_ji,raw_er_ji,raw_san_ji,wei_zhang_shi_jian,wei_zhang_di_dian,wei_zhang_xiang_qing,kou_fen,fa_kuan,chu_li_shi_jian,wei_zhang_wei_chu_li_shi_chang,chu_li_zhuang_tai,year_no,month_no,raw_che_pai_hao,che_pai_fail,first_unit,second_unit,third_unit,area_no,area_name,city_no,city_name,area_no2,area_name2,city_id,city,district_id,district,wei_zhang_nian_yue,chu_li_nian_yue,source)";
+        Long timeout = 60000L;
+        PsqlUtil.copyCsv(dataImportConfig.getCopyScriptPath(), dataImportConfig.getDbHost(),
+                dataImportConfig.getDbPort(), dataImportConfig.getDbUsername(), dataImportConfig.getDbPassword(),
+                dataImportConfig.getDbName(), dbTable, csv, columns, timeout, null);
+    }
+
+    /**
+     * 更新数据库
+     */
+    public void procedure(CarDataImportDto dto) {
+        carWeiZhangDao.insertCarWeiZhangChangQi(dto.getYearMonth());
+        int update1 = gdcCarWeiZhangDao.insertCarWeiZhang(dto.getYearMonth());
+        if (update1 == 0) {
+            throw new MyRuntimeException("插入car_theme.wz_f_violation_details失败");
+        }
+    }
+}

+ 1 - 0
src/main/java/com/nokia/financeapi/utils/AESUtil.java

@@ -112,5 +112,6 @@ public class AESUtil {
         //解密
         System.out.println(AESUtil.decrypt(encrypt,"2na$$PdV9AW8b#CS"));
         System.out.println(AESUtil.decrypt("zoQtYlmhk/add/mBUBZD5mFJB1IXEwaLRS97Uf9z9Hlqdh/UHio66b35GOo/eEziALCI/P8MJ/NfqMqiE5mpkHr0/add/309c0RoCYGzuwK1F7OYCOaxqUB83JYaeV6eJIWkvJpp","2na$$PdV9AW8b#CS"));
+        System.out.println(getTestToken());
     }
 }

+ 5 - 2
src/main/resources/application-dev.yml

@@ -6,9 +6,12 @@ spring:
 #    url: jdbc:postgresql://172.16.107.5:5432/financialdb
 #    username: finance
 #    password: Finance@unicom23
-    url: jdbc:postgresql://192.168.50.3:15432/financialdb
+#    url: jdbc:postgresql://192.168.50.3:15432/financialdb
+#    username: postgres
+#    password: NFQCgBA6YhNvgAqG6THw
+    url: jdbc:postgresql://127.0.0.1:5432/financialdb
     username: postgres
-    password: NFQCgBA6YhNvgAqG6THw
+    password: Test!234
 logging:
   level:
     com:

+ 0 - 24
src/test/java/com/nokia/financeapi/FinanceApiApplicationTests.java

@@ -1,33 +1,9 @@
 package com.nokia.financeapi;
 
-import com.nokia.financeapi.dao.house.HouseResourceMapMapper;
-import org.junit.jupiter.api.Test;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.test.context.ActiveProfiles;
-import org.springframework.util.StringUtils;
-
-import java.time.LocalDate;
-import java.time.format.DateTimeFormatter;
 
 @ActiveProfiles("test")
 @SpringBootTest
 class FinanceApiApplicationTests {
-
-    @Autowired
-    JdbcTemplate jdbcTemplate;
-    @Autowired
-    HouseResourceMapMapper houseResourceMapMapper;
-
-    @Test
-    void contextLoads() {
-        System.out.println(houseResourceMapMapper.getBuildingMonthMaxDate());
-    }
-
-    public static void main(String[] args) {
-        System.out.println(LocalDate.parse("20230901", DateTimeFormatter.ofPattern("yyyyMMdd")));
-        System.out.println(LocalDate.now().withDayOfYear(1));
-        System.out.println(StringUtils.delete("石家庄市", "市"));
-    }
 }