emblemscanner: subpackage support

This commit is contained in:
Fam Zheng 2025-10-15 11:09:48 +01:00
parent fffc50372e
commit e68348fbff
9 changed files with 301 additions and 26 deletions

1
.gitignore vendored
View File

@ -5,3 +5,4 @@ build
/dataset/local
/detection/model
/api/db.sqlite3
/emblemscanner-release

View File

@ -135,8 +135,8 @@ RELEASE_VERSION := $(shell git describe --tags --abbrev=0 | sed 's/^emblemscanne
emblemscanner-release: build/emblemscanner-$(RELEASE_VERSION).tar.gz
build/emblemscanner-$(RELEASE_VERSION).tar.gz:
# if tree dirty or git head not tagged, error out
build/emblemscanner-$(RELEASE_VERSION).tar.gz: FORCE
- rm -rf $@
git diff --exit-code
if test -z "$(RELEASE_VERSION)"; then \
echo "RELEASE_VERSION is empty"; \
@ -146,4 +146,6 @@ build/emblemscanner-$(RELEASE_VERSION).tar.gz:
echo "git head not tagged as $(RELEASE_VERSION)"; \
exit 1; \
fi
tar -czvf build/emblemscanner-$(RELEASE_VERSION).tar.gz -C scanner pages/emblemscanner
tar -cf build/emblemscanner-$(RELEASE_VERSION).tar -C scanner pages/emblemscanner
cd scanner/pages/emblemscanner && tar -uf ../../../build/emblemscanner-$(RELEASE_VERSION).tar README.md
gzip build/emblemscanner-$(RELEASE_VERSION).tar

View File

@ -7,7 +7,23 @@ var performance = {
};
Module['instantiateWasm'] = (info, receiveInstance) => {
console.log("loading wasm...", info);
WebAssembly.instantiate("pages/emblemscanner/qrtool.wx.wasm.br", info).then((result) => {
// Use dynamic path from global data if available, otherwise fallback to hardcoded path
var wasmPath = "pages/emblemscanner/qrtool.wx.wasm.br";
try {
// Check if we're in a WeChat miniprogram environment and have access to getApp
if (typeof getApp !== 'undefined') {
var app = getApp();
if (app && app.globalData && app.globalData.wasmFilePath) {
wasmPath = app.globalData.wasmFilePath;
console.log("Using dynamic WASM path:", wasmPath);
}
}
} catch (e) {
console.warn("Failed to get dynamic WASM path, using fallback:", e);
}
WebAssembly.instantiate(wasmPath, info).then((result) => {
console.log("result:", result);
var inst = result['instance'];
receiveInstance(inst);

View File

@ -0,0 +1,104 @@
# EmblemScanner 微信小程序扫码验证模块
## Release notes
- Version 1.0.1: initial release
- Version 1.1.0: sub package support
## 概述
EmblemScanner 是一个专为微信小程序设计的二维码扫描验证模块,集成了先进的 AI 图像识别技术和二维码检测算法,能够快速准确地识别和验证二维码。
## 集成方式和接口
### 调用参数
| 参数 | 说明 | 示例值 |
|------|------|--------|
| return_page | 扫描完成后跳转的页面路径 | `/pages/scan_result/scan_result` |
### 扫描结果
扫描成功后,模块会跳转到指定的 return_page携带以下参数
- `ok=1` - 扫描成功
- `qr_code` - 二维码内容
- `serial_code` - 验证序列号(如有)
扫描失败时:
- `ok=0` - 扫描失败
### 依赖要求
- 相机权限
- 网络访问权限(连接 themblem.com API等
## 使用步骤
### 1. 项目准备
创建 JS 版小程序项目。
### 2. 文件部署
解压 emblemscanner 代码到小程序项目目录。
```
pages/emblemscanner/
├── assets/ # 静态资源
├── emblemscanner.json # 页面配置
├── emblemscanner.wxml # 页面结构
├── emblemscanner.wxss # 样式文件
├── emblemscanner.js # 主页面逻辑
├── libemblemscanner.js # 核心库
├── qrprocessor.js # 二维码处理器
├── qrtool.wx.js # 二维码工具
├── qrtool.wx.wasm.br # WebAssembly 二进制
├── upload.js # 上传模块
└── worker/ # Worker 线程
```
### 3. 项目配置
`app.json` 中添加页面和 worker 配置:
```json
{
"pages": [
"pages/index/index",
"pages/emblemscanner/emblemscanner",
"pages/logs/logs"
],
"workers": "pages/emblemscanner/worker"
}
```
### 4. 域名配置
参考[微信小程序网络配置指南](https://developers.weixin.qq.com/miniprogram/dev/framework/ability/network.html),在小程序后台配置合法域名:
- `themblem.com`
### 5. 扫码调用
#### 跳转到扫码页面:
```javascript
navigateToScanner() {
wx.navigateTo({
url: '/pages/emblemscanner/emblemscanner?return_page=/pages/scan_result/scan_result'
})
}
```
#### 处理扫码结果:
```javascript
onLoad(options) {
var verify_ok = options['ok']; // ok == 1表示验证通过
var qr_code = options['qr_code']; // 二维码内容
var serial_code = options['serial_code']; // 序列码
this.setData({
verify_ok,
qr_code,
serial_code,
})
}
```

View File

@ -1,6 +1,12 @@
// QR Scanner Module - Self-contained QR scanning page
// Adapted from existing camera implementation
//
// DYNAMIC PATH RESOLUTION:
// - The system now dynamically computes the correct WASM file and worker paths based on the current page location
// - This allows the emblemscanner to work in both main pages directory and subpackages
// - Paths are computed using getCurrentPages() API and stored in global data during page load
// - WASM files and workers use these dynamic paths instead of hardcoded absolute paths
//
// SCANNING MODES:
// 1. Web View Mode - Uses external camera-5.1 web interface in web-view component
// - Fallback for devices that need special handling
@ -31,7 +37,9 @@ const {
make_query,
fetch_real_ip,
get_tenant_id,
is_emblem_qr_pattern
is_emblem_qr_pattern,
get_current_package_path,
get_wasm_file_path
} = require('./libemblemscanner.js');
// Import upload functionality for verification (self-contained)
@ -115,6 +123,13 @@ Page({
no_web_view: no_web_view
});
// Store current package path in global data for dynamic WASM loading
const currentPackagePath = get_current_package_path();
getApp().globalData.currentPackagePath = currentPackagePath;
getApp().globalData.wasmFilePath = get_wasm_file_path('qrtool.wx.wasm.br');
console.log('Current package path:', currentPackagePath);
console.log('WASM file path:', getApp().globalData.wasmFilePath);
// Log page load for backend reporting (like camera.js)
this.log("emblemscanner page load");
this.log("options", JSON.stringify(options));
@ -189,8 +204,12 @@ Page({
* Set up worker for iPhone processing like camera.js
*/
setupWorker() {
// Create worker with local worker file
var worker = wx.createWorker('/pages/emblemscanner/worker/index.js', {
// Create worker with dynamic path based on current package location
var packagePath = getApp().globalData.currentPackagePath || 'pages/emblemscanner';
var workerPath = `${packagePath}/worker/index.js`;
console.log('Creating worker with path:', workerPath);
var worker = wx.createWorker(workerPath, {
useExperimentalWorker: true,
});
@ -240,7 +259,10 @@ Page({
if (this.data.app_state === 'final_scanning' && result.ok) {
// Request worker to submit image data
this.get_worker().postMessage({ type: "ready_to_submit" });
const worker = this.get_worker();
if (worker) {
worker.postMessage({ type: "ready_to_submit" });
}
}
}
@ -361,7 +383,6 @@ Page({
show_scanguide() {
console.log('show_scanguide');
this.setData({
show_modal: 'scanguide',
show_tip: false
@ -420,12 +441,12 @@ Page({
});
this.log(`Camera set initial zoom to ${initial_zoom}x, will zoom in to ${zoom}x when QR is found`);
this.camera_context.setZoom({ zoom: 6 });
this.camera_context.setZoom({ zoom: initial_zoom });
// Set up zoom-in behavior when QR is found
this.on_first_qr_found = () => {
this.log(`First QR found, zoom to ${zoom}x`);
this.camera_context.setZoom({ zoom: 6 });
this.camera_context.setZoom({ zoom: zoom });
this.setData({
zoom: zoom,
qrmarkers_class: '',
@ -479,7 +500,7 @@ Page({
});
// Start the listener with worker if using worker mode
var worker = this.get_worker();
var worker = this.data.use_worker ? this.get_worker() : null;
this.listener.start({
worker,
});
@ -531,12 +552,15 @@ Page({
// Set processing flag before sending message
this.setData({ worker_processing: true });
this.get_worker().postMessage({
type: 'frame',
width: frame.width,
height: frame.height,
camera_sensitivity: this.data.camera_sensitivity
});
const worker = this.get_worker();
if (worker) {
worker.postMessage({
type: 'frame',
width: frame.width,
height: frame.height,
camera_sensitivity: this.data.camera_sensitivity
});
}
} else {
// Direct processing (other devices)
this.processFrameDirect(frame);
@ -698,7 +722,6 @@ Page({
submitImageForVerification(dataUrls, qrCode) {
this.log('Submitting images for verification');
const begin = Date.now();
wx.vibrateShort({ type: "heavy" });
const success = (res) => {
this.log(`Upload success, code: ${res.statusCode}`);
@ -1029,6 +1052,7 @@ Page({
*/
onUnload() {
this.cleanupListener();
this.terminateWorker();
},
/**
@ -1053,11 +1077,10 @@ Page({
},
get_worker() {
var gd = getApp().globalData;
if (!gd.emblemscanner_worker) {
gd.emblemscanner_worker = this.setupWorker();
if (!this.worker && this.data.use_worker) {
this.worker = this.setupWorker();
}
return gd.emblemscanner_worker;
return this.worker;
},
/**
@ -1075,6 +1098,17 @@ Page({
this.log('Worker state cleaned up');
}
},
/**
* Terminate and clean up worker on page unload
*/
terminateWorker() {
if (this.worker) {
this.worker.terminate();
this.worker = null;
this.log('Worker terminated');
}
}
});

View File

@ -1,3 +1,13 @@
page {
width: 100%;
height: 100%;
}
.hidden {
display:none;
}
/* Main container */
view.wrapper {
width: 100%;

View File

@ -131,6 +131,83 @@ function is_emblem_qr_pattern(p) {
return false;
}
/**
* Get the current package path for dynamic WASM file loading
* This function determines the correct path to WASM files based on the current page location
*/
function get_current_package_path() {
try {
// Get current pages stack to determine current page location
const pages = getCurrentPages();
if (pages && pages.length > 0) {
const currentPage = pages[pages.length - 1];
const route = currentPage.route;
// Extract package path from route
// For example: "bofen/packages/emblemscanner/emblemscanner" -> "bofen/packages/emblemscanner"
const pathParts = route.split('/');
if (pathParts.length >= 2) {
// Remove the page file name and return the package path
return pathParts.slice(0, -1).join('/');
}
}
} catch (e) {
console.warn('Failed to get current package path:', e);
}
// Fallback: assume we're in the main pages directory
return 'pages/emblemscanner';
}
/**
* Get the full WASM file path based on current package location
*/
function get_wasm_file_path(filename) {
const packagePath = get_current_package_path();
return `${packagePath}/${filename}`;
}
/**
* Test function to verify path resolution works correctly
* This can be called in development to test the path resolution logic
*/
function test_path_resolution() {
console.log('Testing path resolution...');
// Mock getCurrentPages for testing (would normally be available in WeChat miniprogram)
const originalGetCurrentPages = global.getCurrentPages;
global.getCurrentPages = function() {
return [{
route: 'bofen/packages/emblemscanner/emblemscanner'
}];
};
try {
const packagePath = get_current_package_path();
const wasmPath = get_wasm_file_path('qrtool.wx.wasm.br');
console.log('Expected package path: bofen/packages/emblemscanner');
console.log('Actual package path:', packagePath);
console.log('Expected WASM path: bofen/packages/emblemscanner/qrtool.wx.wasm.br');
console.log('Actual WASM path:', wasmPath);
if (packagePath === 'bofen/packages/emblemscanner' && wasmPath === 'bofen/packages/emblemscanner/qrtool.wx.wasm.br') {
console.log('✅ Path resolution test PASSED');
return true;
} else {
console.log('❌ Path resolution test FAILED');
return false;
}
} finally {
// Restore original function
if (originalGetCurrentPages) {
global.getCurrentPages = originalGetCurrentPages;
} else {
delete global.getCurrentPages;
}
}
}
module.exports = {
get_system_info,
get_phone_model,
@ -138,5 +215,8 @@ module.exports = {
make_query,
fetch_real_ip,
get_tenant_id,
is_emblem_qr_pattern
is_emblem_qr_pattern,
get_current_package_path,
get_wasm_file_path,
test_path_resolution
};

View File

@ -34,7 +34,21 @@ var performance = {
};
Module["instantiateWasm"] = (info, receiveInstance) => {
console.log("loading wasm...", info);
WebAssembly.instantiate("pages/emblemscanner/qrtool.wx.wasm.br", info).then(result => {
// Use dynamic path from global data if available, otherwise fallback to hardcoded path
var wasmPath = "pages/emblemscanner/qrtool.wx.wasm.br";
try {
// Check if we're in a WeChat miniprogram environment and have access to getApp
if (typeof getApp !== "undefined") {
var app = getApp();
if (app && app.globalData && app.globalData.wasmFilePath) {
wasmPath = app.globalData.wasmFilePath;
console.log("Using dynamic WASM path:", wasmPath);
}
}
} catch (e) {
console.warn("Failed to get dynamic WASM path, using fallback:", e);
}
WebAssembly.instantiate(wasmPath, info).then(result => {
console.log("result:", result);
var inst = result["instance"];
receiveInstance(inst);

View File

@ -34,7 +34,21 @@ var performance = {
};
Module["instantiateWasm"] = (info, receiveInstance) => {
console.log("loading wasm...", info);
WebAssembly.instantiate("pages/emblemscanner/qrtool.wx.wasm.br", info).then(result => {
// Use dynamic path from global data if available, otherwise fallback to hardcoded path
var wasmPath = "pages/emblemscanner/qrtool.wx.wasm.br";
try {
// Check if we're in a WeChat miniprogram environment and have access to getApp
if (typeof getApp !== "undefined") {
var app = getApp();
if (app && app.globalData && app.globalData.wasmFilePath) {
wasmPath = app.globalData.wasmFilePath;
console.log("Using dynamic WASM path:", wasmPath);
}
}
} catch (e) {
console.warn("Failed to get dynamic WASM path, using fallback:", e);
}
WebAssembly.instantiate(wasmPath, info).then(result => {
console.log("result:", result);
var inst = result["instance"];
receiveInstance(inst);