chore: remove all mock/debug code for production release
Miniprogram: - ENV switched to 'prod' (disables __DEV__ flag) - Deleted mock.js and test-ble-frame.js - Removed mockBind/mockPurchase from api.js - Removed all debug UI panels and onMock* handlers from 6 pages - Removed mock purchase fallback in subscribe-plans - Removed SIMPLE PERIPHERAL dev board from BLE scan Server: - Removed /device/mock-bind route - Removed /subscription/mock-purchase route - Removed dev_openid fallback in wechat.js - Removed mockBind() from binding.dao.js - Removed mock fallback in purchase endpoint - NODE_ENV default changed to 'production' Admin: - ENV switched to 'prod' All mock code preserved in 'test' branch for future development use.
这个提交包含在:
@@ -1,5 +1,5 @@
|
||||
// Production: change to 'prod'
|
||||
const ENV = 'test'
|
||||
const ENV = 'prod'
|
||||
|
||||
const API_BASES = {
|
||||
local: 'http://localhost:3000',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Production: change to 'prod' to disable debug buttons and mock endpoints
|
||||
var ENV = 'test'
|
||||
var ENV = 'prod'
|
||||
|
||||
var API_BASES = {
|
||||
local: 'http://localhost:3000',
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
var ble = require('../../services/ble')
|
||||
var config = require('../../config/env')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
statusBarHeight: 44,
|
||||
@@ -9,8 +7,7 @@ Page({
|
||||
regions: 0x7F,
|
||||
regionData: [],
|
||||
timeout: false,
|
||||
error: '',
|
||||
devMode: false
|
||||
error: ''
|
||||
},
|
||||
|
||||
onBack: function () {
|
||||
@@ -25,8 +22,7 @@ Page({
|
||||
var app = getApp()
|
||||
this.setData({
|
||||
statusBarHeight: app.globalData.statusBarHeight,
|
||||
regions: parseInt(options.regions) || 0x7F,
|
||||
devMode: config.__DEV__ || false
|
||||
regions: parseInt(options.regions) || 0x7F
|
||||
})
|
||||
this.startScan()
|
||||
},
|
||||
@@ -93,15 +89,4 @@ Page({
|
||||
})
|
||||
},
|
||||
|
||||
onMockScanDone: function () {
|
||||
var self = this
|
||||
if (self._progressTimer) clearInterval(self._progressTimer)
|
||||
var mask = parseInt(self.data.regions) || 0x7F
|
||||
wx.redirectTo({
|
||||
url: '/pages/treating/treating?regions=' + mask +
|
||||
'&wavelength=2' +
|
||||
'&duration=600000' +
|
||||
'&mode=1'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -17,11 +17,6 @@
|
||||
<view class="scan-hint">请将面罩对准面部</view>
|
||||
<view class="scan-hint" style="font-size:24rpx;color:#999;margin-top:8rpx;">连接设备后可获取皮肤数据</view>
|
||||
|
||||
<view class="debug-actions" wx:if="{{devMode}}">
|
||||
<view class="debug-label">🔧 调试工具</view>
|
||||
<button class="btn-secondary btn-sm" bindtap="onMockScanDone">模拟扫描完成</button>
|
||||
</view>
|
||||
|
||||
<view class="scan-progress" wx:if="{{scanning}}">
|
||||
正在扫描... <text class="highlight">{{regionData.length || 3}}</text> 个区域
|
||||
</view>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
var ble = require('../../services/ble')
|
||||
var http = require('../../utils/request')
|
||||
var config = require('../../config/env')
|
||||
var app = getApp()
|
||||
var SCAN_TIMEOUT = 15000
|
||||
|
||||
@@ -11,7 +10,6 @@ Page({
|
||||
bindToken: '',
|
||||
state: 'scanning',
|
||||
error: '',
|
||||
devMode: false
|
||||
},
|
||||
|
||||
onBack: function () {
|
||||
@@ -26,8 +24,7 @@ Page({
|
||||
this.setData({ statusBarHeight: app.globalData.statusBarHeight })
|
||||
this.setData({
|
||||
deviceId: options.device_id || '',
|
||||
bindToken: options.bind_token || '',
|
||||
devMode: config.__DEV__ || false
|
||||
bindToken: options.bind_token || ''
|
||||
})
|
||||
this.startBleConnect()
|
||||
},
|
||||
@@ -110,24 +107,4 @@ Page({
|
||||
}, 1000)
|
||||
},
|
||||
|
||||
onMockConnect: function () {
|
||||
var self = this
|
||||
clearTimeout(this._scanTimer)
|
||||
ble.stopScan()
|
||||
self.setData({ state: 'binding' })
|
||||
http.post('/api/v1/device/mock-bind', {
|
||||
device_id: self.data.deviceId
|
||||
}).then(function () {
|
||||
self.setData({ state: 'done' })
|
||||
wx.redirectTo({
|
||||
url: '/pages/bind-success/bind-success?device_id=' + self.data.deviceId
|
||||
})
|
||||
}).catch(function (err) {
|
||||
self.setData({ state: 'error', error: err.message || '模拟绑定失败' })
|
||||
})
|
||||
},
|
||||
|
||||
onMockBindSuccess: function () {
|
||||
this.onMockConnect()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -28,10 +28,5 @@
|
||||
<button class="retry-btn" bindtap="onRetry">重试</button>
|
||||
</view>
|
||||
|
||||
<view class="debug-actions" wx:if="{{devMode}}">
|
||||
<view class="debug-label">🔧 调试工具</view>
|
||||
<button class="btn-secondary btn-sm" bindtap="onMockConnect">模拟连接成功</button>
|
||||
<button class="btn-secondary btn-sm" bindtap="onMockBindSuccess">模拟绑定成功</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
var ble = require('../../services/ble')
|
||||
var api = require('../../utils/api')
|
||||
var config = require('../../config/env')
|
||||
var app = getApp()
|
||||
|
||||
Page({
|
||||
@@ -14,14 +13,9 @@ Page({
|
||||
bleState: 'disconnected',
|
||||
hasDevice: false,
|
||||
deviceInfo: null,
|
||||
devMode: false,
|
||||
loading: true
|
||||
},
|
||||
|
||||
onLoad: function () {
|
||||
this.setData({ devMode: config.__DEV__ || false })
|
||||
},
|
||||
|
||||
onShow: function () {
|
||||
this.checkState()
|
||||
this._onStatus = this.onBleStatus.bind(this)
|
||||
@@ -173,26 +167,6 @@ Page({
|
||||
wx.navigateTo({ url: '/pages/wear-check/wear-check' })
|
||||
},
|
||||
|
||||
onMockBind: function () {
|
||||
var self = this
|
||||
wx.showModal({
|
||||
title: '模拟绑定',
|
||||
editable: true,
|
||||
placeholderText: '输入设备编号(需先在后台创建)',
|
||||
success: function (res) {
|
||||
if (res.confirm && res.content) {
|
||||
var deviceId = res.content.trim()
|
||||
api.mockBind(deviceId).then(function () {
|
||||
wx.showToast({ title: '模拟绑定成功', icon: 'success' })
|
||||
self.checkState()
|
||||
}).catch(function (err) {
|
||||
wx.showToast({ title: err.message || '绑定失败', icon: 'none' })
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
onViewSubscription: function () {
|
||||
wx.navigateTo({ url: '/pages/subscribe-plans/subscribe-plans' })
|
||||
}
|
||||
|
||||
@@ -9,10 +9,6 @@
|
||||
<text class="empty-icon">📱</text>
|
||||
<view class="empty-text">还没有绑定设备</view>
|
||||
<button class="btn-primary mt-30" bindtap="onAddDevice">扫码添加设备</button>
|
||||
<view class="debug-actions" wx:if="{{devMode}}">
|
||||
<view class="debug-label">🔧 调试工具</view>
|
||||
<button class="btn-secondary btn-sm" bindtap="onMockBind">模拟绑定设备</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
var api = require('../../utils/api')
|
||||
var config = require('../../config/env')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
@@ -163,17 +162,12 @@ Page({
|
||||
var self = this
|
||||
self.setData({ purchasing: true })
|
||||
api.purchase(plan.key).then(function (data) {
|
||||
if (data.mock || !data.payment_params) {
|
||||
// Dev fallback: use mock purchase
|
||||
return api.mockPurchase(plan.key).then(function () {
|
||||
if (!data.payment_params) {
|
||||
self.setData({ purchasing: false })
|
||||
wx.showToast({ title: '支付成功', icon: 'success' })
|
||||
setTimeout(function () {
|
||||
wx.redirectTo({ url: '/pages/subscribe-success/subscribe-success?plan=' + plan.key })
|
||||
}, 1000)
|
||||
})
|
||||
wx.showToast({ title: '支付服务暂不可用', icon: 'none' })
|
||||
return
|
||||
}
|
||||
// Real payment
|
||||
|
||||
var params = data.payment_params
|
||||
self._currentOrderId = data.order_id
|
||||
wx.requestPayment({
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
var ble = require('../../services/ble')
|
||||
var api = require('../../utils/api')
|
||||
var commandSync = require('../../services/command-sync')
|
||||
var config = require('../../config/env')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
statusBarHeight: 44,
|
||||
@@ -18,7 +16,6 @@ Page({
|
||||
paused: false,
|
||||
completed: false,
|
||||
startedAt: 0,
|
||||
devMode: false,
|
||||
regionText: '全脸护理',
|
||||
},
|
||||
|
||||
@@ -34,7 +31,7 @@ Page({
|
||||
|
||||
onLoad: function (options) {
|
||||
var app = getApp()
|
||||
this.setData({ statusBarHeight: app.globalData.statusBarHeight, devMode: config.__DEV__ || false })
|
||||
this.setData({ statusBarHeight: app.globalData.statusBarHeight })
|
||||
this._deviceId = ble.getDeviceId() || (app.globalData.currentDevice && app.globalData.currentDevice.device_id) || ''
|
||||
var regions = parseInt(options.regions) || 0x7F
|
||||
this.setData({
|
||||
@@ -259,7 +256,4 @@ Page({
|
||||
})
|
||||
},
|
||||
|
||||
onMockComplete: function () {
|
||||
this.finishAsComplete()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -27,11 +27,6 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="debug-actions" wx:if="{{devMode}}">
|
||||
<view class="debug-label">🔧 调试工具</view>
|
||||
<button class="btn-secondary btn-sm" bindtap="onMockComplete">模拟护理完成</button>
|
||||
</view>
|
||||
|
||||
<button class="btn-primary btn-primary-red" bindtap="onStop">停止护理</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
var ble = require('../../services/ble')
|
||||
var config = require('../../config/env')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
statusBarHeight: 44,
|
||||
@@ -16,8 +14,7 @@ Page({
|
||||
subDays: 6,
|
||||
fixedDurationMs: 600000,
|
||||
submitting: false,
|
||||
error: '',
|
||||
devMode: false
|
||||
error: ''
|
||||
},
|
||||
|
||||
onBack: function () {
|
||||
@@ -30,7 +27,7 @@ Page({
|
||||
|
||||
onLoad: function () {
|
||||
var app = getApp()
|
||||
this.setData({ statusBarHeight: app.globalData.statusBarHeight, devMode: config.__DEV__ || false })
|
||||
this.setData({ statusBarHeight: app.globalData.statusBarHeight })
|
||||
this.checkSubscription()
|
||||
},
|
||||
|
||||
@@ -109,17 +106,4 @@ Page({
|
||||
})
|
||||
},
|
||||
|
||||
onMockStart: function () {
|
||||
var mask = 0
|
||||
this.data.regions.forEach(function (r) {
|
||||
if (r.checked) mask |= r.mask
|
||||
})
|
||||
if (mask === 0) mask = 0x7F
|
||||
wx.redirectTo({
|
||||
url: '/pages/treating/treating?regions=' + mask +
|
||||
'&wavelength=2' +
|
||||
'&duration=' + this.data.fixedDurationMs +
|
||||
'&mode=' + this.data.selectedMode
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -38,10 +38,5 @@
|
||||
</button>
|
||||
|
||||
<view class="error-text" wx:if="{{error}}">{{error}}</view>
|
||||
|
||||
<view class="debug-actions" wx:if="{{devMode}}">
|
||||
<view class="debug-label">🔧 调试工具</view>
|
||||
<button class="btn-secondary btn-sm" bindtap="onMockStart">模拟开始护理(跳过BLE)</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
var ble = require('../../services/ble')
|
||||
var config = require('../../config/env')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
statusBarHeight: 44,
|
||||
checking: false,
|
||||
result: null,
|
||||
error: '',
|
||||
devMode: false,
|
||||
battery: 0,
|
||||
connected: false
|
||||
},
|
||||
@@ -22,7 +19,7 @@ Page({
|
||||
|
||||
onLoad: function () {
|
||||
var app = getApp()
|
||||
this.setData({ statusBarHeight: app.globalData.statusBarHeight, devMode: config.__DEV__ || false })
|
||||
this.setData({ statusBarHeight: app.globalData.statusBarHeight })
|
||||
var device = app.globalData.currentDevice || {}
|
||||
this.setData({
|
||||
battery: device.battery || 0,
|
||||
@@ -108,7 +105,4 @@ Page({
|
||||
}, 500)
|
||||
},
|
||||
|
||||
onMockWearOk: function () {
|
||||
this.setData({ checking: false, result: 'ok' })
|
||||
}
|
||||
})
|
||||
|
||||
@@ -41,10 +41,5 @@
|
||||
</view>
|
||||
|
||||
<button wx:if="{{result === 'ok'}}" class="btn-primary mt-30" bindtap="onNext">设置护理参数</button>
|
||||
|
||||
<view class="debug-actions" wx:if="{{devMode}}">
|
||||
<view class="debug-label">🔧 调试工具</view>
|
||||
<button class="btn-secondary btn-sm" bindtap="onMockWearOk">模拟佩戴确认</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
global.wx = global.wx || {
|
||||
onBLEConnectionStateChange: function () {}
|
||||
}
|
||||
|
||||
const ble = require('../services/ble')
|
||||
|
||||
function assert(condition, message) {
|
||||
if (!condition) throw new Error(message)
|
||||
}
|
||||
|
||||
function bytes(buffer) {
|
||||
return Array.from(new Uint8Array(buffer))
|
||||
}
|
||||
|
||||
function xor(arr) {
|
||||
return arr.reduce((acc, item) => acc ^ item, 0)
|
||||
}
|
||||
|
||||
function testBuildFrameChecksum() {
|
||||
const frame = bytes(ble.buildFrame(0x02, [0x7F, 0x01]))
|
||||
assert(frame[0] === 0xAA && frame[1] === 0x55, 'frame header mismatch')
|
||||
assert(frame[2] === 0x02, 'frame length mismatch')
|
||||
assert(frame[3] === 0x02, 'frame type mismatch')
|
||||
assert(frame[frame.length - 1] === xor(frame.slice(0, -1)), 'checksum must include header, len, type, payload')
|
||||
}
|
||||
|
||||
function testParseStatusFrame() {
|
||||
const payload = [0x02, 0x7F, 0x02, 200, 0x00, 0x09, 0x27, 0xC0, 0x00, 0x01, 86, 36, 1, 2]
|
||||
const frame = ble.buildFrame(0x21, payload)
|
||||
const parsed = ble.parseFrame(frame)
|
||||
assert(parsed.type === 0x21, 'status type mismatch')
|
||||
const status = ble.parseStatusReport(parsed.payload)
|
||||
assert(status.mode_state === 0x02, 'mode_state mismatch')
|
||||
assert(status.region_mask === 0x7F, 'region mask mismatch')
|
||||
assert(status.remaining_ms === 600000, 'remaining_ms mismatch')
|
||||
assert(status.battery === 86, 'battery mismatch')
|
||||
assert(status.temperature === 36, 'temperature mismatch')
|
||||
}
|
||||
|
||||
function testBindPayloadLength() {
|
||||
const userBytes = ble.hexToBytes('0000000000000001')
|
||||
const tokenBytes = ble.hexToBytes('0011223344556677')
|
||||
assert(userBytes.length === 8, 'user id must be 8 bytes')
|
||||
assert(tokenBytes.length === 8, 'bind token must be 8 bytes')
|
||||
}
|
||||
|
||||
function testVendorCommand33() {
|
||||
const command = ble.buildVendorCommand33({
|
||||
region_mask: 0x1F,
|
||||
wavelength: ble.WAVELENGTH.R,
|
||||
brightness: 200,
|
||||
duration_ms: 600000
|
||||
})
|
||||
assert(command.length === 33, 'vendor command must be 33 bytes')
|
||||
assert(command[32] === xor(command.slice(0, 32)), 'vendor command checksum mismatch')
|
||||
}
|
||||
|
||||
testBuildFrameChecksum()
|
||||
testParseStatusFrame()
|
||||
testBindPayloadLength()
|
||||
testVendorCommand33()
|
||||
|
||||
console.log('BLE frame tests passed')
|
||||
@@ -1,7 +1,6 @@
|
||||
// BLE connection: scan, connect, disconnect, reconnect logic
|
||||
|
||||
var protocol = require('./protocol')
|
||||
var devConfig = require('../../config/env')
|
||||
var SERVICE = protocol.SERVICE
|
||||
var CHAR = protocol.CHAR
|
||||
|
||||
@@ -254,10 +253,6 @@ function startScan(callbacks) {
|
||||
var localName = (d.localName || '').toUpperCase()
|
||||
var matched = name.indexOf('HOX') !== -1 || localName.indexOf('HOX') !== -1 ||
|
||||
name.indexOf('LIGHTMASK') !== -1 || localName.indexOf('LIGHTMASK') !== -1
|
||||
// Dev board name - only match in dev mode
|
||||
if (!matched && devConfig.__DEV__) {
|
||||
matched = name.indexOf('SIMPLE PERIPHERAL') !== -1 || localName.indexOf('SIMPLE PERIPHERAL') !== -1
|
||||
}
|
||||
if (matched) {
|
||||
wx.stopBluetoothDevicesDiscovery({})
|
||||
if (callbacks.onFound) callbacks.onFound(d)
|
||||
|
||||
@@ -13,7 +13,6 @@ module.exports = {
|
||||
getDevices: function () { return http.get('/api/v1/device/list') },
|
||||
bindDevice: function (deviceId) { return http.post('/api/v1/device/bind', { device_id: deviceId }) },
|
||||
confirmBind: function (deviceId, token) { return http.post('/api/v1/device/bind/confirm', { device_id: deviceId, bind_token: token }) },
|
||||
mockBind: function (deviceId) { return http.post('/api/v1/device/mock-bind', { device_id: deviceId }) },
|
||||
unbindDevice: function (deviceId) { return http.post('/api/v1/device/unbind', deviceId ? { device_id: deviceId } : {}) },
|
||||
|
||||
// Subscription
|
||||
@@ -21,7 +20,6 @@ module.exports = {
|
||||
getSubscription: function () { return http.get('/api/v1/subscription') },
|
||||
activateTrial: function () { return http.post('/api/v1/subscription/trial') },
|
||||
purchase: function (plan) { return http.post('/api/v1/subscription/purchase', { plan: plan }) },
|
||||
mockPurchase: function (plan) { return http.post('/api/v1/subscription/mock-purchase', { plan: plan }) },
|
||||
|
||||
// Payment
|
||||
getPaymentOrder: function (orderId) { return http.get('/api/v1/payment/orders/' + orderId) },
|
||||
|
||||
@@ -1,145 +0,0 @@
|
||||
var MOCK_TOKEN = 'mock_token_dev_' + Date.now()
|
||||
|
||||
var MOCK_USER = {
|
||||
user_id: 10001,
|
||||
open_id: 'mock_open_id_12345',
|
||||
nickname: '测试用户',
|
||||
avatar_url: '',
|
||||
phone: '',
|
||||
gender: 0,
|
||||
created_at: '2025-01-01T00:00:00.000Z'
|
||||
}
|
||||
|
||||
var MOCK_DEVICE = {
|
||||
device_id: 'DEV001',
|
||||
name: '我的光子美容仪',
|
||||
firmware_version: '1.0.0',
|
||||
battery_level: 85,
|
||||
status: 'online',
|
||||
bind_time: '2025-01-01T00:00:00.000Z'
|
||||
}
|
||||
|
||||
var MOCK_SUBSCRIPTION = {
|
||||
plan_type: 'trial',
|
||||
status: 'active',
|
||||
start_time: '2025-01-01T00:00:00.000Z',
|
||||
end_time: '2025-02-01T00:00:00.000Z',
|
||||
remaining_days: 30
|
||||
}
|
||||
|
||||
var MOCK_TREATMENTS = [
|
||||
{
|
||||
session_id: 'S20250101001',
|
||||
device_id: 'DEV001',
|
||||
regions: [1, 2],
|
||||
wavelength: 630,
|
||||
brightness: 5,
|
||||
duration: 600000,
|
||||
mode: 1,
|
||||
avg_pd: 128,
|
||||
created_at: '2025-01-15T10:30:00.000Z'
|
||||
},
|
||||
{
|
||||
session_id: 'S20250102002',
|
||||
device_id: 'DEV001',
|
||||
regions: [1],
|
||||
wavelength: 850,
|
||||
brightness: 3,
|
||||
duration: 300000,
|
||||
mode: 2,
|
||||
avg_pd: 96,
|
||||
created_at: '2025-01-14T08:00:00.000Z'
|
||||
}
|
||||
]
|
||||
|
||||
var mockHandlers = {
|
||||
'POST /api/v1/auth/login': function () {
|
||||
return {
|
||||
code: 0,
|
||||
data: {
|
||||
token: MOCK_TOKEN,
|
||||
user_id: MOCK_USER.user_id,
|
||||
user_info: MOCK_USER
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
'POST /api/v1/auth/refresh': function () {
|
||||
return { code: 0, data: { token: MOCK_TOKEN } }
|
||||
},
|
||||
|
||||
'GET /api/v1/user/profile': function () {
|
||||
return { code: 0, data: MOCK_USER }
|
||||
},
|
||||
|
||||
'PUT /api/v1/user/profile': function (data) {
|
||||
Object.assign(MOCK_USER, data)
|
||||
return { code: 0, data: MOCK_USER }
|
||||
},
|
||||
|
||||
'POST /api/v1/device/bind': function (data) {
|
||||
var d = Object.assign({}, MOCK_DEVICE, { device_id: data.device_id || 'DEV001' })
|
||||
return {
|
||||
code: 0,
|
||||
data: {
|
||||
device: d,
|
||||
bind_token: 'mock_bind_token_' + Date.now(),
|
||||
subscription: MOCK_SUBSCRIPTION
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
'POST /api/v1/device/unbind': function () {
|
||||
return { code: 0, data: {} }
|
||||
},
|
||||
|
||||
'GET /api/v1/device/list': function () {
|
||||
return { code: 0, data: { devices: [MOCK_DEVICE], total: 1 } }
|
||||
},
|
||||
|
||||
'GET /api/v1/device/detail': function () {
|
||||
return { code: 0, data: MOCK_DEVICE }
|
||||
},
|
||||
|
||||
'GET /api/v1/subscription/status': function () {
|
||||
return { code: 0, data: MOCK_SUBSCRIPTION }
|
||||
},
|
||||
|
||||
'POST /api/v1/subscription/purchase': function (data) {
|
||||
return {
|
||||
code: 0,
|
||||
data: {
|
||||
order_id: 'ORD' + Date.now(),
|
||||
plan_type: data.plan_type,
|
||||
status: 'paid'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
'POST /api/v1/subscription/verify': function () {
|
||||
return { code: 0, data: { valid: true } }
|
||||
},
|
||||
|
||||
'POST /api/v1/treatment/sync': function (data) {
|
||||
return { code: 0, data: { session_id: data.session_id || 'S' + Date.now() } }
|
||||
},
|
||||
|
||||
'GET /api/v1/treatment/history': function () {
|
||||
return { code: 0, data: { records: MOCK_TREATMENTS, total: MOCK_TREATMENTS.length } }
|
||||
}
|
||||
}
|
||||
|
||||
function handle(method, path, data) {
|
||||
var key = method + ' ' + path
|
||||
var handler = mockHandlers[key]
|
||||
if (!handler) {
|
||||
return { code: -1, message: 'mock: 未定义的接口 ' + key }
|
||||
}
|
||||
return handler(data)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
handle: handle,
|
||||
MOCK_TOKEN: MOCK_TOKEN,
|
||||
enabled: false
|
||||
}
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
require('dotenv').config()
|
||||
|
||||
const config = {
|
||||
nodeEnv: process.env.NODE_ENV || 'development',
|
||||
nodeEnv: process.env.NODE_ENV || 'production',
|
||||
port: parseInt(process.env.PORT, 10) || 3000,
|
||||
region: process.env.TENCENT_REGION || 'ap-guangzhou',
|
||||
db: {
|
||||
|
||||
@@ -101,38 +101,6 @@ async function confirmBind(userId, deviceId, bindToken) {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock bind (dev/test only): directly bind with status=1, auto-trial
|
||||
* @param {number} userId
|
||||
* @param {string} deviceId
|
||||
* @returns {Promise<{success?: boolean, duplicated?: boolean, device_id?: string, invalid?: boolean}>}
|
||||
*/
|
||||
async function mockBind(userId, deviceId) {
|
||||
return transaction(async conn => {
|
||||
const [active] = await conn.execute(
|
||||
'SELECT device_id FROM bindings WHERE user_id = ? AND bind_status = 1 LIMIT 1',
|
||||
[userId]
|
||||
)
|
||||
if (active.length > 0) return { duplicated: true, device_id: active[0].device_id }
|
||||
const [devices] = await conn.execute(
|
||||
'SELECT * FROM devices WHERE device_id = ? AND status <> 4 LIMIT 1',
|
||||
[deviceId]
|
||||
)
|
||||
if (devices.length === 0) return { invalid: true }
|
||||
await conn.execute(
|
||||
'UPDATE bindings SET bind_status = 2 WHERE user_id = ? AND bind_status = 3',
|
||||
[userId]
|
||||
)
|
||||
await conn.execute(
|
||||
"INSERT INTO bindings (user_id, device_id, bind_token, bind_expires, bind_status, bind_time) VALUES (?, ?, 'mock', NOW(), 1, NOW())",
|
||||
[userId, deviceId]
|
||||
)
|
||||
const subscriptionDao = require('./subscription.dao')
|
||||
await subscriptionDao.grantTrialIfEligible(userId, conn)
|
||||
return { success: true }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Unbind device(s) for a user
|
||||
* @param {number} userId
|
||||
@@ -197,7 +165,6 @@ module.exports = {
|
||||
createPending,
|
||||
closeExpiredPending,
|
||||
confirmBind,
|
||||
mockBind,
|
||||
unbindByUser,
|
||||
getHistoryByDevice,
|
||||
findUserDeviceBinding,
|
||||
|
||||
@@ -51,11 +51,7 @@ async function getAccessToken() {
|
||||
}
|
||||
|
||||
async function code2Session(code) {
|
||||
if (config.nodeEnv === 'development' && (!code || code === 'local' || String(code).indexOf('dev_') === 0)) {
|
||||
return { openid: 'dev_openid_' + String(code || 'local').slice(-8) }
|
||||
}
|
||||
if (!config.wechat.appid || !config.wechat.secret) {
|
||||
if (config.nodeEnv === 'development') return { openid: 'dev_openid_' + String(code || 'local').slice(-8) }
|
||||
throw new Error('WECHAT_APPID or WECHAT_SECRET is not configured')
|
||||
}
|
||||
const url = 'https://api.weixin.qq.com/sns/jscode2session?appid=' + encodeURIComponent(config.wechat.appid) + '&secret=' + encodeURIComponent(config.wechat.secret) + '&js_code=' + encodeURIComponent(code) + '&grant_type=authorization_code'
|
||||
|
||||
@@ -56,23 +56,6 @@ router.post('/device/bind/confirm', requireUser, wrap(async (req, res) => {
|
||||
}))
|
||||
}))
|
||||
|
||||
router.post('/device/mock-bind', requireUser, wrap(async (req, res) => {
|
||||
const config = require('../config')
|
||||
if (config.nodeEnv === 'production') return res.json(fail(2001, 'not available in production'))
|
||||
const deviceId = String(req.body.device_id || '').trim()
|
||||
if (!deviceId) return res.json(fail(2001, 'device_id required'))
|
||||
|
||||
const active = await bindingDao.findActiveByUser(req.user.user_id)
|
||||
if (active) return res.json(fail(2001, '已绑定设备', { device_id: active.device_id }))
|
||||
|
||||
const device = await bindingDao.findDeviceExists(deviceId)
|
||||
if (!device) return res.json(fail(1005, 'DEVICE_NOT_FOUND'))
|
||||
|
||||
await bindingDao.mockBind(req.user.user_id, deviceId)
|
||||
await logDao.write({ user_id: req.user.user_id, action: 'device_bind_confirm', detail: '模拟绑定设备: ' + deviceId, ip: req.ip })
|
||||
res.json(ok({ message: 'success', device_id: deviceId }))
|
||||
}))
|
||||
|
||||
router.post('/device/unbind', requireUser, wrap(async (req, res) => {
|
||||
const deviceId = req.body.device_id || null
|
||||
await bindingDao.unbindByUser(req.user.user_id, deviceId)
|
||||
|
||||
@@ -46,11 +46,8 @@ router.post('/subscription/purchase', requireUser, wrap(async (req, res) => {
|
||||
if (!PLANS[plan] || plan === 'trial') return res.json(fail(2001, 'invalid plan'))
|
||||
|
||||
const wxpay = require('../lib/wxpay')
|
||||
const config = require('../config')
|
||||
if (!wxpay.isConfigured()) {
|
||||
if (config.nodeEnv === 'production') return res.json(fail(2001, 'payment not configured'))
|
||||
const orderId = 'ORD' + Date.now()
|
||||
return res.json(ok({ order_id: orderId, payment_params: null, mock: true, plan, amount: PLANS[plan].amount }))
|
||||
return res.json(fail(2001, 'payment not configured'))
|
||||
}
|
||||
|
||||
const paymentOrderDao = require('../dao/payment-order.dao')
|
||||
@@ -82,19 +79,6 @@ router.post('/subscription/purchase', requireUser, wrap(async (req, res) => {
|
||||
res.json(ok({ order_id: orderId, payment_params: paymentParams, plan, amount: priceYuan }))
|
||||
}))
|
||||
|
||||
router.post('/subscription/mock-purchase', requireUser, wrap(async (req, res) => {
|
||||
const config = require('../config')
|
||||
if (config.nodeEnv === 'production') return res.json(fail(2001, 'not available in production'))
|
||||
const plan = req.body.plan || req.body.plan_type
|
||||
if (!PLANS[plan] || plan === 'trial') return res.json(fail(2001, 'invalid plan'))
|
||||
const p = PLANS[plan]
|
||||
const orderId = 'MOCK' + Date.now()
|
||||
await subscriptionDao.purchase(req.user.user_id, plan, p.amount, orderId, p.days)
|
||||
await logDao.write({ user_id: req.user.user_id, action: 'subscription_mock_purchase', detail: '模拟购买: ' + plan, ip: req.ip })
|
||||
const sub = await subscriptionDao.findActive(req.user.user_id)
|
||||
res.json(ok({ status: 'active', plan, remaining_days: sub ? sub.remaining_days : p.days }))
|
||||
}))
|
||||
|
||||
router.post('/subscription/trial', requireUser, wrap(async (req, res) => {
|
||||
const usedTrial = await subscriptionDao.findTrial(req.user.user_id)
|
||||
if (usedTrial) return res.json(fail(2001, '已使用过试用'))
|
||||
|
||||
在新工单中引用
屏蔽一个用户