From 13fe5d9124afabd9a93debcb5e0cec5b98e16939 Mon Sep 17 00:00:00 2001 From: ouhaolan Date: Tue, 3 Mar 2026 15:40:21 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E8=87=AA=E5=AE=9A=E4=B9=89=E6=A0=87?= =?UTF-8?q?=E7=AD=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .editorconfig | 12 + .env | 37 ++ .env.dev | 38 +++ .env.local | 35 ++ .env.prod | 34 ++ .env.stage | 34 ++ .env.test | 34 ++ .eslintignore | 8 + .eslintrc-auto-import.json | 259 ++++++++++++++ .eslintrc.js | 75 +++++ .gitignore | 9 + .prettierignore | 11 + .stylelintignore | 6 + .vscode/extensions.json | 18 + .vscode/launch.json | 16 + .vscode/settings.json | 146 ++++++++ backend-idcard/README.md | 51 +++ backend-idcard/pom.xml | 27 ++ .../module/car/baidu/IdcardRecognizer.java | 316 ++++++++++++++++++ .../java/com/ydoyun/ocr/IdcardRecognizer.java | 254 ++++++++++++++ .../baidu/IdcardRecognizer$AesKeyUtil.class | Bin 0 -> 1130 bytes .../car/baidu/IdcardRecognizer$AesUtil.class | Bin 0 -> 1388 bytes .../car/baidu/IdcardRecognizer$HttpUtil.class | Bin 0 -> 3066 bytes .../baidu/IdcardRecognizer$IdcardResult.class | Bin 0 -> 2220 bytes .../module/car/baidu/IdcardRecognizer.class | Bin 0 -> 8181 bytes .../ocr/IdcardRecognizer$AesKeyUtil.class | Bin 0 -> 1073 bytes .../ydoyun/ocr/IdcardRecognizer$AesUtil.class | Bin 0 -> 1331 bytes .../ocr/IdcardRecognizer$HttpUtil.class | Bin 0 -> 3375 bytes .../ocr/IdcardRecognizer$IdcardResult.class | Bin 0 -> 2163 bytes .../com/ydoyun/ocr/IdcardRecognizer.class | Bin 0 -> 5731 bytes backend-idcard/target/idcard-ocr-1.0.0.jar | Bin 0 -> 9578 bytes .../target/maven-archiver/pom.properties | 3 + .../compile/default-compile/createdFiles.lst | 5 + .../compile/default-compile/inputFiles.lst | 1 + build/vite/index.ts | 100 ++++++ build/vite/optimize.ts | 122 +++++++ .../customtag/CustomTagFormWithParams.vue | 311 +++++++++++++++++ src/views/ydoyun/productcustomtag/index.vue | 190 +++++++++++ .../ydoyun/reportdatabase/DatabaseList.vue | 111 ++++++ src/views/ydoyun/storecustomtag/index.vue | 190 +++++++++++ src/views/ydoyun/suppliercustomtag/index.vue | 190 +++++++++++ 41 files changed, 2643 insertions(+) create mode 100644 .editorconfig create mode 100644 .env create mode 100644 .env.dev create mode 100644 .env.local create mode 100644 .env.prod create mode 100644 .env.stage create mode 100644 .env.test create mode 100644 .eslintignore create mode 100644 .eslintrc-auto-import.json create mode 100644 .eslintrc.js create mode 100644 .gitignore create mode 100644 .prettierignore create mode 100644 .stylelintignore create mode 100644 .vscode/extensions.json create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 backend-idcard/README.md create mode 100644 backend-idcard/pom.xml create mode 100644 backend-idcard/src/main/java/cn/iocoder/yudao/module/car/baidu/IdcardRecognizer.java create mode 100644 backend-idcard/src/main/java/com/ydoyun/ocr/IdcardRecognizer.java create mode 100644 backend-idcard/target/classes/cn/iocoder/yudao/module/car/baidu/IdcardRecognizer$AesKeyUtil.class create mode 100644 backend-idcard/target/classes/cn/iocoder/yudao/module/car/baidu/IdcardRecognizer$AesUtil.class create mode 100644 backend-idcard/target/classes/cn/iocoder/yudao/module/car/baidu/IdcardRecognizer$HttpUtil.class create mode 100644 backend-idcard/target/classes/cn/iocoder/yudao/module/car/baidu/IdcardRecognizer$IdcardResult.class create mode 100644 backend-idcard/target/classes/cn/iocoder/yudao/module/car/baidu/IdcardRecognizer.class create mode 100644 backend-idcard/target/classes/com/ydoyun/ocr/IdcardRecognizer$AesKeyUtil.class create mode 100644 backend-idcard/target/classes/com/ydoyun/ocr/IdcardRecognizer$AesUtil.class create mode 100644 backend-idcard/target/classes/com/ydoyun/ocr/IdcardRecognizer$HttpUtil.class create mode 100644 backend-idcard/target/classes/com/ydoyun/ocr/IdcardRecognizer$IdcardResult.class create mode 100644 backend-idcard/target/classes/com/ydoyun/ocr/IdcardRecognizer.class create mode 100644 backend-idcard/target/idcard-ocr-1.0.0.jar create mode 100644 backend-idcard/target/maven-archiver/pom.properties create mode 100644 backend-idcard/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst create mode 100644 backend-idcard/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst create mode 100644 build/vite/index.ts create mode 100644 build/vite/optimize.ts create mode 100644 src/views/ydoyun/customtag/CustomTagFormWithParams.vue create mode 100644 src/views/ydoyun/productcustomtag/index.vue create mode 100644 src/views/ydoyun/reportdatabase/DatabaseList.vue create mode 100644 src/views/ydoyun/storecustomtag/index.vue create mode 100644 src/views/ydoyun/suppliercustomtag/index.vue diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..79a12ff --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true +[*.{js,ts,vue}] +charset = utf-8 # 设置文件字符集为 utf-8 +end_of_line = lf # 控制换行类型(lf | cr | crlf) +insert_final_newline = true # 始终在文件末尾插入一个新行 +indent_style = space # 缩进风格(tab | space) +indent_size = 2 # 缩进大小 +max_line_length = 100 # 最大行长度 + +[*.md] # 仅 md 文件适用以下规则 +max_line_length = off # 关闭最大行长度限制 +trim_trailing_whitespace = false # 关闭末尾空格修剪 diff --git a/.env b/.env new file mode 100644 index 0000000..c6890d4 --- /dev/null +++ b/.env @@ -0,0 +1,37 @@ +# 标题 +VITE_APP_TITLE=衣朵云管理系统 + +# 项目本地运行端口号 +VITE_PORT=80 + +# open 运行 npm run dev 时自动打开浏览器 +VITE_OPEN=true + +# 租户开关 +VITE_APP_TENANT_ENABLE=true + +# 验证码的开关 +VITE_APP_CAPTCHA_ENABLE=true + +# 文档地址的开关 +VITE_APP_DOCALERT_ENABLE=false + +# 百度统计 +VITE_APP_BAIDU_CODE = a1ff8825baa73c3a78eb96aa40325abc + +# 默认账户密码 +VITE_APP_DEFAULT_LOGIN_TENANT = 衣朵云源码 +VITE_APP_DEFAULT_LOGIN_USERNAME = +VITE_APP_DEFAULT_LOGIN_PASSWORD = + +# API 加解密 +VITE_APP_API_ENCRYPT_ENABLE = true +VITE_APP_API_ENCRYPT_HEADER = X-Api-Encrypt +VITE_APP_API_ENCRYPT_ALGORITHM = AES +VITE_APP_API_ENCRYPT_REQUEST_KEY = 52549111389893486934626385991395 +VITE_APP_API_ENCRYPT_RESPONSE_KEY = 96103715984234343991809655248883 +# VITE_APP_API_ENCRYPT_REQUEST_KEY = MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCls2rIpnGdYnLFgz1XU13GbNQ5DloyPpvW00FPGjqn5Z6JpK+kDtVlnkhwR87iRrE5Vf2WNqRX6vzbLSgveIQY8e8oqGCb829myjf1MuI+ZzN4ghf/7tEYhZJGPI9AbfxFqBUzm+kR3/HByAI22GLT96WM26QiMK8n3tIP/yiLswIDAQAB +# VITE_APP_API_ENCRYPT_RESPONSE_KEY = MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAOH8IfIFxL/MR9XIg1UDv5z1fGXQI93E8wrU4iPFovL/sEt9uSgSkjyidC2O7N+m7EKtoN6b1u7cEwXSkwf3kfK0jdWLSQaNpX5YshqXCBzbDfugDaxuyYrNA4/tIMs7mzZAk0APuRXB35Dmupou7Yw7TFW/7QhQmGfzeEKULQvnAgMBAAECgYAw8LqlQGyQoPv5p3gRxEMOCfgL0JzD3XBJKztiRd35RDh40Nx1ejgjW4dPioFwGiVWd2W8cAGHLzALdcQT2KDJh+T/tsd4SPmI6uSBBK6Ff2DkO+kFFcuYvfclQQKqxma5CaZOSqhgenacmgTMFeg2eKlY3symV6JlFNu/IKU42QJBAOhxAK/Eq3e61aYQV2JSguhMR3b8NXJJRroRs/QHEanksJtl+M+2qhkC9nQVXBmBkndnkU/l2tYcHfSBlAyFySMCQQD445tgm/J2b6qMQmuUGQAYDN8FIkHjeKmha+l/fv0igWm8NDlBAem91lNDIPBUzHL1X1+pcts5bjmq99YdOnhtAkAg2J8dN3B3idpZDiQbC8fd5bGPmdI/pSUudAP27uzLEjr2qrE/QPPGdwm2m7IZFJtK7kK1hKio6u48t/bg0iL7AkEAuUUs94h+v702Fnym+jJ2CHEkXvz2US8UDs52nWrZYiM1o1y4tfSHm8H8bv8JCAa9GHyriEawfBraILOmllFdLQJAQSRZy4wmlaG48MhVXodB85X+VZ9krGXZ2TLhz7kz9iuToy53l9jTkESt6L5BfBDCVdIwcXLYgK+8KFdHN5W7HQ== + +# 百度地图 +VITE_BAIDU_MAP_KEY = 'efHIw2qmH8RzHPxK0z0rbCgzDVLup9LD' \ No newline at end of file diff --git a/.env.dev b/.env.dev new file mode 100644 index 0000000..ab28217 --- /dev/null +++ b/.env.dev @@ -0,0 +1,38 @@ +# 开发环境:本地只启动前端项目,依赖开发环境(后端、APP) +NODE_ENV=production + +VITE_DEV=true + +# 请求路径 +VITE_BASE_URL='http://localhost:48080' +#VITE_BASE_URL='http://118.253.178.8:48080' + +# 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持S3服务 +VITE_UPLOAD_TYPE=server + +# 接口地址 +VITE_API_URL=/admin-api + +# 是否删除debugger +VITE_DROP_DEBUGGER=false + +# 是否删除console.log +VITE_DROP_CONSOLE=false + +# 是否sourcemap +VITE_SOURCEMAP=true + +# 打包路径 +VITE_BASE_PATH=/ + +# 输出路径 +VITE_OUT_DIR=dist + +# 商城H5会员端域名 +VITE_MALL_H5_DOMAIN='http://mall.yudao.iocoder.cn' + +# 验证码的开关 +VITE_APP_CAPTCHA_ENABLE=true + +# GoView域名 +VITE_GOVIEW_URL='http://118.253.178.8:3030' \ No newline at end of file diff --git a/.env.local b/.env.local new file mode 100644 index 0000000..f4adcda --- /dev/null +++ b/.env.local @@ -0,0 +1,35 @@ +# 本地开发环境:本地启动所有项目(前端、后端、APP)时使用,不依赖外部环境 +NODE_ENV=development + +VITE_DEV=true + +# 请求路径 +#VITE_BASE_URL='http://118.253.178.8:48080' +VITE_BASE_URL='http://localhost:48080' + +# 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持 S3 服务 +VITE_UPLOAD_TYPE=server + +# 接口地址 +VITE_API_URL=/admin-api + +# 是否删除debugger +VITE_DROP_DEBUGGER=false + +# 是否删除console.log +VITE_DROP_CONSOLE=false + +# 是否sourcemap +VITE_SOURCEMAP=false + +# 打包路径 +VITE_BASE_PATH=/ + +# 商城H5会员端域名 +VITE_MALL_H5_DOMAIN='http://localhost:3000' + +# 验证码的开关 +VITE_APP_CAPTCHA_ENABLE=false + +# GoView域名 +VITE_GOVIEW_URL='http://127.0.0.1:3000' \ No newline at end of file diff --git a/.env.prod b/.env.prod new file mode 100644 index 0000000..5fe4955 --- /dev/null +++ b/.env.prod @@ -0,0 +1,34 @@ +# 生产环境:只在打包时使用 +NODE_ENV=production + +VITE_DEV=true + +# 请求路径 +VITE_BASE_URL='http://118.253.178.8:48080' + +# 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持S3服务 +VITE_UPLOAD_TYPE=server + +# 接口地址 +VITE_API_URL=/admin-api + +# 是否删除debugger +VITE_DROP_DEBUGGER=true + +# 是否删除console.log +VITE_DROP_CONSOLE=true + +# 是否sourcemap +VITE_SOURCEMAP=false + +# 打包路径 +VITE_BASE_PATH=/ + +# 输出路径 +VITE_OUT_DIR=dist-prod + +# 商城H5会员端域名 +VITE_MALL_H5_DOMAIN='http://mall.yudao.iocoder.cn' + +# GoView域名 +VITE_GOVIEW_URL='http://118.253.178.8:3030' \ No newline at end of file diff --git a/.env.stage b/.env.stage new file mode 100644 index 0000000..e3364ec --- /dev/null +++ b/.env.stage @@ -0,0 +1,34 @@ +# 预发布环境:只在打包时使用 +NODE_ENV=production + +VITE_DEV=false + +# 请求路径 +VITE_BASE_URL='http://118.253.178.8:48080' + +# 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持S3服务 +VITE_UPLOAD_TYPE=server + +# 接口地址 +VITE_API_URL=/admin-api + +# 是否删除debugger +VITE_DROP_DEBUGGER=true + +# 是否删除console.log +VITE_DROP_CONSOLE=true + +# 是否sourcemap +VITE_SOURCEMAP=false + +# 打包路径 +VITE_BASE_PATH='http://static-vue3.yudao.iocoder.cn/' + +# 输出路径 +VITE_OUT_DIR=dist-stage + +# 商城H5会员端域名 +VITE_MALL_H5_DOMAIN='http://mall.yudao.iocoder.cn' + +# GoView域名 +VITE_GOVIEW_URL='http://127.0.0.1:3000' \ No newline at end of file diff --git a/.env.test b/.env.test new file mode 100644 index 0000000..2252e14 --- /dev/null +++ b/.env.test @@ -0,0 +1,34 @@ +# 测试环境:只在打包时使用 +NODE_ENV=production + +VITE_DEV=false + +# 请求路径 +VITE_BASE_URL='http://localhost:48080' + +# 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持S3服务 +VITE_UPLOAD_TYPE=server + +# 接口地址 +VITE_API_URL=/admin-api + +# 是否删除debugger +VITE_DROP_DEBUGGER=true + +# 是否删除console.log +VITE_DROP_CONSOLE=true + +# 是否sourcemap +VITE_SOURCEMAP=false + +# 打包路径 +VITE_BASE_PATH=/admin-ui-vue3/ + +# 输出路径 +VITE_OUT_DIR=dist-test + +# 商城H5会员端域名 +VITE_MALL_H5_DOMAIN='http://mall.yudao.iocoder.cn' + +# GoView域名 +VITE_GOVIEW_URL='http://127.0.0.1:3000' \ No newline at end of file diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..1e85c0f --- /dev/null +++ b/.eslintignore @@ -0,0 +1,8 @@ +/build/ +/config/ +/dist/ +/*.js +/test/unit/coverage/ +/node_modules/* +/dist* +/src/main.ts diff --git a/.eslintrc-auto-import.json b/.eslintrc-auto-import.json new file mode 100644 index 0000000..024c96a --- /dev/null +++ b/.eslintrc-auto-import.json @@ -0,0 +1,259 @@ +{ + "globals": { + "EffectScope": true, + "ElMessage": true, + "ElMessageBox": true, + "ElTag": true, + "asyncComputed": true, + "autoResetRef": true, + "computed": true, + "computedAsync": true, + "computedEager": true, + "computedInject": true, + "computedWithControl": true, + "controlledComputed": true, + "controlledRef": true, + "createApp": true, + "createEventHook": true, + "createGlobalState": true, + "createInjectionState": true, + "createReactiveFn": true, + "createSharedComposable": true, + "createUnrefFn": true, + "customRef": true, + "debouncedRef": true, + "debouncedWatch": true, + "defineAsyncComponent": true, + "defineComponent": true, + "eagerComputed": true, + "effectScope": true, + "extendRef": true, + "getCurrentInstance": true, + "getCurrentScope": true, + "h": true, + "ignorableWatch": true, + "inject": true, + "isDefined": true, + "isProxy": true, + "isReactive": true, + "isReadonly": true, + "isRef": true, + "makeDestructurable": true, + "markRaw": true, + "nextTick": true, + "onActivated": true, + "onBeforeMount": true, + "onBeforeUnmount": true, + "onBeforeUpdate": true, + "onClickOutside": true, + "onDeactivated": true, + "onErrorCaptured": true, + "onKeyStroke": true, + "onLongPress": true, + "onMounted": true, + "onRenderTracked": true, + "onRenderTriggered": true, + "onScopeDispose": true, + "onServerPrefetch": true, + "onStartTyping": true, + "onUnmounted": true, + "onUpdated": true, + "pausableWatch": true, + "provide": true, + "reactify": true, + "reactifyObject": true, + "reactive": true, + "reactiveComputed": true, + "reactiveOmit": true, + "reactivePick": true, + "readonly": true, + "ref": true, + "refAutoReset": true, + "refDebounced": true, + "refDefault": true, + "refThrottled": true, + "refWithControl": true, + "resolveComponent": true, + "resolveRef": true, + "resolveUnref": true, + "shallowReactive": true, + "shallowReadonly": true, + "shallowRef": true, + "syncRef": true, + "syncRefs": true, + "templateRef": true, + "throttledRef": true, + "throttledWatch": true, + "toRaw": true, + "toReactive": true, + "toRef": true, + "toRefs": true, + "triggerRef": true, + "tryOnBeforeMount": true, + "tryOnBeforeUnmount": true, + "tryOnMounted": true, + "tryOnScopeDispose": true, + "tryOnUnmounted": true, + "unref": true, + "unrefElement": true, + "until": true, + "useActiveElement": true, + "useArrayEvery": true, + "useArrayFilter": true, + "useArrayFind": true, + "useArrayFindIndex": true, + "useArrayJoin": true, + "useArrayMap": true, + "useArrayReduce": true, + "useArraySome": true, + "useAsyncQueue": true, + "useAsyncState": true, + "useAttrs": true, + "useBase64": true, + "useBattery": true, + "useBluetooth": true, + "useBreakpoints": true, + "useBroadcastChannel": true, + "useBrowserLocation": true, + "useCached": true, + "useClipboard": true, + "useColorMode": true, + "useConfirmDialog": true, + "useCounter": true, + "useCssModule": true, + "useCssVar": true, + "useCssVars": true, + "useCurrentElement": true, + "useCycleList": true, + "useDark": true, + "useDateFormat": true, + "useDebounce": true, + "useDebounceFn": true, + "useDebouncedRefHistory": true, + "useDeviceMotion": true, + "useDeviceOrientation": true, + "useDevicePixelRatio": true, + "useDevicesList": true, + "useDisplayMedia": true, + "useDocumentVisibility": true, + "useDraggable": true, + "useDropZone": true, + "useElementBounding": true, + "useElementByPoint": true, + "useElementHover": true, + "useElementSize": true, + "useElementVisibility": true, + "useEventBus": true, + "useEventListener": true, + "useEventSource": true, + "useEyeDropper": true, + "useFavicon": true, + "useFetch": true, + "useFileDialog": true, + "useFileSystemAccess": true, + "useFocus": true, + "useFocusWithin": true, + "useFps": true, + "useFullscreen": true, + "useGamepad": true, + "useGeolocation": true, + "useIdle": true, + "useImage": true, + "useInfiniteScroll": true, + "useIntersectionObserver": true, + "useInterval": true, + "useIntervalFn": true, + "useKeyModifier": true, + "useLastChanged": true, + "useLocalStorage": true, + "useMagicKeys": true, + "useManualRefHistory": true, + "useMediaControls": true, + "useMediaQuery": true, + "useMemoize": true, + "useMemory": true, + "useMounted": true, + "useMouse": true, + "useMouseInElement": true, + "useMousePressed": true, + "useMutationObserver": true, + "useNavigatorLanguage": true, + "useNetwork": true, + "useNow": true, + "useObjectUrl": true, + "useOffsetPagination": true, + "useOnline": true, + "usePageLeave": true, + "useParallax": true, + "usePermission": true, + "usePointer": true, + "usePointerSwipe": true, + "usePreferredColorScheme": true, + "usePreferredDark": true, + "usePreferredLanguages": true, + "useRafFn": true, + "useRefHistory": true, + "useResizeObserver": true, + "useRoute": true, + "useRouter": true, + "useScreenOrientation": true, + "useScreenSafeArea": true, + "useScriptTag": true, + "useScroll": true, + "useScrollLock": true, + "useSessionStorage": true, + "useShare": true, + "useSlots": true, + "useSpeechRecognition": true, + "useSpeechSynthesis": true, + "useStepper": true, + "useStorage": true, + "useStorageAsync": true, + "useStyleTag": true, + "useSupported": true, + "useSwipe": true, + "useTemplateRefsList": true, + "useTextDirection": true, + "useTextSelection": true, + "useTextareaAutosize": true, + "useThrottle": true, + "useThrottleFn": true, + "useThrottledRefHistory": true, + "useTimeAgo": true, + "useTimeout": true, + "useTimeoutFn": true, + "useTimeoutPoll": true, + "useTimestamp": true, + "useTitle": true, + "useToggle": true, + "useTransition": true, + "useUrlSearchParams": true, + "useUserMedia": true, + "useVModel": true, + "useVModels": true, + "useVibrate": true, + "useVirtualList": true, + "useWakeLock": true, + "useWebNotification": true, + "useWebSocket": true, + "useWebWorker": true, + "useWebWorkerFn": true, + "useWindowFocus": true, + "useWindowScroll": true, + "useWindowSize": true, + "watch": true, + "watchArray": true, + "watchAtMost": true, + "watchDebounced": true, + "watchEffect": true, + "watchIgnorable": true, + "watchOnce": true, + "watchPausable": true, + "watchPostEffect": true, + "watchSyncEffect": true, + "watchThrottled": true, + "watchTriggerable": true, + "watchWithFilter": true, + "whenever": true + } +} diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..b28255c --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,75 @@ +// @ts-check +const { defineConfig } = require('eslint-define-config') +module.exports = defineConfig({ + root: true, + env: { + browser: true, + node: true, + es6: true + }, + parser: 'vue-eslint-parser', + parserOptions: { + parser: '@typescript-eslint/parser', + ecmaVersion: 2020, + sourceType: 'module', + jsxPragma: 'React', + ecmaFeatures: { + jsx: true + } + }, + extends: [ + 'plugin:vue/vue3-recommended', + 'plugin:@typescript-eslint/recommended', + 'prettier', + 'plugin:prettier/recommended', + '@unocss' + ], + rules: { + 'vue/no-setup-props-destructure': 'off', + 'vue/script-setup-uses-vars': 'error', + 'vue/no-reserved-component-names': 'off', + '@typescript-eslint/ban-ts-ignore': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-var-requires': 'off', + '@typescript-eslint/no-empty-function': 'off', + 'vue/custom-event-name-casing': 'off', + 'no-use-before-define': 'off', + '@typescript-eslint/no-use-before-define': 'off', + '@typescript-eslint/ban-ts-comment': 'off', + '@typescript-eslint/ban-types': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-unused-vars': 'off', + 'no-unused-vars': 'off', + 'space-before-function-paren': 'off', + + 'vue/attributes-order': 'off', + 'vue/one-component-per-file': 'off', + 'vue/html-closing-bracket-newline': 'off', + 'vue/max-attributes-per-line': 'off', + 'vue/multiline-html-element-content-newline': 'off', + 'vue/singleline-html-element-content-newline': 'off', + 'vue/attribute-hyphenation': 'off', + 'vue/require-default-prop': 'off', + 'vue/require-explicit-emits': 'off', + 'vue/require-toggle-inside-transition': 'off', + 'vue/html-self-closing': [ + 'error', + { + html: { + void: 'always', + normal: 'never', + component: 'always' + }, + svg: 'always', + math: 'always' + } + ], + 'vue/multi-word-component-names': 'off', + 'vue/no-v-html': 'off', + 'prettier/prettier': 'off', // 芋艿:默认关闭 prettier 的 ESLint 校验,因为我们使用的是 IDE 的 Prettier 插件 + '@unocss/order': 'off', // 芋艿:禁用 unocss 【css】顺序的提示,因为暂时不需要这么严格,警告也有点繁琐 + '@unocss/order-attributify': 'off' // 芋艿:禁用 unocss 【属性】顺序的提示,因为暂时不需要这么严格,警告也有点繁琐 + } +}) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..848638a --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +node_modules +.DS_Store +dist +dist-ssr +/dist* +pnpm-debug +auto-*.d.ts +.idea +.history diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..f68ea86 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,11 @@ +/node_modules/** +/dist/ +/dist* +/public/* +/docs/* +/vite.config.ts +/src/types/env.d.ts +/src/types/auto-components.d.ts +/src/types/auto-imports.d.ts +/docs/**/* +CHANGELOG diff --git a/.stylelintignore b/.stylelintignore new file mode 100644 index 0000000..aa605b4 --- /dev/null +++ b/.stylelintignore @@ -0,0 +1,6 @@ +/dist/* +/public/* +public/* +/dist* +/src/types/env.d.ts +/docs/**/* diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..65288b5 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,18 @@ +{ + "recommendations": [ + "christian-kohler.path-intellisense", + "vscode-icons-team.vscode-icons", + "davidanson.vscode-markdownlint", + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "mrmlnc.vscode-less", + "lokalise.i18n-ally", + "redhat.vscode-yaml", + "csstools.postcss", + "mikestead.dotenv", + "eamodio.gitlens", + "antfu.iconify", + "antfu.unocss", + "Vue.volar" + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..f43edc0 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "msedge", + "request": "launch", + "name": "Launch Edge against localhost", + "url": "http://localhost", + "webRoot": "${workspaceFolder}/src", + "sourceMaps": true + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..74ab52a --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,146 @@ +{ + "typescript.tsdk": "node_modules/typescript/lib", + "npm.packageManager": "pnpm", + "editor.tabSize": 2, + "prettier.printWidth": 100, // 超过最大值换行 + "editor.defaultFormatter": "esbenp.prettier-vscode", + "files.eol": "\n", + "search.exclude": { + "**/node_modules": true, + "**/*.log": true, + "**/*.log*": true, + "**/bower_components": true, + "**/dist": true, + "**/elehukouben": true, + "**/.git": true, + "**/.gitignore": true, + "**/.svn": true, + "**/.DS_Store": true, + "**/.idea": true, + "**/.vscode": false, + "**/yarn.lock": true, + "**/tmp": true, + "out": true, + "dist": true, + "node_modules": true, + "CHANGELOG.md": true, + "examples": true, + "res": true, + "screenshots": true, + "yarn-error.log": true, + "**/.yarn": true + }, + "files.exclude": { + "**/.cache": true, + "**/.editorconfig": true, + "**/.eslintcache": true, + "**/bower_components": true, + "**/.idea": true, + "**/tmp": true, + "**/.git": true, + "**/.svn": true, + "**/.hg": true, + "**/CVS": true, + "**/.DS_Store": true + }, + "files.watcherExclude": { + "**/.git/objects/**": true, + "**/.git/subtree-cache/**": true, + "**/.vscode/**": true, + "**/node_modules/**": true, + "**/tmp/**": true, + "**/bower_components/**": true, + "**/dist/**": true, + "**/yarn.lock": true + }, + "stylelint.enable": true, + "stylelint.validate": ["css", "less", "postcss", "scss", "vue", "sass"], + "path-intellisense.mappings": { + "@/": "${workspaceRoot}/src" + }, + "[javascriptreact]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[typescriptreact]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[html]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[css]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[less]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[scss]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[markdown]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit", + "source.fixAll.stylelint": "explicit" + }, + "editor.formatOnSave": true, + "[vue]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "i18n-ally.localesPaths": ["src/locales"], + "i18n-ally.keystyle": "nested", + "i18n-ally.sortKeys": true, + "i18n-ally.namespace": false, + "i18n-ally.enabledParsers": ["ts"], + "i18n-ally.sourceLanguage": "en", + "i18n-ally.displayLanguage": "zh-CN", + "i18n-ally.enabledFrameworks": ["vue", "react"], + "cSpell.words": [ + "brotli", + "browserslist", + "codemirror", + "commitlint", + "cropperjs", + "echart", + "echarts", + "esnext", + "esno", + "iconify", + "INTLIFY", + "lintstagedrc", + "logicflow", + "nprogress", + "pinia", + "pnpm", + "qrcode", + "sider", + "sortablejs", + "stylelint", + "svgs", + "unocss", + "unplugin", + "unref", + "videojs", + "VITE", + "vitejs", + "vueuse", + "wangeditor", + "xingyu", + "yudao", + "zxcvbn" + ], + // 控制相关文件嵌套展示 + "explorer.fileNesting.enabled": true, + "explorer.fileNesting.expand": false, + "explorer.fileNesting.patterns": { + "*.ts": "$(capture).test.ts, $(capture).test.tsx", + "*.tsx": "$(capture).test.ts, $(capture).test.tsx", + "*.env": "$(capture).env.*", + "package.json": "pnpm-lock.yaml,yarn.lock,LICENSE,README*,CHANGELOG*,CNAME,.gitattributes,.eslintrc-auto-import.json,.gitignore,prettier.config.js,stylelint.config.js,commitlint.config.js,.stylelintignore,.prettierignore,.gitpod.yml,.eslintrc.js,.eslintignore" + }, + "terminal.integrated.scrollback": 10000, + "nuxt.isNuxtApp": false +} diff --git a/backend-idcard/README.md b/backend-idcard/README.md new file mode 100644 index 0000000..190f8e4 --- /dev/null +++ b/backend-idcard/README.md @@ -0,0 +1,51 @@ +# 身份证识别(百度 OCR + AES) + +调用百度身份证 OCR 接口,对图片做 AES 加密上传,对返回的 `result` 密文做 Base64 解码 + AES 解密后解析出所需字段。 + +## 依赖 + +- JDK 8+ +- [fastjson](https://github.com/alibaba/fastjson)(解析返回 JSON) + +## 配置 + +从百度控制台获取: + +- **access_token**:OAuth2 获取(或使用 API Key + Secret Key 换 token) +- **aesKey**:16 位 hex 字符串(控制台身份证 OCR 安全设置里) + +## 使用示例 + +```java +// 构造识别器(accessToken、aesKey 建议从配置文件或环境变量读取) +IdcardRecognizer recognizer = new IdcardRecognizer(accessToken, aesKey); + +// 方式一:本地文件 +IdcardRecognizer.IdcardResult result = recognizer.recognize("/path/to/idcard_front.jpg", "front"); +if (result != null) { + System.out.println("姓名: " + result.getName()); + System.out.println("身份证号: " + result.getIdNumber()); + System.out.println("出生: " + result.getBirth()); + System.out.println("住址: " + result.getAddress()); + // ... +} + +// 方式二:上传的图片字节(如 MultipartFile.getBytes()) +byte[] imgBytes = ...; +IdcardRecognizer.IdcardResult back = recognizer.recognize(imgBytes, "back"); +if (back != null) { + System.out.println("签发机关: " + back.getIssueAuthority()); + System.out.println("有效期限: " + back.getValidDate()); +} +``` + +## 返回字段说明 + +- **正面 (side=front)**:`name` 姓名、`gender` 性别、`nation` 民族、`birth` 出生、`address` 住址、`idNumber` 公民身份号码 +- **反面 (side=back)**:`issueAuthority` 签发机关、`validDate` 有效期限 + +按百度接口约定,请求时 `id_card_side` 传 `front` 或 `back`。 + +## 集成到现有后端 + +将 `com.ydoyun.ocr` 包拷贝到你的项目中,并加入 fastjson 依赖即可;若项目已使用 Spring,可把 `IdcardRecognizer` 做成 Bean,`accessToken`、`aesKey` 从 `@Value` 或配置类注入。 diff --git a/backend-idcard/pom.xml b/backend-idcard/pom.xml new file mode 100644 index 0000000..3ae6409 --- /dev/null +++ b/backend-idcard/pom.xml @@ -0,0 +1,27 @@ + + + 4.0.0 + + com.ydoyun + idcard-ocr + 1.0.0 + jar + Idcard OCR (Baidu) + 百度身份证 OCR 调用(AES 加解密) + + + 1.8 + 1.8 + UTF-8 + + + + + com.alibaba + fastjson + 2.0.43 + + + diff --git a/backend-idcard/src/main/java/cn/iocoder/yudao/module/car/baidu/IdcardRecognizer.java b/backend-idcard/src/main/java/cn/iocoder/yudao/module/car/baidu/IdcardRecognizer.java new file mode 100644 index 0000000..b8d3df3 --- /dev/null +++ b/backend-idcard/src/main/java/cn/iocoder/yudao/module/car/baidu/IdcardRecognizer.java @@ -0,0 +1,316 @@ +package cn.iocoder.yudao.module.car.baidu; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; +import java.io.ByteArrayOutputStream; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Base64; +import java.util.List; +import java.util.Map; + +/** + * 获取 token + 身份证识别(百度 OCR,AES 加解密)。 + */ +public class IdcardRecognizer { + + private static final String IDCARD_OCR_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/idcard"; + + private final String accessToken; + private byte[] originAesKey; + + public IdcardRecognizer(String accessToken, String aesKey) { + this.accessToken = accessToken; + try { + this.originAesKey = AesKeyUtil.parseAesKey(aesKey); + } catch (Exception e) { + throw new IllegalArgumentException("aesKey 非法,需 16 位 hex", e); + } + } + + // ===================== 获取 token(你原来的逻辑) ===================== + + /** + * 获取权限token + * @return 返回示例: + * { + * "access_token": "24.460da4889caad24cccdb1fea17221975.2592000.1491995545.282335-1234567", + * "expires_in": 2592000 + * } + */ + public static String getAuth() { + String clientId = "mYchXvYyrwXbTscZ0HWfR88s"; + String clientSecret = "AD4QSVg7OMP0GGkcqxkDSwrn7V7rkShN"; + return getAuth(clientId, clientSecret); + } + + /** + * 获取API访问token + * @param ak - 百度云官网获取的 API Key + * @param sk - 百度云官网获取的 Securet Key + * @return assess_token + */ + public static String getAuth(String ak, String sk) { + String authHost = "https://aip.baidubce.com/oauth/2.0/token?"; + String getAccessTokenUrl = authHost + + "grant_type=client_credentials" + + "&client_id=" + ak + + "&client_secret=" + sk; + try { + URL realUrl = new URL(getAccessTokenUrl); + HttpURLConnection connection = (HttpURLConnection) realUrl.openConnection(); + connection.setRequestMethod("GET"); + connection.connect(); + Map> map = connection.getHeaderFields(); + for (String key : map.keySet()) { + System.err.println(key + "--->" + map.get(key)); + } + BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream())); + String result = ""; + String line; + while ((line = in.readLine()) != null) { + result += line; + } + System.err.println("result:" + result); + JSONObject jsonObject = JSONObject.parseObject(result); + String access_token = jsonObject.getString("access_token"); + return access_token; + } catch (Exception e) { + System.err.printf("获取token失败!"); + e.printStackTrace(System.err); + } + return null; + } + + // ===================== 身份证识别(AES 加解密) ===================== + + /** + * 识别身份证图片(本地文件路径) + * @param filePath 图片本地路径 + * @param side 正面 "front" 或反面 "back" + * @return 解析后的身份证字段,识别失败返回 null + */ + public IdcardResult recognize(String filePath, String side) throws Exception { + byte[] imgData = Files.readAllBytes(Paths.get(filePath)); + return recognize(imgData, side); + } + + /** + * 识别身份证图片(字节数组,适合上传场景) + * @param imgData 图片字节 + * @param side 正面 "front" 或反面 "back" + * @return 解析后的身份证字段,识别失败返回 null + */ + public IdcardResult recognize(byte[] imgData, String side) throws Exception { + String plainJson = recognizePlainJson(imgData, side); + return (plainJson == null || plainJson.isEmpty()) ? null : parseToResult(plainJson, side); + } + + /** + * 识别并返回解密后的明文 JSON(便于调试/落库)。 + */ + public String recognizePlainJson(byte[] imgData, String side) throws Exception { + String imgStr = encryptImg(imgData); + String imgParam = URLEncoder.encode(imgStr, "UTF-8"); + String body = "id_card_side=" + side + "&image=" + imgParam + "&AESEncry=true"; + + String rawResponse = HttpUtil.post(IDCARD_OCR_URL, accessToken, body); + if (rawResponse == null || rawResponse.isEmpty()) { + return null; + } + return decryptResult(rawResponse); + } + + private String encryptImg(byte[] imgData) throws Exception { + byte[] encImgBytes = AesUtil.encrypt(imgData, originAesKey); + return Base64.getEncoder().encodeToString(encImgBytes); + } + + private String decryptResult(String encryptResponse) throws Exception { + JSONObject obj = JSON.parseObject(encryptResponse); + String result = obj.getString("result"); + if (result == null) return null; + byte[] arr = Base64.getDecoder().decode(result); + return new String(AesUtil.decrypt(arr, originAesKey), "UTF-8"); + } + + private IdcardResult parseToResult(String plainJson, String side) { + JSONObject root = JSON.parseObject(plainJson); + JSONObject wordsResult = root.getJSONObject("words_result"); + if (wordsResult == null) return null; + + IdcardResult result = new IdcardResult(); + result.setSide("front".equalsIgnoreCase(side) ? "front" : "back"); + + if ("front".equalsIgnoreCase(side)) { + result.setName(getWords(wordsResult, "姓名")); + result.setGender(getWords(wordsResult, "性别")); + result.setNation(getWords(wordsResult, "民族")); + result.setBirth(getWords(wordsResult, "出生")); + result.setAddress(getWords(wordsResult, "住址")); + result.setIdNumber(getWords(wordsResult, "公民身份号码")); + } else { + result.setIssueAuthority(getWords(wordsResult, "签发机关")); + result.setValidDate(getWords(wordsResult, "有效期限")); + } + + return result; + } + + private static String getWords(JSONObject wordsResult, String key) { + if (wordsResult == null) return null; + JSONObject item = wordsResult.getJSONObject(key); + return item == null ? null : item.getString("words"); + } + + // --------------- 结果 DTO --------------- + + public static class IdcardResult { + private String side; + private String name; + private String gender; + private String nation; + private String birth; + private String address; + private String idNumber; + private String issueAuthority; + private String validDate; + + public String getSide() { return side; } + public void setSide(String side) { this.side = side; } + public String getName() { return name; } + public void setName(String name) { this.name = name; } + public String getGender() { return gender; } + public void setGender(String gender) { this.gender = gender; } + public String getNation() { return nation; } + public void setNation(String nation) { this.nation = nation; } + public String getBirth() { return birth; } + public void setBirth(String birth) { this.birth = birth; } + public String getAddress() { return address; } + public void setAddress(String address) { this.address = address; } + public String getIdNumber() { return idNumber; } + public void setIdNumber(String idNumber) { this.idNumber = idNumber; } + public String getIssueAuthority() { return issueAuthority; } + public void setIssueAuthority(String issueAuthority) { this.issueAuthority = issueAuthority; } + public String getValidDate() { return validDate; } + public void setValidDate(String validDate) { this.validDate = validDate; } + } + + // --------------- 工具类 --------------- + + static class HttpUtil { + static String post(String url, String accessToken, String formBody) throws IOException { + String fullUrl = url + "?access_token=" + URLEncoder.encode(accessToken, "UTF-8"); + byte[] bodyBytes = formBody.getBytes(StandardCharsets.UTF_8); + + HttpURLConnection conn = (HttpURLConnection) new URL(fullUrl).openConnection(); + conn.setRequestMethod("POST"); + conn.setConnectTimeout(15000); + conn.setReadTimeout(30000); + conn.setDoOutput(true); + conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"); + conn.setRequestProperty("Accept", "application/json"); + + OutputStream os = conn.getOutputStream(); + try { + os.write(bodyBytes); + } finally { + os.close(); + } + + int code = conn.getResponseCode(); + InputStream is = (code >= 200 && code < 300) ? conn.getInputStream() : conn.getErrorStream(); + if (is == null) return null; + try { + byte[] bytes = readAllBytes(is); + return new String(bytes, "UTF-8"); + } finally { + conn.disconnect(); + } + } + + private static byte[] readAllBytes(InputStream in) throws IOException { + byte[] buf = new byte[4096]; + int n; + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + while ((n = in.read(buf)) >= 0) { + if (n > 0) bos.write(buf, 0, n); + } + return bos.toByteArray(); + } + } + + static class AesKeyUtil { + private static final String HEX = "0123456789abcdef"; + + static byte[] parseAesKey(String hex) throws Exception { + if (hex == null || hex.length() != 16) { + throw new Exception("aes key 需为 16 位"); + } + char[] data = hex.toCharArray(); + byte[] out = new byte[data.length]; + for (int i = 0; i < data.length; i++) { + int f = HEX.indexOf(data[i]); + if (f < 0) throw new Exception("aes key 需为 hex 字符"); + out[i] = (byte) f; + } + return out; + } + } + + static class AesUtil { + private static final String ALGORITHM = "AES"; + private static final String ALGORITHM_STR = "AES/ECB/PKCS5Padding"; + + static byte[] encrypt(byte[] src, byte[] aesKey) throws Exception { + Cipher cipher = getCipher(aesKey, Cipher.ENCRYPT_MODE); + return cipher.doFinal(src); + } + + static byte[] decrypt(byte[] src, byte[] aesKey) throws Exception { + Cipher cipher = getCipher(aesKey, Cipher.DECRYPT_MODE); + return cipher.doFinal(src); + } + + private static Cipher getCipher(byte[] aesKey, int mode) throws Exception { + SecretKeySpec spec = new SecretKeySpec(aesKey, ALGORITHM); + Cipher cipher = Cipher.getInstance(ALGORITHM_STR); + cipher.init(mode, spec); + return cipher; + } + } + + // --------------- 测试 main(改下面三个变量后直接运行) --------------- + + public static void main(String[] args) throws Exception { + // 1. 用你原来的 getAuth 拿 token + String accessToken = getAuth(); + if (accessToken == null) { + System.out.println("获取 access_token 失败"); + return; + } + + // 2. 从控制台拿 16 位 aesKey(hex 字符串) + String aesKey = "填写16位aesKey"; + String filePath = "填写身份证图片路径"; + String side = "front"; // front 正面 / back 反面 + + IdcardRecognizer recognizer = new IdcardRecognizer(accessToken, aesKey); + IdcardResult result = recognizer.recognize(filePath, side); + + System.out.println("========== 识别结果 =========="); + System.out.println(JSON.toJSONString(result, true)); + } +} diff --git a/backend-idcard/src/main/java/com/ydoyun/ocr/IdcardRecognizer.java b/backend-idcard/src/main/java/com/ydoyun/ocr/IdcardRecognizer.java new file mode 100644 index 0000000..2a58050 --- /dev/null +++ b/backend-idcard/src/main/java/com/ydoyun/ocr/IdcardRecognizer.java @@ -0,0 +1,254 @@ +package com.ydoyun.ocr; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Base64; + +/** + * 身份证识别器:调用百度 OCR 身份证接口(AES 加密传输),解析并返回所需字段。 + * 需配置:accessToken、aesKey(16 位 hex),可从百度控制台获取。 + */ +public class IdcardRecognizer { + + private static final String IDCARD_OCR_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/idcard"; + + private final String accessToken; + private byte[] originAesKey; + + public IdcardRecognizer(String accessToken, String aesKey) { + this.accessToken = accessToken; + try { + this.originAesKey = AesKeyUtil.parseAesKey(aesKey); + } catch (Exception e) { + throw new IllegalArgumentException("aesKey 非法,需 16 位 hex", e); + } + } + + /** + * 识别身份证图片(本地文件路径) + * + * @param filePath 图片本地路径 + * @param side 正面 "front" 或反面 "back" + * @return 解析后的身份证字段,识别失败返回 null + */ + public IdcardResult recognize(String filePath, String side) throws IOException, Exception { + byte[] imgData = Files.readAllBytes(Paths.get(filePath)); + return recognize(imgData, side); + } + + /** + * 识别身份证图片(字节数组,适合上传场景) + * + * @param imgData 图片字节 + * @param side 正面 "front" 或反面 "back" + * @return 解析后的身份证字段,识别失败返回 null + */ + public IdcardResult recognize(byte[] imgData, String side) throws Exception { + String plainJson = recognizePlainJson(imgData, side); + return (plainJson == null || plainJson.isEmpty()) ? null : parseToResult(plainJson, side); + } + + /** + * 识别并返回解密后的明文 JSON(便于调试/落库/自行解析)。 + */ + public String recognizePlainJson(byte[] imgData, String side) throws Exception { + String imgStr = encryptImg(imgData); + String imgParam = URLEncoder.encode(imgStr, StandardCharsets.UTF_8.name()); + String body = "id_card_side=" + side + "&image=" + imgParam + "&AESEncry=true"; + + String rawResponse = HttpUtil.post(IDCARD_OCR_URL, accessToken, body); + if (rawResponse == null || rawResponse.isEmpty()) { + return null; + } + return decryptResult(rawResponse); + } + + private String encryptImg(byte[] imgData) throws Exception { + byte[] encImgBytes = AesUtil.encrypt(imgData, originAesKey); + return Base64.getEncoder().encodeToString(encImgBytes); + } + + private String decryptResult(String encryptResponse) throws Exception { + JSONObject obj = JSON.parseObject(encryptResponse); + String result = obj.getString("result"); + if (result == null) return null; + byte[] arr = Base64.getDecoder().decode(result); + return new String(AesUtil.decrypt(arr, originAesKey), StandardCharsets.UTF_8); + } + + private IdcardResult parseToResult(String plainJson, String side) { + JSONObject root = JSON.parseObject(plainJson); + JSONObject wordsResult = root.getJSONObject("words_result"); + if (wordsResult == null) return null; + + IdcardResult result = new IdcardResult(); + result.setSide("front".equalsIgnoreCase(side) ? "front" : "back"); + + if ("front".equalsIgnoreCase(side)) { + result.setName(getWords(wordsResult, "姓名")); + result.setGender(getWords(wordsResult, "性别")); + result.setNation(getWords(wordsResult, "民族")); + result.setBirth(getWords(wordsResult, "出生")); + result.setAddress(getWords(wordsResult, "住址")); + result.setIdNumber(getWords(wordsResult, "公民身份号码")); + } else { + result.setIssueAuthority(getWords(wordsResult, "签发机关")); + result.setValidDate(getWords(wordsResult, "有效期限")); + } + + return result; + } + + private static String getWords(JSONObject wordsResult, String key) { + if (wordsResult == null) return null; + JSONObject item = wordsResult.getJSONObject(key); + return item == null ? null : item.getString("words"); + } + + // --------------- 结果 DTO --------------- + + public static class IdcardResult { + private String side; + private String name; + private String gender; + private String nation; + private String birth; + private String address; + private String idNumber; + private String issueAuthority; + private String validDate; + + public String getSide() { return side; } + public void setSide(String side) { this.side = side; } + public String getName() { return name; } + public void setName(String name) { this.name = name; } + public String getGender() { return gender; } + public void setGender(String gender) { this.gender = gender; } + public String getNation() { return nation; } + public void setNation(String nation) { this.nation = nation; } + public String getBirth() { return birth; } + public void setBirth(String birth) { this.birth = birth; } + public String getAddress() { return address; } + public void setAddress(String address) { this.address = address; } + public String getIdNumber() { return idNumber; } + public void setIdNumber(String idNumber) { this.idNumber = idNumber; } + public String getIssueAuthority() { return issueAuthority; } + public void setIssueAuthority(String issueAuthority) { this.issueAuthority = issueAuthority; } + public String getValidDate() { return validDate; } + public void setValidDate(String validDate) { this.validDate = validDate; } + } + + // --------------- 工具类 --------------- + + static class HttpUtil { + static String post(String url, String accessToken, String formBody) throws IOException { + String fullUrl = url + "?access_token=" + URLEncoder.encode(accessToken, "UTF-8"); + byte[] bodyBytes = formBody.getBytes(StandardCharsets.UTF_8); + + HttpURLConnection conn = (HttpURLConnection) new URL(fullUrl).openConnection(); + conn.setRequestMethod("POST"); + conn.setConnectTimeout(15_000); + conn.setReadTimeout(30_000); + conn.setDoOutput(true); + conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"); + conn.setRequestProperty("Accept", "application/json"); + + try (OutputStream os = conn.getOutputStream()) { + os.write(bodyBytes); + } + + int code = conn.getResponseCode(); + InputStream is = (code >= 200 && code < 300) ? conn.getInputStream() : conn.getErrorStream(); + if (is == null) return null; + try (InputStream in = is) { + byte[] bytes = readAllBytes(in); + return new String(bytes, StandardCharsets.UTF_8); + } finally { + conn.disconnect(); + } + } + + private static byte[] readAllBytes(InputStream in) throws IOException { + byte[] buf = new byte[4096]; + int n; + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + while ((n = in.read(buf)) >= 0) { + if (n > 0) bos.write(buf, 0, n); + } + return bos.toByteArray(); + } + } + + static class AesKeyUtil { + private static final String HEX = "0123456789abcdef"; + + static byte[] parseAesKey(String hex) throws Exception { + if (hex == null || hex.length() != 16) { + throw new Exception("aes key 需为 16 位"); + } + char[] data = hex.toCharArray(); + byte[] out = new byte[data.length]; + for (int i = 0; i < data.length; i++) { + int f = HEX.indexOf(data[i]); + if (f < 0) throw new Exception("aes key 需为 hex 字符"); + out[i] = (byte) f; + } + return out; + } + } + + static class AesUtil { + private static final String ALGORITHM = "AES"; + private static final String ALGORITHM_STR = "AES/ECB/PKCS5Padding"; + + static byte[] encrypt(byte[] src, byte[] aesKey) throws Exception { + Cipher cipher = getCipher(aesKey, Cipher.ENCRYPT_MODE); + return cipher.doFinal(src); + } + + static byte[] decrypt(byte[] src, byte[] aesKey) throws Exception { + Cipher cipher = getCipher(aesKey, Cipher.DECRYPT_MODE); + return cipher.doFinal(src); + } + + private static Cipher getCipher(byte[] aesKey, int mode) throws Exception { + SecretKeySpec spec = new SecretKeySpec(aesKey, ALGORITHM); + Cipher cipher = Cipher.getInstance(ALGORITHM_STR); + cipher.init(mode, spec); + return cipher; + } + } + + /** + * 本地快速测试入口(JDK8 可运行,直接改下面四个变量再运行)。 + */ + public static void main(String[] args) throws Exception { + // TODO:替换成你自己的配置 + String accessToken = "填写你的 access_token"; + String aesKey = "填写 16 位 aesKey(hex 字符串)"; + String side = "front"; // front 正面 / back 反面 + String filePath = "填写身份证图片本地路径"; + + IdcardRecognizer recognizer = new IdcardRecognizer(accessToken, aesKey); + IdcardResult result = recognizer.recognize(filePath, side); + System.out.println(JSON.toJSONString(result, true)); + + // 如果你要看解密后的明文 JSON(排查字段/定位问题),放开下面三行: + // byte[] img = Files.readAllBytes(Paths.get(filePath)); + // String plain = recognizer.recognizePlainJson(img, side); + // System.out.println(plain); + } +} diff --git a/backend-idcard/target/classes/cn/iocoder/yudao/module/car/baidu/IdcardRecognizer$AesKeyUtil.class b/backend-idcard/target/classes/cn/iocoder/yudao/module/car/baidu/IdcardRecognizer$AesKeyUtil.class new file mode 100644 index 0000000000000000000000000000000000000000..80271909fa94cfb52d6b954f4f439bb01792e4ee GIT binary patch literal 1130 zcmbtSOHUI~6#i~IFr`cd!6G1N5zs!6p-^7(3N{!U3m8G7VRbumZ3oH>nNAIL<-)BC zS8hm*8{KH48!)K2bKzfbWr8vO1!KT7g-Ap<#>L!w&tuN{zVDomyIb1;5*U}^VHnd) z)vz>67q(g|>fBOSEWMZ)s>W?K!wtQtrgVDi*M(+fP2;YxJ0?ZpswmxXjJynqp>~Pi z;cA|nS#>tEBs7P?du8e-LtXkXGUwQanH^yWBrUVxaMM}f`J(WliVF0^d;1do1A{}u zJfrF2Hbdp8VH(ajRSHKI7^EZt^uvo|3jC;Ms7V{9m?^GgggwtQdEqv*G@f7Jw&B_b zPRYp`1%`?Af3YOg)vMeth<)M=4dFkTMHVkH_@~x1vFaE^QN~Gz`a=PJMf_;ODH+Yb z3AZngf)=zA`MfZ*POeNo<--|4>uy%mZNgm&IN@S_23t{`VA<8P1_ z++H0DI&qFDI94*p?Md6_B~mmTSxma&u!0Dp46hAJaYO3hhCTN9TJ zcXq*}E7tAm#-OB3Q`pHoFBAj`I?B-d?}MaXhYO1k2q^78Lo+H+Nw-Xv2R{1X?iP6z zdZ~0Xx+|ia2)rt%)X?ev0PLZbPGvs<$5BU?5A`@fMS}E}DAG_a^O>4ekmp+T1C;0n z8f$AGBk%?dx9U!BBJ`eI?djO|Ti>*I{24mye6iNV2F?#iL23OUFXrZTwFae!^Y^{1 zHMad^?^D-Hs_BJ`C`zEgya+IfHm(vaOhy~?(Zs9J!~B?F)tDlCh6T!~6Z?2@fw+M7 zErISb*)}?R5GMxhh@lsKRKJJVBfg(DOH@1*a>4AH}vGuiEv7%HfnCJv&!p;Naxz+RAbcrl5lXYpznZ zUd&f&lP_%7C5E7N#`C@SArB50R|T001sF)9-Nb2}5u9D{e6`frT36wUyrxd>``hKJ%V$GjW~#8 z(YGIXXC(-MRq?hrRcJtP$;4&!vz;3%Zte9$m3vz=eo^RLL zCmu-3Nk^e0Na@O2Z57R{9U4wTCg?m7QRAqjtSm+am$iTc)@WNfR*hj56RSqgzzD{5 z`z=8?X@9->;;Jt4rioj)Eyz<>HJJm4*HaumA^*g(G{&TfJGjex*HfVBs?F1pavGr* z?^vv9IvEU3Gts5WTwStY;3(*?I(VT*Eog*}dgAH9={!FFzY($>)v`rhx=9zlVB}S)klV%B zVj;hW8%txoqn|J_b||c%6Qv({89=*SLAMMh)TJgsB8y0D(mmX#z&!gtg=tc@Q=*od Urai-q6dvFqSN(Pck1&he513OY761SM literal 0 HcmV?d00001 diff --git a/backend-idcard/target/classes/cn/iocoder/yudao/module/car/baidu/IdcardRecognizer$HttpUtil.class b/backend-idcard/target/classes/cn/iocoder/yudao/module/car/baidu/IdcardRecognizer$HttpUtil.class new file mode 100644 index 0000000000000000000000000000000000000000..6dc3a57a4b3cc183a913b1243269c7f7630fa69f GIT binary patch literal 3066 zcmbtWYj7LY75=U)d1ZOy#7>-`)PYbEW5*GqG^DA7p|azEK#5a7LX9b5Ev@ZM*1KZ0 zieuhQ3-ke$Qo!L+3ZZSFX-mp5a=@L^U$oQCF#KVfDHIq6=l}yPKNw)jkbZYpvW4T$ z(CJvd_wJr^&pqEc-#K^x{OQxb2e1i86$Av5Su1YXSv#jY@u^}?v*QzXu4wAl>{O^`?Gr9HEgS(ph;l4XWFpi>E1bsf)xU* z7M|HvG)%@if|WQ$!K%;1&-Y426srZ4eVSR+dq)LYt3CH)oLt1K2$rKoMtF@tbM=-o z*!EpvoF>q4ftJncg~D#v-m6=ka@Xl9zKAoJH!Yvnt(-t?vF#_tp$sJ%#(II!;J~)n zmI%&7yMne#L@eEn5B6nxERSF+HlTyY=-#8imPL*^>6HHlHKyV$#0A2x?E@58(Y8of z>hL9eS;091r+c}un5FEv<`n2!zpGg}a=Y7qE~qSvbA2qkw+O8EovGeY)=bL$eMQ9< ze3c%I>2BAQtCOpzmJuzsrG2J1`J6egEc-I^3Sn#|{de~E4@8i{HWe32ZY(46KK-hqUU0YT?zo+k!nBHu zafv`9g=GT+#)NJcSvkSBbo(x;v|Ysxsnp*y1xr)aC)TXggO&dVh0 z1_+u)UEOkH15E6S1OW`uxZx*Ov3tS>{#>-%v4x z%h)gGG3<00F~^+}IP>$-i4I*(5~f%Mc{hwJ7?EmM_7rSOBDzwAh7mR^X4l6C$x|g;X?TUA+s=TWb(76m`j;vnb6U4Ol51EKP5>n%L=$wwwvqNz85C%?(l1v?RYS!>`1>VT72RNs=fl}C435_0ZXa1jO!pJjnYh@mTQ%7j?ZHG(1v4Z zp1~KIPAy?=N7K4vXyu}WjXO3zfwN24oCwY$F%+A@c_noI0jWeN8fxk;p(h$@OdKrX zBF;rBW}=~B3B45dHw~6>DHp>{-}DwG>`v51YonBB4`WF}iPn~2M3ra!IaGqVS!q@p z@u!EeoU_zdZh;F^S|d4070Xd)tY zua8EaA!3s?zmFK7z_WOcbB!0NQGR6rf5jRcM=Sn@&3F-=cnLlDJGSFx^y3v=j#r`M zHClKb1-ya%coWy*AFSs$aTDIct#}(p@D44%i>L4&?f(mZ;rtx_iRZa~0q=X681+s( zj&I=r&t$amZLVadj^jIA1?a^rEgqn6t+aMCZedX5E}WUg^w13M-EkNVEe|3R>!@id;l2$WCH!Ql^MMoZ#Y%Yi zDAoM{uco`e2OK{n^*$ouK1K`YR{Yxw$|eG%Y5bfZByrc{7X%@RS&zen7{D0>=dT(V wphxi-ElY@N5cm|03hpGD=@axlNEm!%RHi8o;FsQb1i#`ed%M7|@i@Z&0lAGQ82|tP literal 0 HcmV?d00001 diff --git a/backend-idcard/target/classes/cn/iocoder/yudao/module/car/baidu/IdcardRecognizer$IdcardResult.class b/backend-idcard/target/classes/cn/iocoder/yudao/module/car/baidu/IdcardRecognizer$IdcardResult.class new file mode 100644 index 0000000000000000000000000000000000000000..57bb7167018cc718925f8cff1aa3f135729d47bc GIT binary patch literal 2220 zcmbu9Sx*!}5XY;RW7%co1{DuPP!>=|0Yw3U1)?D#n26!Mcc!y6S!R+sNbpl>f-&*I z`^57T8LN7BG&DUA#+Rv{s(*F=x~jVme?5Nz0MoFVha7`N$FrsH_^t@-{n+Kcz3aPi zP1p_(>Nf7}ugaG&1Pe;d|V!aj$CMuj~jXV$c{$S1{;W z{~Owj0_j!fFy`@Hj5Sw!~OHW1%3>r$KTe!jsZm0+i&}~6GbTDXNm!3#wddMp^Mdmxa zw#5TU|8-|$v@LPf^7?;P1viFDG3g#Wf3BNl`>a-OZpCHEZ>Lk!acii$tv`O8pEvNp+-^q`hUq3DS0w_DGb0 zg&A9EuSq;_K^uuJ6#oez%%&d)j<_qSzrE=xO^@vaokYx8^E?r()OZ+*Fb~TNCjY(J zG)?_h&?SIDglfQBRe&6|6h}E~lCszgWwGSSVr7-Z`YDSgQWm$bEba_#H_nJB7r&kO zXQO3ko5r3&*Ape{!Edv29zZXStwaEQa1#F*oPyK%J(_#~{g1Wem&W9?aIT&_i#y5D zQe)#!pc)u#Gn~sK!J#uAsyna5#o{= zVn~O0mjOZDCy2{Nh!Gv)y%A#63^AfZe8_;Hqel>9Mu>48;-eAbsu^Njhxn8MLFb+z zCX5ia4)NItaor4I>kx+-5OnDXV$ujPr9*r%LfkMzOz99`Ga%^36U0p;#EcH{%?NSZ z3^Aiae9wTO=Y}9=jSzD>#1A9H9W%t74)HSsg5ElYug8LVl@?(MZBb#BVFe1m0jjM# A9{>OV literal 0 HcmV?d00001 diff --git a/backend-idcard/target/classes/cn/iocoder/yudao/module/car/baidu/IdcardRecognizer.class b/backend-idcard/target/classes/cn/iocoder/yudao/module/car/baidu/IdcardRecognizer.class new file mode 100644 index 0000000000000000000000000000000000000000..f1c994c53f4241d879eaefedc028b93ce784118a GIT binary patch literal 8181 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 B_5lC@ literal 0 HcmV?d00001 diff --git a/backend-idcard/target/classes/com/ydoyun/ocr/IdcardRecognizer$AesUtil.class b/backend-idcard/target/classes/com/ydoyun/ocr/IdcardRecognizer$AesUtil.class new file mode 100644 index 0000000000000000000000000000000000000000..2e5bf68b3854e8b7cecd5767ad9030c687b1db0b GIT binary patch literal 1331 zcma)5+fEZv6kVq+v`ohfMMUrdUTMo^ynweVN*mP1f`nod!-E;7CuK-GLuRIi`c?V@ zL=!*2cVF~DAH4j9ah)jvFQ{aaIcJ}}&sux!z0ce4uigNdKt7I$z_8^uj8@fcH66pX zJfm2(Os~2iEw|>_&!u;6TKbOzyB-d4`1QoO1lnHbRM5kxUbnM)GT3%d8Ko>YdhcQ#g8}knf<;i)o zS|x@+=Plc@gDGB2XDR}*f?JguqUhFf00#vQm25}OH5<#)TQryJQf1;=X1!v1wz9Vm zW5J5;3uH?Bl+5Jg(y_eO8g(YpOZlaIW+^YA&8%B;EwHI8jsbz5UDZ3DI8JOk){PLs zH466Hiu5!HoYFCfA=a)YgZ8Pw0P%~NuzoleRP`0?SLNbBgJrNa-4AY8ZRA5jE*kSe8q-B)pmI1NKv^0!hT-DkT=p*f) zZO&9wl9zN`#ub4ibrr*=TkJ(9y+{0>X(^0J9anLUO;#;ebj9{r};t+QRSHZO-yNSau!z(HN6#(!KJ^btK z031Ottptvuk2J@)kI~Z~!p!kR(d#3zEu5Sk&2HfI3)&GVG>t3po~R!}#G!U9&f@~l z6~i!hoF9H^WEh=5_Ma>re`Wbdj8FSy(bR(=+pk(0)TN5l{~9AtvZ=%-#%5E=EnJ)% zJ3hLBiLqT_sW9*}lK6sdd}YetLh5WAAe5y9_TxHkP+*dEzlj_vyD3pgjdFjB5fR+R N6j$|71@2%Pi65367{>qr literal 0 HcmV?d00001 diff --git a/backend-idcard/target/classes/com/ydoyun/ocr/IdcardRecognizer$HttpUtil.class b/backend-idcard/target/classes/com/ydoyun/ocr/IdcardRecognizer$HttpUtil.class new file mode 100644 index 0000000000000000000000000000000000000000..c6f8c4dbd0fb93409b099988d1ddf3c0e78d2dd2 GIT binary patch literal 3375 zcma)8TUQ&`75+LLYP$Lp@|LGQc&$G4tzB86-zQ;T- zKjvj~3aSO_r_HlwB4^r@iJ{cAmG%T84`uDF_pm@!Tl-0Y>OLo9X{bVtjxZtuTL!bX zb-Xx}vfNQKm9qq5gHGDaoiyF7+?O9#dsEqhK*!+i`qRa`@=k#s9Bmt1cWcCRv-V`y z?L29}RY^eWKbN-hUe>V-3St6VebZSdF)*|yQLs&5`^Gzai`iVpay2wzhl1@lffabA z!@y1f<*b=2T0;{8t(Be!fOc)-m4>ZomT>M8XskR^4z~TIiai2#kC^GSRVbYHoHLf) zEl=&$@p0V2B$@fVWoHE9n;pLehjJ)gD((~rkBuIQAJlLc+7+~|B4S%!Vr+P@-%c~% zu8t0L@*2zE3LM58Mcl(U3&593Iy32o5nl4pY$Q*fz_C^r921L6xywmvt%VrF_ktUxUMg>k29k zll~`$Mn*OC;E0Y#B{%9B`LOk5(JFYyEN{xmNa28vqxh6S1BGP+quCkDDY9~^+XmWC zN~L2uj!UIF--4MbYe>-v9iPUqK$N1x&QQ_I2PYqwtQ%#}H0oKl7ayI=TPoNB?jhN7 z*|aI6Oq`3)&d$as9Cs#ObaMeeGF>g@Jaw13)q~IK7{}vm4(lo_BuU^ty~3T%rra1=20L09qn=;)N|%*sZnBabHq zYRyb$q?pgUtYnK7yQ4DpHRY}#Z{)RyVi3x(RmihzS+W89-FQF-JFDXy2w@$A9k7|Z z)mXPz#9AR>s6F_ijxVhq>UUko4Gw7#_==9F@l~dsh-G?mxpH|JWiMr;T)(A@l_Bft z>#})$Ltqbq`S8kSa|(!vf7^Pg%~l)E_N}9Cn4KKxYFT zT6ttpmCI9;3A?N^+17hEq5yFu!UOnTvt=l zAk9?W$8{YbjxJ;Cc*iv~F5zRb-6gbi#`ax9D;Fi~KfeDe?kV9wvU(ZG@%R!RDxv#r z^d!SZIM!D}zY%UoK3BqHoQqT(G{V&-3{f}|8!O>6T%3x1jth~Dh@@hKOE{fW4b`Al z+ED+F2Mx7^>;mphM&yPFsyW+=(X0ve2np}fQ$rxJ1$OGEB~NMob{|G12; zoW=eth|0%j7ifj^Q#7HxgRoc>NmV3kXi77*Yxr_9x{RiABg)8@@U>*Ep~-u-622*q zeaom_q=)Oz)ayK}N?&S{s`N!QYNRimq%Rcf!8!dpadk0=XNdC|Jj)+&gSm=v{+H0i zUZNcnd*zJxxIg6Y56PJ+mN`se2ggg;h3CO`;=47DA97WNRl9MJ~RO^OT~uvhb)zzv;M z&lRC}N4i19SNCK%oI0XfHO@mLgN|*nE1atEE6(lmwsd0#jgj&sgRZr|p^Z3Heq|10 zzSzN7b4B`|4DrPmu?lRURprJIni90L@o-9HGbaQBj4hHRO%9qJXcSX6X$pTkYH$|xEyY6g^ zw-oL=x%NLLz!T*wGTzV!?kSBPJaPdT9OKXg%_xmhX>aMElHh5vb*vC&o#K#Bv&e_1 z#^^0wIEoK*XrXukS53T$VqI616eSLYRFQbGs<_4ym6`+-9Ev1p!6melK@sDNNef7E zoOO>^KRYDH1%XV6KieSIVC6%L0;+ezAeQ3f0{Hq*wEc%DNW zi8+e@1Q2E;sD-Y)si>K~=}4wCI!R-PIjg=e!{w@oA{pgjiNU}>I!ihAd8hG)K_qCv zpQ8Xd>L0%4s3Y29<+a87YKvvm7K^1V)IgRak7 z)`QPx?Ys}Y_|6jn^ub~LVsHeG;&XrU7wG?FBtNhwABPk5@d zU!tgQO;}U|B0gz_7%(9oSs@1P5CbN};|vID070C#LJXM@PplAU><~jH#M2B2>OMi7 zwL*-T5YMa-qjrc96XJOW1dSd+j9DSZO^6p(h;w#`aTDTY1_aGLK}=d9924S|72<*& z!Z9IUXF$+TM-UgS5K|_^8!N;mJH(U;@iqg3{&<48Y=xLHA>LUbuG%4HOo;ax5Om!T q#H?2gIO~jgnEl2{A5Cy8~%giGgn9RhPH(;!_ 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 H2#Egy=p#QS literal 0 HcmV?d00001 diff --git a/backend-idcard/target/maven-archiver/pom.properties b/backend-idcard/target/maven-archiver/pom.properties new file mode 100644 index 0000000..14546db --- /dev/null +++ b/backend-idcard/target/maven-archiver/pom.properties @@ -0,0 +1,3 @@ +artifactId=idcard-ocr +groupId=com.ydoyun +version=1.0.0 diff --git a/backend-idcard/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/backend-idcard/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst new file mode 100644 index 0000000..5df64d8 --- /dev/null +++ b/backend-idcard/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -0,0 +1,5 @@ +com/ydoyun/ocr/IdcardRecognizer$HttpUtil.class +com/ydoyun/ocr/IdcardRecognizer$AesKeyUtil.class +com/ydoyun/ocr/IdcardRecognizer$IdcardResult.class +com/ydoyun/ocr/IdcardRecognizer$AesUtil.class +com/ydoyun/ocr/IdcardRecognizer.class diff --git a/backend-idcard/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/backend-idcard/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst new file mode 100644 index 0000000..504013f --- /dev/null +++ b/backend-idcard/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -0,0 +1 @@ +/Users/ouhaolan/project/百盛科技/报表/yudao-ui-admin-vue3/backend-idcard/src/main/java/com/ydoyun/ocr/IdcardRecognizer.java diff --git a/build/vite/index.ts b/build/vite/index.ts new file mode 100644 index 0000000..585759f --- /dev/null +++ b/build/vite/index.ts @@ -0,0 +1,100 @@ +import { resolve } from 'path' +import Vue from '@vitejs/plugin-vue' +import VueJsx from '@vitejs/plugin-vue-jsx' +import progress from 'vite-plugin-progress' +import EslintPlugin from 'vite-plugin-eslint' +import PurgeIcons from 'vite-plugin-purge-icons' +import { ViteEjsPlugin } from 'vite-plugin-ejs' +// @ts-ignore +import ElementPlus from 'unplugin-element-plus/vite' +import AutoImport from 'unplugin-auto-import/vite' +import Components from 'unplugin-vue-components/vite' +import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' +import viteCompression from 'vite-plugin-compression' +import topLevelAwait from 'vite-plugin-top-level-await' +import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite' +import { createSvgIconsPlugin } from 'vite-plugin-svg-icons' +import UnoCSS from 'unocss/vite' + +export function createVitePlugins() { + const root = process.cwd() + + // 路径查找 + function pathResolve(dir: string) { + return resolve(root, '.', dir) + } + + return [ + Vue(), + VueJsx(), + UnoCSS(), + progress(), + PurgeIcons(), + ElementPlus({}), + AutoImport({ + include: [ + /\.[tj]sx?$/, // .ts, .tsx, .js, .jsx + /\.vue$/, + /\.vue\?vue/, // .vue + /\.md$/ // .md + ], + imports: [ + 'vue', + 'vue-router', + // 可额外添加需要 autoImport 的组件 + { + '@/hooks/web/useI18n': ['useI18n'], + '@/hooks/web/useMessage': ['useMessage'], + '@/hooks/web/useTable': ['useTable'], + '@/hooks/web/useCrudSchemas': ['useCrudSchemas'], + '@/utils/formRules': ['required'], + '@/utils/dict': ['DICT_TYPE'] + } + ], + dts: 'src/types/auto-imports.d.ts', + resolvers: [ElementPlusResolver()], + eslintrc: { + enabled: false, // Default `false` + filepath: './.eslintrc-auto-import.json', // Default `./.eslintrc-auto-import.json` + globalsPropValue: true // Default `true`, (true | false | 'readonly' | 'readable' | 'writable' | 'writeable') + } + }), + Components({ + // 生成自定义 `auto-components.d.ts` 全局声明 + dts: 'src/types/auto-components.d.ts', + // 自定义组件的解析器 + resolvers: [ElementPlusResolver()], + globs: ["src/components/**/**.{vue, md}", '!src/components/DiyEditor/components/mobile/**'] + }), + EslintPlugin({ + cache: false, + include: ['src/**/*.vue', 'src/**/*.ts', 'src/**/*.tsx'] // 检查的文件 + }), + VueI18nPlugin({ + runtimeOnly: true, + compositionOnly: true, + include: [resolve(__dirname, 'src/locales/**')] + }), + createSvgIconsPlugin({ + iconDirs: [pathResolve('src/assets/svgs')], + symbolId: 'icon-[dir]-[name]', + svgoOptions: true + }), + viteCompression({ + verbose: true, // 是否在控制台输出压缩结果 + disable: false, // 是否禁用 + threshold: 10240, // 体积大于 threshold 才会被压缩,单位 b + algorithm: 'gzip', // 压缩算法,可选 [ 'gzip' , 'brotliCompress' ,'deflate' , 'deflateRaw'] + ext: '.gz', // 生成的压缩包后缀 + deleteOriginFile: false //压缩后是否删除源文件 + }), + ViteEjsPlugin(), + topLevelAwait({ + // https://juejin.cn/post/7152191742513512485 + // The export name of top-level await promise for each chunk module + promiseExportName: '__tla', + // The function to generate import names of top-level await promise in each chunk module + promiseImportName: (i) => `__tla_${i}` + }) + ] +} diff --git a/build/vite/optimize.ts b/build/vite/optimize.ts new file mode 100644 index 0000000..aa7e68c --- /dev/null +++ b/build/vite/optimize.ts @@ -0,0 +1,122 @@ +const include = [ + 'qs', + 'url', + 'vue', + 'sass', + 'mitt', + 'axios', + 'pinia', + 'dayjs', + 'qrcode', + 'unocss', + 'vue-router', + 'vue-types', + 'vue-i18n', + 'crypto-js', + 'cropperjs', + 'lodash-es', + 'nprogress', + 'web-storage-cache', + '@iconify/iconify', + '@vueuse/core', + '@zxcvbn-ts/core', + 'echarts/core', + 'echarts/charts', + 'echarts/components', + 'echarts/renderers', + 'echarts-wordcloud', + '@wangeditor/editor', + '@wangeditor/editor-for-vue', + '@microsoft/fetch-event-source', + 'markdown-it', + 'markmap-view', + 'markmap-lib', + 'markmap-toolbar', + 'highlight.js', + 'element-plus', + 'element-plus/es', + 'element-plus/es/locale/lang/zh-cn', + 'element-plus/es/locale/lang/en', + 'element-plus/es/components/avatar/style/css', + 'element-plus/es/components/space/style/css', + 'element-plus/es/components/backtop/style/css', + 'element-plus/es/components/form/style/css', + 'element-plus/es/components/radio-group/style/css', + 'element-plus/es/components/radio/style/css', + 'element-plus/es/components/checkbox/style/css', + 'element-plus/es/components/checkbox-group/style/css', + 'element-plus/es/components/switch/style/css', + 'element-plus/es/components/time-picker/style/css', + 'element-plus/es/components/date-picker/style/css', + 'element-plus/es/components/descriptions/style/css', + 'element-plus/es/components/descriptions-item/style/css', + 'element-plus/es/components/link/style/css', + 'element-plus/es/components/tooltip/style/css', + 'element-plus/es/components/drawer/style/css', + 'element-plus/es/components/dialog/style/css', + 'element-plus/es/components/checkbox-button/style/css', + 'element-plus/es/components/option-group/style/css', + 'element-plus/es/components/radio-button/style/css', + 'element-plus/es/components/cascader/style/css', + 'element-plus/es/components/color-picker/style/css', + 'element-plus/es/components/input-number/style/css', + 'element-plus/es/components/rate/style/css', + 'element-plus/es/components/select-v2/style/css', + 'element-plus/es/components/tree-select/style/css', + 'element-plus/es/components/slider/style/css', + 'element-plus/es/components/time-select/style/css', + 'element-plus/es/components/autocomplete/style/css', + 'element-plus/es/components/image-viewer/style/css', + 'element-plus/es/components/upload/style/css', + 'element-plus/es/components/col/style/css', + 'element-plus/es/components/form-item/style/css', + 'element-plus/es/components/alert/style/css', + 'element-plus/es/components/breadcrumb/style/css', + 'element-plus/es/components/select/style/css', + 'element-plus/es/components/input/style/css', + 'element-plus/es/components/breadcrumb-item/style/css', + 'element-plus/es/components/tag/style/css', + 'element-plus/es/components/pagination/style/css', + 'element-plus/es/components/table/style/css', + 'element-plus/es/components/table-v2/style/css', + 'element-plus/es/components/table-column/style/css', + 'element-plus/es/components/card/style/css', + 'element-plus/es/components/row/style/css', + 'element-plus/es/components/button/style/css', + 'element-plus/es/components/menu/style/css', + 'element-plus/es/components/sub-menu/style/css', + 'element-plus/es/components/menu-item/style/css', + 'element-plus/es/components/option/style/css', + 'element-plus/es/components/dropdown/style/css', + 'element-plus/es/components/dropdown-menu/style/css', + 'element-plus/es/components/dropdown-item/style/css', + 'element-plus/es/components/skeleton/style/css', + 'element-plus/es/components/skeleton/style/css', + 'element-plus/es/components/backtop/style/css', + 'element-plus/es/components/menu/style/css', + 'element-plus/es/components/sub-menu/style/css', + 'element-plus/es/components/menu-item/style/css', + 'element-plus/es/components/dropdown/style/css', + 'element-plus/es/components/tree/style/css', + 'element-plus/es/components/dropdown-menu/style/css', + 'element-plus/es/components/dropdown-item/style/css', + 'element-plus/es/components/badge/style/css', + 'element-plus/es/components/breadcrumb/style/css', + 'element-plus/es/components/breadcrumb-item/style/css', + 'element-plus/es/components/image/style/css', + 'element-plus/es/components/collapse-transition/style/css', + 'element-plus/es/components/timeline/style/css', + 'element-plus/es/components/timeline-item/style/css', + 'element-plus/es/components/collapse/style/css', + 'element-plus/es/components/collapse-item/style/css', + 'element-plus/es/components/button-group/style/css', + 'element-plus/es/components/text/style/css', + 'element-plus/es/components/segmented/style/css', + '@element-plus/icons-vue', + 'element-plus/es/components/footer/style/css', + 'element-plus/es/components/empty/style/css' +] + +const exclude = ['@iconify/json'] + +export { include, exclude } diff --git a/src/views/ydoyun/customtag/CustomTagFormWithParams.vue b/src/views/ydoyun/customtag/CustomTagFormWithParams.vue new file mode 100644 index 0000000..3866199 --- /dev/null +++ b/src/views/ydoyun/customtag/CustomTagFormWithParams.vue @@ -0,0 +1,311 @@ + + + + diff --git a/src/views/ydoyun/productcustomtag/index.vue b/src/views/ydoyun/productcustomtag/index.vue new file mode 100644 index 0000000..d6a8bc7 --- /dev/null +++ b/src/views/ydoyun/productcustomtag/index.vue @@ -0,0 +1,190 @@ + + + diff --git a/src/views/ydoyun/reportdatabase/DatabaseList.vue b/src/views/ydoyun/reportdatabase/DatabaseList.vue new file mode 100644 index 0000000..e4101aa --- /dev/null +++ b/src/views/ydoyun/reportdatabase/DatabaseList.vue @@ -0,0 +1,111 @@ + + + diff --git a/src/views/ydoyun/storecustomtag/index.vue b/src/views/ydoyun/storecustomtag/index.vue new file mode 100644 index 0000000..056cc10 --- /dev/null +++ b/src/views/ydoyun/storecustomtag/index.vue @@ -0,0 +1,190 @@ + + + diff --git a/src/views/ydoyun/suppliercustomtag/index.vue b/src/views/ydoyun/suppliercustomtag/index.vue new file mode 100644 index 0000000..87cf95a --- /dev/null +++ b/src/views/ydoyun/suppliercustomtag/index.vue @@ -0,0 +1,190 @@ + + +