Prechádzať zdrojové kódy

游客导出增加年龄和身份

jinch 1 týždeň pred
rodič
commit
33dd748964

+ 3 - 0
ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/controller/admin/order/vo/order/TouristExportVisitorVO.java

@@ -90,6 +90,9 @@ public class TouristExportVisitorVO {
     @Schema(description = "手机号")
     private String mobile;
 
+    @Schema(description = "年龄")
+    private String age;
+
     @Schema(description = "楼层")
     private String floor;
 

+ 3 - 0
ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/service/otc/impl/OtcTradeOrderServiceImpl.java

@@ -104,6 +104,7 @@ import com.yc.ship.module.trade.framework.annotation.TradeOrderLog;
 import com.yc.ship.module.trade.framework.common.ConfigUtils;
 import com.yc.ship.module.trade.framework.common.ThreadLocalUtil;
 import com.yc.ship.module.trade.framework.common.TradeOrderLogUtils;
+import com.yc.ship.module.trade.utils.IdCardProvinceUtil;
 import com.yc.ship.module.trade.framework.mq.TradePublishUtils;
 import com.yc.ship.module.trade.service.invoice.InvoiceService;
 import com.yc.ship.module.trade.service.order.TradeOrderRepositoryService;
@@ -3208,7 +3209,9 @@ public class OtcTradeOrderServiceImpl implements OtcTradeOrderService {
                         item.put("valueAddedService", StringUtils.isEmpty(visitor.getValueAddedService()) ? "" : formatPolicyName(visitor.getValueAddedService())); // 增值服务(如:接送站、保险等)
                         item.put("policyName", ""); // 优惠政策(如:早鸟优惠、团立减等)
                         item.put("remark", StringUtils.isEmpty(visitor.getRemark()) ? "" : visitor.getRemark()); // 备注信息
+                        item.put("age", StringUtils.isEmpty(visitor.getAge()) ? "" : visitor.getAge()); // 年龄
 
+                        item.put("province", IdCardProvinceUtil.getProvinceName(visitor.getCredentialType(), visitor.getCredentialNo(), visitor.getNationalityName())); // 省份(根据身份证号解析)
 
                         item.put("visitorHome", StringUtils.isEmpty(visitor.getVisitorType()) ? "" : DictFrameworkUtils.getDictDataLabel(DictTypeConstants.TOUR_TYPE, visitor.getVisitorType())); // 游客入住类型
 

+ 206 - 0
ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/utils/IdCardProvinceUtil.java

@@ -0,0 +1,206 @@
+package com.yc.ship.module.trade.utils;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 证件号码解析工具类 - 根据证件号前2位省份码解析省份名称
+ * <p>
+ * 中国大陆身份证号码前6位为行政区划码,其中前2位代表省份。
+ * 港澳台居民居住证同样采用18位编码格式,前2位为大陆居住地省份码。
+ * 护照、通行证、回乡证、台胞证、户口本等证件号码格式不同,无法解析省份。
+ * </p>
+ * <p>
+ * 支持解析的证件类型:
+ *   1  - 身份证(18位/15位)
+ *   11 - 港澳台居民居住证(18位,编码规则同身份证)
+ * </p>
+ * <p>
+ * 不支持解析的证件类型(返回空字符串):
+ *   3  - 港澳台通行证
+ *   4  - 护照
+ *   5  - 港澳通行证
+ *   6  - 台湾通行证
+ *   7  - 回乡证(港澳居民来往内地通行证)
+ *   8  - 台胞证(台湾居民来往大陆通行证)
+ *   10 - 户口本
+ * </p>
+ *
+ * @Author: AI
+ * @Date: 2026/05/06
+ */
+public class IdCardProvinceUtil {
+
+    // ==================== 证件类型常量 ====================
+    /** 身份证 */
+    public static final int CREDENTIAL_TYPE_ID_CARD = 0;
+    /** 港澳台通行证 */
+    public static final int CREDENTIAL_TYPE_HK_MACAO_TAIWAN_PASS = 3;
+    /** 护照 */
+    public static final int CREDENTIAL_TYPE_PASSPORT = 1;
+    /** 港澳通行证 */
+    public static final int CREDENTIAL_TYPE_HK_MACAO_PASS = 5;
+    /** 台湾通行证 */
+    public static final int CREDENTIAL_TYPE_TAIWAN_PASS = 6;
+    /** 回乡证(港澳居民来往内地通行证) */
+    public static final int CREDENTIAL_TYPE_HOME_RETURN_PERMIT = 7;
+    /** 台胞证(台湾居民来往大陆通行证) */
+    public static final int CREDENTIAL_TYPE_TAIWAN_COMPATRIOT_PERMIT = 8;
+    /** 户口本 */
+    public static final int CREDENTIAL_TYPE_HOUSEHOLD_REGISTER = 10;
+    /** 港澳台居民居住证 */
+    public static final int CREDENTIAL_TYPE_HK_MACAO_TAIWAN_RESIDENCE_PERMIT = 11;
+
+    // ==================== 校验常量 ====================
+    /** 身份证号码长度(18位) */
+    private static final int ID_CARD_LENGTH_18 = 18;
+    /** 旧身份证号码长度(15位) */
+    private static final int ID_CARD_LENGTH_15 = 15;
+    /** 省份码长度(前2位) */
+    private static final int PROVINCE_CODE_LENGTH = 2;
+    /** 18位身份证号末位校验位可能为X */
+    private static final char ID_CARD_X = 'X';
+    private static final char ID_CARD_X_LOWER = 'x';
+
+    /**
+     * 可以解析省份的证件类型集合(号码格式与身份证相同,采用18位行政区划编码)
+     * 身份证(1) 和 港澳台居民居住证(11) 均采用GB 11643编码规则
+     */
+    private static final Set<Integer> PARSEABLE_CREDENTIAL_TYPES = Collections.unmodifiableSet(
+            new HashSet<>(Arrays.asList(CREDENTIAL_TYPE_ID_CARD, CREDENTIAL_TYPE_HK_MACAO_TAIWAN_RESIDENCE_PERMIT))
+    );
+
+    /** 省份码 -> 省份名称 映射表(不可变,基于GB/T 2260行政区划代码前2位) */
+    private static final Map<String, String> PROVINCE_MAP;
+
+    static {
+        Map<String, String> map = new HashMap<>(40);
+        map.put("11", "北京");
+        map.put("12", "天津");
+        map.put("13", "河北");
+        map.put("14", "山西");
+        map.put("15", "内蒙古");
+        map.put("21", "辽宁");
+        map.put("22", "吉林");
+        map.put("23", "黑龙江");
+        map.put("31", "上海");
+        map.put("32", "江苏");
+        map.put("33", "浙江");
+        map.put("34", "安徽");
+        map.put("35", "福建");
+        map.put("36", "江西");
+        map.put("37", "山东");
+        map.put("41", "河南");
+        map.put("42", "湖北");
+        map.put("43", "湖南");
+        map.put("44", "广东");
+        map.put("45", "广西");
+        map.put("46", "海南");
+        map.put("50", "重庆");
+        map.put("51", "四川");
+        map.put("52", "贵州");
+        map.put("53", "云南");
+        map.put("54", "西藏");
+        map.put("61", "陕西");
+        map.put("62", "甘肃");
+        map.put("63", "青海");
+        map.put("64", "宁夏");
+        map.put("65", "新疆");
+        map.put("71", "台湾");
+        map.put("81", "香港");
+        map.put("82", "澳门");
+        // 91开头的为国外身份证件,无对应省份
+        PROVINCE_MAP = Collections.unmodifiableMap(map);
+    }
+
+    /**
+     * 根据证件类型和证件号解析省份名称
+     * <p>
+     * 身份证(1)和港澳台居民居住证(11):通过号码前2位省份码解析;
+     * 台湾通行证(6)和台胞证(8):直接返回"台湾";
+     * 其他证件类型(护照、港澳通行证等):返回空字符串。
+     * </p>
+     *
+     * @param credentialType 证件类型
+     * @param credentialNo   证件号码
+     * @return 省份名称,无法解析时返回空字符串
+     */
+    public static String getProvinceName(Integer credentialType, String credentialNo, String  nationalityName) {
+        if (credentialType == null) {
+            return "";
+        }
+        // 台湾通行证、台胞证 → 直接返回"台湾"
+        if (credentialType == CREDENTIAL_TYPE_TAIWAN_PASS
+                || credentialType == CREDENTIAL_TYPE_TAIWAN_COMPATRIOT_PERMIT) {
+            return "台湾";
+        }
+
+        if (nationalityName.contains("香港")) {
+            return "香港";
+        }
+
+        if (nationalityName.contains("澳门")) {
+            return "澳门";
+        }
+        // 非可解析的证件类型,直接返回空
+        if (!PARSEABLE_CREDENTIAL_TYPES.contains(credentialType)) {
+            return "";
+        }
+        // 证件号为空或长度不足,无法解析
+        if (credentialNo == null || credentialNo.trim().length() < PROVINCE_CODE_LENGTH) {
+            return "";
+        }
+        String trimmedNo = credentialNo.trim();
+        // 校验号码格式
+        if (!isValidIdCardFormat(trimmedNo)) {
+            return "";
+        }
+        // 提取省份码(前2位)并查表
+        String provinceCode = trimmedNo.substring(0, PROVINCE_CODE_LENGTH);
+        return PROVINCE_MAP.getOrDefault(provinceCode, "");
+    }
+
+    /**
+     * 校验号码是否符合身份证编码格式
+     * <p>
+     * 18位:前17位必须为数字,第18位可为数字或X/x
+     * 15位:全部必须为数字
+     * </p>
+     *
+     * @param idCardNo 证件号码(已trim)
+     * @return 格式是否合法
+     */
+    private static boolean isValidIdCardFormat(String idCardNo) {
+        int len = idCardNo.length();
+        if (len == ID_CARD_LENGTH_18) {
+            // 前17位必须为数字
+            String first17 = idCardNo.substring(0, 17);
+            if (!isNumeric(first17)) {
+                return false;
+            }
+            // 第18位可为数字或X/x
+            char lastChar = idCardNo.charAt(17);
+            return Character.isDigit(lastChar) || lastChar == ID_CARD_X || lastChar == ID_CARD_X_LOWER;
+        } else if (len == ID_CARD_LENGTH_15) {
+            // 15位旧身份证:全部为数字
+            return isNumeric(idCardNo);
+        }
+        return false;
+    }
+
+    /**
+     * 判断字符串是否全部由数字组成
+     */
+    private static boolean isNumeric(String str) {
+        for (int i = 0, len = str.length(); i < len; i++) {
+            if (!Character.isDigit(str.charAt(i))) {
+                return false;
+            }
+        }
+        return true;
+    }
+}

+ 1 - 0
ship-module-trade/ship-module-trade-biz/src/main/resources/mapper/order/TradeVisitorMapper.xml

@@ -275,6 +275,7 @@
             tv.credential_type AS credentialType,
             tv.credential_no AS credentialNo,
             tv.type AS visitorType,
+            tv.age,
             torm.floor as floor,
             a.name AS nationalityName,
             GROUP_CONCAT(ps.product_name) AS valueAddedService,