This commit is contained in:
2026-07-21 14:23:05 +08:00
parent 4794be896c
commit b966c2ec4b
9 changed files with 672 additions and 0 deletions

View File

@@ -255,6 +255,8 @@ func registerAllProcessors(combService *comb.CombService) {
"QCXGIJY3": qcxg.ProcessQCXGIJY3Request, // 名下车辆诺尔
"QCXG6U5G": qcxg.ProcessQCXG6U5GRequest, // 车辆核验
"QCXG7K2N": qcxg.ProcessQCXG7K2NRequest, // 人车核验加强版
"QCXGH3R8": qcxg.ProcessQCXGH3R8Request, // 车辆注册日期查询VIN
"QCXGK5M2": qcxg.ProcessQCXGK5M2Request, // 车辆投保日期与城市查询VIN
// DWBG系列处理器 - 多维报告

View File

@@ -302,6 +302,8 @@ func (s *FormConfigServiceImpl) getDTOStruct(ctx context.Context, apiCode string
"FLXGC4CT": &dto.FLXGC4CTReq{}, //汇博-个人涉诉
"QYGLLUCM": &dto.QYGLLUCMReq{}, //汇博-企业涉诉
"QCXGA8V3": &dto.QCXGA8V3Req{}, //全国车辆配置查验(车五项+使用性质+承保)
"QCXGH3R8": &dto.QCXGH3R8Req{}, //车辆注册日期查询VIN
"QCXGK5M2": &dto.QCXGK5M2Req{}, //车辆投保日期与城市查询VIN
"QCXGCP77": &dto.QCXGCP77Req{}, //全国车辆配置查验(车辆详情)
"QCXGX2X6": &dto.QCXGX2X6Req{}, //行驶证信息核验V2
"QCXG1S2L": &dto.QCXG1S2LReq{}, //汽车车辆五项

View File

@@ -0,0 +1,67 @@
package qcxg
import (
"context"
"encoding/json"
"errors"
"fmt"
"hyapi-server/internal/domains/api/dto"
"hyapi-server/internal/domains/api/services/processors"
)
// a8v3RawResult QCXGA8V3 上游原始字段
type a8v3RawResult struct {
Usage string
RegisterDate string
InsuranceMonth string
VehicleModel string
Brand string
PlateNo string
EngineNo string
Vin string
}
func callA8V3ByVin(ctx context.Context, vinCode string, deps *processors.ProcessorDependencies) (*a8v3RawResult, error) {
params, err := json.Marshal(dto.QCXGA8V3Req{VinCode: vinCode})
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
respBytes, err := ProcessQCXGA8V3Request(ctx, params, deps)
if err != nil {
return nil, err
}
return parseA8V3Result(respBytes)
}
func parseA8V3Result(respBytes []byte) (*a8v3RawResult, error) {
var raw map[string]interface{}
if err := json.Unmarshal(respBytes, &raw); err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
return &a8v3RawResult{
Usage: asString(raw["usage"]),
RegisterDate: asString(raw["registerDate"]),
InsuranceMonth: asString(raw["insuranceMonth"]),
VehicleModel: asString(raw["clxh"]),
Brand: asString(raw["brand"]),
PlateNo: asString(raw["cph"]),
EngineNo: asString(raw["fdjh"]),
Vin: asString(raw["vin"]),
}, nil
}
func asString(v interface{}) string {
if v == nil {
return ""
}
switch t := v.(type) {
case string:
return t
default:
return fmt.Sprint(t)
}
}

View File

@@ -0,0 +1,43 @@
package qcxg
import (
_ "embed"
"encoding/json"
"strings"
"unicode/utf8"
)
//go:embed data/car_plate.json
var carPlateJSON []byte
var carPlateCityMap map[string]string
func init() {
carPlateCityMap = make(map[string]string)
_ = json.Unmarshal(carPlateJSON, &carPlateCityMap)
}
// lookupCityByPlateNo 根据车牌号前缀(省简称+字母)查城市,未匹配返回 "-"
func lookupCityByPlateNo(plateNo string) string {
prefix := extractPlatePrefix(plateNo)
if prefix == "" {
return "-"
}
if city, ok := carPlateCityMap[prefix]; ok && city != "" {
return city
}
return "-"
}
// extractPlatePrefix 取车牌前两个字符作为字典键,如 "鲁A12345" -> "鲁A"
func extractPlatePrefix(plateNo string) string {
plateNo = strings.TrimSpace(plateNo)
if plateNo == "" {
return ""
}
if utf8.RuneCountInString(plateNo) < 2 {
return ""
}
runes := []rune(plateNo)
return string(runes[:2])
}

View File

@@ -0,0 +1,59 @@
package qcxg
import "testing"
func TestLookupCityByPlateNo(t *testing.T) {
tests := []struct {
name string
plateNo string
want string
}{
{name: "济南", plateNo: "鲁A12345", want: "济南市"},
{name: "青岛增补", plateNo: "鲁U88888", want: "青岛市(增补号段)"},
{name: "北京", plateNo: "京A00001", want: "北京市"},
{name: "空车牌", plateNo: "", want: "-"},
{name: "单字", plateNo: "鲁", want: "-"},
{name: "未知前缀", plateNo: "XX12345", want: "-"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := lookupCityByPlateNo(tt.plateNo); got != tt.want {
t.Fatalf("lookupCityByPlateNo(%q) = %q, want %q", tt.plateNo, got, tt.want)
}
})
}
}
func TestParseA8V3Result(t *testing.T) {
raw := []byte(`{
"fdjh": "UJ91220946",
"clxh": "LZW1029PYA",
"usage": "非营业",
"insuranceMonth": "2025-02",
"cph": "鲁A12345",
"vin": "LZABCDEF00",
"brand": "五菱牌",
"registerDate": "2018-12"
}`)
got, err := parseA8V3Result(raw)
if err != nil {
t.Fatalf("parseA8V3Result error: %v", err)
}
if got.RegisterDate != "2018-12" {
t.Fatalf("RegisterDate = %q", got.RegisterDate)
}
if got.InsuranceMonth != "2025-02" {
t.Fatalf("InsuranceMonth = %q", got.InsuranceMonth)
}
if got.PlateNo != "鲁A12345" {
t.Fatalf("PlateNo = %q", got.PlateNo)
}
if got.VehicleModel != "LZW1029PYA" {
t.Fatalf("VehicleModel = %q", got.VehicleModel)
}
if got.EngineNo != "UJ91220946" {
t.Fatalf("EngineNo = %q", got.EngineNo)
}
}

View File

@@ -0,0 +1,420 @@
{
"京A": "北京市",
"京B": "北京市(出租车)",
"京C": "北京市",
"京E": "北京市",
"京F": "北京市",
"京G": "北京市(远郊区县)",
"京H": "北京市",
"京J": "北京市",
"京K": "北京市",
"京L": "北京市",
"京M": "北京市",
"京N": "北京市",
"京P": "北京市",
"京Q": "北京市",
"京Y": "北京市(远郊区县)",
"津A": "天津市",
"津B": "天津市",
"津C": "天津市",
"津D": "天津市",
"津E": "天津市(出租车)",
"津F": "天津市",
"津G": "天津市",
"津H": "天津市",
"津J": "天津市",
"津K": "天津市",
"津L": "天津市",
"津M": "天津市",
"津N": "天津市",
"津P": "天津市",
"津Q": "天津市",
"津R": "天津市",
"沪A": "上海市",
"沪B": "上海市",
"沪C": "上海市(远郊区县,禁入外环内)",
"沪D": "上海市",
"沪E": "上海市",
"沪F": "上海市",
"沪G": "上海市",
"沪H": "上海市",
"沪J": "上海市",
"沪K": "上海市",
"沪L": "上海市",
"沪M": "上海市",
"沪N": "上海市",
"沪R": "上海市(崇明、长兴、横沙)",
"渝A": "重庆市(主城九区)",
"渝B": "重庆市(主城区增补)",
"渝C": "重庆市(永川区、江津区、合川区、潼南区、铜梁区、璧山区、大足区、綦江区、荣昌区)",
"渝D": "重庆市(主城区增补)",
"渝F": "重庆市(万州区、开州区、梁平区、城口县、巫山县、巫溪县、忠县、云阳县、奉节县)",
"渝G": "重庆市(涪陵区、南川区、垫江县、丰都县、武隆区)",
"渝H": "重庆市(黔江区、石柱土家族自治县、秀山土家族苗族自治县、酉阳土家族苗族自治县、彭水苗族土家族自治县)",
"冀A": "石家庄市",
"冀B": "唐山市",
"冀C": "秦皇岛市",
"冀D": "邯郸市",
"冀E": "邢台市",
"冀F": "保定市",
"冀G": "张家口市",
"冀H": "承德市",
"冀J": "沧州市",
"冀R": "廊坊市",
"冀T": "衡水市",
"晋A": "太原市",
"晋B": "大同市",
"晋C": "阳泉市",
"晋D": "长治市",
"晋E": "晋城市",
"晋F": "朔州市",
"晋H": "忻州市",
"晋J": "吕梁市",
"晋K": "晋中市",
"晋L": "临汾市",
"晋M": "运城市",
"蒙A": "呼和浩特市",
"蒙B": "包头市",
"蒙C": "乌海市",
"蒙D": "赤峰市",
"蒙E": "呼伦贝尔市",
"蒙F": "兴安盟",
"蒙G": "通辽市",
"蒙H": "锡林郭勒盟",
"蒙J": "乌兰察布市",
"蒙K": "鄂尔多斯市",
"蒙L": "巴彦淖尔市",
"蒙M": "阿拉善盟",
"辽A": "沈阳市",
"辽B": "大连市",
"辽C": "鞍山市",
"辽D": "抚顺市",
"辽E": "本溪市",
"辽F": "丹东市",
"辽G": "锦州市",
"辽H": "营口市",
"辽J": "阜新市",
"辽K": "辽阳市",
"辽L": "盘锦市",
"辽M": "铁岭市",
"辽N": "朝阳市",
"辽P": "葫芦岛市",
"吉A": "长春市",
"吉B": "吉林市",
"吉C": "四平市",
"吉D": "辽源市",
"吉E": "通化市",
"吉F": "白山市",
"吉G": "白城市",
"吉H": "延边朝鲜族自治州",
"吉J": "松原市",
"吉K": "长白山保护开发区",
"黑A": "哈尔滨市",
"黑B": "齐齐哈尔市",
"黑C": "牡丹江市",
"黑D": "佳木斯市",
"黑E": "大庆市",
"黑F": "伊春市",
"黑G": "鸡西市",
"黑H": "鹤岗市",
"黑J": "双鸭山市",
"黑K": "七台河市",
"黑L": "哈尔滨市(原松花江地区)",
"黑M": "绥化市",
"黑N": "黑河市",
"黑P": "大兴安岭地区",
"黑R": "黑龙江省农垦系统",
"苏A": "南京市",
"苏B": "无锡市",
"苏C": "徐州市",
"苏D": "常州市",
"苏E": "苏州市",
"苏U": "苏州市(增补号段)",
"苏F": "南通市",
"苏G": "连云港市",
"苏H": "淮安市",
"苏J": "盐城市",
"苏K": "扬州市",
"苏L": "镇江市",
"苏M": "泰州市",
"苏N": "宿迁市",
"浙A": "杭州市",
"浙M": "杭州市(增补号段)",
"浙B": "宁波市",
"浙C": "温州市",
"浙D": "绍兴市",
"浙E": "湖州市",
"浙F": "嘉兴市",
"浙G": "金华市",
"浙H": "衢州市",
"浙J": "台州市",
"浙K": "丽水市",
"浙L": "舟山市",
"皖A": "合肥市",
"皖B": "芜湖市",
"皖C": "蚌埠市",
"皖D": "淮南市",
"皖E": "马鞍山市",
"皖F": "淮北市",
"皖G": "铜陵市",
"皖H": "安庆市",
"皖J": "黄山市",
"皖K": "阜阳市",
"皖L": "宿州市",
"皖M": "滁州市",
"皖N": "六安市",
"皖P": "宣城市",
"皖Q": "巢湖市(已撤销,存量继续使用)",
"皖R": "池州市",
"皖S": "亳州市",
"闽A": "福州市",
"闽B": "莆田市",
"闽C": "泉州市",
"闽D": "厦门市",
"闽E": "漳州市",
"闽F": "龙岩市",
"闽G": "三明市",
"闽H": "南平市",
"闽J": "宁德市",
"闽K": "平潭综合实验区",
"赣A": "南昌市",
"赣M": "南昌市(省直系统)",
"赣B": "赣州市",
"赣C": "宜春市",
"赣D": "吉安市",
"赣E": "上饶市",
"赣F": "抚州市",
"赣G": "九江市",
"赣H": "景德镇市",
"赣J": "萍乡市",
"赣K": "新余市",
"赣L": "鹰潭市",
"鲁A": "济南市",
"鲁S": "济南市(原莱芜市,存量继续使用)",
"鲁B": "青岛市",
"鲁U": "青岛市(增补号段)",
"鲁C": "淄博市",
"鲁D": "枣庄市",
"鲁E": "东营市",
"鲁F": "烟台市",
"鲁Y": "烟台市(增补号段)",
"鲁G": "潍坊市",
"鲁V": "潍坊市(增补号段)",
"鲁H": "济宁市",
"鲁J": "泰安市",
"鲁K": "威海市",
"鲁L": "日照市",
"鲁M": "滨州市",
"鲁N": "德州市",
"鲁P": "聊城市",
"鲁Q": "临沂市",
"鲁R": "菏泽市",
"豫A": "郑州市",
"豫V": "郑州市(增补号段)",
"豫B": "开封市",
"豫C": "洛阳市",
"豫D": "平顶山市",
"豫E": "安阳市",
"豫F": "鹤壁市",
"豫G": "新乡市",
"豫H": "焦作市",
"豫J": "濮阳市",
"豫K": "许昌市",
"豫L": "漯河市",
"豫M": "三门峡市",
"豫N": "商丘市",
"豫P": "周口市",
"豫Q": "驻马店市",
"豫R": "南阳市",
"豫S": "信阳市",
"豫U": "济源市",
"鄂A": "武汉市",
"鄂W": "武汉市(增补号段)",
"鄂B": "黄石市",
"鄂C": "十堰市",
"鄂D": "荆州市",
"鄂E": "宜昌市",
"鄂F": "襄阳市",
"鄂G": "鄂州市",
"鄂H": "荆门市",
"鄂J": "黄冈市",
"鄂K": "孝感市",
"鄂L": "咸宁市",
"鄂M": "仙桃市",
"鄂N": "潜江市",
"鄂P": "神农架林区",
"鄂Q": "恩施土家族苗族自治州",
"鄂R": "天门市",
"鄂S": "随州市",
"湘A": "长沙市",
"湘B": "株洲市",
"湘C": "湘潭市",
"湘D": "衡阳市",
"湘E": "邵阳市",
"湘F": "岳阳市",
"湘G": "张家界市",
"湘H": "益阳市",
"湘J": "常德市",
"湘K": "娄底市",
"湘L": "郴州市",
"湘M": "永州市",
"湘N": "怀化市",
"湘U": "湘西土家族苗族自治州",
"粤A": "广州市",
"粤B": "深圳市",
"粤C": "珠海市",
"粤D": "汕头市",
"粤E": "佛山市",
"粤X": "佛山市(顺德区,存量继续使用)",
"粤Y": "佛山市(南海区,存量继续使用)",
"粤F": "韶关市",
"粤G": "湛江市",
"粤H": "肇庆市",
"粤J": "江门市",
"粤K": "茂名市",
"粤L": "惠州市",
"粤M": "梅州市",
"粤N": "汕尾市",
"粤P": "河源市",
"粤Q": "阳江市",
"粤R": "清远市",
"粤S": "东莞市",
"粤T": "中山市",
"粤U": "潮州市",
"粤V": "揭阳市",
"粤W": "云浮市",
"桂A": "南宁市",
"桂B": "柳州市",
"桂C": "桂林市",
"桂H": "桂林市(原桂林地区)",
"桂D": "梧州市",
"桂E": "北海市",
"桂F": "崇左市",
"桂G": "来宾市",
"桂J": "贺州市",
"桂K": "玉林市",
"桂L": "百色市",
"桂M": "河池市",
"桂N": "钦州市",
"桂P": "防城港市",
"桂R": "贵港市",
"琼A": "海口市",
"琼B": "三亚市",
"琼C": "琼北地区(文昌市、琼海市、万宁市、定安县、屯昌县、澄迈县、临高县)",
"琼D": "琼南地区(五指山市、东方市、白沙黎族自治县、昌江黎族自治县、乐东黎族自治县、陵水黎族自治县、保亭黎族苗族自治县、琼中黎族苗族自治县)",
"琼E": "洋浦经济开发区",
"琼F": "儋州市",
"川A": "成都市",
"川G": "成都市(增补号段)",
"川B": "绵阳市",
"川C": "自贡市",
"川D": "攀枝花市",
"川E": "泸州市",
"川F": "德阳市",
"川H": "广元市",
"川J": "遂宁市",
"川K": "内江市",
"川L": "乐山市",
"川M": "资阳市",
"川Q": "宜宾市",
"川R": "南充市",
"川S": "达州市",
"川T": "雅安市",
"川U": "阿坝藏族羌族自治州",
"川V": "甘孜藏族自治州",
"川W": "凉山彝族自治州",
"川X": "广安市",
"川Y": "巴中市",
"川Z": "眉山市",
"贵A": "贵阳市",
"贵B": "六盘水市",
"贵C": "遵义市",
"贵D": "铜仁市",
"贵E": "黔西南布依族苗族自治州",
"贵F": "毕节市",
"贵G": "安顺市",
"贵H": "黔东南苗族侗族自治州",
"贵J": "黔南布依族苗族自治州",
"云A": "昆明市",
"云B": "东川市(已撤销,存量继续使用)",
"云C": "昭通市",
"云D": "曲靖市",
"云E": "楚雄彝族自治州",
"云F": "玉溪市",
"云G": "红河哈尼族彝族自治州",
"云H": "文山壮族苗族自治州",
"云J": "普洱市",
"云K": "西双版纳傣族自治州",
"云L": "大理白族自治州",
"云M": "保山市",
"云N": "德宏傣族景颇族自治州",
"云P": "丽江市",
"云Q": "怒江傈僳族自治州",
"云R": "迪庆藏族自治州",
"云S": "临沧市",
"藏A": "拉萨市",
"藏B": "昌都市",
"藏C": "山南市",
"藏D": "日喀则市",
"藏E": "那曲市",
"藏F": "阿里地区",
"藏G": "林芝市",
"藏H": "驻四川省天全县车辆管理所",
"藏J": "驻青海省格尔木市车辆管理所",
"陕A": "西安市",
"陕U": "西安市(增补号段)",
"陕B": "铜川市",
"陕C": "宝鸡市",
"陕D": "咸阳市",
"陕E": "渭南市",
"陕F": "汉中市",
"陕G": "安康市",
"陕H": "商洛市",
"陕J": "延安市",
"陕K": "榆林市",
"陕V": "杨凌农业高新技术产业示范区",
"甘A": "兰州市",
"甘B": "嘉峪关市",
"甘C": "金昌市",
"甘D": "白银市",
"甘E": "天水市",
"甘F": "酒泉市",
"甘G": "张掖市",
"甘H": "武威市",
"甘J": "定西市",
"甘K": "陇南市",
"甘L": "平凉市",
"甘M": "庆阳市",
"甘N": "临夏回族自治州",
"甘P": "甘南藏族自治州",
"青A": "西宁市",
"青B": "海东市",
"青C": "海北藏族自治州",
"青D": "黄南藏族自治州",
"青E": "海南藏族自治州",
"青F": "果洛藏族自治州",
"青G": "玉树藏族自治州",
"青H": "海西蒙古族藏族自治州",
"宁A": "银川市",
"宁B": "石嘴山市",
"宁C": "吴忠市",
"宁D": "固原市",
"宁E": "中卫市",
"新A": "乌鲁木齐市",
"新B": "昌吉回族自治州",
"新C": "石河子市",
"新D": "奎屯市",
"新E": "博尔塔拉蒙古自治州",
"新F": "伊犁哈萨克自治州",
"新G": "塔城地区",
"新H": "阿勒泰地区",
"新J": "克拉玛依市",
"新K": "吐鲁番市",
"新L": "哈密市",
"新M": "巴音郭楞蒙古自治州",
"新N": "阿克苏地区",
"新P": "克孜勒苏柯尔克孜自治州",
"新Q": "喀什地区",
"新R": "和田地区",
"新S": "昆玉市"
}

View File

@@ -0,0 +1,36 @@
package qcxg
import (
"context"
"encoding/json"
"errors"
"hyapi-server/internal/domains/api/dto"
"hyapi-server/internal/domains/api/services/processors"
)
// ProcessQCXGH3R8Request QCXGH3R8 车辆注册日期查询VIN
// 复用 QCXGA8V3仅返回 registerDate。
func ProcessQCXGH3R8Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.QCXGH3R8Req
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err)
}
result, err := callA8V3ByVin(ctx, paramsDto.VinCode, deps)
if err != nil {
return nil, err
}
respBytes, err := json.Marshal(map[string]string{
"registerDate": result.RegisterDate,
})
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
return respBytes, nil
}

View File

@@ -0,0 +1,37 @@
package qcxg
import (
"context"
"encoding/json"
"errors"
"hyapi-server/internal/domains/api/dto"
"hyapi-server/internal/domains/api/services/processors"
)
// ProcessQCXGK5M2Request QCXGK5M2 车辆投保日期与城市查询VIN
// 复用 QCXGA8V3返回 insuranceMonth并根据车牌前缀字典解析 city。
func ProcessQCXGK5M2Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.QCXGK5M2Req
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err)
}
result, err := callA8V3ByVin(ctx, paramsDto.VinCode, deps)
if err != nil {
return nil, err
}
respBytes, err := json.Marshal(map[string]string{
"insuranceMonth": result.InsuranceMonth,
"city": lookupCityByPlateNo(result.PlateNo),
})
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
return respBytes, nil
}