Compare commits
353 Commits
6905936cf3
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 2fff537bfc | |||
| 97f17e0cb7 | |||
| ad44b190b7 | |||
| 1c7b00e29c | |||
| 47ade3de94 | |||
| 0642d92069 | |||
| 0780ddfff8 | |||
| e8fef3b832 | |||
| 3e0b100bbf | |||
| d5fa2f0a15 | |||
| 0cfcd6a7ee | |||
| 66e8bb267d | |||
| 9d4a3f584c | |||
| 50f21283e8 | |||
| 21f7643b06 | |||
| fd1f83a0f0 | |||
| 6d36c86c0f | |||
| 9f06027820 | |||
| a90dfb90ef | |||
| 11d93067a2 | |||
| 937c0a29ff | |||
| 57ca7be795 | |||
| a4b2d493db | |||
| 0deefd9b0e | |||
| 32f9299576 | |||
| 59c09d6a33 | |||
| e4eb41ce10 | |||
| 85212f4dd2 | |||
| 962599d970 | |||
| 4b78edc071 | |||
| f08a940807 | |||
| 1a1d98194e | |||
| be8b516483 | |||
| 486e586ea1 | |||
| 6d892643ba | |||
| aed109d589 | |||
| 8ab2a6d81d | |||
| 928ff4d766 | |||
| 5ee816d6c1 | |||
| 4b1176b1e3 | |||
| 2a174e49e5 | |||
| a5a0522c91 | |||
| a5c955d04a | |||
| 708f42fbd1 | |||
| 636d9eaf7a | |||
| 0d3a116820 | |||
| 3c2cb5b6ab | |||
| 0973bb099c | |||
| f8bf88f635 | |||
| 8a9b87be0e | |||
| 43acbeb8f4 | |||
| b04b43cb82 | |||
| 6cde82ee69 | |||
| b16e68f3bd | |||
| f2c4618649 | |||
| 9e136bbd6b | |||
| 5cee8ff035 | |||
| b7fb2a73c9 | |||
| 2a33369639 | |||
| 28fc40525e | |||
| 4fe180e4c8 | |||
| f10c5dd626 | |||
| 760c5812ee | |||
| 9bd83ddaba | |||
| 1cb3363b5c | |||
| 95c3fc0315 | |||
| 2020bd732a | |||
| 1dfe688db7 | |||
| 9fb5db56e3 | |||
| 95006a2455 | |||
| a2913c26ab | |||
| 40f12ac1cf | |||
| 8c301de7d2 | |||
| 38410a7378 | |||
| a55d881a3a | |||
| be446de86d | |||
| 07394a4ffa | |||
| 609a35fad6 | |||
| f927f75637 | |||
| ff66c341f5 | |||
| 04a683273c | |||
| 3c71431e55 | |||
| b22fdd297b | |||
| dfd7e86f6c | |||
| 1ca6e1c81d | |||
| c8a5e6a908 | |||
| 1ef1da070f | |||
| 45b0ddcf9f | |||
| 40f4fab1d4 | |||
| c10a54c5b3 | |||
| 1ca9885e38 | |||
| e08a25ba5a | |||
| 4e33927255 | |||
| af91d8c7f3 | |||
| 98a78c3170 | |||
| 3b7bf1052e | |||
| 6253389d1b | |||
| 1b6d59a55d | |||
| 972d1a0217 | |||
| 4f64c22ebc | |||
| 20b63bde0c | |||
| fcbd534b57 | |||
| d564f4eb1b | |||
| e89459f093 | |||
| 18c92584d9 | |||
| ba463ae38d | |||
| e246271a24 | |||
| a1024ed4b2 | |||
| d6b78a5d6d | |||
| 61c6cc4f35 | |||
| cdd1e00745 | |||
| 46ba4e048c | |||
| 3156539319 | |||
| dad8abad16 | |||
| 5f62261c11 | |||
| a0b2105339 | |||
| 83e71ae81b | |||
| 8675961207 | |||
| 4bd6f51728 | |||
| cd1db5276a | |||
| 2f653be375 | |||
| 9c3fb97b3f | |||
| b6053983d9 | |||
| c3b16c0ffe | |||
| 5f6cca5369 | |||
| a01226c7c0 | |||
| e67465a58d | |||
| 75316b10cb | |||
| ebcf3be923 | |||
| cff3fb8814 | |||
| e76fcd89bb | |||
| 10605afe1e | |||
| d3554e8b44 | |||
| 35a2eb03d8 | |||
| 2a6ec6e3ca | |||
| 44d8b3d28c | |||
| cad1d354f5 | |||
| 711dc83e47 | |||
| 65fdc9bf21 | |||
| e9fe7ac303 | |||
| 130f49fb9d | |||
| d66ef0b15f | |||
| a6a2d8d9c5 | |||
| e095553ba8 | |||
| a73097aed3 | |||
| 8bbd098f97 | |||
| 5f0224ad3b | |||
| 9438ccee5e | |||
| 8771261118 | |||
| 96c5870aa0 | |||
| 5e658f2527 | |||
| e03e6b983c | |||
| 6ab9bb21e7 | |||
| 7c4bcefc81 | |||
| 8eec9685db | |||
| 6a801acee1 | |||
| 6120020a7c | |||
| da0990e015 | |||
| 80faf3cac0 | |||
| df1e8f25ed | |||
| bfe2f065c5 | |||
| 2fcf55deee | |||
| 9a1cf0d1d1 | |||
| 895b38ab88 | |||
| a96c153286 | |||
| 040f6eef65 | |||
| 947a983c67 | |||
| df6a51ae62 | |||
| 06b5aa97ec | |||
| 3775101081 | |||
| 39db1e9c1d | |||
| 15c6257762 | |||
| f9a6204b40 | |||
| 4de32c4c39 | |||
| a6f309e472 | |||
| c27b15af18 | |||
| 249ea0b15a | |||
| c193211463 | |||
| 521bfeb4ef | |||
| e0d9fd2791 | |||
| 58ba7e9f70 | |||
| 3779a7d66d | |||
| 8eb6dfc962 | |||
| c8af22f981 | |||
| d837624c0a | |||
| faf4b7f6a7 | |||
| baa45a8a05 | |||
| ec1decfdd9 | |||
| ca45be642b | |||
| bba34f817e | |||
| 4e8f9317f5 | |||
| ce2d4087bb | |||
| 0ce793ac61 | |||
| 12ed1c81e3 | |||
| 6f0a8e0519 | |||
| 14b2c53eeb | |||
| 09db8d003e | |||
| 209ffec51d | |||
| f16274d1e9 | |||
| 869b269fb1 | |||
| 9e76fd467b | |||
| 1a5e771420 | |||
| 2114f602de | |||
| 5650e78254 | |||
| 67c6e2e144 | |||
| ba1a72aa8f | |||
| 058e355d77 | |||
| 2741839cf3 | |||
| 454e60dd72 | |||
| 03cb6fd92b | |||
| 8441e66e93 | |||
| 16a2e4ff09 | |||
| c5970da195 | |||
| 1bcb4a9c2e | |||
| 8877cf9691 | |||
| f63e6df9f9 | |||
| a00fe12141 | |||
| 6b80182986 | |||
| bf4c114ee2 | |||
| 4cd3954574 | |||
| d7a5589873 | |||
| b0ec75d1af | |||
| 57d18be972 | |||
| 3d8775b6dc | |||
| f40950f890 | |||
| ba21a8f965 | |||
| 7d2716da7a | |||
| 9a7bda9527 | |||
| abdae033f0 | |||
| 96abacd392 | |||
| 4e6c93413e | |||
| 9c8dbd458f | |||
| 9e9cee02f5 | |||
| 360bd579ce | |||
| db889ccba0 | |||
| 25a4961328 | |||
| 578e68a76b | |||
| 019e47896d | |||
| c0898e6829 | |||
| 4ee6e891cd | |||
| 44b5f6b145 | |||
| 677b7362cf | |||
| 02dbc02fe8 | |||
| 374143995e | |||
| 7a957a6b87 | |||
| c885d562ee | |||
| 9f36cd8b63 | |||
| 4122f874fc | |||
| 9a32387b21 | |||
| 7bf9150cfc | |||
| fecd5a38fd | |||
| 2636d9dff6 | |||
| 927b08b871 | |||
| dedd4a60a4 | |||
| a54a19e439 | |||
| 6dd392f673 | |||
| 8d5da9d88e | |||
| bc6dce21ee | |||
| 5630d93de6 | |||
| d12529307b | |||
| f17e22f4c8 | |||
| 8c0c16006e | |||
| 532503ffe3 | |||
| af37f8620c | |||
| fb495bc0ca | |||
| 2c57ac0dab | |||
| 4b437ecf56 | |||
| abf6284482 | |||
| e6ab833099 | |||
| 47cbc5b3a5 | |||
| f400052f95 | |||
| a38c58c357 | |||
| ede446fb90 | |||
| e23897a13f | |||
| debd0a973f | |||
| b424082f01 | |||
| 4afbd7dec7 | |||
| 070310cfa7 | |||
| 62e220a7b8 | |||
| c37cf2b54a | |||
| 25a3b4a761 | |||
| 1c83102e06 | |||
| d55520e69c | |||
| 4ea7cf4ecb | |||
| 3eae08a576 | |||
| c9126dd780 | |||
| f48289b32b | |||
| 0091e01574 | |||
| d2b806eda0 | |||
| ff86cb6fb9 | |||
| a6f858dbd3 | |||
| 6a1a59de8d | |||
| dbcecde4e0 | |||
| 3158bf8c04 | |||
| 860756b767 | |||
| 3c90144d51 | |||
| 29c49d6a00 | |||
| 5bff33547c | |||
| f50e11a052 | |||
| d1e06984ac | |||
| 2325a110b6 | |||
| 167dd63a7a | |||
| 360fed3907 | |||
| 88787c6145 | |||
| bfa8bbcfcb | |||
| 2fea046981 | |||
| 4e359060e6 | |||
| 7e5a69ffaa | |||
| 482644a914 | |||
| b2f0b47896 | |||
| b1f573c230 | |||
| 2363a51a6a | |||
| 847d48d276 | |||
| 43e4daa45b | |||
| 2005f09248 | |||
| eb8886d961 | |||
| 411aeb8e25 | |||
| f8806eb71c | |||
| 3ef7b7d1fb | |||
| 32336e4ba0 | |||
| 168a7c7f5f | |||
| ebaff673c7 | |||
| 1cfd4bf0d0 | |||
| 21d312f143 | |||
| 37ce65d6f7 | |||
| 4664c06c03 | |||
| ea59a8ddff | |||
| b171288361 | |||
| 426e6f537c | |||
| bce7ba9ef4 | |||
| da1eef618e | |||
| 791147e520 | |||
| 82959f74a8 | |||
| 6fccd4b223 | |||
| e48efb4566 | |||
| e6e38013b8 | |||
| 9b223c996d | |||
| 78035ca1ab | |||
| 38ca033e31 | |||
| b05c459b81 | |||
| 6104bfb84f | |||
| d15d2f2499 | |||
| 7e45a45309 | |||
| 473468e680 | |||
| 7785d3b6ef | |||
| 6607129083 | |||
| 0036180979 | |||
| 0d3a006c46 | |||
| 17f2433fee | |||
| 8a222a0b7f | |||
| 03cfddee93 | |||
| abc7d655ce | |||
| bdb8395615 |
24
Dockerfile
24
Dockerfile
@@ -37,12 +37,27 @@ FROM alpine:3.19
|
||||
# 设置Alpine镜像源
|
||||
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
|
||||
|
||||
# 安装必要的包
|
||||
RUN apk --no-cache add tzdata curl
|
||||
# 安装必要的包(包含 headless Chrome 所需依赖)
|
||||
# - tzdata: 时区
|
||||
# - curl: 健康检查
|
||||
# - chromium: 无头浏览器,用于 chromedp 生成 HTML 报告 PDF
|
||||
# - nss、freetype、harfbuzz、ttf-freefont、font-noto-cjk: 字体及渲染依赖,避免中文/图标丢失和乱码
|
||||
RUN apk --no-cache add \
|
||||
tzdata \
|
||||
curl \
|
||||
chromium \
|
||||
nss \
|
||||
freetype \
|
||||
harfbuzz \
|
||||
ttf-freefont \
|
||||
font-noto-cjk
|
||||
|
||||
# 设置时区
|
||||
ENV TZ=Asia/Shanghai
|
||||
|
||||
# 为 chromedp 指定默认的 Chrome 路径(Alpine 下 chromium 包的可执行文件)
|
||||
ENV CHROME_BIN=/usr/bin/chromium-browser
|
||||
|
||||
# 设置工作目录
|
||||
WORKDIR /app
|
||||
|
||||
@@ -53,9 +68,8 @@ COPY --from=builder /app/tyapi-server .
|
||||
COPY config.yaml .
|
||||
COPY configs/ ./configs/
|
||||
|
||||
# 复制资源文件(直接从构建上下文复制,与配置文件一致)
|
||||
COPY resources/etc ./resources/etc
|
||||
COPY resources/pdf ./resources/pdf
|
||||
# 复制资源文件(报告模板、PDF、组件等)
|
||||
COPY resources ./resources
|
||||
|
||||
# 暴露端口
|
||||
EXPOSE 8080
|
||||
|
||||
79
cmd/qygl_report_build/main.go
Normal file
79
cmd/qygl_report_build/main.go
Normal file
@@ -0,0 +1,79 @@
|
||||
// 将 raw fixture(各处理器原始 JSON)经 BuildReportFromRawSources 转化为与线上一致的完整报告 JSON。
|
||||
//
|
||||
// go run ./cmd/qygl_report_build -in resources/dev-report/fixture.raw.example.json -out resources/dev-report/built.json
|
||||
// go run ./cmd/qygl_report_build -in raw.json -out - # 输出到 stdout
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"tyapi-server/internal/domains/api/services/processors/qygl"
|
||||
)
|
||||
|
||||
type rawBundle struct {
|
||||
Kind string `json:"kind"`
|
||||
|
||||
JiguangFull map[string]interface{} `json:"jiguangFull"`
|
||||
JudicialCertFull map[string]interface{} `json:"judicialCertFull"`
|
||||
EquityPanorama map[string]interface{} `json:"equityPanorama"`
|
||||
AnnualReport map[string]interface{} `json:"annualReport"`
|
||||
TaxViolation map[string]interface{} `json:"taxViolation"`
|
||||
TaxArrears map[string]interface{} `json:"taxArrears"`
|
||||
CustomsCredit map[string]interface{} `json:"customsCredit"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
inPath := flag.String("in", "", "raw fixture JSON 路径(含 jiguangFull 等字段,可参考 fixture.raw.example.json)")
|
||||
outPath := flag.String("out", "", "输出文件;- 或留空表示输出到 stdout")
|
||||
flag.Parse()
|
||||
|
||||
if *inPath == "" {
|
||||
log.Fatal("请指定 -in <raw.json>")
|
||||
}
|
||||
raw, err := os.ReadFile(*inPath)
|
||||
if err != nil {
|
||||
log.Fatalf("读取输入失败: %v", err)
|
||||
}
|
||||
|
||||
var b rawBundle
|
||||
if err := json.Unmarshal(raw, &b); err != nil {
|
||||
log.Fatalf("解析 JSON 失败: %v", err)
|
||||
}
|
||||
|
||||
if b.Kind == "full" {
|
||||
log.Fatal("输入为 kind=full(已是 build 结果),无需再转化;预览请用: go run ./cmd/qygl_report_preview")
|
||||
}
|
||||
if b.Kind != "" && b.Kind != "raw" {
|
||||
log.Fatalf("若填写 kind,仅支持 raw,当前: %q", b.Kind)
|
||||
}
|
||||
|
||||
report := qygl.BuildReportFromRawSources(
|
||||
b.JiguangFull,
|
||||
b.JudicialCertFull,
|
||||
b.EquityPanorama,
|
||||
b.AnnualReport,
|
||||
b.TaxViolation,
|
||||
b.TaxArrears,
|
||||
b.CustomsCredit,
|
||||
)
|
||||
|
||||
out, err := json.MarshalIndent(report, "", " ")
|
||||
if err != nil {
|
||||
log.Fatalf("序列化报告失败: %v", err)
|
||||
}
|
||||
|
||||
if *outPath == "" || *outPath == "-" {
|
||||
if _, err := os.Stdout.Write(append(out, '\n')); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err := os.WriteFile(*outPath, append(out, '\n'), 0644); err != nil {
|
||||
log.Fatalf("写入失败: %v", err)
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "已写入 %s\n", *outPath)
|
||||
}
|
||||
60
cmd/qygl_report_pdf/main.go
Normal file
60
cmd/qygl_report_pdf/main.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"tyapi-server/internal/shared/pdf"
|
||||
)
|
||||
|
||||
// 一个本地调试用的小工具:
|
||||
// 从 JSON 文件(企业报告.json)读取 QYGL 聚合结果,使用 gofpdf 生成企业全景报告 PDF,输出到当前目录。
|
||||
func main() {
|
||||
var (
|
||||
jsonPath string
|
||||
outPath string
|
||||
)
|
||||
flag.StringVar(&jsonPath, "json", "企业报告.json", "企业报告 JSON 数据源文件路径")
|
||||
flag.StringVar(&outPath, "out", "企业全景报告_gofpdf.pdf", "输出 PDF 文件路径")
|
||||
flag.Parse()
|
||||
|
||||
logger, _ := zap.NewDevelopment()
|
||||
defer logger.Sync()
|
||||
|
||||
absJSON, _ := filepath.Abs(jsonPath)
|
||||
fmt.Printf("读取 JSON 数据源:%s\n", absJSON)
|
||||
|
||||
data, err := os.ReadFile(jsonPath)
|
||||
if err != nil {
|
||||
fmt.Printf("读取 JSON 文件失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var report map[string]interface{}
|
||||
if err := json.Unmarshal(data, &report); err != nil {
|
||||
fmt.Printf("解析 JSON 失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Println("开始使用 gofpdf 生成企业全景报告 PDF...")
|
||||
pdfBytes, err := pdf.GenerateQYGLReportPDF(context.Background(), logger, report)
|
||||
if err != nil {
|
||||
fmt.Printf("生成 PDF 失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(outPath, pdfBytes, 0644); err != nil {
|
||||
fmt.Printf("写入 PDF 文件失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
absOut, _ := filepath.Abs(outPath)
|
||||
fmt.Printf("PDF 生成完成:%s\n", absOut)
|
||||
}
|
||||
|
||||
159
cmd/qygl_report_preview/main.go
Normal file
159
cmd/qygl_report_preview/main.go
Normal file
@@ -0,0 +1,159 @@
|
||||
// 仅读取 build 后的报告 JSON,本地渲染 qiye.html(不执行 BuildReportFromRawSources)。
|
||||
//
|
||||
// go run ./cmd/qygl_report_preview -in resources/dev-report/built.json
|
||||
// go run ./cmd/qygl_report_preview -in built.json -addr :8899 -watch
|
||||
//
|
||||
// 每次打开/刷新页面都会重新读取 -in 文件;加 -watch 后保存 JSON 会自动刷新浏览器。
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func parseBuiltReport(data []byte) (map[string]interface{}, error) {
|
||||
var root map[string]interface{}
|
||||
if err := json.Unmarshal(data, &root); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, ok := root["jiguangFull"]; ok {
|
||||
return nil, fmt.Errorf("检测到 raw 字段 jiguangFull,请先执行: go run ./cmd/qygl_report_build -in <raw.json> -out built.json")
|
||||
}
|
||||
if k, _ := root["kind"].(string); k == "full" {
|
||||
r, ok := root["report"].(map[string]interface{})
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("kind=full 时缺少 report 对象")
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
if r, ok := root["report"].(map[string]interface{}); ok {
|
||||
return r, nil
|
||||
}
|
||||
if root["entName"] != nil || root["basic"] != nil || root["reportTime"] != nil {
|
||||
return root, nil
|
||||
}
|
||||
return nil, fmt.Errorf("不是有效的 build 后报告(根级应有 entName、basic、reportTime 之一,或 {\"report\":{...}} / kind=full)")
|
||||
}
|
||||
|
||||
func fileVersionTag(path string) (string, error) {
|
||||
st, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("%d-%d", st.ModTime().UnixNano(), st.Size()), nil
|
||||
}
|
||||
|
||||
func renderPage(tmpl *template.Template, report map[string]interface{}, injectLive bool) ([]byte, error) {
|
||||
reportBytes, err := json.Marshal(report)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if err := tmpl.Execute(&buf, map[string]interface{}{
|
||||
"ReportJSON": template.JS(reportBytes),
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b := buf.Bytes()
|
||||
if !injectLive {
|
||||
return b, nil
|
||||
}
|
||||
script := `<script>(function(){var v0=null;function tick(){fetch("/__version?="+Date.now(),{cache:"no-store"}).then(function(r){return r.text();}).then(function(v){if(v==="")return;if(v0===null)v0=v;else if(v0!==v){v0=v;location.reload();}}).catch(function(){});}setInterval(tick,600);tick();})();</script>`
|
||||
closing := []byte("</body>")
|
||||
idx := bytes.LastIndex(b, closing)
|
||||
if idx < 0 {
|
||||
return append(b, []byte(script)...), nil
|
||||
}
|
||||
out := make([]byte, 0, len(b)+len(script))
|
||||
out = append(out, b[:idx]...)
|
||||
out = append(out, script...)
|
||||
out = append(out, b[idx:]...)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
addr := flag.String("addr", ":8899", "监听地址")
|
||||
root := flag.String("root", ".", "项目根目录(含 resources/qiye.html)")
|
||||
inPath := flag.String("in", "", "build 后的 JSON(由 qygl_report_build 生成,或 fixture.full 中的 report 形态)")
|
||||
watch := flag.Bool("watch", false, "监听 -in 文件变化并自动刷新浏览器(轮询)")
|
||||
flag.Parse()
|
||||
|
||||
if *inPath == "" {
|
||||
log.Fatal("请指定 -in <built.json>")
|
||||
}
|
||||
|
||||
rootAbs, err := filepath.Abs(*root)
|
||||
if err != nil {
|
||||
log.Fatalf("解析 root: %v", err)
|
||||
}
|
||||
tplPath := filepath.Join(rootAbs, "resources", "qiye.html")
|
||||
if _, err := os.Stat(tplPath); err != nil {
|
||||
log.Fatalf("未找到模板 %s: %v", tplPath, err)
|
||||
}
|
||||
|
||||
var inAbs string
|
||||
if filepath.IsAbs(*inPath) {
|
||||
inAbs = *inPath
|
||||
} else {
|
||||
inAbs = filepath.Join(rootAbs, *inPath)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(inAbs); err != nil {
|
||||
log.Fatalf("读取 %s: %v", inAbs, err)
|
||||
}
|
||||
|
||||
tmpl, err := template.ParseFiles(tplPath)
|
||||
if err != nil {
|
||||
log.Fatalf("解析模板: %v", err)
|
||||
}
|
||||
|
||||
http.HandleFunc("/__version", func(w http.ResponseWriter, r *http.Request) {
|
||||
tag, err := fileVersionTag(inAbs)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
_, _ = w.Write([]byte(tag))
|
||||
})
|
||||
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/" {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
raw, err := os.ReadFile(inAbs)
|
||||
if err != nil {
|
||||
http.Error(w, "读取报告文件失败: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
report, err := parseBuiltReport(raw)
|
||||
if err != nil {
|
||||
http.Error(w, "解析 JSON 失败: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
html, err := renderPage(tmpl, report, *watch)
|
||||
if err != nil {
|
||||
http.Error(w, "渲染失败: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
_, _ = w.Write(html)
|
||||
})
|
||||
|
||||
log.Printf("报告预览: http://127.0.0.1%s/ (每请求重读 %s)", *addr, inAbs)
|
||||
if *watch {
|
||||
log.Printf("已启用 -watch:保存 JSON 后约 0.6s 内自动刷新页面")
|
||||
}
|
||||
if err := http.ListenAndServe(*addr, nil); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
224
config.yaml
224
config.yaml
@@ -5,6 +5,8 @@ app:
|
||||
name: "TYAPI Server"
|
||||
version: "1.0.0"
|
||||
env: "development"
|
||||
# 子账号入口与主站可同域;邀请链接 {sub_portal_base_url}/sub/auth/register?invite=...
|
||||
sub_portal_base_url: "http://localhost:5173/"
|
||||
|
||||
server:
|
||||
host: "0.0.0.0"
|
||||
@@ -119,6 +121,7 @@ jwt:
|
||||
|
||||
api:
|
||||
domain: "api.tianyuanapi.com"
|
||||
# public_base_url: "" # 可选,无尾斜杠;空则按 https://{domain} 推导;环境变量 API_PUBLIC_BASE_URL 优先
|
||||
|
||||
sms:
|
||||
access_key_id: "LTAI5tKGB3TVJbMHSoZN3yr9"
|
||||
@@ -129,6 +132,14 @@ sms:
|
||||
code_length: 6
|
||||
expire_time: 5m
|
||||
mock_enabled: false
|
||||
# 签名验证配置(用于防止接口被刷)
|
||||
signature_enabled: true # 是否启用签名验证
|
||||
signature_secret: "TyApi2024SMSSecretKey!@#$%^&*()_+QWERTYUIOP" # 签名密钥(请修改为复杂密钥)
|
||||
# 滑块验证码配置
|
||||
captcha_enabled: true # 是否启用滑块验证码
|
||||
captcha_secret: "" # 阿里云验证码密钥(加密模式时需要,可选)EKEY
|
||||
captcha_endpoint: "captcha.cn-shanghai.aliyuncs.com" # 阿里云验证码服务Endpoint
|
||||
scene_id: "wynt39to" # 阿里云验证码场景ID
|
||||
rate_limit:
|
||||
daily_limit: 10
|
||||
hourly_limit: 5
|
||||
@@ -192,11 +203,12 @@ daily_ratelimit:
|
||||
- "python" # Python脚本
|
||||
- "java" # Java脚本
|
||||
- "go-http-client" # Go HTTP客户端
|
||||
- "LangShen"
|
||||
|
||||
enable_referer: true # 是否检查Referer
|
||||
allowed_referers: # 允许的Referer
|
||||
- "https://console.tianyuanapi.com" # 天元API控制台
|
||||
- "https://consoletest.tianyuanapi.com" # 天元API测试控制台
|
||||
- "https://subsole.tianyuanapi.com" # 天元API子账号控制台
|
||||
|
||||
enable_proxy_check: false # 是否检查代理
|
||||
enable_geo_block: false # 是否启用地理位置阻止
|
||||
@@ -227,13 +239,13 @@ development:
|
||||
debug: true
|
||||
enable_profiler: true
|
||||
enable_cors: true
|
||||
cors_allowed_origins: "http://localhost:5173,https://consoletest.tianyuanapi.com,https://console.tianyuanapi.com"
|
||||
cors_allowed_origins: "http://localhost:5173,https://console.tianyuanapi.com,https://subsole.tianyuanapi.com"
|
||||
cors_allowed_methods: "GET,POST,PUT,PATCH,DELETE,OPTIONS"
|
||||
cors_allowed_headers: "Origin,Content-Type,Accept,Authorization,X-Requested-With,Access-Id"
|
||||
|
||||
# 企业微信配置
|
||||
wechat_work:
|
||||
webhook_url: ""
|
||||
webhook_url: "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=649bf737-28ca-4f30-ad5f-cfb65b2af113"
|
||||
secret: ""
|
||||
|
||||
# ===========================================
|
||||
@@ -243,7 +255,7 @@ esign:
|
||||
app_id: "7439073138"
|
||||
app_secret: "d76e27fdd169b391e09262a0959dac5c"
|
||||
server_url: "https://smlopenapi.esign.cn"
|
||||
template_id: "9f7a3f63cc5a48b085b127ba027d234d"
|
||||
template_id: "6c91bfd5b1bb48c585f5eaceeea893d4"
|
||||
contract:
|
||||
name: "天远数据API合作协议"
|
||||
expire_days: 7
|
||||
@@ -267,6 +279,8 @@ wallet:
|
||||
default_credit_limit: 50.00
|
||||
min_amount: "100.00" # 生产环境最低充值金额
|
||||
max_amount: "100000.00" # 单次最高充值金额
|
||||
recharge_bonus_enabled: true # 是否启用充值赠送,设为 false 时仅展示商务洽谈提示
|
||||
api_store_recharge_tip: "" # 关闭赠送时展示的提示文案,为空则使用默认文案
|
||||
# 支付宝充值赠送配置
|
||||
alipay_recharge_bonus:
|
||||
- recharge_amount: 1000.00 # 充值1000元
|
||||
@@ -369,7 +383,7 @@ Wxpay:
|
||||
app_id: "wxa581992dc74d860e"
|
||||
mch_id: "1683589176"
|
||||
mch_certificate_serial_number: "1F4E8B3C39C60035D4CC154F276D03D9CC2C603D"
|
||||
mch_apiv3_key: "TY8X9nP2qR5tY7uW3zA6bC4dE1flgGJ0"
|
||||
mch_apiv3_key: "4g9ff87IJMebGmQOG0IshdMbM97YcBnt"
|
||||
mch_private_key_path: "resources/etc/wxetc_cert/apiclient_key.pem"
|
||||
mch_public_key_id: "PUB_KEY_ID_0116835891762025062600211574000800"
|
||||
mch_public_key_path: "resources/etc/wxetc_cert/pub_key.pem"
|
||||
@@ -387,6 +401,7 @@ WechatH5:
|
||||
# ===========================================
|
||||
# 🔍 天眼查配置
|
||||
# ===========================================
|
||||
|
||||
tianyancha:
|
||||
base_url: http://open.api.tianyancha.com/services
|
||||
api_key: e6a43dc9-786e-4a16-bb12-392b8201d8e2
|
||||
@@ -530,3 +545,202 @@ jiguang:
|
||||
max_backups: 5
|
||||
max_age: 30
|
||||
compress: true
|
||||
|
||||
# ===========================================
|
||||
# 📄 PDF生成服务配置
|
||||
# ===========================================
|
||||
pdfgen:
|
||||
# 服务地址配置
|
||||
development_url: "http://pdfg.tianyuanapi.com" # 开发环境服务地址
|
||||
production_url: "http://1.117.67.95:15990" # 生产环境服务地址
|
||||
|
||||
# API路径配置
|
||||
api_path: "/api/v1/generate/guangzhou" # PDF生成API路径
|
||||
|
||||
# 超时配置
|
||||
timeout: 120s # 请求超时时间(120秒)
|
||||
|
||||
# 缓存配置
|
||||
cache:
|
||||
ttl: 24h # 缓存过期时间(24小时)
|
||||
cache_dir: "" # 缓存目录(空则使用默认目录)
|
||||
max_size: 0 # 最大缓存大小(0表示不限制,单位:字节)
|
||||
|
||||
# ===========================================
|
||||
# ✨ 数脉配置走实时接口
|
||||
# ===========================================
|
||||
shumai:
|
||||
url: "https://api.shumaidata.com"
|
||||
app_id: "pIfqx8MsoTOjhbB762qi5BfkjJ4D7w0O"
|
||||
app_secret: "BnJWo61hUgNEa5fqBCueiT1IZ1e0DxPU"
|
||||
# ===========================================
|
||||
# ✨ 数脉子账号配置走政务
|
||||
# ===========================================
|
||||
# 走政务接口使用这个
|
||||
app_id2: "AwZZRzWkArtFDO2lDcT2jHfuoo9n35Tq"
|
||||
app_secret2: "nCXN6fKLImjfvzI12hj8O1CMl1gJeaWh"
|
||||
|
||||
sign_method: "md5" # 签名方法:md5 或 hmac,默认 hmac
|
||||
timeout: 60s # 请求超时时间,默认 60 秒
|
||||
|
||||
# 数脉日志配置
|
||||
logging:
|
||||
enabled: true
|
||||
log_dir: "logs/external_services"
|
||||
service_name: "shumai"
|
||||
use_daily: true
|
||||
enable_level_separation: true
|
||||
|
||||
# 各级别配置
|
||||
level_configs:
|
||||
info:
|
||||
max_size: 100
|
||||
max_backups: 5
|
||||
max_age: 30
|
||||
compress: true
|
||||
error:
|
||||
max_size: 200
|
||||
max_backups: 10
|
||||
max_age: 90
|
||||
compress: true
|
||||
warn:
|
||||
max_size: 100
|
||||
max_backups: 5
|
||||
max_age: 30
|
||||
compress: true
|
||||
|
||||
# ===========================================
|
||||
# ✨ 数据宝配置走实时接口
|
||||
# ===========================================
|
||||
shujubao:
|
||||
url: "https://api.chinadatapay.com"
|
||||
app_secret: "iOk0ALBX0BSdTSTf"
|
||||
sign_method: "md5" # 签名方法:md5 或 hmac,默认 hmac
|
||||
timeout: 60s # 请求超时时间,默认 60 秒
|
||||
# 数据宝日志配置
|
||||
logging:
|
||||
enabled: true
|
||||
log_dir: "logs/external_services"
|
||||
service_name: "shujubao"
|
||||
use_daily: true
|
||||
enable_level_separation: true
|
||||
# 各级别配置
|
||||
level_configs:
|
||||
info:
|
||||
max_size: 100
|
||||
max_backups: 5
|
||||
max_age: 30
|
||||
compress: true
|
||||
error:
|
||||
max_size: 200
|
||||
max_backups: 10
|
||||
max_age: 90
|
||||
compress: true
|
||||
warn:
|
||||
max_size: 100
|
||||
max_backups: 5
|
||||
max_age: 30
|
||||
compress: true
|
||||
|
||||
# ===========================================
|
||||
# ✨ 汇博(BHSC)配置
|
||||
# ===========================================
|
||||
huibo:
|
||||
url: "http://47.111.187.101:12654/api/v1/project-api/bg_check_ssw"
|
||||
app_id: "db0029527bb4558c"
|
||||
app_key: "a6c9935e967894e731c62ecfcd9b7c95"
|
||||
x_order_code: "cpdd219219725093"
|
||||
secret_id: "cf581fe84aaf46ca"
|
||||
aes_key: "NQYN3YO+pb/GEcCBNX0ptMb7cUlnXSPvcX7VvNofBkc="
|
||||
work_order_code: "gd219219725093"
|
||||
product_code: "22089"
|
||||
baseUrl2: "https://napi.zhixin.net:9000/api/data"
|
||||
app_code2: "1508795945301708800"
|
||||
|
||||
logging:
|
||||
enabled: true
|
||||
log_dir: "logs/external_services"
|
||||
service_name: "huibo"
|
||||
use_daily: true
|
||||
enable_level_separation: true
|
||||
level_configs:
|
||||
info:
|
||||
max_size: 100
|
||||
max_backups: 5
|
||||
max_age: 30
|
||||
compress: true
|
||||
error:
|
||||
max_size: 200
|
||||
max_backups: 10
|
||||
max_age: 90
|
||||
compress: true
|
||||
warn:
|
||||
max_size: 100
|
||||
max_backups: 5
|
||||
max_age: 30
|
||||
compress: true
|
||||
|
||||
|
||||
|
||||
# ===========================================
|
||||
# 🌐 诺尔智汇配置
|
||||
# ===========================================
|
||||
nuoer:
|
||||
url: "https://api.enolfax.com/enol/api"
|
||||
app_id: "t4qO2mR3"
|
||||
app_secret: "d1515bf9ed2f2fe063b5f4f7e2c50f0ec65bfd58"
|
||||
timeout: 4s
|
||||
logging:
|
||||
enabled: true
|
||||
log_dir: "logs/external_services"
|
||||
service_name: "nuoer"
|
||||
use_daily: true
|
||||
enable_level_separation: true
|
||||
# 各级别配置
|
||||
level_configs:
|
||||
info:
|
||||
max_size: 100
|
||||
max_backups: 5
|
||||
max_age: 30
|
||||
compress: true
|
||||
error:
|
||||
max_size: 200
|
||||
max_backups: 10
|
||||
max_age: 90
|
||||
compress: true
|
||||
warn:
|
||||
max_size: 100
|
||||
max_backups: 5
|
||||
max_age: 30
|
||||
compress: true
|
||||
|
||||
# ===========================================
|
||||
# 🌐 海宇API(上游数据源)配置
|
||||
# ===========================================
|
||||
haiyuapi:
|
||||
base_url: "https://api.haiyudata.com"
|
||||
access_id: "2c136588d34b47fd" # 请求头 Access-Id
|
||||
secret_key: "39c5c2c02ff13bf2be0ac0b705e21e28" # Access Key(16进制 AES-128 密钥)
|
||||
timeout: 60s
|
||||
logging:
|
||||
enabled: true
|
||||
log_dir: "logs/external_services"
|
||||
service_name: "haiyuapi"
|
||||
use_daily: true
|
||||
enable_level_separation: true
|
||||
level_configs:
|
||||
info:
|
||||
max_size: 100
|
||||
max_backups: 5
|
||||
max_age: 30
|
||||
compress: true
|
||||
error:
|
||||
max_size: 200
|
||||
max_backups: 10
|
||||
max_age: 90
|
||||
compress: true
|
||||
warn:
|
||||
max_size: 100
|
||||
max_backups: 5
|
||||
max_age: 30
|
||||
compress: true
|
||||
@@ -6,6 +6,8 @@
|
||||
# ===========================================
|
||||
app:
|
||||
env: development
|
||||
# 子账号专属前端域名(用于邀请链接复制)
|
||||
sub_portal_base_url: "http://localhost:5173"
|
||||
|
||||
# ===========================================
|
||||
# 🗄️ 数据库配置
|
||||
@@ -20,6 +22,10 @@ database:
|
||||
jwt:
|
||||
secret: JwT8xR4mN9vP2sL7kH3oB6yC1zA5uF0qE9tW
|
||||
|
||||
# 本地联调:企业报告链接与 headless PDF 需能访问到本机服务;端口与 server 监听一致。环境变量 API_PUBLIC_BASE_URL 可覆盖。
|
||||
api:
|
||||
public_base_url: "http://127.0.0.1:8080"
|
||||
|
||||
# ===========================================
|
||||
# 📁 存储服务配置 - 七牛云
|
||||
# ===========================================
|
||||
@@ -40,10 +46,10 @@ ocr:
|
||||
# 📝 e签宝服务配置
|
||||
# ===========================================
|
||||
esign:
|
||||
app_id: "7439073713"
|
||||
app_secret: "c7d8cb0d701f7890601d221e9b6edfef"
|
||||
server_url: "https://smlopenapi.esign.cn"
|
||||
template_id: "9f7a3f63cc5a48b085b127ba027d234d"
|
||||
app_id: "5112008003"
|
||||
app_secret: "d487672273e7aa70c800804a1d9499b9"
|
||||
server_url: "https://openapi.esign.cn"
|
||||
template_id: "6c91bfd5b1bb48c585f5eaceeea893d4"
|
||||
contract:
|
||||
name: "天远数据API合作协议"
|
||||
expire_days: 7
|
||||
@@ -87,7 +93,7 @@ Wxpay:
|
||||
app_id: "wxa581992dc74d860e"
|
||||
mch_id: "1683589176"
|
||||
mch_certificate_serial_number: "1F4E8B3C39C60035D4CC154F276D03D9CC2C603D"
|
||||
mch_apiv3_key: "TY8X9nP2qR5tY7uW3zA6bC4dE1flgGJ0"
|
||||
mch_apiv3_key: "4g9ff87IJMebGmQOG0IshdMbM97YcBnt"
|
||||
mch_private_key_path: "resources/etc/wxetc_cert/apiclient_key.pem"
|
||||
mch_public_key_id: "PUB_KEY_ID_0116835891762025062600211574000800"
|
||||
mch_public_key_path: "resources/etc/wxetc_cert/pub_key.pem"
|
||||
@@ -108,6 +114,8 @@ wallet:
|
||||
default_credit_limit: 0.01
|
||||
min_amount: "0.01" # 生产环境最低充值金额
|
||||
max_amount: "100000.00" # 单次最高充值金额
|
||||
recharge_bonus_enabled: false # 开发环境可设为 true 测试赠送
|
||||
api_store_recharge_tip: "尊敬的客户,若您的充值金额较大或有批量调价需求,为获取专属商务优惠方案,请直接联系我司商务团队进行洽谈。感谢您的支持!"
|
||||
# 支付宝充值赠送配置
|
||||
alipay_recharge_bonus:
|
||||
- recharge_amount: 0.01 # 充值1000元
|
||||
@@ -174,3 +182,13 @@ daily_ratelimit:
|
||||
enable_user_agent: false # 开发环境禁用User-Agent检查
|
||||
enable_referer: false # 开发环境禁用Referer检查
|
||||
enable_proxy_check: false # 开发环境禁用代理检查
|
||||
|
||||
# ===========================================
|
||||
# 📱 短信服务配置
|
||||
# ===========================================
|
||||
sms:
|
||||
# 滑块验证码配置
|
||||
captcha_enabled: true # 是否启用滑块验证码
|
||||
captcha_secret: "" # 阿里云验证码密钥(可选)
|
||||
scene_id: "wynt39to" # 阿里云验证码场景ID
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
# ===========================================
|
||||
app:
|
||||
env: production
|
||||
# 子账号专属前端域名(用于邀请链接复制)
|
||||
sub_portal_base_url: "https://subsole.tianyuanapi.com"
|
||||
|
||||
# ===========================================
|
||||
# 🌐 服务器配置
|
||||
@@ -18,7 +20,7 @@ server:
|
||||
# ===========================================
|
||||
development:
|
||||
enable_cors: true
|
||||
cors_allowed_origins: "http://localhost:5173,https://consoletest.tianyuanapi.com,https://console.tianyuanapi.com"
|
||||
cors_allowed_origins: "https://console.tianyuanapi.com,https://subsole.tianyuanapi.com"
|
||||
cors_allowed_methods: "GET,POST,PUT,PATCH,DELETE,OPTIONS"
|
||||
cors_allowed_headers: "Origin,Content-Type,Accept,Authorization,X-Requested-With,Access-Id"
|
||||
|
||||
@@ -52,6 +54,8 @@ jwt:
|
||||
|
||||
api:
|
||||
domain: "api.tianyuanapi.com"
|
||||
# 可选:对外可访问的 API 完整基址(无尾斜杠),用于企业报告 reportUrl、PDF 预生成等;不设则按 https://{domain} 推导。环境变量 API_PUBLIC_BASE_URL 优先于本项。
|
||||
# public_base_url: "https://api.tianyuanapi.com"
|
||||
# ===========================================
|
||||
# 📁 存储服务配置 - 七牛云
|
||||
# ===========================================
|
||||
@@ -75,7 +79,7 @@ esign:
|
||||
app_id: "5112008003"
|
||||
app_secret: "d487672273e7aa70c800804a1d9499b9"
|
||||
server_url: "https://openapi.esign.cn"
|
||||
template_id: "9f7a3f63cc5a48b085b127ba027d234d"
|
||||
template_id: "6c91bfd5b1bb48c585f5eaceeea893d4"
|
||||
contract:
|
||||
name: "天远数据API合作协议"
|
||||
expire_days: 7
|
||||
@@ -109,7 +113,9 @@ wallet:
|
||||
default_credit_limit: 50.00
|
||||
min_amount: "100.00" # 生产环境最低充值金额
|
||||
max_amount: "100000.00" # 单次最高充值金额
|
||||
# 支付宝充值赠送配置
|
||||
recharge_bonus_enabled: false # 暂不赠送,展示商务洽谈提示
|
||||
api_store_recharge_tip: "尊敬的客户,若您的充值金额较大或有批量调价需求,为获取专属商务优惠方案,请直接联系我司商务团队进行洽谈。感谢您的支持!"
|
||||
# 支付宝充值赠送配置(recharge_bonus_enabled 为 true 时生效)
|
||||
alipay_recharge_bonus:
|
||||
- recharge_amount: 1000.00 # 充值1000元
|
||||
bonus_amount: 50.00 # 赠送50元
|
||||
@@ -148,12 +154,22 @@ daily_ratelimit:
|
||||
- "curl" # 阻止curl请求
|
||||
- "wget" # 阻止wget请求
|
||||
- "python-requests" # 阻止Python requests
|
||||
- "LangShen" # 阻止LangShen请求
|
||||
|
||||
enable_referer: true # 启用Referer检查
|
||||
allowed_referers: # 允许的Referer
|
||||
- "https://console.tianyuanapi.com"
|
||||
- "https://consoletest.tianyuanapi.com"
|
||||
- "https://subsole.tianyuanapi.com"
|
||||
|
||||
enable_geo_block: false # 生产环境暂时不启用地理位置阻止
|
||||
enable_proxy_check: true # 启用代理检查
|
||||
|
||||
# ===========================================
|
||||
# 📱 短信服务配置
|
||||
# ===========================================
|
||||
sms:
|
||||
# 滑块验证码配置
|
||||
captcha_enabled: true # 是否启用滑块验证码
|
||||
captcha_secret: "" # 阿里云验证码密钥(可选)
|
||||
scene_id: "wynt39to" # 阿里云验证码场景ID
|
||||
|
||||
|
||||
@@ -37,3 +37,12 @@ logger:
|
||||
# ===========================================
|
||||
jwt:
|
||||
secret: test-jwt-secret-key-for-testing-only
|
||||
|
||||
# ===========================================
|
||||
# 📱 短信服务配置
|
||||
# ===========================================
|
||||
sms:
|
||||
# 滑块验证码配置
|
||||
captcha_enabled: true # 是否启用滑块验证码
|
||||
captcha_secret: "" # 阿里云验证码密钥(可选)
|
||||
scene_id: "wynt39to" # 阿里云验证码场景ID
|
||||
|
||||
@@ -89,7 +89,10 @@ services:
|
||||
- "25000:8080"
|
||||
volumes:
|
||||
- ./logs:/app/logs
|
||||
- ./resources/Pure_Component:/app/resources/Pure_Component
|
||||
# 挂载完整 resources 目录(包含 qiye.html、Pure_Component、pdf 等)
|
||||
- ./resources:/app/resources
|
||||
# 持久化PDF缓存目录,确保生成的PDF在容器重启后仍然存在
|
||||
- ./storage/pdfg-cache:/app/storage/pdfg-cache
|
||||
# user: "1001:1001" # 注释掉,使用root权限运行
|
||||
networks:
|
||||
- tyapi-network
|
||||
@@ -105,14 +108,6 @@ services:
|
||||
retries: 5
|
||||
start_period: 60s
|
||||
restart: unless-stopped
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 1G
|
||||
cpus: "1.0"
|
||||
reservations:
|
||||
memory: 256M
|
||||
cpus: "0.3"
|
||||
|
||||
# TYAPI Worker 服务
|
||||
tyapi-worker:
|
||||
@@ -147,14 +142,6 @@ services:
|
||||
retries: 5
|
||||
start_period: 60s
|
||||
restart: unless-stopped
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 512M
|
||||
cpus: "0.5"
|
||||
reservations:
|
||||
memory: 128M
|
||||
cpus: "0.1"
|
||||
|
||||
# Asynq 任务监控 (生产环境)
|
||||
asynq-monitor:
|
||||
|
||||
347
docs/短信接口签名验证使用指南.md
Normal file
347
docs/短信接口签名验证使用指南.md
Normal file
@@ -0,0 +1,347 @@
|
||||
# 短信接口签名验证使用指南
|
||||
|
||||
## 概述
|
||||
|
||||
为了防止短信发送接口被恶意刷取,系统实现了基于HMAC-SHA256的签名验证机制。所有发送短信的请求必须包含有效的签名,否则请求将被拒绝。
|
||||
|
||||
## 工作原理
|
||||
|
||||
1. **前端生成签名**:使用密钥对请求参数进行HMAC-SHA256签名
|
||||
2. **后端验证签名**:后端使用相同密钥重新计算签名并比对
|
||||
3. **时间戳验证**:防止重放攻击,时间戳必须在5分钟内有效
|
||||
4. **随机数验证**:每次请求必须包含唯一的随机字符串(nonce)
|
||||
5. **参数编码传输**(推荐):将所有参数(包括签名)编码成Base64字符串后传输,隐藏参数结构,增加安全性
|
||||
|
||||
## 配置说明
|
||||
|
||||
### 后端配置
|
||||
|
||||
在 `config.yaml` 中配置签名相关参数:
|
||||
|
||||
```yaml
|
||||
sms:
|
||||
# ... 其他配置 ...
|
||||
# 签名验证配置
|
||||
signature_enabled: true # 是否启用签名验证
|
||||
signature_secret: "TyApi2024SMSSecretKey!@#$%^&*()_+QWERTYUIOP" # 签名密钥(请修改为复杂密钥)
|
||||
```
|
||||
|
||||
**重要提示**:
|
||||
- 生产环境必须修改 `signature_secret` 为复杂的随机字符串
|
||||
- 密钥长度建议至少32个字符
|
||||
- 密钥应包含大小写字母、数字和特殊字符
|
||||
|
||||
## 签名算法
|
||||
|
||||
### 1. 构建待签名字符串
|
||||
|
||||
将请求参数(排除`signature`字段)按key排序,拼接成以下格式:
|
||||
|
||||
```
|
||||
key1=value1&key2=value2×tamp=1234567890&nonce=random_string
|
||||
```
|
||||
|
||||
### 2. 计算HMAC-SHA256签名
|
||||
|
||||
使用配置的密钥对待签名字符串进行HMAC-SHA256计算,结果转换为hex编码。
|
||||
|
||||
### 3. 请求参数
|
||||
|
||||
系统支持两种请求方式:
|
||||
|
||||
#### 方式1:直接传递参数
|
||||
|
||||
发送请求时直接传递所有字段:
|
||||
|
||||
```json
|
||||
{
|
||||
"phone": "13800138000",
|
||||
"scene": "register",
|
||||
"timestamp": 1704067200,
|
||||
"nonce": "a1b2c3d4e5f6g7h8",
|
||||
"signature": "abc123def456..."
|
||||
}
|
||||
```
|
||||
|
||||
#### 方式2:编码后传输(推荐,更安全)
|
||||
|
||||
将所有参数(包括签名)编码成Base64字符串后传输,只传递一个`data`字段:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": "eyJwaG9uZSI6IjEzODAwMTM4MDAwIiwic2NlbmUiOiJyZWdpc3RlciIsInRpbWVzdGFtcCI6MTcwNDA2NzIwMCwibm9uY2UiOiJhMWIyYzNkNGE1ZjYiLCJzaWduYXR1cmUiOiJhYmMxMjNkZWY0NTYifQ=="
|
||||
}
|
||||
```
|
||||
|
||||
**编码传输的优势**:
|
||||
- 隐藏参数结构,增加破解难度
|
||||
- 参数不可见,防止参数被直接修改
|
||||
- 增加一层编码保护
|
||||
|
||||
## 前端实现
|
||||
|
||||
### Node.js 示例
|
||||
|
||||
参考文件:`tyapi-frontend/public/examples/nodejs/sms_signature_demo.js`
|
||||
|
||||
#### 方式1:直接传递参数
|
||||
|
||||
```javascript
|
||||
const crypto = require('crypto');
|
||||
|
||||
function generateSignature(params, secretKey, timestamp, nonce) {
|
||||
// 1. 构建待签名字符串
|
||||
const keys = Object.keys(params)
|
||||
.filter(k => k !== 'signature')
|
||||
.sort();
|
||||
|
||||
const parts = keys.map(k => `${k}=${params[k]}`);
|
||||
parts.push(`timestamp=${timestamp}`);
|
||||
parts.push(`nonce=${nonce}`);
|
||||
|
||||
const signString = parts.join('&');
|
||||
|
||||
// 2. 计算HMAC-SHA256签名
|
||||
const signature = crypto
|
||||
.createHmac('sha256', secretKey)
|
||||
.update(signString)
|
||||
.digest('hex');
|
||||
|
||||
return signature;
|
||||
}
|
||||
|
||||
// 使用示例
|
||||
const params = { phone: '13800138000', scene: 'register' };
|
||||
const timestamp = Math.floor(Date.now() / 1000);
|
||||
const nonce = generateRandomString(16);
|
||||
const secretKey = 'your_secret_key';
|
||||
const signature = generateSignature(params, secretKey, timestamp, nonce);
|
||||
|
||||
// 发送请求
|
||||
const requestBody = {
|
||||
phone: '13800138000',
|
||||
scene: 'register',
|
||||
timestamp: timestamp,
|
||||
nonce: nonce,
|
||||
signature: signature,
|
||||
};
|
||||
```
|
||||
|
||||
#### 方式2:编码后传输(推荐)
|
||||
|
||||
```javascript
|
||||
// 1. 生成签名(同上)
|
||||
const params = { phone: '13800138000', scene: 'register' };
|
||||
const timestamp = Math.floor(Date.now() / 1000);
|
||||
const nonce = generateRandomString(16);
|
||||
const secretKey = 'your_secret_key';
|
||||
const signature = generateSignature(params, secretKey, timestamp, nonce);
|
||||
|
||||
// 2. 构建包含所有参数的JSON对象
|
||||
const allParams = {
|
||||
phone: '13800138000',
|
||||
scene: 'register',
|
||||
timestamp: timestamp,
|
||||
nonce: nonce,
|
||||
signature: signature,
|
||||
};
|
||||
|
||||
// 3. 编码为Base64
|
||||
const jsonString = JSON.stringify(allParams);
|
||||
const encodedData = Buffer.from(jsonString).toString('base64');
|
||||
|
||||
// 4. 发送请求(只传递data字段)
|
||||
const requestBody = {
|
||||
data: encodedData,
|
||||
};
|
||||
```
|
||||
|
||||
### 浏览器 JavaScript 示例
|
||||
|
||||
参考文件:`tyapi-frontend/public/examples/javascript/sms_signature_demo.js`
|
||||
|
||||
#### 方式1:直接传递参数
|
||||
|
||||
```javascript
|
||||
async function generateSignature(params, secretKey, timestamp, nonce) {
|
||||
// 1. 构建待签名字符串
|
||||
const keys = Object.keys(params)
|
||||
.filter(k => k !== 'signature')
|
||||
.sort();
|
||||
|
||||
const parts = keys.map(k => `${k}=${params[k]}`);
|
||||
parts.push(`timestamp=${timestamp}`);
|
||||
parts.push(`nonce=${nonce}`);
|
||||
|
||||
const signString = parts.join('&');
|
||||
|
||||
// 2. 使用Web Crypto API计算HMAC-SHA256签名
|
||||
const encoder = new TextEncoder();
|
||||
const keyData = encoder.encode(secretKey);
|
||||
const messageData = encoder.encode(signString);
|
||||
|
||||
const cryptoKey = await crypto.subtle.importKey(
|
||||
'raw',
|
||||
keyData,
|
||||
{ name: 'HMAC', hash: 'SHA-256' },
|
||||
false,
|
||||
['sign']
|
||||
);
|
||||
|
||||
const signature = await crypto.subtle.sign('HMAC', cryptoKey, messageData);
|
||||
const hashArray = Array.from(new Uint8Array(signature));
|
||||
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
||||
|
||||
return hashHex;
|
||||
}
|
||||
|
||||
// 使用示例
|
||||
const params = { phone: '13800138000', scene: 'register' };
|
||||
const timestamp = Math.floor(Date.now() / 1000);
|
||||
const nonce = generateNonce(16);
|
||||
const secretKey = 'your_secret_key';
|
||||
const signature = await generateSignature(params, secretKey, timestamp, nonce);
|
||||
|
||||
// 发送请求
|
||||
const requestBody = {
|
||||
phone: '13800138000',
|
||||
scene: 'register',
|
||||
timestamp: timestamp,
|
||||
nonce: nonce,
|
||||
signature: signature,
|
||||
};
|
||||
```
|
||||
|
||||
#### 方式2:编码后传输(推荐)
|
||||
|
||||
```javascript
|
||||
// 1. 生成签名(同上)
|
||||
const params = { phone: '13800138000', scene: 'register' };
|
||||
const timestamp = Math.floor(Date.now() / 1000);
|
||||
const nonce = generateNonce(16);
|
||||
const secretKey = 'your_secret_key';
|
||||
const signature = await generateSignature(params, secretKey, timestamp, nonce);
|
||||
|
||||
// 2. 构建包含所有参数的JSON对象
|
||||
const allParams = {
|
||||
phone: '13800138000',
|
||||
scene: 'register',
|
||||
timestamp: timestamp,
|
||||
nonce: nonce,
|
||||
signature: signature,
|
||||
};
|
||||
|
||||
// 3. 编码为Base64(浏览器环境)
|
||||
const jsonString = JSON.stringify(allParams);
|
||||
const encodedData = btoa(unescape(encodeURIComponent(jsonString)));
|
||||
|
||||
// 4. 发送请求(只传递data字段)
|
||||
const requestBody = {
|
||||
data: encodedData,
|
||||
};
|
||||
```
|
||||
|
||||
## 密钥隐藏策略
|
||||
|
||||
由于前端代码可以被查看,完全隐藏密钥是不可能的。但可以通过以下方式增加破解难度:
|
||||
|
||||
### 1. 字符串拆分和拼接
|
||||
|
||||
```javascript
|
||||
function getSecretKey() {
|
||||
const part1 = 'TyApi2024';
|
||||
const part2 = 'SMSSecret';
|
||||
const part3 = 'Key!@#$%^';
|
||||
return part1 + part2 + part3;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 字符数组拼接
|
||||
|
||||
```javascript
|
||||
function getSecretKey() {
|
||||
const chars = ['T', 'y', 'A', 'p', 'i', ...];
|
||||
return chars.join('');
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Base64编码混淆
|
||||
|
||||
```javascript
|
||||
function getSecretKey() {
|
||||
const encoded = 'base64_encoded_string';
|
||||
return atob(encoded);
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 代码混淆
|
||||
|
||||
使用构建工具(如webpack、rollup等)进行代码混淆和压缩,使密钥更难被发现。
|
||||
|
||||
### 5. 后端代理(推荐)
|
||||
|
||||
将签名逻辑放在后端代理接口中,前端只调用代理接口,不直接包含密钥。
|
||||
|
||||
## 安全建议
|
||||
|
||||
1. **定期更换密钥**:建议每3-6个月更换一次签名密钥
|
||||
2. **监控异常请求**:监控签名验证失败的请求,及时发现攻击行为
|
||||
3. **结合其他防护措施**:
|
||||
- IP限流
|
||||
- 设备指纹识别
|
||||
- 验证码(图形验证码)
|
||||
- 行为分析
|
||||
|
||||
4. **日志记录**:记录所有签名验证失败的请求,包括IP、User-Agent等信息
|
||||
|
||||
## 错误处理
|
||||
|
||||
### 常见错误
|
||||
|
||||
1. **签名字段缺失**:返回 `"签名字段缺失"`
|
||||
2. **时间戳无效**:返回 `"时间戳无效"`
|
||||
3. **请求已过期**:返回 `"请求已过期,时间戳超出容差范围"`
|
||||
4. **签名验证失败**:返回 `"签名验证失败"`
|
||||
|
||||
### 时间戳容差
|
||||
|
||||
系统允许的时间戳容差为 **5分钟**(300秒)。如果请求时间戳与服务器时间差超过5分钟,请求将被拒绝。
|
||||
|
||||
## 测试
|
||||
|
||||
### 测试签名生成
|
||||
|
||||
```bash
|
||||
# 使用Node.js示例
|
||||
node tyapi-frontend/public/examples/nodejs/sms_signature_demo.js
|
||||
```
|
||||
|
||||
### 测试API调用
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/api/v1/users/send-code \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"phone": "13800138000",
|
||||
"scene": "register",
|
||||
"timestamp": 1704067200,
|
||||
"nonce": "a1b2c3d4e5f6g7h8",
|
||||
"signature": "计算得到的签名"
|
||||
}'
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **时间同步**:确保客户端和服务器时间同步,避免时间戳验证失败
|
||||
2. **随机数唯一性**:每次请求的nonce应该是唯一的,可以使用UUID或时间戳+随机数
|
||||
3. **密钥安全**:生产环境密钥不要提交到代码仓库,应使用环境变量或密钥管理服务
|
||||
4. **向后兼容**:如果需要在开发环境禁用签名验证,可以设置 `signature_enabled: false`
|
||||
|
||||
## 相关文件
|
||||
|
||||
- 后端签名工具:`internal/shared/crypto/signature.go`
|
||||
- 后端Handler:`internal/infrastructure/http/handlers/user_handler.go`
|
||||
- 配置结构:`internal/config/config.go`
|
||||
- Node.js示例:`tyapi-frontend/public/examples/nodejs/sms_signature_demo.js`
|
||||
- 浏览器示例:`tyapi-frontend/public/examples/javascript/sms_signature_demo.js`
|
||||
|
||||
17
go.mod
17
go.mod
@@ -3,7 +3,12 @@ module tyapi-server
|
||||
go 1.23.4
|
||||
|
||||
require (
|
||||
github.com/alibabacloud-go/captcha-20230305 v1.1.3
|
||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.13
|
||||
github.com/alibabacloud-go/tea v1.3.13
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.63.107
|
||||
github.com/chromedp/cdproto v0.0.0-20250319231242-a755498943c8
|
||||
github.com/chromedp/chromedp v0.13.2
|
||||
github.com/gin-contrib/cors v1.7.6
|
||||
github.com/gin-gonic/gin v1.10.1
|
||||
github.com/go-playground/locales v0.14.1
|
||||
@@ -13,6 +18,7 @@ require (
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/hibiken/asynq v0.25.1
|
||||
github.com/jung-kurt/gofpdf/v2 v2.17.3
|
||||
github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20260313013624-04e51e218220
|
||||
github.com/prometheus/client_golang v1.22.0
|
||||
github.com/qiniu/go-sdk/v7 v7.25.4
|
||||
github.com/redis/go-redis/v9 v9.11.0
|
||||
@@ -46,11 +52,17 @@ require (
|
||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||
github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82 // indirect
|
||||
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect
|
||||
github.com/alibabacloud-go/debug v1.0.1 // indirect
|
||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.7 // indirect
|
||||
github.com/aliyun/credentials-go v1.4.5 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bytedance/sonic v1.13.3 // indirect
|
||||
github.com/bytedance/sonic/loader v0.2.4 // indirect
|
||||
github.com/cenkalti/backoff/v5 v5.0.2 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/chromedp/sysutil v1.1.0 // indirect
|
||||
github.com/clbanning/mxj/v2 v2.7.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
@@ -58,6 +70,7 @@ require (
|
||||
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
|
||||
github.com/gammazero/toposort v0.1.1 // indirect
|
||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||
github.com/go-json-experiment/json v0.0.0-20250211171154-1ae217ad3535 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
@@ -65,6 +78,9 @@ require (
|
||||
github.com/go-openapi/spec v0.20.4 // indirect
|
||||
github.com/go-openapi/swag v0.19.15 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
|
||||
github.com/gobwas/httphead v0.1.0 // indirect
|
||||
github.com/gobwas/pool v0.2.1 // indirect
|
||||
github.com/gobwas/ws v1.4.0 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/gofrs/flock v0.8.1 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect
|
||||
@@ -104,6 +120,7 @@ require (
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.0 // indirect
|
||||
github.com/tiendc/go-deepcopy v1.6.0 // indirect
|
||||
github.com/tjfoc/gmsm v1.4.1 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.3.0 // indirect
|
||||
github.com/xuri/efp v0.0.1 // indirect
|
||||
|
||||
205
go.sum
205
go.sum
@@ -1,4 +1,6 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
|
||||
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
@@ -14,8 +16,54 @@ github.com/agiledragon/gomonkey v2.0.2+incompatible/go.mod h1:2NGfXu1a80LLr2cmWX
|
||||
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
|
||||
github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82 h1:7dONQ3WNZ1zy960TmkxJPuwoolZwL7xKtpcM04MBnt4=
|
||||
github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82/go.mod h1:nLnM0KdK1CmygvjpDUO6m1TjSsiQtL61juhNsvV/JVI=
|
||||
github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6 h1:eIf+iGJxdU4U9ypaUfbtOWCsZSbTb8AUHvyPrxu6mAA=
|
||||
github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6/go.mod h1:4EUIoxs/do24zMOGGqYVWgw0s9NtiylnJglOeEB5UJo=
|
||||
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc=
|
||||
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 h1:zE8vH9C7JiZLNJJQ5OwjU9mSi4T9ef9u3BURT6LCLC8=
|
||||
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5/go.mod h1:tWnyE9AjF8J8qqLk645oUmVUnFybApTQWklQmi5tY6g=
|
||||
github.com/alibabacloud-go/captcha-20230305 v1.1.3 h1:0Aobw12m3x28aeDMPjwjXsfF8MuLvRjlQ4Hhoy5hFOY=
|
||||
github.com/alibabacloud-go/captcha-20230305 v1.1.3/go.mod h1:ydzBIN2OiM7eeQPpAFyBrv1H5TY1MtUP2rQig44C4UQ=
|
||||
github.com/alibabacloud-go/darabonba-array v0.1.0 h1:vR8s7b1fWAQIjEjWnuF0JiKsCvclSRTfDzZHTYqfufY=
|
||||
github.com/alibabacloud-go/darabonba-array v0.1.0/go.mod h1:BLKxr0brnggqOJPqT09DFJ8g3fsDshapUD3C3aOEFaI=
|
||||
github.com/alibabacloud-go/darabonba-encode-util v0.0.2 h1:1uJGrbsGEVqWcWxrS9MyC2NG0Ax+GpOM5gtupki31XE=
|
||||
github.com/alibabacloud-go/darabonba-encode-util v0.0.2/go.mod h1:JiW9higWHYXm7F4PKuMgEUETNZasrDM6vqVr/Can7H8=
|
||||
github.com/alibabacloud-go/darabonba-map v0.0.2 h1:qvPnGB4+dJbJIxOOfawxzF3hzMnIpjmafa0qOTp6udc=
|
||||
github.com/alibabacloud-go/darabonba-map v0.0.2/go.mod h1:28AJaX8FOE/ym8OUFWga+MtEzBunJwQGceGQlvaPGPc=
|
||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.13 h1:Q00FU3H94Ts0ZIHDmY+fYGgB7dV9D/YX6FGsgorQPgw=
|
||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.13/go.mod h1:lxFGfobinVsQ49ntjpgWghXmIF0/Sm4+wvBJ1h5RtaE=
|
||||
github.com/alibabacloud-go/darabonba-signature-util v0.0.7 h1:UzCnKvsjPFzApvODDNEYqBHMFt1w98wC7FOo0InLyxg=
|
||||
github.com/alibabacloud-go/darabonba-signature-util v0.0.7/go.mod h1:oUzCYV2fcCH797xKdL6BDH8ADIHlzrtKVjeRtunBNTQ=
|
||||
github.com/alibabacloud-go/darabonba-string v1.0.2 h1:E714wms5ibdzCqGeYJ9JCFywE5nDyvIXIIQbZVFkkqo=
|
||||
github.com/alibabacloud-go/darabonba-string v1.0.2/go.mod h1:93cTfV3vuPhhEwGGpKKqhVW4jLe7tDpo3LUM0i0g6mA=
|
||||
github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68/go.mod h1:6pb/Qy8c+lqua8cFpEy7g39NRRqOWc3rOwAy8m5Y2BY=
|
||||
github.com/alibabacloud-go/debug v1.0.0/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc=
|
||||
github.com/alibabacloud-go/debug v1.0.1 h1:MsW9SmUtbb1Fnt3ieC6NNZi6aEwrXfDksD4QA6GSbPg=
|
||||
github.com/alibabacloud-go/debug v1.0.1/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc=
|
||||
github.com/alibabacloud-go/endpoint-util v1.1.0 h1:r/4D3VSw888XGaeNpP994zDUaxdgTSHBbVfZlzf6b5Q=
|
||||
github.com/alibabacloud-go/endpoint-util v1.1.0/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE=
|
||||
github.com/alibabacloud-go/openapi-util v0.1.0 h1:0z75cIULkDrdEhkLWgi9tnLe+KhAFE/r5Pb3312/eAY=
|
||||
github.com/alibabacloud-go/openapi-util v0.1.0/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws=
|
||||
github.com/alibabacloud-go/tea v1.1.0/go.mod h1:IkGyUSX4Ba1V+k4pCtJUc6jDpZLFph9QMy2VUPTwukg=
|
||||
github.com/alibabacloud-go/tea v1.1.7/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
|
||||
github.com/alibabacloud-go/tea v1.1.8/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
|
||||
github.com/alibabacloud-go/tea v1.1.11/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
|
||||
github.com/alibabacloud-go/tea v1.1.17/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A=
|
||||
github.com/alibabacloud-go/tea v1.1.20/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A=
|
||||
github.com/alibabacloud-go/tea v1.2.2/go.mod h1:CF3vOzEMAG+bR4WOql8gc2G9H3EkH3ZLAQdpmpXMgwk=
|
||||
github.com/alibabacloud-go/tea v1.3.13 h1:WhGy6LIXaMbBM6VBYcsDCz6K/TPsT1Ri2hPmmZffZ94=
|
||||
github.com/alibabacloud-go/tea v1.3.13/go.mod h1:A560v/JTQ1n5zklt2BEpurJzZTI8TUT+Psg2drWlxRg=
|
||||
github.com/alibabacloud-go/tea-utils v1.3.1 h1:iWQeRzRheqCMuiF3+XkfybB3kTgUXkXX+JMrqfLeB2I=
|
||||
github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE=
|
||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.5/go.mod h1:dL6vbUT35E4F4bFTHL845eUloqaerYBYPsdWR2/jhe4=
|
||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.7 h1:WDx5qW3Xa5ZgJ1c8NfqJkF6w+AU5wB8835UdhPr6Ax0=
|
||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.7/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.63.107 h1:qagvUyrgOnBIlVRQWOyCZGVKUIYbMBdGdJ104vBpRFU=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.63.107/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ=
|
||||
github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw=
|
||||
github.com/aliyun/credentials-go v1.3.1/go.mod h1:8jKYhQuDawt8x2+fusqa1Y6mPxemTsBEN04dgcAcYz0=
|
||||
github.com/aliyun/credentials-go v1.3.6/go.mod h1:1LxUuX7L5YrZUWzBrRyk0SwSdH4OmPrib8NVePL3fxM=
|
||||
github.com/aliyun/credentials-go v1.4.5 h1:O76WYKgdy1oQYYiJkERjlA2dxGuvLRrzuO2ScrtGWSk=
|
||||
github.com/aliyun/credentials-go v1.4.5/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
@@ -29,11 +77,22 @@ github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCN
|
||||
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8=
|
||||
github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chromedp/cdproto v0.0.0-20250319231242-a755498943c8 h1:AqW2bDQf67Zbq6Tpop/+yJSIknxhiQecO2B8jNYTAPs=
|
||||
github.com/chromedp/cdproto v0.0.0-20250319231242-a755498943c8/go.mod h1:NItd7aLkcfOA/dcMXvl8p1u+lQqioRMq/SqDp71Pb/k=
|
||||
github.com/chromedp/chromedp v0.13.2 h1:f6sZFFzCzPLvWSzeuXQBgONKG7zPq54YfEyEj0EplOY=
|
||||
github.com/chromedp/chromedp v0.13.2/go.mod h1:khsDP9OP20GrowpJfZ7N05iGCwcAYxk7qf9AZBzR3Qw=
|
||||
github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipwM=
|
||||
github.com/chromedp/sysutil v1.1.0/go.mod h1:WiThHUdltqCNKGc4gaU50XgYjwjYIhKWoHGPTUfWTJ8=
|
||||
github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME=
|
||||
github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
||||
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/dave/jennifer v1.6.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -41,6 +100,9 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
@@ -59,6 +121,8 @@ github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fq
|
||||
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
||||
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-json-experiment/json v0.0.0-20250211171154-1ae217ad3535 h1:yE7argOs92u+sSCRgqqe6eF+cDaVhSPlioy1UkA0p/w=
|
||||
github.com/go-json-experiment/json v0.0.0-20250211171154-1ae217ad3535/go.mod h1:BWmvoE1Xia34f3l/ibJweyhrT+aROb/FQ6d+37F0e2s=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
@@ -90,6 +154,12 @@ github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc
|
||||
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
||||
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
|
||||
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
|
||||
@@ -98,14 +168,32 @@ github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptG
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww=
|
||||
@@ -132,8 +220,10 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGw
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
|
||||
github.com/jung-kurt/gofpdf/v2 v2.17.3 h1:otZXZby2gXJ7uU6pzprXHq/R57lsHLi0WtH79VabWxY=
|
||||
github.com/jung-kurt/gofpdf/v2 v2.17.3/go.mod h1:Qx8ZNg4cNsO5i6uLDiBngnm+ii/FjtAqjRNO6drsoYU=
|
||||
@@ -154,10 +244,13 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20260313013624-04e51e218220 h1:FLQyP/6tTsTEtAhcIq/kS/zkDEMdOMon0I70pXVehOU=
|
||||
github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20260313013624-04e51e218220/go.mod h1:+mNMTBuDMdEGhWzoQgc6kBdqeaQpWh5ba8zqmp2MxCU=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
|
||||
@@ -167,6 +260,8 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
@@ -174,6 +269,7 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A=
|
||||
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mod h1:AC62GU6hc0BrNm+9RK9VSiwa/EUe1bkIeFORAMcHvJU=
|
||||
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
@@ -183,6 +279,7 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
|
||||
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
|
||||
@@ -220,6 +317,9 @@ github.com/smartwalle/ngx v1.0.9 h1:pUXDvWRZJIHVrCKA1uZ15YwNti+5P4GuJGbpJ4WvpMw=
|
||||
github.com/smartwalle/ngx v1.0.9/go.mod h1:mx/nz2Pk5j+RBs7t6u6k22MPiBG/8CtOMpCnALIG8Y0=
|
||||
github.com/smartwalle/nsign v1.0.9 h1:8poAgG7zBd8HkZy9RQDwasC6XZvJpDGQWSjzL2FZL6E=
|
||||
github.com/smartwalle/nsign v1.0.9/go.mod h1:eY6I4CJlyNdVMP+t6z1H6Jpd4m5/V+8xi44ufSTxXgc=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||
github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs=
|
||||
@@ -231,10 +331,12 @@ github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
|
||||
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
|
||||
github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
@@ -258,6 +360,9 @@ github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tiendc/go-deepcopy v1.6.0 h1:0UtfV/imoCwlLxVsyfUd4hNHnB3drXsfle+wzSCA5Wo=
|
||||
github.com/tiendc/go-deepcopy v1.6.0/go.mod h1:toXoeQoUqXOOS/X4sKuiAoSk6elIdqc0pN7MTgOOo2I=
|
||||
github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w=
|
||||
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
|
||||
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o=
|
||||
@@ -274,6 +379,8 @@ github.com/xuri/excelize/v2 v2.9.1 h1:VdSGk+rraGmgLHGFaGG9/9IWu1nj4ufjJ7uwMDtj8Q
|
||||
github.com/xuri/excelize/v2 v2.9.1/go.mod h1:x7L6pKz2dvo9ejrRuD8Lnl98z4JLt0TGAwjhW+EiP8s=
|
||||
github.com/xuri/nfp v0.0.1 h1:MDamSGatIvp8uOmDP8FnmjuQpu90NzdJxo7242ANR9Q=
|
||||
github.com/xuri/nfp v0.0.1/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
@@ -309,12 +416,24 @@ golang.org/x/arch v0.18.0 h1:WN9poc33zL4AzGxqf8VtpKUnGvMi8O9lhNyBMF/85qc=
|
||||
golang.org/x/arch v0.18.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
||||
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
@@ -323,26 +442,63 @@ golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMx
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ=
|
||||
golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
|
||||
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
|
||||
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -350,42 +506,88 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
|
||||
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
|
||||
gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
|
||||
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
|
||||
gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
|
||||
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -394,6 +596,7 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||
@@ -412,6 +615,8 @@ gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
|
||||
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
|
||||
gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs=
|
||||
gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
modernc.org/fileutil v1.0.0 h1:Z1AFLZwl6BO8A5NldQg/xTSjGLetp+1Ubvl4alfGx8w=
|
||||
modernc.org/fileutil v1.0.0/go.mod h1:JHsWpkrk/CnVV1H/eGlFf85BEpfkrp56ro8nojIq9Q8=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
|
||||
@@ -26,9 +26,11 @@ import (
|
||||
articleEntities "tyapi-server/internal/domains/article/entities"
|
||||
|
||||
// 统计域实体
|
||||
securityEntities "tyapi-server/internal/domains/security/entities"
|
||||
statisticsEntities "tyapi-server/internal/domains/statistics/entities"
|
||||
|
||||
apiEntities "tyapi-server/internal/domains/api/entities"
|
||||
subordinateEntities "tyapi-server/internal/domains/subordinate/entities"
|
||||
"tyapi-server/internal/infrastructure/database"
|
||||
taskEntities "tyapi-server/internal/infrastructure/task/entities"
|
||||
)
|
||||
@@ -256,10 +258,20 @@ func (a *Application) autoMigrate(db *gorm.DB) error {
|
||||
&statisticsEntities.StatisticsMetric{},
|
||||
&statisticsEntities.StatisticsDashboard{},
|
||||
&statisticsEntities.StatisticsReport{},
|
||||
&securityEntities.SuspiciousIPRecord{},
|
||||
|
||||
// api
|
||||
&apiEntities.ApiUser{},
|
||||
&apiEntities.ApiCall{},
|
||||
&apiEntities.Report{},
|
||||
|
||||
// 下属账号域
|
||||
&subordinateEntities.SubordinateInvitation{},
|
||||
&subordinateEntities.UserSubordinateLink{},
|
||||
&subordinateEntities.SubordinateWalletAllocation{},
|
||||
&subordinateEntities.SubordinateQuotaPurchase{},
|
||||
&subordinateEntities.UserProductQuotaAccount{},
|
||||
&subordinateEntities.UserProductQuotaLedger{},
|
||||
|
||||
// 任务域
|
||||
&taskEntities.AsyncTask{},
|
||||
|
||||
@@ -20,6 +20,8 @@ import (
|
||||
finance_services "tyapi-server/internal/domains/finance/services"
|
||||
product_entities "tyapi-server/internal/domains/product/entities"
|
||||
product_services "tyapi-server/internal/domains/product/services"
|
||||
subordinate_entities "tyapi-server/internal/domains/subordinate/entities"
|
||||
subordinate_repositories "tyapi-server/internal/domains/subordinate/repositories"
|
||||
user_repositories "tyapi-server/internal/domains/user/repositories"
|
||||
task_entities "tyapi-server/internal/infrastructure/task/entities"
|
||||
"tyapi-server/internal/infrastructure/task/interfaces"
|
||||
@@ -93,6 +95,7 @@ type ApiApplicationServiceImpl struct {
|
||||
walletService finance_services.WalletAggregateService
|
||||
subscriptionService *product_services.ProductSubscriptionService
|
||||
balanceAlertService finance_services.BalanceAlertService
|
||||
subordinateRepo subordinate_repositories.SubordinateRepository
|
||||
}
|
||||
|
||||
func NewApiApplicationService(
|
||||
@@ -112,6 +115,7 @@ func NewApiApplicationService(
|
||||
subscriptionService *product_services.ProductSubscriptionService,
|
||||
exportManager *export.ExportManager,
|
||||
balanceAlertService finance_services.BalanceAlertService,
|
||||
subordinateRepo subordinate_repositories.SubordinateRepository,
|
||||
) ApiApplicationService {
|
||||
service := &ApiApplicationServiceImpl{
|
||||
apiCallService: apiCallService,
|
||||
@@ -130,6 +134,7 @@ func NewApiApplicationService(
|
||||
walletService: walletService,
|
||||
subscriptionService: subscriptionService,
|
||||
balanceAlertService: balanceAlertService,
|
||||
subordinateRepo: subordinateRepo,
|
||||
}
|
||||
|
||||
return service
|
||||
@@ -226,13 +231,19 @@ func (s *ApiApplicationServiceImpl) validateApiCall(ctx context.Context, cmd *co
|
||||
|
||||
// 4. 验证IP白名单(非开发环境)
|
||||
if !s.config.App.IsDevelopment() && !cmd.Options.IsDebug {
|
||||
whiteListIPs := make([]string, 0, len(apiUser.WhiteList))
|
||||
for _, item := range apiUser.WhiteList {
|
||||
whiteListIPs = append(whiteListIPs, item.IPAddress)
|
||||
}
|
||||
|
||||
// 添加调试日志
|
||||
s.logger.Info("开始验证白名单",
|
||||
zap.String("userId", apiUser.UserId),
|
||||
zap.String("clientIP", cmd.ClientIP),
|
||||
zap.Bool("isDevelopment", s.config.App.IsDevelopment()),
|
||||
zap.Bool("isDebug", cmd.Options.IsDebug),
|
||||
zap.Int("whiteListCount", len(apiUser.WhiteList)))
|
||||
zap.Int("whiteListCount", len(apiUser.WhiteList)),
|
||||
zap.Strings("whiteListIPs", whiteListIPs))
|
||||
|
||||
// 输出白名单详细信息(用于调试)
|
||||
for idx, item := range apiUser.WhiteList {
|
||||
@@ -246,24 +257,27 @@ func (s *ApiApplicationServiceImpl) validateApiCall(ctx context.Context, cmd *co
|
||||
s.logger.Error("IP不在白名单内",
|
||||
zap.String("userId", apiUser.UserId),
|
||||
zap.String("ip", cmd.ClientIP),
|
||||
zap.Int("whiteListSize", len(apiUser.WhiteList)))
|
||||
zap.Int("whiteListSize", len(apiUser.WhiteList)),
|
||||
zap.Strings("whiteListIPs", whiteListIPs))
|
||||
return nil, ErrInvalidIP
|
||||
}
|
||||
s.logger.Info("白名单验证通过", zap.String("ip", cmd.ClientIP))
|
||||
s.logger.Info("白名单验证通过",
|
||||
zap.String("ip", cmd.ClientIP),
|
||||
zap.Strings("whiteListIPs", whiteListIPs))
|
||||
}
|
||||
|
||||
// 5. 验证钱包状态
|
||||
if err := s.validateWalletStatus(ctx, apiUser.UserId, product); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 6. 验证订阅状态并获取订阅信息
|
||||
// 5. 先验证订阅(与扣费金额一致,便于余额预检使用订阅价)
|
||||
subscription, err := s.validateSubscriptionStatus(ctx, apiUser.UserId, product)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result.SetSubscription(subscription)
|
||||
|
||||
// 6. 验证钱包状态(有订阅时按订阅价与目录价取较大者预检,避免代配价高于目录价时误判余额不足)
|
||||
if err := s.validateWalletStatus(ctx, apiUser.UserId, product, subscription); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 7. 解密参数
|
||||
requestParams, err := crypto.AesDecrypt(cmd.Data, apiUser.SecretKey)
|
||||
if err != nil {
|
||||
@@ -277,6 +291,44 @@ func (s *ApiApplicationServiceImpl) validateApiCall(ctx context.Context, cmd *co
|
||||
s.logger.Error("解析解密参数失败", zap.Error(err))
|
||||
return nil, ErrDecryptFail
|
||||
}
|
||||
|
||||
// 7.1 子账号主账号 AccessId 校验(仅在请求参数中携带时生效)
|
||||
if parentAccessID, ok := extractParentAccessID(paramsMap); ok {
|
||||
if s.subordinateRepo == nil {
|
||||
s.logger.Error("子账号主账号AccessId校验失败:subordinateRepo未初始化")
|
||||
return nil, ErrSystem
|
||||
}
|
||||
|
||||
link, err := s.subordinateRepo.FindLinkByChildUserID(ctx, apiUser.UserId)
|
||||
if err != nil {
|
||||
s.logger.Error("查询子账号主从关系失败",
|
||||
zap.String("user_id", apiUser.UserId),
|
||||
zap.Error(err))
|
||||
return nil, ErrSystem
|
||||
}
|
||||
if link == nil {
|
||||
s.logger.Warn("子账号主账号AccessId校验失败:未找到主从关系",
|
||||
zap.String("user_id", apiUser.UserId),
|
||||
zap.String("parent_access_id", parentAccessID))
|
||||
return nil, ErrSubordinateLinkNotFound
|
||||
}
|
||||
|
||||
parentApiUser, err := s.apiUserService.LoadApiUserByUserId(ctx, link.ParentUserID)
|
||||
if err != nil {
|
||||
s.logger.Error("加载主账号API用户失败",
|
||||
zap.String("child_user_id", apiUser.UserId),
|
||||
zap.String("parent_user_id", link.ParentUserID),
|
||||
zap.Error(err))
|
||||
return nil, ErrSystem
|
||||
}
|
||||
if parentApiUser == nil || parentApiUser.AccessId != parentAccessID {
|
||||
s.logger.Warn("子账号主账号AccessId校验失败:主账号不匹配",
|
||||
zap.String("child_user_id", apiUser.UserId),
|
||||
zap.String("parent_user_id", link.ParentUserID),
|
||||
zap.String("parent_access_id", parentAccessID))
|
||||
return nil, ErrSubordinateParentMismatch
|
||||
}
|
||||
}
|
||||
result.SetRequestParams(paramsMap)
|
||||
|
||||
// 8. 获取合同信息
|
||||
@@ -293,6 +345,26 @@ func (s *ApiApplicationServiceImpl) validateApiCall(ctx context.Context, cmd *co
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// extractParentAccessID 从解密参数中提取主账号 AccessId
|
||||
// 仅支持键名:master_accessid
|
||||
func extractParentAccessID(params map[string]interface{}) (string, bool) {
|
||||
if len(params) == 0 {
|
||||
return "", false
|
||||
}
|
||||
|
||||
value, ok := params["master_accessid"]
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
if str, ok := value.(string); ok {
|
||||
str = strings.TrimSpace(str)
|
||||
if str != "" {
|
||||
return str, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// callExternalApi 同步调用外部API
|
||||
func (s *ApiApplicationServiceImpl) callExternalApi(ctx context.Context, cmd *commands.ApiCallCommand, validation *dto.ApiCallValidationResult) (string, error) {
|
||||
// 创建CallContext
|
||||
@@ -319,15 +391,28 @@ func (s *ApiApplicationServiceImpl) callExternalApi(ctx context.Context, cmd *co
|
||||
callContext)
|
||||
|
||||
if err != nil {
|
||||
mappedErrorType := entities.ApiCallErrorSystem
|
||||
if errors.Is(err, processors.ErrDatasource) {
|
||||
return "", ErrSystem
|
||||
mappedErrorType = entities.ApiCallErrorDatasource
|
||||
} else if errors.Is(err, processors.ErrInvalidParam) {
|
||||
return "", ErrInvalidParam
|
||||
mappedErrorType = entities.ApiCallErrorInvalidParam
|
||||
} else if errors.Is(err, processors.ErrNotFound) {
|
||||
return "", ErrQueryEmpty
|
||||
} else {
|
||||
return "", ErrSystem
|
||||
mappedErrorType = entities.ApiCallErrorQueryEmpty
|
||||
}
|
||||
|
||||
s.logger.Error("调用第三方接口失败",
|
||||
zap.String("transaction_id", validation.ApiCall.TransactionId),
|
||||
zap.String("api_name", cmd.ApiName),
|
||||
zap.String("error_type", mappedErrorType),
|
||||
zap.Error(err))
|
||||
|
||||
if mappedErrorType == entities.ApiCallErrorInvalidParam {
|
||||
return "", ErrInvalidParam
|
||||
}
|
||||
if mappedErrorType == entities.ApiCallErrorQueryEmpty {
|
||||
return "", ErrQueryEmpty
|
||||
}
|
||||
return "", ErrSystem
|
||||
}
|
||||
|
||||
return string(response), nil
|
||||
@@ -609,42 +694,7 @@ func (s *ApiApplicationServiceImpl) GetUserApiCalls(ctx context.Context, userID
|
||||
// 转换为响应DTO
|
||||
var items []dto.ApiCallRecordResponse
|
||||
for _, call := range calls {
|
||||
// 解密请求参数
|
||||
var requestParamsStr string = call.RequestParams // 默认使用原始值
|
||||
if call.UserId != nil && *call.UserId != "" {
|
||||
// 获取用户的API密钥信息
|
||||
apiUser, err := s.apiUserService.LoadApiUserByUserId(ctx, *call.UserId)
|
||||
if err != nil {
|
||||
s.logger.Error("获取用户API信息失败",
|
||||
zap.Error(err),
|
||||
zap.String("call_id", call.ID),
|
||||
zap.String("user_id", *call.UserId))
|
||||
// 获取失败时使用原始值
|
||||
} else if apiUser.SecretKey != "" {
|
||||
// 使用用户的SecretKey解密请求参数
|
||||
decryptedParams, err := s.DecryptParams(ctx, *call.UserId, &commands.DecryptCommand{
|
||||
EncryptedData: call.RequestParams,
|
||||
SecretKey: apiUser.SecretKey,
|
||||
})
|
||||
if err != nil {
|
||||
s.logger.Error("解密请求参数失败",
|
||||
zap.Error(err),
|
||||
zap.String("call_id", call.ID),
|
||||
zap.String("user_id", *call.UserId))
|
||||
// 解密失败时使用原始值
|
||||
} else {
|
||||
// 将解密后的数据转换为JSON字符串
|
||||
if jsonBytes, err := json.Marshal(decryptedParams); err == nil {
|
||||
requestParamsStr = string(jsonBytes)
|
||||
} else {
|
||||
s.logger.Error("序列化解密参数失败",
|
||||
zap.Error(err),
|
||||
zap.String("call_id", call.ID))
|
||||
// 序列化失败时使用原始值
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
requestParamsStr := call.RequestParams
|
||||
|
||||
item := dto.ApiCallRecordResponse{
|
||||
ID: call.ID,
|
||||
@@ -713,50 +763,50 @@ func (s *ApiApplicationServiceImpl) GetAdminApiCalls(ctx context.Context, filter
|
||||
continue
|
||||
}
|
||||
|
||||
// 解密请求参数
|
||||
var requestParamsStr string = call.RequestParams // 默认使用原始值
|
||||
if call.UserId != nil && *call.UserId != "" {
|
||||
// 获取用户的API密钥信息
|
||||
apiUser, err := s.apiUserService.LoadApiUserByUserId(ctx, *call.UserId)
|
||||
if err != nil {
|
||||
s.logger.Error("获取用户API信息失败",
|
||||
zap.Error(err),
|
||||
zap.String("call_id", call.ID),
|
||||
zap.String("user_id", *call.UserId))
|
||||
// 获取失败时使用原始值
|
||||
} else if apiUser.SecretKey != "" {
|
||||
// 使用用户的SecretKey解密请求参数
|
||||
decryptedParams, err := s.DecryptParams(ctx, *call.UserId, &commands.DecryptCommand{
|
||||
EncryptedData: call.RequestParams,
|
||||
SecretKey: apiUser.SecretKey,
|
||||
})
|
||||
if err != nil {
|
||||
s.logger.Error("解密请求参数失败",
|
||||
zap.Error(err),
|
||||
zap.String("call_id", call.ID),
|
||||
zap.String("user_id", *call.UserId))
|
||||
// 解密失败时使用原始值
|
||||
} else {
|
||||
// 将解密后的数据转换为JSON字符串
|
||||
if jsonBytes, err := json.Marshal(decryptedParams); err == nil {
|
||||
requestParamsStr = string(jsonBytes)
|
||||
} else {
|
||||
s.logger.Error("序列化解密参数失败",
|
||||
zap.Error(err),
|
||||
zap.String("call_id", call.ID))
|
||||
// 序列化失败时使用原始值
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// // 解密请求参数
|
||||
// var requestParamsStr string = call.RequestParams // 默认使用原始值
|
||||
// if call.UserId != nil && *call.UserId != "" {
|
||||
// // 获取用户的API密钥信息
|
||||
// apiUser, err := s.apiUserService.LoadApiUserByUserId(ctx, *call.UserId)
|
||||
// if err != nil {
|
||||
// s.logger.Error("获取用户API信息失败",
|
||||
// zap.Error(err),
|
||||
// zap.String("call_id", call.ID),
|
||||
// zap.String("user_id", *call.UserId))
|
||||
// // 获取失败时使用原始值
|
||||
// } else if apiUser.SecretKey != "" {
|
||||
// // 使用用户的SecretKey解密请求参数
|
||||
// decryptedParams, err := s.DecryptParams(ctx, *call.UserId, &commands.DecryptCommand{
|
||||
// EncryptedData: call.RequestParams,
|
||||
// SecretKey: apiUser.SecretKey,
|
||||
// })
|
||||
// if err != nil {
|
||||
// s.logger.Error("解密请求参数失败",
|
||||
// zap.Error(err),
|
||||
// zap.String("call_id", call.ID),
|
||||
// zap.String("user_id", *call.UserId))
|
||||
// // 解密失败时使用原始值
|
||||
// } else {
|
||||
// // 将解密后的数据转换为JSON字符串
|
||||
// if jsonBytes, err := json.Marshal(decryptedParams); err == nil {
|
||||
// requestParamsStr = string(jsonBytes)
|
||||
// } else {
|
||||
// s.logger.Error("序列化解密参数失败",
|
||||
// zap.Error(err),
|
||||
// zap.String("call_id", call.ID))
|
||||
// // 序列化失败时使用原始值
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
item := dto.ApiCallRecordResponse{
|
||||
ID: call.ID,
|
||||
AccessId: call.AccessId,
|
||||
TransactionId: call.TransactionId,
|
||||
ClientIp: call.ClientIp,
|
||||
RequestParams: requestParamsStr,
|
||||
Status: call.Status,
|
||||
// RequestParams: requestParamsStr,
|
||||
Status: call.Status,
|
||||
}
|
||||
|
||||
// 安全设置用户ID
|
||||
@@ -1121,6 +1171,24 @@ func (s *ApiApplicationServiceImpl) ProcessDeduction(ctx context.Context, cmd *c
|
||||
return err
|
||||
}
|
||||
|
||||
// 优先扣减产品额度(若存在且可用),避免子账号有额度却因钱包余额不足失败
|
||||
deductedByQuota, err := s.tryDeductQuota(ctx, cmd.UserID, cmd.ProductID, cmd.ApiCallID, cmd.TransactionID)
|
||||
if err != nil {
|
||||
s.logger.Error("额度扣减失败",
|
||||
zap.String("transaction_id", cmd.TransactionID),
|
||||
zap.String("user_id", cmd.UserID),
|
||||
zap.String("product_id", cmd.ProductID),
|
||||
zap.Error(err))
|
||||
return err
|
||||
}
|
||||
if deductedByQuota {
|
||||
s.logger.Info("额度扣减成功",
|
||||
zap.String("transaction_id", cmd.TransactionID),
|
||||
zap.String("user_id", cmd.UserID),
|
||||
zap.String("product_id", cmd.ProductID))
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := s.walletService.Deduct(ctx, cmd.UserID, amount, cmd.ApiCallID, cmd.TransactionID, cmd.ProductID); err != nil {
|
||||
s.logger.Error("扣款处理失败",
|
||||
zap.String("transaction_id", cmd.TransactionID),
|
||||
@@ -1214,7 +1282,26 @@ func (s *ApiApplicationServiceImpl) ProcessCompensation(ctx context.Context, cmd
|
||||
}
|
||||
|
||||
// validateWalletStatus 验证钱包状态
|
||||
func (s *ApiApplicationServiceImpl) validateWalletStatus(ctx context.Context, userID string, product *product_entities.Product) error {
|
||||
func (s *ApiApplicationServiceImpl) validateWalletStatus(ctx context.Context, userID string, product *product_entities.Product, subscription *product_entities.Subscription) error {
|
||||
// 若用户在该产品有可用额度,则本次调用将走额度扣减,不再要求钱包余额预检通过
|
||||
if s.subordinateRepo != nil {
|
||||
quotaAccount, err := s.subordinateRepo.FindQuotaAccount(ctx, userID, product.ID)
|
||||
if err != nil {
|
||||
s.logger.Error("查询额度账户失败",
|
||||
zap.String("user_id", userID),
|
||||
zap.String("product_id", product.ID),
|
||||
zap.Error(err))
|
||||
return ErrSystem
|
||||
}
|
||||
if quotaAccount != nil && quotaAccount.AvailableQuota > 0 {
|
||||
s.logger.Info("额度校验通过,跳过钱包余额预检",
|
||||
zap.String("user_id", userID),
|
||||
zap.String("product_id", product.ID),
|
||||
zap.Int64("available_quota", quotaAccount.AvailableQuota))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// 1. 获取用户钱包信息
|
||||
wallet, err := s.walletService.LoadWalletByUserId(ctx, userID)
|
||||
if err != nil {
|
||||
@@ -1232,8 +1319,13 @@ func (s *ApiApplicationServiceImpl) validateWalletStatus(ctx context.Context, us
|
||||
return ErrFrozenAccount
|
||||
}
|
||||
|
||||
// 3. 检查钱包余额是否充足
|
||||
// 3. 检查钱包余额是否充足(有订阅时与扣费金额对齐:取目录价与订阅价较大者)
|
||||
requiredAmount := product.Price
|
||||
if subscription != nil {
|
||||
if subscription.Price.GreaterThan(requiredAmount) {
|
||||
requiredAmount = subscription.Price
|
||||
}
|
||||
}
|
||||
if wallet.Balance.LessThan(requiredAmount) {
|
||||
s.logger.Error("钱包余额不足",
|
||||
zap.String("user_id", userID),
|
||||
@@ -1259,6 +1351,56 @@ func (s *ApiApplicationServiceImpl) validateWalletStatus(ctx context.Context, us
|
||||
return nil
|
||||
}
|
||||
|
||||
// tryDeductQuota 尝试扣减产品额度;若不存在额度账户则返回 false,nil 以便回退钱包扣款
|
||||
func (s *ApiApplicationServiceImpl) tryDeductQuota(ctx context.Context, userID, productID, apiCallID, transactionID string) (bool, error) {
|
||||
if s.subordinateRepo == nil || productID == "" {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
var deducted bool
|
||||
err := s.txManager.ExecuteInTx(ctx, func(txCtx context.Context) error {
|
||||
account, err := s.subordinateRepo.FindQuotaAccount(txCtx, userID, productID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if account == nil {
|
||||
return nil
|
||||
}
|
||||
if account.AvailableQuota <= 0 {
|
||||
return ErrInsufficientBalance
|
||||
}
|
||||
|
||||
before := account.AvailableQuota
|
||||
account.AvailableQuota -= 1
|
||||
account.UsedQuota += 1
|
||||
if err := s.subordinateRepo.UpdateQuotaAccount(txCtx, account); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ledger := &subordinate_entities.UserProductQuotaLedger{
|
||||
UserID: userID,
|
||||
ProductID: productID,
|
||||
ChangeType: subordinate_entities.QuotaLedgerChangeTypeConsumeAPI,
|
||||
DeltaQuota: -1,
|
||||
BeforeQuota: before,
|
||||
AfterQuota: account.AvailableQuota,
|
||||
SourceID: apiCallID,
|
||||
OperatorID: userID,
|
||||
Remark: fmt.Sprintf("API调用扣减,transaction_id=%s", transactionID),
|
||||
}
|
||||
if err := s.subordinateRepo.CreateQuotaLedger(txCtx, ledger); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
deducted = true
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return deducted, nil
|
||||
}
|
||||
|
||||
// validateSubscriptionStatus 验证订阅状态并返回订阅信息
|
||||
func (s *ApiApplicationServiceImpl) validateSubscriptionStatus(ctx context.Context, userID string, product *product_entities.Product) (*product_entities.Subscription, error) {
|
||||
// 1. 检查用户是否已订阅该产品
|
||||
|
||||
@@ -16,13 +16,13 @@ type ApiCallValidationResult struct {
|
||||
SecretKey string `json:"secret_key"`
|
||||
IsValid bool `json:"is_valid"`
|
||||
ErrorMessage string `json:"error_message"`
|
||||
|
||||
|
||||
// 新增字段
|
||||
ContractCode string `json:"contract_code"`
|
||||
ApiCall *api_entities.ApiCall `json:"api_call"`
|
||||
RequestParams map[string]interface{} `json:"request_params"`
|
||||
Product *product_entities.Product `json:"product"`
|
||||
Subscription *product_entities.Subscription `json:"subscription"`
|
||||
ContractCode string `json:"contract_code"`
|
||||
ApiCall *api_entities.ApiCall `json:"api_call"`
|
||||
RequestParams map[string]interface{} `json:"request_params"`
|
||||
Product *product_entities.Product `json:"product"`
|
||||
Subscription *product_entities.Subscription `json:"subscription"`
|
||||
}
|
||||
|
||||
// GetUserID 获取用户ID
|
||||
@@ -99,6 +99,6 @@ func (r *ApiCallValidationResult) SetContractCode(code string) {
|
||||
// SetSubscription 设置订阅信息(包含实际扣费金额)
|
||||
func (r *ApiCallValidationResult) SetSubscription(subscription *product_entities.Subscription) {
|
||||
r.SubscriptionID = subscription.ID
|
||||
r.Amount = subscription.Price // 使用订阅价格作为扣费金额
|
||||
r.Amount = subscription.Price // 使用订阅价格作为扣费金额
|
||||
r.Subscription = subscription
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import "errors"
|
||||
// API调用相关错误类型
|
||||
var (
|
||||
ErrQueryEmpty = errors.New("查询为空")
|
||||
ErrQueryFailed = errors.New("查询失败")
|
||||
ErrSystem = errors.New("接口异常")
|
||||
ErrDecryptFail = errors.New("解密失败")
|
||||
ErrRequestParam = errors.New("请求参数结构不正确")
|
||||
@@ -21,12 +22,15 @@ var (
|
||||
ErrProductNotSubscribed = errors.New("未订阅此产品")
|
||||
ErrSubscriptionExpired = errors.New("订阅已过期")
|
||||
ErrSubscriptionSuspended = errors.New("订阅已暂停")
|
||||
ErrBusiness = errors.New("业务失败")
|
||||
ErrBusiness = errors.New("业务失败")
|
||||
ErrSubordinateLinkNotFound = errors.New("非子账号,无法使用master_accessid")
|
||||
ErrSubordinateParentMismatch = errors.New("master_accessid与主账号不匹配")
|
||||
)
|
||||
|
||||
// 错误码映射 - 严格按照用户要求
|
||||
var ErrorCodeMap = map[error]int{
|
||||
ErrQueryEmpty: 1000,
|
||||
ErrQueryFailed: 1000,
|
||||
ErrSystem: 1001,
|
||||
ErrDecryptFail: 1002,
|
||||
ErrRequestParam: 1003,
|
||||
@@ -43,7 +47,9 @@ var ErrorCodeMap = map[error]int{
|
||||
ErrProductNotSubscribed: 1008,
|
||||
ErrSubscriptionExpired: 1008,
|
||||
ErrSubscriptionSuspended: 1008,
|
||||
ErrBusiness: 2001,
|
||||
ErrBusiness: 2001,
|
||||
ErrSubordinateLinkNotFound: 1301,
|
||||
ErrSubordinateParentMismatch: 1302,
|
||||
}
|
||||
|
||||
// GetErrorCode 获取错误对应的错误码
|
||||
|
||||
@@ -32,6 +32,22 @@ type CertificationApplicationService interface {
|
||||
// 获取认证列表(管理员)
|
||||
ListCertifications(ctx context.Context, query *queries.ListCertificationsQuery) (*responses.CertificationListResponse, error)
|
||||
|
||||
// ================ 管理员后台操作用例 ================
|
||||
|
||||
// AdminCompleteCertificationWithoutContract 管理员代用户完成认证(暂不关联合同)
|
||||
AdminCompleteCertificationWithoutContract(ctx context.Context, cmd *commands.AdminCompleteCertificationCommand) (*responses.CertificationResponse, error)
|
||||
|
||||
// AdminListSubmitRecords 管理端分页查询企业信息提交记录
|
||||
AdminListSubmitRecords(ctx context.Context, query *queries.AdminListSubmitRecordsQuery) (*responses.AdminSubmitRecordsListResponse, error)
|
||||
// AdminGetSubmitRecordByID 管理端获取单条提交记录详情
|
||||
AdminGetSubmitRecordByID(ctx context.Context, recordID string) (*responses.AdminSubmitRecordDetail, error)
|
||||
// AdminApproveSubmitRecord 管理端审核通过(按提交记录 ID)
|
||||
AdminApproveSubmitRecord(ctx context.Context, recordID, adminID, remark string) error
|
||||
// AdminRejectSubmitRecord 管理端审核拒绝(按提交记录 ID)
|
||||
AdminRejectSubmitRecord(ctx context.Context, recordID, adminID, remark string) error
|
||||
// AdminTransitionCertificationStatus 管理端按用户变更认证状态(以状态机为准:info_submitted=通过 / info_rejected=拒绝)
|
||||
AdminTransitionCertificationStatus(ctx context.Context, cmd *commands.AdminTransitionCertificationStatusCommand) error
|
||||
|
||||
// ================ e签宝回调处理 ================
|
||||
|
||||
// 处理e签宝回调
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -71,7 +71,20 @@ type EsignOrganization struct {
|
||||
// 可以根据需要添加更多企业信息字段
|
||||
}
|
||||
|
||||
|
||||
// AdminCompleteCertificationCommand 管理员代用户完成认证命令(可不关联合同)
|
||||
type AdminCompleteCertificationCommand struct {
|
||||
// AdminID 从JWT中获取,不从请求体传递,因此不做必填校验
|
||||
AdminID string `json:"-"`
|
||||
UserID string `json:"user_id" validate:"required"`
|
||||
CompanyName string `json:"company_name" validate:"required,min=2,max=100"`
|
||||
UnifiedSocialCode string `json:"unified_social_code" validate:"required"`
|
||||
LegalPersonName string `json:"legal_person_name" validate:"required,min=2,max=20"`
|
||||
LegalPersonID string `json:"legal_person_id" validate:"required"`
|
||||
LegalPersonPhone string `json:"legal_person_phone" validate:"required"`
|
||||
EnterpriseAddress string `json:"enterprise_address" validate:"required"`
|
||||
// 备注信息,用于记录后台操作原因
|
||||
Reason string `json:"reason" validate:"required"`
|
||||
}
|
||||
// ForceTransitionStatusCommand 强制状态转换命令(管理员)
|
||||
type ForceTransitionStatusCommand struct {
|
||||
CertificationID string `json:"certification_id" validate:"required"`
|
||||
@@ -81,6 +94,14 @@ type ForceTransitionStatusCommand struct {
|
||||
Force bool `json:"force,omitempty"` // 是否强制执行,跳过业务规则验证
|
||||
}
|
||||
|
||||
// AdminTransitionCertificationStatusCommand 管理端变更认证状态(以状态机为准,用于审核通过/拒绝等)
|
||||
type AdminTransitionCertificationStatusCommand struct {
|
||||
AdminID string `json:"-"`
|
||||
UserID string `json:"user_id" validate:"required"`
|
||||
TargetStatus string `json:"target_status" validate:"required,oneof=info_submitted info_rejected"` // 审核通过 -> info_submitted;审核拒绝 -> info_rejected
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
|
||||
// SubmitEnterpriseInfoCommand 提交企业信息命令
|
||||
type SubmitEnterpriseInfoCommand struct {
|
||||
UserID string `json:"-" comment:"用户唯一标识,从JWT token获取,不在JSON中暴露"`
|
||||
@@ -91,4 +112,19 @@ type SubmitEnterpriseInfoCommand struct {
|
||||
LegalPersonPhone string `json:"legal_person_phone" binding:"required,phone" comment:"法定代表人手机号,11位,如:13800138000"`
|
||||
EnterpriseAddress string `json:"enterprise_address" binding:"required,enterprise_address" comment:"企业地址,如:北京市海淀区"`
|
||||
VerificationCode string `json:"verification_code" binding:"required,len=6" comment:"验证码"`
|
||||
|
||||
// 营业执照图片 URL(单张)
|
||||
BusinessLicenseImageURL string `json:"business_license_image_url" binding:"omitempty,url" comment:"营业执照图片URL"`
|
||||
// 办公场地图片 URL 列表(前端传 string 数组)
|
||||
OfficePlaceImageURLs []string `json:"office_place_image_urls" binding:"omitempty,dive,url" comment:"办公场地图片URL列表"`
|
||||
|
||||
// 授权代表信息(与前端 authorized_rep_* 及表字段一致)
|
||||
AuthorizedRepName string `json:"authorized_rep_name" binding:"omitempty,min=2,max=20" comment:"授权代表姓名"`
|
||||
AuthorizedRepID string `json:"authorized_rep_id" binding:"omitempty,id_card" comment:"授权代表身份证号"`
|
||||
AuthorizedRepPhone string `json:"authorized_rep_phone" binding:"omitempty,phone" comment:"授权代表手机号"`
|
||||
AuthorizedRepIDImageURLs []string `json:"authorized_rep_id_image_urls" binding:"omitempty,dive,url" comment:"授权代表身份证正反面图片URL"`
|
||||
|
||||
// 应用场景
|
||||
APIUsage string `json:"api_usage" binding:"omitempty,min=5,max=500" comment:"接口用途及业务场景说明"`
|
||||
ScenarioAttachmentURLs []string `json:"scenario_attachment_urls" binding:"omitempty,dive,url" comment:"场景附件图片URL列表"`
|
||||
}
|
||||
|
||||
@@ -192,3 +192,13 @@ func (q *GetSystemMonitoringQuery) ShouldIncludeMetric(metric string) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// AdminListSubmitRecordsQuery 管理端企业信息提交记录列表查询(以状态机 certification_status 为准,不做审核状态筛选)
|
||||
type AdminListSubmitRecordsQuery struct {
|
||||
Page int `json:"page" form:"page"`
|
||||
PageSize int `json:"page_size" form:"page_size"`
|
||||
CertificationStatus string `json:"certification_status" form:"certification_status"` // 按认证状态筛选,如 info_pending_review / info_submitted / info_rejected,空为全部
|
||||
CompanyName string `json:"company_name" form:"company_name"` // 企业名称(模糊搜索)
|
||||
LegalPersonPhone string `json:"legal_person_phone" form:"legal_person_phone"` // 法人手机号
|
||||
LegalPersonName string `json:"legal_person_name" form:"legal_person_name"` // 法人姓名(模糊搜索)
|
||||
}
|
||||
|
||||
@@ -53,13 +53,13 @@ type CertificationResponse struct {
|
||||
// ConfirmAuthResponse 确认认证状态响应
|
||||
type ConfirmAuthResponse struct {
|
||||
Status enums.CertificationStatus `json:"status"`
|
||||
Reason string `json:"reason"`
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
|
||||
// ConfirmSignResponse 确认签署状态响应
|
||||
type ConfirmSignResponse struct {
|
||||
Status enums.CertificationStatus `json:"status"`
|
||||
Reason string `json:"reason"`
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
|
||||
// CertificationListResponse 认证列表响应
|
||||
@@ -81,7 +81,6 @@ type ContractSignUrlResponse struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
|
||||
// SystemMonitoringResponse 系统监控响应
|
||||
type SystemMonitoringResponse struct {
|
||||
TimeRange string `json:"time_range"`
|
||||
@@ -111,6 +110,55 @@ type SystemHealthStatus struct {
|
||||
Details map[string]interface{} `json:"details,omitempty"`
|
||||
}
|
||||
|
||||
// AdminSubmitRecordItem 管理端提交记录列表项
|
||||
type AdminSubmitRecordItem struct {
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
CompanyName string `json:"company_name"`
|
||||
UnifiedSocialCode string `json:"unified_social_code"`
|
||||
LegalPersonName string `json:"legal_person_name"`
|
||||
SubmitAt time.Time `json:"submit_at"`
|
||||
Status string `json:"status"`
|
||||
CertificationStatus string `json:"certification_status,omitempty"` // 以状态机为准:info_pending_review/info_submitted/info_rejected 等
|
||||
}
|
||||
|
||||
// AdminSubmitRecordDetail 管理端提交记录详情(含完整信息与图片 URL)
|
||||
type AdminSubmitRecordDetail struct {
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
CompanyName string `json:"company_name"`
|
||||
UnifiedSocialCode string `json:"unified_social_code"`
|
||||
LegalPersonName string `json:"legal_person_name"`
|
||||
LegalPersonID string `json:"legal_person_id"`
|
||||
LegalPersonPhone string `json:"legal_person_phone"`
|
||||
EnterpriseAddress string `json:"enterprise_address"`
|
||||
AuthorizedRepName string `json:"authorized_rep_name"`
|
||||
AuthorizedRepID string `json:"authorized_rep_id"`
|
||||
AuthorizedRepPhone string `json:"authorized_rep_phone"`
|
||||
AuthorizedRepIDImageURLs string `json:"authorized_rep_id_image_urls"` // JSON 字符串或解析后数组
|
||||
BusinessLicenseImageURL string `json:"business_license_image_url"`
|
||||
OfficePlaceImageURLs string `json:"office_place_image_urls"` // JSON 数组字符串
|
||||
APIUsage string `json:"api_usage"`
|
||||
ScenarioAttachmentURLs string `json:"scenario_attachment_urls"`
|
||||
Status string `json:"status"`
|
||||
SubmitAt time.Time `json:"submit_at"`
|
||||
VerifiedAt *time.Time `json:"verified_at,omitempty"`
|
||||
FailedAt *time.Time `json:"failed_at,omitempty"`
|
||||
FailureReason string `json:"failure_reason,omitempty"`
|
||||
CertificationStatus string `json:"certification_status,omitempty"` // 以状态机为准
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// AdminSubmitRecordsListResponse 管理端提交记录列表响应
|
||||
type AdminSubmitRecordsListResponse struct {
|
||||
Items []*AdminSubmitRecordItem `json:"items"`
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
TotalPages int `json:"total_pages"`
|
||||
}
|
||||
|
||||
// ================ 响应构建辅助方法 ================
|
||||
|
||||
// NewCertificationListResponse 创建认证列表响应
|
||||
@@ -146,7 +194,6 @@ func NewContractSignUrlResponse(certificationID, signURL, contractURL, nextActio
|
||||
return response
|
||||
}
|
||||
|
||||
|
||||
// NewSystemAlert 创建系统警告
|
||||
func NewSystemAlert(level, alertType, message, metric string, value, threshold interface{}) *SystemAlert {
|
||||
return &SystemAlert{
|
||||
@@ -161,7 +208,6 @@ func NewSystemAlert(level, alertType, message, metric string, value, threshold i
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// IsHealthy 检查系统是否健康
|
||||
func (r *SystemMonitoringResponse) IsHealthy() bool {
|
||||
return r.SystemHealth.Overall == "healthy"
|
||||
|
||||
1733
internal/application/certification/new.md
Normal file
1733
internal/application/certification/new.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -108,8 +108,10 @@ type AlipayRechargeOrderResponse struct {
|
||||
|
||||
// RechargeConfigResponse 充值配置响应
|
||||
type RechargeConfigResponse struct {
|
||||
MinAmount string `json:"min_amount"` // 最低充值金额
|
||||
MaxAmount string `json:"max_amount"` // 最高充值金额
|
||||
MinAmount string `json:"min_amount"` // 最低充值金额
|
||||
MaxAmount string `json:"max_amount"` // 最高充值金额
|
||||
RechargeBonusEnabled bool `json:"recharge_bonus_enabled"` // 是否启用充值赠送
|
||||
ApiStoreRechargeTip string `json:"api_store_recharge_tip"` // API 商店充值提示(大额/批量联系商务)
|
||||
AlipayRechargeBonus []AlipayRechargeBonusRuleResponse `json:"alipay_recharge_bonus"`
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
finance_services "tyapi-server/internal/domains/finance/services"
|
||||
product_repositories "tyapi-server/internal/domains/product/repositories"
|
||||
user_repositories "tyapi-server/internal/domains/user/repositories"
|
||||
"tyapi-server/internal/infrastructure/external/notification"
|
||||
"tyapi-server/internal/shared/component_report"
|
||||
"tyapi-server/internal/shared/database"
|
||||
"tyapi-server/internal/shared/export"
|
||||
@@ -43,6 +44,7 @@ type FinanceApplicationServiceImpl struct {
|
||||
exportManager *export.ExportManager
|
||||
logger *zap.Logger
|
||||
config *config.Config
|
||||
wechatWorkService *notification.WeChatWorkService
|
||||
}
|
||||
|
||||
// NewFinanceApplicationService 创建财务应用服务
|
||||
@@ -63,6 +65,11 @@ func NewFinanceApplicationService(
|
||||
config *config.Config,
|
||||
exportManager *export.ExportManager,
|
||||
) FinanceApplicationService {
|
||||
var wechatSvc *notification.WeChatWorkService
|
||||
if config != nil && config.WechatWork.WebhookURL != "" {
|
||||
wechatSvc = notification.NewWeChatWorkService(config.WechatWork.WebhookURL, config.WechatWork.Secret, logger)
|
||||
}
|
||||
|
||||
return &FinanceApplicationServiceImpl{
|
||||
aliPayClient: aliPayClient,
|
||||
wechatPayService: wechatPayService,
|
||||
@@ -79,9 +86,46 @@ func NewFinanceApplicationService(
|
||||
exportManager: exportManager,
|
||||
logger: logger,
|
||||
config: config,
|
||||
wechatWorkService: wechatSvc,
|
||||
}
|
||||
}
|
||||
|
||||
// getUserContactInfo 获取企业名称和联系手机号(尽量用企业信息里的手机号,退化到用户登录手机号)
|
||||
func (s *FinanceApplicationServiceImpl) getUserContactInfo(ctx context.Context, userID string) (companyName, phone string) {
|
||||
companyName = "未知企业"
|
||||
phone = ""
|
||||
|
||||
if userID == "" {
|
||||
return
|
||||
}
|
||||
|
||||
user, err := s.userRepo.GetByIDWithEnterpriseInfo(ctx, userID)
|
||||
if err != nil {
|
||||
s.logger.Warn("获取用户企业信息失败,使用默认企业名称",
|
||||
zap.String("user_id", userID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// 登录手机号
|
||||
if user.Phone != "" {
|
||||
phone = user.Phone
|
||||
}
|
||||
|
||||
// 企业名称和企业手机号
|
||||
if user.EnterpriseInfo != nil {
|
||||
if user.EnterpriseInfo.CompanyName != "" {
|
||||
companyName = user.EnterpriseInfo.CompanyName
|
||||
}
|
||||
if user.EnterpriseInfo.LegalPersonPhone != "" {
|
||||
phone = user.EnterpriseInfo.LegalPersonPhone
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *FinanceApplicationServiceImpl) CreateWallet(ctx context.Context, cmd *commands.CreateWalletCommand) (*responses.WalletResponse, error) {
|
||||
// 调用钱包聚合服务创建钱包
|
||||
wallet, err := s.walletService.CreateWallet(ctx, cmd.UserID)
|
||||
@@ -936,6 +980,33 @@ func (s *FinanceApplicationServiceImpl) processAlipayPaymentSuccess(ctx context.
|
||||
zap.String("amount", amount.String()),
|
||||
)
|
||||
|
||||
// 充值成功企业微信通知(仅充值订单,且忽略发送错误)
|
||||
if s.wechatWorkService != nil {
|
||||
// 再次获取充值记录,拿到用户ID
|
||||
rechargeRecord, err := s.rechargeRecordRepo.GetByID(ctx, alipayOrder.RechargeID)
|
||||
if err == nil {
|
||||
companyName, phone := s.getUserContactInfo(ctx, rechargeRecord.UserID)
|
||||
content := fmt.Sprintf(
|
||||
"### 【天远API】用户充值成功通知\n"+
|
||||
"> 企业名称:%s\n"+
|
||||
"> 联系手机:%s\n"+
|
||||
"> 充值渠道:支付宝\n"+
|
||||
"> 充值金额:%s 元\n"+
|
||||
"> 时间:%s\n",
|
||||
companyName,
|
||||
phone,
|
||||
amount.String(),
|
||||
time.Now().Format("2006-01-02 15:04:05"),
|
||||
)
|
||||
_ = s.wechatWorkService.SendMarkdownMessage(ctx, content)
|
||||
} else {
|
||||
s.logger.Warn("获取充值记录失败,跳过企业微信充值通知",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
zap.Error(err),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1266,17 +1337,26 @@ func (s *FinanceApplicationServiceImpl) GetAdminRechargeRecords(ctx context.Cont
|
||||
|
||||
// GetRechargeConfig 获取充值配置
|
||||
func (s *FinanceApplicationServiceImpl) GetRechargeConfig(ctx context.Context) (*responses.RechargeConfigResponse, error) {
|
||||
bonus := make([]responses.AlipayRechargeBonusRuleResponse, 0, len(s.config.Wallet.AliPayRechargeBonus))
|
||||
for _, rule := range s.config.Wallet.AliPayRechargeBonus {
|
||||
bonus = append(bonus, responses.AlipayRechargeBonusRuleResponse{
|
||||
RechargeAmount: rule.RechargeAmount,
|
||||
BonusAmount: rule.BonusAmount,
|
||||
})
|
||||
bonus := make([]responses.AlipayRechargeBonusRuleResponse, 0)
|
||||
if s.config.Wallet.RechargeBonusEnabled && len(s.config.Wallet.AliPayRechargeBonus) > 0 {
|
||||
bonus = make([]responses.AlipayRechargeBonusRuleResponse, 0, len(s.config.Wallet.AliPayRechargeBonus))
|
||||
for _, rule := range s.config.Wallet.AliPayRechargeBonus {
|
||||
bonus = append(bonus, responses.AlipayRechargeBonusRuleResponse{
|
||||
RechargeAmount: rule.RechargeAmount,
|
||||
BonusAmount: rule.BonusAmount,
|
||||
})
|
||||
}
|
||||
}
|
||||
tip := s.config.Wallet.ApiStoreRechargeTip
|
||||
if tip == "" && !s.config.Wallet.RechargeBonusEnabled {
|
||||
tip = "尊敬的客户,若您的充值金额较大或有批量调价需求,为获取专属商务优惠方案,请直接联系我司商务团队进行洽谈。感谢您的支持!"
|
||||
}
|
||||
return &responses.RechargeConfigResponse{
|
||||
MinAmount: s.config.Wallet.MinAmount,
|
||||
MaxAmount: s.config.Wallet.MaxAmount,
|
||||
AlipayRechargeBonus: bonus,
|
||||
MinAmount: s.config.Wallet.MinAmount,
|
||||
MaxAmount: s.config.Wallet.MaxAmount,
|
||||
RechargeBonusEnabled: s.config.Wallet.RechargeBonusEnabled,
|
||||
ApiStoreRechargeTip: tip,
|
||||
AlipayRechargeBonus: bonus,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -1580,9 +1660,9 @@ func (s *FinanceApplicationServiceImpl) processWechatPaymentSuccess(ctx context.
|
||||
return nil
|
||||
}
|
||||
|
||||
// 计算充值赠送金额(复用支付宝的赠送逻辑)
|
||||
// 计算充值赠送金额(复用支付宝的赠送逻辑,受 recharge_bonus_enabled 开关控制)
|
||||
bonusAmount := decimal.Zero
|
||||
if len(s.config.Wallet.AliPayRechargeBonus) > 0 {
|
||||
if s.config.Wallet.RechargeBonusEnabled && len(s.config.Wallet.AliPayRechargeBonus) > 0 {
|
||||
for i := len(s.config.Wallet.AliPayRechargeBonus) - 1; i >= 0; i-- {
|
||||
rule := s.config.Wallet.AliPayRechargeBonus[i]
|
||||
if amount.GreaterThanOrEqual(decimal.NewFromFloat(rule.RechargeAmount)) {
|
||||
@@ -1681,6 +1761,24 @@ func (s *FinanceApplicationServiceImpl) processWechatPaymentSuccess(ctx context.
|
||||
zap.String("user_id", rechargeRecord.UserID),
|
||||
)
|
||||
|
||||
// 微信充值成功企业微信通知(忽略发送错误)
|
||||
if s.wechatWorkService != nil {
|
||||
companyName, phone := s.getUserContactInfo(ctx, rechargeRecord.UserID)
|
||||
content := fmt.Sprintf(
|
||||
"### 【天远API】用户充值成功通知\n"+
|
||||
"> 企业名称:%s\n"+
|
||||
"> 联系手机:%s\n"+
|
||||
"> 充值渠道:微信\n"+
|
||||
"> 充值金额:%s 元\n"+
|
||||
"> 时间:%s\n",
|
||||
companyName,
|
||||
phone,
|
||||
amount.String(),
|
||||
time.Now().Format("2006-01-02 15:04:05"),
|
||||
)
|
||||
_ = s.wechatWorkService.SendMarkdownMessage(ctx, content)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -7,12 +7,14 @@ import (
|
||||
"time"
|
||||
|
||||
"tyapi-server/internal/application/finance/dto"
|
||||
"tyapi-server/internal/config"
|
||||
"tyapi-server/internal/domains/finance/entities"
|
||||
finance_repo "tyapi-server/internal/domains/finance/repositories"
|
||||
"tyapi-server/internal/domains/finance/services"
|
||||
"tyapi-server/internal/domains/finance/value_objects"
|
||||
user_repo "tyapi-server/internal/domains/user/repositories"
|
||||
user_service "tyapi-server/internal/domains/user/services"
|
||||
"tyapi-server/internal/infrastructure/external/notification"
|
||||
"tyapi-server/internal/infrastructure/external/storage"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
@@ -59,8 +61,9 @@ type InvoiceApplicationServiceImpl struct {
|
||||
userAggregateService user_service.UserAggregateService
|
||||
|
||||
// 外部服务依赖
|
||||
storageService *storage.QiNiuStorageService
|
||||
logger *zap.Logger
|
||||
storageService *storage.QiNiuStorageService
|
||||
logger *zap.Logger
|
||||
wechatWorkServer *notification.WeChatWorkService
|
||||
}
|
||||
|
||||
// NewInvoiceApplicationService 创建发票应用服务
|
||||
@@ -76,7 +79,13 @@ func NewInvoiceApplicationService(
|
||||
userInvoiceInfoService services.UserInvoiceInfoService,
|
||||
storageService *storage.QiNiuStorageService,
|
||||
logger *zap.Logger,
|
||||
cfg *config.Config,
|
||||
) InvoiceApplicationService {
|
||||
var wechatSvc *notification.WeChatWorkService
|
||||
if cfg != nil && cfg.WechatWork.WebhookURL != "" {
|
||||
wechatSvc = notification.NewWeChatWorkService(cfg.WechatWork.WebhookURL, cfg.WechatWork.Secret, logger)
|
||||
}
|
||||
|
||||
return &InvoiceApplicationServiceImpl{
|
||||
invoiceRepo: invoiceRepo,
|
||||
userInvoiceInfoRepo: userInvoiceInfoRepo,
|
||||
@@ -89,6 +98,7 @@ func NewInvoiceApplicationService(
|
||||
userInvoiceInfoService: userInvoiceInfoService,
|
||||
storageService: storageService,
|
||||
logger: logger,
|
||||
wechatWorkServer: wechatSvc,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,7 +185,7 @@ func (s *InvoiceApplicationServiceImpl) ApplyInvoice(ctx context.Context, userID
|
||||
}
|
||||
|
||||
// 10. 构建响应DTO
|
||||
return &dto.InvoiceApplicationResponse{
|
||||
resp := &dto.InvoiceApplicationResponse{
|
||||
ID: application.ID,
|
||||
UserID: application.UserID,
|
||||
InvoiceType: application.InvoiceType,
|
||||
@@ -183,7 +193,33 @@ func (s *InvoiceApplicationServiceImpl) ApplyInvoice(ctx context.Context, userID
|
||||
Status: application.Status,
|
||||
InvoiceInfo: invoiceInfo,
|
||||
CreatedAt: application.CreatedAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 11. 企业微信通知(忽略发送错误),只使用企业名称和联系电话
|
||||
if s.wechatWorkServer != nil {
|
||||
companyName := userWithEnterprise.EnterpriseInfo.CompanyName
|
||||
phone := user.Phone
|
||||
if userWithEnterprise.EnterpriseInfo.LegalPersonPhone != "" {
|
||||
phone = userWithEnterprise.EnterpriseInfo.LegalPersonPhone
|
||||
}
|
||||
|
||||
content := fmt.Sprintf(
|
||||
"### 【天远API】用户申请开发票\n"+
|
||||
"> 企业名称:%s\n"+
|
||||
"> 联系手机:%s\n"+
|
||||
"> 申请开票金额:%s 元\n"+
|
||||
"> 发票类型:%s\n"+
|
||||
"> 申请时间:%s\n",
|
||||
companyName,
|
||||
phone,
|
||||
application.Amount.String(),
|
||||
string(application.InvoiceType),
|
||||
time.Now().Format("2006-01-02 15:04:05"),
|
||||
)
|
||||
_ = s.wechatWorkServer.SendMarkdownMessage(ctx, content)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// GetUserInvoiceInfo 获取用户发票信息
|
||||
@@ -355,27 +391,56 @@ func (s *InvoiceApplicationServiceImpl) DownloadInvoiceFile(ctx context.Context,
|
||||
return nil, fmt.Errorf("发票尚未通过审核")
|
||||
}
|
||||
|
||||
// 4. 验证文件信息
|
||||
if application.FileURL == nil || *application.FileURL == "" {
|
||||
return nil, fmt.Errorf("发票文件不存在")
|
||||
}
|
||||
|
||||
// 5. 从七牛云下载文件内容
|
||||
fileContent, err := s.storageService.DownloadFile(ctx, *application.FileURL)
|
||||
fileContent, fileID, fileName, fileSize, fileURL, err := downloadInvoiceFileFromStorage(ctx, s.storageService, application)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("下载文件失败: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 6. 构建响应DTO
|
||||
return &dto.FileDownloadResponse{
|
||||
FileID: *application.FileID,
|
||||
FileName: *application.FileName,
|
||||
FileSize: *application.FileSize,
|
||||
FileURL: *application.FileURL,
|
||||
FileID: fileID,
|
||||
FileName: fileName,
|
||||
FileSize: fileSize,
|
||||
FileURL: fileURL,
|
||||
FileContent: fileContent,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// downloadInvoiceFileFromStorage 从七牛云下载发票文件(优先用 FileID 生成新 URL,避免私有空间签名过期)
|
||||
func downloadInvoiceFileFromStorage(ctx context.Context, storageService *storage.QiNiuStorageService, application *entities.InvoiceApplication) ([]byte, string, string, int64, string, error) {
|
||||
if application.FileID == nil || *application.FileID == "" {
|
||||
if application.FileURL == nil || *application.FileURL == "" {
|
||||
return nil, "", "", 0, "", fmt.Errorf("发票文件不存在")
|
||||
}
|
||||
fileContent, err := storageService.DownloadFile(ctx, *application.FileURL)
|
||||
if err != nil {
|
||||
return nil, "", "", 0, "", fmt.Errorf("下载文件失败: %w", err)
|
||||
}
|
||||
return fileContent, derefString(application.FileID), derefString(application.FileName), derefInt64(application.FileSize), *application.FileURL, nil
|
||||
}
|
||||
|
||||
fileContent, err := storageService.DownloadFileByKey(ctx, *application.FileID)
|
||||
if err != nil {
|
||||
return nil, "", "", 0, "", fmt.Errorf("下载文件失败: %w", err)
|
||||
}
|
||||
|
||||
fileURL := storageService.GetFileURL(ctx, *application.FileID)
|
||||
return fileContent, *application.FileID, derefString(application.FileName), derefInt64(application.FileSize), fileURL, nil
|
||||
}
|
||||
|
||||
func derefString(v *string) string {
|
||||
if v == nil {
|
||||
return ""
|
||||
}
|
||||
return *v
|
||||
}
|
||||
|
||||
func derefInt64(v *int64) int64 {
|
||||
if v == nil {
|
||||
return 0
|
||||
}
|
||||
return *v
|
||||
}
|
||||
|
||||
// GetAvailableAmount 获取可开票金额
|
||||
func (s *InvoiceApplicationServiceImpl) GetAvailableAmount(ctx context.Context, userID string) (*dto.AvailableAmountResponse, error) {
|
||||
// 1. 验证用户是否存在
|
||||
@@ -728,23 +793,16 @@ func (s *AdminInvoiceApplicationServiceImpl) DownloadInvoiceFile(ctx context.Con
|
||||
return nil, fmt.Errorf("发票尚未通过审核")
|
||||
}
|
||||
|
||||
// 3. 验证文件信息
|
||||
if application.FileURL == nil || *application.FileURL == "" {
|
||||
return nil, fmt.Errorf("发票文件不存在")
|
||||
}
|
||||
|
||||
// 4. 从七牛云下载文件内容
|
||||
fileContent, err := s.storageService.DownloadFile(ctx, *application.FileURL)
|
||||
fileContent, fileID, fileName, fileSize, fileURL, err := downloadInvoiceFileFromStorage(ctx, s.storageService, application)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("下载文件失败: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 5. 构建响应DTO
|
||||
return &dto.FileDownloadResponse{
|
||||
FileID: *application.FileID,
|
||||
FileName: *application.FileName,
|
||||
FileSize: *application.FileSize,
|
||||
FileURL: *application.FileURL,
|
||||
FileID: fileID,
|
||||
FileName: fileName,
|
||||
FileSize: fileSize,
|
||||
FileURL: fileURL,
|
||||
FileContent: fileContent,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -47,4 +47,7 @@ type ProductApplicationService interface {
|
||||
CreateProductApiConfig(ctx context.Context, productID string, config *responses.ProductApiConfigResponse) error
|
||||
UpdateProductApiConfig(ctx context.Context, configID string, config *responses.ProductApiConfigResponse) error
|
||||
DeleteProductApiConfig(ctx context.Context, configID string) error
|
||||
|
||||
// 产品字典导出
|
||||
ExportProductDictionary(ctx context.Context, format string) ([]byte, error)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
@@ -16,6 +17,7 @@ import (
|
||||
api_services "tyapi-server/internal/domains/api/services"
|
||||
"tyapi-server/internal/domains/product/entities"
|
||||
product_service "tyapi-server/internal/domains/product/services"
|
||||
"tyapi-server/internal/shared/export"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
)
|
||||
|
||||
@@ -28,6 +30,7 @@ type ProductApplicationServiceImpl struct {
|
||||
documentationAppService DocumentationApplicationServiceInterface
|
||||
formConfigService api_services.FormConfigService
|
||||
logger *zap.Logger
|
||||
exportManager *export.ExportManager
|
||||
}
|
||||
|
||||
// NewProductApplicationService 创建产品应用服务
|
||||
@@ -38,6 +41,7 @@ func NewProductApplicationService(
|
||||
documentationAppService DocumentationApplicationServiceInterface,
|
||||
formConfigService api_services.FormConfigService,
|
||||
logger *zap.Logger,
|
||||
exportManager *export.ExportManager,
|
||||
) ProductApplicationService {
|
||||
return &ProductApplicationServiceImpl{
|
||||
productManagementService: productManagementService,
|
||||
@@ -46,6 +50,7 @@ func NewProductApplicationService(
|
||||
documentationAppService: documentationAppService,
|
||||
formConfigService: formConfigService,
|
||||
logger: logger,
|
||||
exportManager: exportManager,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -492,24 +497,24 @@ func (s *ProductApplicationServiceImpl) GetProductByIDForUser(ctx context.Contex
|
||||
// convertToProductInfoResponse 转换为产品信息响应
|
||||
func (s *ProductApplicationServiceImpl) convertToProductInfoResponse(product *entities.Product) *responses.ProductInfoResponse {
|
||||
response := &responses.ProductInfoResponse{
|
||||
ID: product.ID,
|
||||
OldID: product.OldID,
|
||||
Name: product.Name,
|
||||
Code: product.Code,
|
||||
Description: product.Description,
|
||||
Content: product.Content,
|
||||
CategoryID: product.CategoryID,
|
||||
SubCategoryID: product.SubCategoryID,
|
||||
Price: product.Price.InexactFloat64(),
|
||||
IsEnabled: product.IsEnabled,
|
||||
IsPackage: product.IsPackage,
|
||||
SellUIComponent: product.SellUIComponent,
|
||||
ID: product.ID,
|
||||
OldID: product.OldID,
|
||||
Name: product.Name,
|
||||
Code: product.Code,
|
||||
Description: product.Description,
|
||||
Content: product.Content,
|
||||
CategoryID: product.CategoryID,
|
||||
SubCategoryID: product.SubCategoryID,
|
||||
Price: product.Price.InexactFloat64(),
|
||||
IsEnabled: product.IsEnabled,
|
||||
IsPackage: product.IsPackage,
|
||||
SellUIComponent: product.SellUIComponent,
|
||||
UIComponentPrice: product.UIComponentPrice.InexactFloat64(),
|
||||
SEOTitle: product.SEOTitle,
|
||||
SEODescription: product.SEODescription,
|
||||
SEOKeywords: product.SEOKeywords,
|
||||
CreatedAt: product.CreatedAt,
|
||||
UpdatedAt: product.UpdatedAt,
|
||||
SEOTitle: product.SEOTitle,
|
||||
SEODescription: product.SEODescription,
|
||||
SEOKeywords: product.SEOKeywords,
|
||||
CreatedAt: product.CreatedAt,
|
||||
UpdatedAt: product.UpdatedAt,
|
||||
}
|
||||
|
||||
// 添加一级分类信息
|
||||
@@ -544,27 +549,27 @@ func (s *ProductApplicationServiceImpl) convertToProductInfoResponse(product *en
|
||||
// convertToProductAdminInfoResponse 转换为管理员产品信息响应
|
||||
func (s *ProductApplicationServiceImpl) convertToProductAdminInfoResponse(product *entities.Product) *responses.ProductAdminInfoResponse {
|
||||
response := &responses.ProductAdminInfoResponse{
|
||||
ID: product.ID,
|
||||
OldID: product.OldID,
|
||||
Name: product.Name,
|
||||
Code: product.Code,
|
||||
Description: product.Description,
|
||||
Content: product.Content,
|
||||
CategoryID: product.CategoryID,
|
||||
SubCategoryID: product.SubCategoryID,
|
||||
Price: product.Price.InexactFloat64(),
|
||||
CostPrice: product.CostPrice.InexactFloat64(),
|
||||
Remark: product.Remark,
|
||||
IsEnabled: product.IsEnabled,
|
||||
IsVisible: product.IsVisible, // 管理员可以看到可见状态
|
||||
IsPackage: product.IsPackage,
|
||||
SellUIComponent: product.SellUIComponent,
|
||||
ID: product.ID,
|
||||
OldID: product.OldID,
|
||||
Name: product.Name,
|
||||
Code: product.Code,
|
||||
Description: product.Description,
|
||||
Content: product.Content,
|
||||
CategoryID: product.CategoryID,
|
||||
SubCategoryID: product.SubCategoryID,
|
||||
Price: product.Price.InexactFloat64(),
|
||||
CostPrice: product.CostPrice.InexactFloat64(),
|
||||
Remark: product.Remark,
|
||||
IsEnabled: product.IsEnabled,
|
||||
IsVisible: product.IsVisible, // 管理员可以看到可见状态
|
||||
IsPackage: product.IsPackage,
|
||||
SellUIComponent: product.SellUIComponent,
|
||||
UIComponentPrice: product.UIComponentPrice.InexactFloat64(),
|
||||
SEOTitle: product.SEOTitle,
|
||||
SEODescription: product.SEODescription,
|
||||
SEOKeywords: product.SEOKeywords,
|
||||
CreatedAt: product.CreatedAt,
|
||||
UpdatedAt: product.UpdatedAt,
|
||||
SEOTitle: product.SEOTitle,
|
||||
SEODescription: product.SEODescription,
|
||||
SEOKeywords: product.SEOKeywords,
|
||||
CreatedAt: product.CreatedAt,
|
||||
UpdatedAt: product.UpdatedAt,
|
||||
}
|
||||
|
||||
// 添加一级分类信息
|
||||
@@ -994,101 +999,103 @@ func (s *ProductApplicationServiceImpl) mergeRequestParamsFromDTOs(ctx context.C
|
||||
// getDTOMap 获取API代码到DTO结构体的映射(复用form_config_service的逻辑)
|
||||
func (s *ProductApplicationServiceImpl) getDTOMap() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"IVYZ9363": &dto.IVYZ9363Req{},
|
||||
"IVYZ385E": &dto.IVYZ385EReq{},
|
||||
"IVYZ5733": &dto.IVYZ5733Req{},
|
||||
"FLXG3D56": &dto.FLXG3D56Req{},
|
||||
"FLXG75FE": &dto.FLXG75FEReq{},
|
||||
"FLXG0V3B": &dto.FLXG0V3BReq{},
|
||||
"FLXG0V4B": &dto.FLXG0V4BReq{},
|
||||
"FLXG54F5": &dto.FLXG54F5Req{},
|
||||
"FLXG162A": &dto.FLXG162AReq{},
|
||||
"FLXG0687": &dto.FLXG0687Req{},
|
||||
"FLXGBC21": &dto.FLXGBC21Req{},
|
||||
"FLXG970F": &dto.FLXG970FReq{},
|
||||
"FLXG5876": &dto.FLXG5876Req{},
|
||||
"FLXG9687": &dto.FLXG9687Req{},
|
||||
"FLXGC9D1": &dto.FLXGC9D1Req{},
|
||||
"FLXGCA3D": &dto.FLXGCA3DReq{},
|
||||
"FLXGDEC7": &dto.FLXGDEC7Req{},
|
||||
"JRZQ0A03": &dto.JRZQ0A03Req{},
|
||||
"JRZQ4AA8": &dto.JRZQ4AA8Req{},
|
||||
"JRZQ8203": &dto.JRZQ8203Req{},
|
||||
"JRZQDCBE": &dto.JRZQDCBEReq{},
|
||||
"QYGL2ACD": &dto.QYGL2ACDReq{},
|
||||
"QYGL6F2D": &dto.QYGL6F2DReq{},
|
||||
"QYGL45BD": &dto.QYGL45BDReq{},
|
||||
"QYGL8261": &dto.QYGL8261Req{},
|
||||
"QYGL8271": &dto.QYGL8271Req{},
|
||||
"QYGLB4C0": &dto.QYGLB4C0Req{},
|
||||
"QYGL23T7": &dto.QYGL23T7Req{},
|
||||
"QYGL5A3C": &dto.QYGL5A3CReq{},
|
||||
"QYGL8B4D": &dto.QYGL8B4DReq{},
|
||||
"QYGL9E2F": &dto.QYGL9E2FReq{},
|
||||
"QYGL7C1A": &dto.QYGL7C1AReq{},
|
||||
"QYGL3F8E": &dto.QYGL3F8EReq{},
|
||||
"YYSY4B37": &dto.YYSY4B37Req{},
|
||||
"YYSY4B21": &dto.YYSY4B21Req{},
|
||||
"YYSY6F2E": &dto.YYSY6F2EReq{},
|
||||
"YYSY09CD": &dto.YYSY09CDReq{},
|
||||
"IVYZ0B03": &dto.IVYZ0B03Req{},
|
||||
"YYSYBE08": &dto.YYSYBE08Req{},
|
||||
"YYSYD50F": &dto.YYSYD50FReq{},
|
||||
"YYSYF7DB": &dto.YYSYF7DBReq{},
|
||||
"IVYZ9A2B": &dto.IVYZ9A2BReq{},
|
||||
"IVYZ7F2A": &dto.IVYZ7F2AReq{},
|
||||
"IVYZ4E8B": &dto.IVYZ4E8BReq{},
|
||||
"IVYZ1C9D": &dto.IVYZ1C9DReq{},
|
||||
"IVYZGZ08": &dto.IVYZGZ08Req{},
|
||||
"FLXG8A3F": &dto.FLXG8A3FReq{},
|
||||
"FLXG5B2E": &dto.FLXG5B2EReq{},
|
||||
"COMB298Y": &dto.COMB298YReq{},
|
||||
"COMB86PM": &dto.COMB86PMReq{},
|
||||
"QCXG7A2B": &dto.QCXG7A2BReq{},
|
||||
"COMENT01": &dto.COMENT01Req{},
|
||||
"JRZQ09J8": &dto.JRZQ09J8Req{},
|
||||
"FLXGDEA8": &dto.FLXGDEA8Req{},
|
||||
"FLXGDEA9": &dto.FLXGDEA9Req{},
|
||||
"JRZQ1D09": &dto.JRZQ1D09Req{},
|
||||
"IVYZ2A8B": &dto.IVYZ2A8BReq{},
|
||||
"IVYZ7C9D": &dto.IVYZ7C9DReq{},
|
||||
"IVYZ5E3F": &dto.IVYZ5E3FReq{},
|
||||
"YYSY4F2E": &dto.YYSY4F2EReq{},
|
||||
"YYSY8B1C": &dto.YYSY8B1CReq{},
|
||||
"YYSY6D9A": &dto.YYSY6D9AReq{},
|
||||
"YYSY3E7F": &dto.YYSY3E7FReq{},
|
||||
"FLXG5A3B": &dto.FLXG5A3BReq{},
|
||||
"FLXG9C1D": &dto.FLXG9C1DReq{},
|
||||
"FLXG2E8F": &dto.FLXG2E8FReq{},
|
||||
"JRZQ3C7B": &dto.JRZQ3C7BReq{},
|
||||
"JRZQ8A2D": &dto.JRZQ8A2DReq{},
|
||||
"JRZQ5E9F": &dto.JRZQ5E9FReq{},
|
||||
"JRZQ4B6C": &dto.JRZQ4B6CReq{},
|
||||
"JRZQ7F1A": &dto.JRZQ7F1AReq{},
|
||||
"DWBG6A2C": &dto.DWBG6A2CReq{},
|
||||
"DWBG8B4D": &dto.DWBG8B4DReq{},
|
||||
"FLXG8B4D": &dto.FLXG8B4DReq{},
|
||||
"IVYZ81NC": &dto.IVYZ81NCReq{},
|
||||
"IVYZ7F3A": &dto.IVYZ7F3AReq{},
|
||||
"IVYZ3P9M": &dto.IVYZ3P9MReq{},
|
||||
"IVYZ3A7F": &dto.IVYZ3A7FReq{},
|
||||
"IVYZ9D2E": &dto.IVYZ9D2EReq{},
|
||||
"DWBG7F3A": &dto.DWBG7F3AReq{},
|
||||
"YYSY8F3A": &dto.YYSY8F3AReq{},
|
||||
"QCXG9P1C": &dto.QCXG9P1CReq{},
|
||||
"JRZQ9E2A": &dto.JRZQ9E2AReq{},
|
||||
"YYSY9A1B": &dto.YYSY9A1BReq{},
|
||||
"YYSY8C2D": &dto.YYSY8C2DReq{},
|
||||
"YYSY7D3E": &dto.YYSY7D3EReq{},
|
||||
"YYSY9E4A": &dto.YYSY9E4AReq{},
|
||||
"JRZQ6F2A": &dto.JRZQ6F2AReq{},
|
||||
"JRZQ8B3C": &dto.JRZQ8B3CReq{},
|
||||
"JRZQ9D4E": &dto.JRZQ9D4EReq{},
|
||||
"FLXG7E8F": &dto.FLXG7E8FReq{},
|
||||
"QYGL5F6A": &dto.QYGL5F6AReq{},
|
||||
"IVYZ6G7H": &dto.IVYZ6G7HReq{},
|
||||
"IVYZ8I9J": &dto.IVYZ8I9JReq{},
|
||||
"JRZQ0L85": &dto.JRZQ0L85Req{},
|
||||
"IVYZ9363": &dto.IVYZ9363Req{},
|
||||
"IVYZ385E": &dto.IVYZ385EReq{},
|
||||
"IVYZ5733": &dto.IVYZ5733Req{},
|
||||
"FLXG3D56": &dto.FLXG3D56Req{},
|
||||
"FLXG75FE": &dto.FLXG75FEReq{},
|
||||
"FLXG0V3B": &dto.FLXG0V3BReq{},
|
||||
"FLXG0V4B": &dto.FLXG0V4BReq{},
|
||||
"FLXG54F5": &dto.FLXG54F5Req{},
|
||||
"FLXG162A": &dto.FLXG162AReq{},
|
||||
"FLXG0687": &dto.FLXG0687Req{},
|
||||
"FLXGBC21": &dto.FLXGBC21Req{},
|
||||
"FLXG970F": &dto.FLXG970FReq{},
|
||||
"FLXG5876": &dto.FLXG5876Req{},
|
||||
"FLXG9687": &dto.FLXG9687Req{},
|
||||
"FLXGC9D1": &dto.FLXGC9D1Req{},
|
||||
"FLXGCA3D": &dto.FLXGCA3DReq{},
|
||||
"FLXGDEC7": &dto.FLXGDEC7Req{},
|
||||
"JRZQ0A03": &dto.JRZQ0A03Req{},
|
||||
"JRZQ4AA8": &dto.JRZQ4AA8Req{},
|
||||
"JRZQ8203": &dto.JRZQ8203Req{},
|
||||
"JRZQDCBE": &dto.JRZQDCBEReq{},
|
||||
"QYGL2ACD": &dto.QYGL2ACDReq{},
|
||||
"QYGL6F2D": &dto.QYGL6F2DReq{},
|
||||
"QYGL45BD": &dto.QYGL45BDReq{},
|
||||
"QYGL8261": &dto.QYGL8261Req{},
|
||||
"QYGL8271": &dto.QYGL8271Req{},
|
||||
"QYGLB4C0": &dto.QYGLB4C0Req{},
|
||||
"QYGL23T7": &dto.QYGL23T7Req{},
|
||||
"QYGL5A3C": &dto.QYGL5A3CReq{},
|
||||
"QYGL8B4D": &dto.QYGL8B4DReq{},
|
||||
"QYGL9E2F": &dto.QYGL9E2FReq{},
|
||||
"QYGL7C1A": &dto.QYGL7C1AReq{},
|
||||
"QYGL3F8E": &dto.QYGL3F8EReq{},
|
||||
"YYSY4B37": &dto.YYSY4B37Req{},
|
||||
"YYSY4B21": &dto.YYSY4B21Req{},
|
||||
"YYSY6F2E": &dto.YYSY6F2EReq{},
|
||||
"YYSY09CD": &dto.YYSY09CDReq{},
|
||||
"IVYZ0B03": &dto.IVYZ0B03Req{},
|
||||
"YYSYBE08": &dto.YYSYBE08Req{},
|
||||
"YYSYBE08TEST": &dto.YYSYBE08Req{},
|
||||
"YYSYD50F": &dto.YYSYD50FReq{},
|
||||
"YYSYF7DB": &dto.YYSYF7DBReq{},
|
||||
"IVYZ9A2B": &dto.IVYZ9A2BReq{},
|
||||
"IVYZ7F2A": &dto.IVYZ7F2AReq{},
|
||||
"IVYZ4E8B": &dto.IVYZ4E8BReq{},
|
||||
"IVYZ1C9D": &dto.IVYZ1C9DReq{},
|
||||
"IVYZGZ08": &dto.IVYZGZ08Req{},
|
||||
"FLXG8A3F": &dto.FLXG8A3FReq{},
|
||||
"FLXG5B2E": &dto.FLXG5B2EReq{},
|
||||
"COMB298Y": &dto.COMB298YReq{},
|
||||
"COMB86PM": &dto.COMB86PMReq{},
|
||||
"QCXG7A2B": &dto.QCXG7A2BReq{},
|
||||
"COMENT01": &dto.COMENT01Req{},
|
||||
"JRZQ09J8": &dto.JRZQ09J8Req{},
|
||||
"FLXGDEA8": &dto.FLXGDEA8Req{},
|
||||
"FLXGDEA9": &dto.FLXGDEA9Req{},
|
||||
"JRZQ1D09": &dto.JRZQ1D09Req{},
|
||||
"IVYZ2A8B": &dto.IVYZ2A8BReq{},
|
||||
"IVYZ7C9D": &dto.IVYZ7C9DReq{},
|
||||
"IVYZ5E3F": &dto.IVYZ5E3FReq{},
|
||||
"YYSY4F2E": &dto.YYSY4F2EReq{},
|
||||
"YYSY8B1C": &dto.YYSY8B1CReq{},
|
||||
"YYSY6D9A": &dto.YYSY6D9AReq{},
|
||||
"YYSY3E7F": &dto.YYSY3E7FReq{},
|
||||
"FLXG5A3B": &dto.FLXG5A3BReq{},
|
||||
"FLXG9C1D": &dto.FLXG9C1DReq{},
|
||||
"FLXG2E8F": &dto.FLXG2E8FReq{},
|
||||
"JRZQ3C7B": &dto.JRZQ3C7BReq{},
|
||||
"JRZQ8A2D": &dto.JRZQ8A2DReq{},
|
||||
"JRZQ5E9F": &dto.JRZQ5E9FReq{},
|
||||
"JRZQ4B6C": &dto.JRZQ4B6CReq{},
|
||||
"JRZQ7F1A": &dto.JRZQ7F1AReq{},
|
||||
"DWBG6A2C": &dto.DWBG6A2CReq{},
|
||||
"DWBG8B4D": &dto.DWBG8B4DReq{},
|
||||
"FLXG8B4D": &dto.FLXG8B4DReq{},
|
||||
"IVYZ81NC": &dto.IVYZ81NCReq{},
|
||||
"IVYZ2MN6": &dto.IVYZ2MN6Req{},
|
||||
"IVYZ7F3A": &dto.IVYZ7F3AReq{},
|
||||
"IVYZ3P9M": &dto.IVYZ3P9MReq{},
|
||||
"IVYZ3A7F": &dto.IVYZ3A7FReq{},
|
||||
"IVYZ9D2E": &dto.IVYZ9D2EReq{},
|
||||
"DWBG7F3A": &dto.DWBG7F3AReq{},
|
||||
"YYSY8F3A": &dto.YYSY8F3AReq{},
|
||||
"QCXG9P1C": &dto.QCXG9P1CReq{},
|
||||
"JRZQ9E2A": &dto.JRZQ9E2AReq{},
|
||||
"YYSY9A1B": &dto.YYSY9A1BReq{},
|
||||
"YYSY8C2D": &dto.YYSY8C2DReq{},
|
||||
"YYSY7D3E": &dto.YYSY7D3EReq{},
|
||||
"YYSY9E4A": &dto.YYSY9E4AReq{},
|
||||
"JRZQ6F2A": &dto.JRZQ6F2AReq{},
|
||||
"JRZQ8B3C": &dto.JRZQ8B3CReq{},
|
||||
"JRZQ9D4E": &dto.JRZQ9D4EReq{},
|
||||
"FLXG7E8F": &dto.FLXG7E8FReq{},
|
||||
"QYGL5F6A": &dto.QYGL5F6AReq{},
|
||||
"IVYZ6G7H": &dto.IVYZ6G7HReq{},
|
||||
"IVYZ8I9J": &dto.IVYZ8I9JReq{},
|
||||
"JRZQ0L85": &dto.JRZQ0L85Req{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1238,3 +1245,120 @@ func (s *ProductApplicationServiceImpl) mapFieldTypeToDocType(frontendType strin
|
||||
return "string"
|
||||
}
|
||||
}
|
||||
|
||||
// ExportProductDictionary 导出产品字典
|
||||
func (s *ProductApplicationServiceImpl) ExportProductDictionary(ctx context.Context, format string) ([]byte, error) {
|
||||
// 查询所有启用且可见的产品及其分类信息
|
||||
products, err := s.productManagementService.GetAllProductsForDictionary(ctx)
|
||||
if err != nil {
|
||||
s.logger.Error("获取产品字典数据失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(products) == 0 {
|
||||
return nil, fmt.Errorf("没有找到符合条件的产品数据")
|
||||
}
|
||||
|
||||
// 按分类名称分组整理数据
|
||||
type categoryInfo struct {
|
||||
name string
|
||||
sort int
|
||||
}
|
||||
|
||||
categoryGroups := make(map[string][]map[string]interface{}) // 使用分类名称作为key
|
||||
categorySortMap := make(map[string]int) // 分类名称到sort值的映射
|
||||
var categoryNames []string // 保持分类顺序
|
||||
|
||||
for _, product := range products {
|
||||
// 获取分类信息
|
||||
categoryName := "未分类"
|
||||
categorySort := 999999 // 默认排序值,确保未分类的产品排在最后
|
||||
|
||||
if product.Category != nil {
|
||||
categoryName = product.Category.Name
|
||||
categorySort = product.Category.Sort
|
||||
// 如果有二级分类,添加到分类名称中
|
||||
if product.SubCategory != nil && product.SubCategory.Name != "" {
|
||||
categoryName = categoryName + " / " + product.SubCategory.Name
|
||||
}
|
||||
}
|
||||
|
||||
// 如果分类不存在,初始化并添加到顺序列表
|
||||
if _, exists := categoryGroups[categoryName]; !exists {
|
||||
categoryGroups[categoryName] = []map[string]interface{}{}
|
||||
categoryNames = append(categoryNames, categoryName)
|
||||
categorySortMap[categoryName] = categorySort
|
||||
}
|
||||
|
||||
// 添加产品到对应分类
|
||||
productInfo := map[string]interface{}{
|
||||
"category": categoryName,
|
||||
"product_code": product.Code,
|
||||
"product_name": product.Name,
|
||||
"description": product.Description,
|
||||
}
|
||||
categoryGroups[categoryName] = append(categoryGroups[categoryName], productInfo)
|
||||
}
|
||||
|
||||
// 按分类的sort值对分类名称进行排序
|
||||
sort.Slice(categoryNames, func(i, j int) bool {
|
||||
return categorySortMap[categoryNames[i]] < categorySortMap[categoryNames[j]]
|
||||
})
|
||||
|
||||
// 准备导出数据
|
||||
headers := []string{"分类", "产品编码", "产品名称", "产品简介"}
|
||||
columnWidths := []float64{25, 15, 20, 40}
|
||||
|
||||
// 构建数据行
|
||||
var data [][]interface{}
|
||||
for _, categoryName := range categoryNames {
|
||||
productsInCategory := categoryGroups[categoryName]
|
||||
for i, product := range productsInCategory {
|
||||
// 只有每个分类的第一个产品才显示分类名称
|
||||
var categoryNameForRow interface{}
|
||||
if i == 0 {
|
||||
categoryNameForRow = categoryName
|
||||
} else {
|
||||
categoryNameForRow = ""
|
||||
}
|
||||
|
||||
row := []interface{}{
|
||||
categoryNameForRow,
|
||||
product["product_code"],
|
||||
product["product_name"],
|
||||
product["description"],
|
||||
}
|
||||
data = append(data, row)
|
||||
}
|
||||
}
|
||||
|
||||
// 计算需要合并的行
|
||||
mergedRegions := [][]int{}
|
||||
currentRow := 1 // 从第1行开始(第0行是表头)
|
||||
for _, categoryName := range categoryNames {
|
||||
productsCount := len(categoryGroups[categoryName])
|
||||
if productsCount > 1 {
|
||||
// 合并相同分类的单元格:从当前行开始,合并productsCount行,第0列
|
||||
// Excel格式:[startRow, startCol, endRow, endCol]
|
||||
mergedRegions = append(mergedRegions, []int{
|
||||
currentRow, // startRow
|
||||
0, // startCol (分类列)
|
||||
currentRow + productsCount - 1, // endRow
|
||||
0, // endCol
|
||||
})
|
||||
}
|
||||
currentRow += productsCount
|
||||
}
|
||||
|
||||
// 创建导出配置
|
||||
config := &export.ExportConfig{
|
||||
SheetName: "产品字典",
|
||||
Headers: headers,
|
||||
Data: data,
|
||||
ColumnWidths: columnWidths,
|
||||
MergedRegions: mergedRegions,
|
||||
}
|
||||
|
||||
// 使用导出管理器生成文件
|
||||
return s.exportManager.Export(ctx, config, format)
|
||||
}
|
||||
|
||||
16
internal/application/product/self_subscribe_policy.go
Normal file
16
internal/application/product/self_subscribe_policy.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package product
|
||||
|
||||
import "context"
|
||||
|
||||
// SelfSubscribePolicy 是否允许用户在控制台自助发起「订阅产品」
|
||||
type SelfSubscribePolicy interface {
|
||||
Allow(ctx context.Context, userID string) (allowed bool, message string, err error)
|
||||
}
|
||||
|
||||
// DefaultAllowSelfSubscribe 未装配下属模块时:恒允许
|
||||
type DefaultAllowSelfSubscribe struct{}
|
||||
|
||||
// Allow 恒允许
|
||||
func (DefaultAllowSelfSubscribe) Allow(_ context.Context, _ string) (bool, string, error) {
|
||||
return true, "", nil
|
||||
}
|
||||
@@ -23,6 +23,7 @@ type SubscriptionApplicationServiceImpl struct {
|
||||
productSubscriptionService *product_service.ProductSubscriptionService
|
||||
userRepo user_repositories.UserRepository
|
||||
apiCallRepository domain_api_repo.ApiCallRepository
|
||||
selfSubscribePolicy SelfSubscribePolicy
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
@@ -31,12 +32,17 @@ func NewSubscriptionApplicationService(
|
||||
productSubscriptionService *product_service.ProductSubscriptionService,
|
||||
userRepo user_repositories.UserRepository,
|
||||
apiCallRepository domain_api_repo.ApiCallRepository,
|
||||
selfSubscribePolicy SelfSubscribePolicy,
|
||||
logger *zap.Logger,
|
||||
) SubscriptionApplicationService {
|
||||
if selfSubscribePolicy == nil {
|
||||
selfSubscribePolicy = DefaultAllowSelfSubscribe{}
|
||||
}
|
||||
return &SubscriptionApplicationServiceImpl{
|
||||
productSubscriptionService: productSubscriptionService,
|
||||
userRepo: userRepo,
|
||||
apiCallRepository: apiCallRepository,
|
||||
selfSubscribePolicy: selfSubscribePolicy,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
@@ -157,7 +163,17 @@ func (s *SubscriptionApplicationServiceImpl) BatchUpdateSubscriptionPrices(ctx c
|
||||
// CreateSubscription 创建订阅
|
||||
// 业务流程:1. 创建订阅
|
||||
func (s *SubscriptionApplicationServiceImpl) CreateSubscription(ctx context.Context, cmd *commands.CreateSubscriptionCommand) error {
|
||||
_, err := s.productSubscriptionService.CreateSubscription(ctx, cmd.UserID, cmd.ProductID)
|
||||
allow, msg, err := s.selfSubscribePolicy.Allow(ctx, cmd.UserID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !allow {
|
||||
if msg == "" {
|
||||
msg = "当前账号不允许自助订阅"
|
||||
}
|
||||
return fmt.Errorf("%s", msg)
|
||||
}
|
||||
_, err = s.productSubscriptionService.CreateSubscription(ctx, cmd.UserID, cmd.ProductID)
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
package commands
|
||||
|
||||
// SubPortalRegisterCommand 子站注册(邀请码必填)
|
||||
type SubPortalRegisterCommand struct {
|
||||
Phone string `json:"phone" binding:"required"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
ConfirmPassword string `json:"confirm_password" binding:"required"`
|
||||
Code string `json:"code" binding:"required"`
|
||||
InviteToken string `json:"invite_token" binding:"required"`
|
||||
}
|
||||
|
||||
// ListChildApiCallsCommand 下属 API 调用记录查询
|
||||
type ListChildApiCallsCommand struct {
|
||||
ParentUserID string
|
||||
ChildUserID string `form:"child_user_id" binding:"required"`
|
||||
Page int `form:"page"`
|
||||
PageSize int `form:"page_size"`
|
||||
TransactionID string `form:"transaction_id"`
|
||||
ProductName string `form:"product_name"`
|
||||
Status string `form:"status"`
|
||||
StartTime string `form:"start_time"`
|
||||
EndTime string `form:"end_time"`
|
||||
}
|
||||
|
||||
// ListSubordinatesCommand 下属列表查询
|
||||
type ListSubordinatesCommand struct {
|
||||
ParentUserID string
|
||||
Page int `form:"page"`
|
||||
PageSize int `form:"page_size"`
|
||||
Remark string `form:"remark"`
|
||||
Phone string `form:"phone"`
|
||||
CompanyName string `form:"company_name"`
|
||||
}
|
||||
|
||||
// UpdateSubordinateRemarkCommand 更新下属备注
|
||||
type UpdateSubordinateRemarkCommand struct {
|
||||
ParentUserID string
|
||||
ChildUserID string `json:"child_user_id" binding:"required"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
|
||||
// CreateInvitationCommand 主账号创建邀请
|
||||
type CreateInvitationCommand struct {
|
||||
ParentUserID string
|
||||
// ExpiresInHours 可选,0 或不传为永久有效(100年)
|
||||
ExpiresInHours int `json:"expires_in_hours"`
|
||||
}
|
||||
|
||||
// AllocateToChildCommand 主账号向下属划余额
|
||||
type AllocateToChildCommand struct {
|
||||
ParentUserID string
|
||||
ChildUserID string `json:"child_user_id" binding:"required"`
|
||||
Amount string `json:"amount" binding:"required"`
|
||||
VerifyCode string `json:"verify_code" binding:"required,len=6"`
|
||||
}
|
||||
|
||||
// AssignChildSubscriptionCommand 为下属代配订阅
|
||||
type AssignChildSubscriptionCommand struct {
|
||||
ParentUserID string
|
||||
ChildUserID string `json:"child_user_id" binding:"required"`
|
||||
ProductID string `json:"product_id" binding:"required"`
|
||||
Price string `json:"price" binding:"required"`
|
||||
UIComponentPrice string `json:"ui_component_price"`
|
||||
}
|
||||
|
||||
// ListChildAllocationsCommand 下属划拨记录查询
|
||||
type ListChildAllocationsCommand struct {
|
||||
ParentUserID string
|
||||
ChildUserID string `json:"child_user_id" form:"child_user_id" binding:"required"`
|
||||
Page int `json:"page" form:"page"`
|
||||
PageSize int `json:"page_size" form:"page_size"`
|
||||
}
|
||||
|
||||
// ListChildSubscriptionsCommand 下属订阅列表查询
|
||||
type ListChildSubscriptionsCommand struct {
|
||||
ParentUserID string
|
||||
ChildUserID string `json:"child_user_id" form:"child_user_id" binding:"required"`
|
||||
}
|
||||
|
||||
// RemoveChildSubscriptionCommand 删除下属订阅
|
||||
type RemoveChildSubscriptionCommand struct {
|
||||
ParentUserID string
|
||||
ChildUserID string `json:"child_user_id" binding:"required"`
|
||||
SubscriptionID string `json:"subscription_id" binding:"required"`
|
||||
}
|
||||
|
||||
// PurchaseChildQuotaCommand 主账号为子账号购买调用额度
|
||||
type PurchaseChildQuotaCommand struct {
|
||||
ParentUserID string
|
||||
ChildUserID string `json:"child_user_id" binding:"required"`
|
||||
ProductID string `json:"product_id" binding:"required"`
|
||||
CallCount int64 `json:"call_count" binding:"required,min=1"`
|
||||
VerifyCode string `json:"verify_code" binding:"required,len=6"`
|
||||
}
|
||||
|
||||
// ListChildQuotaPurchasesCommand 下属额度购买记录查询
|
||||
type ListChildQuotaPurchasesCommand struct {
|
||||
ParentUserID string
|
||||
ChildUserID string `json:"child_user_id" form:"child_user_id" binding:"required"`
|
||||
Page int `json:"page" form:"page"`
|
||||
PageSize int `json:"page_size" form:"page_size"`
|
||||
}
|
||||
|
||||
// ListChildQuotaAccountsCommand 下属额度账户查询
|
||||
type ListChildQuotaAccountsCommand struct {
|
||||
ParentUserID string
|
||||
ChildUserID string `json:"child_user_id" form:"child_user_id" binding:"required"`
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package responses
|
||||
|
||||
import "time"
|
||||
|
||||
// CreateInvitationResponse 创建邀请
|
||||
type CreateInvitationResponse struct {
|
||||
InviteToken string `json:"invite_token" description:"主账号固定邀请码,可重复使用"`
|
||||
InviteURL string `json:"invite_url" description:"子站注册完整链接"`
|
||||
ExpiresAt time.Time `json:"expires_at"`
|
||||
InvitationID string `json:"invitation_id"`
|
||||
}
|
||||
|
||||
// SubordinateProductQuotaItem 下属产品额度
|
||||
type SubordinateProductQuotaItem struct {
|
||||
ProductID string `json:"product_id"`
|
||||
ProductName string `json:"product_name"`
|
||||
AvailableQuota int64 `json:"available_quota"`
|
||||
}
|
||||
|
||||
// SubordinateListItem 下属一条
|
||||
type SubordinateListItem struct {
|
||||
ChildUserID string `json:"child_user_id"`
|
||||
Phone string `json:"phone,omitempty"`
|
||||
LinkID string `json:"link_id"`
|
||||
Remark string `json:"remark"`
|
||||
RegisteredAt time.Time `json:"registered_at"`
|
||||
CompanyName string `json:"company_name"`
|
||||
IsCertified bool `json:"is_certified"`
|
||||
Balance string `json:"balance"`
|
||||
ProductQuotas []SubordinateProductQuotaItem `json:"product_quotas"`
|
||||
}
|
||||
|
||||
// SubordinateListResponse 列表
|
||||
type SubordinateListResponse struct {
|
||||
Total int64 `json:"total"`
|
||||
Items []SubordinateListItem `json:"items"`
|
||||
}
|
||||
|
||||
// SubPortalRegisterResponse 子站注册
|
||||
type SubPortalRegisterResponse struct {
|
||||
ID string `json:"id"`
|
||||
Phone string `json:"phone"`
|
||||
}
|
||||
|
||||
// ChildAllocationItem 下属划拨记录
|
||||
type ChildAllocationItem struct {
|
||||
ID string `json:"id"`
|
||||
Amount string `json:"amount"`
|
||||
BusinessRef string `json:"business_ref"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
// ChildAllocationListResponse 下属划拨记录列表
|
||||
type ChildAllocationListResponse struct {
|
||||
Total int64 `json:"total"`
|
||||
Items []ChildAllocationItem `json:"items"`
|
||||
}
|
||||
|
||||
// ChildSubscriptionItem 下属订阅项
|
||||
type ChildSubscriptionItem struct {
|
||||
ID string `json:"id"`
|
||||
ProductID string `json:"product_id"`
|
||||
Price string `json:"price"`
|
||||
UIComponentPrice string `json:"ui_component_price"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
// ChildQuotaPurchaseItem 下属额度购买记录
|
||||
type ChildQuotaPurchaseItem struct {
|
||||
ID string `json:"id"`
|
||||
ProductID string `json:"product_id"`
|
||||
CallCount int64 `json:"call_count"`
|
||||
UnitPrice string `json:"unit_price"`
|
||||
TotalAmount string `json:"total_amount"`
|
||||
BusinessRef string `json:"business_ref"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
// ChildQuotaPurchaseListResponse 下属额度购买记录列表
|
||||
type ChildQuotaPurchaseListResponse struct {
|
||||
Total int64 `json:"total"`
|
||||
Items []ChildQuotaPurchaseItem `json:"items"`
|
||||
}
|
||||
|
||||
// ChildQuotaAccountItem 下属产品额度账户
|
||||
type ChildQuotaAccountItem struct {
|
||||
ProductID string `json:"product_id"`
|
||||
TotalQuota int64 `json:"total_quota"`
|
||||
UsedQuota int64 `json:"used_quota"`
|
||||
AvailableQuota int64 `json:"available_quota"`
|
||||
}
|
||||
35
internal/application/subordinate/invite_token.go
Normal file
35
internal/application/subordinate/invite_token.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package subordinate
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
const (
|
||||
// 邀请码固定 6 位,字符集为大写字母+数字
|
||||
inviteTokenLength = 6
|
||||
inviteTokenCharset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
)
|
||||
|
||||
// HashInviteToken 邀请码 SHA256 十六进制
|
||||
func HashInviteToken(raw string) string {
|
||||
sum := sha256.Sum256([]byte(raw))
|
||||
return hex.EncodeToString(sum[:])
|
||||
}
|
||||
|
||||
// GenerateInviteToken 生成随机邀请明文与存储用哈希
|
||||
func GenerateInviteToken() (raw string, hash string, err error) {
|
||||
token := make([]byte, inviteTokenLength)
|
||||
charsetSize := big.NewInt(int64(len(inviteTokenCharset)))
|
||||
for i := range token {
|
||||
n, e := rand.Int(rand.Reader, charsetSize)
|
||||
if e != nil {
|
||||
return "", "", e
|
||||
}
|
||||
token[i] = inviteTokenCharset[n.Int64()]
|
||||
}
|
||||
raw = string(token)
|
||||
return raw, HashInviteToken(raw), nil
|
||||
}
|
||||
26
internal/application/subordinate/invite_token_test.go
Normal file
26
internal/application/subordinate/invite_token_test.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package subordinate
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestGenerateInviteTokenFormat(t *testing.T) {
|
||||
raw, hash, err := GenerateInviteToken()
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateInviteToken error: %v", err)
|
||||
}
|
||||
|
||||
if len(raw) != inviteTokenLength {
|
||||
t.Fatalf("unexpected token length: got %d, want %d", len(raw), inviteTokenLength)
|
||||
}
|
||||
|
||||
for _, ch := range raw {
|
||||
isUpper := ch >= 'A' && ch <= 'Z'
|
||||
isDigit := ch >= '0' && ch <= '9'
|
||||
if !isUpper && !isDigit {
|
||||
t.Fatalf("token contains invalid char: %q", ch)
|
||||
}
|
||||
}
|
||||
|
||||
if hash != HashInviteToken(raw) {
|
||||
t.Fatalf("hash mismatch for token")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package subordinate
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"tyapi-server/internal/application/product"
|
||||
"tyapi-server/internal/domains/subordinate/repositories"
|
||||
)
|
||||
|
||||
// BlockSelfSubscribeForSubordinate 子账号禁止自助订
|
||||
type BlockSelfSubscribeForSubordinate struct {
|
||||
repo repositories.SubordinateRepository
|
||||
}
|
||||
|
||||
// NewBlockSelfSubscribeForSubordinate 构造
|
||||
func NewBlockSelfSubscribeForSubordinate(repo repositories.SubordinateRepository) product.SelfSubscribePolicy {
|
||||
return &BlockSelfSubscribeForSubordinate{repo: repo}
|
||||
}
|
||||
|
||||
// Allow 若为主账号的下属则拒绝
|
||||
func (p *BlockSelfSubscribeForSubordinate) Allow(ctx context.Context, userID string) (bool, string, error) {
|
||||
ok, err := p.repo.IsUserSubordinate(ctx, userID)
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
if ok {
|
||||
return false, "子账号需由主账号配置订阅", nil
|
||||
}
|
||||
return true, "", nil
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package subordinate
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
api_dto "tyapi-server/internal/application/api/dto"
|
||||
"tyapi-server/internal/application/subordinate/dto/commands"
|
||||
"tyapi-server/internal/application/subordinate/dto/responses"
|
||||
)
|
||||
|
||||
// SubordinateApplicationService 下属账号:邀请/注册/划款/代配
|
||||
type SubordinateApplicationService interface {
|
||||
RegisterSubPortal(ctx context.Context, cmd *commands.SubPortalRegisterCommand) (*responses.SubPortalRegisterResponse, error)
|
||||
CreateInvitation(ctx context.Context, cmd *commands.CreateInvitationCommand) (*responses.CreateInvitationResponse, error)
|
||||
ListMySubordinates(ctx context.Context, cmd *commands.ListSubordinatesCommand) (*responses.SubordinateListResponse, error)
|
||||
UpdateSubordinateRemark(ctx context.Context, cmd *commands.UpdateSubordinateRemarkCommand) error
|
||||
AllocateToChild(ctx context.Context, cmd *commands.AllocateToChildCommand) error
|
||||
ListChildAllocations(ctx context.Context, cmd *commands.ListChildAllocationsCommand) (*responses.ChildAllocationListResponse, error)
|
||||
AssignChildSubscription(ctx context.Context, cmd *commands.AssignChildSubscriptionCommand) error
|
||||
ListChildSubscriptions(ctx context.Context, cmd *commands.ListChildSubscriptionsCommand) ([]responses.ChildSubscriptionItem, error)
|
||||
RemoveChildSubscription(ctx context.Context, cmd *commands.RemoveChildSubscriptionCommand) error
|
||||
PurchaseChildQuota(ctx context.Context, cmd *commands.PurchaseChildQuotaCommand) error
|
||||
ListChildQuotaPurchases(ctx context.Context, cmd *commands.ListChildQuotaPurchasesCommand) (*responses.ChildQuotaPurchaseListResponse, error)
|
||||
ListChildQuotaAccounts(ctx context.Context, cmd *commands.ListChildQuotaAccountsCommand) ([]responses.ChildQuotaAccountItem, error)
|
||||
ListChildApiCalls(ctx context.Context, cmd *commands.ListChildApiCallsCommand) (*api_dto.ApiCallListResponse, error)
|
||||
ListMyQuotaAccounts(ctx context.Context, userID string) ([]responses.ChildQuotaAccountItem, error)
|
||||
}
|
||||
@@ -0,0 +1,767 @@
|
||||
package subordinate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/shopspring/decimal"
|
||||
"go.uber.org/zap"
|
||||
|
||||
api_app "tyapi-server/internal/application/api"
|
||||
api_dto "tyapi-server/internal/application/api/dto"
|
||||
"tyapi-server/internal/application/subordinate/dto/commands"
|
||||
"tyapi-server/internal/application/subordinate/dto/responses"
|
||||
"tyapi-server/internal/config"
|
||||
"tyapi-server/internal/domains/finance/repositories"
|
||||
productentities "tyapi-server/internal/domains/product/entities"
|
||||
product_service "tyapi-server/internal/domains/product/services"
|
||||
product_repositories "tyapi-server/internal/domains/product/repositories"
|
||||
subentities "tyapi-server/internal/domains/subordinate/entities"
|
||||
subrepositories "tyapi-server/internal/domains/subordinate/repositories"
|
||||
user_entities "tyapi-server/internal/domains/user/entities"
|
||||
user_repositories "tyapi-server/internal/domains/user/repositories"
|
||||
domain_user_services "tyapi-server/internal/domains/user/services"
|
||||
"tyapi-server/internal/shared/database"
|
||||
shared_interfaces "tyapi-server/internal/shared/interfaces"
|
||||
)
|
||||
|
||||
// SubordinateApplicationServiceImpl 实现
|
||||
type SubordinateApplicationServiceImpl struct {
|
||||
subRepo subrepositories.SubordinateRepository
|
||||
userAgg domain_user_services.UserAggregateService
|
||||
smsService *domain_user_services.SMSCodeService
|
||||
productSub *product_service.ProductSubscriptionService
|
||||
productRepo product_repositories.ProductRepository
|
||||
cfg *config.Config
|
||||
txm *database.TransactionManager
|
||||
walletRepo repositories.WalletRepository
|
||||
userRepo user_repositories.UserRepository
|
||||
apiApp api_app.ApiApplicationService
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewSubordinateApplicationService 构造
|
||||
func NewSubordinateApplicationService(
|
||||
subRepo subrepositories.SubordinateRepository,
|
||||
userAgg domain_user_services.UserAggregateService,
|
||||
smsService *domain_user_services.SMSCodeService,
|
||||
productSub *product_service.ProductSubscriptionService,
|
||||
productRepo product_repositories.ProductRepository,
|
||||
cfg *config.Config,
|
||||
txm *database.TransactionManager,
|
||||
walletRepo repositories.WalletRepository,
|
||||
userRepo user_repositories.UserRepository,
|
||||
apiApp api_app.ApiApplicationService,
|
||||
logger *zap.Logger,
|
||||
) SubordinateApplicationService {
|
||||
return &SubordinateApplicationServiceImpl{
|
||||
subRepo: subRepo,
|
||||
userAgg: userAgg,
|
||||
smsService: smsService,
|
||||
productSub: productSub,
|
||||
productRepo: productRepo,
|
||||
cfg: cfg,
|
||||
txm: txm,
|
||||
walletRepo: walletRepo,
|
||||
userRepo: userRepo,
|
||||
apiApp: apiApp,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterSubPortal 子站注册
|
||||
func (s *SubordinateApplicationServiceImpl) RegisterSubPortal(ctx context.Context, cmd *commands.SubPortalRegisterCommand) (*responses.SubPortalRegisterResponse, error) {
|
||||
if cmd.Password != cmd.ConfirmPassword {
|
||||
return nil, fmt.Errorf("两次输入的密码不一致")
|
||||
}
|
||||
if err := s.smsService.VerifyCode(ctx, cmd.Phone, cmd.Code, user_entities.SMSSceneRegister); err != nil {
|
||||
return nil, fmt.Errorf("验证码错误或已过期")
|
||||
}
|
||||
|
||||
var resp *responses.SubPortalRegisterResponse
|
||||
err := s.txm.ExecuteInTx(ctx, func(txCtx context.Context) error {
|
||||
inv, err := s.subRepo.FindInvitationByTokenHash(txCtx, HashInviteToken(strings.TrimSpace(cmd.InviteToken)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if inv == nil {
|
||||
return fmt.Errorf("邀请码无效")
|
||||
}
|
||||
switch inv.Status {
|
||||
case subentities.InvitationStatusRevoked:
|
||||
return fmt.Errorf("邀请码已失效")
|
||||
case subentities.InvitationStatusConsumed:
|
||||
return fmt.Errorf("邀请码已失效,请联系主账号获取新邀请码")
|
||||
case subentities.InvitationStatusPending:
|
||||
// 固定邀请码可重复使用,注册后不核销
|
||||
default:
|
||||
return fmt.Errorf("邀请码无效")
|
||||
}
|
||||
now := time.Now()
|
||||
if now.After(inv.ExpiresAt) {
|
||||
return fmt.Errorf("邀请码已过期")
|
||||
}
|
||||
|
||||
u, createErr := s.userAgg.CreateUser(txCtx, cmd.Phone, cmd.Password)
|
||||
if createErr != nil {
|
||||
return createErr
|
||||
}
|
||||
|
||||
link := &subentities.UserSubordinateLink{
|
||||
ParentUserID: inv.ParentUserID,
|
||||
ChildUserID: u.ID,
|
||||
InvitationID: &inv.ID,
|
||||
Status: subentities.LinkStatusActive,
|
||||
}
|
||||
if linkErr := s.subRepo.CreateLink(txCtx, link); linkErr != nil {
|
||||
s.logger.Error("创建主从关系失败", zap.Error(linkErr), zap.String("user_id", u.ID))
|
||||
return fmt.Errorf("注册失败,请重试或联系主账号")
|
||||
}
|
||||
|
||||
resp = &responses.SubPortalRegisterResponse{ID: u.ID, Phone: u.Phone}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// CreateInvitation 获取或创建主账号固定邀请码(可重复使用)
|
||||
func (s *SubordinateApplicationServiceImpl) CreateInvitation(ctx context.Context, cmd *commands.CreateInvitationCommand) (*responses.CreateInvitationResponse, error) {
|
||||
existing, err := s.subRepo.FindActiveInvitationByParent(ctx, cmd.ParentUserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if existing != nil {
|
||||
if raw := strings.TrimSpace(existing.Token); raw != "" {
|
||||
return s.buildInvitationResponse(existing, raw)
|
||||
}
|
||||
// 历史 pending 记录未存明文,无法找回,继续创建新的固定邀请码
|
||||
}
|
||||
|
||||
hours := cmd.ExpiresInHours
|
||||
if hours <= 0 {
|
||||
// 永久有效:设置100年后过期
|
||||
hours = 24 * 365 * 100
|
||||
}
|
||||
raw, hash, err := GenerateInviteToken()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("生成邀请失败")
|
||||
}
|
||||
inv := &subentities.SubordinateInvitation{
|
||||
ParentUserID: cmd.ParentUserID,
|
||||
Token: raw,
|
||||
TokenHash: hash,
|
||||
ExpiresAt: time.Now().Add(time.Duration(hours) * time.Hour),
|
||||
Status: subentities.InvitationStatusPending,
|
||||
}
|
||||
if err := s.subRepo.CreateInvitation(ctx, inv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.buildInvitationResponse(inv, raw)
|
||||
}
|
||||
|
||||
func (s *SubordinateApplicationServiceImpl) buildInvitationResponse(inv *subentities.SubordinateInvitation, raw string) (*responses.CreateInvitationResponse, error) {
|
||||
base := strings.TrimSpace(os.Getenv("SUB_PORTAL_BASE_URL"))
|
||||
if base == "" {
|
||||
base = s.cfg.App.SubPortalBaseURL
|
||||
}
|
||||
base = strings.TrimRight(base, "/")
|
||||
if base == "" {
|
||||
return nil, fmt.Errorf("子账号域名未配置,请设置 app.sub_portal_base_url 或环境变量 SUB_PORTAL_BASE_URL")
|
||||
}
|
||||
// 与前端同仓路由一致:/sub/auth/register 为子账号专用注册页
|
||||
inviteURL := base + "/sub/auth/register?invite=" + raw
|
||||
return &responses.CreateInvitationResponse{
|
||||
InviteToken: raw,
|
||||
InviteURL: inviteURL,
|
||||
ExpiresAt: inv.ExpiresAt,
|
||||
InvitationID: inv.ID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ListMySubordinates 主账号的下属
|
||||
func (s *SubordinateApplicationServiceImpl) ListMySubordinates(ctx context.Context, cmd *commands.ListSubordinatesCommand) (*responses.SubordinateListResponse, error) {
|
||||
page := cmd.Page
|
||||
pageSize := cmd.PageSize
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize < 1 || pageSize > 100 {
|
||||
pageSize = 20
|
||||
}
|
||||
offset := (page - 1) * pageSize
|
||||
filter := subrepositories.SubordinateListFilter{
|
||||
Remark: strings.TrimSpace(cmd.Remark),
|
||||
Phone: strings.TrimSpace(cmd.Phone),
|
||||
CompanyName: strings.TrimSpace(cmd.CompanyName),
|
||||
}
|
||||
links, total, err := s.subRepo.ListChildrenByParent(ctx, cmd.ParentUserID, filter, pageSize, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
childIDs := make([]string, 0, len(links))
|
||||
for _, ln := range links {
|
||||
childIDs = append(childIDs, ln.ChildUserID)
|
||||
}
|
||||
|
||||
quotaByUser := make(map[string][]*subentities.UserProductQuotaAccount)
|
||||
if len(childIDs) > 0 {
|
||||
accounts, quotaErr := s.subRepo.ListQuotaAccountsByUserIDs(ctx, childIDs)
|
||||
if quotaErr != nil {
|
||||
return nil, quotaErr
|
||||
}
|
||||
for _, account := range accounts {
|
||||
quotaByUser[account.UserID] = append(quotaByUser[account.UserID], account)
|
||||
}
|
||||
}
|
||||
|
||||
productNameMap := make(map[string]string)
|
||||
if s.productRepo != nil {
|
||||
productIDSet := make(map[string]struct{})
|
||||
for _, accounts := range quotaByUser {
|
||||
for _, account := range accounts {
|
||||
productIDSet[account.ProductID] = struct{}{}
|
||||
}
|
||||
}
|
||||
if len(productIDSet) > 0 {
|
||||
productIDs := make([]string, 0, len(productIDSet))
|
||||
for id := range productIDSet {
|
||||
productIDs = append(productIDs, id)
|
||||
}
|
||||
products, productErr := s.productRepo.FindProductsByIDs(ctx, productIDs)
|
||||
if productErr != nil {
|
||||
s.logger.Warn("批量获取产品名称失败", zap.Error(productErr))
|
||||
} else {
|
||||
for _, product := range products {
|
||||
if product != nil {
|
||||
productNameMap[product.ID] = product.Name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
items := make([]responses.SubordinateListItem, 0, len(links))
|
||||
for _, ln := range links {
|
||||
phone := ""
|
||||
companyName := "未认证"
|
||||
isCertified := false
|
||||
registeredAt := ln.CreatedAt
|
||||
balance := "0.00"
|
||||
|
||||
if u, e := s.userRepo.GetByIDWithEnterpriseInfo(ctx, ln.ChildUserID); e == nil {
|
||||
phone = u.Phone
|
||||
isCertified = u.IsCertified
|
||||
registeredAt = u.CreatedAt
|
||||
if u.EnterpriseInfo != nil && strings.TrimSpace(u.EnterpriseInfo.CompanyName) != "" {
|
||||
companyName = strings.TrimSpace(u.EnterpriseInfo.CompanyName)
|
||||
}
|
||||
} else {
|
||||
s.logger.Warn("获取下属用户失败", zap.String("child_id", ln.ChildUserID), zap.Error(e))
|
||||
}
|
||||
if w, e := s.walletRepo.GetByUserID(ctx, ln.ChildUserID); e == nil && w != nil {
|
||||
balance = w.Balance.StringFixed(2)
|
||||
}
|
||||
|
||||
productQuotas := make([]responses.SubordinateProductQuotaItem, 0)
|
||||
for _, account := range quotaByUser[ln.ChildUserID] {
|
||||
name := productNameMap[account.ProductID]
|
||||
if name == "" {
|
||||
name = account.ProductID
|
||||
}
|
||||
productQuotas = append(productQuotas, responses.SubordinateProductQuotaItem{
|
||||
ProductID: account.ProductID,
|
||||
ProductName: name,
|
||||
AvailableQuota: account.AvailableQuota,
|
||||
})
|
||||
}
|
||||
|
||||
items = append(items, responses.SubordinateListItem{
|
||||
ChildUserID: ln.ChildUserID,
|
||||
Phone: phone,
|
||||
LinkID: ln.ID,
|
||||
Remark: ln.Remark,
|
||||
RegisteredAt: registeredAt,
|
||||
CompanyName: companyName,
|
||||
IsCertified: isCertified,
|
||||
Balance: balance,
|
||||
ProductQuotas: productQuotas,
|
||||
})
|
||||
}
|
||||
return &responses.SubordinateListResponse{Total: total, Items: items}, nil
|
||||
}
|
||||
|
||||
// UpdateSubordinateRemark 更新下属备注
|
||||
func (s *SubordinateApplicationServiceImpl) UpdateSubordinateRemark(ctx context.Context, cmd *commands.UpdateSubordinateRemarkCommand) error {
|
||||
lnk, err := s.subRepo.FindLinkByParentAndChild(ctx, cmd.ParentUserID, cmd.ChildUserID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if lnk == nil || lnk.Status != subentities.LinkStatusActive {
|
||||
return fmt.Errorf("该用户不是您的有效下属")
|
||||
}
|
||||
remark := strings.TrimSpace(cmd.Remark)
|
||||
if len([]rune(remark)) > 255 {
|
||||
return fmt.Errorf("备注不能超过255个字符")
|
||||
}
|
||||
lnk.Remark = remark
|
||||
return s.subRepo.UpdateLink(ctx, lnk)
|
||||
}
|
||||
|
||||
// AllocateToChild 划款
|
||||
func (s *SubordinateApplicationServiceImpl) AllocateToChild(ctx context.Context, cmd *commands.AllocateToChildCommand) error {
|
||||
amount, err := decimal.NewFromString(strings.TrimSpace(cmd.Amount))
|
||||
if err != nil || !amount.GreaterThan(decimal.Zero) {
|
||||
return fmt.Errorf("金额必须大于0")
|
||||
}
|
||||
parentUser, err := s.userRepo.GetByID(ctx, cmd.ParentUserID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("主账号信息获取失败")
|
||||
}
|
||||
if err := s.smsService.VerifyCode(ctx, parentUser.Phone, strings.TrimSpace(cmd.VerifyCode), user_entities.SMSSceneLogin); err != nil {
|
||||
return fmt.Errorf("验证码错误或已过期")
|
||||
}
|
||||
lnk, err := s.subRepo.FindLinkByParentAndChild(ctx, cmd.ParentUserID, cmd.ChildUserID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if lnk == nil || lnk.Status != subentities.LinkStatusActive {
|
||||
return fmt.Errorf("该用户不是您的有效下属")
|
||||
}
|
||||
bizRef := uuid.New().String()
|
||||
return s.txm.ExecuteInTx(ctx, func(txCtx context.Context) error {
|
||||
ok, err := s.walletRepo.UpdateBalanceByUserID(txCtx, cmd.ParentUserID, amount, "subtract")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return fmt.Errorf("主账号扣款失败,请重试")
|
||||
}
|
||||
ok2, err := s.walletRepo.UpdateBalanceByUserID(txCtx, cmd.ChildUserID, amount, "add")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok2 {
|
||||
return fmt.Errorf("向下属入账失败,请重试")
|
||||
}
|
||||
alloc := &subentities.SubordinateWalletAllocation{
|
||||
FromUserID: cmd.ParentUserID,
|
||||
ToUserID: cmd.ChildUserID,
|
||||
Amount: amount,
|
||||
BusinessRef: bizRef,
|
||||
OperatorUserID: cmd.ParentUserID,
|
||||
}
|
||||
return s.subRepo.CreateWalletAllocation(txCtx, alloc)
|
||||
})
|
||||
}
|
||||
|
||||
// ListChildAllocations 下属划拨记录
|
||||
func (s *SubordinateApplicationServiceImpl) ListChildAllocations(ctx context.Context, cmd *commands.ListChildAllocationsCommand) (*responses.ChildAllocationListResponse, error) {
|
||||
lnk, err := s.subRepo.FindLinkByParentAndChild(ctx, cmd.ParentUserID, cmd.ChildUserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if lnk == nil || lnk.Status != subentities.LinkStatusActive {
|
||||
return nil, fmt.Errorf("该用户不是您的有效下属")
|
||||
}
|
||||
page := cmd.Page
|
||||
pageSize := cmd.PageSize
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize < 1 || pageSize > 100 {
|
||||
pageSize = 20
|
||||
}
|
||||
offset := (page - 1) * pageSize
|
||||
rows, total, err := s.subRepo.ListWalletAllocationsByParentAndChild(ctx, cmd.ParentUserID, cmd.ChildUserID, pageSize, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items := make([]responses.ChildAllocationItem, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
items = append(items, responses.ChildAllocationItem{
|
||||
ID: row.ID,
|
||||
Amount: row.Amount.StringFixed(2),
|
||||
BusinessRef: row.BusinessRef,
|
||||
CreatedAt: row.CreatedAt,
|
||||
})
|
||||
}
|
||||
return &responses.ChildAllocationListResponse{
|
||||
Total: total,
|
||||
Items: items,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// AssignChildSubscription 代配订阅
|
||||
func (s *SubordinateApplicationServiceImpl) AssignChildSubscription(ctx context.Context, cmd *commands.AssignChildSubscriptionCommand) error {
|
||||
lnk, err := s.subRepo.FindLinkByParentAndChild(ctx, cmd.ParentUserID, cmd.ChildUserID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if lnk == nil || lnk.Status != subentities.LinkStatusActive {
|
||||
return fmt.Errorf("该用户不是您的有效下属")
|
||||
}
|
||||
|
||||
price, err := decimal.NewFromString(strings.TrimSpace(cmd.Price))
|
||||
if err != nil {
|
||||
return fmt.Errorf("价格格式无效")
|
||||
}
|
||||
|
||||
parentSub, err := s.productSub.GetUserSubscribedProduct(ctx, cmd.ParentUserID, cmd.ProductID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if parentSub == nil {
|
||||
return fmt.Errorf("主账号未订阅该产品,无法为下属代配")
|
||||
}
|
||||
if price.LessThan(parentSub.Price) {
|
||||
return fmt.Errorf("下属订阅价不能低于主账号对该产品的订阅价")
|
||||
}
|
||||
|
||||
uip := parentSub.UIComponentPrice
|
||||
if strings.TrimSpace(cmd.UIComponentPrice) != "" {
|
||||
p, err2 := decimal.NewFromString(strings.TrimSpace(cmd.UIComponentPrice))
|
||||
if err2 != nil {
|
||||
return fmt.Errorf("UI组件价格格式无效")
|
||||
}
|
||||
if p.LessThan(parentSub.UIComponentPrice) {
|
||||
return fmt.Errorf("下属 UI 组合价不能低于主账号的 UI 组合价")
|
||||
}
|
||||
uip = p
|
||||
}
|
||||
|
||||
existing, err := s.productSub.GetUserSubscribedProduct(ctx, cmd.ChildUserID, cmd.ProductID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if existing == nil {
|
||||
newSub := &productentities.Subscription{
|
||||
UserID: cmd.ChildUserID,
|
||||
ProductID: cmd.ProductID,
|
||||
Price: price,
|
||||
UIComponentPrice: uip,
|
||||
}
|
||||
return s.productSub.SaveSubscription(ctx, newSub)
|
||||
}
|
||||
existing.Price = price
|
||||
existing.UIComponentPrice = uip
|
||||
return s.productSub.SaveSubscription(ctx, existing)
|
||||
}
|
||||
|
||||
// ListChildSubscriptions 下属订阅列表
|
||||
func (s *SubordinateApplicationServiceImpl) ListChildSubscriptions(ctx context.Context, cmd *commands.ListChildSubscriptionsCommand) ([]responses.ChildSubscriptionItem, error) {
|
||||
lnk, err := s.subRepo.FindLinkByParentAndChild(ctx, cmd.ParentUserID, cmd.ChildUserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if lnk == nil || lnk.Status != subentities.LinkStatusActive {
|
||||
return nil, fmt.Errorf("该用户不是您的有效下属")
|
||||
}
|
||||
subs, err := s.productSub.GetUserSubscriptions(ctx, cmd.ChildUserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items := make([]responses.ChildSubscriptionItem, 0, len(subs))
|
||||
for _, sub := range subs {
|
||||
items = append(items, responses.ChildSubscriptionItem{
|
||||
ID: sub.ID,
|
||||
ProductID: sub.ProductID,
|
||||
Price: sub.Price.StringFixed(2),
|
||||
UIComponentPrice: sub.UIComponentPrice.StringFixed(2),
|
||||
CreatedAt: sub.CreatedAt,
|
||||
})
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
// RemoveChildSubscription 删除下属订阅
|
||||
func (s *SubordinateApplicationServiceImpl) RemoveChildSubscription(ctx context.Context, cmd *commands.RemoveChildSubscriptionCommand) error {
|
||||
lnk, err := s.subRepo.FindLinkByParentAndChild(ctx, cmd.ParentUserID, cmd.ChildUserID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if lnk == nil || lnk.Status != subentities.LinkStatusActive {
|
||||
return fmt.Errorf("该用户不是您的有效下属")
|
||||
}
|
||||
sub, err := s.productSub.GetSubscriptionByID(ctx, cmd.SubscriptionID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("订阅不存在")
|
||||
}
|
||||
if sub.UserID != cmd.ChildUserID {
|
||||
return fmt.Errorf("订阅不属于该下属")
|
||||
}
|
||||
return s.productSub.CancelSubscription(ctx, cmd.SubscriptionID)
|
||||
}
|
||||
|
||||
// PurchaseChildQuota 主账号为子账号购买调用额度(按子账号订阅价结算)
|
||||
func (s *SubordinateApplicationServiceImpl) PurchaseChildQuota(ctx context.Context, cmd *commands.PurchaseChildQuotaCommand) error {
|
||||
if cmd.CallCount <= 0 {
|
||||
return fmt.Errorf("购买次数必须大于0")
|
||||
}
|
||||
|
||||
parentUser, err := s.userRepo.GetByID(ctx, cmd.ParentUserID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("主账号信息获取失败")
|
||||
}
|
||||
if err := s.smsService.VerifyCode(ctx, parentUser.Phone, strings.TrimSpace(cmd.VerifyCode), user_entities.SMSSceneLogin); err != nil {
|
||||
return fmt.Errorf("验证码错误或已过期")
|
||||
}
|
||||
|
||||
lnk, err := s.subRepo.FindLinkByParentAndChild(ctx, cmd.ParentUserID, cmd.ChildUserID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if lnk == nil || lnk.Status != subentities.LinkStatusActive {
|
||||
return fmt.Errorf("该用户不是您的有效下属")
|
||||
}
|
||||
|
||||
parentSub, err := s.productSub.GetUserSubscribedProduct(ctx, cmd.ParentUserID, cmd.ProductID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if parentSub == nil {
|
||||
return fmt.Errorf("主账号未订阅该产品,无法购买额度")
|
||||
}
|
||||
if !parentSub.Price.GreaterThan(decimal.Zero) {
|
||||
return fmt.Errorf("主账号订阅价格异常,无法购买额度")
|
||||
}
|
||||
|
||||
callCountDec := decimal.NewFromInt(cmd.CallCount)
|
||||
totalAmount := parentSub.Price.Mul(callCountDec)
|
||||
if !totalAmount.GreaterThan(decimal.Zero) {
|
||||
return fmt.Errorf("购买金额必须大于0")
|
||||
}
|
||||
|
||||
bizRef := uuid.New().String()
|
||||
return s.txm.ExecuteInTx(ctx, func(txCtx context.Context) error {
|
||||
// 购买额度前自动确保子账号存在该产品订阅,并统一为主账号订阅价
|
||||
childSub, err := s.productSub.GetUserSubscribedProduct(txCtx, cmd.ChildUserID, cmd.ProductID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if childSub == nil {
|
||||
newSub := &productentities.Subscription{
|
||||
UserID: cmd.ChildUserID,
|
||||
ProductID: cmd.ProductID,
|
||||
Price: parentSub.Price,
|
||||
UIComponentPrice: parentSub.UIComponentPrice,
|
||||
}
|
||||
if err := s.productSub.SaveSubscription(txCtx, newSub); err != nil {
|
||||
return fmt.Errorf("为下属创建订阅失败: %w", err)
|
||||
}
|
||||
} else {
|
||||
childSub.Price = parentSub.Price
|
||||
childSub.UIComponentPrice = parentSub.UIComponentPrice
|
||||
if err := s.productSub.SaveSubscription(txCtx, childSub); err != nil {
|
||||
return fmt.Errorf("更新下属订阅失败: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
ok, err := s.walletRepo.UpdateBalanceByUserID(txCtx, cmd.ParentUserID, totalAmount, "subtract")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return fmt.Errorf("主账号扣款失败,请重试")
|
||||
}
|
||||
|
||||
account, err := s.subRepo.FindQuotaAccount(txCtx, cmd.ChildUserID, cmd.ProductID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var beforeAvailable int64
|
||||
if account == nil {
|
||||
account = &subentities.UserProductQuotaAccount{
|
||||
UserID: cmd.ChildUserID,
|
||||
ProductID: cmd.ProductID,
|
||||
TotalQuota: cmd.CallCount,
|
||||
UsedQuota: 0,
|
||||
AvailableQuota: cmd.CallCount,
|
||||
}
|
||||
beforeAvailable = 0
|
||||
if err := s.subRepo.CreateQuotaAccount(txCtx, account); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
beforeAvailable = account.AvailableQuota
|
||||
account.TotalQuota += cmd.CallCount
|
||||
account.AvailableQuota += cmd.CallCount
|
||||
if err := s.subRepo.UpdateQuotaAccount(txCtx, account); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
purchase := &subentities.SubordinateQuotaPurchase{
|
||||
ParentUserID: cmd.ParentUserID,
|
||||
ChildUserID: cmd.ChildUserID,
|
||||
ProductID: cmd.ProductID,
|
||||
CallCount: cmd.CallCount,
|
||||
UnitPrice: parentSub.Price,
|
||||
TotalAmount: totalAmount,
|
||||
BusinessRef: bizRef,
|
||||
OperatorUserID: cmd.ParentUserID,
|
||||
}
|
||||
if err := s.subRepo.CreateQuotaPurchase(txCtx, purchase); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ledger := &subentities.UserProductQuotaLedger{
|
||||
UserID: cmd.ChildUserID,
|
||||
ProductID: cmd.ProductID,
|
||||
ChangeType: subentities.QuotaLedgerChangeTypePurchaseForSub,
|
||||
DeltaQuota: cmd.CallCount,
|
||||
BeforeQuota: beforeAvailable,
|
||||
AfterQuota: beforeAvailable + cmd.CallCount,
|
||||
SourceID: purchase.ID,
|
||||
OperatorID: cmd.ParentUserID,
|
||||
Remark: "主账号为子账号购买额度",
|
||||
}
|
||||
return s.subRepo.CreateQuotaLedger(txCtx, ledger)
|
||||
})
|
||||
}
|
||||
|
||||
// ListChildQuotaPurchases 下属额度购买记录
|
||||
func (s *SubordinateApplicationServiceImpl) ListChildQuotaPurchases(ctx context.Context, cmd *commands.ListChildQuotaPurchasesCommand) (*responses.ChildQuotaPurchaseListResponse, error) {
|
||||
lnk, err := s.subRepo.FindLinkByParentAndChild(ctx, cmd.ParentUserID, cmd.ChildUserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if lnk == nil || lnk.Status != subentities.LinkStatusActive {
|
||||
return nil, fmt.Errorf("该用户不是您的有效下属")
|
||||
}
|
||||
|
||||
page := cmd.Page
|
||||
pageSize := cmd.PageSize
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize < 1 || pageSize > 100 {
|
||||
pageSize = 20
|
||||
}
|
||||
offset := (page - 1) * pageSize
|
||||
rows, total, err := s.subRepo.ListQuotaPurchasesByParentAndChild(ctx, cmd.ParentUserID, cmd.ChildUserID, pageSize, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
items := make([]responses.ChildQuotaPurchaseItem, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
items = append(items, responses.ChildQuotaPurchaseItem{
|
||||
ID: row.ID,
|
||||
ProductID: row.ProductID,
|
||||
CallCount: row.CallCount,
|
||||
UnitPrice: row.UnitPrice.StringFixed(2),
|
||||
TotalAmount: row.TotalAmount.StringFixed(2),
|
||||
BusinessRef: row.BusinessRef,
|
||||
CreatedAt: row.CreatedAt,
|
||||
})
|
||||
}
|
||||
|
||||
return &responses.ChildQuotaPurchaseListResponse{
|
||||
Total: total,
|
||||
Items: items,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ListChildQuotaAccounts 下属额度账户
|
||||
func (s *SubordinateApplicationServiceImpl) ListChildQuotaAccounts(ctx context.Context, cmd *commands.ListChildQuotaAccountsCommand) ([]responses.ChildQuotaAccountItem, error) {
|
||||
lnk, err := s.subRepo.FindLinkByParentAndChild(ctx, cmd.ParentUserID, cmd.ChildUserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if lnk == nil || lnk.Status != subentities.LinkStatusActive {
|
||||
return nil, fmt.Errorf("该用户不是您的有效下属")
|
||||
}
|
||||
|
||||
accounts, err := s.subRepo.ListQuotaAccountsByUser(ctx, cmd.ChildUserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items := make([]responses.ChildQuotaAccountItem, 0, len(accounts))
|
||||
for _, account := range accounts {
|
||||
items = append(items, responses.ChildQuotaAccountItem{
|
||||
ProductID: account.ProductID,
|
||||
TotalQuota: account.TotalQuota,
|
||||
UsedQuota: account.UsedQuota,
|
||||
AvailableQuota: account.AvailableQuota,
|
||||
})
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
// ListChildApiCalls 主账号查看下属 API 调用记录
|
||||
func (s *SubordinateApplicationServiceImpl) ListChildApiCalls(ctx context.Context, cmd *commands.ListChildApiCallsCommand) (*api_dto.ApiCallListResponse, error) {
|
||||
if s.apiApp == nil {
|
||||
return nil, fmt.Errorf("API 服务未初始化")
|
||||
}
|
||||
lnk, err := s.subRepo.FindLinkByParentAndChild(ctx, cmd.ParentUserID, cmd.ChildUserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if lnk == nil || lnk.Status != subentities.LinkStatusActive {
|
||||
return nil, fmt.Errorf("该用户不是您的有效下属")
|
||||
}
|
||||
|
||||
page := cmd.Page
|
||||
pageSize := cmd.PageSize
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize < 1 || pageSize > 100 {
|
||||
pageSize = 10
|
||||
}
|
||||
|
||||
filters := make(map[string]interface{})
|
||||
if strings.TrimSpace(cmd.TransactionID) != "" {
|
||||
filters["transaction_id"] = strings.TrimSpace(cmd.TransactionID)
|
||||
}
|
||||
if strings.TrimSpace(cmd.ProductName) != "" {
|
||||
filters["product_name"] = strings.TrimSpace(cmd.ProductName)
|
||||
}
|
||||
if strings.TrimSpace(cmd.Status) != "" {
|
||||
filters["status"] = strings.TrimSpace(cmd.Status)
|
||||
}
|
||||
if strings.TrimSpace(cmd.StartTime) != "" {
|
||||
if t, parseErr := time.Parse("2006-01-02 15:04:05", strings.TrimSpace(cmd.StartTime)); parseErr == nil {
|
||||
filters["start_time"] = t
|
||||
}
|
||||
}
|
||||
if strings.TrimSpace(cmd.EndTime) != "" {
|
||||
if t, parseErr := time.Parse("2006-01-02 15:04:05", strings.TrimSpace(cmd.EndTime)); parseErr == nil {
|
||||
filters["end_time"] = t
|
||||
}
|
||||
}
|
||||
|
||||
return s.apiApp.GetUserApiCalls(ctx, cmd.ChildUserID, filters, shared_interfaces.ListOptions{
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
Sort: "created_at",
|
||||
Order: "desc",
|
||||
})
|
||||
}
|
||||
|
||||
// ListMyQuotaAccounts 查询当前用户额度账户(通用能力,适配所有用户)
|
||||
func (s *SubordinateApplicationServiceImpl) ListMyQuotaAccounts(ctx context.Context, userID string) ([]responses.ChildQuotaAccountItem, error) {
|
||||
accounts, err := s.subRepo.ListQuotaAccountsByUser(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items := make([]responses.ChildQuotaAccountItem, 0, len(accounts))
|
||||
for _, account := range accounts {
|
||||
items = append(items, responses.ChildQuotaAccountItem{
|
||||
ProductID: account.ProductID,
|
||||
TotalQuota: account.TotalQuota,
|
||||
UsedQuota: account.UsedQuota,
|
||||
AvailableQuota: account.AvailableQuota,
|
||||
})
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
@@ -43,10 +43,20 @@ type ResetPasswordCommand struct {
|
||||
}
|
||||
|
||||
// SendCodeCommand 发送验证码命令
|
||||
// @Description 发送短信验证码请求参数
|
||||
// @Description 发送短信验证码请求参数。只接收编码后的data字段(使用自定义编码方案,非Base64)
|
||||
type SendCodeCommand struct {
|
||||
Phone string `json:"phone" binding:"required,phone" example:"13800138000"`
|
||||
Scene string `json:"scene" binding:"required,oneof=register login change_password reset_password bind unbind certification" example:"register"`
|
||||
// 编码后的数据(使用自定义编码方案的JSON字符串,包含所有参数:phone, scene, timestamp, nonce, signature)
|
||||
Data string `json:"data" binding:"required" example:"K8mN9vP2sL7kH3oB6yC1zA5uF0qE9tW..."` // 自定义编码后的数据
|
||||
|
||||
// 阿里云滑块验证码参数(直接接收,不参与编码)
|
||||
CaptchaVerifyParam string `json:"captchaVerifyParam,omitempty" example:"..."` // 滑块验证码验证参数
|
||||
|
||||
// 以下字段从data解码后填充,不直接接收
|
||||
Phone string `json:"-"` // 从data解码后获取
|
||||
Scene string `json:"-"` // 从data解码后获取
|
||||
Timestamp int64 `json:"-"` // 从data解码后获取
|
||||
Nonce string `json:"-"` // 从data解码后获取
|
||||
Signature string `json:"-"` // 从data解码后获取
|
||||
}
|
||||
|
||||
// UpdateProfileCommand 更新用户信息命令
|
||||
|
||||
@@ -51,6 +51,8 @@ type UserProfileResponse struct {
|
||||
IsCertified bool `json:"is_certified" example:"false"`
|
||||
CreatedAt time.Time `json:"created_at" example:"2024-01-01T00:00:00Z"`
|
||||
UpdatedAt time.Time `json:"updated_at" example:"2024-01-01T00:00:00Z"`
|
||||
// AccountKind standalone=普通/主站用户 subordinate=主账号邀请的下属
|
||||
AccountKind string `json:"account_kind" example:"standalone"`
|
||||
}
|
||||
|
||||
// SendCodeResponse 发送验证码响应
|
||||
|
||||
@@ -9,11 +9,11 @@ import (
|
||||
|
||||
func (s *UserApplicationServiceImpl) SendCode(ctx context.Context, cmd *commands.SendCodeCommand, clientIP, userAgent string) error {
|
||||
// 1. 检查频率限制
|
||||
if err := s.smsCodeService.CheckRateLimit(ctx, cmd.Phone, entities.SMSScene(cmd.Scene)); err != nil {
|
||||
if err := s.smsCodeService.CheckRateLimit(ctx, cmd.Phone, entities.SMSScene(cmd.Scene), clientIP, userAgent); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err := s.smsCodeService.SendCode(ctx, cmd.Phone, entities.SMSScene(cmd.Scene), clientIP, userAgent)
|
||||
err := s.smsCodeService.SendCode(ctx, cmd.Phone, entities.SMSScene(cmd.Scene), clientIP, userAgent, cmd.CaptchaVerifyParam)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"tyapi-server/internal/domains/user/entities"
|
||||
"tyapi-server/internal/domains/user/events"
|
||||
user_service "tyapi-server/internal/domains/user/services"
|
||||
"tyapi-server/internal/shared/auth"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
"tyapi-server/internal/shared/middleware"
|
||||
)
|
||||
@@ -27,6 +28,7 @@ type UserApplicationServiceImpl struct {
|
||||
contractService user_service.ContractAggregateService
|
||||
eventBus interfaces.EventBus
|
||||
jwtAuth *middleware.JWTAuthMiddleware
|
||||
accountKindProvider interfaces.AccountKindProvider
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
@@ -39,6 +41,7 @@ func NewUserApplicationService(
|
||||
contractService user_service.ContractAggregateService,
|
||||
eventBus interfaces.EventBus,
|
||||
jwtAuth *middleware.JWTAuthMiddleware,
|
||||
accountKindProvider interfaces.AccountKindProvider,
|
||||
logger *zap.Logger,
|
||||
) UserApplicationService {
|
||||
return &UserApplicationServiceImpl{
|
||||
@@ -49,6 +52,7 @@ func NewUserApplicationService(
|
||||
contractService: contractService,
|
||||
eventBus: eventBus,
|
||||
jwtAuth: jwtAuth,
|
||||
accountKindProvider: accountKindProvider,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
@@ -90,76 +94,16 @@ func (s *UserApplicationServiceImpl) LoginWithPassword(ctx context.Context, cmd
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 2. 生成包含用户类型的token
|
||||
accessToken, err := s.jwtAuth.GenerateToken(user.ID, user.Phone, user.Phone, user.UserType)
|
||||
if err != nil {
|
||||
s.logger.Error("生成令牌失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("生成访问令牌失败")
|
||||
}
|
||||
|
||||
// 3. 如果是管理员,更新登录统计
|
||||
if user.IsAdmin() {
|
||||
if err := s.userAggregateService.UpdateLoginStats(ctx, user.ID); err != nil {
|
||||
s.logger.Error("更新登录统计失败", zap.Error(err))
|
||||
}
|
||||
// 重新获取用户信息以获取最新的登录统计
|
||||
updatedUser, err := s.userAggregateService.GetUserByID(ctx, user.ID)
|
||||
if err != nil {
|
||||
s.logger.Error("重新获取用户信息失败", zap.Error(err))
|
||||
} else {
|
||||
user = updatedUser
|
||||
// 2. 账号类型(下属/普通)
|
||||
accountKind := auth.AccountKindStandalone
|
||||
if s.accountKindProvider != nil {
|
||||
if k, err := s.accountKindProvider.AccountKind(ctx, user.ID); err == nil && k != "" {
|
||||
accountKind = k
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 获取用户权限(仅管理员)
|
||||
var permissions []string
|
||||
if user.IsAdmin() {
|
||||
permissions, err = s.userAuthService.GetUserPermissions(ctx, user)
|
||||
if err != nil {
|
||||
s.logger.Error("获取用户权限失败", zap.Error(err))
|
||||
permissions = []string{}
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 构建用户信息
|
||||
userProfile := &responses.UserProfileResponse{
|
||||
ID: user.ID,
|
||||
Phone: user.Phone,
|
||||
Username: user.Username,
|
||||
UserType: user.UserType,
|
||||
IsActive: user.Active,
|
||||
LastLoginAt: user.LastLoginAt,
|
||||
LoginCount: user.LoginCount,
|
||||
Permissions: permissions,
|
||||
CreatedAt: user.CreatedAt,
|
||||
UpdatedAt: user.UpdatedAt,
|
||||
}
|
||||
|
||||
return &responses.LoginUserResponse{
|
||||
User: userProfile,
|
||||
AccessToken: accessToken,
|
||||
TokenType: "Bearer",
|
||||
ExpiresIn: 86400, // 24h
|
||||
LoginMethod: "password",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// LoginWithSMS 短信验证码登录
|
||||
// 业务流程:1. 验证短信验证码 2. 验证用户登录状态 3. 生成访问令牌 4. 更新登录统计 5. 获取用户权限
|
||||
func (s *UserApplicationServiceImpl) LoginWithSMS(ctx context.Context, cmd *commands.LoginWithSMSCommand) (*responses.LoginUserResponse, error) {
|
||||
// 1. 验证短信验证码
|
||||
if err := s.smsCodeService.VerifyCode(ctx, cmd.Phone, cmd.Code, entities.SMSSceneLogin); err != nil {
|
||||
return nil, fmt.Errorf("验证码错误或已过期")
|
||||
}
|
||||
|
||||
// 2. 验证用户登录状态
|
||||
user, err := s.userAuthService.ValidateUserLogin(ctx, cmd.Phone)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 3. 生成包含用户类型的token
|
||||
accessToken, err := s.jwtAuth.GenerateToken(user.ID, user.Phone, user.Phone, user.UserType)
|
||||
// 3. 生成包含用户类型的 token
|
||||
accessToken, err := s.jwtAuth.GenerateToken(user.ID, user.Phone, user.Phone, user.UserType, accountKind)
|
||||
if err != nil {
|
||||
s.logger.Error("生成令牌失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("生成访问令牌失败")
|
||||
@@ -201,6 +145,83 @@ func (s *UserApplicationServiceImpl) LoginWithSMS(ctx context.Context, cmd *comm
|
||||
Permissions: permissions,
|
||||
CreatedAt: user.CreatedAt,
|
||||
UpdatedAt: user.UpdatedAt,
|
||||
AccountKind: accountKind,
|
||||
}
|
||||
|
||||
return &responses.LoginUserResponse{
|
||||
User: userProfile,
|
||||
AccessToken: accessToken,
|
||||
TokenType: "Bearer",
|
||||
ExpiresIn: 86400, // 24h
|
||||
LoginMethod: "password",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// LoginWithSMS 短信验证码登录
|
||||
// 业务流程:1. 验证短信验证码 2. 验证用户登录状态 3. 生成访问令牌 4. 更新登录统计 5. 获取用户权限
|
||||
func (s *UserApplicationServiceImpl) LoginWithSMS(ctx context.Context, cmd *commands.LoginWithSMSCommand) (*responses.LoginUserResponse, error) {
|
||||
// 1. 验证短信验证码
|
||||
if err := s.smsCodeService.VerifyCode(ctx, cmd.Phone, cmd.Code, entities.SMSSceneLogin); err != nil {
|
||||
return nil, fmt.Errorf("验证码错误或已过期")
|
||||
}
|
||||
|
||||
// 2. 验证用户登录状态
|
||||
user, err := s.userAuthService.ValidateUserLogin(ctx, cmd.Phone)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
accountKind := auth.AccountKindStandalone
|
||||
if s.accountKindProvider != nil {
|
||||
if k, err := s.accountKindProvider.AccountKind(ctx, user.ID); err == nil && k != "" {
|
||||
accountKind = k
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 生成包含用户类型的 token
|
||||
accessToken, err := s.jwtAuth.GenerateToken(user.ID, user.Phone, user.Phone, user.UserType, accountKind)
|
||||
if err != nil {
|
||||
s.logger.Error("生成令牌失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("生成访问令牌失败")
|
||||
}
|
||||
|
||||
// 4. 如果是管理员,更新登录统计
|
||||
if user.IsAdmin() {
|
||||
if err := s.userAggregateService.UpdateLoginStats(ctx, user.ID); err != nil {
|
||||
s.logger.Error("更新登录统计失败", zap.Error(err))
|
||||
}
|
||||
// 重新获取用户信息以获取最新的登录统计
|
||||
updatedUser, err := s.userAggregateService.GetUserByID(ctx, user.ID)
|
||||
if err != nil {
|
||||
s.logger.Error("重新获取用户信息失败", zap.Error(err))
|
||||
} else {
|
||||
user = updatedUser
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 获取用户权限(仅管理员)
|
||||
var permissions []string
|
||||
if user.IsAdmin() {
|
||||
permissions, err = s.userAuthService.GetUserPermissions(ctx, user)
|
||||
if err != nil {
|
||||
s.logger.Error("获取用户权限失败", zap.Error(err))
|
||||
permissions = []string{}
|
||||
}
|
||||
}
|
||||
|
||||
// 6. 构建用户信息
|
||||
userProfile := &responses.UserProfileResponse{
|
||||
ID: user.ID,
|
||||
Phone: user.Phone,
|
||||
Username: user.Username,
|
||||
UserType: user.UserType,
|
||||
IsActive: user.Active,
|
||||
LastLoginAt: user.LastLoginAt,
|
||||
LoginCount: user.LoginCount,
|
||||
Permissions: permissions,
|
||||
CreatedAt: user.CreatedAt,
|
||||
UpdatedAt: user.UpdatedAt,
|
||||
AccountKind: accountKind,
|
||||
}
|
||||
|
||||
return &responses.LoginUserResponse{
|
||||
@@ -212,12 +233,6 @@ func (s *UserApplicationServiceImpl) LoginWithSMS(ctx context.Context, cmd *comm
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SendSMS 发送短信验证码
|
||||
// 业务流程:1. 发送短信验证码
|
||||
func (s *UserApplicationServiceImpl) SendSMS(ctx context.Context, cmd *commands.SendCodeCommand) error {
|
||||
return s.smsCodeService.SendCode(ctx, cmd.Phone, entities.SMSScene(cmd.Scene), "", "")
|
||||
}
|
||||
|
||||
// ChangePassword 修改密码
|
||||
// 业务流程:1. 修改用户密码
|
||||
func (s *UserApplicationServiceImpl) ChangePassword(ctx context.Context, cmd *commands.ChangePasswordCommand) error {
|
||||
@@ -268,6 +283,12 @@ func (s *UserApplicationServiceImpl) GetUserProfile(ctx context.Context, userID
|
||||
Permissions: permissions,
|
||||
CreatedAt: user.CreatedAt,
|
||||
UpdatedAt: user.UpdatedAt,
|
||||
AccountKind: auth.AccountKindStandalone,
|
||||
}
|
||||
if s.accountKindProvider != nil {
|
||||
if k, err := s.accountKindProvider.AccountKind(ctx, userID); err == nil && k != "" {
|
||||
userProfile.AccountKind = k
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 添加企业信息
|
||||
@@ -345,19 +366,19 @@ func (s *UserApplicationServiceImpl) ListUsers(ctx context.Context, query *queri
|
||||
EnterpriseAddress: user.EnterpriseInfo.EnterpriseAddress,
|
||||
CreatedAt: user.EnterpriseInfo.CreatedAt,
|
||||
}
|
||||
|
||||
|
||||
// 获取企业合同信息
|
||||
contracts, err := s.contractService.FindByUserID(ctx, user.ID)
|
||||
if err == nil && len(contracts) > 0 {
|
||||
contractItems := make([]*responses.ContractInfoItem, 0, len(contracts))
|
||||
for _, contract := range contracts {
|
||||
contractItems = append(contractItems, &responses.ContractInfoItem{
|
||||
ID: contract.ID,
|
||||
ContractName: contract.ContractName,
|
||||
ContractType: string(contract.ContractType),
|
||||
ID: contract.ID,
|
||||
ContractName: contract.ContractName,
|
||||
ContractType: string(contract.ContractType),
|
||||
ContractTypeName: contract.GetContractTypeName(),
|
||||
ContractFileURL: contract.ContractFileURL,
|
||||
CreatedAt: contract.CreatedAt,
|
||||
ContractFileURL: contract.ContractFileURL,
|
||||
CreatedAt: contract.CreatedAt,
|
||||
})
|
||||
}
|
||||
item.EnterpriseInfo.Contracts = contractItems
|
||||
@@ -417,19 +438,19 @@ func (s *UserApplicationServiceImpl) GetUserDetail(ctx context.Context, userID s
|
||||
EnterpriseAddress: user.EnterpriseInfo.EnterpriseAddress,
|
||||
CreatedAt: user.EnterpriseInfo.CreatedAt,
|
||||
}
|
||||
|
||||
|
||||
// 获取企业合同信息
|
||||
contracts, err := s.contractService.FindByUserID(ctx, user.ID)
|
||||
if err == nil && len(contracts) > 0 {
|
||||
contractItems := make([]*responses.ContractInfoItem, 0, len(contracts))
|
||||
for _, contract := range contracts {
|
||||
contractItems = append(contractItems, &responses.ContractInfoItem{
|
||||
ID: contract.ID,
|
||||
ContractName: contract.ContractName,
|
||||
ContractType: string(contract.ContractType),
|
||||
ID: contract.ID,
|
||||
ContractName: contract.ContractName,
|
||||
ContractType: string(contract.ContractType),
|
||||
ContractTypeName: contract.GetContractTypeName(),
|
||||
ContractFileURL: contract.ContractFileURL,
|
||||
CreatedAt: contract.CreatedAt,
|
||||
ContractFileURL: contract.ContractFileURL,
|
||||
CreatedAt: contract.CreatedAt,
|
||||
})
|
||||
}
|
||||
item.EnterpriseInfo.Contracts = contractItems
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -39,6 +41,12 @@ type Config struct {
|
||||
Alicloud AlicloudConfig `mapstructure:"alicloud"`
|
||||
Xingwei XingweiConfig `mapstructure:"xingwei"`
|
||||
Jiguang JiguangConfig `mapstructure:"jiguang"`
|
||||
Shumai ShumaiConfig `mapstructure:"shumai"`
|
||||
Shujubao ShujubaoConfig `mapstructure:"shujubao"`
|
||||
PDFGen PDFGenConfig `mapstructure:"pdfgen"`
|
||||
Huibo HuiboConfig `mapstructure:"huibo"`
|
||||
Nuoer NuoerConfig `mapstructure:"nuoer"`
|
||||
Haiyuapi HaiyuapiConfig `mapstructure:"haiyuapi"`
|
||||
}
|
||||
|
||||
// ServerConfig HTTP服务器配置
|
||||
@@ -192,11 +200,44 @@ type AppConfig struct {
|
||||
Name string `mapstructure:"name"`
|
||||
Version string `mapstructure:"version"`
|
||||
Env string `mapstructure:"env"`
|
||||
// SubPortalBaseURL 子账号使用的前端基址(可与主站同域),用于邀请链接,无尾斜杠
|
||||
SubPortalBaseURL string `mapstructure:"sub_portal_base_url"`
|
||||
}
|
||||
|
||||
// APIConfig API配置
|
||||
type APIConfig struct {
|
||||
Domain string `mapstructure:"domain"`
|
||||
// PublicBaseURL 浏览器/第三方访问本 API 服务的完整基址(如 https://api.example.com 或 http://127.0.0.1:8080),无尾斜杠。
|
||||
// 用于企业全景报告 reportUrl、headless PDF 预生成等。为空时由 Domain 推导为 https://{Domain}(Domain 若已含 scheme 则沿用)。
|
||||
PublicBaseURL string `mapstructure:"public_base_url"`
|
||||
}
|
||||
|
||||
// ResolvedPublicBaseURL 由配置推导对外基址(不读环境变量)。
|
||||
func (c *APIConfig) ResolvedPublicBaseURL() string {
|
||||
u := strings.TrimSpace(c.PublicBaseURL)
|
||||
if u != "" {
|
||||
return strings.TrimRight(u, "/")
|
||||
}
|
||||
d := strings.TrimSpace(c.Domain)
|
||||
if d == "" {
|
||||
return ""
|
||||
}
|
||||
lo := strings.ToLower(d)
|
||||
if strings.HasPrefix(lo, "http://") || strings.HasPrefix(lo, "https://") {
|
||||
return strings.TrimRight(d, "/")
|
||||
}
|
||||
return "https://" + strings.TrimRight(d, "/")
|
||||
}
|
||||
|
||||
// ResolveAPIPublicBaseURL 对外 API 基址。优先环境变量 API_PUBLIC_BASE_URL,否则使用 API 配置。
|
||||
func ResolveAPIPublicBaseURL(cfg *APIConfig) string {
|
||||
if s := strings.TrimSpace(os.Getenv("API_PUBLIC_BASE_URL")); s != "" {
|
||||
return strings.TrimRight(s, "/")
|
||||
}
|
||||
if cfg == nil {
|
||||
return ""
|
||||
}
|
||||
return cfg.ResolvedPublicBaseURL()
|
||||
}
|
||||
|
||||
// SMSConfig 短信配置
|
||||
@@ -210,6 +251,14 @@ type SMSConfig struct {
|
||||
ExpireTime time.Duration `mapstructure:"expire_time"`
|
||||
RateLimit SMSRateLimit `mapstructure:"rate_limit"`
|
||||
MockEnabled bool `mapstructure:"mock_enabled"` // 是否启用模拟短信服务
|
||||
// 签名验证配置
|
||||
SignatureEnabled bool `mapstructure:"signature_enabled"` // 是否启用签名验证
|
||||
SignatureSecret string `mapstructure:"signature_secret"` // 签名密钥
|
||||
// 滑块验证码配置
|
||||
CaptchaEnabled bool `mapstructure:"captcha_enabled"` // 是否启用滑块验证码
|
||||
CaptchaSecret string `mapstructure:"captcha_secret"` // 阿里云验证码密钥
|
||||
CaptchaEndpoint string `mapstructure:"captcha_endpoint"` // 阿里云验证码服务Endpoint
|
||||
SceneID string `mapstructure:"scene_id"` // 阿里云验证码场景ID
|
||||
}
|
||||
|
||||
// SMSRateLimit 短信限流配置
|
||||
@@ -320,11 +369,13 @@ type SignConfig struct {
|
||||
|
||||
// WalletConfig 钱包配置
|
||||
type WalletConfig struct {
|
||||
DefaultCreditLimit float64 `mapstructure:"default_credit_limit"`
|
||||
MinAmount string `mapstructure:"min_amount"` // 最低充值金额
|
||||
MaxAmount string `mapstructure:"max_amount"` // 最高充值金额
|
||||
AliPayRechargeBonus []AliPayRechargeBonusRule `mapstructure:"alipay_recharge_bonus"`
|
||||
BalanceAlert BalanceAlertConfig `mapstructure:"balance_alert"`
|
||||
DefaultCreditLimit float64 `mapstructure:"default_credit_limit"`
|
||||
MinAmount string `mapstructure:"min_amount"` // 最低充值金额
|
||||
MaxAmount string `mapstructure:"max_amount"` // 最高充值金额
|
||||
RechargeBonusEnabled bool `mapstructure:"recharge_bonus_enabled"` // 是否启用充值赠送,关闭后仅展示商务洽谈提示
|
||||
ApiStoreRechargeTip string `mapstructure:"api_store_recharge_tip"` // API 商店充值提示文案(大额/批量需求联系商务)
|
||||
AliPayRechargeBonus []AliPayRechargeBonusRule `mapstructure:"alipay_recharge_bonus"`
|
||||
BalanceAlert BalanceAlertConfig `mapstructure:"balance_alert"`
|
||||
}
|
||||
|
||||
// BalanceAlertConfig 余额预警配置
|
||||
@@ -550,6 +601,168 @@ type JiguangLevelFileConfig struct {
|
||||
Compress bool `mapstructure:"compress"`
|
||||
}
|
||||
|
||||
// ShumaiConfig 数脉配置
|
||||
type ShumaiConfig struct {
|
||||
URL string `mapstructure:"url"`
|
||||
AppID string `mapstructure:"app_id"`
|
||||
AppSecret string `mapstructure:"app_secret"`
|
||||
AppID2 string `mapstructure:"app_id2"` // 走政务接口使用这个
|
||||
AppSecret2 string `mapstructure:"app_secret2"` // 走政务接口使用这个
|
||||
SignMethod string `mapstructure:"sign_method"` // md5 或 hmac,默认 hmac
|
||||
Timeout time.Duration `mapstructure:"timeout"`
|
||||
|
||||
Logging ShumaiLoggingConfig `mapstructure:"logging"`
|
||||
}
|
||||
|
||||
// ShumaiLoggingConfig 数脉日志配置
|
||||
type ShumaiLoggingConfig struct {
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
LogDir string `mapstructure:"log_dir"`
|
||||
UseDaily bool `mapstructure:"use_daily"`
|
||||
EnableLevelSeparation bool `mapstructure:"enable_level_separation"`
|
||||
LevelConfigs map[string]ShumaiLevelFileConfig `mapstructure:"level_configs"`
|
||||
}
|
||||
|
||||
// ShumaiLevelFileConfig 数脉级别文件配置
|
||||
type ShumaiLevelFileConfig struct {
|
||||
MaxSize int `mapstructure:"max_size"`
|
||||
MaxBackups int `mapstructure:"max_backups"`
|
||||
MaxAge int `mapstructure:"max_age"`
|
||||
Compress bool `mapstructure:"compress"`
|
||||
}
|
||||
|
||||
// ShujubaoConfig 数据宝配置
|
||||
type ShujubaoConfig struct {
|
||||
URL string `mapstructure:"url"`
|
||||
AppSecret string `mapstructure:"app_secret"`
|
||||
SignMethod string `mapstructure:"sign_method"` // md5 或 hmac,默认 hmac
|
||||
Timeout time.Duration `mapstructure:"timeout"`
|
||||
|
||||
Logging ShujubaoLoggingConfig `mapstructure:"logging"`
|
||||
}
|
||||
|
||||
// ShujubaoLoggingConfig 数据宝日志配置
|
||||
type ShujubaoLoggingConfig struct {
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
LogDir string `mapstructure:"log_dir"`
|
||||
UseDaily bool `mapstructure:"use_daily"`
|
||||
EnableLevelSeparation bool `mapstructure:"enable_level_separation"`
|
||||
LevelConfigs map[string]ShujubaoLevelFileConfig `mapstructure:"level_configs"`
|
||||
}
|
||||
|
||||
// ShujubaoLevelFileConfig 数据宝级别文件配置
|
||||
type ShujubaoLevelFileConfig struct {
|
||||
MaxSize int `mapstructure:"max_size"`
|
||||
MaxBackups int `mapstructure:"max_backups"`
|
||||
MaxAge int `mapstructure:"max_age"`
|
||||
Compress bool `mapstructure:"compress"`
|
||||
}
|
||||
|
||||
// PDFGenConfig PDF生成服务配置
|
||||
type PDFGenConfig struct {
|
||||
DevelopmentURL string `mapstructure:"development_url"` // 开发环境服务地址
|
||||
ProductionURL string `mapstructure:"production_url"` // 生产环境服务地址
|
||||
APIPath string `mapstructure:"api_path"` // API路径
|
||||
Timeout time.Duration `mapstructure:"timeout"` // 请求超时时间
|
||||
Cache PDFGenCacheConfig `mapstructure:"cache"` // 缓存配置
|
||||
}
|
||||
|
||||
// PDFGenCacheConfig PDF生成缓存配置
|
||||
type PDFGenCacheConfig struct {
|
||||
TTL time.Duration `mapstructure:"ttl"` // 缓存过期时间
|
||||
CacheDir string `mapstructure:"cache_dir"` // 缓存目录(空则使用默认目录)
|
||||
MaxSize int64 `mapstructure:"max_size"` // 最大缓存大小(0表示不限制,单位:字节)
|
||||
}
|
||||
|
||||
// HuiboConfig 汇博(BHSC)配置
|
||||
type HuiboConfig struct {
|
||||
URL string `mapstructure:"url"`
|
||||
AppID string `mapstructure:"app_id"`
|
||||
AppKey string `mapstructure:"app_key"`
|
||||
XOrderCode string `mapstructure:"x_order_code"`
|
||||
SecretID string `mapstructure:"secret_id"`
|
||||
AESKey string `mapstructure:"aes_key"`
|
||||
WorkOrderCode string `mapstructure:"work_order_code"`
|
||||
ProductCode string `mapstructure:"product_code"`
|
||||
BaseURL2 string `mapstructure:"baseUrl2"`
|
||||
AppCode2 string `mapstructure:"app_code2"`
|
||||
|
||||
Logging HuiboLoggingConfig `mapstructure:"logging"`
|
||||
}
|
||||
|
||||
// HuiboLoggingConfig 汇博日志配置
|
||||
type HuiboLoggingConfig struct {
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
LogDir string `mapstructure:"log_dir"`
|
||||
UseDaily bool `mapstructure:"use_daily"`
|
||||
EnableLevelSeparation bool `mapstructure:"enable_level_separation"`
|
||||
LevelConfigs map[string]HuiboLevelFileConfig `mapstructure:"level_configs"`
|
||||
}
|
||||
|
||||
// HuiboLevelFileConfig 汇博级别日志配置
|
||||
type HuiboLevelFileConfig struct {
|
||||
MaxSize int `mapstructure:"max_size"`
|
||||
MaxBackups int `mapstructure:"max_backups"`
|
||||
MaxAge int `mapstructure:"max_age"`
|
||||
Compress bool `mapstructure:"compress"`
|
||||
}
|
||||
|
||||
// NuoerConfig 诺尔智汇配置
|
||||
type NuoerConfig struct {
|
||||
URL string `mapstructure:"url"`
|
||||
AppID string `mapstructure:"app_id"`
|
||||
AppSecret string `mapstructure:"app_secret"`
|
||||
Timeout time.Duration `mapstructure:"timeout"`
|
||||
|
||||
Logging NuoerLoggingConfig `mapstructure:"logging"`
|
||||
}
|
||||
|
||||
// NuoerLoggingConfig 诺尔智汇日志配置
|
||||
type NuoerLoggingConfig struct {
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
LogDir string `mapstructure:"log_dir"`
|
||||
ServiceName string `mapstructure:"service_name"`
|
||||
UseDaily bool `mapstructure:"use_daily"`
|
||||
EnableLevelSeparation bool `mapstructure:"enable_level_separation"`
|
||||
LevelConfigs map[string]NuoerLevelFileConfig `mapstructure:"level_configs"`
|
||||
}
|
||||
|
||||
// NuoerLevelFileConfig 诺尔智汇级别文件配置
|
||||
type NuoerLevelFileConfig struct {
|
||||
MaxSize int `mapstructure:"max_size"`
|
||||
MaxBackups int `mapstructure:"max_backups"`
|
||||
MaxAge int `mapstructure:"max_age"`
|
||||
Compress bool `mapstructure:"compress"`
|
||||
}
|
||||
|
||||
// HaiyuapiConfig 海宇API(上游数据源)配置
|
||||
type HaiyuapiConfig struct {
|
||||
BaseURL string `mapstructure:"base_url"`
|
||||
AccessID string `mapstructure:"access_id"`
|
||||
SecretKey string `mapstructure:"secret_key"`
|
||||
Timeout time.Duration `mapstructure:"timeout"`
|
||||
|
||||
Logging HaiyuapiLoggingConfig `mapstructure:"logging"`
|
||||
}
|
||||
|
||||
// HaiyuapiLoggingConfig 海宇API日志配置
|
||||
type HaiyuapiLoggingConfig struct {
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
LogDir string `mapstructure:"log_dir"`
|
||||
ServiceName string `mapstructure:"service_name"`
|
||||
UseDaily bool `mapstructure:"use_daily"`
|
||||
EnableLevelSeparation bool `mapstructure:"enable_level_separation"`
|
||||
LevelConfigs map[string]HaiyuapiLevelFileConfig `mapstructure:"level_configs"`
|
||||
}
|
||||
|
||||
// HaiyuapiLevelFileConfig 海宇API级别文件配置
|
||||
type HaiyuapiLevelFileConfig struct {
|
||||
MaxSize int `mapstructure:"max_size"`
|
||||
MaxBackups int `mapstructure:"max_backups"`
|
||||
MaxAge int `mapstructure:"max_age"`
|
||||
Compress bool `mapstructure:"compress"`
|
||||
}
|
||||
|
||||
// DomainConfig 域名配置
|
||||
type DomainConfig struct {
|
||||
API string `mapstructure:"api"` // API域名
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"go.uber.org/fx"
|
||||
@@ -17,6 +16,7 @@ import (
|
||||
"tyapi-server/internal/application/finance"
|
||||
"tyapi-server/internal/application/product"
|
||||
"tyapi-server/internal/application/statistics"
|
||||
subordinate_app "tyapi-server/internal/application/subordinate"
|
||||
"tyapi-server/internal/application/user"
|
||||
"tyapi-server/internal/config"
|
||||
api_repositories "tyapi-server/internal/domains/api/repositories"
|
||||
@@ -29,6 +29,7 @@ import (
|
||||
domain_product_repo "tyapi-server/internal/domains/product/repositories"
|
||||
product_service "tyapi-server/internal/domains/product/services"
|
||||
statistics_service "tyapi-server/internal/domains/statistics/services"
|
||||
domain_subordinate_repo "tyapi-server/internal/domains/subordinate/repositories"
|
||||
user_service "tyapi-server/internal/domains/user/services"
|
||||
"tyapi-server/internal/infrastructure/cache"
|
||||
"tyapi-server/internal/infrastructure/database"
|
||||
@@ -36,14 +37,21 @@ import (
|
||||
certification_repo "tyapi-server/internal/infrastructure/database/repositories/certification"
|
||||
finance_repo "tyapi-server/internal/infrastructure/database/repositories/finance"
|
||||
product_repo "tyapi-server/internal/infrastructure/database/repositories/product"
|
||||
subordinate_db "tyapi-server/internal/infrastructure/database/repositories/subordinate"
|
||||
infra_events "tyapi-server/internal/infrastructure/events"
|
||||
"tyapi-server/internal/infrastructure/external/alicloud"
|
||||
"tyapi-server/internal/infrastructure/external/captcha"
|
||||
"tyapi-server/internal/infrastructure/external/email"
|
||||
"tyapi-server/internal/infrastructure/external/huibo"
|
||||
"tyapi-server/internal/infrastructure/external/jiguang"
|
||||
"tyapi-server/internal/infrastructure/external/muzi"
|
||||
"tyapi-server/internal/infrastructure/external/nuoer"
|
||||
"tyapi-server/internal/infrastructure/external/ocr"
|
||||
"tyapi-server/internal/infrastructure/external/shujubao"
|
||||
"tyapi-server/internal/infrastructure/external/shumai"
|
||||
"tyapi-server/internal/infrastructure/external/sms"
|
||||
"tyapi-server/internal/infrastructure/external/storage"
|
||||
"tyapi-server/internal/infrastructure/external/haiyuapi"
|
||||
"tyapi-server/internal/infrastructure/external/tianyancha"
|
||||
"tyapi-server/internal/infrastructure/external/westdex"
|
||||
"tyapi-server/internal/infrastructure/external/xingwei"
|
||||
@@ -51,6 +59,7 @@ import (
|
||||
"tyapi-server/internal/infrastructure/external/zhicha"
|
||||
"tyapi-server/internal/infrastructure/http/handlers"
|
||||
"tyapi-server/internal/infrastructure/http/routes"
|
||||
subordinate_infra "tyapi-server/internal/infrastructure/subordinate"
|
||||
"tyapi-server/internal/infrastructure/task"
|
||||
task_implementations "tyapi-server/internal/infrastructure/task/implementations"
|
||||
asynq "tyapi-server/internal/infrastructure/task/implementations/asynq"
|
||||
@@ -65,6 +74,7 @@ import (
|
||||
"tyapi-server/internal/shared/hooks"
|
||||
sharedhttp "tyapi-server/internal/shared/http"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
"tyapi-server/internal/shared/ipgeo"
|
||||
"tyapi-server/internal/shared/logger"
|
||||
"tyapi-server/internal/shared/metrics"
|
||||
"tyapi-server/internal/shared/middleware"
|
||||
@@ -85,6 +95,7 @@ import (
|
||||
api_app "tyapi-server/internal/application/api"
|
||||
domain_api_repo "tyapi-server/internal/domains/api/repositories"
|
||||
api_services "tyapi-server/internal/domains/api/services"
|
||||
api_processors "tyapi-server/internal/domains/api/services/processors"
|
||||
finance_services "tyapi-server/internal/domains/finance/services"
|
||||
product_services "tyapi-server/internal/domains/product/services"
|
||||
domain_statistics_repo "tyapi-server/internal/domains/statistics/repositories"
|
||||
@@ -237,6 +248,19 @@ func NewContainer() *Container {
|
||||
},
|
||||
// 短信服务
|
||||
sms.NewAliSMSService,
|
||||
// 验证码服务
|
||||
fx.Annotate(
|
||||
func(cfg *config.Config) *captcha.CaptchaService {
|
||||
return captcha.NewCaptchaService(captcha.CaptchaConfig{
|
||||
AccessKeyID: cfg.SMS.AccessKeyID,
|
||||
AccessKeySecret: cfg.SMS.AccessKeySecret,
|
||||
EndpointURL: cfg.SMS.CaptchaEndpoint,
|
||||
SceneID: cfg.SMS.SceneID,
|
||||
EncryptKey: cfg.SMS.CaptchaSecret, // 加密模式 ekey(Base64 编码的 32 字节)
|
||||
})
|
||||
},
|
||||
fx.ResultTags(`name:"captchaService"`),
|
||||
),
|
||||
// 邮件服务
|
||||
fx.Annotate(
|
||||
func(cfg *config.Config, logger *zap.Logger) *email.QQEmailService {
|
||||
@@ -371,6 +395,26 @@ func NewContainer() *Container {
|
||||
func(cfg *config.Config) (*jiguang.JiguangService, error) {
|
||||
return jiguang.NewJiguangServiceWithConfig(cfg)
|
||||
},
|
||||
// ShumaiService - 数脉服务
|
||||
func(cfg *config.Config) (*shumai.ShumaiService, error) {
|
||||
return shumai.NewShumaiServiceWithConfig(cfg)
|
||||
},
|
||||
// HuiboService - 汇博(BHSC)服务
|
||||
func(cfg *config.Config) (*huibo.HuiboService, error) {
|
||||
return huibo.NewHuiboServiceWithConfig(cfg)
|
||||
},
|
||||
// ShujubaoService - 数据宝服务
|
||||
func(cfg *config.Config) (*shujubao.ShujubaoService, error) {
|
||||
return shujubao.NewShujubaoServiceWithConfig(cfg)
|
||||
},
|
||||
// NuoerService - 诺尔智汇服务
|
||||
func(cfg *config.Config) (*nuoer.NuoerService, error) {
|
||||
return nuoer.NewNuoerServiceWithConfig(cfg)
|
||||
},
|
||||
// HaiyuapiService - 海宇API上游服务
|
||||
func(cfg *config.Config) (*haiyuapi.HaiyuapiService, error) {
|
||||
return haiyuapi.NewHaiyuapiServiceWithConfig(cfg)
|
||||
},
|
||||
func(cfg *config.Config) *yushan.YushanService {
|
||||
return yushan.NewYushanService(
|
||||
cfg.Yushan.URL,
|
||||
@@ -388,13 +432,11 @@ func NewContainer() *Container {
|
||||
)
|
||||
},
|
||||
// AlicloudService - 阿里云服务
|
||||
func(cfg *config.Config) *alicloud.AlicloudService {
|
||||
return alicloud.NewAlicloudService(
|
||||
cfg.Alicloud.Host,
|
||||
cfg.Alicloud.AppCode,
|
||||
)
|
||||
func(cfg *config.Config) (*alicloud.AlicloudService, error) {
|
||||
return alicloud.NewAlicloudServiceWithConfig(cfg)
|
||||
},
|
||||
sharedhttp.NewGinRouter,
|
||||
ipgeo.NewLocator,
|
||||
),
|
||||
|
||||
// 中间件组件
|
||||
@@ -405,7 +447,7 @@ func NewContainer() *Container {
|
||||
middleware.NewCORSMiddleware,
|
||||
middleware.NewRateLimitMiddleware,
|
||||
// 每日限流中间件
|
||||
func(cfg *config.Config, redis *redis.Client, response interfaces.ResponseBuilder, logger *zap.Logger) *middleware.DailyRateLimitMiddleware {
|
||||
func(cfg *config.Config, redis *redis.Client, db *gorm.DB, response interfaces.ResponseBuilder, logger *zap.Logger) *middleware.DailyRateLimitMiddleware {
|
||||
limitConfig := middleware.DailyRateLimitConfig{
|
||||
MaxRequestsPerDay: cfg.DailyRateLimit.MaxRequestsPerDay,
|
||||
MaxRequestsPerIP: cfg.DailyRateLimit.MaxRequestsPerIP,
|
||||
@@ -429,7 +471,7 @@ func NewContainer() *Container {
|
||||
// 排除域名配置
|
||||
ExcludeDomains: cfg.DailyRateLimit.ExcludeDomains,
|
||||
}
|
||||
return middleware.NewDailyRateLimitMiddleware(cfg, redis, response, logger, limitConfig)
|
||||
return middleware.NewDailyRateLimitMiddleware(cfg, redis, db, response, logger, limitConfig)
|
||||
},
|
||||
NewRequestLoggerMiddlewareWrapper,
|
||||
middleware.NewJWTAuthMiddleware,
|
||||
@@ -637,6 +679,18 @@ func NewContainer() *Container {
|
||||
api_repo.NewGormApiCallRepository,
|
||||
fx.As(new(domain_api_repo.ApiCallRepository)),
|
||||
),
|
||||
fx.Annotate(
|
||||
api_repo.NewGormReportRepository,
|
||||
fx.As(new(domain_api_repo.ReportRepository)),
|
||||
),
|
||||
),
|
||||
|
||||
// 下属账号仓储
|
||||
fx.Provide(
|
||||
fx.Annotate(
|
||||
subordinate_db.NewGormSubordinateRepository,
|
||||
fx.As(new(domain_subordinate_repo.SubordinateRepository)),
|
||||
),
|
||||
),
|
||||
|
||||
// 统计域仓储层
|
||||
@@ -661,7 +715,10 @@ func NewContainer() *Container {
|
||||
user_service.NewUserAggregateService,
|
||||
),
|
||||
user_service.NewUserAuthService,
|
||||
user_service.NewSMSCodeService,
|
||||
fx.Annotate(
|
||||
user_service.NewSMSCodeService,
|
||||
fx.ParamTags(``, ``, ``, `name:"captchaService"`),
|
||||
),
|
||||
user_service.NewContractAggregateService,
|
||||
product_service.NewProductManagementService,
|
||||
product_service.NewProductSubscriptionService,
|
||||
@@ -743,7 +800,8 @@ func NewContainer() *Container {
|
||||
api_services.NewApiUserAggregateService,
|
||||
),
|
||||
api_services.NewApiCallAggregateService,
|
||||
api_services.NewApiRequestService,
|
||||
// 使用带仓储注入的构造函数,支持企业报告记录持久化
|
||||
api_services.NewApiRequestServiceWithRepos,
|
||||
api_services.NewFormConfigService,
|
||||
),
|
||||
|
||||
@@ -768,6 +826,7 @@ func NewContainer() *Container {
|
||||
subscriptionService *product_services.ProductSubscriptionService,
|
||||
exportManager *export.ExportManager,
|
||||
balanceAlertService finance_services.BalanceAlertService,
|
||||
subordinateRepo domain_subordinate_repo.SubordinateRepository,
|
||||
) api_app.ApiApplicationService {
|
||||
return api_app.NewApiApplicationService(
|
||||
apiCallService,
|
||||
@@ -786,6 +845,7 @@ func NewContainer() *Container {
|
||||
subscriptionService,
|
||||
exportManager,
|
||||
balanceAlertService,
|
||||
subordinateRepo,
|
||||
)
|
||||
},
|
||||
fx.As(new(api_app.ApiApplicationService)),
|
||||
@@ -858,6 +918,21 @@ func NewContainer() *Container {
|
||||
user.NewUserApplicationService,
|
||||
fx.As(new(user.UserApplicationService)),
|
||||
),
|
||||
// 下属:账号类型供 JWT / 资料
|
||||
fx.Annotate(
|
||||
subordinate_infra.NewAccountKindProviderImpl,
|
||||
fx.As(new(interfaces.AccountKindProvider)),
|
||||
),
|
||||
// 下属:禁止子账号自助订
|
||||
fx.Annotate(
|
||||
subordinate_app.NewBlockSelfSubscribeForSubordinate,
|
||||
fx.As(new(product.SelfSubscribePolicy)),
|
||||
),
|
||||
// 下属:邀请/划款/代配
|
||||
fx.Annotate(
|
||||
subordinate_app.NewSubordinateApplicationService,
|
||||
fx.As(new(subordinate_app.SubordinateApplicationService)),
|
||||
),
|
||||
// 认证应用服务 - 绑定到接口
|
||||
fx.Annotate(
|
||||
func(
|
||||
@@ -874,8 +949,11 @@ func NewContainer() *Container {
|
||||
apiUserAggregateService api_services.ApiUserAggregateService,
|
||||
enterpriseInfoSubmitRecordService *certification_service.EnterpriseInfoSubmitRecordService,
|
||||
ocrService sharedOCR.OCRService,
|
||||
subordinateRepo domain_subordinate_repo.SubordinateRepository,
|
||||
walletRepo domain_finance_repo.WalletRepository,
|
||||
txManager *shared_database.TransactionManager,
|
||||
logger *zap.Logger,
|
||||
cfg *config.Config,
|
||||
) certification.CertificationApplicationService {
|
||||
return certification.NewCertificationApplicationService(
|
||||
aggregateService,
|
||||
@@ -891,8 +969,11 @@ func NewContainer() *Container {
|
||||
apiUserAggregateService,
|
||||
enterpriseInfoSubmitRecordService,
|
||||
ocrService,
|
||||
subordinateRepo,
|
||||
walletRepo,
|
||||
txManager,
|
||||
logger,
|
||||
cfg,
|
||||
)
|
||||
},
|
||||
fx.As(new(certification.CertificationApplicationService)),
|
||||
@@ -955,6 +1036,7 @@ func NewContainer() *Container {
|
||||
documentationAppService product.DocumentationApplicationServiceInterface,
|
||||
formConfigService api_services.FormConfigService,
|
||||
logger *zap.Logger,
|
||||
exportManager *export.ExportManager,
|
||||
) product.ProductApplicationService {
|
||||
return product.NewProductApplicationService(
|
||||
productManagementService,
|
||||
@@ -963,6 +1045,7 @@ func NewContainer() *Container {
|
||||
documentationAppService,
|
||||
formConfigService,
|
||||
logger,
|
||||
exportManager,
|
||||
)
|
||||
},
|
||||
fx.As(new(product.ProductApplicationService)),
|
||||
@@ -1140,36 +1223,33 @@ func NewContainer() *Container {
|
||||
return pdf.NewPDFGenerator(logger)
|
||||
},
|
||||
),
|
||||
// PDF缓存管理器
|
||||
// PDF缓存管理器(用于PDFG)
|
||||
fx.Provide(
|
||||
func(logger *zap.Logger) (*pdf.PDFCacheManager, error) {
|
||||
// 使用默认配置:缓存目录在临时目录,TTL为24小时,最大缓存大小为500MB
|
||||
cacheDir := "" // 使用默认目录(临时目录下的tyapi_pdf_cache)
|
||||
ttl := 24 * time.Hour
|
||||
maxSize := int64(500 * 1024 * 1024) // 500MB
|
||||
func(cfg *config.Config, logger *zap.Logger) (*pdf.PDFCacheManager, error) {
|
||||
cacheDir := cfg.PDFGen.Cache.CacheDir
|
||||
ttl := cfg.PDFGen.Cache.TTL
|
||||
if ttl == 0 {
|
||||
ttl = 24 * time.Hour
|
||||
}
|
||||
|
||||
// 可以通过环境变量覆盖
|
||||
if envCacheDir := os.Getenv("PDF_CACHE_DIR"); envCacheDir != "" {
|
||||
// 环境变量可以覆盖配置
|
||||
if envCacheDir := os.Getenv("PDFG_CACHE_DIR"); envCacheDir != "" {
|
||||
cacheDir = envCacheDir
|
||||
}
|
||||
if envTTL := os.Getenv("PDF_CACHE_TTL"); envTTL != "" {
|
||||
if envTTL := os.Getenv("PDFG_CACHE_TTL"); envTTL != "" {
|
||||
if parsedTTL, err := time.ParseDuration(envTTL); err == nil {
|
||||
ttl = parsedTTL
|
||||
}
|
||||
}
|
||||
if envMaxSize := os.Getenv("PDF_CACHE_MAX_SIZE"); envMaxSize != "" {
|
||||
if parsedMaxSize, err := strconv.ParseInt(envMaxSize, 10, 64); err == nil {
|
||||
maxSize = parsedMaxSize
|
||||
}
|
||||
}
|
||||
|
||||
maxSize := cfg.PDFGen.Cache.MaxSize
|
||||
cacheManager, err := pdf.NewPDFCacheManager(logger, cacheDir, ttl, maxSize)
|
||||
if err != nil {
|
||||
logger.Warn("PDF缓存管理器初始化失败,将禁用缓存功能", zap.Error(err))
|
||||
return nil, nil // 返回nil,handler中会检查
|
||||
logger.Warn("PDFG缓存管理器初始化失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logger.Info("PDF缓存管理器已初始化",
|
||||
logger.Info("PDFG缓存管理器已初始化",
|
||||
zap.String("cache_dir", cacheDir),
|
||||
zap.Duration("ttl", ttl),
|
||||
zap.Int64("max_size", maxSize),
|
||||
@@ -1178,6 +1258,18 @@ func NewContainer() *Container {
|
||||
return cacheManager, nil
|
||||
},
|
||||
),
|
||||
// 企业全景报告 PDF 异步预生成(依赖 PDF 缓存目录与公网可访问基址)
|
||||
// 同时以 processors.QYGLReportPDFScheduler 注入 ApiRequestService
|
||||
fx.Provide(
|
||||
fx.Annotate(
|
||||
func(cfg *config.Config, logger *zap.Logger, cache *pdf.PDFCacheManager) *pdf.QYGLReportPDFPregen {
|
||||
base := config.ResolveAPIPublicBaseURL(&cfg.API)
|
||||
return pdf.NewQYGLReportPDFPregen(logger, cache, base)
|
||||
},
|
||||
fx.As(new(api_processors.QYGLReportPDFScheduler)),
|
||||
fx.As(fx.Self()), // 同时保留 *pdf.QYGLReportPDFPregen,供 QYGLReportHandler 等注入
|
||||
),
|
||||
),
|
||||
// 本地文件存储服务
|
||||
fx.Provide(
|
||||
func(logger *zap.Logger) *storage.LocalFileStorageService {
|
||||
@@ -1200,6 +1292,7 @@ func NewContainer() *Container {
|
||||
fx.Provide(
|
||||
// 用户HTTP处理器
|
||||
handlers.NewUserHandler,
|
||||
handlers.NewSubordinateHandler,
|
||||
// 认证HTTP处理器
|
||||
handlers.NewCertificationHandler,
|
||||
// 财务HTTP处理器
|
||||
@@ -1214,6 +1307,8 @@ func NewContainer() *Container {
|
||||
handlers.NewApiHandler,
|
||||
// 统计HTTP处理器
|
||||
handlers.NewStatisticsHandler,
|
||||
// 管理员安全HTTP处理器
|
||||
handlers.NewAdminSecurityHandler,
|
||||
// 文章HTTP处理器
|
||||
func(
|
||||
appService article.ArticleApplicationService,
|
||||
@@ -1232,6 +1327,8 @@ func NewContainer() *Container {
|
||||
) *handlers.AnnouncementHandler {
|
||||
return handlers.NewAnnouncementHandler(appService, responseBuilder, validator, logger)
|
||||
},
|
||||
// PDFG HTTP处理器
|
||||
handlers.NewPDFGHandler,
|
||||
// 组件报告处理器
|
||||
func(
|
||||
productRepo domain_product_repo.ProductRepository,
|
||||
@@ -1257,6 +1354,8 @@ func NewContainer() *Container {
|
||||
) *handlers.ComponentReportOrderHandler {
|
||||
return handlers.NewComponentReportOrderHandler(componentReportOrderService, purchaseOrderRepo, config, logger)
|
||||
},
|
||||
// 企业全景报告页面处理器
|
||||
handlers.NewQYGLReportHandler,
|
||||
// UI组件HTTP处理器
|
||||
func(
|
||||
uiComponentAppService product.UIComponentApplicationService,
|
||||
@@ -1266,12 +1365,20 @@ func NewContainer() *Container {
|
||||
) *handlers.UIComponentHandler {
|
||||
return handlers.NewUIComponentHandler(uiComponentAppService, responseBuilder, validator, logger)
|
||||
},
|
||||
// 验证码HTTP处理器
|
||||
fx.Annotate(
|
||||
handlers.NewCaptchaHandler,
|
||||
fx.ParamTags(`name:"captchaService"`, ``, ``, ``),
|
||||
),
|
||||
),
|
||||
|
||||
// 路由注册
|
||||
fx.Provide(
|
||||
// 用户路由
|
||||
routes.NewUserRoutes,
|
||||
routes.NewSubordinateRoutes,
|
||||
// 验证码路由
|
||||
routes.NewCaptchaRoutes,
|
||||
// 认证路由
|
||||
routes.NewCertificationRoutes,
|
||||
// 财务路由
|
||||
@@ -1294,6 +1401,12 @@ func NewContainer() *Container {
|
||||
routes.NewApiRoutes,
|
||||
// 统计路由
|
||||
routes.NewStatisticsRoutes,
|
||||
// 管理员安全路由
|
||||
routes.NewAdminSecurityRoutes,
|
||||
// PDFG路由
|
||||
routes.NewPDFGRoutes,
|
||||
// 企业报告页面路由
|
||||
routes.NewQYGLReportRoutes,
|
||||
),
|
||||
|
||||
// 应用生命周期
|
||||
@@ -1396,6 +1509,8 @@ func RegisterMiddlewares(
|
||||
func RegisterRoutes(
|
||||
router *sharedhttp.GinRouter,
|
||||
userRoutes *routes.UserRoutes,
|
||||
subordinateRoutes *routes.SubordinateRoutes,
|
||||
captchaRoutes *routes.CaptchaRoutes,
|
||||
certificationRoutes *routes.CertificationRoutes,
|
||||
financeRoutes *routes.FinanceRoutes,
|
||||
productRoutes *routes.ProductRoutes,
|
||||
@@ -1407,6 +1522,9 @@ func RegisterRoutes(
|
||||
announcementRoutes *routes.AnnouncementRoutes,
|
||||
apiRoutes *routes.ApiRoutes,
|
||||
statisticsRoutes *routes.StatisticsRoutes,
|
||||
adminSecurityRoutes *routes.AdminSecurityRoutes,
|
||||
pdfgRoutes *routes.PDFGRoutes,
|
||||
qyglReportRoutes *routes.QYGLReportRoutes,
|
||||
jwtAuth *middleware.JWTAuthMiddleware,
|
||||
adminAuth *middleware.AdminAuthMiddleware,
|
||||
cfg *config.Config,
|
||||
@@ -1419,6 +1537,8 @@ func RegisterRoutes(
|
||||
|
||||
// 所有域名路由路由
|
||||
userRoutes.Register(router)
|
||||
subordinateRoutes.Register(router)
|
||||
captchaRoutes.Register(router)
|
||||
certificationRoutes.Register(router)
|
||||
financeRoutes.Register(router)
|
||||
productRoutes.Register(router)
|
||||
@@ -1430,6 +1550,9 @@ func RegisterRoutes(
|
||||
articleRoutes.Register(router)
|
||||
announcementRoutes.Register(router)
|
||||
statisticsRoutes.Register(router)
|
||||
adminSecurityRoutes.Register(router)
|
||||
pdfgRoutes.Register(router)
|
||||
qyglReportRoutes.Register(router)
|
||||
|
||||
// 打印注册的路由信息
|
||||
router.PrintRoutes()
|
||||
|
||||
@@ -72,6 +72,11 @@ type IVYZ81NCReq struct {
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
}
|
||||
type IVYZ2MN6Req struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
||||
}
|
||||
type IVYZ9363Req struct {
|
||||
ManName string `json:"man_name" validate:"required,min=1,validName"`
|
||||
ManIDCard string `json:"man_id_card" validate:"required,validIDCard"`
|
||||
@@ -100,11 +105,96 @@ type JRZQDCBEReq struct {
|
||||
BankCard string `json:"bank_card" validate:"required,validBankCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
}
|
||||
type JRZQACABReq struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
BankCard string `json:"bank_card" validate:"required,validBankCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
}
|
||||
|
||||
// shujubao
|
||||
type QYGL2ACDReq struct {
|
||||
EntName string `json:"ent_name" validate:"required,min=1,validEnterpriseName"`
|
||||
LegalPerson string `json:"legal_person" validate:"required,min=1,validName"`
|
||||
EntCode string `json:"ent_code" validate:"required,validUSCI"`
|
||||
}
|
||||
type QYGLUY3SReq struct {
|
||||
EntName string `json:"ent_name" validate:"omitempty,min=1,validEnterpriseName"`
|
||||
EntRegno string `json:"ent_reg_no" validate:"omitempty"`
|
||||
EntCode string `json:"ent_code" validate:"omitempty,validUSCI"`
|
||||
}
|
||||
type QYGLJ1U9Req struct {
|
||||
EntName string `json:"ent_name" validate:"required,min=1,validEnterpriseName"`
|
||||
EntCode string `json:"ent_code" validate:"required,validUSCI"`
|
||||
}
|
||||
type JRZQOCRYReq struct {
|
||||
PhotoData string `json:"photo_data" validate:"required,validBase64Image"`
|
||||
}
|
||||
type JRZQOCREReq struct {
|
||||
PhotoData string `json:"photo_data" validate:"omitempty,validBase64Image"`
|
||||
ImageUrl string `json:"image_url" validate:"omitempty,url"`
|
||||
}
|
||||
|
||||
type YYSYK9R4Req struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
}
|
||||
|
||||
type YYSY35TAReq struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
|
||||
type YYSYTJC4Req struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
|
||||
type YYSYT7C4Req struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
type QCXG1S2LReq struct {
|
||||
VinCode string `json:"vin_code" validate:"required"`
|
||||
}
|
||||
type YYSYXF7JReq struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
type QCXGA8V3Req struct {
|
||||
VinCode string `json:"vin_code" validate:"required"`
|
||||
}
|
||||
|
||||
type YYSYP72DReq struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
type QCXG9F5CReq struct {
|
||||
PlateNo string `json:"plate_no" validate:"required"`
|
||||
}
|
||||
type QCXG3M7ZReq struct {
|
||||
PlateNo string `json:"plate_no" validate:"required"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
PlateColor string `json:"plate_color" validate:"omitempty"`
|
||||
}
|
||||
type QCXG3B8ZReq struct {
|
||||
PlateNo string `json:"plate_no" validate:"required"`
|
||||
}
|
||||
type QCXGM7R9Req struct {
|
||||
PlateNo string `json:"plate_no" validate:"required"`
|
||||
}
|
||||
type QCXGP1W3Req struct {
|
||||
PlateNo string `json:"plate_no" validate:"required"`
|
||||
}
|
||||
type QCXG5U0ZReq struct {
|
||||
VinCode string `json:"vin_code" validate:"required"`
|
||||
}
|
||||
type QCXGU2K4Req struct {
|
||||
PlateNo string `json:"plate_no" validate:"required"`
|
||||
}
|
||||
type QCXGY7F2Req struct {
|
||||
VinCode string `json:"vin_code" validate:"required"`
|
||||
VehicleName string `json:"vehicle_name" validate:"omitempty"`
|
||||
VehicleLocation string `json:"vehicle_location" validate:"required"`
|
||||
FirstRegistrationdate string `json:"first_registrationdate" validate:"required"`
|
||||
Color string `json:"color" validate:"omitempty"`
|
||||
}
|
||||
type QYGL6F2DReq struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
}
|
||||
@@ -176,6 +266,13 @@ type IVYZZQT3Req struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
PhotoData string `json:"photo_data" validate:"required,validBase64Image"`
|
||||
}
|
||||
type IVYZZQ3BReq struct {
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
PhotoData string `json:"photo_data" validate:"omitempty,validBase64Image"`
|
||||
ImageUrl string `json:"image_url" validate:"omitempty,url"`
|
||||
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
||||
}
|
||||
|
||||
type IVYZSFELReq struct {
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
@@ -265,9 +362,8 @@ type COMB86PMReq struct {
|
||||
}
|
||||
|
||||
type QCXG7A2BReq struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
PlateNo string `json:"plate_no" validate:"required"`
|
||||
}
|
||||
|
||||
type QCXG4896Req struct {
|
||||
PlateNo string `json:"plate_no" validate:"required"`
|
||||
AuthDate string `json:"auth_date" validate:"required,validAuthDate" encrypt:"false"`
|
||||
@@ -282,6 +378,11 @@ type QCXGGB2QReq struct {
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
CarPlateType string `json:"carplate_type" validate:"required"`
|
||||
}
|
||||
|
||||
type QCXGM4CLReq struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
}
|
||||
|
||||
type QCXGJJ2AReq struct {
|
||||
VinCode string `json:"vin_code" validate:"required"`
|
||||
EngineNumber string `json:"engine_number" validate:"omitempty"`
|
||||
@@ -346,7 +447,7 @@ type QCXGP00WReq struct {
|
||||
VinCode string `json:"vin_code" validate:"required"`
|
||||
PlateNo string `json:"plate_no" validate:"omitempty"`
|
||||
ReturnURL string `json:"return_url" validate:"required,validReturnURL"`
|
||||
VlPhotoData string `json:"vlphoto_data" validate:"omitempty,validBase64Image"`
|
||||
VlPhotoData string `json:"vlphoto_data" validate:"required,validBase64Image"`
|
||||
}
|
||||
|
||||
type QCXG4D2EReq struct {
|
||||
@@ -359,7 +460,7 @@ type COMENT01Req struct {
|
||||
}
|
||||
|
||||
type JRZQ09J8Req struct {
|
||||
MobileNo string `json:"mobile_no" validate:"omitempty,min=11,max=11,validMobileNo"`
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
||||
@@ -412,6 +513,30 @@ type IVYZ2A8BReq struct {
|
||||
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
||||
}
|
||||
|
||||
type QYGL4YABReq struct {
|
||||
EntName string `json:"ent_name" validate:"required,min=1,validEnterpriseName"`
|
||||
LegalPerson string `json:"legal_person" validate:"required,min=1,validName"`
|
||||
EntCode string `json:"ent_code" validate:"required,validUSCI"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
}
|
||||
|
||||
type QYGL3YSBReq struct {
|
||||
EntName string `json:"ent_name" validate:"required,min=1,validEnterpriseName"`
|
||||
LegalPerson string `json:"legal_person" validate:"required,min=1,validName"`
|
||||
EntCode string `json:"ent_code" validate:"required,validUSCI"`
|
||||
}
|
||||
|
||||
type QYGL2YSBReq struct {
|
||||
LegalPerson string `json:"legal_person" validate:"required,min=1,validName"`
|
||||
EntCode string `json:"ent_code" validate:"required,validUSCI"`
|
||||
}
|
||||
|
||||
type QYGLDG77Req struct {
|
||||
EntName string `json:"ent_name" validate:"required,min=1,validEnterpriseName"`
|
||||
AccountNo string `json:"account_no" validate:"required,min=1"`
|
||||
AccountBank string `json:"account_bank" validate:"required,min=1"`
|
||||
}
|
||||
|
||||
type IVYZ7C9DReq struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
@@ -430,6 +555,12 @@ type IVYZ7F3AReq struct {
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
||||
}
|
||||
type IVYZRAX1Req struct {
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
||||
}
|
||||
|
||||
type IVYZ3P9MReq struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
@@ -447,6 +578,18 @@ type IVYZ9K2LReq struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
PhotoData string `json:"photo_data" validate:"required,validBase64Image"`
|
||||
}
|
||||
type IVYZ4Y27Req struct {
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
AuthAuthorizeFileBase64 string `json:"auth_authorize_file_base64" validate:"required,validBase64PDF"`
|
||||
}
|
||||
|
||||
type FLXGHB4FReq struct {
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
AuthAuthorizeFileBase64 string `json:"auth_authorize_file_base64" validate:"required,validBase64PDF"`
|
||||
}
|
||||
|
||||
type IVYZP2Q6Req struct {
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
@@ -485,6 +628,11 @@ type QYGL5A3CReq struct {
|
||||
PageNum int64 `json:"page_num" validate:"omitempty,min=1"`
|
||||
}
|
||||
|
||||
type QYGLBH7YReq struct {
|
||||
EntName string `json:"ent_name" validate:"required,min=1,validEnterpriseName"`
|
||||
AuthAuthorizeFileBase64 string `json:"auth_authorize_file_base64" validate:"required,validBase64PDF"`
|
||||
}
|
||||
|
||||
type QYGL2naoReq struct {
|
||||
EntCode string `json:"ent_code" validate:"required,validUSCI"`
|
||||
PageSize int64 `json:"page_size" validate:"omitempty,min=1,max=100"`
|
||||
@@ -540,10 +688,67 @@ type YYSY6F2BReq struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
|
||||
type IVYZOCR1Req struct {
|
||||
PhotoData string `json:"photo_data" validate:"omitempty,validBase64Image"`
|
||||
ImageUrl string `json:"image_url" validate:"omitempty,url"`
|
||||
}
|
||||
type YYSY8B1CReq struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
|
||||
type QYGLJ0Q1Req struct {
|
||||
EntName string `json:"ent_name" validate:"omitempty,min=1,validEnterpriseName"`
|
||||
EntCode string `json:"ent_code" validate:"omitempty,validUSCI"`
|
||||
}
|
||||
|
||||
type IVYZ18HYReq struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
MaritalType string `json:"marital_type" validate:"required" oneof=10 20 30 40`
|
||||
AuthAuthorizeFileBase64 string `json:"auth_authorize_file_base64" validate:"required,validBase64"`
|
||||
AuthAuthorizeFileCode string `json:"auth_authorize_file_code" validate:"required"`
|
||||
AuthDate string `json:"auth_date" validate:"omitempty"`
|
||||
}
|
||||
|
||||
type IVYZ38SRReq struct {
|
||||
ManName string `json:"man_name" validate:"required,min=1,validName"`
|
||||
ManIDCard string `json:"man_id_card" validate:"required,validIDCard"`
|
||||
WomanName string `json:"woman_name" validate:"required,min=1,validName"`
|
||||
WomanIDCard string `json:"woman_id_card" validate:"required,validIDCard"`
|
||||
}
|
||||
|
||||
type IVYZ5E22Req struct {
|
||||
ManName string `json:"man_name" validate:"required,min=1,validName"`
|
||||
ManIDCard string `json:"man_id_card" validate:"required,validIDCard"`
|
||||
WomanName string `json:"woman_name" validate:"required,min=1,validName"`
|
||||
WomanIDCard string `json:"woman_id_card" validate:"required,validIDCard"`
|
||||
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
||||
}
|
||||
type IVYZ48SRReq struct {
|
||||
ManName string `json:"man_name" validate:"required,min=1,validName"`
|
||||
ManIDCard string `json:"man_id_card" validate:"required,validIDCard"`
|
||||
WomanName string `json:"woman_name" validate:"required,min=1,validName"`
|
||||
WomanIDCard string `json:"woman_id_card" validate:"required,validIDCard"`
|
||||
MaritalType string `json:"marital_type" validate:"required" oneof=10 20 30 40`
|
||||
AuthAuthorizeFileCode string `json:"auth_authorize_file_code" validate:"required"`
|
||||
}
|
||||
type IVYZ28HYReq struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
}
|
||||
type FLXGDJG3Req struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
}
|
||||
type QYGLDJ12Req struct {
|
||||
EntName string `json:"ent_name" validate:"omitempty,min=1,validEnterpriseName"`
|
||||
EntCode string `json:"ent_code" validate:"omitempty,validUSCI"`
|
||||
EntRegNo string `json:"ent_reg_no" validate:"omitempty"`
|
||||
}
|
||||
type QYGLDJ33Req struct {
|
||||
EntName string `json:"ent_name" validate:"omitempty,min=1,validEnterpriseName"`
|
||||
EntCode string `json:"ent_code" validate:"omitempty,validUSCI"`
|
||||
EntRegNo string `json:"ent_reg_no" validate:"omitempty"`
|
||||
}
|
||||
type YYSY6D9AReq struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
@@ -567,6 +772,13 @@ type FLXG9C1DReq struct {
|
||||
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
||||
}
|
||||
|
||||
type DWBG5SAMReq struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
AuthorizationURL string `json:"authorization_url" validate:"required,authorization_url"`
|
||||
}
|
||||
|
||||
// 法院被执行人限高版
|
||||
type FLXG3A9BReq struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
@@ -709,6 +921,24 @@ type QCXG9P1CReq struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
}
|
||||
|
||||
type DWBG3BF9Req struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
|
||||
type DWBG9FB3Req struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
|
||||
type DWBG9FB2Req struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
|
||||
type QCXG8A3DReq struct {
|
||||
PlateNo string `json:"plate_no" validate:"required"`
|
||||
PlateType string `json:"plate_type" validate:"omitempty,oneof=01 02"`
|
||||
@@ -829,6 +1059,10 @@ type QYGL5F6AReq struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
}
|
||||
|
||||
type QYGLVR76Req struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
}
|
||||
|
||||
type IVYZ6G7HReq struct {
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
@@ -853,3 +1087,152 @@ type IVYZ9H2MReq struct {
|
||||
type YYSY9E4AReq struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
|
||||
// YYSY运营商相关API DTO
|
||||
type YYSY3M8SReq struct {
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
|
||||
type YYSYC4R9Req struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
|
||||
type YYSYH6D2Req struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
|
||||
type YYSYH6F3Req struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
type YYSYP0T4Req struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
|
||||
type YYSYE7V5Req struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
|
||||
type YYSYS9W1Req struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
|
||||
type YYSYK8R3Req struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
|
||||
type YYSYF2T7Req struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
DateRange string `json:"date_range" validate:"required,validDateRange"`
|
||||
}
|
||||
|
||||
type QYGL5S1IReq struct {
|
||||
EntCode string `json:"ent_code" validate:"required,validUSCI"`
|
||||
EntName string `json:"ent_name" validate:"required,min=1,validEnterpriseName"`
|
||||
}
|
||||
|
||||
// 数脉 API
|
||||
type IVYZ3M8SReq struct {
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
|
||||
type IVYZ9K7FReq struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
}
|
||||
|
||||
type IVYZS1MPReq struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
|
||||
type IVYZA1B3Req struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
PhotoData string `json:"photo_data" validate:"required,validBase64Image"`
|
||||
}
|
||||
|
||||
type IVYZFIC1Req struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
PhotoData string `json:"photo_data" validate:"omitempty,validBase64Image"`
|
||||
ImageUrl string `json:"+" validate:"omitempty,url"`
|
||||
}
|
||||
|
||||
type IVYZC4R9Req struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
|
||||
type IVYZP0T4Req struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
|
||||
type IVYZX5QZReq struct {
|
||||
ReturnURL string `json:"return_url" validate:"required,validReturnURL"`
|
||||
}
|
||||
|
||||
type IVYZX5Q2Req struct {
|
||||
Token string `json:"token" validate:"required"`
|
||||
}
|
||||
|
||||
type JRZQ1P5GReq struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
AuthAuthorizeFileCode string `json:"auth_authorize_file_code" validate:"required"`
|
||||
}
|
||||
|
||||
type JRZQV87MReq struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
|
||||
type JRZQV0MDReq struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
|
||||
type JRZQVT43Req struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
|
||||
type JRZQV3HMReq struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
|
||||
type JRZQV4TFReq struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
|
||||
type JRZQV5F4Req struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
|
||||
type JRZQV7MDReq struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
|
||||
type IVYZVJJ6Req struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
|
||||
10
internal/domains/api/dto/pdfg_dto.go
Normal file
10
internal/domains/api/dto/pdfg_dto.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package dto
|
||||
|
||||
// PDFG01GZReq PDFG01GZ 请求参数
|
||||
type PDFG01GZReq struct {
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
Authorized string `json:"authorized" validate:"required,oneof=0 1"` // 授权标识,0或1
|
||||
}
|
||||
|
||||
@@ -71,9 +71,6 @@ func NewApiCall(accessId, requestParams, clientIp string) (*ApiCall, error) {
|
||||
if accessId == "" {
|
||||
return nil, errors.New("AccessId不能为空")
|
||||
}
|
||||
if requestParams == "" {
|
||||
return nil, errors.New("请求参数不能为空")
|
||||
}
|
||||
if clientIp == "" {
|
||||
return nil, errors.New("ClientIp不能为空")
|
||||
}
|
||||
@@ -92,11 +89,11 @@ func NewApiCall(accessId, requestParams, clientIp string) (*ApiCall, error) {
|
||||
// MarkSuccess 标记为成功
|
||||
func (a *ApiCall) MarkSuccess(cost decimal.Decimal) error {
|
||||
// 校验除ErrorMsg和ErrorType外所有字段不能为空
|
||||
if a.ID == "" || a.AccessId == "" || a.TransactionId == "" || a.RequestParams == "" || a.Status == "" || a.StartAt.IsZero() {
|
||||
if a.ID == "" || a.AccessId == "" || a.TransactionId == "" || a.Status == "" || a.StartAt.IsZero() {
|
||||
return errors.New("ApiCall字段不能为空(除ErrorMsg和ErrorType)")
|
||||
}
|
||||
// 可选字段也要有值
|
||||
if a.UserId == nil || a.ProductId == nil {
|
||||
if a.UserId == nil || a.ProductId == nil {
|
||||
return errors.New("ApiCall标记成功时UserId、ProductId不能为空")
|
||||
}
|
||||
a.Status = ApiCallStatusSuccess
|
||||
@@ -132,9 +129,6 @@ func (a *ApiCall) Validate() error {
|
||||
if a.TransactionId == "" {
|
||||
return errors.New("TransactionId不能为空")
|
||||
}
|
||||
if a.RequestParams == "" {
|
||||
return errors.New("请求参数不能为空")
|
||||
}
|
||||
if a.Status != ApiCallStatusPending && a.Status != ApiCallStatusSuccess && a.Status != ApiCallStatusFailed {
|
||||
return errors.New("无效的调用状态")
|
||||
}
|
||||
|
||||
35
internal/domains/api/entities/enterprise_report.go
Normal file
35
internal/domains/api/entities/enterprise_report.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package entities
|
||||
|
||||
import "time"
|
||||
|
||||
// Report 报告记录实体
|
||||
// 用于持久化存储各类报告(企业报告等),通过编号和类型区分
|
||||
type Report struct {
|
||||
// 报告编号,直接使用业务生成的 reportId 作为主键
|
||||
ReportID string `gorm:"primaryKey;type:varchar(64)" json:"report_id"`
|
||||
|
||||
// 报告类型,例如 enterprise(企业报告)、personal 等
|
||||
Type string `gorm:"type:varchar(32);not null;index" json:"type"`
|
||||
|
||||
// 调用来源API编码,例如 QYGLJ1U9
|
||||
ApiCode string `gorm:"type:varchar(32);not null;index" json:"api_code"`
|
||||
|
||||
// 企业名称和统一社会信用代码,便于后续检索
|
||||
EntName string `gorm:"type:varchar(255);index" json:"ent_name"`
|
||||
EntCode string `gorm:"type:varchar(64);index" json:"ent_code"`
|
||||
|
||||
// 原始请求参数(JSON字符串),用于审计和排错
|
||||
RequestParams string `gorm:"type:text" json:"request_params"`
|
||||
|
||||
// 报告完整JSON内容
|
||||
ReportData string `gorm:"type:text" json:"report_data"`
|
||||
|
||||
// 创建时间
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||
}
|
||||
|
||||
// TableName 指定数据库表名
|
||||
func (Report) TableName() string {
|
||||
return "reports"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"tyapi-server/internal/domains/api/entities"
|
||||
)
|
||||
|
||||
// ReportRepository 报告记录仓储接口
|
||||
type ReportRepository interface {
|
||||
// Create 创建报告记录
|
||||
Create(ctx context.Context, report *entities.Report) error
|
||||
|
||||
// FindByReportID 根据报告编号查询记录
|
||||
FindByReportID(ctx context.Context, reportID string) (*entities.Report, error)
|
||||
}
|
||||
|
||||
@@ -6,20 +6,28 @@ import (
|
||||
"fmt"
|
||||
|
||||
"tyapi-server/internal/application/api/commands"
|
||||
appconfig "tyapi-server/internal/config"
|
||||
api_repositories "tyapi-server/internal/domains/api/repositories"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/domains/api/services/processors/comb"
|
||||
"tyapi-server/internal/domains/api/services/processors/dwbg"
|
||||
"tyapi-server/internal/domains/api/services/processors/flxg"
|
||||
"tyapi-server/internal/domains/api/services/processors/ivyz"
|
||||
"tyapi-server/internal/domains/api/services/processors/jrzq"
|
||||
"tyapi-server/internal/domains/api/services/processors/pdfg"
|
||||
"tyapi-server/internal/domains/api/services/processors/qcxg"
|
||||
"tyapi-server/internal/domains/api/services/processors/qygl"
|
||||
"tyapi-server/internal/domains/api/services/processors/test"
|
||||
"tyapi-server/internal/domains/api/services/processors/yysy"
|
||||
"tyapi-server/internal/domains/product/services"
|
||||
"tyapi-server/internal/infrastructure/external/alicloud"
|
||||
"tyapi-server/internal/infrastructure/external/haiyuapi"
|
||||
"tyapi-server/internal/infrastructure/external/huibo"
|
||||
"tyapi-server/internal/infrastructure/external/jiguang"
|
||||
"tyapi-server/internal/infrastructure/external/muzi"
|
||||
"tyapi-server/internal/infrastructure/external/nuoer"
|
||||
"tyapi-server/internal/infrastructure/external/shujubao"
|
||||
"tyapi-server/internal/infrastructure/external/shumai"
|
||||
"tyapi-server/internal/infrastructure/external/tianyancha"
|
||||
"tyapi-server/internal/infrastructure/external/westdex"
|
||||
"tyapi-server/internal/infrastructure/external/xingwei"
|
||||
@@ -45,10 +53,14 @@ type ApiRequestService struct {
|
||||
validator interfaces.RequestValidator
|
||||
processorDeps *processors.ProcessorDependencies
|
||||
combService *comb.CombService
|
||||
config *appconfig.Config
|
||||
|
||||
reportRepo api_repositories.ReportRepository
|
||||
}
|
||||
|
||||
func NewApiRequestService(
|
||||
westDexService *westdex.WestDexService,
|
||||
shujubaoService *shujubao.ShujubaoService,
|
||||
muziService *muzi.MuziService,
|
||||
yushanService *yushan.YushanService,
|
||||
tianYanChaService *tianyancha.TianYanChaService,
|
||||
@@ -56,14 +68,86 @@ func NewApiRequestService(
|
||||
zhichaService *zhicha.ZhichaService,
|
||||
xingweiService *xingwei.XingweiService,
|
||||
jiguangService *jiguang.JiguangService,
|
||||
shumaiService *shumai.ShumaiService,
|
||||
huiboService *huibo.HuiboService,
|
||||
nuoerService *nuoer.NuoerService,
|
||||
haiyuapiService *haiyuapi.HaiyuapiService,
|
||||
validator interfaces.RequestValidator,
|
||||
productManagementService *services.ProductManagementService,
|
||||
cfg *appconfig.Config,
|
||||
) *ApiRequestService {
|
||||
return NewApiRequestServiceWithRepos(
|
||||
westDexService,
|
||||
shujubaoService,
|
||||
muziService,
|
||||
yushanService,
|
||||
tianYanChaService,
|
||||
alicloudService,
|
||||
zhichaService,
|
||||
xingweiService,
|
||||
jiguangService,
|
||||
shumaiService,
|
||||
huiboService,
|
||||
nuoerService,
|
||||
haiyuapiService,
|
||||
validator,
|
||||
productManagementService,
|
||||
cfg,
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
}
|
||||
|
||||
// NewApiRequestServiceWithRepos 带自定义仓储的构造函数,便于扩展(例如企业报告记录)
|
||||
func NewApiRequestServiceWithRepos(
|
||||
westDexService *westdex.WestDexService,
|
||||
shujubaoService *shujubao.ShujubaoService,
|
||||
muziService *muzi.MuziService,
|
||||
yushanService *yushan.YushanService,
|
||||
tianYanChaService *tianyancha.TianYanChaService,
|
||||
alicloudService *alicloud.AlicloudService,
|
||||
zhichaService *zhicha.ZhichaService,
|
||||
xingweiService *xingwei.XingweiService,
|
||||
jiguangService *jiguang.JiguangService,
|
||||
shumaiService *shumai.ShumaiService,
|
||||
huiboService *huibo.HuiboService,
|
||||
nuoerService *nuoer.NuoerService,
|
||||
haiyuapiService *haiyuapi.HaiyuapiService,
|
||||
validator interfaces.RequestValidator,
|
||||
productManagementService *services.ProductManagementService,
|
||||
cfg *appconfig.Config,
|
||||
reportRepo api_repositories.ReportRepository,
|
||||
qyglReportPDFScheduler processors.QYGLReportPDFScheduler,
|
||||
) *ApiRequestService {
|
||||
// 创建组合包服务
|
||||
combService := comb.NewCombService(productManagementService)
|
||||
|
||||
apiPublicBase := ""
|
||||
if cfg != nil {
|
||||
apiPublicBase = appconfig.ResolveAPIPublicBaseURL(&cfg.API)
|
||||
}
|
||||
|
||||
// 创建处理器依赖容器
|
||||
processorDeps := processors.NewProcessorDependencies(westDexService, muziService, yushanService, tianYanChaService, alicloudService, zhichaService, xingweiService, jiguangService, validator, combService)
|
||||
processorDeps := processors.NewProcessorDependencies(
|
||||
westDexService,
|
||||
shujubaoService,
|
||||
muziService,
|
||||
yushanService,
|
||||
tianYanChaService,
|
||||
alicloudService,
|
||||
zhichaService,
|
||||
xingweiService,
|
||||
jiguangService,
|
||||
shumaiService,
|
||||
huiboService,
|
||||
nuoerService,
|
||||
haiyuapiService,
|
||||
validator,
|
||||
combService,
|
||||
reportRepo,
|
||||
qyglReportPDFScheduler,
|
||||
apiPublicBase,
|
||||
)
|
||||
|
||||
// 统一注册所有处理器
|
||||
registerAllProcessors(combService)
|
||||
@@ -77,6 +161,8 @@ func NewApiRequestService(
|
||||
validator: validator,
|
||||
processorDeps: processorDeps,
|
||||
combService: combService,
|
||||
config: cfg,
|
||||
reportRepo: reportRepo,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,11 +195,15 @@ func registerAllProcessors(combService *comb.CombService) {
|
||||
"FLXG7E8F": flxg.ProcessFLXG7E8FRequest,
|
||||
"FLXG3A9B": flxg.ProcessFLXG3A9BRequest,
|
||||
"FLXGK5D2": flxg.ProcessFLXGK5D2Request,
|
||||
"FLXGDJG3": flxg.ProcessFLXGDJG3Request, //董监高司法综合信息核验
|
||||
"FLXGHB4F": flxg.ProcessFLXGHB4FRequest, //个人涉诉案件查询(海宇API)
|
||||
|
||||
// JRZQ系列处理器
|
||||
"JRZQ8203": jrzq.ProcessJRZQ8203Request,
|
||||
"JRZQ0A03": jrzq.ProcessJRZQ0A03Request,
|
||||
"JRZQ4AA8": jrzq.ProcessJRZQ4AA8Request,
|
||||
"JRZQDCBE": jrzq.ProcessJRZQDCBERequest,
|
||||
"JRZQACAB": jrzq.ProcessJRZQACABERequest, // 银行卡四要素
|
||||
"JRZQ09J8": jrzq.ProcessJRZQ09J8Request,
|
||||
"JRZQ1D09": jrzq.ProcessJRZQ1D09Request,
|
||||
"JRZQ3C7B": jrzq.ProcessJRZQ3C7BRequest,
|
||||
@@ -135,9 +225,19 @@ func registerAllProcessors(combService *comb.CombService) {
|
||||
"JRZQ1W4X": jrzq.ProcessJRZQ1W4XRequest,
|
||||
"JRZQ3P01": jrzq.ProcessJRZQ3P01Request,
|
||||
"JRZQ3AG6": jrzq.ProcessJRZQ3AG6Request,
|
||||
"JRZQO6L7": jrzq.ProcessJRZQO6L7Request, // 全国自然人经济特征评分模型v3 简版
|
||||
"JRZQO7L1": jrzq.ProcessJRZQO7L1Request, // 全国自然人经济特征评分模型v4 详版
|
||||
"JRZQS7G0": jrzq.ProcessJRZQS7G0Request, // 社保综合评分V1
|
||||
"JRZQO6L7": jrzq.ProcessJRZQO6L7Request, // 全国自然人经济特征评分模型v3 简版
|
||||
"JRZQO7L1": jrzq.ProcessJRZQO7L1Request, // 全国自然人经济特征评分模型v4 详版
|
||||
"JRZQS7G0": jrzq.ProcessJRZQS7G0Request, // 社保综合评分V1
|
||||
"JRZQ1P5G": jrzq.ProcessJRZQ1P5GRequest, // 全国自然人借贷压力指数查询(2)
|
||||
"JRZQOCRE": jrzq.ProcessJRZQOCREERequest, // 银行卡OCR数卖
|
||||
"JRZQOCRY": jrzq.ProcessJRZQOCRYERequest, // 银行卡OCR数据宝
|
||||
"JRZQV87M": jrzq.ProcessJRZQV87MRequest, // 金融黑名单V110_c10
|
||||
"JRZQV0MD": jrzq.ProcessJRZQV0MDRequest, // 行为黑名单
|
||||
"JRZQVT43": jrzq.ProcessJRZQVT43Request, // 投诉黑名单
|
||||
"JRZQV3HM": jrzq.ProcessJRZQV3HMRequest, // 债务逾期黑名单
|
||||
"JRZQV4TF": jrzq.ProcessJRZQV4TFRequest, // 智瞳分尊享版
|
||||
"JRZQV5F4": jrzq.ProcessJRZQV5F4Request, // 风险变量V5F4
|
||||
"JRZQV7MD": jrzq.ProcessJRZQV7MDRequest, // 特殊名单
|
||||
|
||||
// QYGL系列处理器
|
||||
"QYGL8261": qygl.ProcessQYGL8261Request,
|
||||
@@ -166,26 +266,54 @@ func registerAllProcessors(combService *comb.CombService) {
|
||||
"QYGL2NAO": qygl.ProcessQYGL2naoRequest, //股权变更
|
||||
"QYGLNIO8": qygl.ProcessQYGLNIO8Request, //企业基本信息
|
||||
"QYGLP0HT": qygl.ProcessQYGLP0HTRequest, //股权穿透
|
||||
"QYGL5S1I": qygl.ProcessQYGL5S1IRequest, //企业司法涉诉I
|
||||
"QYGLJ1U9": qygl.ProcessQYGLJ1U9Request, //企业全景报告(聚合 QYGLUY3S/QYGLJ0Q1/QYGL5S1I)
|
||||
"QYGLJ0Q1": qygl.ProcessQYGLJ0Q1Request, //企业股权结构全景查询
|
||||
"QYGLUY3S": qygl.ProcessQYGLUY3SRequest, //企业经营状态全景查询
|
||||
"QYGLDJ12": qygl.ProcessQYGLDJ12Request, //企业年报信息核验
|
||||
"QYGL8848": qygl.ProcessQYGL8848Request, //企业税收违法核查
|
||||
"QYGLDJ33": qygl.ProcessQYGLDJ33Request, //企业年报信息核验
|
||||
"QYGL4YAB": qygl.ProcessQYGL4YABRequest, //企业四要素认证shumai
|
||||
"QYGL3YSB": qygl.ProcessQYGL3YSBRequest, //企业三要素认证shumai
|
||||
"QYGL2YSB": qygl.ProcessQYGL2YSBRequest, //企业二要素认证shumai
|
||||
"QYGLDG77": qygl.ProcessQYGLDG77Request, //企业对公打款认证shumai
|
||||
"QYGLBH7Y": qygl.ProcessQYGLBH7YRequest, //企业涉诉案件查询海宇
|
||||
|
||||
// YYSY系列处理器
|
||||
"YYSYD50F": yysy.ProcessYYSYD50FRequest,
|
||||
"YYSY09CD": yysy.ProcessYYSY09CDRequest,
|
||||
"YYSY4B21": yysy.ProcessYYSY4B21Request,
|
||||
"YYSY4B37": yysy.ProcessYYSY4B37Request,
|
||||
"YYSY6F2E": yysy.ProcessYYSY6F2ERequest,
|
||||
"YYSYBE08": yysy.ProcessYYSYBE08Request,
|
||||
"YYSYF7DB": yysy.ProcessYYSYF7DBRequest,
|
||||
"YYSY4F2E": yysy.ProcessYYSY4F2ERequest,
|
||||
"YYSY8B1C": yysy.ProcessYYSY8B1CRequest,
|
||||
"YYSY6D9A": yysy.ProcessYYSY6D9ARequest,
|
||||
"YYSY3E7F": yysy.ProcessYYSY3E7FRequest,
|
||||
"YYSY8F3A": yysy.ProcessYYSY8F3ARequest,
|
||||
"YYSY9A1B": yysy.ProcessYYSY9A1BRequest,
|
||||
"YYSY8C2D": yysy.ProcessYYSY8C2DRequest,
|
||||
"YYSY7D3E": yysy.ProcessYYSY7D3ERequest,
|
||||
"YYSY9E4A": yysy.ProcessYYSY9E4ARequest,
|
||||
"YYSY9F1B": yysy.ProcessYYSY9F1BYequest,
|
||||
"YYSY6F2B": yysy.ProcessYYSY6F2BRequest,
|
||||
"YYSY35TA": yysy.ProcessYYSY35TARequest, //运营商归属地数卖
|
||||
"YYSYD50F": yysy.ProcessYYSYD50FRequest,
|
||||
"YYSY09CD": yysy.ProcessYYSY09CDRequest,
|
||||
"YYSY4B21": yysy.ProcessYYSY4B21Request,
|
||||
"YYSY4B37": yysy.ProcessYYSY4B37Request,
|
||||
"YYSY6F2E": yysy.ProcessYYSY6F2ERequest,
|
||||
"YYSYBE08": yysy.ProcessYYSYBE08Request,
|
||||
"YYSYBE08TEST": yysy.ProcessYYSYBE08testRequest, // 二要素(阿里云市场),与 YYSYBE08 入参一致
|
||||
"YYSYF7DB": yysy.ProcessYYSYF7DBRequest,
|
||||
"YYSY4F2E": yysy.ProcessYYSY4F2ERequest,
|
||||
"YYSY8B1C": yysy.ProcessYYSY8B1CRequest,
|
||||
"YYSY6D9A": yysy.ProcessYYSY6D9ARequest,
|
||||
"YYSY3E7F": yysy.ProcessYYSY3E7FRequest,
|
||||
"YYSY8F3A": yysy.ProcessYYSY8F3ARequest,
|
||||
"YYSY9A1B": yysy.ProcessYYSY9A1BRequest,
|
||||
"YYSY8C2D": yysy.ProcessYYSY8C2DRequest,
|
||||
"YYSY7D3E": yysy.ProcessYYSY7D3ERequest,
|
||||
"YYSY9E4A": yysy.ProcessYYSY9E4ARequest,
|
||||
"YYSY9F1B": yysy.ProcessYYSY9F1BYequest,
|
||||
"YYSY6F2B": yysy.ProcessYYSY6F2BRequest,
|
||||
"YYSY3M8S": yysy.ProcessYYSY3M8SRequest, //运营商二要素查询
|
||||
"YYSYC4R9": yysy.ProcessYYSYC4R9Request, //运营商三要素详版查询
|
||||
"YYSYH6D2": yysy.ProcessYYSYH6D2Request, //运营商三要素简版查询
|
||||
"YYSYP0T4": yysy.ProcessYYSYP0T4Request, //在网时长查询
|
||||
"YYSYE7V5": yysy.ProcessYYSYE7V5Request, //手机在网状态查询
|
||||
"YYSYS9W1": yysy.ProcessYYSYS9W1Request, //手机携号转网查询
|
||||
"YYSYK8R3": yysy.ProcessYYSYK8R3Request, //手机空号检测查询
|
||||
"YYSYH6F3": yysy.ProcessYYSYH6F3Request, //运营商三要素即时版查询
|
||||
"YYSYK9R4": yysy.ProcessYYSYK9R4Request, //全网手机三要素验证1979周更新版
|
||||
"YYSYF2T7": yysy.ProcessYYSYF2T7Request, //手机二次放号检测查询
|
||||
"YYSYTJC4": yysy.ProcessYYSYTJC4Request, //运营商近三个月欠费次数脉
|
||||
"YYSYP72D": yysy.ProcessYYSYP72DRequest, //运营商近三个月平均账单
|
||||
"YYSYT7C4": yysy.ProcessYYSYT7C4Request, //手机停机验证
|
||||
"YYSYXF7J": yysy.ProcessYYSYXF7JRequest, //手机消费区间验证
|
||||
|
||||
// IVYZ系列处理器
|
||||
"IVYZ0B03": ivyz.ProcessIVYZ0B03Request,
|
||||
@@ -207,6 +335,7 @@ func registerAllProcessors(combService *comb.CombService) {
|
||||
"IVYZ3A7F": ivyz.ProcessIVYZ3A7FRequest,
|
||||
"IVYZ9D2E": ivyz.ProcessIVYZ9D2ERequest,
|
||||
"IVYZ81NC": ivyz.ProcessIVYZ81NCRequest,
|
||||
"IVYZ2MN6": ivyz.ProcessIVYZ2MN6Request,
|
||||
"IVYZ6G7H": ivyz.ProcessIVYZ6G7HRequest,
|
||||
"IVYZ8I9J": ivyz.ProcessIVYZ8I9JRequest,
|
||||
"IVYZ9K2L": ivyz.ProcessIVYZ9K2LRequest,
|
||||
@@ -217,10 +346,29 @@ func registerAllProcessors(combService *comb.CombService) {
|
||||
"IVYZ6M8P": ivyz.ProcessIVYZ6M8PRequest, //职业资格证书
|
||||
"IVYZ9H2M": ivyz.ProcessIVYZ9H2MRequest, //极光个人婚姻查询(V2版)
|
||||
"IVYZZQT3": ivyz.ProcessIVYZZQT3Request, //人脸比对V3
|
||||
"IVYZZQ3B": ivyz.ProcessIVYZZQ3BRequest, //人脸比对B(B:similarity + verification_result)
|
||||
"IVYZBPQ2": ivyz.ProcessIVYZBPQ2Request, //人脸比对V2
|
||||
"IVYZSFEL": ivyz.ProcessIVYZSFELRequest, //全国自然人人像三要素核验_V1
|
||||
"IVYZ0S0D": ivyz.ProcessIVYZ0S0DRequest, //劳动仲裁信息查询(个人版)
|
||||
"IVYZ1J7H": ivyz.ProcessIVYZ1J7HRequest, //行驶证核查v2
|
||||
"IVYZ9K7F": ivyz.ProcessIVYZ9K7FRequest, //身份证实名认证即时版
|
||||
"IVYZA1B3": ivyz.ProcessIVYZA1B3Request, //公安三要素人脸识别
|
||||
"IVYZFIC1": ivyz.ProcessIVYZFIC1Request, //人脸身份证比对(数脉)
|
||||
"IVYZN2P8": ivyz.ProcessIVYZN2P8Request, //身份证实名认证政务版
|
||||
"IVYZX5QZ": ivyz.ProcessIVYZX5QZRequest, //活体检测
|
||||
"IVYZX5Q2": ivyz.ProcessIVYZX5Q2Request, //活体识别步骤二
|
||||
"IVYZOCR1": ivyz.ProcessIVYZOCR1Request, //身份证OCR
|
||||
"IVYZOCR2": ivyz.ProcessIVYZOCR2Request, //身份证OCR2数卖
|
||||
"IVYZ18HY": ivyz.ProcessIVYZ18HYRequest, //婚姻状况核验V2(单人)
|
||||
"IVYZ28HY": ivyz.ProcessIVYZ28HYRequest, //婚姻状况核验(单人)
|
||||
"IVYZ38SR": ivyz.ProcessIVYZ38SRRequest, //婚姻状态核验(双人)
|
||||
"IVYZ48SR": ivyz.ProcessIVYZ48SRRequest, //婚姻状态核验V2(双人)
|
||||
"IVYZ5E22": ivyz.ProcessIVYZ5E22Request, //双人婚姻评估查询zhicha版本
|
||||
"IVYZRAX1": ivyz.ProcessIVYZRAX1Request, //融安信用分
|
||||
"IVYZRAX2": ivyz.ProcessIVYZRAX2Request, //融御反欺诈分
|
||||
"IVYZ2MN7": ivyz.ProcessIVYZ2MN7Request, //学历B
|
||||
"IVYZ4Y27": ivyz.ProcessIVYZ4Y27Request, //学历高级版
|
||||
"IVYZVJJ6": ivyz.ProcessIVYZVJJ6Request, //收入等级tax_income_level_v8
|
||||
|
||||
// COMB系列处理器 - 只注册有自定义逻辑的组合包
|
||||
"COMB86PM": comb.ProcessCOMB86PMRequest, // 有自定义逻辑:重命名ApiCode
|
||||
@@ -233,24 +381,39 @@ func registerAllProcessors(combService *comb.CombService) {
|
||||
"QCXG8A3D": qcxg.ProcessQCXG8A3DRequest,
|
||||
"QCXG6B4E": qcxg.ProcessQCXG6B4ERequest,
|
||||
"QCXG4896": qcxg.ProcessQCXG4896Request,
|
||||
"QCXG5F3A": qcxg.ProcessQCXG5F3ARequest, // 极光个人车辆查询
|
||||
"QCXG4D2E": qcxg.ProcessQCXG4D2ERequest, // 极光名下车辆数量查询
|
||||
"QCXGJJ2A": qcxg.ProcessQCXGJJ2ARequest, // vin码查车辆信息(一对多)
|
||||
"QCXGGJ3A": qcxg.ProcessQCXGGJ3ARequest, // 车辆vin码查询号牌
|
||||
"QCXGYTS2": qcxg.ProcessQCXGYTS2Request, // 车辆二要素核验v2
|
||||
"QCXGP00W": qcxg.ProcessQCXGP00WRequest, // 车辆出险详版查询
|
||||
"QCXGGB2Q": qcxg.ProcessQCXGGB2QRequest, // 车辆二要素核验V1
|
||||
"QCXG4I1Z": qcxg.ProcessQCXG4I1ZRequest, // 车辆过户详版查询
|
||||
"QCXG1H7Y": qcxg.ProcessQCXG1H7YRequest, // 车辆过户简版查询
|
||||
"QCXG3Z3L": qcxg.ProcessQCXG3Z3LRequest, // 车辆维保详细版查询
|
||||
"QCXG3Y6B": qcxg.ProcessQCXG3Y6BRequest, // 车辆维保简版查询
|
||||
"QCXG2T6S": qcxg.ProcessQCXG2T6SRequest, // 车辆里程记录(品牌查询)
|
||||
"QCXG1U4U": qcxg.ProcessQCXG1U4URequest, // 车辆里程记录(混合查询)
|
||||
|
||||
"QCXG5F3A": qcxg.ProcessQCXG5F3ARequest, // 极光个人车辆查询
|
||||
"QCXG4D2E": qcxg.ProcessQCXG4D2ERequest, // 极光名下车辆数量查询
|
||||
"QCXGJJ2A": qcxg.ProcessQCXGJJ2ARequest, // vin码查车辆信息(一对多)
|
||||
"QCXGGJ3A": qcxg.ProcessQCXGGJ3ARequest, // 车辆vin码查询号牌
|
||||
"QCXGYTS2": qcxg.ProcessQCXGYTS2Request, // 车辆二要素核验v2
|
||||
"QCXGP00W": qcxg.ProcessQCXGP00WRequest, // 车辆出险详版查询
|
||||
"QCXGGB2Q": qcxg.ProcessQCXGGB2QRequest, // 车辆二要素核验V1
|
||||
"QCXG4I1Z": qcxg.ProcessQCXG4I1ZRequest, // 车辆过户详版查询
|
||||
"QCXG1H7Y": qcxg.ProcessQCXG1H7YRequest, // 车辆过户简版查询
|
||||
"QCXG3Z3L": qcxg.ProcessQCXG3Z3LRequest, // 车辆维保详细版查询
|
||||
"QCXG3Y6B": qcxg.ProcessQCXG3Y6BRequest, // 车辆维保简版查询
|
||||
"QCXG2T6S": qcxg.ProcessQCXG2T6SRequest, // 车辆里程记录(品牌查询)
|
||||
"QCXG1U4U": qcxg.ProcessQCXG1U4URequest, //
|
||||
"QCXG9F5C": qcxg.ProcessQCXG9F5CERequest, //疑似营运车辆注册平台数 10386
|
||||
"QCXG3B8Z": qcxg.ProcessQCXG3B8ZRequest, //疑似运营车辆查询(月度里程)10268
|
||||
"QCXGP1W3": qcxg.ProcessQCXGP1W3Request, //疑似运营车辆查询(季度里程)10269
|
||||
"QCXGM7R9": qcxg.ProcessQCXGM7R9Request, //疑似运营车辆查询(半年度里程)10270
|
||||
"QCXGU2K4": qcxg.ProcessQCXGU2K4Request, //疑似运营车辆查询(年度里程)10271
|
||||
"QCXG5U0Z": qcxg.ProcessQCXG5U0ZRequest, // 车辆静态信息查询 10479
|
||||
"QCXGY7F2": qcxg.ProcessQCXGY7F2Request, // 二手车VIN估值 10443
|
||||
"QCXG3M7Z": qcxg.ProcessQCXG3M7ZRequest, //人车关系核验(ETC)10093 月更
|
||||
"QCXGM4CL": qcxg.ProcessQCXGM4CLRequest, //名下车辆诺尔
|
||||
"QYGLVR76": qygl.ProcessQYGLVR76Request, //名下企业诺尔
|
||||
"QCXG1S2L": qcxg.ProcessQCXG1S2LRequest, //车辆车五项信息核验V2
|
||||
"QCXGA8V3": qcxg.ProcessQCXGA8V3Request, //全国车辆配置查验(车五项+使用性质+承保)
|
||||
// DWBG系列处理器 - 多维报告
|
||||
"DWBG6A2C": dwbg.ProcessDWBG6A2CRequest,
|
||||
"DWBG8B4D": dwbg.ProcessDWBG8B4DRequest,
|
||||
"DWBG7F3A": dwbg.ProcessDWBG7F3ARequest,
|
||||
"DWBG5SAM": dwbg.ProcessDWBG5SAMRequest,
|
||||
"DWBG3BF9": dwbg.ProcessDWBG3BF9Request, // 个人风险档案max
|
||||
"DWBG9FB3": dwbg.ProcessDWBG9FB3Request, // 个人风险档案1
|
||||
"DWBG9FB2": dwbg.ProcessDWBG9FB2Request, // 个人风险档案2
|
||||
|
||||
// FLXG系列处理器 - 风险管控 (包含原FXHY功能)
|
||||
"FLXG8B4D": flxg.ProcessFLXG8B4DRequest,
|
||||
@@ -259,6 +422,9 @@ func registerAllProcessors(combService *comb.CombService) {
|
||||
"TEST001": test.ProcessTestRequest,
|
||||
"TEST002": test.ProcessTestErrorRequest,
|
||||
"TEST003": test.ProcessTestTimeoutRequest,
|
||||
|
||||
// PDFG系列处理器 - PDF生成
|
||||
"PDFG01GZ": pdfg.ProcessPDFG01GZRequest,
|
||||
}
|
||||
|
||||
// 批量注册到组合包服务
|
||||
@@ -280,6 +446,8 @@ func (a *ApiRequestService) PreprocessRequestApi(ctx context.Context, apiCode st
|
||||
|
||||
// 将apiCode放入context,供外部服务使用
|
||||
ctx = context.WithValue(ctx, "api_code", apiCode)
|
||||
// 将config放入context,供处理器使用
|
||||
ctx = context.WithValue(ctx, "config", a.config)
|
||||
|
||||
// 1. 优先查找已注册的自定义处理器
|
||||
if processor, exists := RequestProcessors[apiCode]; exists {
|
||||
|
||||
@@ -76,158 +76,237 @@ func (s *FormConfigServiceImpl) GetFormConfig(ctx context.Context, apiCode strin
|
||||
func (s *FormConfigServiceImpl) getDTOStruct(ctx context.Context, apiCode string) (interface{}, error) {
|
||||
// 建立API代码到DTO结构体的映射
|
||||
dtoMap := map[string]interface{}{
|
||||
"IVYZ9363": &dto.IVYZ9363Req{},
|
||||
"IVYZ385E": &dto.IVYZ385EReq{},
|
||||
"IVYZ5733": &dto.IVYZ5733Req{},
|
||||
"FLXG3D56": &dto.FLXG3D56Req{},
|
||||
"FLXG75FE": &dto.FLXG75FEReq{},
|
||||
"FLXG0V3B": &dto.FLXG0V3BReq{},
|
||||
"FLXG0V4B": &dto.FLXG0V4BReq{},
|
||||
"FLXG54F5": &dto.FLXG54F5Req{},
|
||||
"FLXG162A": &dto.FLXG162AReq{},
|
||||
"FLXG0687": &dto.FLXG0687Req{},
|
||||
"FLXGBC21": &dto.FLXGBC21Req{},
|
||||
"FLXG970F": &dto.FLXG970FReq{},
|
||||
"FLXG5876": &dto.FLXG5876Req{},
|
||||
"FLXG9687": &dto.FLXG9687Req{},
|
||||
"FLXGC9D1": &dto.FLXGC9D1Req{},
|
||||
"FLXGCA3D": &dto.FLXGCA3DReq{},
|
||||
"FLXGDEC7": &dto.FLXGDEC7Req{},
|
||||
"JRZQ0A03": &dto.JRZQ0A03Req{},
|
||||
"JRZQ4AA8": &dto.JRZQ4AA8Req{},
|
||||
"JRZQ8203": &dto.JRZQ8203Req{},
|
||||
"JRZQDCBE": &dto.JRZQDCBEReq{},
|
||||
"QYGL2ACD": &dto.QYGL2ACDReq{},
|
||||
"QYGL6F2D": &dto.QYGL6F2DReq{},
|
||||
"QYGL45BD": &dto.QYGL45BDReq{},
|
||||
"QYGL8261": &dto.QYGL8261Req{},
|
||||
"QYGL8271": &dto.QYGL8271Req{},
|
||||
"QYGLB4C0": &dto.QYGLB4C0Req{},
|
||||
"QYGL23T7": &dto.QYGL23T7Req{},
|
||||
"QYGL5A3C": &dto.QYGL5A3CReq{},
|
||||
"QYGL8B4D": &dto.QYGL8B4DReq{},
|
||||
"QYGL9E2F": &dto.QYGL9E2FReq{},
|
||||
"QYGL7C1A": &dto.QYGL7C1AReq{},
|
||||
"QYGL3F8E": &dto.QYGL3F8EReq{},
|
||||
"YYSY4B37": &dto.YYSY4B37Req{},
|
||||
"YYSY4B21": &dto.YYSY4B21Req{},
|
||||
"YYSY6F2E": &dto.YYSY6F2EReq{},
|
||||
"YYSY09CD": &dto.YYSY09CDReq{},
|
||||
"IVYZ0B03": &dto.IVYZ0B03Req{},
|
||||
"YYSYBE08": &dto.YYSYBE08Req{},
|
||||
"YYSYD50F": &dto.YYSYD50FReq{},
|
||||
"YYSYF7DB": &dto.YYSYF7DBReq{},
|
||||
"IVYZ9A2B": &dto.IVYZ9A2BReq{},
|
||||
"IVYZ7F2A": &dto.IVYZ7F2AReq{},
|
||||
"IVYZ4E8B": &dto.IVYZ4E8BReq{},
|
||||
"IVYZ1C9D": &dto.IVYZ1C9DReq{},
|
||||
"IVYZGZ08": &dto.IVYZGZ08Req{},
|
||||
"FLXG8A3F": &dto.FLXG8A3FReq{},
|
||||
"FLXG5B2E": &dto.FLXG5B2EReq{},
|
||||
"COMB298Y": &dto.COMB298YReq{},
|
||||
"COMB86PM": &dto.COMB86PMReq{},
|
||||
"QCXG7A2B": &dto.QCXG7A2BReq{},
|
||||
"COMENT01": &dto.COMENT01Req{},
|
||||
"JRZQ09J8": &dto.JRZQ09J8Req{},
|
||||
"FLXGDEA8": &dto.FLXGDEA8Req{},
|
||||
"FLXGDEA9": &dto.FLXGDEA9Req{},
|
||||
"JRZQ1D09": &dto.JRZQ1D09Req{},
|
||||
"IVYZ2A8B": &dto.IVYZ2A8BReq{},
|
||||
"IVYZ7C9D": &dto.IVYZ7C9DReq{},
|
||||
"IVYZ5E3F": &dto.IVYZ5E3FReq{},
|
||||
"YYSY4F2E": &dto.YYSY4F2EReq{},
|
||||
"YYSY8B1C": &dto.YYSY8B1CReq{},
|
||||
"YYSY6D9A": &dto.YYSY6D9AReq{},
|
||||
"YYSY3E7F": &dto.YYSY3E7FReq{},
|
||||
"FLXG5A3B": &dto.FLXG5A3BReq{},
|
||||
"FLXG9C1D": &dto.FLXG9C1DReq{},
|
||||
"FLXG2E8F": &dto.FLXG2E8FReq{},
|
||||
"JRZQ3C7B": &dto.JRZQ3C7BReq{},
|
||||
"JRZQ8A2D": &dto.JRZQ8A2DReq{},
|
||||
"JRZQ5E9F": &dto.JRZQ5E9FReq{},
|
||||
"JRZQ4B6C": &dto.JRZQ4B6CReq{},
|
||||
"JRZQ7F1A": &dto.JRZQ7F1AReq{},
|
||||
"DWBG6A2C": &dto.DWBG6A2CReq{},
|
||||
"DWBG8B4D": &dto.DWBG8B4DReq{},
|
||||
"FLXG8B4D": &dto.FLXG8B4DReq{},
|
||||
"IVYZ81NC": &dto.IVYZ81NCReq{},
|
||||
"IVYZ7F3A": &dto.IVYZ7F3AReq{},
|
||||
"IVYZ3P9M": &dto.IVYZ3P9MReq{},
|
||||
"IVYZ3A7F": &dto.IVYZ3A7FReq{},
|
||||
"IVYZ9D2E": &dto.IVYZ9D2EReq{},
|
||||
"IVYZ9K2L": &dto.IVYZ9K2LReq{},
|
||||
"DWBG7F3A": &dto.DWBG7F3AReq{},
|
||||
"YYSY8F3A": &dto.YYSY8F3AReq{},
|
||||
"QCXG9P1C": &dto.QCXG9P1CReq{},
|
||||
"JRZQ9E2A": &dto.JRZQ9E2AReq{},
|
||||
"YYSY9A1B": &dto.YYSY9A1BReq{},
|
||||
"YYSY8C2D": &dto.YYSY8C2DReq{},
|
||||
"YYSY7D3E": &dto.YYSY7D3EReq{},
|
||||
"YYSY9E4A": &dto.YYSY9E4AReq{},
|
||||
"JRZQ6F2A": &dto.JRZQ6F2AReq{},
|
||||
"JRZQ8B3C": &dto.JRZQ8B3CReq{},
|
||||
"JRZQ9D4E": &dto.JRZQ9D4EReq{},
|
||||
"FLXG7E8F": &dto.FLXG7E8FReq{},
|
||||
"QYGL5F6A": &dto.QYGL5F6AReq{},
|
||||
"IVYZ6G7H": &dto.IVYZ6G7HReq{},
|
||||
"IVYZ8I9J": &dto.IVYZ8I9JReq{},
|
||||
"JRZQ0L85": &dto.JRZQ0L85Req{},
|
||||
"COMBHZY2": &dto.COMBHZY2Req{}, // 自此无imp11.28
|
||||
"QCXG8A3D": &dto.QCXG8A3DReq{},
|
||||
"QCXG6B4E": &dto.QCXG6B4EReq{},
|
||||
"QYGL2B5C": &dto.QYGL2B5CReq{},
|
||||
"JRZQ2F8A": &dto.JRZQ2F8AReq{},
|
||||
"JRZQ1E7B": &dto.JRZQ1E7BReq{},
|
||||
"JRZQ3C9R": &dto.JRZQ3C9RReq{},
|
||||
"IVYZ2C1P": &dto.IVYZ2C1PReq{},
|
||||
"YYSY9F1B": &dto.YYSY9F1BReq{},
|
||||
"YYSY6F2B": &dto.YYSY6F2BReq{},
|
||||
"QYGL6S1B": &dto.QYGL6S1BReq{},
|
||||
"JRZQ0B6Y": &dto.JRZQ0B6YReq{},
|
||||
"JRZQ9A1W": &dto.JRZQ9A1WReq{},
|
||||
"JRZQ8F7C": &dto.JRZQ8F7CReq{}, //综合多头
|
||||
"FLXGK5D2": &dto.FLXGK5D2Req{},
|
||||
"FLXG3A9B": &dto.FLXG3A9BReq{},
|
||||
"IVYZP2Q6": &dto.IVYZP2Q6Req{},
|
||||
"JRZQ1W4X": &dto.JRZQ1W4XReq{}, //全景档案
|
||||
"QYGL2S0W": &dto.QYGL2S0WReq{}, //失信被执行企业个人查询
|
||||
"QYGL9T1Q": &dto.QYGL9T1QReq{}, //全国企业借贷意向验证查询_V1
|
||||
"QYGL5A9T": &dto.QYGL5A9TReq{}, //全国企业各类工商风险统计数量查询
|
||||
"JRZQ3P01": &dto.JRZQ3P01Req{}, //天远风控决策
|
||||
"JRZQ3AG6": &dto.JRZQ3AG6Req{}, //轻松查公积
|
||||
"IVYZ2B2T": &dto.IVYZ2B2TReq{}, //能力资质核验(学历)
|
||||
"IVYZ5A9O": &dto.IVYZ5A9OReq{}, //全国⾃然⼈⻛险评估评分模型
|
||||
"IVYZ6M8P": &dto.IVYZ6M8PReq{}, //职业资格证书
|
||||
"IVYZ9H2M": &dto.IVYZ9H2MReq{}, //极光个人婚姻查询(V2版)
|
||||
"QYGL5CMP": &dto.QYGL5CMPReq{}, //企业五要素验证
|
||||
"QCXG4896": &dto.QCXG4896Req{}, //网约车风险查询
|
||||
"IVYZZQT3": &dto.IVYZZQT3Req{}, //人脸比对V3
|
||||
"IVYZBPQ2": &dto.IVYZBPQ2Req{}, //人脸比对V2
|
||||
"IVYZSFEL": &dto.IVYZSFELReq{}, //全国自然人人像三要素核验_V1
|
||||
"QYGL66SL": &dto.QYGL66SLReq{}, //全国企业司法模型服务查询_V1
|
||||
"QCXG5F3A": &dto.QCXG5F3AReq{}, //极光个人车辆查询
|
||||
"QCXG4D2E": &dto.QCXG4D2EReq{}, //极光名下车辆数量查询
|
||||
"QYGLP0HT": &dto.QYGLP0HTReq{}, //股权穿透
|
||||
"QYGL2NAO": &dto.QYGL2naoReq{}, //股权变更
|
||||
"QYGLNIO8": &dto.QYGLNIO8Req{}, //企业基本信息
|
||||
"QYGL4B2E": &dto.QYGL5A3CReq{}, //税收违法
|
||||
"QYGL7D9A": &dto.QYGL5A3CReq{}, //欠税公告
|
||||
"IVYZ0S0D": &dto.IVYZ0S0DReq{}, //劳动仲裁信息查询(个人版)
|
||||
"IVYZ1J7H": &dto.IVYZ1J7HReq{}, //行驶证核查v2
|
||||
"QCXGJJ2A": &dto.QCXGJJ2AReq{}, //vin码查车辆信息(一对多)
|
||||
"QCXGGJ3A": &dto.QCXGGJ3AReq{}, //车辆vin码查询号牌
|
||||
"QCXGYTS2": &dto.QCXGYTS2Req{}, //车辆二要素核验v2
|
||||
"QCXGP00W": &dto.QCXGP00WReq{}, //车辆出险详版查询
|
||||
"QCXGGB2Q": &dto.QCXGGB2QReq{}, //车辆二要素核验V1
|
||||
"QCXG4I1Z": &dto.QCXG4I1ZReq{}, //车辆过户详版查询
|
||||
"QCXG1H7Y": &dto.QCXG1H7YReq{}, //车辆过户简版查询
|
||||
"QCXG3Z3L": &dto.QCXG3Z3LReq{}, //车辆维保详细版查询
|
||||
"QCXG3Y6B": &dto.QCXG1U4UReq{}, //车辆维保简版查询
|
||||
"QCXG2T6S": &dto.QCXG2T6SReq{}, //车辆里程记录(品牌查询)
|
||||
"QCXG1U4U": &dto.QCXG1U4UReq{}, //车辆里程记录(混合查询)
|
||||
"JRZQO6L7": &dto.JRZQO6L7Req{}, //全国自然人经济特征评分模型v3 简版
|
||||
"JRZQO7L1": &dto.JRZQO7L1Req{}, //全国自然人经济特征评分模型v4 详版
|
||||
"JRZQS7G0": &dto.JRZQS7G0Req{}, //社保综合评分V1
|
||||
"IVYZ9363": &dto.IVYZ9363Req{},
|
||||
"IVYZ385E": &dto.IVYZ385EReq{},
|
||||
"IVYZ5733": &dto.IVYZ5733Req{},
|
||||
"FLXG3D56": &dto.FLXG3D56Req{},
|
||||
"FLXG75FE": &dto.FLXG75FEReq{},
|
||||
"FLXG0V3B": &dto.FLXG0V3BReq{},
|
||||
"FLXG0V4B": &dto.FLXG0V4BReq{},
|
||||
"FLXG54F5": &dto.FLXG54F5Req{},
|
||||
"FLXG162A": &dto.FLXG162AReq{},
|
||||
"FLXG0687": &dto.FLXG0687Req{},
|
||||
"FLXGBC21": &dto.FLXGBC21Req{},
|
||||
"FLXG970F": &dto.FLXG970FReq{},
|
||||
"FLXG5876": &dto.FLXG5876Req{},
|
||||
"FLXG9687": &dto.FLXG9687Req{},
|
||||
"FLXGC9D1": &dto.FLXGC9D1Req{},
|
||||
"FLXGCA3D": &dto.FLXGCA3DReq{},
|
||||
"FLXGDEC7": &dto.FLXGDEC7Req{},
|
||||
"JRZQ0A03": &dto.JRZQ0A03Req{},
|
||||
"JRZQ4AA8": &dto.JRZQ4AA8Req{},
|
||||
"JRZQ8203": &dto.JRZQ8203Req{},
|
||||
"JRZQDCBE": &dto.JRZQDCBEReq{},
|
||||
"QYGL2ACD": &dto.QYGL2ACDReq{},
|
||||
"QYGL6F2D": &dto.QYGL6F2DReq{},
|
||||
"QYGL45BD": &dto.QYGL45BDReq{},
|
||||
"QYGL8261": &dto.QYGL8261Req{},
|
||||
"QYGL8271": &dto.QYGL8271Req{},
|
||||
"QYGLB4C0": &dto.QYGLB4C0Req{},
|
||||
"QYGL23T7": &dto.QYGL23T7Req{},
|
||||
"QYGL5A3C": &dto.QYGL5A3CReq{},
|
||||
"QYGL8B4D": &dto.QYGL8B4DReq{},
|
||||
"QYGL9E2F": &dto.QYGL9E2FReq{},
|
||||
"QYGL7C1A": &dto.QYGL7C1AReq{},
|
||||
"QYGL3F8E": &dto.QYGL3F8EReq{},
|
||||
"YYSY4B37": &dto.YYSY4B37Req{},
|
||||
"YYSY4B21": &dto.YYSY4B21Req{},
|
||||
"YYSY6F2E": &dto.YYSY6F2EReq{},
|
||||
"YYSY09CD": &dto.YYSY09CDReq{},
|
||||
"IVYZ0B03": &dto.IVYZ0B03Req{},
|
||||
"YYSYBE08": &dto.YYSYBE08Req{},
|
||||
"YYSYBE08TEST": &dto.YYSYBE08Req{},
|
||||
"YYSYD50F": &dto.YYSYD50FReq{},
|
||||
"YYSYF7DB": &dto.YYSYF7DBReq{},
|
||||
"IVYZ9A2B": &dto.IVYZ9A2BReq{},
|
||||
"IVYZ7F2A": &dto.IVYZ7F2AReq{},
|
||||
"IVYZ4E8B": &dto.IVYZ4E8BReq{},
|
||||
"IVYZ4Y27": &dto.IVYZ4Y27Req{}, //教育背景(详细)PDF授权书
|
||||
"IVYZ1C9D": &dto.IVYZ1C9DReq{},
|
||||
"IVYZGZ08": &dto.IVYZGZ08Req{},
|
||||
"FLXG8A3F": &dto.FLXG8A3FReq{},
|
||||
"FLXG5B2E": &dto.FLXG5B2EReq{},
|
||||
"COMB298Y": &dto.COMB298YReq{},
|
||||
"COMB86PM": &dto.COMB86PMReq{},
|
||||
"QCXG7A2B": &dto.QCXG7A2BReq{},
|
||||
"COMENT01": &dto.COMENT01Req{},
|
||||
"JRZQ09J8": &dto.JRZQ09J8Req{},
|
||||
"FLXGDEA8": &dto.FLXGDEA8Req{},
|
||||
"FLXGDEA9": &dto.FLXGDEA9Req{},
|
||||
"JRZQ1D09": &dto.JRZQ1D09Req{},
|
||||
"IVYZ2A8B": &dto.IVYZ2A8BReq{},
|
||||
"IVYZ7C9D": &dto.IVYZ7C9DReq{},
|
||||
"IVYZ5E3F": &dto.IVYZ5E3FReq{},
|
||||
"YYSY4F2E": &dto.YYSY4F2EReq{},
|
||||
"YYSY8B1C": &dto.YYSY8B1CReq{},
|
||||
"YYSY6D9A": &dto.YYSY6D9AReq{},
|
||||
"YYSY3E7F": &dto.YYSY3E7FReq{},
|
||||
"FLXG5A3B": &dto.FLXG5A3BReq{},
|
||||
"FLXG9C1D": &dto.FLXG9C1DReq{},
|
||||
"FLXG2E8F": &dto.FLXG2E8FReq{},
|
||||
"JRZQ3C7B": &dto.JRZQ3C7BReq{},
|
||||
"JRZQ8A2D": &dto.JRZQ8A2DReq{},
|
||||
"JRZQ5E9F": &dto.JRZQ5E9FReq{},
|
||||
"JRZQ4B6C": &dto.JRZQ4B6CReq{},
|
||||
"JRZQ7F1A": &dto.JRZQ7F1AReq{},
|
||||
"DWBG6A2C": &dto.DWBG6A2CReq{},
|
||||
"DWBG8B4D": &dto.DWBG8B4DReq{},
|
||||
"FLXG8B4D": &dto.FLXG8B4DReq{},
|
||||
"IVYZ81NC": &dto.IVYZ81NCReq{},
|
||||
"IVYZ2MN6": &dto.IVYZ2MN6Req{},
|
||||
"IVYZ7F3A": &dto.IVYZ7F3AReq{},
|
||||
"IVYZ3P9M": &dto.IVYZ3P9MReq{},
|
||||
"IVYZ3A7F": &dto.IVYZ3A7FReq{},
|
||||
"IVYZ9D2E": &dto.IVYZ9D2EReq{},
|
||||
"IVYZ9K2L": &dto.IVYZ9K2LReq{},
|
||||
"DWBG7F3A": &dto.DWBG7F3AReq{},
|
||||
"YYSY8F3A": &dto.YYSY8F3AReq{},
|
||||
"QCXG9P1C": &dto.QCXG9P1CReq{},
|
||||
"JRZQ9E2A": &dto.JRZQ9E2AReq{},
|
||||
"YYSY9A1B": &dto.YYSY9A1BReq{},
|
||||
"YYSY8C2D": &dto.YYSY8C2DReq{},
|
||||
"YYSY7D3E": &dto.YYSY7D3EReq{},
|
||||
"YYSY9E4A": &dto.YYSY9E4AReq{},
|
||||
"JRZQ6F2A": &dto.JRZQ6F2AReq{},
|
||||
"JRZQ8B3C": &dto.JRZQ8B3CReq{},
|
||||
"JRZQ9D4E": &dto.JRZQ9D4EReq{},
|
||||
"FLXG7E8F": &dto.FLXG7E8FReq{},
|
||||
"QYGL5F6A": &dto.QYGL5F6AReq{},
|
||||
"IVYZ6G7H": &dto.IVYZ6G7HReq{},
|
||||
"IVYZ8I9J": &dto.IVYZ8I9JReq{},
|
||||
"JRZQ0L85": &dto.JRZQ0L85Req{},
|
||||
"COMBHZY2": &dto.COMBHZY2Req{}, //
|
||||
"QCXG8A3D": &dto.QCXG8A3DReq{},
|
||||
"QCXG6B4E": &dto.QCXG6B4EReq{},
|
||||
"QYGL2B5C": &dto.QYGL2B5CReq{},
|
||||
"QYGLJ1U9": &dto.QYGLJ1U9Req{},
|
||||
"JRZQ2F8A": &dto.JRZQ2F8AReq{},
|
||||
"JRZQ1E7B": &dto.JRZQ1E7BReq{},
|
||||
"JRZQ3C9R": &dto.JRZQ3C9RReq{},
|
||||
"IVYZ2C1P": &dto.IVYZ2C1PReq{},
|
||||
"YYSY9F1B": &dto.YYSY9F1BReq{},
|
||||
"YYSY6F2B": &dto.YYSY6F2BReq{},
|
||||
"QYGL6S1B": &dto.QYGL6S1BReq{},
|
||||
"JRZQ0B6Y": &dto.JRZQ0B6YReq{},
|
||||
"JRZQ9A1W": &dto.JRZQ9A1WReq{},
|
||||
"JRZQ8F7C": &dto.JRZQ8F7CReq{}, //综合多头
|
||||
"FLXGK5D2": &dto.FLXGK5D2Req{},
|
||||
"FLXG3A9B": &dto.FLXG3A9BReq{},
|
||||
"IVYZP2Q6": &dto.IVYZP2Q6Req{},
|
||||
"JRZQ1W4X": &dto.JRZQ1W4XReq{}, //全景档案
|
||||
"QYGL2S0W": &dto.QYGL2S0WReq{}, //失信被执行企业个人查询
|
||||
"QYGL9T1Q": &dto.QYGL9T1QReq{}, //全国企业借贷意向验证查询_V1
|
||||
"QYGL5A9T": &dto.QYGL5A9TReq{}, //全国企业各类工商风险统计数量查询
|
||||
"JRZQ3P01": &dto.JRZQ3P01Req{}, //天远风控决策
|
||||
"JRZQ3AG6": &dto.JRZQ3AG6Req{}, //轻松查公积
|
||||
"IVYZ2B2T": &dto.IVYZ2B2TReq{}, //能力资质核验(学历)
|
||||
"IVYZ5A9O": &dto.IVYZ5A9OReq{}, //全国⾃然⼈⻛险评估评分模型
|
||||
"IVYZ6M8P": &dto.IVYZ6M8PReq{}, //职业资格证书
|
||||
"IVYZ9H2M": &dto.IVYZ9H2MReq{}, //极光个人婚姻查询(V2版)
|
||||
"QYGL5CMP": &dto.QYGL5CMPReq{}, //企业五要素验证
|
||||
"QCXG4896": &dto.QCXG4896Req{}, //网约车风险查询
|
||||
"IVYZZQT3": &dto.IVYZZQT3Req{}, //人脸比对V3
|
||||
"IVYZZQ3B": &dto.IVYZZQ3BReq{}, //人脸比对B(B:similarity + verification_result)
|
||||
"IVYZBPQ2": &dto.IVYZBPQ2Req{}, //人脸比对V2
|
||||
"IVYZSFEL": &dto.IVYZSFELReq{}, //全国自然人人像三要素核验_V1
|
||||
"QYGL66SL": &dto.QYGL66SLReq{}, //全国企业司法模型服务查询_V1
|
||||
"QCXG5F3A": &dto.QCXG5F3AReq{}, //极光个人车辆查询
|
||||
"QCXG4D2E": &dto.QCXG4D2EReq{}, //极光名下车辆数量查询
|
||||
"QYGLP0HT": &dto.QYGLP0HTReq{}, //股权穿透
|
||||
"QYGL2NAO": &dto.QYGL2naoReq{}, //股权变更
|
||||
"QYGLNIO8": &dto.QYGLNIO8Req{}, //企业基本信息
|
||||
"QYGL4B2E": &dto.QYGL5A3CReq{}, //税收违法
|
||||
"QYGL7D9A": &dto.QYGL5A3CReq{}, //欠税公告
|
||||
"IVYZ0S0D": &dto.IVYZ0S0DReq{}, //劳动仲裁信息查询(个人版)
|
||||
"IVYZ1J7H": &dto.IVYZ1J7HReq{}, //行驶证核查v2
|
||||
"QCXGJJ2A": &dto.QCXGJJ2AReq{}, //vin码查车辆信息(一对多)
|
||||
"QCXGGJ3A": &dto.QCXGGJ3AReq{}, //车辆vin码查询号牌
|
||||
"QCXGYTS2": &dto.QCXGYTS2Req{}, //车辆二要素核验v2
|
||||
"QCXGP00W": &dto.QCXGP00WReq{}, //车辆出险详版查询
|
||||
"QCXGGB2Q": &dto.QCXGGB2QReq{}, //车辆二要素核验V1
|
||||
"QCXG4I1Z": &dto.QCXG4I1ZReq{}, //车辆过户详版查询
|
||||
"QCXG1H7Y": &dto.QCXG1H7YReq{}, //车辆过户简版查询
|
||||
"QCXG3Z3L": &dto.QCXG3Z3LReq{}, //车辆维保详细版查询
|
||||
"QCXG3Y6B": &dto.QCXG1U4UReq{}, //车辆维保简版查询
|
||||
"QCXG2T6S": &dto.QCXG2T6SReq{}, //车辆里程记录(品牌查询)
|
||||
"QCXG1U4U": &dto.QCXG1U4UReq{}, //车辆里程记录(混合查询)
|
||||
"JRZQO6L7": &dto.JRZQO6L7Req{}, //全国自然人经济特征评分模型v3 简版
|
||||
"JRZQO7L1": &dto.JRZQO7L1Req{}, //全国自然人经济特征评分模型v4 详版
|
||||
"JRZQS7G0": &dto.JRZQS7G0Req{}, //社保综合评分V1
|
||||
"IVYZ9K7F": &dto.IVYZ9K7FReq{}, //身份证实名认证即时版
|
||||
"YYSY3M8S": &dto.YYSY3M8SReq{}, //运营商二要素查询
|
||||
"YYSYC4R9": &dto.YYSYC4R9Req{}, //运营商三要素详版查询
|
||||
"YYSYH6D2": &dto.YYSYH6D2Req{}, //运营商三要素简版政务版查询
|
||||
"YYSYP0T4": &dto.YYSYP0T4Req{}, //在网时长查询
|
||||
"YYSYE7V5": &dto.YYSYE7V5Req{}, //手机在网状态查询
|
||||
"YYSYS9W1": &dto.YYSYS9W1Req{}, //手机携号转网查询
|
||||
"YYSYK8R3": &dto.YYSYK8R3Req{}, //手机空号检测查询
|
||||
"YYSYF2T7": &dto.YYSYF2T7Req{}, //手机二次放号检测查询
|
||||
"IVYZA1B3": &dto.IVYZA1B3Req{}, //公安三要素人脸识别
|
||||
"IVYZFIC1": &dto.IVYZFIC1Req{}, //人脸身份证比对(数脉)
|
||||
"IVYZX5QZ": &dto.IVYZX5QZReq{}, //活体识别
|
||||
"IVYZN2P8": &dto.IVYZ9K7FReq{}, //身份证实名认证政务版
|
||||
"YYSYH6F3": &dto.YYSYH6F3Req{}, //运营商三要素简版即时版查询
|
||||
"IVYZX5Q2": &dto.IVYZX5Q2Req{}, //活体识别步骤二
|
||||
"PDFG01GZ": &dto.PDFG01GZReq{}, //
|
||||
"QYGL5S1I": &dto.QYGL5S1IReq{}, //企业司法涉诉V2
|
||||
"JRZQACAB": &dto.JRZQACABReq{}, //银行卡四要素
|
||||
"QCXG9F5C": &dto.QCXG9F5CReq{}, //疑似营运车辆注册平台数 10386
|
||||
"QCXG3B8Z": &dto.QCXG3B8ZReq{}, //疑似运营车辆查询(月度里程)10268
|
||||
"QCXGP1W3": &dto.QCXGP1W3Req{}, //疑似运营车辆查询(季度里程)10269
|
||||
"QCXGM7R9": &dto.QCXGM7R9Req{}, //疑似运营车辆查询(半年度里程)10270
|
||||
"QCXGU2K4": &dto.QCXGU2K4Req{}, //疑似运营车辆查询(年度里程)10271
|
||||
"QCXG5U0Z": &dto.QCXG5U0ZReq{}, //车辆静态信息查询 10479
|
||||
"QCXGY7F2": &dto.QCXGY7F2Req{}, //二手车VIN估值 10443
|
||||
"YYSYK9R4": &dto.YYSYK9R4Req{}, //全网手机三要素验证1979周更新版
|
||||
"QCXG3M7Z": &dto.QCXG3M7ZReq{}, //人车关系核验(ETC)10093 月更
|
||||
"JRZQ1P5G": &dto.JRZQ1P5GReq{}, //全国自然人借贷压力指数查询(2)
|
||||
"IVYZOCR1": &dto.IVYZOCR1Req{}, //身份证OCR
|
||||
"IVYZOCR2": &dto.IVYZOCR1Req{}, //身份证OCR2数卖
|
||||
"QYGLJ0Q1": &dto.QYGLJ0Q1Req{}, //企业股权结构全景查询
|
||||
"QYGLUY3S": &dto.QYGLUY3SReq{}, //企业全量信息核验V2 可用
|
||||
"JRZQOCRE": &dto.JRZQOCREReq{}, //银行卡OCR数卖
|
||||
"JRZQOCRY": &dto.JRZQOCRYReq{}, //银行卡OCR数据宝
|
||||
"YYSY35TA": &dto.YYSY35TAReq{}, //运营商归属地数卖
|
||||
"QYGLDJ12": &dto.QYGLDJ12Req{}, //企业年报信息核验
|
||||
"FLXGDJG3": &dto.FLXGDJG3Req{}, //董监高司法综合信息核验
|
||||
"QYGL8848": &dto.QYGLDJ12Req{}, //企业税收违法核查
|
||||
"IVYZ18HY": &dto.IVYZ18HYReq{}, //婚姻状况核验V2(单人)
|
||||
"IVYZ28HY": &dto.IVYZ28HYReq{}, //婚姻状况核验(单人)
|
||||
"IVYZ38SR": &dto.IVYZ38SRReq{}, //婚姻状态核验(双人)
|
||||
"IVYZ48SR": &dto.IVYZ48SRReq{}, //婚姻状态核验V2(双人)
|
||||
"IVYZ5E22": &dto.IVYZ5E22Req{}, //双人婚姻评估查询zhicha版本
|
||||
"DWBG5SAM": &dto.DWBG5SAMReq{}, //天远指迷报告
|
||||
"QYGLDJ33": &dto.QYGLDJ33Req{}, //企业年报信息核验
|
||||
"IVYZRAX1": &dto.IVYZRAX1Req{}, //融安信用分
|
||||
"IVYZRAX2": &dto.IVYZRAX1Req{}, //融御反欺诈
|
||||
"IVYZ2MN7": &dto.IVYZ2MN6Req{}, //学历Bzhicha
|
||||
"FLXGHB4F": &dto.FLXGHB4FReq{}, //个人涉诉案件查询(海宇API)
|
||||
"QYGLBH7Y": &dto.QYGLBH7YReq{}, //企业涉诉案件查询(海宇API)
|
||||
"QYGL4YAB": &dto.QYGL4YABReq{}, //企业四要素认证shumai
|
||||
"QYGL3YSB": &dto.QYGL3YSBReq{}, //企业三要素认证shumai
|
||||
"QYGL2YSB": &dto.QYGL2YSBReq{}, //企业二要素认证shumai
|
||||
"QYGLDG77": &dto.QYGLDG77Req{}, //企业对公打款认证shumai
|
||||
"QCXGM4CL": &dto.QCXGM4CLReq{}, //名下车辆诺尔
|
||||
"QYGLVR76": &dto.QYGLVR76Req{}, //名下企业诺尔
|
||||
"JRZQV87M": &dto.JRZQV87MReq{}, //金融黑名单V110_c10
|
||||
"JRZQV0MD": &dto.JRZQV0MDReq{}, //行为黑名单
|
||||
"JRZQVT43": &dto.JRZQVT43Req{}, //投诉黑名单
|
||||
"JRZQV3HM": &dto.JRZQV3HMReq{}, //债务逾期黑名单
|
||||
"JRZQV4TF": &dto.JRZQV4TFReq{}, //智瞳分尊享版
|
||||
"JRZQV5F4": &dto.JRZQV5F4Req{}, //风险变量V5F4
|
||||
"JRZQV7MD": &dto.JRZQV7MDReq{}, //特殊名单
|
||||
"IVYZVJJ6": &dto.IVYZVJJ6Req{}, //收入等级tax_income_level_v8
|
||||
"YYSYTJC4": &dto.YYSYTJC4Req{}, //运营商近三个月欠费次数脉
|
||||
"YYSYP72D": &dto.YYSYP72DReq{}, //运营商近三个月欠费次数脉
|
||||
"DWBG3BF9": &dto.DWBG3BF9Req{}, // 个人风险档案max
|
||||
"DWBG9FB3": &dto.DWBG9FB3Req{}, // 个人风险档案1
|
||||
"DWBG9FB2": &dto.DWBG9FB2Req{}, // 个人风险档案2
|
||||
"YYSYT7C4": &dto.YYSYT7C4Req{}, //手机停机验证
|
||||
"YYSYXF7J": &dto.YYSYXF7JReq{}, //手机消费区间验证
|
||||
"QCXG1S2L": &dto.QCXG1S2LReq{}, //车辆车五项信息核验V2
|
||||
"QCXGA8V3": &dto.QCXGA8V3Req{}, //全国车辆配置查验(车五项+使用性质+承保)
|
||||
|
||||
}
|
||||
|
||||
// 优先返回已配置的DTO
|
||||
@@ -335,6 +414,8 @@ func (s *FormConfigServiceImpl) parseValidationRules(validateTag string) string
|
||||
frontendRules = append(frontendRules, "日期格式")
|
||||
case rule == "validAuthDate":
|
||||
frontendRules = append(frontendRules, "授权日期格式")
|
||||
case rule == "validDateRange":
|
||||
frontendRules = append(frontendRules, "日期范围格式(YYYYMMDD-YYYYMMDD)")
|
||||
case rule == "validTimeRange":
|
||||
frontendRules = append(frontendRules, "时间范围格式")
|
||||
case rule == "validMobileType":
|
||||
@@ -347,6 +428,10 @@ func (s *FormConfigServiceImpl) parseValidationRules(validateTag string) string
|
||||
frontendRules = append(frontendRules, "授权链接格式")
|
||||
case rule == "validBase64Image":
|
||||
frontendRules = append(frontendRules, "Base64图片格式(JPG、BMP、PNG)")
|
||||
case rule == "base64" || rule == "validBase64":
|
||||
frontendRules = append(frontendRules, "Base64编码格式(支持图片/PDF)")
|
||||
case rule == "validBase64PDF":
|
||||
frontendRules = append(frontendRules, "PDF文件的Base64编码(仅PDF,最大500KB)")
|
||||
case strings.HasPrefix(rule, "oneof="):
|
||||
values := strings.TrimPrefix(rule, "oneof=")
|
||||
frontendRules = append(frontendRules, "可选值: "+values)
|
||||
@@ -372,13 +457,15 @@ func (s *FormConfigServiceImpl) getFieldType(fieldType reflect.Type, validation
|
||||
return "text" // time_range是HH:MM-HH:MM格式,使用文本输入
|
||||
} else if strings.Contains(validation, "授权日期格式") {
|
||||
return "text" // auth_date是YYYYMMDD-YYYYMMDD格式,使用文本输入
|
||||
} else if strings.Contains(validation, "日期范围格式") {
|
||||
return "text" // date_range 为 YYYYMMDD-YYYYMMDD,使用文本输入便于直接输入
|
||||
} else if strings.Contains(validation, "日期") {
|
||||
return "date"
|
||||
} else if strings.Contains(validation, "链接") {
|
||||
return "url"
|
||||
} else if strings.Contains(validation, "可选值") {
|
||||
return "select"
|
||||
} else if strings.Contains(validation, "Base64图片") || strings.Contains(validation, "base64") {
|
||||
} else if strings.Contains(validation, "Base64图片") || strings.Contains(validation, "Base64编码") || strings.Contains(validation, "base64") {
|
||||
return "textarea"
|
||||
} else if strings.Contains(validation, "图片地址") {
|
||||
return "url"
|
||||
@@ -397,50 +484,63 @@ func (s *FormConfigServiceImpl) getFieldType(fieldType reflect.Type, validation
|
||||
func (s *FormConfigServiceImpl) generateFieldLabel(jsonTag string) string {
|
||||
// 将下划线命名转换为中文标签
|
||||
labelMap := map[string]string{
|
||||
"mobile_no": "手机号码",
|
||||
"id_card": "身份证号",
|
||||
"name": "姓名",
|
||||
"man_name": "男方姓名",
|
||||
"woman_name": "女方姓名",
|
||||
"man_id_card": "男方身份证",
|
||||
"woman_id_card": "女方身份证",
|
||||
"ent_name": "企业名称",
|
||||
"legal_person": "法人姓名",
|
||||
"ent_code": "企业代码",
|
||||
"auth_date": "授权日期",
|
||||
"time_range": "时间范围",
|
||||
"authorized": "是否授权",
|
||||
"authorization_url": "授权链接",
|
||||
"unique_id": "唯一标识",
|
||||
"return_url": "返回链接",
|
||||
"mobile_type": "手机类型",
|
||||
"start_date": "开始日期",
|
||||
"years": "年数",
|
||||
"bank_card": "银行卡号",
|
||||
"user_type": "关系类型",
|
||||
"vehicle_type": "车辆类型",
|
||||
"page_num": "页码",
|
||||
"page_size": "每页数量",
|
||||
"use_scenario": "使用场景",
|
||||
"auth_authorize_file_code": "授权文件编码",
|
||||
"plate_no": "车牌号",
|
||||
"plate_type": "号牌类型",
|
||||
"vin_code": "车辆识别代号VIN码",
|
||||
"return_type": "返回类型",
|
||||
"photo_data": "人脸图片",
|
||||
"owner_type": "企业主类型",
|
||||
"type": "查询类型",
|
||||
"query_reason_id": "查询原因ID",
|
||||
"flag": "层次",
|
||||
"dir": "方向",
|
||||
"min_percent": "股权穿透比例下限",
|
||||
"max_percent": "股权穿透比例上限",
|
||||
"engine_number": "发动机号码",
|
||||
"notice_model": "车辆型号",
|
||||
"vlphoto_data": "行驶证图片",
|
||||
"carplate_type": "车辆号牌类型",
|
||||
"image_url": "行驶证图片地址",
|
||||
"reg_url": "车辆登记证图片地址",
|
||||
"mobile_no": "手机号码",
|
||||
"id_card": "身份证号",
|
||||
"idCard": "身份证号",
|
||||
"name": "姓名",
|
||||
"man_name": "男方姓名",
|
||||
"woman_name": "女方姓名",
|
||||
"man_id_card": "男方身份证",
|
||||
"woman_id_card": "女方身份证",
|
||||
"ent_name": "企业名称",
|
||||
"legal_person": "法人姓名",
|
||||
"ent_code": "企业代码",
|
||||
"ent_reg_no": "企业注册号",
|
||||
"auth_date": "授权日期",
|
||||
"date_range": "日期范围",
|
||||
"time_range": "时间范围",
|
||||
"authorized": "是否授权",
|
||||
"authorization_url": "授权链接",
|
||||
"unique_id": "唯一标识",
|
||||
"return_url": "回调地址",
|
||||
"mobile_type": "手机类型",
|
||||
"start_date": "开始日期",
|
||||
"years": "年数",
|
||||
"bank_card": "银行卡号",
|
||||
"user_type": "关系类型",
|
||||
"vehicle_type": "车辆类型",
|
||||
"page_num": "页码",
|
||||
"page_size": "每页数量",
|
||||
"use_scenario": "使用场景",
|
||||
"auth_authorize_file_code": "授权文件编码",
|
||||
"plate_no": "车牌号",
|
||||
"plate_type": "号牌类型",
|
||||
"vin_code": "车辆识别代号VIN码",
|
||||
"return_type": "返回类型",
|
||||
"photo_data": "入参图片base64编码",
|
||||
"owner_type": "企业主类型",
|
||||
"type": "查询类型",
|
||||
"query_reason_id": "查询原因ID",
|
||||
"flag": "层次",
|
||||
"dir": "方向",
|
||||
"min_percent": "股权穿透比例下限",
|
||||
"max_percent": "股权穿透比例上限",
|
||||
"engine_number": "发动机号码",
|
||||
"notice_model": "车辆型号",
|
||||
"vlphoto_data": "行驶证图片",
|
||||
"carplate_type": "车辆号牌类型",
|
||||
"image_url": "入参图片地址",
|
||||
"reg_url": "车辆登记证图片地址",
|
||||
"token": "token采集及获取结果时所使用的凭证,有效期2个小时,在此时效内,应用侧可以发起采集请求(重复的采集所触发的结果会被忽略)和结果查询",
|
||||
"vehicle_name": "车型名称",
|
||||
"vehicle_location": "车辆所在地",
|
||||
"first_registrationdate": "首次登记日期",
|
||||
"color": "颜色",
|
||||
"plate_color": "车牌颜色",
|
||||
"marital_type": "婚姻状况类型",
|
||||
"auth_authorize_file_base64": "PDF授权文件Base64编码(≤500KB,仅PDF)",
|
||||
"account_no": "企业账户",
|
||||
"account_bank": "开户行:如中国银行股份有限公司,查询失败请查看支持银行列表或使用主银行公司。",
|
||||
}
|
||||
|
||||
if label, exists := labelMap[jsonTag]; exists {
|
||||
@@ -454,48 +554,61 @@ func (s *FormConfigServiceImpl) generateFieldLabel(jsonTag string) string {
|
||||
// generateExampleValue 生成示例值
|
||||
func (s *FormConfigServiceImpl) generateExampleValue(fieldType reflect.Type, jsonTag string) string {
|
||||
exampleMap := map[string]string{
|
||||
"mobile_no": "13800138000",
|
||||
"id_card": "110101199001011234",
|
||||
"name": "张三",
|
||||
"man_name": "张三",
|
||||
"woman_name": "李四",
|
||||
"ent_name": "示例企业有限公司",
|
||||
"legal_person": "王五",
|
||||
"ent_code": "91110000123456789X",
|
||||
"auth_date": "20240101-20241231",
|
||||
"time_range": "09:00-18:00",
|
||||
"authorized": "1",
|
||||
"years": "5",
|
||||
"bank_card": "6222021234567890123",
|
||||
"mobile_type": "移动",
|
||||
"start_date": "2024-01-01",
|
||||
"unique_id": "UNIQUE123456",
|
||||
"return_url": "https://example.com/return",
|
||||
"authorization_url": "https://example.com/auth20250101.pdf 注意:请不要使用示例链接,示例链接仅作为参考格式。必须为实际的被查询人授权具有法律效益的授权书文件链接,如访问不到或为不实授权书将追究责任。协议必须为http https",
|
||||
"user_type": "1",
|
||||
"vehicle_type": "0",
|
||||
"page_num": "1",
|
||||
"page_size": "10",
|
||||
"use_scenario": "1",
|
||||
"auth_authorize_file_code": "AUTH123456",
|
||||
"plate_no": "京A12345",
|
||||
"plate_type": "01",
|
||||
"vin_code": "LSGBF53M8DS123456",
|
||||
"return_type": "1",
|
||||
"photo_data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
|
||||
"ownerType": "1",
|
||||
"type": "per",
|
||||
"query_reason_id": "1",
|
||||
"flag": "4",
|
||||
"dir": "down",
|
||||
"min_percent": "0",
|
||||
"max_percent": "1",
|
||||
"engine_number": "1234567890",
|
||||
"notice_model": "1",
|
||||
"vlphoto_data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
|
||||
"carplate_type": "01",
|
||||
"image_url": "https://example.com/images/driving_license.jpg",
|
||||
"reg_url": "https://example.com/images/vehicle_registration.jpg",
|
||||
"mobile_no": "13800138000",
|
||||
"id_card": "110101199001011234",
|
||||
"idCard": "110101199001011234",
|
||||
"name": "张三",
|
||||
"man_name": "张三",
|
||||
"woman_name": "李四",
|
||||
"ent_name": "示例企业有限公司",
|
||||
"legal_person": "王五",
|
||||
"ent_code": "91110000123456789X",
|
||||
"ent_reg_no": "110000000123456",
|
||||
"auth_date": "20240101-20241231",
|
||||
"date_range": "20240101-20241231",
|
||||
"time_range": "09:00-18:00",
|
||||
"authorized": "1",
|
||||
"years": "5",
|
||||
"bank_card": "6222021234567890123",
|
||||
"mobile_type": "移动",
|
||||
"start_date": "2024-01-01",
|
||||
"unique_id": "UNIQUE123456",
|
||||
"return_url": "https://example.com/return ,回调地址链接用于接收回调数据。",
|
||||
"authorization_url": "https://example.com/auth20250101.pdf 注意:请不要使用示例链接,示例链接仅作为参考格式。必须为实际的被查询人授权具有法律效益的授权书文件链接,如访问不到或为不实授权书将追究责任。协议必须为http https",
|
||||
"user_type": "1",
|
||||
"vehicle_type": "0",
|
||||
"page_num": "1",
|
||||
"page_size": "10",
|
||||
"use_scenario": "1",
|
||||
"auth_authorize_file_code": "AUTH123456",
|
||||
"plate_no": "京A12345",
|
||||
"plate_type": "01",
|
||||
"vin_code": "LSGBF53M8DS123456",
|
||||
"return_type": "1",
|
||||
"photo_data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
|
||||
"ownerType": "1",
|
||||
"type": "per",
|
||||
"query_reason_id": "1",
|
||||
"flag": "4",
|
||||
"dir": "down",
|
||||
"min_percent": "0",
|
||||
"max_percent": "1",
|
||||
"engine_number": "1234567890",
|
||||
"notice_model": "1",
|
||||
"vlphoto_data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
|
||||
"carplate_type": "01",
|
||||
"image_url": "https://example.com/images/driving_license.jpg",
|
||||
"reg_url": "https://example.com/images/vehicle_registration.jpg",
|
||||
"token": "0fc79b80371f45e2ac1c693ef9136b24",
|
||||
"vehicle_name": "车型名称,示例:凌派 2020款 锐·混动 1.5L 锐·舒适版",
|
||||
"vehicle_location": "车辆所在地,示例:北京",
|
||||
"first_registrationdate": "初登日期,示例:2020-05",
|
||||
"color": "示例:白色",
|
||||
"plate_color": "0",
|
||||
"marital_type": "10",
|
||||
"auth_authorize_file_base64": "JVBERi0xLjQKJcTl8uXr...(示例PDF的Base64编码)",
|
||||
"account_no": "6222021234567890123",
|
||||
"account_bank": "中国工商银行",
|
||||
}
|
||||
|
||||
if example, exists := exampleMap[jsonTag]; exists {
|
||||
@@ -518,48 +631,61 @@ func (s *FormConfigServiceImpl) generateExampleValue(fieldType reflect.Type, jso
|
||||
// generatePlaceholder 生成占位符
|
||||
func (s *FormConfigServiceImpl) generatePlaceholder(jsonTag string, fieldType string) string {
|
||||
placeholderMap := map[string]string{
|
||||
"mobile_no": "请输入11位手机号码",
|
||||
"id_card": "请输入18位身份证号码",
|
||||
"name": "请输入真实姓名",
|
||||
"man_name": "请输入男方真实姓名",
|
||||
"woman_name": "请输入女方真实姓名",
|
||||
"ent_name": "请输入企业全称",
|
||||
"legal_person": "请输入法人真实姓名",
|
||||
"ent_code": "请输入统一社会信用代码",
|
||||
"auth_date": "请输入授权日期范围(YYYYMMDD-YYYYMMDD)",
|
||||
"time_range": "请输入时间范围(HH:MM-HH:MM)",
|
||||
"authorized": "请选择是否授权",
|
||||
"years": "请输入查询年数(0-100)",
|
||||
"bank_card": "请输入银行卡号",
|
||||
"mobile_type": "请选择手机类型",
|
||||
"start_date": "请选择开始日期",
|
||||
"unique_id": "请输入唯一标识",
|
||||
"return_url": "请输入返回链接",
|
||||
"authorization_url": "请输入授权链接",
|
||||
"user_type": "请选择关系类型",
|
||||
"vehicle_type": "请选择车辆类型",
|
||||
"page_num": "请输入页码",
|
||||
"page_size": "请输入每页数量(1-100)",
|
||||
"use_scenario": "请选择使用场景",
|
||||
"auth_authorize_file_code": "请输入授权文件编码",
|
||||
"plate_no": "请输入车牌号",
|
||||
"plate_type": "请选择号牌类型(01或02)",
|
||||
"vin_code": "请输入17位车辆识别代号VIN码",
|
||||
"return_type": "请选择返回类型",
|
||||
"photo_data": "请输入base64编码的人脸图片(支持JPG、BMP、PNG格式)",
|
||||
"ownerType": "请选择企业主类型",
|
||||
"type": "请选择查询类型",
|
||||
"query_reason_id": "请选择查询原因ID",
|
||||
"flag": "请输入层次(最大4)",
|
||||
"dir": "请选择方向(up-向上,down-向下)",
|
||||
"min_percent": "请输入股权穿透比例下限(默认0)",
|
||||
"max_percent": "请输入股权穿透比例上限(默认1)",
|
||||
"engine_number": "请输入发动机号码",
|
||||
"notice_model": "请输入车辆型号",
|
||||
"vlphoto_data": "请输入行驶证图片",
|
||||
"carplate_type": "请选择车辆号牌类型(01-大型汽车 02-小型汽车 03-使馆汽车 04-领馆汽车 05-境外汽车 06-外籍汽车 07-普通摩托车 08-轻便摩托车 09-使馆摩托车 10-领馆摩托车 11-境外摩托车 12-外籍摩托车 13-低速车 14-拖拉机 15-挂车 16-教练汽车 17-教练摩托车 20-临时入境汽车 21-临时入境摩托车 22-临时行驶车 23-警用汽车 24-警用摩托 51-新能源大型车 52-新能源小型车)",
|
||||
"image_url": "请输入行驶证图片地址",
|
||||
"reg_url": "请输入车辆登记证图片地址",
|
||||
"mobile_no": "请输入11位手机号码",
|
||||
"id_card": "请输入18位身份证号码",
|
||||
"idCard": "请输入18位身份证号码",
|
||||
"name": "请输入真实姓名",
|
||||
"man_name": "请输入男方真实姓名",
|
||||
"woman_name": "请输入女方真实姓名",
|
||||
"ent_name": "请输入企业全称",
|
||||
"legal_person": "请输入法人真实姓名",
|
||||
"ent_code": "请输入统一社会信用代码",
|
||||
"ent_reg_no": "请输入企业注册号(统一社会信用代码)",
|
||||
"auth_date": "请输入授权日期范围(YYYYMMDD-YYYYMMDD)",
|
||||
"date_range": "请输入日期范围(YYYYMMDD-YYYYMMDD)",
|
||||
"time_range": "请输入时间范围(HH:MM-HH:MM)",
|
||||
"authorized": "请选择是否授权",
|
||||
"years": "请输入查询年数(0-100)",
|
||||
"bank_card": "请输入银行卡号",
|
||||
"mobile_type": "请选择手机类型",
|
||||
"start_date": "请选择开始日期",
|
||||
"unique_id": "请输入唯一标识",
|
||||
"return_url": "请输入回调地址链接",
|
||||
"authorization_url": "请输入授权链接",
|
||||
"user_type": "请选择关系类型",
|
||||
"vehicle_type": "请选择车辆类型",
|
||||
"page_num": "请输入页码",
|
||||
"page_size": "请输入每页数量(1-100)",
|
||||
"use_scenario": "请选择使用场景",
|
||||
"auth_authorize_file_code": "请输入授权文件编码",
|
||||
"plate_no": "请输入车牌号",
|
||||
"plate_type": "请选择号牌类型(01或02)",
|
||||
"vin_code": "请输入17位车辆识别代号VIN码",
|
||||
"return_type": "请选择返回类型",
|
||||
"photo_data": "请输入base64编码的入参图片(支持JPG、BMP、PNG格式)",
|
||||
"ownerType": "请选择企业主类型",
|
||||
"type": "请选择查询类型",
|
||||
"query_reason_id": "请选择查询原因ID",
|
||||
"flag": "请输入层次(最大4)",
|
||||
"dir": "请选择方向(up-向上,down-向下)",
|
||||
"min_percent": "请输入股权穿透比例下限(默认0)",
|
||||
"max_percent": "请输入股权穿透比例上限(默认1)",
|
||||
"engine_number": "请输入发动机号码",
|
||||
"notice_model": "请输入车辆型号",
|
||||
"vlphoto_data": "请输入行驶证图片",
|
||||
"carplate_type": "请选择车辆号牌类型(01-大型汽车 02-小型汽车 03-使馆汽车 04-领馆汽车 05-境外汽车 06-外籍汽车 07-普通摩托车 08-轻便摩托车 09-使馆摩托车 10-领馆摩托车 11-境外摩托车 12-外籍摩托车 13-低速车 14-拖拉机 15-挂车 16-教练汽车 17-教练摩托车 20-临时入境汽车 21-临时入境摩托车 22-临时行驶车 23-警用汽车 24-警用摩托 51-新能源大型车 52-新能源小型车)",
|
||||
"image_url": "请输入入参图片地址",
|
||||
"reg_url": "请输入车辆登记证图片地址",
|
||||
"token": "请输入token",
|
||||
"vehicle_name": "请输入车型名称",
|
||||
"vehicle_location": "请输入车辆所在地",
|
||||
"first_registrationdate": "请输入首次登记日期,格式:YYYY-MM",
|
||||
"color": "请输入颜色",
|
||||
"plate_color": "请输入车牌颜色",
|
||||
"marital_type": "请选择婚姻状况类型",
|
||||
"auth_authorize_file_base64": "请输入PDF文件的Base64编码字符串",
|
||||
"account_no": "请输入企业账户",
|
||||
"account_bank": "请输入开户行:如中国银行股份有限公司,查询失败请查看支持银行列表或使用主银行公司。",
|
||||
}
|
||||
|
||||
if placeholder, exists := placeholderMap[jsonTag]; exists {
|
||||
@@ -584,48 +710,61 @@ func (s *FormConfigServiceImpl) generatePlaceholder(jsonTag string, fieldType st
|
||||
// generateDescription 生成字段描述
|
||||
func (s *FormConfigServiceImpl) generateDescription(jsonTag string, validation string) string {
|
||||
descMap := map[string]string{
|
||||
"mobile_no": "请输入11位手机号码",
|
||||
"id_card": "请输入18位身份证号码最后一位如是字母请大写",
|
||||
"name": "请输入真实姓名",
|
||||
"man_name": "请输入男方真实姓名",
|
||||
"woman_name": "请输入女方真实姓名",
|
||||
"ent_name": "请输入企业全称",
|
||||
"legal_person": "请输入法人真实姓名",
|
||||
"ent_code": "请输入统一社会信用代码",
|
||||
"auth_date": "请输入授权日期范围,格式:YYYYMMDD-YYYYMMDD,且日期范围必须包括今天",
|
||||
"time_range": "请输入时间范围,格式:HH:MM-HH:MM",
|
||||
"authorized": "请输入是否授权:0-未授权,1-已授权",
|
||||
"years": "请输入查询年数(0-100)",
|
||||
"bank_card": "请输入银行卡号",
|
||||
"mobile_type": "请选择手机类型",
|
||||
"start_date": "请选择开始日期",
|
||||
"unique_id": "请输入唯一标识",
|
||||
"return_url": "请输入返回链接",
|
||||
"authorization_url": "请输入授权链接",
|
||||
"user_type": "关系类型:1-ETC开户人;2-车辆所有人;3-ETC经办人(默认1-ETC开户人)",
|
||||
"vehicle_type": "车辆类型:0-客车;1-货车;2-全部(默认查全部)",
|
||||
"page_num": "请输入页码,从1开始",
|
||||
"page_size": "请输入每页数量,范围1-100",
|
||||
"use_scenario": "使用场景:1-信贷审核;2-保险评估;3-招聘背景调查;4-其他业务场景;99-其他",
|
||||
"auth_authorize_file_code": "请输入授权文件编码",
|
||||
"plate_no": "请输入车牌号",
|
||||
"plate_type": "号牌类型:01-小型汽车;02-大型汽车(可选)",
|
||||
"vin_code": "请输入17位车辆识别代号VIN码(Vehicle Identification Number)",
|
||||
"return_type": "返回类型:1-专业和学校名称数据返回编码形式(默认);2-专业和学校名称数据返回中文名称",
|
||||
"photo_data": "人脸图片(必填):base64编码的图片数据,仅支持JPG、BMP、PNG三种格式",
|
||||
"owner_type": "企业主类型编码:1-法定代表人;2-主要人员;3-自然人股东;4-法定代表人及自然人股东;5-其他",
|
||||
"type": "查询类型:per-人员,ent-企业 ",
|
||||
"query_reason_id": "查询原因ID:1-授信审批;2-贷中管理;3-贷后管理;4-异议处理;5-担保查询;6-租赁资质审查;7-融资租赁审批;8-借贷撮合查询;9-保险审批;10-资质审核;11-风控审核;12-企业背调",
|
||||
"flag": "层次,最大4",
|
||||
"dir": "方向:up-向上穿透,down-向下穿透",
|
||||
"min_percent": "股权穿透比例下限(大于等于),默认为0,支持小数点后两位(以小数指代百分比)",
|
||||
"max_percent": "股权穿透比例上限(小于等于),默认为1,支持小数点后两位(以小数指代百分比)",
|
||||
"engine_number": "发动机号码",
|
||||
"notice_model": "车辆型号",
|
||||
"vlphoto_data": "行驶证图片:base64编码的图片数据,仅支持JPG、BMP、PNG三种格式",
|
||||
"carplate_type": "车辆号牌类型:01-大型汽车;02-小型汽车;03-使馆汽车;04-领馆汽车;05-境外汽车;06-外籍汽车;07-普通摩托车;08-轻便摩托车;09-使馆摩托车;10-领馆摩托车;11-境外摩托车;12-外籍摩托车;13-低速车;14-拖拉机;15-挂车;16-教练汽车;17-教练摩托车;20-临时入境汽车;21-临时入境摩托车;22-临时行驶车;23-警用汽车;24-警用摩托;51-新能源大型车;52-新能源小型车",
|
||||
"image_url": "行驶证图片地址(必填):请提供行驶证的图片URL地址",
|
||||
"reg_url": "车辆登记证图片地址(非必填):请提供车辆登记证的图片URL地址",
|
||||
"mobile_no": "请输入11位手机号码",
|
||||
"id_card": "请输入18位身份证号码最后一位如是字母请大写",
|
||||
"idCard": "请输入18位身份证号码最后一位如是字母请大写",
|
||||
"name": "请输入真实姓名",
|
||||
"man_name": "请输入男方真实姓名",
|
||||
"woman_name": "请输入女方真实姓名",
|
||||
"ent_name": "请输入企业全称",
|
||||
"legal_person": "请输入法人真实姓名",
|
||||
"ent_code": "请输入统一社会信用代码",
|
||||
"ent_reg_no": "请输入企业注册号(统一社会信用代码)",
|
||||
"auth_date": "请输入授权日期范围,格式:YYYYMMDD-YYYYMMDD,且日期范围必须包括今天",
|
||||
"date_range": "请输入日期范围,格式:YYYYMMDD-YYYYMMDD",
|
||||
"time_range": "请输入时间范围,格式:HH:MM-HH:MM",
|
||||
"authorized": "请输入是否授权:0-未授权,1-已授权",
|
||||
"years": "请输入查询年数(0-100)",
|
||||
"bank_card": "请输入银行卡号",
|
||||
"mobile_type": "请选择手机类型",
|
||||
"start_date": "请选择开始日期",
|
||||
"unique_id": "请输入唯一标识",
|
||||
"return_url": "请输入回调地址链接",
|
||||
"authorization_url": "请输入授权链接",
|
||||
"user_type": "关系类型:1-ETC开户人;2-车辆所有人;3-ETC经办人(默认1-ETC开户人)",
|
||||
"vehicle_type": "车辆类型:0-客车;1-货车;2-全部(默认查全部)",
|
||||
"page_num": "请输入页码,从1开始",
|
||||
"page_size": "请输入每页数量,范围1-100",
|
||||
"use_scenario": "使用场景:1-信贷审核;2-保险评估;3-招聘背景调查;4-其他业务场景;99-其他",
|
||||
"auth_authorize_file_code": "请输入授权文件编码",
|
||||
"plate_no": "请输入车牌号",
|
||||
"plate_type": "号牌类型:01-小型汽车;02-大型汽车(可选)",
|
||||
"vin_code": "请输入17位车辆识别代号VIN码(Vehicle Identification Number)",
|
||||
"return_type": "返回类型:1-专业和学校名称数据返回编码形式(默认);2-专业和学校名称数据返回中文名称",
|
||||
"photo_data": "入参图片:base64编码的图片数据,仅支持JPG、BMP、PNG三种格式",
|
||||
"owner_type": "企业主类型编码:1-法定代表人;2-主要人员;3-自然人股东;4-法定代表人及自然人股东;5-其他",
|
||||
"type": "查询类型:per-人员,ent-企业 ",
|
||||
"query_reason_id": "查询原因ID:1-授信审批;2-贷中管理;3-贷后管理;4-异议处理;5-担保查询;6-租赁资质审查;7-融资租赁审批;8-借贷撮合查询;9-保险审批;10-资质审核;11-风控审核;12-企业背调",
|
||||
"flag": "层次,最大4",
|
||||
"dir": "方向:up-向上穿透,down-向下穿透",
|
||||
"min_percent": "股权穿透比例下限(大于等于),默认为0,支持小数点后两位(以小数指代百分比)",
|
||||
"max_percent": "股权穿透比例上限(小于等于),默认为1,支持小数点后两位(以小数指代百分比)",
|
||||
"engine_number": "发动机号码",
|
||||
"notice_model": "车辆型号",
|
||||
"vlphoto_data": "行驶证图片:base64编码的图片数据,仅支持JPG、BMP、PNG三种格式",
|
||||
"carplate_type": "车辆号牌类型:01-大型汽车;02-小型汽车;03-使馆汽车;04-领馆汽车;05-境外汽车;06-外籍汽车;07-普通摩托车;08-轻便摩托车;09-使馆摩托车;10-领馆摩托车;11-境外摩托车;12-外籍摩托车;13-低速车;14-拖拉机;15-挂车;16-教练汽车;17-教练摩托车;20-临时入境汽车;21-临时入境摩托车;22-临时行驶车;23-警用汽车;24-警用摩托;51-新能源大型车;52-新能源小型车",
|
||||
"image_url": "入参图片url地址",
|
||||
"reg_url": "车辆登记证图片地址(非必填):请提供车辆登记证的图片URL地址",
|
||||
"token": "token采集及获取结果时所使用的凭证,有效期2个小时,在此时效内,应用侧可以发起采集请求(重复的采集所触发的结果会被忽略)和结果查询",
|
||||
"vehicle_name": "车型名称,示例:凌派 2020款 锐·混动 1.5L 锐·舒适版",
|
||||
"vehicle_location": "车辆所在地",
|
||||
"first_registrationdate": "首次登记日期,格式:YYYY-MM",
|
||||
"color": "颜色",
|
||||
"plate_color": "车牌颜色",
|
||||
"marital_type": "婚姻状况类型:10-未登记(无登记记录),20-已婚,30-丧偶,40-离异",
|
||||
"auth_authorize_file_base64": "请输入PDF文件的Base64编码字符串",
|
||||
"account_no": "请输入企业账户",
|
||||
"account_bank": "请输入开户行:如中国银行股份有限公司,查询失败请查看支持银行列表或使用主银行公司。",
|
||||
}
|
||||
|
||||
if desc, exists := descMap[jsonTag]; exists {
|
||||
|
||||
@@ -28,10 +28,10 @@ func TestFormConfigService_GetFormConfig(t *testing.T) {
|
||||
|
||||
// 验证字段信息
|
||||
expectedFields := map[string]bool{
|
||||
"man_name": false,
|
||||
"man_id_card": false,
|
||||
"woman_name": false,
|
||||
"woman_id_card": false,
|
||||
"man_name": false,
|
||||
"man_id_card": false,
|
||||
"woman_name": false,
|
||||
"woman_id_card": false,
|
||||
}
|
||||
|
||||
for _, field := range config.Fields {
|
||||
|
||||
3243
internal/domains/api/services/processors/1.json
Normal file
3243
internal/domains/api/services/processors/1.json
Normal file
File diff suppressed because one or more lines are too long
350
internal/domains/api/services/processors/1.md
Normal file
350
internal/domains/api/services/processors/1.md
Normal file
@@ -0,0 +1,350 @@
|
||||
## 返回字段说明
|
||||
|
||||
# 5. 返回参数说明
|
||||
|
||||
## result.detail 参数说明(Object)
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| lawsuitStat | object | 涉诉结果,详见【lawsuitStat 参数说明】 |
|
||||
| breachCaseList | array | 失信结果,详见【breachCaseList 参数说明】 |
|
||||
| consumptionRestrictionList | array | 限高结果,详见【consumptionRestrictionList 参数说明】 |
|
||||
|
||||
---
|
||||
|
||||
## lawsuitStat 参数说明(Object)
|
||||
|
||||
### 一、统计概览(count)
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| area_stat | string | 涉案地点分布 |
|
||||
| ay_stat | string | 涉案案由分布 |
|
||||
| jafs_stat | string | 结案方式分布 |
|
||||
| larq_stat | string | 涉案时间分布 |
|
||||
| count_total | string | 案件总数 |
|
||||
| count_beigao | string | 被告总数 |
|
||||
| count_yuangao | string | 原告总数 |
|
||||
| count_other | string | 第三人总数 |
|
||||
| count_jie_total | string | 已结案总数 |
|
||||
| count_jie_beigao | string | 被告已结案总数 |
|
||||
| count_jie_yuangao | string | 原告已结案总数 |
|
||||
| count_jie_other | string | 第三人已结案总数 |
|
||||
| count_wei_total | string | 未结案总数 |
|
||||
| count_wei_beigao | string | 被告未结案总数 |
|
||||
| count_wei_yuangao | string | 原告未结案总数 |
|
||||
| count_wei_other | string | 第三人未结案总数 |
|
||||
| money_total | string | 涉案总金额 |
|
||||
| money_beigao | string | 被告金额 |
|
||||
| money_yuangao | string | 原告金额 |
|
||||
| money_other | string | 第三人金额 |
|
||||
| money_jie_total | string | 已结案金额 |
|
||||
| money_jie_beigao | string | 被告已结案金额 |
|
||||
| money_jie_yuangao | string | 原告已结金额 |
|
||||
| money_jie_other | string | 第三人已结案金额 |
|
||||
| money_wei_total | string | 未结案金额 |
|
||||
| money_wei_beigao | string | 被告未结案金额 |
|
||||
| money_wei_yuangao | string | 原告未结案金额 |
|
||||
| money_wei_other | string | 第三人未结案金额 |
|
||||
| money_wei_percent | string | 未结案金额百分比 |
|
||||
|
||||
---
|
||||
|
||||
### 二、案件明细
|
||||
|
||||
#### 1. 行政案件(administrative)
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| cases | array | 案件列表 |
|
||||
| count | object | 统计信息(同 count 结构) |
|
||||
|
||||
**cases 子字段说明**
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| c_ah | string | 案号 |
|
||||
| c_ah_hx | string | 后续案号 |
|
||||
| c_ah_ys | string | 原审案号 |
|
||||
| c_gkws_dsr | string | 当事人 |
|
||||
| c_gkws_glah | string | 相关案件号 |
|
||||
| c_gkws_id | string | 公开文书 ID |
|
||||
| c_gkws_pjjg | string | 判决结果 |
|
||||
| c_slfsxx | string | 审理方式信息 |
|
||||
| c_ssdy | string | 所属地域 |
|
||||
| d_jarq | string | 结案时间 |
|
||||
| d_larq | string | 立案时间 |
|
||||
| n_ajbs | string | 案件标识 |
|
||||
| n_ajjzjd | string | 案件进展阶段 |
|
||||
| n_ajlx | string | 案件类型 |
|
||||
| n_jaay | string | 结案案由 |
|
||||
| n_jaay_tree | string | 结案案由详细 |
|
||||
| n_jabdje | string | 结案标的金额 |
|
||||
| n_jabdje_gj | string | 结案标的金额估计 |
|
||||
| n_jabdje_gj_level | string | 结案标的金额估计等级(万元) |
|
||||
| n_jabdje_level | string | 结案标的金额等级(万元) |
|
||||
| n_jafs | string | 结案方式 |
|
||||
| n_jbfy | string | 经办法院 |
|
||||
| n_jbfy_cj | string | 法院所属层级 |
|
||||
| n_laay | string | 立案案由 |
|
||||
| n_laay_tree | string | 立案案由详细 |
|
||||
| n_pj_victory | string | 胜诉估计 |
|
||||
| n_qsbdje | string | 起诉标的金额 |
|
||||
| n_qsbdje_level | string | 起诉标的金额等级 |
|
||||
| n_slcx | string | 审理程序 |
|
||||
| n_ssdw | string | 诉讼地位 |
|
||||
| n_ssdw_ys | string | 一审诉讼地位 |
|
||||
| c_dsrxx | array | 当事人信息列表 |
|
||||
|
||||
**c_dsrxx 子字段说明**
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| n_ssdw | string | 诉讼地位 |
|
||||
| c_mc | string | 名称 |
|
||||
| n_dsrlx | string | 当事人类型 |
|
||||
|
||||
---
|
||||
|
||||
#### 2. 民事案件(civil)
|
||||
|
||||
> 字段结构与 **行政案件** 基本一致,新增以下字段:
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| n_jaay_tag | string | 结案案由标签 |
|
||||
| n_laay_tag | string | 立案案由标签 |
|
||||
| n_qsbdje_gj | string | 起诉标的金额估计 |
|
||||
| n_qsbdje_gj_level | string | 起诉标的金额估计等级 |
|
||||
|
||||
---
|
||||
|
||||
#### 3. 刑事案件(criminal)
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| n_dzzm | string | 定罪罪名 |
|
||||
| n_dzzm_tree | string | 定罪罪名详细 |
|
||||
| n_fzje | string | 犯罪金额 |
|
||||
| n_fzje_level | string | 犯罪金额等级 |
|
||||
| n_ccxzxje | string | 财产刑执行金额 |
|
||||
| n_ccxzxje_gj | string | 财产刑执行金额估计 |
|
||||
| n_ccxzxje_gj_level | string | 财产刑执行金额估计等级 |
|
||||
| n_pcjg | string | 判处结果 |
|
||||
| n_pcpcje | string | 判处赔偿金额 |
|
||||
| n_pcpcje_gj | string | 判处赔偿金额估计 |
|
||||
| n_pcpcje_gj_level | string | 判处赔偿金额估计等级 |
|
||||
| n_bqqpcje | string | 被请求赔偿金额 |
|
||||
| n_bqqpcje_level | string | 被请求赔偿金额等级 |
|
||||
|
||||
---
|
||||
|
||||
#### 4. 执行案件(implement)
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| n_sqzxbdje | string | 申请执行标的金额 |
|
||||
| n_sjdwje | string | 实际到位金额 |
|
||||
| n_wzxje | string | 未执行金额 |
|
||||
|
||||
---
|
||||
|
||||
#### 5. 非诉保全审查(preservation)
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| c_sqbqbdw | string | 申请保全标的物 |
|
||||
| n_sqbqse | string | 申请保全数额 |
|
||||
| n_sqbqse_level | string | 申请保全数额等级 |
|
||||
|
||||
---
|
||||
|
||||
#### 6. 强制清算与破产案件(bankrupt)
|
||||
|
||||
> 字段结构与行政案件一致,主要用于破产/清算类案件。
|
||||
|
||||
---
|
||||
|
||||
### 三、案件串联树(cases_tree)
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| administrative | array | 行政案件链 |
|
||||
| civil | array | 民事案件链 |
|
||||
| criminal | array | 刑事案件链 |
|
||||
|
||||
**案件节点说明**
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| c_ah | string | 案号 |
|
||||
| case_type | string | 案件类型 |
|
||||
| n_ajbs | string | 案件标识 |
|
||||
| stage_type | string | 审理阶段类型 |
|
||||
| next | object | 后续关联案件 |
|
||||
|
||||
---
|
||||
|
||||
## breachCaseList 参数说明(Array)
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| xb | string | 性别 |
|
||||
| zzjgdm | string | 组织机构代码(企业查询时有值) |
|
||||
| qyfr | string | 企业法人 |
|
||||
| zxfy | string | 执行法院 |
|
||||
| sf | string | 省份 |
|
||||
| zxyjwh | string | 执行依据文号 |
|
||||
| larq | string | 立案时间(yyyy-mm-dd) |
|
||||
| ah | string | 案号 |
|
||||
| zxyjdw | string | 执行依据单位 |
|
||||
| yw | string | 生效法律文书确定的义务 |
|
||||
| lxqk | string | 被执行人的履行情况 |
|
||||
| xwqx | string | 失信被执行人行为具体情形 |
|
||||
| fbrq | string | 发布时间(yyyy-mm-dd) |
|
||||
| pjje_gj | string | 判决金额估计 |
|
||||
|
||||
---
|
||||
|
||||
## consumptionRestrictionList 参数说明(Array)
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| qyfr | string | 企业法人 |
|
||||
| qymc | string | 企业名称 |
|
||||
| zxfy | string | 执行法院 |
|
||||
| fbrq | string | 发布时间(yyyy-mm-dd) |
|
||||
| larq | string | 立案时间(yyyy-mm-dd) |
|
||||
| ah | string | 案号 |
|
||||
|
||||
---
|
||||
## 字典说明
|
||||
|
||||
### 案件类型(case_type)
|
||||
|
||||
| 代码 | 说明 |
|
||||
|---|---|
|
||||
| 200 | 刑事案件 |
|
||||
| 300 | 民事案件 |
|
||||
| 400 | 行政案件 |
|
||||
| 1000 | 执行案件 |
|
||||
|
||||
---
|
||||
|
||||
### 审理阶段(stage_type)
|
||||
|
||||
| 代码 | 说明 |
|
||||
|---|---|
|
||||
| 1 | 一审 |
|
||||
| 2 | 二审 |
|
||||
| 3 | 再审审查 |
|
||||
| 4 | 再审 |
|
||||
| 5 | 执行 |
|
||||
|
||||
---
|
||||
|
||||
### 金额等级(单位:万元)
|
||||
|
||||
| 代码 | 金额区间(万元) |
|
||||
|---|---|
|
||||
| 0 | 未填写或 0 |
|
||||
| 1 | N < 1 |
|
||||
| 2 | 1 ≤ N < 2 |
|
||||
| 3 | 2 ≤ N < 3 |
|
||||
| 4 | 3 ≤ N < 4 |
|
||||
| 5 | 4 ≤ N < 5 |
|
||||
| 6 | 5 ≤ N < 6 |
|
||||
| 7 | 6 ≤ N < 7 |
|
||||
| 8 | 7 ≤ N < 8 |
|
||||
| 9 | 8 ≤ N < 9 |
|
||||
| 10 | 9 ≤ N < 10 |
|
||||
| 11 | 10 ≤ N < 20 |
|
||||
| 12 | 20 ≤ N < 30 |
|
||||
| 13 | 30 ≤ N < 40 |
|
||||
| 14 | 40 ≤ N < 50 |
|
||||
| 15 | 50 ≤ N < 60 |
|
||||
| 16 | 60 ≤ N < 70 |
|
||||
| 17 | 70 ≤ N < 80 |
|
||||
| 18 | 80 ≤ N < 90 |
|
||||
| 19 | 90 ≤ N < 100 |
|
||||
| 20 | 100 ≤ N < 150 |
|
||||
| 21 | 150 ≤ N < 200 |
|
||||
| 22 | 200 ≤ N < 250 |
|
||||
| 23 | 250 ≤ N < 300 |
|
||||
| 24 | 300 ≤ N < 350 |
|
||||
| 25 | 350 ≤ N < 400 |
|
||||
| 26 | 400 ≤ N < 450 |
|
||||
| 27 | 450 ≤ N < 500 |
|
||||
| 28 | 500 ≤ N < 550 |
|
||||
| 29 | 550 ≤ N < 600 |
|
||||
| 30 | 600 ≤ N < 650 |
|
||||
| 31 | 650 ≤ N < 700 |
|
||||
| 32 | 700 ≤ N < 750 |
|
||||
| 33 | 750 ≤ N < 800 |
|
||||
| 34 | 800 ≤ N < 850 |
|
||||
| 35 | 850 ≤ N < 900 |
|
||||
| 36 | 900 ≤ N < 950 |
|
||||
| 37 | 950 ≤ N < 1000 |
|
||||
| 38 | 1000 ≤ N < 1500 |
|
||||
| 39 | 1500 ≤ N < 2000 |
|
||||
| 40 | 2000 ≤ N < 2500 |
|
||||
| 41 | 2500 ≤ N < 3000 |
|
||||
| 42 | 3000 ≤ N < 3500 |
|
||||
| 43 | 3500 ≤ N < 4000 |
|
||||
| 44 | 4000 ≤ N < 4500 |
|
||||
| 45 | 4500 ≤ N < 5000 |
|
||||
| 46 | 5000 ≤ N < 5500 |
|
||||
| 47 | 5500 ≤ N < 6000 |
|
||||
| 48 | 6000 ≤ N < 6500 |
|
||||
| 49 | 6500 ≤ N < 7000 |
|
||||
| 50 | 7000 ≤ N < 7500 |
|
||||
| 51 | 7500 ≤ N < 8000 |
|
||||
| 52 | 8000 ≤ N < 8500 |
|
||||
| 53 | 8500 ≤ N < 9000 |
|
||||
| 54 | 9000 ≤ N < 9500 |
|
||||
| 55 | 9500 ≤ N < 10000 |
|
||||
| 56 | 10000 ≤ N < 15000 |
|
||||
| 57 | 15000 ≤ N < 20000 |
|
||||
| 58 | 20000 ≤ N < 25000 |
|
||||
| 59 | 25000 ≤ N < 30000 |
|
||||
| 60 | 30000 ≤ N < 35000 |
|
||||
| 61 | 35000 ≤ N < 40000 |
|
||||
| 62 | 40000 ≤ N < 45000 |
|
||||
| 63 | 45000 ≤ N < 50000 |
|
||||
| 64 | 50000 ≤ N < 55000 |
|
||||
| 65 | 55000 ≤ N < 60000 |
|
||||
| 66 | 60000 ≤ N < 65000 |
|
||||
| 67 | 65000 ≤ N < 70000 |
|
||||
| 68 | 70000 ≤ N < 75000 |
|
||||
| 69 | 75000 ≤ N < 80000 |
|
||||
| 70 | 80000 ≤ N < 85000 |
|
||||
| 71 | 85000 ≤ N < 90000 |
|
||||
| 72 | 90000 ≤ N < 95000 |
|
||||
| 73 | 95000 ≤ N < 100000 |
|
||||
| 74 | 100000 ≤ N < 150000 |
|
||||
| 75 | 150000 ≤ N < 200000 |
|
||||
| 76 | 200000 ≤ N < 250000 |
|
||||
| 77 | 250000 ≤ N < 300000 |
|
||||
| 78 | 300000 ≤ N < 350000 |
|
||||
| 79 | 350000 ≤ N < 400000 |
|
||||
| 80 | 400000 ≤ N < 450000 |
|
||||
| 81 | 450000 ≤ N < 500000 |
|
||||
| 82 | 500000 ≤ N < 550000 |
|
||||
| 83 | 550000 ≤ N < 600000 |
|
||||
| 84 | 600000 ≤ N < 650000 |
|
||||
| 85 | 650000 ≤ N < 700000 |
|
||||
| 86 | 700000 ≤ N < 750000 |
|
||||
| 87 | 750000 ≤ N < 800000 |
|
||||
| 88 | 800000 ≤ N < 850000 |
|
||||
| 89 | 850000 ≤ N < 900000 |
|
||||
| 90 | 900000 ≤ N < 950000 |
|
||||
| 91 | 950000 ≤ N < 1000000 |
|
||||
| 92 | 1000000 ≤ N < 1500000 |
|
||||
| 93 | 1500000 ≤ N < 2000000 |
|
||||
| 94 | 2000000 ≤ N < 2500000 |
|
||||
| 95 | 2500000 ≤ N < 3000000 |
|
||||
| 96 | 3000000 ≤ N < 3500000 |
|
||||
| 97 | 3500000 ≤ N < 4000000 |
|
||||
| 98 | 4000000 ≤ N < 4500000 |
|
||||
| 99 | 4500000 ≤ N < 5000000 |
|
||||
| 100 | 5000000 ≤ N |
|
||||
137
internal/domains/api/services/processors/2.json
Normal file
137
internal/domains/api/services/processors/2.json
Normal file
@@ -0,0 +1,137 @@
|
||||
{
|
||||
"sxbzxr": [],
|
||||
"xgbzxr": [],
|
||||
"entout": {
|
||||
"jurisdict": {
|
||||
"cases": [],
|
||||
"count": {}
|
||||
},
|
||||
"preservation": {
|
||||
"cases": [],
|
||||
"count": {
|
||||
"money_jie_total": "11",
|
||||
"count_total": "1",
|
||||
"larq_stat": "2025(1)",
|
||||
"money_wei_percent": "0",
|
||||
"area_stat": "重庆市(1)",
|
||||
"money_jie_beigao": "0",
|
||||
"count_jie_total": "1",
|
||||
"money_jie_other": "0",
|
||||
"count_wei_total": "0",
|
||||
"money_yuangao": "11",
|
||||
"count_jie_beigao": "0",
|
||||
"money_beigao": "0",
|
||||
"ay_stat": "非讼程序案件案由(1)",
|
||||
"count_wei_other": "0",
|
||||
"count_wei_beigao": "0",
|
||||
"count_wei_yuangao": "0",
|
||||
"money_other": "0",
|
||||
"count_yuangao": "1",
|
||||
"money_wei_yuangao": "0",
|
||||
"money_jie_yuangao": "11",
|
||||
"money_wei_beigao": "0",
|
||||
"count_jie_yuangao": "1",
|
||||
"count_other": "0",
|
||||
"count_jie_other": "0",
|
||||
"count_beigao": "0",
|
||||
"money_wei_total": "0",
|
||||
"money_wei_other": "0",
|
||||
"money_total": "11",
|
||||
"jafs_stat": "转为其他案件中的保全(1)"
|
||||
}
|
||||
},
|
||||
"cases_tree": {},
|
||||
"administrative": {
|
||||
"cases": [],
|
||||
"count": {}
|
||||
},
|
||||
"civil": {
|
||||
"cases": [
|
||||
{
|
||||
"n_ssdw": "原告",
|
||||
"n_ccxzxje": "",
|
||||
"n_pcjg": "",
|
||||
"n_sqbqbdw": "",
|
||||
"c_gkws_glah": "",
|
||||
"n_sqbqse_level": "",
|
||||
"n_pj_victory": "未知",
|
||||
"n_wzxje": "",
|
||||
"c_gkws_dsr": "",
|
||||
"n_qsbdje_gj_level": "",
|
||||
"n_jbfy_cj": "基层法院",
|
||||
"n_qsbdje": "14858.43",
|
||||
"n_slcx": "一审",
|
||||
"n_ajjzjd": "已结案",
|
||||
"n_jbfy": "淮南市田家庵区人民法院",
|
||||
"c_gkws_id": "",
|
||||
"d_jarq": "2023-03-29",
|
||||
"n_jabdje_level": "2",
|
||||
"c_slfsxx": "1,2023-03-27 15:00:00,淮南市田家庵区法院6号法庭,1",
|
||||
"n_ccxzxje_gj_level": "",
|
||||
"n_pcpcje": "",
|
||||
"n_sjdwje": "",
|
||||
"n_fzje_level": "",
|
||||
"n_pcpcje_level": "",
|
||||
"c_ah": "(2023)皖0403民初1229号",
|
||||
"n_qsbdje_level": "2",
|
||||
"c_ssdy": "安徽省",
|
||||
"n_jabdje": "14460.43",
|
||||
"n_ajlx": "民事一审",
|
||||
"n_laay": "合同、准合同纠纷",
|
||||
"n_jaay": "合同、准合同纠纷",
|
||||
"n_laay_tree": "合同、准合同纠纷,合同纠纷,租赁合同纠纷",
|
||||
"n_bqqpcje": "",
|
||||
"n_jabdje_gj": "",
|
||||
"n_jaay_tree": "合同、准合同纠纷,合同纠纷,租赁合同纠纷",
|
||||
"n_pcpcje_gj": "",
|
||||
"n_jabdje_gj_level": "",
|
||||
"n_ccxzxje_level": "",
|
||||
"n_ssdw_ys": "原告",
|
||||
"n_pcpcje_gj_level": "",
|
||||
"n_jafs": "判决",
|
||||
"c_id": "71542222",
|
||||
"n_qsbdje_gj": "",
|
||||
"c_ah_hx": "(2023)皖0403执3847号:c996fda58ff2552d6ba834603c2e9e63",
|
||||
"n_fzje": "",
|
||||
"n_ccxzxje_gj": "",
|
||||
"n_sqbqse": "",
|
||||
"n_dzzm": "",
|
||||
"n_bqqpcje_level": "",
|
||||
"c_gkws_pjjg": "",
|
||||
"n_laay_tag": "",
|
||||
"n_jaay_tag": "",
|
||||
"n_ajbs": "b1802615be1d2dd9236192322b247c6d",
|
||||
"n_dzzm_tree": "",
|
||||
"n_sqzxbdje": "",
|
||||
"c_ah_ys": "",
|
||||
"c_dsrxx": [
|
||||
{
|
||||
"n_ssdw": "原告",
|
||||
"c_mc": "xxxxxx",
|
||||
"n_dsrlx": "企业组织"
|
||||
},
|
||||
{
|
||||
"n_ssdw": "被告",
|
||||
"c_mc": "xxx",
|
||||
"n_dsrlx": "自然人"
|
||||
}
|
||||
],
|
||||
"d_larq": "2023-02-06"
|
||||
}
|
||||
],
|
||||
"count": {}
|
||||
},
|
||||
"criminal": {
|
||||
"cases": [],
|
||||
"count": {}
|
||||
},
|
||||
"compensate": {
|
||||
"cases": [],
|
||||
"count": {}
|
||||
},
|
||||
"bankrupt": {
|
||||
"cases": [],
|
||||
"count": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
68
internal/domains/api/services/processors/2.md
Normal file
68
internal/domains/api/services/processors/2.md
Normal file
@@ -0,0 +1,68 @@
|
||||
## 返回字段说明
|
||||
|
||||
| 名称 | 说明 | 备注 |
|
||||
|--------|--------------------|------|
|
||||
| sxbzxr | 失信被执行人 | |
|
||||
| entout | 涉诉信息 | |
|
||||
| xgbzxr | 限高被执行人 | |
|
||||
|
||||
### sxbzxr参数
|
||||
| 名称 | 说明 | 备注 |
|
||||
|------------------|------------------------------|------|
|
||||
| yw | 生效法律文书确定的义务 | |
|
||||
| pjje_gj | 判决金额_估计 | |
|
||||
| xwqx | 失信被执行人行为具体情形 | |
|
||||
| id | 标识 | |
|
||||
| zxfy | 执行法院 | |
|
||||
| ah | 案号 | |
|
||||
| zxyjwh | 执行依据文号 | |
|
||||
| lxqk | 被执行人的履行情况 | |
|
||||
| zxyjdw | 出执行依据单位 | |
|
||||
| fbrq | 发布时间(日期) | |
|
||||
| xb | 性别 | |
|
||||
| larq | 立案日期 | |
|
||||
| sf | 省份 | |
|
||||
|
||||
### entout参数
|
||||
| 名称 | 说明 | 备注 |
|
||||
|--------------------------------|------------------------------|------|
|
||||
| civil | 案件名称 | 返回有案件的信息,如:<br>administrative/行政案<br>implement/执行案件<br>count/统计<br>preservation/非诉保全审查<br>crc/当事人变更码<br>civil/民事案件<br>criminal/刑事案件<br>cases_tree/串联树<br>bankrupt/强制清算与破产案件<br>jurisdict/管辖案件<br>compensate/赔偿案件 |
|
||||
| cases | 案件 | |
|
||||
| n_jabdje_gj_level | 结案标的金额估计等级 | |
|
||||
| n_jbfy_cj | 法院所属层级 | |
|
||||
| c_gkws_glah | 相关案件号 | |
|
||||
| n_jafs | 结案方式 | |
|
||||
| n_sjdwje | 实际到位金额 | |
|
||||
| n_sqzxbdje | 申请执行标的金额 | |
|
||||
| n_wzxje | 未执行金额 | |
|
||||
| n_sqbqse | 申请保全数额 | |
|
||||
| n_sqbqse_level | 申请保全数额等级 | |
|
||||
| n_jaay_tag | 结案案由标签 | |
|
||||
| n_qsbdje_gj_level | 起诉标的金额估计等级 | |
|
||||
| n_laay_tag | 立案案由标签 | |
|
||||
| n_pcpcje_level | 判处赔偿金额等级 | |
|
||||
| n_bqqpcje | 被请求赔偿金额 | |
|
||||
| n_pcpcje_gj_level | 判处赔偿金额估计等级 | |
|
||||
| n_dzzm | 定罪罪名 | |
|
||||
| n_ccxzxje_level | 财产刑执行金额等级 | |
|
||||
| n_ccxzxje_gj_level | 财产刑执行金额估计等级 | |
|
||||
| n_fzje | 犯罪金额 | |
|
||||
| n_pcpcje | 判处赔偿金额 | |
|
||||
| n_dzzm_tree | 定罪罪名树 | |
|
||||
| n_pcjg | 判处结果 | |
|
||||
| n_ccxzxje | 财产刑执行金额 | |
|
||||
| n_fzje_level | 犯罪金额等级 | |
|
||||
| n_ccxzxje_gj | 财产刑执行金额估计 | |
|
||||
| n_bqqpcje_level | 被请求赔偿金额等级 | |
|
||||
| n_ssdw | 诉讼地位 | |
|
||||
| d_jarq | 结案时间 | |
|
||||
| c_gkws_pjjg | 判决结果 | |
|
||||
| n_qsbdje | 起诉标的金额 | |
|
||||
| n_crc | 案件变更码 | |
|
||||
| c_ssdy | 所属地域 | |
|
||||
| n_ajjzjd | 案件进展阶段 | |
|
||||
| n_jaay | 结案案由 | |
|
||||
| n_ajlx | 案件类型 | |
|
||||
| c_ah_ys | 原审案号 | |
|
||||
| n_laay_tree | 立案案由详细 | |
|
||||
| n_ajbs | 案件
|
||||
@@ -32,6 +32,12 @@ func (cs *CombService) RegisterProcessor(apiCode string, processor processors.Pr
|
||||
cs.processorRegistry[apiCode] = processor
|
||||
}
|
||||
|
||||
// GetProcessor 获取处理器(用于内部调用)
|
||||
func (cs *CombService) GetProcessor(apiCode string) (processors.ProcessorFunc, bool) {
|
||||
processor, exists := cs.processorRegistry[apiCode]
|
||||
return processor, exists
|
||||
}
|
||||
|
||||
// ProcessCombRequest 处理组合包请求 - 实现 CombServiceInterface
|
||||
func (cs *CombService) ProcessCombRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies, packageCode string) (*processors.CombinedResult, error) {
|
||||
// 1. 根据组合包code获取产品信息
|
||||
|
||||
@@ -48,6 +48,7 @@ type baseProductData struct {
|
||||
RiskWarning riskWarning `json:"riskWarning"`
|
||||
StandLiveInfo standLiveInfo `json:"standLiveInfo"`
|
||||
VerifyRule string `json:"verifyRule"`
|
||||
MultCourtInfo multCourtInfo `json:"multCourtInfo"`
|
||||
}
|
||||
|
||||
// baseInfo 存放被查询人的基础身份信息
|
||||
@@ -171,6 +172,34 @@ type standLiveInfo struct {
|
||||
FinalAuthResult string `json:"finalAuthResult"`
|
||||
}
|
||||
|
||||
// multCourtInfo 司法风险核验产品,统一承载涉案/执行/失信/限高四类公告
|
||||
type multCourtInfo struct {
|
||||
LegalCasesFlag int `json:"legalCasesFlag"`
|
||||
LegalCases []multCaseItem `json:"legalCases"`
|
||||
ExecutionCasesFlag int `json:"executionCasesFlag"`
|
||||
ExecutionCases []multCaseItem `json:"executionCases"`
|
||||
DisinCasesFlag int `json:"disinCasesFlag"`
|
||||
DisinCases []multCaseItem `json:"disinCases"`
|
||||
LimitCasesFlag int `json:"limitCasesFlag"`
|
||||
LimitCases []multCaseItem `json:"limitCases"`
|
||||
}
|
||||
|
||||
// multCaseItem 司法各类公告的通用记录结构
|
||||
type multCaseItem struct {
|
||||
CaseNumber string `json:"caseNumber"`
|
||||
CaseType string `json:"caseType"`
|
||||
Court string `json:"court"`
|
||||
LitigantType string `json:"litigantType"`
|
||||
FilingTime string `json:"filingTime"`
|
||||
DisposalTime string `json:"disposalTime"`
|
||||
CaseStatus string `json:"caseStatus"`
|
||||
ExecutionAmount string `json:"executionAmount"`
|
||||
RepaidAmount string `json:"repaidAmount"`
|
||||
CaseReason string `json:"caseReason"`
|
||||
DisposalMethod string `json:"disposalMethod"`
|
||||
JudgmentResult string `json:"judgmentResult"`
|
||||
}
|
||||
|
||||
// --- FLXG7E8F ---
|
||||
|
||||
// judicialProductData 对应 FLXG7E8F 司法产品数据
|
||||
@@ -702,52 +731,60 @@ func buildBasicInfo(ctx context.Context, sourceCtx *sourceContext) reportBasicIn
|
||||
Details: carrierDetails,
|
||||
})
|
||||
|
||||
// 兼容处理:安全访问JudicialData
|
||||
var stat *lawsuitStat
|
||||
if sourceCtx != nil && sourceCtx.JudicialData != nil {
|
||||
stat = &sourceCtx.JudicialData.JudicialData.LawsuitStat
|
||||
}
|
||||
|
||||
totalCaseCount := 0
|
||||
totalCriminal := 0
|
||||
// 兼容处理:从DWBG8B4D.multCourtInfo获取司法记录条数,并按案件类型拆分
|
||||
totalExecution := 0
|
||||
if stat != nil {
|
||||
// 兼容处理:安全访问Cases数组
|
||||
totalCriminal = safeLen(stat.Criminal.Cases)
|
||||
totalCaseCount = totalCriminal + safeLen(stat.Civil.Cases) + safeLen(stat.Administrative.Cases) + safeLen(stat.Preservation.Cases) + safeLen(stat.Bankrupt.Cases)
|
||||
totalExecution = safeLen(stat.Implement.Cases)
|
||||
}
|
||||
|
||||
totalDishonest := 0
|
||||
totalRestriction := 0
|
||||
if sourceCtx != nil && sourceCtx.JudicialData != nil {
|
||||
totalDishonest = safeLen(sourceCtx.JudicialData.JudicialData.BreachCaseList)
|
||||
totalRestriction = safeLen(sourceCtx.JudicialData.JudicialData.ConsumptionRestrictionList)
|
||||
criminalCount := 0
|
||||
civilCount := 0
|
||||
administrativeCount := 0
|
||||
preservationCount := 0
|
||||
bankruptCount := 0
|
||||
|
||||
if sourceCtx != nil && sourceCtx.BaseData != nil {
|
||||
mc := sourceCtx.BaseData.MultCourtInfo
|
||||
totalExecution = safeLen(mc.ExecutionCases)
|
||||
totalDishonest = safeLen(mc.DisinCases)
|
||||
totalRestriction = safeLen(mc.LimitCases)
|
||||
|
||||
for _, c := range mc.LegalCases {
|
||||
switch strings.TrimSpace(c.CaseType) {
|
||||
case "刑事案件":
|
||||
criminalCount++
|
||||
case "民事案件":
|
||||
civilCount++
|
||||
case "行政案件":
|
||||
administrativeCount++
|
||||
case "保全审查":
|
||||
preservationCount++
|
||||
case "破产清算":
|
||||
bankruptCount++
|
||||
default:
|
||||
// 其它类型暂不单独展示,只参与是否有司法记录的判断
|
||||
civilCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if totalCaseCount > 0 || totalExecution > 0 || totalDishonest > 0 || totalRestriction > 0 {
|
||||
detailParts := make([]string, 0, 5)
|
||||
if stat != nil {
|
||||
addCaseDetail := func(label string, count int) {
|
||||
if count > 0 {
|
||||
detailParts = append(detailParts, fmt.Sprintf("%s%d条", label, count))
|
||||
}
|
||||
totalLegal := criminalCount + civilCount + administrativeCount + preservationCount + bankruptCount
|
||||
|
||||
if totalLegal > 0 || totalExecution > 0 || totalDishonest > 0 || totalRestriction > 0 {
|
||||
detailParts := make([]string, 0, 8)
|
||||
addCaseDetail := func(label string, count int) {
|
||||
if count > 0 {
|
||||
detailParts = append(detailParts, fmt.Sprintf("%s%d条", label, count))
|
||||
}
|
||||
addCaseDetail("刑事案件", safeLen(stat.Criminal.Cases))
|
||||
addCaseDetail("民事案件", safeLen(stat.Civil.Cases))
|
||||
addCaseDetail("行政案件", safeLen(stat.Administrative.Cases))
|
||||
addCaseDetail("非诉保全审查案件", safeLen(stat.Preservation.Cases))
|
||||
addCaseDetail("强制清算与破产案件", safeLen(stat.Bankrupt.Cases))
|
||||
}
|
||||
if totalExecution > 0 {
|
||||
detailParts = append(detailParts, fmt.Sprintf("执行案件%d条", totalExecution))
|
||||
}
|
||||
if totalDishonest > 0 {
|
||||
detailParts = append(detailParts, fmt.Sprintf("失信案件%d条", totalDishonest))
|
||||
}
|
||||
if totalRestriction > 0 {
|
||||
detailParts = append(detailParts, fmt.Sprintf("限高案件%d条", totalRestriction))
|
||||
}
|
||||
|
||||
addCaseDetail("刑事案件", criminalCount)
|
||||
addCaseDetail("民事案件", civilCount)
|
||||
addCaseDetail("行政案件", administrativeCount)
|
||||
addCaseDetail("保全审查案件", preservationCount)
|
||||
addCaseDetail("强制清算与破产案件", bankruptCount)
|
||||
addCaseDetail("执行案件", totalExecution)
|
||||
addCaseDetail("失信案件", totalDishonest)
|
||||
addCaseDetail("限高案件", totalRestriction)
|
||||
|
||||
details := buildCaseDetails(detailParts)
|
||||
verifications = append(verifications, verificationItem{
|
||||
Item: "法院信息",
|
||||
@@ -821,50 +858,78 @@ func buildRiskIdentification(ctx context.Context, sourceCtx *sourceContext) risk
|
||||
},
|
||||
}
|
||||
|
||||
// 兼容处理:安全访问JudicialData
|
||||
if sourceCtx == nil || sourceCtx.JudicialData == nil {
|
||||
log.Debug("JudicialData为空,返回空的风险识别数据",
|
||||
// 兼容处理:从DWBG8B4D.multCourtInfo获取司法信息
|
||||
if sourceCtx == nil || sourceCtx.BaseData == nil {
|
||||
log.Debug("BaseData为空,返回空的风险识别数据",
|
||||
zap.String("api_code", "COMBHZY2"),
|
||||
)
|
||||
return identification
|
||||
}
|
||||
|
||||
stat := sourceCtx.JudicialData.JudicialData.LawsuitStat
|
||||
mc := sourceCtx.BaseData.MultCourtInfo
|
||||
baseName := ""
|
||||
baseID := ""
|
||||
if sourceCtx.BaseData != nil {
|
||||
baseName = sourceCtx.BaseData.BaseInfo.Name
|
||||
baseID = sourceCtx.BaseData.BaseInfo.IdCard
|
||||
}
|
||||
baseName = sourceCtx.BaseData.BaseInfo.Name
|
||||
baseID = sourceCtx.BaseData.BaseInfo.IdCard
|
||||
|
||||
// 兼容处理:安全访问Cases数组
|
||||
// 涉案公告列表:直接使用multCourtInfo.legalCases
|
||||
caseRecords := make([]caseAnnouncementRecord, 0)
|
||||
if stat.Civil.Cases != nil {
|
||||
caseRecords = append(caseRecords, convertCaseAnnouncements(stat.Civil.Cases, "民事案件")...)
|
||||
}
|
||||
if stat.Criminal.Cases != nil {
|
||||
caseRecords = append(caseRecords, convertCaseAnnouncements(stat.Criminal.Cases, "刑事案件")...)
|
||||
}
|
||||
if stat.Administrative.Cases != nil {
|
||||
caseRecords = append(caseRecords, convertCaseAnnouncements(stat.Administrative.Cases, "行政案件")...)
|
||||
}
|
||||
if stat.Preservation.Cases != nil {
|
||||
caseRecords = append(caseRecords, convertCaseAnnouncements(stat.Preservation.Cases, "非诉保全审查")...)
|
||||
}
|
||||
if stat.Bankrupt.Cases != nil {
|
||||
caseRecords = append(caseRecords, convertCaseAnnouncements(stat.Bankrupt.Cases, "强制清算与破产")...)
|
||||
for _, c := range mc.LegalCases {
|
||||
record := caseAnnouncementRecord{
|
||||
CaseNumber: defaultIfEmpty(c.CaseNumber, "-"),
|
||||
CaseType: defaultIfEmpty(c.CaseType, "-"),
|
||||
FilingDate: defaultIfEmpty(c.FilingTime, ""),
|
||||
Authority: defaultIfEmpty(c.Court, ""),
|
||||
}
|
||||
caseRecords = append(caseRecords, record)
|
||||
}
|
||||
identification.CaseAnnouncements.Records = caseRecords
|
||||
|
||||
if stat.Implement.Cases != nil {
|
||||
identification.EnforcementAnnouncements.Records = convertEnforcementAnnouncements(stat.Implement.Cases)
|
||||
// 执行公告列表:multCourtInfo.executionCases
|
||||
enfRecords := make([]enforcementAnnouncementRecord, 0, len(mc.ExecutionCases))
|
||||
for _, c := range mc.ExecutionCases {
|
||||
amountStr := strings.TrimSpace(c.ExecutionAmount)
|
||||
targetAmount := "-"
|
||||
if amountStr != "" && amountStr != "-" {
|
||||
targetAmount = formatCurrencyYuan(parseFloatSafe(amountStr))
|
||||
}
|
||||
record := enforcementAnnouncementRecord{
|
||||
CaseNumber: defaultIfEmpty(c.CaseNumber, "-"),
|
||||
TargetAmount: targetAmount,
|
||||
FilingDate: defaultIfEmpty(c.FilingTime, ""),
|
||||
Court: defaultIfEmpty(c.Court, ""),
|
||||
Status: defaultIfEmpty(c.CaseStatus, "-"),
|
||||
}
|
||||
enfRecords = append(enfRecords, record)
|
||||
}
|
||||
if sourceCtx.JudicialData.JudicialData.BreachCaseList != nil {
|
||||
identification.DishonestAnnouncements.Records = convertDishonestAnnouncements(sourceCtx.JudicialData.JudicialData.BreachCaseList, baseName, baseID)
|
||||
identification.EnforcementAnnouncements.Records = enfRecords
|
||||
|
||||
// 失信公告列表:multCourtInfo.disinCases
|
||||
dishonestRecords := make([]dishonestAnnouncementRecord, 0, len(mc.DisinCases))
|
||||
for _, item := range mc.DisinCases {
|
||||
record := dishonestAnnouncementRecord{
|
||||
DishonestPerson: defaultIfEmpty(baseName, "-"),
|
||||
IdCard: defaultIfEmpty(baseID, "-"),
|
||||
Court: defaultIfEmpty(item.Court, ""),
|
||||
FilingDate: defaultIfEmpty(item.FilingTime, item.DisposalTime),
|
||||
PerformanceStatus: defaultIfEmpty(item.JudgmentResult, defaultIfEmpty(item.CaseStatus, "-")),
|
||||
}
|
||||
dishonestRecords = append(dishonestRecords, record)
|
||||
}
|
||||
if sourceCtx.JudicialData.JudicialData.ConsumptionRestrictionList != nil {
|
||||
identification.HighConsumptionRestrictionAnn.Records = convertConsumptionRestrictions(sourceCtx.JudicialData.JudicialData.ConsumptionRestrictionList, baseName, baseID)
|
||||
identification.DishonestAnnouncements.Records = dishonestRecords
|
||||
|
||||
// 限高公告列表:multCourtInfo.limitCases
|
||||
limitRecords := make([]highRestrictionAnnouncementRecord, 0, len(mc.LimitCases))
|
||||
for _, item := range mc.LimitCases {
|
||||
record := highRestrictionAnnouncementRecord{
|
||||
RestrictedPerson: defaultIfEmpty(baseName, "-"),
|
||||
IdCard: defaultIfEmpty(baseID, "-"),
|
||||
Court: defaultIfEmpty(item.Court, ""),
|
||||
StartDate: defaultIfEmpty(item.FilingTime, item.DisposalTime),
|
||||
Measure: "限制高消费",
|
||||
}
|
||||
limitRecords = append(limitRecords, record)
|
||||
}
|
||||
identification.HighConsumptionRestrictionAnn.Records = limitRecords
|
||||
|
||||
return identification
|
||||
}
|
||||
@@ -1299,13 +1364,13 @@ func gatherOtherRiskDetails(sourceCtx *sourceContext) string {
|
||||
if risk.VeryFrequentRentalApplications > 0 {
|
||||
hits = append(hits, "租赁机构申请次数极多")
|
||||
}
|
||||
// 兼容处理:安全访问JudicialData
|
||||
if sourceCtx != nil && sourceCtx.JudicialData != nil {
|
||||
stat := sourceCtx.JudicialData.JudicialData.LawsuitStat
|
||||
totalCase := safeLen(stat.Civil.Cases) + safeLen(stat.Criminal.Cases) + safeLen(stat.Administrative.Cases) + safeLen(stat.Preservation.Cases) + safeLen(stat.Bankrupt.Cases)
|
||||
totalExecution := safeLen(stat.Implement.Cases)
|
||||
totalDishonest := safeLen(sourceCtx.JudicialData.JudicialData.BreachCaseList)
|
||||
totalRestriction := safeLen(sourceCtx.JudicialData.JudicialData.ConsumptionRestrictionList)
|
||||
// 兼容处理:根据DWBG8B4D.multCourtInfo判断是否存在司法记录
|
||||
if sourceCtx != nil && sourceCtx.BaseData != nil {
|
||||
mc := sourceCtx.BaseData.MultCourtInfo
|
||||
totalCase := safeLen(mc.LegalCases)
|
||||
totalExecution := safeLen(mc.ExecutionCases)
|
||||
totalDishonest := safeLen(mc.DisinCases)
|
||||
totalRestriction := safeLen(mc.LimitCases)
|
||||
if totalCase > 0 || totalExecution > 0 || totalDishonest > 0 || totalRestriction > 0 {
|
||||
hits = append(hits, "存在司法风险记录")
|
||||
}
|
||||
@@ -1393,11 +1458,10 @@ func buildRuleHitBullet(summary reportSummary) (string, bool, bool) {
|
||||
}
|
||||
|
||||
func buildJudicialBullet(ctx *sourceContext) (string, bool, bool) {
|
||||
if ctx.JudicialData == nil {
|
||||
if ctx == nil || ctx.BaseData == nil {
|
||||
return "", false, false
|
||||
}
|
||||
|
||||
stat := ctx.JudicialData.JudicialData.LawsuitStat
|
||||
mc := ctx.BaseData.MultCourtInfo
|
||||
parts := make([]string, 0, 6)
|
||||
|
||||
addPart := func(label string, count int) {
|
||||
@@ -1406,12 +1470,10 @@ func buildJudicialBullet(ctx *sourceContext) (string, bool, bool) {
|
||||
}
|
||||
}
|
||||
|
||||
addPart("刑事案件", len(stat.Criminal.Cases))
|
||||
addPart("民事案件", len(stat.Civil.Cases))
|
||||
addPart("行政案件", len(stat.Administrative.Cases))
|
||||
addPart("执行案件", len(stat.Implement.Cases))
|
||||
addPart("失信记录", len(ctx.JudicialData.JudicialData.BreachCaseList))
|
||||
addPart("限高记录", len(ctx.JudicialData.JudicialData.ConsumptionRestrictionList))
|
||||
addPart("涉案公告", len(mc.LegalCases))
|
||||
addPart("执行案件", len(mc.ExecutionCases))
|
||||
addPart("失信记录", len(mc.DisinCases))
|
||||
addPart("限高记录", len(mc.LimitCases))
|
||||
|
||||
if len(parts) == 0 {
|
||||
return "", false, false
|
||||
|
||||
@@ -2,10 +2,17 @@ package processors
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"tyapi-server/internal/application/api/commands"
|
||||
"tyapi-server/internal/domains/api/repositories"
|
||||
"tyapi-server/internal/infrastructure/external/alicloud"
|
||||
"tyapi-server/internal/infrastructure/external/muzi"
|
||||
"tyapi-server/internal/infrastructure/external/huibo"
|
||||
"tyapi-server/internal/infrastructure/external/jiguang"
|
||||
"tyapi-server/internal/infrastructure/external/muzi"
|
||||
"tyapi-server/internal/infrastructure/external/nuoer"
|
||||
"tyapi-server/internal/infrastructure/external/shujubao"
|
||||
"tyapi-server/internal/infrastructure/external/shumai"
|
||||
"tyapi-server/internal/infrastructure/external/haiyuapi"
|
||||
"tyapi-server/internal/infrastructure/external/tianyancha"
|
||||
"tyapi-server/internal/infrastructure/external/westdex"
|
||||
"tyapi-server/internal/infrastructure/external/xingwei"
|
||||
@@ -27,6 +34,7 @@ type CallContext struct {
|
||||
// ProcessorDependencies 处理器依赖容器
|
||||
type ProcessorDependencies struct {
|
||||
WestDexService *westdex.WestDexService
|
||||
ShujubaoService *shujubao.ShujubaoService
|
||||
MuziService *muzi.MuziService
|
||||
YushanService *yushan.YushanService
|
||||
TianYanChaService *tianyancha.TianYanChaService
|
||||
@@ -34,15 +42,29 @@ type ProcessorDependencies struct {
|
||||
ZhichaService *zhicha.ZhichaService
|
||||
XingweiService *xingwei.XingweiService
|
||||
JiguangService *jiguang.JiguangService
|
||||
ShumaiService *shumai.ShumaiService
|
||||
HuiboService *huibo.HuiboService
|
||||
NuoerService *nuoer.NuoerService
|
||||
HaiyuapiService *haiyuapi.HaiyuapiService
|
||||
Validator interfaces.RequestValidator
|
||||
CombService CombServiceInterface // Changed to interface to break import cycle
|
||||
Options *commands.ApiCallOptions // 添加Options支持
|
||||
CallContext *CallContext // 添加CallApi调用上下文
|
||||
|
||||
// 企业报告记录仓储,用于持久化 QYGLJ1U9 生成的企业报告
|
||||
ReportRepo repositories.ReportRepository
|
||||
|
||||
// 企业报告 PDF 异步预生成(可为 nil)
|
||||
ReportPDFScheduler QYGLReportPDFScheduler
|
||||
|
||||
// APIPublicBaseURL 对外 API 根地址(无尾斜杠),用于 QYGL reportUrl 等
|
||||
APIPublicBaseURL string
|
||||
}
|
||||
|
||||
// NewProcessorDependencies 创建处理器依赖容器
|
||||
func NewProcessorDependencies(
|
||||
westDexService *westdex.WestDexService,
|
||||
shujubaoService *shujubao.ShujubaoService,
|
||||
muziService *muzi.MuziService,
|
||||
yushanService *yushan.YushanService,
|
||||
tianYanChaService *tianyancha.TianYanChaService,
|
||||
@@ -50,22 +72,37 @@ func NewProcessorDependencies(
|
||||
zhichaService *zhicha.ZhichaService,
|
||||
xingweiService *xingwei.XingweiService,
|
||||
jiguangService *jiguang.JiguangService,
|
||||
shumaiService *shumai.ShumaiService,
|
||||
huiboService *huibo.HuiboService,
|
||||
nuoerService *nuoer.NuoerService,
|
||||
haiyuapiService *haiyuapi.HaiyuapiService,
|
||||
validator interfaces.RequestValidator,
|
||||
combService CombServiceInterface, // Changed to interface
|
||||
reportRepo repositories.ReportRepository,
|
||||
reportPDFScheduler QYGLReportPDFScheduler,
|
||||
apiPublicBaseURL string,
|
||||
) *ProcessorDependencies {
|
||||
return &ProcessorDependencies{
|
||||
WestDexService: westDexService,
|
||||
MuziService: muziService,
|
||||
YushanService: yushanService,
|
||||
TianYanChaService: tianYanChaService,
|
||||
AlicloudService: alicloudService,
|
||||
ZhichaService: zhichaService,
|
||||
XingweiService: xingweiService,
|
||||
JiguangService: jiguangService,
|
||||
Validator: validator,
|
||||
CombService: combService,
|
||||
Options: nil, // 初始化为nil,在调用时设置
|
||||
CallContext: nil, // 初始化为nil,在调用时设置
|
||||
WestDexService: westDexService,
|
||||
ShujubaoService: shujubaoService,
|
||||
MuziService: muziService,
|
||||
YushanService: yushanService,
|
||||
TianYanChaService: tianYanChaService,
|
||||
AlicloudService: alicloudService,
|
||||
ZhichaService: zhichaService,
|
||||
XingweiService: xingweiService,
|
||||
JiguangService: jiguangService,
|
||||
ShumaiService: shumaiService,
|
||||
HuiboService: huiboService,
|
||||
NuoerService: nuoerService,
|
||||
HaiyuapiService: haiyuapiService,
|
||||
Validator: validator,
|
||||
CombService: combService,
|
||||
Options: nil, // 初始化为nil,在调用时设置
|
||||
CallContext: nil, // 初始化为nil,在调用时设置
|
||||
ReportRepo: reportRepo,
|
||||
ReportPDFScheduler: reportPDFScheduler,
|
||||
APIPublicBaseURL: apiPublicBaseURL,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
33
internal/domains/api/services/processors/dwbg/1.md
Normal file
33
internal/domains/api/services/processors/dwbg/1.md
Normal file
@@ -0,0 +1,33 @@
|
||||
|
|
||||
| 产品编码 | 产品名称 | 价格 |
|
||||
| :--- | :--- | :--- |
|
||||
|| mobile4Verify | 运营商状态查询 | ¥0.04 / 次 |
|
||||
|| mobileDuration | 手机入网时长 | ¥0.10 / 次 |
|
||||
|| mobile3Verify | 运营商三要素验证 | ¥0.15 / 次 |
|
||||
|| realNameAuth | 实名认证 | ¥0.02 / 次 |
|
||||
|| blackListV121_3 | 债务欺诈黑名单V3 | ¥0.22 / 次 |
|
||||
|| loanRiskTagV10 | 风险变量V10 | ¥0.38 / 次 |
|
||||
|| loanRiskTagV21 | 信用全景V21 | ¥0.30 / 次 |
|
||||
|| loanRiskTagV11 | 借贷意向查询 | ¥0.20 / 次 |
|
||||
|| loanRiskTagV12 | 特殊名单 | ¥0.00 / 次 |
|
||||
|| mobileRiskV709 | 投诉风险筛查V709 | ¥0.20 / 次 |
|
||||
|| blackListV110 | 特殊名单V110 | ¥0.20 / 次 |
|
||||
|| personalLawsuit_cv1 | 个人法院涉诉定制版 | ¥0.20 / 次 |
|
||||
|
||||
|
||||
|
||||
| 产品编码 | 产品名称 | 价格 ¥2.06 | API接口名称 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
|| YYSYE7V5 | 手机在网状态V即时版 | ¥0.03 / 次 | /v1/mobile_status/check |
|
||||
|| YYSYP0T4 | 手机号码在网时长V即时版 | ¥0.15 / 次 | /v2/mobile_online/check |
|
||||
|| YYSYK9R4 | 全网手机三要素验证 | ¥0.16 / 次 | /communication/personal/1979 |
|
||||
|| IVYZN2P8 | 公安二要素政务版 | ¥0.02 / 次 | /v4/id_card/check |
|
||||
|| JRZQV3HM | 债务欺诈黑名单V3 | ¥0.22 / 次 | blackListV121_3 |
|
||||
|| JRZQ4B6C | 探针C | ¥0.38 / 次 | loanRiskTagV10 |
|
||||
|| JRZQ5E9F | 借选指数 | ¥0.30 / 次 | loanRiskTagV21 |
|
||||
|| JRZQ3C7B | 借贷意向验证 | ¥0.20 / 次 | loanRiskTagV11 |
|
||||
|| JRZQV7MD | 特殊名单 | ¥0.00 / 次 | loanRiskTagV12 |
|
||||
|| JRZQVT43 | 投诉风险筛查V709(投诉黑名单) | ¥0.20 / 次 | mobileRiskV709 |
|
||||
|| JRZQV0MD | 行为黑名单 | ¥0.20 / 次 | blackListV110 |
|
||||
|| FLXG7E8F | 个人司法数据查询 | ¥0.20 / 次 | personalLawsuit_cv1 |
|
||||
|
||||
117
internal/domains/api/services/processors/dwbg/desensitize.js
Normal file
117
internal/domains/api/services/processors/dwbg/desensitize.js
Normal file
@@ -0,0 +1,117 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
// 读取JSON文件
|
||||
// 读取JSON文件
|
||||
const inputFile = path.join(__dirname, 'DWBG3BF9.json');
|
||||
const data = JSON.parse(fs.readFileSync(inputFile, 'utf8'));
|
||||
|
||||
// 姓名映射表(保持同一姓名脱敏后一致)
|
||||
const nameMap = {
|
||||
'何志勇': '何某某',
|
||||
'覃圣有': '覃某',
|
||||
'刘飞': '刘某某',
|
||||
'陈波': '陈某某',
|
||||
'覃小群': '覃某某',
|
||||
'陈观海': '陈某某',
|
||||
'刘国富': '刘某某'
|
||||
};
|
||||
|
||||
// 脱敏函数
|
||||
function desensitizeName(name) {
|
||||
if (nameMap[name]) {
|
||||
return nameMap[name];
|
||||
}
|
||||
// 对于未知的姓名,保留姓氏,名字用星号代替
|
||||
if (name && name.length > 0) {
|
||||
const surname = name[0];
|
||||
return surname + '某某';
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
function desensitizeIdCard(idCard) {
|
||||
if (!idCard || idCard.length !== 18) return idCard;
|
||||
return idCard.substring(0, 6) + '********' + idCard.substring(14);
|
||||
}
|
||||
|
||||
function desensitizeMobile(mobile) {
|
||||
if (!mobile || mobile.length !== 11) return mobile;
|
||||
return mobile.substring(0, 3) + '****' + mobile.substring(7);
|
||||
}
|
||||
|
||||
function desensitizeText(text) {
|
||||
if (!text || typeof text !== 'string') return text;
|
||||
let result = text;
|
||||
// 替换所有出现的人名
|
||||
for (const [realName, maskedName] of Object.entries(nameMap)) {
|
||||
// 替换姓名
|
||||
const regex1 = new RegExp(realName, 'g');
|
||||
result = result.replace(regex1, maskedName);
|
||||
// 替换姓名+某的形式(如:何志某 -> 何某某某)
|
||||
const regex2 = new RegExp(realName.substring(0, realName.length - 1) + '某', 'g');
|
||||
result = result.replace(regex2, maskedName);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// 递归遍历对象进行脱敏
|
||||
function desensitizeObject(obj) {
|
||||
if (obj === null || typeof obj !== 'object') {
|
||||
return obj;
|
||||
}
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map(item => desensitizeObject(item));
|
||||
}
|
||||
|
||||
const result = {};
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
switch (key) {
|
||||
case 'name':
|
||||
result[key] = desensitizeName(value);
|
||||
break;
|
||||
case 'id_card':
|
||||
result[key] = desensitizeIdCard(value);
|
||||
break;
|
||||
case 'mobile':
|
||||
result[key] = desensitizeMobile(value);
|
||||
break;
|
||||
case 'c_mc':
|
||||
// 当事人姓名
|
||||
result[key] = desensitizeName(value);
|
||||
break;
|
||||
case 'c_gkws_dsr':
|
||||
case 'c_gkws_pjjg':
|
||||
// 判决书内容中的文本
|
||||
result[key] = desensitizeText(value);
|
||||
break;
|
||||
default:
|
||||
result[key] = desensitizeObject(value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// 执行脱敏
|
||||
const desensitizedData = desensitizeObject(data);
|
||||
|
||||
// 保存脱敏后的文件
|
||||
const outputFile = path.join(__dirname, 'DWBG3BF9_desensitized.json');
|
||||
fs.writeFileSync(outputFile, JSON.stringify(desensitizedData, null, 2), 'utf8');
|
||||
|
||||
console.log('脱敏完成!');
|
||||
console.log('原始文件:', inputFile);
|
||||
console.log('脱敏后文件:', outputFile);
|
||||
|
||||
// 显示脱敏摘要
|
||||
console.log('\n脱敏摘要:');
|
||||
console.log('- 姓名:已脱敏(保留姓氏)');
|
||||
console.log('- 身份证号:已脱敏(保留前6位和后4位)');
|
||||
console.log('- 手机号:已脱敏(保留前3位和后4位)');
|
||||
console.log('- 判决书文本中的姓名:已批量替换');
|
||||
@@ -0,0 +1,328 @@
|
||||
package dwbg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/shared/logger"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const dwbg3BF9FieldRA = "RA"
|
||||
|
||||
// dwbg3BF9OutputField 最终扁平结构中的单个顶层字段定义
|
||||
type dwbg3BF9OutputField struct {
|
||||
Key string // 顶层 JSON key
|
||||
ApiCode string // 子产品编码;RA 由聚合计算,无 ApiCode
|
||||
Source string // 数据来源方法
|
||||
}
|
||||
|
||||
// dwbg3BF9OutputSchema DWBG3BF9 最终返回扁平结构(顶层共 13 个 key)
|
||||
//
|
||||
// {
|
||||
// "RA": { ra_score, ra_level, ra_credit_score, ra_verify_score, ra_credit_risk_index, ra_performance_amt_index, ra_performance_cnt_index },
|
||||
// "presence": { ... }, // YYSYE7V5
|
||||
// "duration": { ... }, // YYSYP0T4
|
||||
// "triple": { ... }, // YYSYK9R4
|
||||
// "identity": { ... }, // IVYZN2P8
|
||||
// "fraud": { ... }, // JRZQV3HM
|
||||
// "probe": { ... }, // JRZQ4B6C
|
||||
// "rating": { ... }, // JRZQ5E9F
|
||||
// "intent": { ... }, // JRZQ3C7B
|
||||
// "special": { ... }, // JRZQV7MD
|
||||
// "complaint": { ... }, // JRZQVT43
|
||||
// "behavior": { ... }, // JRZQV0MD
|
||||
// "judicial": { ... }, // FLXG7E8F
|
||||
// }
|
||||
var dwbg3BF9OutputSchema = []dwbg3BF9OutputField{
|
||||
{Key: dwbg3BF9FieldRA, Source: "buildDWBG3BF9RA"},
|
||||
{Key: "presence", ApiCode: "YYSYE7V5", Source: "ProcessYYSYE7V5Request"},
|
||||
{Key: "duration", ApiCode: "YYSYP0T4", Source: "ProcessYYSYP0T4Request"},
|
||||
{Key: "triple", ApiCode: "YYSYK9R4", Source: "ProcessYYSYK9R4Request"},
|
||||
{Key: "identity", ApiCode: "IVYZN2P8", Source: "ProcessIVYZN2P8Request"},
|
||||
{Key: "fraud", ApiCode: "JRZQV3HM", Source: "ProcessJRZQV3HMRequest"},
|
||||
{Key: "probe", ApiCode: "JRZQ4B6C", Source: "ProcessJRZQ4B6CRequest"},
|
||||
{Key: "rating", ApiCode: "JRZQ5E9F", Source: "ProcessJRZQ5E9FRequest"},
|
||||
{Key: "intent", ApiCode: "JRZQ3C7B", Source: "ProcessJRZQ3C7BRequest"},
|
||||
{Key: "special", ApiCode: "JRZQV7MD", Source: "ProcessJRZQV7MDRequest"},
|
||||
{Key: "complaint", ApiCode: "JRZQVT43", Source: "ProcessJRZQVT43Request"},
|
||||
{Key: "behavior", ApiCode: "JRZQV0MD", Source: "ProcessJRZQV0MDRequest"},
|
||||
{Key: "judicial", ApiCode: "FLXG7E8F", Source: "ProcessFLXG7E8FRequest"},
|
||||
}
|
||||
|
||||
// dwbg3BF9OutputFieldOrder 最终 JSON 顶层字段顺序
|
||||
var dwbg3BF9OutputFieldOrder = func() []string {
|
||||
order := make([]string, len(dwbg3BF9OutputSchema))
|
||||
for i, f := range dwbg3BF9OutputSchema {
|
||||
order[i] = f.Key
|
||||
}
|
||||
return order
|
||||
}()
|
||||
|
||||
// dwbg3BF9SubProductFieldNames 子产品编码 → 响应字段名
|
||||
var dwbg3BF9SubProductFieldNames = func() map[string]string {
|
||||
m := make(map[string]string, len(dwbg3BF9OutputSchema)-1)
|
||||
for _, f := range dwbg3BF9OutputSchema {
|
||||
if f.ApiCode == "" {
|
||||
continue
|
||||
}
|
||||
m[f.ApiCode] = f.Key
|
||||
}
|
||||
return m
|
||||
}()
|
||||
|
||||
// dwbg3BF9StripKeys 汇总报告中需剔除的上游流水号/追踪类字段
|
||||
var dwbg3BF9StripKeys = map[string]struct{}{
|
||||
"order_no": {},
|
||||
"orderNo": {},
|
||||
}
|
||||
|
||||
// stripDWBG3BF9MetaKeys 递归剔除子接口返回中的流水号等元数据字段
|
||||
func stripDWBG3BF9MetaKeys(v interface{}) interface{} {
|
||||
switch val := v.(type) {
|
||||
case map[string]interface{}:
|
||||
out := make(map[string]interface{}, len(val))
|
||||
for k, item := range val {
|
||||
if _, drop := dwbg3BF9StripKeys[k]; drop {
|
||||
continue
|
||||
}
|
||||
out[k] = stripDWBG3BF9MetaKeys(item)
|
||||
}
|
||||
return out
|
||||
case []interface{}:
|
||||
out := make([]interface{}, len(val))
|
||||
for i, item := range val {
|
||||
out[i] = stripDWBG3BF9MetaKeys(item)
|
||||
}
|
||||
return out
|
||||
default:
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// ProcessDWBG3BF9Request DWBG3BF9 API处理方法 - 个人风险档案
|
||||
// 并发调用子产品处理器,将各子接口返回数据以大数据英文字段名拼接融合为一个对象
|
||||
func ProcessDWBG3BF9Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.DWBG3BF9Req
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
log := logger.GetGlobalLogger()
|
||||
log.Info("开始处理个人风险档案请求",
|
||||
zap.String("name", paramsDto.Name),
|
||||
zap.String("id_card", maskIDCard(paramsDto.IDCard)),
|
||||
zap.String("mobile_no", maskMobile(paramsDto.MobileNo)),
|
||||
)
|
||||
|
||||
subProducts := collectDWBG3BF9SubProducts(ctx, paramsDto, deps, log)
|
||||
result := buildDWBG3BF9Response(subProducts)
|
||||
|
||||
respBytes, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
log.Info("个人风险档案处理完成", zap.Int("field_count", len(result)))
|
||||
return respBytes, nil
|
||||
}
|
||||
|
||||
// buildDWBG3BF9Response 按 dwbg3BF9OutputSchema 构造最终扁平返回结构
|
||||
func buildDWBG3BF9Response(subProducts map[string]interface{}) map[string]interface{} {
|
||||
result := make(map[string]interface{}, len(dwbg3BF9OutputSchema))
|
||||
for _, field := range dwbg3BF9OutputSchema {
|
||||
if field.Key == dwbg3BF9FieldRA {
|
||||
result[field.Key] = buildDWBG3BF9RA(subProducts)
|
||||
continue
|
||||
}
|
||||
if v, ok := subProducts[field.Key]; ok {
|
||||
result[field.Key] = v
|
||||
} else {
|
||||
result[field.Key] = nil
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
type dwbg3bf9APICall struct {
|
||||
apiCode string
|
||||
fieldName string
|
||||
params map[string]interface{}
|
||||
}
|
||||
|
||||
func collectDWBG3BF9SubProducts(
|
||||
ctx context.Context,
|
||||
params dto.DWBG3BF9Req,
|
||||
deps *processors.ProcessorDependencies,
|
||||
log *zap.Logger,
|
||||
) map[string]interface{} {
|
||||
apiCalls := []dwbg3bf9APICall{
|
||||
{apiCode: "YYSYE7V5", fieldName: dwbg3BF9SubProductFieldNames["YYSYE7V5"], params: map[string]interface{}{"mobile_no": params.MobileNo}},
|
||||
{apiCode: "YYSYP0T4", fieldName: dwbg3BF9SubProductFieldNames["YYSYP0T4"], params: map[string]interface{}{"mobile_no": params.MobileNo}},
|
||||
{
|
||||
apiCode: "YYSYK9R4",
|
||||
fieldName: dwbg3BF9SubProductFieldNames["YYSYK9R4"],
|
||||
params: map[string]interface{}{
|
||||
"mobile_no": params.MobileNo,
|
||||
"id_card": params.IDCard,
|
||||
"name": params.Name,
|
||||
},
|
||||
},
|
||||
{
|
||||
apiCode: "IVYZN2P8",
|
||||
fieldName: dwbg3BF9SubProductFieldNames["IVYZN2P8"],
|
||||
params: map[string]interface{}{
|
||||
"id_card": params.IDCard,
|
||||
"name": params.Name,
|
||||
},
|
||||
},
|
||||
{
|
||||
apiCode: "JRZQV3HM",
|
||||
fieldName: dwbg3BF9SubProductFieldNames["JRZQV3HM"],
|
||||
params: map[string]interface{}{
|
||||
"id_card": params.IDCard,
|
||||
"name": params.Name,
|
||||
"mobile_no": params.MobileNo,
|
||||
},
|
||||
},
|
||||
{
|
||||
apiCode: "JRZQ4B6C",
|
||||
fieldName: dwbg3BF9SubProductFieldNames["JRZQ4B6C"],
|
||||
params: map[string]interface{}{
|
||||
"mobile_no": params.MobileNo,
|
||||
"id_card": params.IDCard,
|
||||
"name": params.Name,
|
||||
"authorized": "1",
|
||||
},
|
||||
},
|
||||
{
|
||||
apiCode: "JRZQ5E9F",
|
||||
fieldName: dwbg3BF9SubProductFieldNames["JRZQ5E9F"],
|
||||
params: map[string]interface{}{
|
||||
"mobile_no": params.MobileNo,
|
||||
"id_card": params.IDCard,
|
||||
"name": params.Name,
|
||||
"authorized": "1",
|
||||
},
|
||||
},
|
||||
{
|
||||
apiCode: "JRZQ3C7B",
|
||||
fieldName: dwbg3BF9SubProductFieldNames["JRZQ3C7B"],
|
||||
params: map[string]interface{}{
|
||||
"mobile_no": params.MobileNo,
|
||||
"id_card": params.IDCard,
|
||||
"name": params.Name,
|
||||
"authorized": "1",
|
||||
},
|
||||
},
|
||||
{
|
||||
apiCode: "JRZQV7MD",
|
||||
fieldName: dwbg3BF9SubProductFieldNames["JRZQV7MD"],
|
||||
params: map[string]interface{}{
|
||||
"id_card": params.IDCard,
|
||||
"name": params.Name,
|
||||
"mobile_no": params.MobileNo,
|
||||
},
|
||||
},
|
||||
{
|
||||
apiCode: "JRZQVT43",
|
||||
fieldName: dwbg3BF9SubProductFieldNames["JRZQVT43"],
|
||||
params: map[string]interface{}{
|
||||
"id_card": params.IDCard,
|
||||
"name": params.Name,
|
||||
"mobile_no": params.MobileNo,
|
||||
},
|
||||
},
|
||||
{
|
||||
apiCode: "JRZQV0MD",
|
||||
fieldName: dwbg3BF9SubProductFieldNames["JRZQV0MD"],
|
||||
params: map[string]interface{}{
|
||||
"id_card": params.IDCard,
|
||||
"name": params.Name,
|
||||
"mobile_no": params.MobileNo,
|
||||
},
|
||||
},
|
||||
{
|
||||
apiCode: "FLXG7E8F",
|
||||
fieldName: dwbg3BF9SubProductFieldNames["FLXG7E8F"],
|
||||
params: map[string]interface{}{
|
||||
"name": params.Name,
|
||||
"id_card": params.IDCard,
|
||||
"mobile_no": params.MobileNo,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
type callResult struct {
|
||||
fieldName string
|
||||
data interface{}
|
||||
err error
|
||||
}
|
||||
|
||||
results := make(chan callResult, len(apiCalls))
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for _, apiCall := range apiCalls {
|
||||
wg.Add(1)
|
||||
go func(ac dwbg3bf9APICall) {
|
||||
defer wg.Done()
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Error("调用子产品处理器时发生panic",
|
||||
zap.String("api_code", ac.apiCode),
|
||||
zap.String("field_name", ac.fieldName),
|
||||
zap.Any("panic", r),
|
||||
)
|
||||
results <- callResult{ac.fieldName, nil, fmt.Errorf("处理器panic: %v", r)}
|
||||
}
|
||||
}()
|
||||
|
||||
paramsBytes, err := json.Marshal(ac.params)
|
||||
if err != nil {
|
||||
results <- callResult{ac.fieldName, nil, err}
|
||||
return
|
||||
}
|
||||
|
||||
data, err := callProcessor(ctx, ac.apiCode, paramsBytes, deps)
|
||||
results <- callResult{ac.fieldName, data, err}
|
||||
}(apiCall)
|
||||
}
|
||||
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(results)
|
||||
}()
|
||||
|
||||
output := make(map[string]interface{}, len(apiCalls))
|
||||
successCount := 0
|
||||
for result := range results {
|
||||
if result.err != nil {
|
||||
log.Warn("子产品调用失败,该字段置空",
|
||||
zap.String("field_name", result.fieldName),
|
||||
zap.Error(result.err),
|
||||
)
|
||||
output[result.fieldName] = nil
|
||||
continue
|
||||
}
|
||||
output[result.fieldName] = stripDWBG3BF9MetaKeys(result.data)
|
||||
successCount++
|
||||
}
|
||||
|
||||
log.Info("子产品调用完成",
|
||||
zap.Int("total", len(apiCalls)),
|
||||
zap.Int("success", successCount),
|
||||
zap.Int("failed", len(apiCalls)-successCount),
|
||||
)
|
||||
|
||||
return output
|
||||
}
|
||||
486
internal/domains/api/services/processors/dwbg/dwbg3bf9_ra.go
Normal file
486
internal/domains/api/services/processors/dwbg/dwbg3bf9_ra.go
Normal file
@@ -0,0 +1,486 @@
|
||||
package dwbg
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const raScoreMax = 1000
|
||||
|
||||
const (
|
||||
raWeightVerify = 0.10
|
||||
raWeightJudicialBase = 0.50
|
||||
raWeightCreditBase = 0.40
|
||||
raHighRiskThreshold = 400
|
||||
raForcedFScoreCap = 500 // 双高风险强制 F 档时,ra_score 上限(保留加权分,不置 0)
|
||||
raForcedFTotalHitThreshold = 5 // 司法 + 借贷命中总条数须超过此值才触发强制 F
|
||||
raFraudPointsPerHit = 23 // 欺诈每命中一项扣分(非整齐倍数)
|
||||
|
||||
// 司法涉诉扣分系数(件数/条数 × 系数,带非整齐上限)
|
||||
raJudicialLawsuitTotalPerCase = 47
|
||||
raJudicialLawsuitTotalCap = 387
|
||||
raJudicialLawsuitWeiPerCase = 38
|
||||
raJudicialLawsuitWeiCap = 293
|
||||
raJudicialLawsuitBeigaoPerCase = 33
|
||||
raJudicialLawsuitBeigaoCap = 247
|
||||
raJudicialBreachPerCase = 143
|
||||
raJudicialBreachCap = 437
|
||||
raJudicialConsumptionPerCase = 187
|
||||
raJudicialConsumptionCap = 413
|
||||
|
||||
// 借贷维度扣分
|
||||
raCreditOverduePoints = 287
|
||||
raCreditSleepPoints = 143
|
||||
raCreditPerformancePoints = 97
|
||||
raCreditProbeHitPoints = 103
|
||||
raCreditIntentReject = 387
|
||||
raCreditIntentReview = 237
|
||||
raCreditIntentWeightMult = 4 // Rule_final_weight × 4
|
||||
raCreditIntentWeightCap = 243
|
||||
|
||||
// 身份核验扣分
|
||||
raVerifyMismatchPoints = 387
|
||||
raVerifyPresenceDesc = 73
|
||||
raVerifyPresenceStatus = 47
|
||||
|
||||
xypModelScoreMin = 350
|
||||
xypModelScoreRiskThreshold = 650 // [350,950] 区间中位,低于此值视为高风险
|
||||
xypModelScoreRiskCapPerField = 97 // 单项上限,三项合计最高 291
|
||||
)
|
||||
|
||||
// buildDWBG3BF9RA 构建顶层 RA 总体安全评估(千分制,分值越高越安全)
|
||||
//
|
||||
// 输出字段(对外):
|
||||
// - ra_score / ra_level → 综合安全分与等级
|
||||
// - ra_credit_score → 借贷维度安全分(参与加权)
|
||||
// - ra_verify_score → 个人身份区安全分(参与加权)
|
||||
// - ra_credit_risk_index → 信用风险指数(越高风险越大,与 ra_score 同向)
|
||||
// - ra_performance_amt_index → 履约金额综合指数(越高越好)
|
||||
// - ra_performance_cnt_index → 履约笔数综合指数(越高越好)
|
||||
// 内部仍计算 fraud/judicial 分用于 ra_score 加权,但不对外返回。
|
||||
func buildDWBG3BF9RA(data map[string]interface{}) map[string]interface{} {
|
||||
creditScore := calcRACreditScore(data)
|
||||
judicialScore := calcRAJudicialScore(data)
|
||||
verifyScore := calcRAVerifyScore(data)
|
||||
|
||||
total := calcRAWightedScore(verifyScore, judicialScore, creditScore)
|
||||
level := raLevelFromScore(total)
|
||||
if isRAForcedFGrade(data, judicialScore, creditScore) {
|
||||
level = "F"
|
||||
total = clampRAInt(total, 0, raForcedFScoreCap)
|
||||
}
|
||||
|
||||
return map[string]interface{}{
|
||||
"ra_score": total,
|
||||
"ra_level": level,
|
||||
"ra_credit_score": creditScore,
|
||||
"ra_verify_score": verifyScore,
|
||||
"ra_credit_risk_index": calcRACreditRiskIndex(data, creditScore),
|
||||
"ra_performance_amt_index": calcRAPerformanceAmtIndex(data),
|
||||
"ra_performance_cnt_index": calcRAPerformanceCntIndex(data),
|
||||
}
|
||||
}
|
||||
|
||||
// calcRAWightedScore 三维度动态加权汇总(身份 10% 固定 + 司法/借贷动态 90%)
|
||||
func calcRAWightedScore(verifyScore, judicialScore, creditScore int) int {
|
||||
wJudicial, wCredit := calcRADynamicWeights(judicialScore, creditScore)
|
||||
total := int(math.Round(
|
||||
float64(verifyScore)*raWeightVerify +
|
||||
float64(judicialScore)*wJudicial +
|
||||
float64(creditScore)*wCredit,
|
||||
))
|
||||
return clampRAInt(total, 0, raScoreMax)
|
||||
}
|
||||
|
||||
// calcRADynamicWeights 按司法/借贷风险场景分配剩余 90% 权重(身份固定 10%)
|
||||
func calcRADynamicWeights(judicialScore, creditScore int) (wJudicial, wCredit float64) {
|
||||
hasJudicialRisk := judicialScore < raScoreMax
|
||||
hasCreditRisk := creditScore < raScoreMax
|
||||
|
||||
switch {
|
||||
case hasJudicialRisk && hasCreditRisk:
|
||||
return 0.70, 0.20
|
||||
case hasJudicialRisk:
|
||||
return 0.65, 0.25
|
||||
case hasCreditRisk:
|
||||
return 0.40, 0.50
|
||||
default:
|
||||
return raWeightJudicialBase, raWeightCreditBase
|
||||
}
|
||||
}
|
||||
|
||||
// isRAForcedFGrade 司法 + 借贷双重高风险且命中总条数 > 5 时强制 F 档
|
||||
func isRAForcedFGrade(data map[string]interface{}, judicialScore, creditScore int) bool {
|
||||
if judicialScore > raHighRiskThreshold || creditScore > raHighRiskThreshold {
|
||||
return false
|
||||
}
|
||||
totalHits := calcRAJudicialHitCount(data) + calcRACreditHitCount(data)
|
||||
return totalHits > raForcedFTotalHitThreshold
|
||||
}
|
||||
|
||||
// calcRAJudicialHitCount 统计司法维度命中条数(涉诉件数 + 失信条数 + 限高条数)
|
||||
func calcRAJudicialHitCount(data map[string]interface{}) int {
|
||||
judicial := raAsMap(data["judicial"])
|
||||
if judicial == nil {
|
||||
return 0
|
||||
}
|
||||
judicialData := raAsMap(judicial["judicial_data"])
|
||||
if judicialData == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
hits := 0
|
||||
if lawsuitStat := raAsMap(judicialData["lawsuitStat"]); lawsuitStat != nil {
|
||||
for _, section := range lawsuitStat {
|
||||
sectionMap := raAsMap(section)
|
||||
if sectionMap == nil {
|
||||
continue
|
||||
}
|
||||
count := raAsMap(sectionMap["count"])
|
||||
if count == nil {
|
||||
continue
|
||||
}
|
||||
hits += raAsInt(count["count_total"])
|
||||
}
|
||||
}
|
||||
hits += len(raAsSlice(judicialData["breachCaseList"]))
|
||||
hits += len(raAsSlice(judicialData["consumptionRestrictionList"]))
|
||||
return hits
|
||||
}
|
||||
|
||||
// calcRACreditHitCount 统计借贷维度命中条数(探针 / 意向 / 借选模型分各算 1 条)
|
||||
func calcRACreditHitCount(data map[string]interface{}) int {
|
||||
hits := 0
|
||||
|
||||
if probe := raAsMap(data["probe"]); probe != nil {
|
||||
if raAsString(probe["currently_overdue"]) == "1" {
|
||||
hits++
|
||||
}
|
||||
if raAsString(probe["acc_sleep"]) == "1" {
|
||||
hits++
|
||||
}
|
||||
if raAsString(probe["currently_performance"]) == "0" {
|
||||
hits++
|
||||
}
|
||||
if raAsString(probe["result_code"]) == "1" {
|
||||
hits++
|
||||
}
|
||||
}
|
||||
|
||||
if intent := raAsMap(data["intent"]); intent != nil {
|
||||
switch raAsString(intent["Rule_final_decision"]) {
|
||||
case "Reject", "Review":
|
||||
hits++
|
||||
}
|
||||
}
|
||||
|
||||
if rating := raAsMap(data["rating"]); rating != nil {
|
||||
for _, key := range []string{"xyp_model_score_high", "xyp_model_score_mid", "xyp_model_score_low"} {
|
||||
score := raXypModelScore(rating[key])
|
||||
if score >= xypModelScoreMin && score < xypModelScoreRiskThreshold {
|
||||
hits++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hits
|
||||
}
|
||||
|
||||
// raLevelFromScore 由 ra_score 映射等级(千分制,越高越安全,每档 100 分)
|
||||
// A: 900-1000 B: 800-899 C: 700-799 D: 600-699 E: 500-599 F: 0-499
|
||||
func raLevelFromScore(score int) string {
|
||||
switch {
|
||||
case score >= 900:
|
||||
return "A"
|
||||
case score >= 800:
|
||||
return "B"
|
||||
case score >= 700:
|
||||
return "C"
|
||||
case score >= 600:
|
||||
return "D"
|
||||
case score >= 500:
|
||||
return "E"
|
||||
default:
|
||||
return "F"
|
||||
}
|
||||
}
|
||||
|
||||
// toRASafetyScore 将风险扣分转为安全分:安全分 = 1000 - 风险扣分
|
||||
func toRASafetyScore(riskPoints int) int {
|
||||
return clampRAInt(raScoreMax-riskPoints, 0, raScoreMax)
|
||||
}
|
||||
|
||||
// calcRAFraudScore 欺诈/黑名单子维度安全分(来源:calcRAFraudRiskPoints)
|
||||
// 统计方式:满分 1000,根据 behavior/complaint/fraud/special 风险信号扣分后取补集;同时计入 ra_verify_score
|
||||
func calcRAFraudScore(data map[string]interface{}) int {
|
||||
return toRASafetyScore(calcRAFraudRiskPoints(data))
|
||||
}
|
||||
|
||||
// calcRAFraudRiskPoints 统计欺诈维度风险扣分(计入个人身份区,分值越高代表越不安全)
|
||||
// 每命中一项扣 raFraudPointsPerHit(23)分,多项独立累加
|
||||
func calcRAFraudRiskPoints(data map[string]interface{}) int {
|
||||
hits := 0
|
||||
|
||||
// 来源子字段 behavior(JRZQV0MD 行为黑名单)
|
||||
if behavior := raAsMap(data["behavior"]); behavior != nil {
|
||||
if result := raAsMap(behavior["result"]); result != nil {
|
||||
if raAsString(result["black_list"]) == "1" {
|
||||
hits++
|
||||
}
|
||||
for k, v := range result {
|
||||
if strings.HasPrefix(k, "black_tag") && raAsString(v) == "1" {
|
||||
hits++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 来源子字段 complaint(JRZQVT43 投诉风险筛查)
|
||||
if complaint := raAsMap(data["complaint"]); complaint != nil {
|
||||
if result := raAsMap(complaint["result"]); result != nil {
|
||||
if raAsInt(result["score"]) > 0 {
|
||||
hits++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 来源子字段 fraud(JRZQV3HM 债务欺诈黑名单)
|
||||
if fraud := raAsMap(data["fraud"]); fraud != nil {
|
||||
if raAsInt(fraud["hit"]) == 1 || raAsString(fraud["hit"]) == "1" {
|
||||
hits++
|
||||
}
|
||||
}
|
||||
|
||||
// 来源子字段 special(JRZQV7MD 特殊名单)
|
||||
if special := raAsMap(data["special"]); special != nil && len(special) > 0 {
|
||||
switch raAsString(special["Rule_final_decision"]) {
|
||||
case "Reject", "Review":
|
||||
hits++
|
||||
}
|
||||
}
|
||||
|
||||
return clampRAInt(hits*raFraudPointsPerHit, 0, raScoreMax)
|
||||
}
|
||||
|
||||
// calcRACreditScore 借贷/逾期维度安全分(来源:calcRACreditRiskPoints)
|
||||
// 统计方式:满分 1000,根据 probe/intent/rating 风险信号扣分后取补集
|
||||
func calcRACreditScore(data map[string]interface{}) int {
|
||||
return toRASafetyScore(calcRACreditRiskPoints(data))
|
||||
}
|
||||
|
||||
// calcRACreditRiskPoints 统计借贷维度风险扣分
|
||||
func calcRACreditRiskPoints(data map[string]interface{}) int {
|
||||
risk := 0
|
||||
|
||||
// 来源子字段 probe(JRZQ4B6C 探针C)
|
||||
if probe := raAsMap(data["probe"]); probe != nil {
|
||||
if raAsString(probe["currently_overdue"]) == "1" {
|
||||
risk += raCreditOverduePoints
|
||||
}
|
||||
if raAsString(probe["acc_sleep"]) == "1" {
|
||||
risk += raCreditSleepPoints
|
||||
}
|
||||
if raAsString(probe["currently_performance"]) == "0" {
|
||||
risk += raCreditPerformancePoints
|
||||
}
|
||||
if raAsString(probe["result_code"]) == "1" {
|
||||
risk += raCreditProbeHitPoints
|
||||
}
|
||||
}
|
||||
|
||||
// 来源子字段 intent(JRZQ3C7B 借贷意向验证)
|
||||
if intent := raAsMap(data["intent"]); intent != nil {
|
||||
switch raAsString(intent["Rule_final_decision"]) {
|
||||
case "Reject":
|
||||
risk += raCreditIntentReject
|
||||
case "Review":
|
||||
risk += raCreditIntentReview
|
||||
}
|
||||
weight := raAsInt(intent["Rule_final_weight"])
|
||||
if weight > 0 {
|
||||
risk += clampRAInt(weight*raCreditIntentWeightMult, 0, raCreditIntentWeightCap)
|
||||
}
|
||||
}
|
||||
|
||||
// 来源子字段 rating(JRZQ5E9F / loanRiskTagV21 借选指数)
|
||||
if rating := raAsMap(data["rating"]); rating != nil {
|
||||
risk += calcRARatingXypModelRiskPoints(rating)
|
||||
}
|
||||
|
||||
return clampRAInt(risk, 0, raScoreMax)
|
||||
}
|
||||
|
||||
// calcRARatingXypModelRiskPoints 根据借选指数三个模型分统计风险扣分
|
||||
// xyp_model_score_* 范围 [350,950],越大逾期率越低;-1 为未命中,不参与计分
|
||||
func calcRARatingXypModelRiskPoints(rating map[string]interface{}) int {
|
||||
risk := 0
|
||||
for _, key := range []string{"xyp_model_score_high", "xyp_model_score_mid", "xyp_model_score_low"} {
|
||||
score := raXypModelScore(rating[key])
|
||||
if score < xypModelScoreMin || score >= xypModelScoreRiskThreshold {
|
||||
continue
|
||||
}
|
||||
risk += clampRAInt(xypModelScoreRiskThreshold-score, 0, xypModelScoreRiskCapPerField)
|
||||
}
|
||||
return risk
|
||||
}
|
||||
|
||||
// raXypModelScore 解析借选指数模型分,未命中(-1 / 空)返回 -1
|
||||
func raXypModelScore(v interface{}) int {
|
||||
s := raAsString(v)
|
||||
if s == "" || s == "-1" {
|
||||
return -1
|
||||
}
|
||||
return raAsInt(v)
|
||||
}
|
||||
|
||||
// calcRAJudicialScore 司法涉诉维度安全分(来源:calcRAJudicialRiskPoints)
|
||||
// 统计方式:满分 1000,根据 judicial.judicial_data 涉诉统计扣分后取补集
|
||||
func calcRAJudicialScore(data map[string]interface{}) int {
|
||||
return toRASafetyScore(calcRAJudicialRiskPoints(data))
|
||||
}
|
||||
|
||||
// calcRAJudicialRiskPoints 统计司法维度风险扣分
|
||||
func calcRAJudicialRiskPoints(data map[string]interface{}) int {
|
||||
risk := 0
|
||||
|
||||
// 来源子字段 judicial(FLXG7E8F 个人司法数据查询)
|
||||
judicial := raAsMap(data["judicial"])
|
||||
if judicial == nil {
|
||||
return 0
|
||||
}
|
||||
judicialData := raAsMap(judicial["judicial_data"])
|
||||
if judicialData == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
// lawsuitStat 下 civil/criminal/administrative/preservation 等节点累加
|
||||
if lawsuitStat := raAsMap(judicialData["lawsuitStat"]); lawsuitStat != nil {
|
||||
for _, section := range lawsuitStat {
|
||||
sectionMap := raAsMap(section)
|
||||
if sectionMap == nil {
|
||||
continue
|
||||
}
|
||||
count := raAsMap(sectionMap["count"])
|
||||
if count == nil {
|
||||
continue
|
||||
}
|
||||
risk += clampRAInt(raAsInt(count["count_total"])*raJudicialLawsuitTotalPerCase, 0, raJudicialLawsuitTotalCap)
|
||||
risk += clampRAInt(raAsInt(count["count_wei_total"])*raJudicialLawsuitWeiPerCase, 0, raJudicialLawsuitWeiCap)
|
||||
risk += clampRAInt(raAsInt(count["count_beigao"])*raJudicialLawsuitBeigaoPerCase, 0, raJudicialLawsuitBeigaoCap)
|
||||
}
|
||||
}
|
||||
|
||||
risk += clampRAInt(len(raAsSlice(judicialData["breachCaseList"]))*raJudicialBreachPerCase, 0, raJudicialBreachCap)
|
||||
risk += clampRAInt(len(raAsSlice(judicialData["consumptionRestrictionList"]))*raJudicialConsumptionPerCase, 0, raJudicialConsumptionCap)
|
||||
|
||||
return clampRAInt(risk, 0, raScoreMax)
|
||||
}
|
||||
|
||||
// calcRAVerifyScore 个人身份区安全分(来源:calcRAVerifyRiskPoints)
|
||||
// 统计方式:满分 1000,核验异常 + 欺诈/黑名单信号扣分后取补集,参与 ra_score 10% 加权
|
||||
func calcRAVerifyScore(data map[string]interface{}) int {
|
||||
return toRASafetyScore(calcRAVerifyRiskPoints(data))
|
||||
}
|
||||
|
||||
// calcRAVerifyRiskPoints 统计个人身份区风险扣分(核验 + 欺诈/黑名单)
|
||||
func calcRAVerifyRiskPoints(data map[string]interface{}) int {
|
||||
risk := 0
|
||||
|
||||
// 来源子字段 triple(YYSYK9R4 三要素验证)
|
||||
if triple := raAsMap(data["triple"]); triple != nil {
|
||||
if state := raAsString(triple["state"]); state != "" && state != "1" {
|
||||
risk += raVerifyMismatchPoints
|
||||
}
|
||||
}
|
||||
|
||||
// 来源子字段 identity(IVYZN2P8 二要素认证)
|
||||
if identity := raAsMap(data["identity"]); identity != nil {
|
||||
if result := raAsInt(identity["result"]); result != 0 {
|
||||
risk += raVerifyMismatchPoints
|
||||
}
|
||||
}
|
||||
|
||||
// 来源子字段 presence(YYSYE7V5 在网状态)
|
||||
if presence := raAsMap(data["presence"]); presence != nil {
|
||||
desc := raAsString(presence["desc"])
|
||||
if strings.Contains(desc, "停机") || strings.Contains(desc, "销号") || strings.Contains(desc, "不在网") {
|
||||
risk += raVerifyPresenceDesc
|
||||
}
|
||||
if status := raAsInt(presence["status"]); status > 1 {
|
||||
risk += raVerifyPresenceStatus
|
||||
}
|
||||
}
|
||||
|
||||
// 来源子字段 behavior / complaint / fraud / special(欺诈/黑名单,与 calcRAFraudRiskPoints 一致)
|
||||
risk += calcRAFraudRiskPoints(data)
|
||||
|
||||
return clampRAInt(risk, 0, raScoreMax)
|
||||
}
|
||||
|
||||
func raAsMap(v interface{}) map[string]interface{} {
|
||||
m, ok := v.(map[string]interface{})
|
||||
if !ok || m == nil {
|
||||
return nil
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func raAsSlice(v interface{}) []interface{} {
|
||||
s, ok := v.([]interface{})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func raAsString(v interface{}) string {
|
||||
switch val := v.(type) {
|
||||
case string:
|
||||
return strings.TrimSpace(val)
|
||||
case float64:
|
||||
return strconv.FormatInt(int64(val), 10)
|
||||
case int:
|
||||
return strconv.Itoa(val)
|
||||
case int64:
|
||||
return strconv.FormatInt(val, 10)
|
||||
case bool:
|
||||
if val {
|
||||
return "1"
|
||||
}
|
||||
return "0"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func raAsInt(v interface{}) int {
|
||||
switch val := v.(type) {
|
||||
case int:
|
||||
return val
|
||||
case int64:
|
||||
return int(val)
|
||||
case float64:
|
||||
return int(val)
|
||||
case string:
|
||||
n, err := strconv.Atoi(strings.TrimSpace(val))
|
||||
if err == nil {
|
||||
return n
|
||||
}
|
||||
case bool:
|
||||
if val {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func clampRAInt(v, min, max int) int {
|
||||
if v < min {
|
||||
return min
|
||||
}
|
||||
if v > max {
|
||||
return max
|
||||
}
|
||||
return v
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
package dwbg
|
||||
|
||||
import (
|
||||
"math"
|
||||
"regexp"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// raIndexField 借选指数区间桶加权项
|
||||
type raIndexField struct {
|
||||
key string
|
||||
weight float64
|
||||
failure bool // true 表示桶值越高表现越差(如失败次数、距上次成功天数)
|
||||
}
|
||||
|
||||
// 履约金额:成功金额 + 历史还款总额,扣减失败金额
|
||||
var raPerformanceAmtFields = []raIndexField{
|
||||
{key: "xyp_t01aazzzc", weight: 0.30},
|
||||
{key: "xyp_cpl0035", weight: 0.12},
|
||||
{key: "xyp_cpl0037", weight: 0.14},
|
||||
{key: "xyp_cpl0039", weight: 0.14},
|
||||
{key: "xyp_cpl0041", weight: 0.12},
|
||||
{key: "xyp_cpl0034", weight: 0.09, failure: true},
|
||||
{key: "xyp_cpl0036", weight: 0.09, failure: true},
|
||||
}
|
||||
|
||||
// 履约笔数:成功笔数,扣减失败笔数及距上次成功天数
|
||||
var raPerformanceCntFields = []raIndexField{
|
||||
{key: "xyp_cpl0019", weight: 0.12},
|
||||
{key: "xyp_cpl0021", weight: 0.14},
|
||||
{key: "xyp_cpl0023", weight: 0.14},
|
||||
{key: "xyp_cpl0025", weight: 0.12},
|
||||
{key: "xyp_cpl0027", weight: 0.10},
|
||||
{key: "xyp_cpl0068", weight: 0.18, failure: true},
|
||||
{key: "xyp_cpl0018", weight: 0.06, failure: true},
|
||||
{key: "xyp_cpl0020", weight: 0.07, failure: true},
|
||||
{key: "xyp_cpl0022", weight: 0.07, failure: true},
|
||||
}
|
||||
|
||||
var raProbeOverdueAmtPattern = regexp.MustCompile(`(\d+)`)
|
||||
|
||||
// calcRACreditRiskIndex 信用风险指数(0-1000,越高信用风险越大,与 ra_score 方向一致)
|
||||
func calcRACreditRiskIndex(data map[string]interface{}, creditSafetyScore int) int {
|
||||
riskFromModel := raScoreMax - creditSafetyScore
|
||||
|
||||
rating := raAsMap(data["rating"])
|
||||
if rating == nil {
|
||||
return riskFromModel
|
||||
}
|
||||
|
||||
raw := raAsString(rating["xyp_cpl0081"])
|
||||
if raw == "" || raw == "0" || raw == "-1" {
|
||||
return riskFromModel
|
||||
}
|
||||
|
||||
v, ok := raParseFloat(raw)
|
||||
if !ok || v < 0 || v > 1 {
|
||||
return riskFromModel
|
||||
}
|
||||
|
||||
riskFromRating := int(math.Round(v * float64(raScoreMax)))
|
||||
if riskFromRating > riskFromModel {
|
||||
return riskFromRating
|
||||
}
|
||||
return riskFromModel
|
||||
}
|
||||
|
||||
// calcRAPerformanceAmtIndex 履约金额综合指数(0-1000,越高履约金额表现越好)
|
||||
func calcRAPerformanceAmtIndex(data map[string]interface{}) int {
|
||||
rating := raAsMap(data["rating"])
|
||||
probe := raAsMap(data["probe"])
|
||||
|
||||
if rating != nil {
|
||||
if idx, ok := calcRACompositeBucketIndex(rating, raPerformanceAmtFields); ok {
|
||||
if probe != nil {
|
||||
probeIdx := calcRAPerformanceAmtIndexFromProbe(probe)
|
||||
return clampRAInt(int(math.Round(float64(idx)*0.75+float64(probeIdx)*0.25)), 0, raScoreMax)
|
||||
}
|
||||
return idx
|
||||
}
|
||||
}
|
||||
if probe != nil {
|
||||
return calcRAPerformanceAmtIndexFromProbe(probe)
|
||||
}
|
||||
return 500
|
||||
}
|
||||
|
||||
// calcRAPerformanceCntIndex 履约笔数综合指数(0-1000,越高履约笔数表现越好)
|
||||
func calcRAPerformanceCntIndex(data map[string]interface{}) int {
|
||||
rating := raAsMap(data["rating"])
|
||||
probe := raAsMap(data["probe"])
|
||||
|
||||
if rating != nil {
|
||||
if idx, ok := calcRACompositeBucketIndex(rating, raPerformanceCntFields); ok {
|
||||
if probe != nil {
|
||||
probeIdx := calcRAPerformanceCntIndexFromProbe(probe)
|
||||
return clampRAInt(int(math.Round(float64(idx)*0.75+float64(probeIdx)*0.25)), 0, raScoreMax)
|
||||
}
|
||||
return idx
|
||||
}
|
||||
}
|
||||
if probe != nil {
|
||||
return calcRAPerformanceCntIndexFromProbe(probe)
|
||||
}
|
||||
return 500
|
||||
}
|
||||
|
||||
func calcRACompositeBucketIndex(rating map[string]interface{}, fields []raIndexField) (int, bool) {
|
||||
totalWeight := 0.0
|
||||
totalScore := 0.0
|
||||
for _, f := range fields {
|
||||
bucket := raAsInt(rating[f.key])
|
||||
if bucket <= 0 {
|
||||
continue
|
||||
}
|
||||
tierScore := raBucketTierScore(bucket)
|
||||
if f.failure {
|
||||
tierScore = raScoreMax - tierScore
|
||||
}
|
||||
totalScore += float64(tierScore) * f.weight
|
||||
totalWeight += f.weight
|
||||
}
|
||||
if totalWeight == 0 {
|
||||
return 0, false
|
||||
}
|
||||
return clampRAInt(int(math.Round(totalScore/totalWeight)), 0, raScoreMax), true
|
||||
}
|
||||
|
||||
func raBucketTierScore(bucket int) int {
|
||||
switch {
|
||||
case bucket <= 0:
|
||||
return 0
|
||||
case bucket == 1:
|
||||
return 280
|
||||
case bucket == 2:
|
||||
return 530
|
||||
case bucket == 3:
|
||||
return 760
|
||||
default:
|
||||
return 920
|
||||
}
|
||||
}
|
||||
|
||||
func calcRAPerformanceAmtIndexFromProbe(probe map[string]interface{}) int {
|
||||
score := 620
|
||||
|
||||
switch raAsString(probe["currently_performance"]) {
|
||||
case "1":
|
||||
score += 180
|
||||
case "0":
|
||||
score -= 280
|
||||
}
|
||||
|
||||
if raAsString(probe["currently_overdue"]) == "1" {
|
||||
score -= raProbeOverdueAmtPenalty(probe["max_overdue_amt"])
|
||||
}
|
||||
if raAsString(probe["acc_sleep"]) == "1" {
|
||||
score -= 80
|
||||
}
|
||||
if raAsString(probe["acc_exc"]) == "1" {
|
||||
score -= 60
|
||||
}
|
||||
|
||||
return clampRAInt(score, 0, raScoreMax)
|
||||
}
|
||||
|
||||
func calcRAPerformanceCntIndexFromProbe(probe map[string]interface{}) int {
|
||||
score := 580
|
||||
|
||||
switch raAsString(probe["currently_performance"]) {
|
||||
case "1":
|
||||
score += 220
|
||||
case "0":
|
||||
score -= 260
|
||||
}
|
||||
if raAsString(probe["currently_overdue"]) == "1" {
|
||||
score -= 150
|
||||
}
|
||||
if raAsString(probe["result_code"]) == "1" {
|
||||
score -= 120
|
||||
}
|
||||
if raAsString(probe["acc_sleep"]) == "1" {
|
||||
score -= 90
|
||||
}
|
||||
if raAsString(probe["acc_exc"]) == "1" {
|
||||
score -= 70
|
||||
}
|
||||
|
||||
return clampRAInt(score, 0, raScoreMax)
|
||||
}
|
||||
|
||||
func raProbeOverdueAmtPenalty(v interface{}) int {
|
||||
mid := raProbeAmtMidpoint(v)
|
||||
switch {
|
||||
case mid >= 10000:
|
||||
return 280
|
||||
case mid >= 4000:
|
||||
return 200
|
||||
case mid >= 1000:
|
||||
return 120
|
||||
case mid > 0:
|
||||
return 60
|
||||
default:
|
||||
return 100
|
||||
}
|
||||
}
|
||||
|
||||
func raProbeAmtMidpoint(v interface{}) int {
|
||||
s := raAsString(v)
|
||||
if s == "" {
|
||||
return 0
|
||||
}
|
||||
matches := raProbeOverdueAmtPattern.FindAllString(s, -1)
|
||||
if len(matches) == 0 {
|
||||
return 0
|
||||
}
|
||||
if len(matches) == 1 {
|
||||
return raAsInt(matches[0])
|
||||
}
|
||||
low := raAsInt(matches[0])
|
||||
high := raAsInt(matches[len(matches)-1])
|
||||
if high <= 0 {
|
||||
return low
|
||||
}
|
||||
return (low + high) / 2
|
||||
}
|
||||
|
||||
func raParseFloat(raw string) (float64, bool) {
|
||||
f, err := strconv.ParseFloat(raw, 64)
|
||||
if err != nil || math.IsNaN(f) || math.IsInf(f, 0) {
|
||||
return 0, false
|
||||
}
|
||||
return f, true
|
||||
}
|
||||
@@ -0,0 +1,386 @@
|
||||
package dwbg
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBuildDWBG3BF9RAFromSample(t *testing.T) {
|
||||
raw, err := os.ReadFile(filepath.Join("DWBG3BF9.json"))
|
||||
if err != nil {
|
||||
t.Skip("DWBG3BF9.json not found")
|
||||
}
|
||||
|
||||
var data map[string]interface{}
|
||||
if err := json.Unmarshal(raw, &data); err != nil {
|
||||
t.Fatalf("unmarshal sample: %v", err)
|
||||
}
|
||||
|
||||
ra := buildDWBG3BF9RA(data)
|
||||
|
||||
required := []string{
|
||||
"ra_score",
|
||||
"ra_level",
|
||||
"ra_credit_score",
|
||||
"ra_verify_score",
|
||||
"ra_credit_risk_index",
|
||||
"ra_performance_amt_index",
|
||||
"ra_performance_cnt_index",
|
||||
}
|
||||
for _, key := range required {
|
||||
if _, ok := ra[key]; !ok {
|
||||
t.Fatalf("RA missing key %s", key)
|
||||
}
|
||||
}
|
||||
|
||||
hidden := []string{"ra_fraud_score", "ra_judicial_score"}
|
||||
for _, key := range hidden {
|
||||
if _, ok := ra[key]; ok {
|
||||
t.Fatalf("RA should not expose %s", key)
|
||||
}
|
||||
}
|
||||
|
||||
score := raAsInt(ra["ra_score"])
|
||||
if score < 0 || score > raScoreMax {
|
||||
t.Fatalf("ra_score out of range: %d", score)
|
||||
}
|
||||
|
||||
level := raAsString(ra["ra_level"])
|
||||
validLevels := map[string]bool{"A": true, "B": true, "C": true, "D": true, "E": true, "F": true}
|
||||
if !validLevels[level] {
|
||||
t.Fatalf("invalid ra_level: %s", level)
|
||||
}
|
||||
|
||||
if raAsString(ra["ra_level"]) != "D" {
|
||||
t.Fatalf("sample ra_level want D, got %s", raAsString(ra["ra_level"]))
|
||||
}
|
||||
if raAsInt(ra["ra_credit_risk_index"]) != 1000 {
|
||||
t.Fatalf("sample credit risk index want 1000, got %d", raAsInt(ra["ra_credit_risk_index"]))
|
||||
}
|
||||
if raAsInt(ra["ra_performance_amt_index"]) != 60 {
|
||||
t.Fatalf("sample perf amt index want 60, got %d", raAsInt(ra["ra_performance_amt_index"]))
|
||||
}
|
||||
if raAsInt(ra["ra_performance_cnt_index"]) != 0 {
|
||||
t.Fatalf("sample perf cnt index want 0, got %d", raAsInt(ra["ra_performance_cnt_index"]))
|
||||
}
|
||||
|
||||
t.Logf("RA sample: score=%d level=%s credit=%d verify=%d creditRisk=%d perfAmt=%d perfCnt=%d",
|
||||
raAsInt(ra["ra_score"]),
|
||||
level,
|
||||
raAsInt(ra["ra_credit_score"]),
|
||||
raAsInt(ra["ra_verify_score"]),
|
||||
raAsInt(ra["ra_credit_risk_index"]),
|
||||
raAsInt(ra["ra_performance_amt_index"]),
|
||||
raAsInt(ra["ra_performance_cnt_index"]),
|
||||
)
|
||||
}
|
||||
|
||||
func TestRALevelFromScore(t *testing.T) {
|
||||
cases := map[int]string{
|
||||
0: "F",
|
||||
499: "F",
|
||||
500: "E",
|
||||
599: "E",
|
||||
600: "D",
|
||||
699: "D",
|
||||
700: "C",
|
||||
799: "C",
|
||||
800: "B",
|
||||
899: "B",
|
||||
900: "A",
|
||||
1000: "A",
|
||||
}
|
||||
for score, want := range cases {
|
||||
if got := raLevelFromScore(score); got != want {
|
||||
t.Fatalf("score %d: got %s want %s", score, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSafetyScoreHigherIsSafer(t *testing.T) {
|
||||
clean := map[string]interface{}{
|
||||
"behavior": map[string]interface{}{"result": map[string]interface{}{"black_list": "0"}},
|
||||
"complaint": nil,
|
||||
"fraud": nil,
|
||||
"special": nil,
|
||||
"probe": nil,
|
||||
"intent": nil,
|
||||
"rating": nil,
|
||||
"judicial": nil,
|
||||
"triple": map[string]interface{}{"state": "1"},
|
||||
"identity": map[string]interface{}{"result": 0},
|
||||
"presence": map[string]interface{}{"desc": "正常", "status": 1},
|
||||
}
|
||||
ra := buildDWBG3BF9RA(clean)
|
||||
if raAsInt(ra["ra_score"]) < 800 {
|
||||
t.Fatalf("clean data should have safety score >= 800, got %d", raAsInt(ra["ra_score"]))
|
||||
}
|
||||
if raLevelFromScore(raAsInt(ra["ra_score"])) != "A" {
|
||||
t.Fatalf("clean data should be level A, got %s", raLevelFromScore(raAsInt(ra["ra_score"])))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalcRADynamicWeights(t *testing.T) {
|
||||
cases := []struct {
|
||||
judicial, credit int
|
||||
wantJudicial float64
|
||||
wantCredit float64
|
||||
}{
|
||||
{1000, 1000, 0.50, 0.40},
|
||||
{900, 1000, 0.65, 0.25},
|
||||
{1000, 900, 0.40, 0.50},
|
||||
{900, 900, 0.70, 0.20},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
gotJ, gotC := calcRADynamicWeights(tc.judicial, tc.credit)
|
||||
if gotJ != tc.wantJudicial || gotC != tc.wantCredit {
|
||||
t.Fatalf("judicial=%d credit=%d: got (%.2f, %.2f) want (%.2f, %.2f)",
|
||||
tc.judicial, tc.credit, gotJ, gotC, tc.wantJudicial, tc.wantCredit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRAForcedFGrade(t *testing.T) {
|
||||
data := map[string]interface{}{
|
||||
"behavior": nil,
|
||||
"complaint": nil,
|
||||
"fraud": nil,
|
||||
"special": nil,
|
||||
"probe": map[string]interface{}{
|
||||
"currently_overdue": "1",
|
||||
"currently_performance": "0",
|
||||
"result_code": "1",
|
||||
},
|
||||
"intent": map[string]interface{}{
|
||||
"Rule_final_decision": "Reject",
|
||||
"Rule_final_weight": "40",
|
||||
},
|
||||
"rating": nil,
|
||||
"triple": map[string]interface{}{"state": "1"},
|
||||
"identity": map[string]interface{}{"result": 0},
|
||||
"presence": map[string]interface{}{"desc": "正常", "status": 1},
|
||||
"judicial": map[string]interface{}{
|
||||
"judicial_data": map[string]interface{}{
|
||||
"breachCaseList": []interface{}{
|
||||
map[string]interface{}{"caseNo": "1"},
|
||||
map[string]interface{}{"caseNo": "2"},
|
||||
map[string]interface{}{"caseNo": "3"},
|
||||
},
|
||||
"consumptionRestrictionList": []interface{}{
|
||||
map[string]interface{}{"caseNo": "4"},
|
||||
map[string]interface{}{"caseNo": "5"},
|
||||
},
|
||||
"lawsuitStat": map[string]interface{}{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ra := buildDWBG3BF9RA(data)
|
||||
// 司法 197 + 借贷 0,双风险权重 70%/20%,身份 1000 → 加权分 238,强制 F 档保留分数
|
||||
if raAsInt(ra["ra_score"]) != 238 {
|
||||
t.Fatalf("forced F should keep weighted ra_score=238, got %d", raAsInt(ra["ra_score"]))
|
||||
}
|
||||
if raAsString(ra["ra_level"]) != "F" {
|
||||
t.Fatalf("forced F should have ra_level=F, got %s", raAsString(ra["ra_level"]))
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsRAForcedFGradeRequiresMoreThan5Hits(t *testing.T) {
|
||||
fiveHits := map[string]interface{}{
|
||||
"judicial": map[string]interface{}{
|
||||
"judicial_data": map[string]interface{}{
|
||||
"breachCaseList": []interface{}{
|
||||
map[string]interface{}{"caseNo": "1"},
|
||||
map[string]interface{}{"caseNo": "2"},
|
||||
map[string]interface{}{"caseNo": "3"},
|
||||
},
|
||||
"consumptionRestrictionList": []interface{}{
|
||||
map[string]interface{}{"caseNo": "4"},
|
||||
map[string]interface{}{"caseNo": "5"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
if calcRAJudicialHitCount(fiveHits)+calcRACreditHitCount(fiveHits) != 5 {
|
||||
t.Fatal("test setup want 5 total hits")
|
||||
}
|
||||
if isRAForcedFGrade(fiveHits, 150, 100) {
|
||||
t.Fatal("total hits = 5 should not trigger forced F")
|
||||
}
|
||||
|
||||
sixHits := map[string]interface{}{
|
||||
"probe": map[string]interface{}{"currently_overdue": "1"},
|
||||
"judicial": map[string]interface{}{
|
||||
"judicial_data": map[string]interface{}{
|
||||
"breachCaseList": []interface{}{
|
||||
map[string]interface{}{"caseNo": "1"},
|
||||
map[string]interface{}{"caseNo": "2"},
|
||||
map[string]interface{}{"caseNo": "3"},
|
||||
},
|
||||
"consumptionRestrictionList": []interface{}{
|
||||
map[string]interface{}{"caseNo": "4"},
|
||||
map[string]interface{}{"caseNo": "5"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
if calcRAJudicialHitCount(sixHits)+calcRACreditHitCount(sixHits) != 6 {
|
||||
t.Fatal("test setup want 6 total hits")
|
||||
}
|
||||
if !isRAForcedFGrade(sixHits, 150, 100) {
|
||||
t.Fatal("total hits = 6 with dual high should trigger forced F")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRAForcedFGradeScoreCap(t *testing.T) {
|
||||
// 加权分高于 500 时,强制 F 档封顶 500
|
||||
verify, judicial, credit := 1000, 1000, 1000
|
||||
raw := calcRAWightedScore(verify, judicial, credit)
|
||||
if raw != 1000 {
|
||||
t.Fatalf("baseline weighted score want 1000, got %d", raw)
|
||||
}
|
||||
|
||||
// 模拟双高:司法/借贷维度分均 ≤ 400,但加权后可能 > 500(此处用边界值构造)
|
||||
verify, judicial, credit = 1000, 400, 400
|
||||
raw = calcRAWightedScore(verify, judicial, credit)
|
||||
// 1000×0.10 + 400×0.70 + 400×0.20 = 100+280+80 = 460
|
||||
if raw != 460 {
|
||||
t.Fatalf("dual-risk weighted score want 460, got %d", raw)
|
||||
}
|
||||
|
||||
data := map[string]interface{}{
|
||||
"behavior": nil, "complaint": nil, "fraud": nil, "special": nil,
|
||||
"probe": map[string]interface{}{"currently_overdue": "1"},
|
||||
"intent": map[string]interface{}{"Rule_final_decision": "Reject"},
|
||||
"rating": nil,
|
||||
"triple": map[string]interface{}{"state": "1"},
|
||||
"identity": map[string]interface{}{"result": 0},
|
||||
"presence": map[string]interface{}{"desc": "正常", "status": 1},
|
||||
"judicial": map[string]interface{}{
|
||||
"judicial_data": map[string]interface{}{
|
||||
"breachCaseList": []interface{}{
|
||||
map[string]interface{}{"caseNo": "1"},
|
||||
map[string]interface{}{"caseNo": "2"},
|
||||
map[string]interface{}{"caseNo": "3"},
|
||||
},
|
||||
"consumptionRestrictionList": []interface{}{
|
||||
map[string]interface{}{"caseNo": "4"},
|
||||
map[string]interface{}{"caseNo": "5"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
ra := buildDWBG3BF9RA(data)
|
||||
score := raAsInt(ra["ra_score"])
|
||||
if score > raForcedFScoreCap {
|
||||
t.Fatalf("forced F score should be capped at %d, got %d", raForcedFScoreCap, score)
|
||||
}
|
||||
if raAsString(ra["ra_level"]) != "F" {
|
||||
t.Fatalf("forced F should have ra_level=F, got %s", raAsString(ra["ra_level"]))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalcRARatingXypModelRiskPoints(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
rating map[string]interface{}
|
||||
want int
|
||||
}{
|
||||
{
|
||||
name: "all miss",
|
||||
rating: map[string]interface{}{
|
||||
"xyp_model_score_high": "-1",
|
||||
"xyp_model_score_mid": "-1",
|
||||
"xyp_model_score_low": "-1",
|
||||
},
|
||||
want: 0,
|
||||
},
|
||||
{
|
||||
name: "one low score",
|
||||
rating: map[string]interface{}{
|
||||
"xyp_model_score_high": "600",
|
||||
"xyp_model_score_mid": "-1",
|
||||
"xyp_model_score_low": "-1",
|
||||
},
|
||||
want: 50, // 650 - 600
|
||||
},
|
||||
{
|
||||
name: "three low scores capped",
|
||||
rating: map[string]interface{}{
|
||||
"xyp_model_score_high": "400",
|
||||
"xyp_model_score_mid": "400",
|
||||
"xyp_model_score_low": "400",
|
||||
},
|
||||
want: 291, // (650-400)*3 = 750, capped per field at 97
|
||||
},
|
||||
{
|
||||
name: "safe scores no deduction",
|
||||
rating: map[string]interface{}{
|
||||
"xyp_model_score_high": "900",
|
||||
"xyp_model_score_mid": "800",
|
||||
"xyp_model_score_low": "700",
|
||||
},
|
||||
want: 0,
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if got := calcRARatingXypModelRiskPoints(tc.rating); got != tc.want {
|
||||
t.Fatalf("got %d want %d", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRAVerifyScoreIncludesFraudSignals(t *testing.T) {
|
||||
base := map[string]interface{}{
|
||||
"triple": map[string]interface{}{"state": "1"},
|
||||
"identity": map[string]interface{}{"result": 0},
|
||||
"presence": map[string]interface{}{"desc": "正常", "status": 1},
|
||||
"behavior": map[string]interface{}{"result": map[string]interface{}{"black_list": "0"}},
|
||||
"fraud": nil,
|
||||
"special": nil,
|
||||
}
|
||||
if got := calcRAVerifyScore(base); got != raScoreMax {
|
||||
t.Fatalf("clean verify want %d, got %d", raScoreMax, got)
|
||||
}
|
||||
|
||||
withComplaint := map[string]interface{}{
|
||||
"triple": map[string]interface{}{"state": "1"},
|
||||
"identity": map[string]interface{}{"result": 0},
|
||||
"presence": map[string]interface{}{"desc": "正常", "status": 1},
|
||||
"behavior": map[string]interface{}{"result": map[string]interface{}{"black_list": "0"}},
|
||||
"complaint": map[string]interface{}{
|
||||
"result": map[string]interface{}{"score": 11},
|
||||
},
|
||||
"fraud": nil,
|
||||
"special": nil,
|
||||
}
|
||||
want := raScoreMax - raFraudPointsPerHit
|
||||
if got := calcRAVerifyScore(withComplaint); got != want {
|
||||
t.Fatalf("verify with fraud signal want %d, got %d", want, got)
|
||||
}
|
||||
if got := calcRAFraudScore(withComplaint); got != want {
|
||||
t.Fatalf("fraud sub-score should match fraud-only deduction, want %d, got %d", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRAScoreWeightedSum(t *testing.T) {
|
||||
verify, judicial, credit := 920, 980, 760
|
||||
wJ, wC := calcRADynamicWeights(judicial, credit)
|
||||
// 980/760 均 < 1000,触发司法+借贷双风险 → 70%/20%
|
||||
want := int(math.Round(float64(verify)*raWeightVerify + float64(judicial)*wJ + float64(credit)*wC))
|
||||
if want != 930 {
|
||||
t.Fatalf("dual-risk example want 930, got %d (wJ=%.2f wC=%.2f)", want, wJ, wC)
|
||||
}
|
||||
|
||||
verify, judicial, credit = 920, 1000, 1000
|
||||
wJ, wC = calcRADynamicWeights(judicial, credit)
|
||||
want = int(math.Round(float64(verify)*raWeightVerify + float64(judicial)*wJ + float64(credit)*wC))
|
||||
if want != 992 {
|
||||
t.Fatalf("baseline example want 992, got %d", want)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package dwbg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/zhicha"
|
||||
)
|
||||
|
||||
// ProcessDWBG5SAMRequest DWBG5SAM 天远指迷报告
|
||||
func ProcessDWBG5SAMRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.DWBG5SAMReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedIDCard, err := deps.ZhichaService.Encrypt(paramsDto.IDCard)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedMobileNo, err := deps.ZhichaService.Encrypt(paramsDto.MobileNo)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"name": encryptedName,
|
||||
"idCard": encryptedIDCard,
|
||||
"phone": encryptedMobileNo,
|
||||
"accessoryUrl": paramsDto.AuthorizationURL,
|
||||
}
|
||||
|
||||
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI112", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, zhicha.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 过滤响应数据,删除指定字段
|
||||
if respMap, ok := respData.(map[string]interface{}); ok {
|
||||
delete(respMap, "reportUrl")
|
||||
}
|
||||
|
||||
// 将响应数据转换为JSON字节
|
||||
respBytes, err := json.Marshal(respData)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -54,7 +54,7 @@ func ProcessDWBG6A2CRequest(ctx context.Context, params []byte, deps *processors
|
||||
if respMap, ok := respData.(map[string]interface{}); ok {
|
||||
delete(respMap, "reportUrl")
|
||||
delete(respMap, "multCourtInfo")
|
||||
delete(respMap, "judiciaRiskInfos")
|
||||
// delete(respMap, "judiciaRiskInfos")
|
||||
}
|
||||
|
||||
// 将响应数据转换为JSON字节
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,55 @@
|
||||
package dwbg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/nuoer"
|
||||
)
|
||||
|
||||
// ProcessDWBG9FB2Request DWBG9FB2 API处理方法 - 个人风险档案2
|
||||
func ProcessDWBG9FB2Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.DWBG9FB2Req
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
body := map[string]string{
|
||||
"name": paramsDto.Name,
|
||||
"idCard": paramsDto.IDCard,
|
||||
"mobile": paramsDto.MobileNo,
|
||||
}
|
||||
|
||||
nuoerDoCheckAPIKey := "gamaReportPageP02"
|
||||
ApiPath := "/v1/doCheck"
|
||||
resp, err := deps.NuoerService.CallAPI(ctx, nuoerDoCheckAPIKey, ApiPath, body)
|
||||
if err != nil {
|
||||
if errors.Is(err, nuoer.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
}
|
||||
if errors.Is(err, nuoer.ErrNotFound) {
|
||||
return nil, errors.Join(processors.ErrNotFound, err)
|
||||
}
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if respMap, ok := resp.Data.(map[string]interface{}); ok {
|
||||
delete(respMap, "reportUrl")
|
||||
if resultMap, ok := respMap["result"].(map[string]interface{}); ok {
|
||||
delete(resultMap, "reportUrl")
|
||||
}
|
||||
}
|
||||
|
||||
respBytes, err := json.Marshal(resp.Data)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package dwbg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/nuoer"
|
||||
)
|
||||
|
||||
// ProcessDWBG9FB3Request DWBG9FB3 API处理方法 - 个人风险档案1
|
||||
func ProcessDWBG9FB3Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.DWBG9FB3Req
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
body := map[string]string{
|
||||
"name": paramsDto.Name,
|
||||
"idCard": paramsDto.IDCard,
|
||||
"mobile": paramsDto.MobileNo,
|
||||
}
|
||||
|
||||
nuoerDoCheckAPIKey := "gamaReportPageP01"
|
||||
ApiPath := "/v1/doCheck"
|
||||
resp, err := deps.NuoerService.CallAPI(ctx, nuoerDoCheckAPIKey, ApiPath, body)
|
||||
if err != nil {
|
||||
if errors.Is(err, nuoer.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
}
|
||||
if errors.Is(err, nuoer.ErrNotFound) {
|
||||
return nil, errors.Join(processors.ErrNotFound, err)
|
||||
}
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if respMap, ok := resp.Data.(map[string]interface{}); ok {
|
||||
delete(respMap, "reportUrl")
|
||||
if resultMap, ok := respMap["result"].(map[string]interface{}); ok {
|
||||
delete(resultMap, "reportUrl")
|
||||
}
|
||||
}
|
||||
|
||||
respBytes, err := json.Marshal(resp.Data)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -4,17 +4,15 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/westdex"
|
||||
nuoerext "tyapi-server/internal/infrastructure/external/nuoer"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
// ProcessFLXG0V4BRequest FLXG0V4B API处理方法
|
||||
// ProcessFLXG0V4BRequest FLXG0V4B API处理方法 - 个人司法涉诉
|
||||
func ProcessFLXG0V4BRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.FLXG0V4BReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
@@ -24,73 +22,39 @@ func ProcessFLXG0V4BRequest(ctx context.Context, params []byte, deps *processors
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" {
|
||||
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" || paramsDto.IDCard == "320682198910134998" || paramsDto.IDCard == "640102198708020925" || paramsDto.IDCard == "420624197310234034" || paramsDto.IDCard == "350104198501184416" || paramsDto.IDCard == "410521198606018056" || paramsDto.IDCard == "410482198504029333" || paramsDto.IDCard == "370982199012037272" || paramsDto.IDCard == "431027198810290730" || paramsDto.IDCard == "362502199510298017" || paramsDto.IDCard == "340826199008250378" || paramsDto.IDCard == "321027198304072129" || paramsDto.IDCard == "420116198907031413" || paramsDto.IDCard == "13032319930128263X" || paramsDto.IDCard == "350681198412013041" {
|
||||
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
|
||||
}
|
||||
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
|
||||
body := map[string]string{
|
||||
"name": paramsDto.Name,
|
||||
"idCard": paramsDto.IDCard,
|
||||
}
|
||||
|
||||
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
|
||||
resp, err := deps.NuoerService.CallAPI(ctx, "personalLawsuit_cv1", "/v1/doCheck", body)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
if deps.CallContext.ContractCode == "" {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, errors.New("合同编号不能为空"))
|
||||
}
|
||||
encryptedAuthAuthorizeFileCode, err := deps.WestDexService.Encrypt(deps.CallContext.ContractCode)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
reqData := map[string]interface{}{
|
||||
"data": map[string]interface{}{
|
||||
"name": encryptedName,
|
||||
"idcard": encryptedIDCard,
|
||||
"auth_authorizeFileCode": encryptedAuthAuthorizeFileCode,
|
||||
"inquired_auth": fmt.Sprintf("authed:%s", paramsDto.AuthDate),
|
||||
},
|
||||
}
|
||||
log.Println("reqData", reqData)
|
||||
respBytes, err := deps.WestDexService.CallAPI(ctx, "G22SC01", reqData)
|
||||
if err != nil {
|
||||
// 数据源错误
|
||||
if errors.Is(err, westdex.ErrDatasource) {
|
||||
// 如果有返回内容,优先解析返回内容
|
||||
if respBytes != nil {
|
||||
parsed, parseErr := ParseJsonResponse(respBytes)
|
||||
if parseErr == nil {
|
||||
// 通过gjson获取指定路径的数据
|
||||
contentResult := gjson.GetBytes(parsed, "G22SC0101.G22SC0102.content")
|
||||
if contentResult.Exists() {
|
||||
return []byte(contentResult.Raw), errors.Join(processors.ErrDatasource, err)
|
||||
}
|
||||
return parsed, errors.Join(processors.ErrDatasource, err)
|
||||
}
|
||||
// 解析失败,返回原始内容和系统错误
|
||||
return respBytes, fmt.Errorf("%s: %w", processors.ErrSystem, parseErr)
|
||||
}
|
||||
// 没有返回内容,直接返回数据源错误
|
||||
if errors.Is(err, nuoerext.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
}
|
||||
// 其他系统错误
|
||||
if errors.Is(err, nuoerext.ErrNotFound) {
|
||||
return nil, errors.Join(processors.ErrNotFound, err)
|
||||
}
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
// 正常返回 - 不管有没有deps.Options.Json都进行ParseJsonResponse
|
||||
parsed, parseErr := ParseJsonResponse(respBytes)
|
||||
if parseErr != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, parseErr)
|
||||
rawData, ok := resp.Data.(map[string]interface{})
|
||||
if !ok {
|
||||
return processors.MarshalRawResponse(resp.Data)
|
||||
}
|
||||
|
||||
// 通过gjson获取指定路径的数据
|
||||
contentResult := gjson.GetBytes(parsed, "G22SC0101.G22SC0102.content")
|
||||
if contentResult.Exists() {
|
||||
return []byte(contentResult.Raw), nil
|
||||
} else {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
result := mapFLXG0V4BResponse(rawData)
|
||||
|
||||
respBytes, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
|
||||
// Content 数据内容
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
package flxg
|
||||
|
||||
const (
|
||||
flxg0v4bMsgSuccess = "成功"
|
||||
flxg0v4bMsgNotFound = "没有找到"
|
||||
)
|
||||
|
||||
// mapFLXG0V4BResponse 将 nuoer 响应(2.json / 2.md)转为 FLXG0V4B 对外结构(1.json / 1.md)。
|
||||
func mapFLXG0V4BResponse(data map[string]interface{}) map[string]interface{} {
|
||||
if data == nil {
|
||||
return defaultFLXG0V4BResponse()
|
||||
}
|
||||
if _, ok := data["entout"]; ok {
|
||||
return normalizeFLXG0V4BResponse(data)
|
||||
}
|
||||
|
||||
flat := unwrapNuoerPersonalLawsuitData(data)
|
||||
return buildFLXG0V4BResponse(flat)
|
||||
}
|
||||
|
||||
func defaultFLXG0V4BResponse() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"entout": map[string]interface{}{"msg": flxg0v4bMsgNotFound},
|
||||
"sxbzxr": map[string]interface{}{"msg": flxg0v4bMsgNotFound},
|
||||
"xgbzxr": map[string]interface{}{"msg": flxg0v4bMsgNotFound},
|
||||
}
|
||||
}
|
||||
|
||||
func buildFLXG0V4BResponse(flat map[string]interface{}) map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"entout": buildFLXG0V4BEntout(asMap(flat["lawsuitStat"])),
|
||||
"sxbzxr": buildFLXG0V4BBzxrSection("sxbzxr", asSlice(flat["breachCaseList"])),
|
||||
"xgbzxr": buildFLXG0V4BBzxrSection("xgbzxr", asSlice(flat["consumptionRestrictionList"])),
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeFLXG0V4BResponse(data map[string]interface{}) map[string]interface{} {
|
||||
out := map[string]interface{}{
|
||||
"entout": normalizeFLXG0V4BEntoutSection(data["entout"]),
|
||||
"sxbzxr": normalizeFLXG0V4BBzxrSection(data["sxbzxr"], "sxbzxr"),
|
||||
"xgbzxr": normalizeFLXG0V4BBzxrSection(data["xgbzxr"], "xgbzxr"),
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func buildFLXG0V4BEntout(lawsuitStat map[string]interface{}) map[string]interface{} {
|
||||
if !isLawsuitStatPayload(lawsuitStat) {
|
||||
return map[string]interface{}{"msg": flxg0v4bMsgNotFound}
|
||||
}
|
||||
return map[string]interface{}{
|
||||
"msg": flxg0v4bMsgSuccess,
|
||||
"data": normalizeFLXG0V4FEntoutData(lawsuitStat),
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeFLXG0V4BEntoutSection(raw interface{}) map[string]interface{} {
|
||||
entout := asMap(raw)
|
||||
if len(entout) == 0 {
|
||||
return map[string]interface{}{"msg": flxg0v4bMsgNotFound}
|
||||
}
|
||||
|
||||
if data := asMap(entout["data"]); len(data) > 0 {
|
||||
entout["data"] = normalizeFLXG0V4FEntoutData(data)
|
||||
if msg, _ := entout["msg"].(string); msg == "" {
|
||||
entout["msg"] = flxg0v4bMsgSuccess
|
||||
}
|
||||
return entout
|
||||
}
|
||||
|
||||
if isLawsuitStatPayload(entout) {
|
||||
return map[string]interface{}{
|
||||
"msg": flxg0v4bMsgSuccess,
|
||||
"data": normalizeFLXG0V4FEntoutData(entout),
|
||||
}
|
||||
}
|
||||
|
||||
if msg, _ := entout["msg"].(string); msg != "" {
|
||||
return map[string]interface{}{"msg": msg}
|
||||
}
|
||||
return map[string]interface{}{"msg": flxg0v4bMsgNotFound}
|
||||
}
|
||||
|
||||
func normalizeFLXG0V4FEntoutData(src map[string]interface{}) map[string]interface{} {
|
||||
if len(src) == 0 {
|
||||
return map[string]interface{}{}
|
||||
}
|
||||
|
||||
dst := make(map[string]interface{}, len(src))
|
||||
for k, v := range src {
|
||||
switch k {
|
||||
case "cases_tree":
|
||||
dst[k] = normalizeFLXG0V4FCasesTree(asMap(v))
|
||||
case "preservation", "administrative", "civil", "implement", "criminal", "bankrupt":
|
||||
dst[k] = normalizeFLXG0V4FCaseSection(asMap(v))
|
||||
default:
|
||||
dst[k] = v
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func normalizeFLXG0V4FCasesTree(src map[string]interface{}) map[string]interface{} {
|
||||
if len(src) == 0 {
|
||||
return map[string]interface{}{}
|
||||
}
|
||||
dst := make(map[string]interface{}, len(src))
|
||||
for _, key := range []string{"administrative", "criminal", "civil"} {
|
||||
if v, ok := src[key]; ok {
|
||||
dst[key] = asSlice(v)
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func normalizeFLXG0V4FCaseSection(src map[string]interface{}) map[string]interface{} {
|
||||
if len(src) == 0 {
|
||||
return map[string]interface{}{}
|
||||
}
|
||||
if _, hasCases := src["cases"]; !hasCases {
|
||||
if _, hasCount := src["count"]; !hasCount {
|
||||
return map[string]interface{}{}
|
||||
}
|
||||
}
|
||||
|
||||
dst := make(map[string]interface{}, 2)
|
||||
if v, ok := src["cases"]; ok {
|
||||
dst["cases"] = asSlice(v)
|
||||
}
|
||||
if v, ok := src["count"]; ok {
|
||||
dst["count"] = asMap(v)
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func buildFLXG0V4BBzxrSection(key string, list []interface{}) map[string]interface{} {
|
||||
if len(list) == 0 {
|
||||
return map[string]interface{}{"msg": flxg0v4bMsgNotFound}
|
||||
}
|
||||
return map[string]interface{}{
|
||||
"data": map[string]interface{}{
|
||||
key: list,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeFLXG0V4BBzxrSection(raw interface{}, key string) map[string]interface{} {
|
||||
section := asMap(raw)
|
||||
if len(section) == 0 {
|
||||
return map[string]interface{}{"msg": flxg0v4bMsgNotFound}
|
||||
}
|
||||
|
||||
if data := asMap(section["data"]); len(data) > 0 {
|
||||
if arr := asSlice(data[key]); len(arr) > 0 {
|
||||
return map[string]interface{}{
|
||||
"data": map[string]interface{}{key: arr},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if arr := asSlice(section[key]); len(arr) > 0 {
|
||||
return map[string]interface{}{
|
||||
"data": map[string]interface{}{key: arr},
|
||||
}
|
||||
}
|
||||
|
||||
if msg, _ := section["msg"].(string); msg != "" {
|
||||
return map[string]interface{}{"msg": msg}
|
||||
}
|
||||
return map[string]interface{}{"msg": flxg0v4bMsgNotFound}
|
||||
}
|
||||
@@ -20,7 +20,9 @@ func ProcessFLXG3A9BRequest(ctx context.Context, params []byte, deps *processors
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
if paramsDto.IDCard == "410482198504029333" {
|
||||
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
|
||||
}
|
||||
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package flxg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/zhicha"
|
||||
)
|
||||
|
||||
// ProcessFLXG5A3BCOPYRequest FLXG5A3B COPY API处理方法 - 个人司法涉诉
|
||||
func ProcessFLXG5A3BCOPYRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.FLXG5A3BReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" || paramsDto.IDCard == "320682198910134998" || paramsDto.IDCard == "640102198708020925" || paramsDto.IDCard == "420624197310234034" || paramsDto.IDCard == "350104198501184416" || paramsDto.IDCard == "410521198606018056" || paramsDto.IDCard == "410482198504029333" || paramsDto.IDCard == "370982199012037272" || paramsDto.IDCard == "431027198810290730" || paramsDto.IDCard == "362502199510298017" || paramsDto.IDCard == "340826199008250378" || paramsDto.IDCard == "321027198304072129" || paramsDto.IDCard == "420116198907031413" || paramsDto.IDCard == "13032319930128263X" || paramsDto.IDCard == "350681198412013041" {
|
||||
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
|
||||
}
|
||||
|
||||
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedIDCard, err := deps.ZhichaService.Encrypt(paramsDto.IDCard)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"name": encryptedName,
|
||||
"idCard": encryptedIDCard,
|
||||
"authorized": paramsDto.Authorized,
|
||||
}
|
||||
|
||||
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI006", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, zhicha.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 将响应数据转换为JSON字节
|
||||
respBytes, err := json.Marshal(respData)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/zhicha"
|
||||
"tyapi-server/internal/infrastructure/external/nuoer"
|
||||
)
|
||||
|
||||
// ProcessFLXG5A3BRequest FLXG5A3B API处理方法 - 个人司法涉诉
|
||||
@@ -20,36 +20,37 @@ func ProcessFLXG5A3BRequest(ctx context.Context, params []byte, deps *processors
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550"{
|
||||
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" || paramsDto.IDCard == "320682198910134998" || paramsDto.IDCard == "640102198708020925" || paramsDto.IDCard == "420624197310234034" || paramsDto.IDCard == "350104198501184416" || paramsDto.IDCard == "410521198606018056" || paramsDto.IDCard == "410482198504029333" || paramsDto.IDCard == "370982199012037272" || paramsDto.IDCard == "431027198810290730" || paramsDto.IDCard == "362502199510298017" || paramsDto.IDCard == "340826199008250378" || paramsDto.IDCard == "321027198304072129" || paramsDto.IDCard == "420116198907031413" || paramsDto.IDCard == "13032319930128263X" || paramsDto.IDCard == "350681198412013041" {
|
||||
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
|
||||
}
|
||||
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
|
||||
body := map[string]string{
|
||||
"name": paramsDto.Name,
|
||||
"idCard": paramsDto.IDCard,
|
||||
}
|
||||
|
||||
encryptedIDCard, err := deps.ZhichaService.Encrypt(paramsDto.IDCard)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
nuoerDoCheckAPIKey := "personalLawsuit_cv1"
|
||||
ApiPath := "/v1/doCheck"
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"name": encryptedName,
|
||||
"idCard": encryptedIDCard,
|
||||
"authorized": paramsDto.Authorized,
|
||||
}
|
||||
|
||||
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI006", reqData)
|
||||
resp, err := deps.NuoerService.CallAPI(ctx, nuoerDoCheckAPIKey, ApiPath, body)
|
||||
if err != nil {
|
||||
if errors.Is(err, zhicha.ErrDatasource) {
|
||||
if errors.Is(err, nuoer.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
if errors.Is(err, nuoer.ErrNotFound) {
|
||||
return nil, errors.Join(processors.ErrNotFound, err)
|
||||
}
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
// 将响应数据转换为JSON字节
|
||||
respBytes, err := json.Marshal(respData)
|
||||
rawData, ok := resp.Data.(map[string]interface{})
|
||||
if !ok {
|
||||
return processors.MarshalRawResponse(resp.Data)
|
||||
}
|
||||
|
||||
result := mapNuoerPersonalLawsuitToResponse(rawData)
|
||||
|
||||
respBytes, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
package flxg
|
||||
|
||||
// mapNuoerPersonalLawsuitToResponse 将 nuoer 响应转为对外结构(2.md):
|
||||
// lawsuitStat -> entout, breachCaseList -> sxbzxr, consumptionRestrictionList -> xgbzxr
|
||||
func mapNuoerPersonalLawsuitToResponse(data map[string]interface{}) map[string]interface{} {
|
||||
if data == nil {
|
||||
return defaultFLXG5A3BResponse()
|
||||
}
|
||||
if _, hasEntout := data["entout"]; hasEntout {
|
||||
return data
|
||||
}
|
||||
|
||||
flat := unwrapNuoerPersonalLawsuitData(data)
|
||||
return map[string]interface{}{
|
||||
"sxbzxr": asSlice(flat["breachCaseList"]),
|
||||
"xgbzxr": asSlice(flat["consumptionRestrictionList"]),
|
||||
"entout": pickEntout(flat["lawsuitStat"]),
|
||||
}
|
||||
}
|
||||
|
||||
func defaultFLXG5A3BResponse() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"sxbzxr": []interface{}{},
|
||||
"entout": map[string]interface{}{},
|
||||
"xgbzxr": []interface{}{},
|
||||
}
|
||||
}
|
||||
|
||||
func pickEntout(v interface{}) interface{} {
|
||||
if v == nil {
|
||||
return map[string]interface{}{}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// unwrapNuoerPersonalLawsuitData 解包 nuoer 嵌套结构 data.result.detail[],提取 1.md 平铺字段。
|
||||
func unwrapNuoerPersonalLawsuitData(data map[string]interface{}) map[string]interface{} {
|
||||
if _, ok := data["lawsuitStat"]; ok {
|
||||
return data
|
||||
}
|
||||
if result, ok := data["result"].(map[string]interface{}); ok {
|
||||
for _, item := range asSlice(result["detail"]) {
|
||||
if flat := extractFromNuoerDetailItem(asMap(item)); len(flat) > 0 {
|
||||
return flat
|
||||
}
|
||||
}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func extractFromNuoerDetailItem(item map[string]interface{}) map[string]interface{} {
|
||||
if len(item) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
lawsuitStat := extractLawsuitStatFromDetailItem(item)
|
||||
if !isLawsuitStatPayload(asMap(lawsuitStat)) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return map[string]interface{}{
|
||||
"lawsuitStat": lawsuitStat,
|
||||
"breachCaseList": extractBreachCaseRecords(item),
|
||||
"consumptionRestrictionList": extractConsumptionRestrictionRecords(item),
|
||||
}
|
||||
}
|
||||
|
||||
func extractLawsuitStatFromDetailItem(item map[string]interface{}) interface{} {
|
||||
// nuoer 真实涉诉数据通常在 breachCaseList/consumptionRestrictionList 的包装对象内
|
||||
for _, v := range asSlice(item["breachCaseList"]) {
|
||||
if m := asMap(v); m != nil {
|
||||
if ls := asMap(m["lawsuitStat"]); isLawsuitStatPayload(ls) {
|
||||
return ls
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, v := range asSlice(item["consumptionRestrictionList"]) {
|
||||
if m := asMap(v); m != nil {
|
||||
if ls := asMap(m["lawsuitStat"]); isLawsuitStatPayload(ls) {
|
||||
return ls
|
||||
}
|
||||
}
|
||||
}
|
||||
if lsWrap := asMap(item["lawsuitStat"]); lsWrap != nil {
|
||||
if ls := extractLawsuitStatFromWrapper(lsWrap); ls != nil {
|
||||
return ls
|
||||
}
|
||||
}
|
||||
return map[string]interface{}{}
|
||||
}
|
||||
|
||||
func extractLawsuitStatFromWrapper(wrap map[string]interface{}) map[string]interface{} {
|
||||
if isLawsuitStatPayload(wrap) {
|
||||
return wrap
|
||||
}
|
||||
for _, v := range asSlice(wrap["detail"]) {
|
||||
if m := asMap(v); m != nil {
|
||||
if ls := asMap(m["lawsuitStat"]); isLawsuitStatPayload(ls) {
|
||||
return ls
|
||||
}
|
||||
if isLawsuitStatPayload(m) {
|
||||
return m
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isLawsuitStatPayload(m map[string]interface{}) bool {
|
||||
if len(m) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, k := range []string{"criminal", "civil", "administrative", "cases_tree", "count", "crc", "implement", "preservation", "bankrupt"} {
|
||||
if _, ok := m[k]; ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func extractBreachCaseRecords(item map[string]interface{}) []interface{} {
|
||||
out := make([]interface{}, 0)
|
||||
collectBreachCaseRecords(asSlice(item["breachCaseList"]), &out)
|
||||
if lsWrap := asMap(item["lawsuitStat"]); lsWrap != nil {
|
||||
for _, v := range asSlice(lsWrap["detail"]) {
|
||||
collectBreachCaseRecords(asSlice(asMap(v)["breachCaseList"]), &out)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func collectBreachCaseRecords(list []interface{}, out *[]interface{}) {
|
||||
for _, v := range list {
|
||||
m := asMap(v)
|
||||
if len(m) == 0 {
|
||||
continue
|
||||
}
|
||||
if _, isWrapper := m["lawsuitStat"]; isWrapper {
|
||||
if inner := asSlice(m["breachCaseList"]); len(inner) > 0 {
|
||||
collectBreachCaseRecords(inner, out)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if _, hasAh := m["ah"]; hasAh {
|
||||
*out = append(*out, m)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func extractConsumptionRestrictionRecords(item map[string]interface{}) []interface{} {
|
||||
out := make([]interface{}, 0)
|
||||
collectConsumptionRestrictionRecords(asSlice(item["consumptionRestrictionList"]), &out)
|
||||
if lsWrap := asMap(item["lawsuitStat"]); lsWrap != nil {
|
||||
for _, v := range asSlice(lsWrap["detail"]) {
|
||||
collectConsumptionRestrictionRecords(asSlice(asMap(v)["consumptionRestrictionList"]), &out)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func collectConsumptionRestrictionRecords(list []interface{}, out *[]interface{}) {
|
||||
for _, v := range list {
|
||||
m := asMap(v)
|
||||
if len(m) == 0 {
|
||||
continue
|
||||
}
|
||||
if _, isWrapper := m["lawsuitStat"]; isWrapper {
|
||||
if inner := asSlice(m["consumptionRestrictionList"]); len(inner) > 0 {
|
||||
collectConsumptionRestrictionRecords(inner, out)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if _, hasAh := m["ah"]; hasAh {
|
||||
*out = append(*out, m)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/xingwei"
|
||||
nuoerext "tyapi-server/internal/infrastructure/external/nuoer"
|
||||
)
|
||||
|
||||
// ProcessFLXG7E8FRequest FLXG7E8F API处理方法 - 个人司法数据查询
|
||||
@@ -20,29 +20,38 @@ func ProcessFLXG7E8FRequest(ctx context.Context, params []byte, deps *processors
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" {
|
||||
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" || paramsDto.IDCard == "320682198910134998" || paramsDto.IDCard == "640102198708020925" || paramsDto.IDCard == "420624197310234034" || paramsDto.IDCard == "350104198501184416" || paramsDto.IDCard == "410521198606018056" || paramsDto.IDCard == "410482198504029333" || paramsDto.IDCard == "370982199012037272" || paramsDto.IDCard == "431027198810290730" || paramsDto.IDCard == "362502199510298017" || paramsDto.IDCard == "340826199008250378" || paramsDto.IDCard == "321027198304072129" || paramsDto.IDCard == "420116198907031413" || paramsDto.IDCard == "13032319930128263X" || paramsDto.IDCard == "350681198412013041" {
|
||||
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
|
||||
}
|
||||
// 构建请求数据,将项目规范的字段名转换为 XingweiService 需要的字段名
|
||||
reqData := map[string]interface{}{
|
||||
"name": paramsDto.Name,
|
||||
"idCardNum": paramsDto.IDCard,
|
||||
"phoneNumber": paramsDto.MobileNo,
|
||||
|
||||
body := map[string]string{
|
||||
"name": paramsDto.Name,
|
||||
"idCard": paramsDto.IDCard,
|
||||
}
|
||||
|
||||
// 调用行为数据API,使用指定的project_id
|
||||
projectID := "CDJ-1101695378264092672"
|
||||
respBytes, err := deps.XingweiService.CallAPI(ctx, projectID, reqData)
|
||||
resp, err := deps.NuoerService.CallAPI(ctx, "personalLawsuit_cv1", "/v1/doCheck", body)
|
||||
if err != nil {
|
||||
if errors.Is(err, xingwei.ErrNotFound) {
|
||||
return nil, errors.Join(processors.ErrNotFound, err)
|
||||
} else if errors.Is(err, xingwei.ErrDatasource) {
|
||||
if errors.Is(err, nuoerext.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
} else if errors.Is(err, xingwei.ErrSystem) {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
} else {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
if errors.Is(err, nuoerext.ErrNotFound) {
|
||||
return nil, errors.Join(processors.ErrNotFound, err)
|
||||
}
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
rawData, ok := resp.Data.(map[string]interface{})
|
||||
if !ok {
|
||||
return processors.MarshalRawResponse(resp.Data)
|
||||
}
|
||||
|
||||
result := map[string]interface{}{
|
||||
"judicial_data": mapFLXG7E8FToJudicialData(rawData),
|
||||
}
|
||||
|
||||
respBytes, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
package flxg
|
||||
|
||||
// mapFLXG7E8FToJudicialData 将 nuoer 响应(2.json / 2.md)转为 FLXG7E8F 对外结构(1.json / 1.md)。
|
||||
func mapFLXG7E8FToJudicialData(data map[string]interface{}) map[string]interface{} {
|
||||
if data == nil {
|
||||
return defaultFLXG7E8FJudicialData()
|
||||
}
|
||||
if _, ok := data["lawsuitStat"]; ok {
|
||||
return buildFLXG7E8FJudicialData(data)
|
||||
}
|
||||
if _, ok := data["judicial_data"]; ok {
|
||||
if jd := asMap(data["judicial_data"]); len(jd) > 0 {
|
||||
return buildFLXG7E8FJudicialData(jd)
|
||||
}
|
||||
}
|
||||
|
||||
flat := unwrapNuoerPersonalLawsuitData(data)
|
||||
return buildFLXG7E8FJudicialData(flat)
|
||||
}
|
||||
|
||||
func defaultFLXG7E8FJudicialData() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"consumptionRestrictionList": []interface{}{},
|
||||
"breachCaseList": []interface{}{},
|
||||
"lawsuitStat": defaultFLXG7E8FLawsuitStat(),
|
||||
}
|
||||
}
|
||||
|
||||
func buildFLXG7E8FJudicialData(flat map[string]interface{}) map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"consumptionRestrictionList": mapFLXG7E8FConsumptionRestrictionList(asSlice(flat["consumptionRestrictionList"])),
|
||||
"breachCaseList": mapFLXG7E8FBreachCaseList(asSlice(flat["breachCaseList"])),
|
||||
"lawsuitStat": normalizeFLXG7E8FLawsuitStat(asMap(flat["lawsuitStat"])),
|
||||
}
|
||||
}
|
||||
|
||||
func mapFLXG7E8FConsumptionRestrictionList(items []interface{}) []map[string]interface{} {
|
||||
result := make([]map[string]interface{}, 0, len(items))
|
||||
for _, item := range items {
|
||||
m := asMap(item)
|
||||
if len(m) == 0 {
|
||||
continue
|
||||
}
|
||||
if _, ok := m["caseNumber"]; ok {
|
||||
result = append(result, m)
|
||||
continue
|
||||
}
|
||||
|
||||
result = append(result, map[string]interface{}{
|
||||
"caseNumber": m["ah"],
|
||||
"id": m["id"],
|
||||
"issueDate": m["fbrq"],
|
||||
"executiveCourt": m["zxfy"],
|
||||
"fileDate": m["larq"],
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func mapFLXG7E8FBreachCaseList(items []interface{}) []map[string]interface{} {
|
||||
result := make([]map[string]interface{}, 0, len(items))
|
||||
for _, item := range items {
|
||||
m := asMap(item)
|
||||
if len(m) == 0 {
|
||||
continue
|
||||
}
|
||||
if _, ok := m["caseNumber"]; ok {
|
||||
result = append(result, m)
|
||||
continue
|
||||
}
|
||||
|
||||
result = append(result, map[string]interface{}{
|
||||
"caseNumber": m["ah"],
|
||||
"issueDate": m["fbrq"],
|
||||
"id": m["id"],
|
||||
"fileDate": m["larq"],
|
||||
"fulfillStatus": m["lxqk"],
|
||||
"estimatedJudgementAmount": m["pjje_gj"],
|
||||
"province": m["sf"],
|
||||
"sex": m["xb"],
|
||||
"concreteDetails": m["xwqx"],
|
||||
"obligation": m["yw"],
|
||||
"executiveCourt": m["zxfy"],
|
||||
"enforcementBasisOrganization": m["zxyjdw"],
|
||||
"enforcementBasisNumber": m["zxyjwh"],
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func normalizeFLXG7E8FLawsuitStat(src map[string]interface{}) map[string]interface{} {
|
||||
lawsuitStat := defaultFLXG7E8FLawsuitStat()
|
||||
for k, v := range src {
|
||||
switch k {
|
||||
case "cases_tree":
|
||||
lawsuitStat[k] = normalizeFLXG7E8FCasesTree(asMap(v))
|
||||
case "count":
|
||||
lawsuitStat[k] = normalizeFLXG7E8FCount(asMap(v))
|
||||
case "preservation", "administrative", "civil", "implement", "criminal", "bankrupt":
|
||||
lawsuitStat[k] = normalizeFLXG7E8FCaseSection(asMap(v))
|
||||
default:
|
||||
lawsuitStat[k] = v
|
||||
}
|
||||
}
|
||||
return lawsuitStat
|
||||
}
|
||||
|
||||
func defaultFLXG7E8FLawsuitStat() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"crc": 0,
|
||||
"cases_tree": normalizeFLXG7E8FCasesTree(map[string]interface{}{}),
|
||||
"count": normalizeFLXG7E8FCount(map[string]interface{}{}),
|
||||
"preservation": normalizeFLXG7E8FCaseSection(map[string]interface{}{}),
|
||||
"administrative": normalizeFLXG7E8FCaseSection(map[string]interface{}{}),
|
||||
"civil": normalizeFLXG7E8FCaseSection(map[string]interface{}{}),
|
||||
"implement": normalizeFLXG7E8FCaseSection(map[string]interface{}{}),
|
||||
"criminal": normalizeFLXG7E8FCaseSection(map[string]interface{}{}),
|
||||
"bankrupt": normalizeFLXG7E8FCaseSection(map[string]interface{}{}),
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeFLXG7E8FCasesTree(src map[string]interface{}) map[string]interface{} {
|
||||
dst := map[string]interface{}{
|
||||
"administrative": []interface{}{},
|
||||
"criminal": []interface{}{},
|
||||
"civil": []interface{}{},
|
||||
}
|
||||
for _, key := range []string{"administrative", "criminal", "civil"} {
|
||||
if v, ok := src[key]; ok {
|
||||
dst[key] = asSlice(v)
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func normalizeFLXG7E8FCaseSection(src map[string]interface{}) map[string]interface{} {
|
||||
dst := map[string]interface{}{
|
||||
"cases": []interface{}{},
|
||||
"count": normalizeFLXG7E8FCount(map[string]interface{}{}),
|
||||
}
|
||||
if v, ok := src["cases"]; ok {
|
||||
dst["cases"] = asSlice(v)
|
||||
}
|
||||
if v, ok := src["count"]; ok {
|
||||
dst["count"] = normalizeFLXG7E8FCount(asMap(v))
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func normalizeFLXG7E8FCount(src map[string]interface{}) map[string]interface{} {
|
||||
dst := map[string]interface{}{
|
||||
"money_yuangao": 0,
|
||||
"area_stat": "",
|
||||
"count_jie_beigao": 0,
|
||||
"count_total": 0,
|
||||
"money_wei_yuangao": 0,
|
||||
"count_wei_total": 0,
|
||||
"money_wei_beigao": 0,
|
||||
"count_other": 0,
|
||||
"money_beigao": 0,
|
||||
"count_yuangao": 0,
|
||||
"money_jie_other": 0,
|
||||
"money_total": 0,
|
||||
"money_wei_total": 0,
|
||||
"count_wei_yuangao": 0,
|
||||
"ay_stat": "",
|
||||
"count_beigao": 0,
|
||||
"money_jie_yuangao": 0,
|
||||
"jafs_stat": "",
|
||||
"money_jie_beigao": 0,
|
||||
"count_wei_beigao": 0,
|
||||
"count_jie_other": 0,
|
||||
"count_jie_total": 0,
|
||||
"count_wei_other": 0,
|
||||
"money_other": 0,
|
||||
"count_jie_yuangao": 0,
|
||||
"money_jie_total": 0,
|
||||
"money_wei_other": 0,
|
||||
"money_wei_percent": 0,
|
||||
"larq_stat": "",
|
||||
}
|
||||
for k, v := range src {
|
||||
dst[k] = v
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func asMap(v interface{}) map[string]interface{} {
|
||||
if v == nil {
|
||||
return map[string]interface{}{}
|
||||
}
|
||||
if m, ok := v.(map[string]interface{}); ok {
|
||||
return m
|
||||
}
|
||||
return map[string]interface{}{}
|
||||
}
|
||||
|
||||
func asSlice(v interface{}) []interface{} {
|
||||
if v == nil {
|
||||
return []interface{}{}
|
||||
}
|
||||
if s, ok := v.([]interface{}); ok {
|
||||
return s
|
||||
}
|
||||
return []interface{}{}
|
||||
}
|
||||
@@ -22,33 +22,24 @@ func ProcessFLXG8B4DRequest(ctx context.Context, params []byte, deps *processors
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
// 三选一校验:MobileNo、IDCard、BankCard 必须且只能有一个
|
||||
var fieldCount int
|
||||
// 从 MobileNo、IDCard、BankCard 中按优先级选择一个(MobileNo > IDCard > BankCard)
|
||||
var selectedField string
|
||||
var selectedValue string
|
||||
|
||||
if paramsDto.MobileNo != "" {
|
||||
fieldCount++
|
||||
selectedField = "mobile_no"
|
||||
selectedValue = paramsDto.MobileNo
|
||||
}
|
||||
if paramsDto.IDCard != "" {
|
||||
fieldCount++
|
||||
} else if paramsDto.IDCard != "" {
|
||||
selectedField = "id_card"
|
||||
selectedValue = paramsDto.IDCard
|
||||
}
|
||||
if paramsDto.BankCard != "" {
|
||||
fieldCount++
|
||||
} else if paramsDto.BankCard != "" {
|
||||
selectedField = "bank_card"
|
||||
selectedValue = paramsDto.BankCard
|
||||
}
|
||||
|
||||
if fieldCount == 0 {
|
||||
if selectedField == "" {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, errors.New("必须提供手机号、身份证号或银行卡号中的其中一个"))
|
||||
}
|
||||
if fieldCount > 1 {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, errors.New("只能提供手机号、身份证号或银行卡号中的一个,不能同时提供多个"))
|
||||
}
|
||||
|
||||
// 只对选中的字段进行加密
|
||||
var encryptedValue string
|
||||
@@ -77,12 +68,12 @@ func ProcessFLXG8B4DRequest(ctx context.Context, params []byte, deps *processors
|
||||
}
|
||||
|
||||
switch selectedField {
|
||||
case "mobile_no":
|
||||
reqData["phone"] = encryptedValue
|
||||
case "id_card":
|
||||
reqData["idCard"] = encryptedValue
|
||||
case "bank_card":
|
||||
reqData["name"] = encryptedValue
|
||||
case "mobile_no":
|
||||
reqData["phone"] = encryptedValue
|
||||
}
|
||||
|
||||
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI027", reqData)
|
||||
|
||||
@@ -20,7 +20,9 @@ func ProcessFLXG9C1DRequest(ctx context.Context, params []byte, deps *processors
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
if paramsDto.IDCard == "410482198504029333" {
|
||||
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
|
||||
}
|
||||
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
|
||||
@@ -20,7 +20,7 @@ func ProcessFLXGCA3DRequest(ctx context.Context, params []byte, deps *processors
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" {
|
||||
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" || paramsDto.IDCard == "320682198910134998" || paramsDto.IDCard == "640102198708020925" || paramsDto.IDCard == "420624197310234034" || paramsDto.IDCard == "350104198501184416" || paramsDto.IDCard == "410521198606018056" || paramsDto.IDCard == "410482198504029333" || paramsDto.IDCard == "370982199012037272" || paramsDto.IDCard == "431027198810290730" || paramsDto.IDCard == "362502199510298017" || paramsDto.IDCard == "340826199008250378" || paramsDto.IDCard == "321027198304072129" || paramsDto.IDCard == "420116198907031413" || paramsDto.IDCard == "13032319930128263X" || paramsDto.IDCard == "350681198412013041" {
|
||||
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
|
||||
}
|
||||
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
|
||||
|
||||
@@ -20,6 +20,10 @@ func ProcessFLXGDEA8Request(ctx context.Context, params []byte, deps *processors
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
// 去掉司法案件案件去掉身份证号码
|
||||
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" || paramsDto.IDCard == "320682198910134998" || paramsDto.IDCard == "640102198708020925" || paramsDto.IDCard == "420624197310234034" || paramsDto.IDCard == "350104198501184416" || paramsDto.IDCard == "410521198606018056" || paramsDto.IDCard == "410482198504029333" || paramsDto.IDCard == "370982199012037272" || paramsDto.IDCard == "431027198810290730" || paramsDto.IDCard == "362502199510298017" || paramsDto.IDCard == "340826199008250378" || paramsDto.IDCard == "321027198304072129" || paramsDto.IDCard == "420116198907031413" || paramsDto.IDCard == "13032319930128263X" || paramsDto.IDCard == "350681198412013041" {
|
||||
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
|
||||
}
|
||||
|
||||
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
|
||||
if err != nil {
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/zhicha"
|
||||
"tyapi-server/internal/infrastructure/external/nuoer"
|
||||
)
|
||||
|
||||
// ProcessFLXGDEA9Request FLXGDEA9 API处理方法
|
||||
@@ -21,34 +21,36 @@ func ProcessFLXGDEA9Request(ctx context.Context, params []byte, deps *processors
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550"{
|
||||
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" || paramsDto.IDCard == "320682198910134998" || paramsDto.IDCard == "640102198708020925" || paramsDto.IDCard == "420624197310234034" || paramsDto.IDCard == "350104198501184416" || paramsDto.IDCard == "410521198606018056" || paramsDto.IDCard == "410482198504029333" || paramsDto.IDCard == "370982199012037272" || paramsDto.IDCard == "431027198810290730" || paramsDto.IDCard == "362502199510298017" || paramsDto.IDCard == "340826199008250378" || paramsDto.IDCard == "321027198304072129" || paramsDto.IDCard == "420116198907031413" || paramsDto.IDCard == "13032319930128263X" || paramsDto.IDCard == "350681198412013041" {
|
||||
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
|
||||
}
|
||||
encryptedIDCard, err := deps.ZhichaService.Encrypt(paramsDto.IDCard)
|
||||
body := map[string]string{
|
||||
"name": paramsDto.Name,
|
||||
"idCard": paramsDto.IDCard,
|
||||
}
|
||||
|
||||
nuoerDoCheckAPIKey := "idRiskTagV106"
|
||||
ApiPath := "/v1/doCheck"
|
||||
|
||||
resp, err := deps.NuoerService.CallAPI(ctx, nuoerDoCheckAPIKey, ApiPath, body)
|
||||
if err != nil {
|
||||
if errors.Is(err, nuoer.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
}
|
||||
if errors.Is(err, nuoer.ErrNotFound) {
|
||||
return nil, errors.Join(processors.ErrNotFound, err)
|
||||
}
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
reqData := map[string]interface{}{
|
||||
"name": encryptedName,
|
||||
"idCard": encryptedIDCard,
|
||||
"authorized": paramsDto.Authorized,
|
||||
|
||||
rawData, ok := resp.Data.(map[string]interface{})
|
||||
if !ok {
|
||||
return processors.MarshalRawResponse(resp.Data)
|
||||
}
|
||||
|
||||
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI005", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, zhicha.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
result := mapNuoerIdRiskToResponse(rawData)
|
||||
|
||||
// 将响应数据转换为JSON字节
|
||||
respBytes, err := json.Marshal(respData)
|
||||
respBytes, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
package flxg
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// riskCode 位序(从左至右 1-25)到 level 代码映射,见 1.md 与风险类型代码表。
|
||||
// 多位命中时去重后以逗号拼接,如 "A,B"。
|
||||
var riskCodeBitLevelMappings = map[int]string{
|
||||
// A — 前科:侵犯公民人身权利、民主权利(在逃、盗窃、诈骗、抢劫、故意伤害、强奸等在刑或前科等)
|
||||
1: "A", // 是否 SX(涉刑)
|
||||
11: "A", // 近5年其他 SX 案
|
||||
12: "A", // 5年前 SX 案
|
||||
16: "A", // 是否 ZT(在逃)
|
||||
|
||||
// B — 经济类前科(破坏金融秩序、非法吸存、违发贷款、金融诈骗、集资诈骗、保险诈骗、假币等在刑或前科等)
|
||||
2: "B2", // 近5年破坏金融管理秩序
|
||||
3: "B", // 近5年非法吸收公众存款
|
||||
4: "B", // 近5年违法发放贷款
|
||||
5: "B4", // 近5年洗钱
|
||||
6: "B3", // 近5年金融诈骗
|
||||
7: "B3", // 近5年集资诈骗
|
||||
8: "B3", // 近5年贷款诈骗
|
||||
9: "B3", // 近5年信用卡诈骗
|
||||
10: "B3", // 近5年保险诈骗
|
||||
|
||||
// C — 妨害社会管理秩序(扰乱公共秩序、妨害司法、涉毒、涉黄等在刑或前科等)
|
||||
14: "C3", // 是否 SD(涉毒)
|
||||
15: "C3", // 是否 XD(吸毒)
|
||||
|
||||
// D — 重点(危害国家、公共安全,涉恐、疆藏,涉稳、涉黑、涉及境外等)
|
||||
13: "D", // 是否 SA(涉案)
|
||||
17: "D", // 是否 ZD(重点)
|
||||
18: "D4", // 是否 SK 人员(涉恐、疆藏)
|
||||
19: "D2", // 是否 SW 人员(涉稳)
|
||||
20: "D5", // 是否其他 ZD 人员(涉黑)
|
||||
|
||||
// E — 涉交通案件(危险驾驶、交通肇事等)
|
||||
21: "E", // 是否 SJTLAJ
|
||||
22: "E", // 近5年危险驾驶
|
||||
23: "E", // 近5年交通肇事
|
||||
24: "E", // 5年前 SJTLAJ
|
||||
}
|
||||
|
||||
// mapNuoerIdRiskToResponse 将 nuoer 响应(2json.md)转为对外结构(1json.md):
|
||||
// 解包 result,将 riskCode 转为 level。
|
||||
func mapNuoerIdRiskToResponse(data map[string]interface{}) map[string]interface{} {
|
||||
if data == nil {
|
||||
return map[string]interface{}{"level": "0"}
|
||||
}
|
||||
if level, ok := data["level"]; ok && level != nil {
|
||||
if s := strings.TrimSpace(stringifyRiskCodeVal(level)); s != "" {
|
||||
return map[string]interface{}{"level": s}
|
||||
}
|
||||
}
|
||||
|
||||
payload := unwrapNuoerIdRiskData(data)
|
||||
riskCode := strings.TrimSpace(stringifyRiskCodeVal(payload["riskCode"]))
|
||||
return map[string]interface{}{
|
||||
"level": mapRiskCodeToLevel(riskCode),
|
||||
}
|
||||
}
|
||||
|
||||
func unwrapNuoerIdRiskData(data map[string]interface{}) map[string]interface{} {
|
||||
if _, ok := data["riskCode"]; ok {
|
||||
return data
|
||||
}
|
||||
if result, ok := data["result"].(map[string]interface{}); ok {
|
||||
return result
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func mapRiskCodeToLevel(riskCode string) string {
|
||||
if riskCode == "" {
|
||||
return "0"
|
||||
}
|
||||
|
||||
codes := make([]string, 0, 8)
|
||||
for i, ch := range riskCode {
|
||||
if ch != '1' {
|
||||
continue
|
||||
}
|
||||
pos := i + 1
|
||||
if pos == 25 {
|
||||
continue
|
||||
}
|
||||
if code, ok := riskCodeBitLevelMappings[pos]; ok && code != "" {
|
||||
codes = append(codes, code)
|
||||
}
|
||||
}
|
||||
|
||||
codes = dedupeLevelCodes(codes)
|
||||
codes = collapseParentLevelCodes(codes)
|
||||
if len(codes) == 0 {
|
||||
return "0"
|
||||
}
|
||||
return strings.Join(codes, ",")
|
||||
}
|
||||
|
||||
func dedupeLevelCodes(codes []string) []string {
|
||||
if len(codes) == 0 {
|
||||
return codes
|
||||
}
|
||||
seen := make(map[string]struct{}, len(codes))
|
||||
out := make([]string, 0, len(codes))
|
||||
for _, code := range codes {
|
||||
if _, ok := seen[code]; ok {
|
||||
continue
|
||||
}
|
||||
seen[code] = struct{}{}
|
||||
out = append(out, code)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func collapseParentLevelCodes(codes []string) []string {
|
||||
if len(codes) == 0 {
|
||||
return codes
|
||||
}
|
||||
hasChild := func(parent string) bool {
|
||||
for _, code := range codes {
|
||||
if code != parent && strings.HasPrefix(code, parent) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
out := make([]string, 0, len(codes))
|
||||
for _, code := range codes {
|
||||
switch code {
|
||||
case "A", "B", "C", "D":
|
||||
if hasChild(code) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
out = append(out, code)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func stringifyRiskCodeVal(v interface{}) string {
|
||||
if v == nil {
|
||||
return ""
|
||||
}
|
||||
switch val := v.(type) {
|
||||
case string:
|
||||
return val
|
||||
case float64:
|
||||
if val == float64(int64(val)) {
|
||||
return strconv.FormatInt(int64(val), 10)
|
||||
}
|
||||
return strconv.FormatFloat(val, 'f', -1, 64)
|
||||
case int:
|
||||
return strconv.Itoa(val)
|
||||
case int64:
|
||||
return strconv.FormatInt(val, 10)
|
||||
default:
|
||||
return fmt.Sprint(val)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package flxg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/shujubao"
|
||||
)
|
||||
|
||||
// ProcessFLXGDJG3Request FLXGDJG3 董监高司法综合信息核验 API 处理方法(使用数据宝服务示例)
|
||||
func ProcessFLXGDJG3Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.FLXGDJG3Req
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
// 构建数据宝入参(sign 外的业务参数可按需 AES 加密后作为 bodyData)
|
||||
reqParams := map[string]interface{}{
|
||||
"key": "1cce582f0a6f3ca40de80f1bea9b9698",
|
||||
"idcard": paramsDto.IDCard,
|
||||
}
|
||||
|
||||
// 最终请求 URL = https://api.chinadatapay.com/communication + 拼接接口地址值,如 personal/197
|
||||
apiPath := "/communication/personal/10166"
|
||||
data, err := deps.ShujubaoService.CallAPI(ctx, apiPath, reqParams)
|
||||
if err != nil {
|
||||
if errors.Is(err, shujubao.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
}
|
||||
if errors.Is(err, shujubao.ErrQueryEmpty) {
|
||||
return nil, errors.Join(processors.ErrNotFound, err)
|
||||
}
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
// 解析响应中的 JSON 字符串(使用 qyglb4c0 中的 RecursiveParse)
|
||||
parsedResp, err := RecursiveParse(data)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
respBytes, err := json.Marshal(parsedResp)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package flxg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/haiyuapi"
|
||||
)
|
||||
|
||||
// 去掉身份证去掉身份证
|
||||
// ProcessFLXGHB4FRequest FLXGHB4F API处理方法 - 个人涉诉案件查询(海宇API)
|
||||
func ProcessFLXGHB4FRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.FLXGHB4FReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
if deps.HaiyuapiService == nil {
|
||||
return nil, errors.Join(processors.ErrSystem, errors.New("海宇API服务未初始化"))
|
||||
}
|
||||
// 去掉司法案件案件去掉身份证号码
|
||||
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" || paramsDto.IDCard == "320682198910134998" || paramsDto.IDCard == "640102198708020925" || paramsDto.IDCard == "420624197310234034" || paramsDto.IDCard == "350104198501184416" || paramsDto.IDCard == "410521198606018056" || paramsDto.IDCard == "410482198504029333" || paramsDto.IDCard == "370982199012037272" || paramsDto.IDCard == "431027198810290730" || paramsDto.IDCard == "362502199510298017" || paramsDto.IDCard == "340826199008250378" || paramsDto.IDCard == "321027198304072129" || paramsDto.IDCard == "420116198907031413" || paramsDto.IDCard == "13032319930128263X" || paramsDto.IDCard == "350681198412013041" {
|
||||
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
|
||||
}
|
||||
|
||||
reqParams := map[string]interface{}{
|
||||
"name": paramsDto.Name,
|
||||
"id_card": paramsDto.IDCard,
|
||||
"auth_pdf_base64": paramsDto.AuthAuthorizeFileBase64,
|
||||
}
|
||||
|
||||
apiPath := "/api/v1/FLXGHB4F"
|
||||
respBytes, err := deps.HaiyuapiService.CallAPI(ctx, apiPath, reqParams)
|
||||
if err != nil {
|
||||
if errors.Is(err, haiyuapi.ErrNotFound) {
|
||||
return nil, errors.Join(processors.ErrNotFound, err)
|
||||
}
|
||||
if errors.Is(err, haiyuapi.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
}
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -20,7 +20,9 @@ func ProcessFLXGK5D2Request(ctx context.Context, params []byte, deps *processors
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
if paramsDto.IDCard == "410482198504029333" {
|
||||
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
|
||||
}
|
||||
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
package ivyz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/shujubao"
|
||||
)
|
||||
|
||||
// ProcessIVYZ18HYRequest IVYZ18HY 婚姻状况核验V2(单人) API 处理方法(使用数据宝服务示例)
|
||||
func ProcessIVYZ18HYRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.IVYZ18HYReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
fixedData := map[string]interface{}{"msg": "请联系商务咨询"}
|
||||
fixedRespBytes, err := json.Marshal(fixedData)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
return fixedRespBytes, nil
|
||||
|
||||
authDate := ""
|
||||
if len(paramsDto.AuthDate) >= 8 {
|
||||
authDate = paramsDto.AuthDate[len(paramsDto.AuthDate)-8:]
|
||||
}
|
||||
reqParams := map[string]interface{}{
|
||||
"key": "",
|
||||
"idcard": paramsDto.IDCard,
|
||||
"name": paramsDto.Name,
|
||||
"maritalType": paramsDto.MaritalType,
|
||||
"authcode": paramsDto.AuthAuthorizeFileBase64,
|
||||
"authAuthorizeFileCode": paramsDto.AuthAuthorizeFileCode,
|
||||
"authDate": authDate,
|
||||
}
|
||||
|
||||
// 最终请求 URL = https://api.chinadatapay.com/communication + 拼接接口地址值,如 personal/197
|
||||
apiPath := "/communication/personal/10333"
|
||||
data, err := deps.ShujubaoService.CallAPI(ctx, apiPath, reqParams)
|
||||
if err != nil {
|
||||
|
||||
if errors.Is(err, shujubao.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
}
|
||||
if errors.Is(err, shujubao.ErrQueryEmpty) {
|
||||
return nil, errors.Join(processors.ErrNotFound, err)
|
||||
}
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
respBytes, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package ivyz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/shujubao"
|
||||
)
|
||||
|
||||
// ProcessIVYZ28HYRequest IVYZ28HY 婚姻状况核验单人) API 处理方法(使用数据宝服务示例)
|
||||
func ProcessIVYZ28HYRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.IVYZ28HYReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
fixedData := map[string]interface{}{"msg": "请联系商务咨询"}
|
||||
fixedRespBytes, err := json.Marshal(fixedData)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
return fixedRespBytes, nil
|
||||
|
||||
reqParams := map[string]interface{}{
|
||||
"key": "",
|
||||
"idcard": paramsDto.IDCard,
|
||||
"name": paramsDto.Name,
|
||||
}
|
||||
|
||||
// 最终请求 URL = https://api.chinadatapay.com/communication + 拼接接口地址值,如 personal/197
|
||||
apiPath := "/communication/personal/10149"
|
||||
data, err := deps.ShujubaoService.CallAPI(ctx, apiPath, reqParams)
|
||||
if err != nil {
|
||||
if errors.Is(err, shujubao.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
}
|
||||
if errors.Is(err, shujubao.ErrQueryEmpty) {
|
||||
return nil, errors.Join(processors.ErrNotFound, err)
|
||||
}
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
respBytes, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -4,13 +4,26 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/zhicha"
|
||||
"tyapi-server/internal/infrastructure/external/shumai"
|
||||
)
|
||||
|
||||
// ProcessIVYZ2A8BRequest IVYZ2A8B API处理方法 - 身份二要素认证
|
||||
// ivyz2a8bShumaiResp 数脉 /v4/id_card/check 返回格式
|
||||
type ivyz2a8bShumaiResp struct {
|
||||
Result float64 `json:"result"`
|
||||
OrderNo string `json:"order_no"`
|
||||
Desc string `json:"desc"`
|
||||
Sex string `json:"sex"`
|
||||
Birthday string `json:"birthday"` // yyyyMMdd
|
||||
Address string `json:"address"`
|
||||
}
|
||||
|
||||
// ProcessIVYZ2A8BRequest IVYZ2A8B API处理方法 - 身份二要素认证政务版 数脉内部替换
|
||||
func ProcessIVYZ2A8BRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.IVYZ2A8BReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
@@ -20,37 +33,129 @@ func ProcessIVYZ2A8BRequest(ctx context.Context, params []byte, deps *processors
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
reqFormData := map[string]interface{}{
|
||||
"idcard": paramsDto.IDCard,
|
||||
"name": paramsDto.Name,
|
||||
}
|
||||
|
||||
encryptedIDCard, err := deps.ZhichaService.Encrypt(paramsDto.IDCard)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
// 以表单方式调用数脉 API;参数在 CallAPIForm 内转为 application/x-www-form-urlencoded
|
||||
apiPath := "/v4/id_card/check" // 接口路径,根据数脉文档填写(如 v4/xxx)
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"name": encryptedName,
|
||||
"idCard": encryptedIDCard,
|
||||
"authorized": paramsDto.Authorized,
|
||||
}
|
||||
|
||||
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI001", reqData)
|
||||
// 先尝试使用政务接口(app_id2 和 app_secret2)
|
||||
respBytes, err := deps.ShumaiService.CallAPIForm(ctx, apiPath, reqFormData, true)
|
||||
if err != nil {
|
||||
if errors.Is(err, zhicha.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
// 使用实时接口(app_id 和 app_secret)重试
|
||||
respBytes, err = deps.ShumaiService.CallAPIForm(ctx, apiPath, reqFormData, false)
|
||||
// 如果重试后仍然失败,或者原本就是查无记录错误,返回错误
|
||||
if err != nil {
|
||||
if errors.Is(err, shumai.ErrNotFound) {
|
||||
// 查无记录情况
|
||||
return nil, errors.Join(processors.ErrNotFound, err)
|
||||
} else if errors.Is(err, shumai.ErrDatasource) {
|
||||
// 数据源错误
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
} else if errors.Is(err, shumai.ErrSystem) {
|
||||
// 系统错误
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
} else {
|
||||
// 其他未知错误
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 将响应数据转换为JSON字节
|
||||
respBytes, err := json.Marshal(respData)
|
||||
// 将数脉返回的新格式映射为原有 API 输出格式
|
||||
oldFormat, err := mapIVYZ2A8BShumaiToOld(respBytes)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
return json.Marshal(oldFormat)
|
||||
}
|
||||
|
||||
func mapIVYZ2A8BShumaiToOld(respBytes []byte) (map[string]interface{}, error) {
|
||||
var r ivyz2a8bShumaiResp
|
||||
if err := json.Unmarshal(respBytes, &r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// final_auth_result: "0"=一致,"1"=不一致/无记录;按 result:0-一致,1-不一致,2-无记录(预留)
|
||||
finalAuth := "1"
|
||||
switch int(r.Result) {
|
||||
case 0:
|
||||
finalAuth = "0"
|
||||
}
|
||||
return map[string]interface{}{
|
||||
"final_auth_result": finalAuth,
|
||||
"birthday": formatBirthdayOld(r.Birthday),
|
||||
"address": r.Address,
|
||||
"constellation": constellationFromBirthday(r.Birthday),
|
||||
"gender": r.Sex,
|
||||
"age": ageFromBirthday(r.Birthday),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func formatBirthdayOld(s string) string {
|
||||
s = strings.TrimSpace(s)
|
||||
if len(s) != 8 {
|
||||
return s
|
||||
}
|
||||
for _, c := range s {
|
||||
if c < '0' || c > '9' {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return s[0:4] + "年" + s[4:6] + "月" + s[6:8] + "日"
|
||||
}
|
||||
|
||||
func constellationFromBirthday(s string) string {
|
||||
if len(s) != 8 {
|
||||
return ""
|
||||
}
|
||||
month, _ := strconv.Atoi(s[4:6])
|
||||
day, _ := strconv.Atoi(s[6:8])
|
||||
if month < 1 || month > 12 || day < 1 || day > 31 {
|
||||
return ""
|
||||
}
|
||||
switch {
|
||||
case (month == 12 && day >= 22) || (month == 1 && day <= 19):
|
||||
return "魔羯座"
|
||||
case (month == 1 && day >= 20) || (month == 2 && day <= 18):
|
||||
return "水瓶座"
|
||||
case (month == 2 && day >= 19) || (month == 3 && day <= 20):
|
||||
return "双鱼座"
|
||||
case (month == 3 && day >= 21) || (month == 4 && day <= 19):
|
||||
return "白羊座"
|
||||
case (month == 4 && day >= 20) || (month == 5 && day <= 20):
|
||||
return "金牛座"
|
||||
case (month == 5 && day >= 21) || (month == 6 && day <= 21):
|
||||
return "双子座"
|
||||
case (month == 6 && day >= 22) || (month == 7 && day <= 22):
|
||||
return "巨蟹座"
|
||||
case (month == 7 && day >= 23) || (month == 8 && day <= 22):
|
||||
return "狮子座"
|
||||
case (month == 8 && day >= 23) || (month == 9 && day <= 22):
|
||||
return "处女座"
|
||||
case (month == 9 && day >= 23) || (month == 10 && day <= 23):
|
||||
return "天秤座"
|
||||
case (month == 10 && day >= 24) || (month == 11 && day <= 22):
|
||||
return "天蝎座"
|
||||
case (month == 11 && day >= 23) || (month == 12 && day <= 21):
|
||||
return "射手座"
|
||||
default:
|
||||
return "魔羯座"
|
||||
}
|
||||
}
|
||||
|
||||
func ageFromBirthday(s string) string {
|
||||
if len(s) < 4 {
|
||||
return ""
|
||||
}
|
||||
y, err := strconv.Atoi(s[0:4])
|
||||
if err != nil || y <= 0 {
|
||||
return ""
|
||||
}
|
||||
age := time.Now().Year() - y
|
||||
if age < 0 {
|
||||
age = 0
|
||||
}
|
||||
return strconv.Itoa(age)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package ivyz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/zhicha"
|
||||
)
|
||||
|
||||
// ProcessIVYZ2MN6Request IVYZ2MN6 API处理方法
|
||||
func ProcessIVYZ2MN6Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.IVYZ2MN6Req
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedIDCard, err := deps.ZhichaService.Encrypt(paramsDto.IDCard)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"name": encryptedName,
|
||||
"idCard": encryptedIDCard,
|
||||
"authorized": paramsDto.Authorized,
|
||||
}
|
||||
|
||||
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI1004", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, zhicha.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
}
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
respBytes, err := json.Marshal(respData)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package ivyz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/zhicha"
|
||||
)
|
||||
|
||||
// ProcessIVYZ2MN7Request IVYZ2MN7 API处理方法
|
||||
func ProcessIVYZ2MN7Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.IVYZ2MN6Req
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedIDCard, err := deps.ZhichaService.Encrypt(paramsDto.IDCard)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"name": encryptedName,
|
||||
"idCard": encryptedIDCard,
|
||||
"authorized": paramsDto.Authorized,
|
||||
}
|
||||
|
||||
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI035", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, zhicha.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
}
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
respBytes, err := json.Marshal(respData)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package ivyz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/shujubao"
|
||||
)
|
||||
|
||||
// ProcessIVYZ38SRRequest IVYZ38SR 婚姻状态核验(双人) API 处理方法(使用数据宝服务示例)
|
||||
func ProcessIVYZ38SRRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.IVYZ38SRReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
fixedData := map[string]interface{}{"msg": "请联系商务咨询"}
|
||||
fixedRespBytes, err := json.Marshal(fixedData)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
return fixedRespBytes, nil
|
||||
|
||||
reqParams := map[string]interface{}{
|
||||
"key": "",
|
||||
"name": paramsDto.ManName,
|
||||
"idcard": paramsDto.ManIDCard,
|
||||
"woman_name": paramsDto.WomanName,
|
||||
"woman_idcard": paramsDto.WomanIDCard,
|
||||
}
|
||||
|
||||
// 最终请求 URL = https://api.chinadatapay.com/communication + 拼接接口地址值,如 personal/197
|
||||
apiPath := "/communication/personal/10148"
|
||||
data, err := deps.ShujubaoService.CallAPI(ctx, apiPath, reqParams)
|
||||
if err != nil {
|
||||
if errors.Is(err, shujubao.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
}
|
||||
if errors.Is(err, shujubao.ErrQueryEmpty) {
|
||||
return nil, errors.Join(processors.ErrNotFound, err)
|
||||
}
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
respBytes, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package ivyz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/muzi"
|
||||
)
|
||||
|
||||
// ProcessIVYZ3P9MRequest IVYZ3P9M API处理方法 - 学历查询实时版
|
||||
func ProcessIVYZ3P9MRequest_2(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.IVYZ3P9MReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
encryptedName, err := deps.MuziService.Encrypt(paramsDto.Name)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedCertCode, err := deps.MuziService.Encrypt(paramsDto.IDCard)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
// 处理 returnType 参数,默认为 "1"
|
||||
returnType := paramsDto.ReturnType
|
||||
if returnType == "" {
|
||||
returnType = "1"
|
||||
}
|
||||
paramSign := map[string]interface{}{
|
||||
"returnType": returnType,
|
||||
"realName": encryptedName,
|
||||
"certCode": encryptedCertCode,
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"realName": encryptedName,
|
||||
"certCode": encryptedCertCode,
|
||||
"returnType": returnType,
|
||||
}
|
||||
|
||||
respData, err := deps.MuziService.CallAPI(ctx, "PC0041", "/academic", reqData, paramSign)
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, muzi.ErrDatasource):
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
case errors.Is(err, muzi.ErrSystem):
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
default:
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
return respData, nil
|
||||
}
|
||||
@@ -4,10 +4,11 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/muzi"
|
||||
"tyapi-server/internal/infrastructure/external/zhicha"
|
||||
)
|
||||
|
||||
// ProcessIVYZ3P9MRequest IVYZ3P9M API处理方法 - 学历查询实时版
|
||||
@@ -21,45 +22,147 @@ func ProcessIVYZ3P9MRequest(ctx context.Context, params []byte, deps *processors
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
encryptedName, err := deps.MuziService.Encrypt(paramsDto.Name)
|
||||
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedCertCode, err := deps.MuziService.Encrypt(paramsDto.IDCard)
|
||||
encryptedIDCard, err := deps.ZhichaService.Encrypt(paramsDto.IDCard)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
// 处理 returnType 参数,默认为 "1"
|
||||
returnType := paramsDto.ReturnType
|
||||
if returnType == "" {
|
||||
returnType = "1"
|
||||
}
|
||||
paramSign := map[string]interface{}{
|
||||
"returnType": returnType,
|
||||
"realName": encryptedName,
|
||||
"certCode": encryptedCertCode,
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"realName": encryptedName,
|
||||
"certCode": encryptedCertCode,
|
||||
"returnType": returnType,
|
||||
"name": encryptedName,
|
||||
"idCard": encryptedIDCard,
|
||||
"authorized": "1",
|
||||
}
|
||||
|
||||
|
||||
respData, err := deps.MuziService.CallAPI(ctx, "PC0041", "/academic",reqData,paramSign)
|
||||
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI1004", reqData)
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, muzi.ErrDatasource):
|
||||
if errors.Is(err, zhicha.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
case errors.Is(err, muzi.ErrSystem):
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
default:
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
out, err := mapZCI1004ToIVYZ3P9M(respData, paramsDto.Name, paramsDto.IDCard)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
return json.Marshal(out)
|
||||
}
|
||||
|
||||
type zci1004Item struct {
|
||||
EndDate string `json:"endDate"`
|
||||
EducationLevel string `json:"educationLevel"`
|
||||
LearningForm string `json:"learningForm"`
|
||||
}
|
||||
|
||||
type ivyz3p9mItem struct {
|
||||
GraduationDate string `json:"graduationDate"`
|
||||
StudentName string `json:"studentName"`
|
||||
EducationLevel string `json:"educationLevel"`
|
||||
LearningForm string `json:"learningForm"`
|
||||
IDNumber string `json:"idNumber"`
|
||||
}
|
||||
|
||||
func mapZCI1004ToIVYZ3P9M(respData interface{}, name, idCard string) ([]ivyz3p9mItem, error) {
|
||||
respBytes, err := json.Marshal(respData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var source []zci1004Item
|
||||
if err := json.Unmarshal(respBytes, &source); err != nil {
|
||||
var wrapped struct {
|
||||
Data []zci1004Item `json:"data"`
|
||||
}
|
||||
if err2 := json.Unmarshal(respBytes, &wrapped); err2 != nil {
|
||||
return nil, err
|
||||
}
|
||||
source = wrapped.Data
|
||||
}
|
||||
|
||||
out := make([]ivyz3p9mItem, 0, len(source))
|
||||
for _, it := range source {
|
||||
out = append(out, ivyz3p9mItem{
|
||||
GraduationDate: normalizeDateDigits(it.EndDate),
|
||||
StudentName: name,
|
||||
EducationLevel: mapEducationLevelToCode(it.EducationLevel),
|
||||
LearningForm: mapLearningFormToCode(it.LearningForm),
|
||||
IDNumber: idCard,
|
||||
})
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func mapEducationLevelToCode(level string) string {
|
||||
v := normalizeText(level)
|
||||
switch {
|
||||
case strings.Contains(v, "第二学士"):
|
||||
return "5"
|
||||
case strings.Contains(v, "博士"):
|
||||
return "4"
|
||||
case strings.Contains(v, "硕士"):
|
||||
return "3"
|
||||
case strings.Contains(v, "本科"):
|
||||
return "2"
|
||||
case strings.Contains(v, "专科"), strings.Contains(v, "大专"):
|
||||
return "1"
|
||||
default:
|
||||
return "99"
|
||||
}
|
||||
}
|
||||
|
||||
func mapLearningFormToCode(form string) string {
|
||||
v := normalizeText(form)
|
||||
switch {
|
||||
case strings.Contains(v, "脱产"):
|
||||
return "1"
|
||||
case strings.Contains(v, "普通全日制"):
|
||||
return "2"
|
||||
case strings.Contains(v, "全日制"):
|
||||
return "3"
|
||||
case strings.Contains(v, "开放教育"), strings.Contains(v, "开放大学"):
|
||||
return "4"
|
||||
case strings.Contains(v, "夜大学"), strings.Contains(v, "夜大"):
|
||||
return "5"
|
||||
case strings.Contains(v, "函授"):
|
||||
return "6"
|
||||
case strings.Contains(v, "网络教育"), strings.Contains(v, "网教"), strings.Contains(v, "远程教育"):
|
||||
return "7"
|
||||
case strings.Contains(v, "非全日制"):
|
||||
return "8"
|
||||
case strings.Contains(v, "业余"):
|
||||
return "9"
|
||||
case strings.Contains(v, "自学考试"), strings.Contains(v, "自考"):
|
||||
// 自考在既有枚举中无直对应,兼容并入“业余”
|
||||
return "9"
|
||||
default:
|
||||
return "99"
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeDateDigits(s string) string {
|
||||
trimmed := strings.TrimSpace(s)
|
||||
if trimmed == "" {
|
||||
return ""
|
||||
}
|
||||
var b strings.Builder
|
||||
for _, ch := range trimmed {
|
||||
if ch >= '0' && ch <= '9' {
|
||||
b.WriteRune(ch)
|
||||
}
|
||||
}
|
||||
|
||||
return respData, nil
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func normalizeText(s string) string {
|
||||
v := strings.TrimSpace(strings.ToLower(s))
|
||||
v = strings.ReplaceAll(v, " ", "")
|
||||
v = strings.ReplaceAll(v, "-", "")
|
||||
v = strings.ReplaceAll(v, "_", "")
|
||||
return v
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package ivyz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/shujubao"
|
||||
)
|
||||
|
||||
// ProcessIVYZ48SRRequest IVYZ48SR 婚姻状态核验V2(双人) API 处理方法(使用数据宝服务示例)
|
||||
func ProcessIVYZ48SRRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.IVYZ48SRReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
fixedData := map[string]interface{}{"msg": "请联系商务咨询"}
|
||||
fixedRespBytes, err := json.Marshal(fixedData)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
return fixedRespBytes, nil
|
||||
|
||||
reqParams := map[string]interface{}{
|
||||
"key": "",
|
||||
"name": paramsDto.ManName,
|
||||
"idcard": paramsDto.ManIDCard,
|
||||
"woman_name": paramsDto.WomanName,
|
||||
"woman_idcard": paramsDto.WomanIDCard,
|
||||
"marital_type": paramsDto.MaritalType,
|
||||
"auth_authorize_file_code": paramsDto.AuthAuthorizeFileCode,
|
||||
}
|
||||
|
||||
// 最终请求 URL = https://api.chinadatapay.com/communication + 拼接接口地址值,如 personal/197
|
||||
apiPath := "/communication/personal/10332"
|
||||
data, err := deps.ShujubaoService.CallAPI(ctx, apiPath, reqParams)
|
||||
if err != nil {
|
||||
if errors.Is(err, shujubao.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
}
|
||||
if errors.Is(err, shujubao.ErrQueryEmpty) {
|
||||
return nil, errors.Join(processors.ErrNotFound, err)
|
||||
}
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
respBytes, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package ivyz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/zhicha"
|
||||
)
|
||||
|
||||
// ProcessIVYZ4Y27Request IVYZ4Y27 API处理方法 - 教育背景(详细)查询
|
||||
func ProcessIVYZ4Y27Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.IVYZ4Y27Req
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedIDCard, err := deps.ZhichaService.Encrypt(paramsDto.IDCard)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"name": encryptedName,
|
||||
"idCard": encryptedIDCard,
|
||||
"authorized": "1",
|
||||
}
|
||||
|
||||
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI1004", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, zhicha.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
}
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
out, err := mapZCI1004ToIVYZ4Y27(respData)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
respBytes, err := json.Marshal(out)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
|
||||
// if deps.HuiboService == nil {
|
||||
// return nil, errors.Join(processors.ErrSystem, errors.New("汇博服务未初始化"))
|
||||
// }
|
||||
|
||||
// respBytes, err := deps.HuiboService.CallEducationBackgroundDetailed(ctx, paramsDto.Name, paramsDto.IDCard, paramsDto.AuthAuthorizeFileBase64)
|
||||
// if err != nil {
|
||||
// return nil, errors.Join(processors.ErrDatasource, err)
|
||||
// }
|
||||
|
||||
// return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package ivyz
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ivyz4y27AbilityInfo struct {
|
||||
AbilityStartDate string `json:"abilityStartDate"`
|
||||
AbilityEndDate string `json:"abilityEndDate"`
|
||||
AbilityType string `json:"abilityType"`
|
||||
AbilityName string `json:"abilityName"`
|
||||
AbilityCompetitiveDegree string `json:"abilityCompetitiveDegree"`
|
||||
AbilityCompetitive string `json:"abilityCompetitive"`
|
||||
AbilityField string `json:"abilityField"`
|
||||
}
|
||||
|
||||
type ivyz4y27Response struct {
|
||||
AbilityInfo []ivyz4y27AbilityInfo `json:"abilityInfo"`
|
||||
}
|
||||
|
||||
func mapZCI1004ToIVYZ4Y27(respData interface{}) (*ivyz4y27Response, error) {
|
||||
respBytes, err := json.Marshal(respData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var source []zci1004Item
|
||||
if err := json.Unmarshal(respBytes, &source); err != nil {
|
||||
var wrapped struct {
|
||||
Data []zci1004Item `json:"data"`
|
||||
}
|
||||
if err2 := json.Unmarshal(respBytes, &wrapped); err2 != nil {
|
||||
return nil, err
|
||||
}
|
||||
source = wrapped.Data
|
||||
}
|
||||
|
||||
out := &ivyz4y27Response{
|
||||
AbilityInfo: make([]ivyz4y27AbilityInfo, 0, len(source)),
|
||||
}
|
||||
for _, it := range source {
|
||||
out.AbilityInfo = append(out.AbilityInfo, ivyz4y27AbilityInfo{
|
||||
AbilityStartDate: "",
|
||||
AbilityEndDate: formatDateToYYYYMMDD(it.EndDate),
|
||||
AbilityType: strings.TrimSpace(it.LearningForm),
|
||||
AbilityName: "",
|
||||
AbilityCompetitiveDegree: mapEducationLevelToAbilityCompetitiveDegree(it.EducationLevel),
|
||||
AbilityCompetitive: "",
|
||||
AbilityField: "",
|
||||
})
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func mapEducationLevelToAbilityCompetitiveDegree(level string) string {
|
||||
v := normalizeText(level)
|
||||
switch {
|
||||
case strings.Contains(v, "博士"):
|
||||
return "9"
|
||||
case strings.Contains(v, "硕士"):
|
||||
return "8"
|
||||
case strings.Contains(v, "本科"), strings.Contains(v, "第二学士"):
|
||||
return "7"
|
||||
case strings.Contains(v, "专科"), strings.Contains(v, "大专"):
|
||||
return "6"
|
||||
default:
|
||||
return "5"
|
||||
}
|
||||
}
|
||||
|
||||
func formatDateToYYYYMMDD(s string) string {
|
||||
digits := normalizeDateDigits(s)
|
||||
switch len(digits) {
|
||||
case 8:
|
||||
return digits[:4] + "-" + digits[4:6] + "-" + digits[6:8]
|
||||
case 6:
|
||||
return digits[:4] + "-" + digits[4:6] + "-01"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package ivyz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/zhicha"
|
||||
)
|
||||
|
||||
// ProcessIVYZ5E22Request API处理方法 - 双人婚姻评估查询
|
||||
func ProcessIVYZ5E22Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.IVYZ5E22Req
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
encryptedManName, err := deps.ZhichaService.Encrypt(paramsDto.ManName)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedManIDCard, err := deps.ZhichaService.Encrypt(paramsDto.ManIDCard)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
encryptedWomanName, err := deps.ZhichaService.Encrypt(paramsDto.WomanName)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
encryptedWomanIDCard, err := deps.ZhichaService.Encrypt(paramsDto.WomanIDCard)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"nameMan": encryptedManName,
|
||||
"idCardMan": encryptedManIDCard,
|
||||
"nameWoman": encryptedWomanName,
|
||||
"idCardWoman": encryptedWomanIDCard,
|
||||
"authorized": paramsDto.Authorized,
|
||||
}
|
||||
|
||||
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI042", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, zhicha.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 将响应数据转换为JSON字节
|
||||
respBytes, err := json.Marshal(respData)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user