diff --git a/uniCloud-tcb/cloudfunctions/jjxReviewStream/config.json b/uniCloud-tcb/cloudfunctions/jjxReviewStream/config.json
new file mode 100644
index 0000000..26bcc55
--- /dev/null
+++ b/uniCloud-tcb/cloudfunctions/jjxReviewStream/config.json
@@ -0,0 +1,12 @@
+{
+ "triggers": [
+ {
+ "name": "http",
+ "type": "http",
+ "config": {
+ "path": "/review-stream",
+ "method": ["POST"]
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/uniCloud-tcb/cloudfunctions/jjxReviewStream/index.js b/uniCloud-tcb/cloudfunctions/jjxReviewStream/index.js
new file mode 100644
index 0000000..6abcd12
--- /dev/null
+++ b/uniCloud-tcb/cloudfunctions/jjxReviewStream/index.js
@@ -0,0 +1,106 @@
+'use strict';
+
+exports.main = async (event, context) => {
+ // uniCloud HTTP 触发器:event 本身就是请求信息
+ // event.httpMethod = 'GET'/'POST'/'OPTIONS'
+ // event.body = 请求体
+ // event.headers = 请求头
+ // context.callbackWaitsForEmptyEventLoop = true 保持连接
+
+ const corsHeaders = {
+ 'Access-Control-Allow-Origin': '*',
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
+ 'Access-Control-Allow-Headers': 'Content-Type, Authorization',
+ 'Access-Control-Max-Age': '86400'
+ };
+
+ // 处理 CORS 预检
+ if (event.httpMethod === 'OPTIONS') {
+ return {
+ statusCode: 204,
+ headers: corsHeaders,
+ body: ''
+ };
+ }
+
+ // 设置等待空事件循环(保持 SSE 连接)
+ context.callbackWaitsForEmptyEventLoop = false;
+
+ // 解析请求体
+ let body = {};
+ try {
+ const raw = event.body || '';
+ if (event.isBase64Encoded) {
+ body = JSON.parse(Buffer.from(raw, 'base64').toString());
+ } else {
+ try { body = JSON.parse(raw); } catch (e) {
+ const params = new URLSearchParams(raw);
+ for (const [k, v] of params) body[k] = v;
+ }
+ }
+ } catch (e) {}
+
+ const { file_id, user_id, file_name, file_size, record_id } = body;
+
+ if (!file_id) {
+ return {
+ statusCode: 400,
+ headers: { ...corsHeaders, 'Content-Type': 'application/json' },
+ body: JSON.stringify({ error: '缺少 file_id' })
+ };
+ }
+
+ const COZE_TOKEN = 'sat_OiXlI4Da5Q99fxmul6pNfExmA6hR898UbxBDwrvjZ638APxRSlFnXbz6zyFedKNG';
+ const BOT_ID = '7689821072762011698';
+
+ const objectString = JSON.stringify([
+ { type: 'file', file_id: file_id },
+ { type: 'text', text: '请审查这份合同' }
+ ]);
+
+ // 调用扣子(流式)
+ try {
+ const chatRes = await uniCloud.httpclient.request('https://api.coze.cn/v3/chat', {
+ method: 'POST',
+ dataType: 'text',
+ contentType: 'json',
+ timeout: 300000,
+ headers: { 'Authorization': 'Bearer ' + COZE_TOKEN },
+ data: {
+ bot_id: BOT_ID,
+ user_id: user_id || 'anonymous',
+ stream: true,
+ auto_save_history: true,
+ additional_messages: [{
+ role: 'user',
+ content: objectString,
+ content_type: 'object_string'
+ }]
+ }
+ });
+
+ // 返回 SSE 格式
+ const text = chatRes.data || '';
+ const sseBody = text.split('\n').map(line => {
+ if (line.startsWith('data:')) return line + '\n\n';
+ return '';
+ }).join('') + 'data: [DONE]\n\n';
+
+ return {
+ statusCode: 200,
+ headers: {
+ ...corsHeaders,
+ 'Content-Type': 'text/event-stream',
+ 'Cache-Control': 'no-cache',
+ 'Connection': 'keep-alive'
+ },
+ body: sseBody
+ };
+ } catch (e) {
+ return {
+ statusCode: 500,
+ headers: { ...corsHeaders, 'Content-Type': 'application/json' },
+ body: JSON.stringify({ error: e.message })
+ };
+ }
+};
\ No newline at end of file
diff --git a/uniCloud-tcb/cloudfunctions/jjxReviewStream/package.json b/uniCloud-tcb/cloudfunctions/jjxReviewStream/package.json
new file mode 100644
index 0000000..85ab71b
--- /dev/null
+++ b/uniCloud-tcb/cloudfunctions/jjxReviewStream/package.json
@@ -0,0 +1,6 @@
+{
+ "name": "jjxReviewStream",
+ "version": "1.0.0",
+ "description": "合同审查流式输出云函数",
+ "main": "index.js"
+}
\ No newline at end of file
diff --git a/uniCloud-tcb/cloudfunctions/jjxReviewTimer/index.js b/uniCloud-tcb/cloudfunctions/jjxReviewTimer/index.js
new file mode 100644
index 0000000..1034e80
--- /dev/null
+++ b/uniCloud-tcb/cloudfunctions/jjxReviewTimer/index.js
@@ -0,0 +1,239 @@
+'use strict';
+
+// 定时任务:每分钟检查 reviewing 状态的记录,触发审查
+const db = uniCloud.database();
+const _ = db.command;
+
+// 导入审查执行函数(从 router 的 service 目录)
+// 注意:定时任务是独立云函数,不能直接 require router 的 service
+// 所以把审查逻辑直接写在这里
+
+const COZE_TOKEN = 'sat_OiXlI4Da5Q99fxmul6pNfExmA6hR898UbxBDwrvjZ638APxRSlFnXbz6zyFedKNG';
+const BOT_ID = '7689821072762011698';
+
+async function doReview(record) {
+ const { _id: record_id, file_id, user_id, file_name, file_size } = record;
+
+ const objectString = JSON.stringify([
+ { type: 'file', file_id: file_id },
+ { type: 'text', text: '请审查这份合同' }
+ ]);
+
+ try {
+ // 1. 非流式发起对话(立即返回 chat_id)
+ const chatRes = await uniCloud.httpclient.request('https://api.coze.cn/v3/chat', {
+ method: 'POST',
+ dataType: 'json',
+ contentType: 'json',
+ timeout: 30000,
+ headers: { 'Authorization': 'Bearer ' + COZE_TOKEN },
+ data: {
+ bot_id: BOT_ID,
+ user_id: user_id || 'anonymous',
+ stream: false,
+ auto_save_history: true,
+ additional_messages: [{
+ role: 'user',
+ content: objectString,
+ content_type: 'object_string'
+ }]
+ }
+ });
+
+ const chatData = chatRes.data && chatRes.data.data;
+ const chatId = chatData && chatData.id;
+ const conversationId = chatData && chatData.conversation_id;
+ console.log('[审查] chat_id:', chatId, 'conversation_id:', conversationId);
+
+ if (!chatId) {
+ throw new Error('未获取到 chat_id: ' + JSON.stringify(chatRes.data));
+ }
+
+ // 保存 chat_id 和 conversation_id 到数据库
+ await db.collection('jjx-review-records').doc(record_id).update({
+ chat_id: chatId,
+ conversation_id: conversationId || '',
+ update_time: new Date()
+ });
+ console.log('[审查] 已保存 chat_id:', chatId);
+
+ // 2. 轮询对话状态(每5秒查一次,最多5分钟)
+ let answer = '';
+ let tokenCount = 0;
+ for (let i = 0; i < 60; i++) {
+ await new Promise(r => setTimeout(r, 5000));
+
+ const statusRes = await uniCloud.httpclient.request(
+ 'https://api.coze.cn/v3/chat/retrieve?chat_id=' + chatId + '&conversation_id=' + conversationId,
+ { method: 'GET', dataType: 'json', headers: { 'Authorization': 'Bearer ' + COZE_TOKEN }, timeout: 15000 }
+ );
+
+ const status = statusRes.data && statusRes.data.data && statusRes.data.data.status;
+ console.log('[审查] 第', i + 1, '次轮询, 状态:', status);
+
+ if (status === 'completed') {
+ // 保存 token 消耗信息
+ const usage = statusRes.data && statusRes.data.data && statusRes.data.data.usage;
+ tokenCount = (usage && usage.token_count) || 0;
+ console.log('[审查] Token 消耗:', tokenCount);
+
+ // 3. 获取消息列表
+ const msgRes = await uniCloud.httpclient.request(
+ 'https://api.coze.cn/v3/chat/message/list?chat_id=' + chatId + '&conversation_id=' + conversationId,
+ { method: 'GET', dataType: 'json', headers: { 'Authorization': 'Bearer ' + COZE_TOKEN }, timeout: 15000 }
+ );
+
+ const messages = msgRes.data && msgRes.data.data;
+ if (Array.isArray(messages)) {
+ for (const msg of messages) {
+ if (msg.type === 'answer' && msg.role === 'assistant' && msg.content) {
+ answer = msg.content; // answer 类型的完整内容
+ }
+ if (msg.type === 'tool_response' && msg.content) {
+ try {
+ const toolData = JSON.parse(msg.content);
+ if (toolData.url) answer += '\n\n📎 报告下载:' + toolData.url;
+ } catch (e) {}
+ }
+ }
+ }
+ break;
+ }
+
+ if (status === 'failed') {
+ throw new Error('对话失败');
+ }
+ }
+
+ if (!answer) answer = '审查未能获取结果,请稍后重试';
+ console.log('[审查] 结果长度:', answer.length);
+
+ // 计算消耗积分
+ // 积分 = token消耗 / 100(最低1积分)
+ const pointsUsed = Math.max(1, Math.round(tokenCount / 100));
+ console.log('[积分] token_count:', tokenCount, '→ 扣除积分:', pointsUsed);
+
+ // 扣积分
+ const userPoints = await db.collection('jjx-user-points').doc(user_id).get();
+ if (userPoints.data.length && userPoints.data[0].balance >= pointsUsed) {
+ await db.collection('jjx-user-points').doc(user_id).update({
+ balance: _.inc(-pointsUsed),
+ total_spent: _.inc(pointsUsed),
+ update_time: new Date()
+ });
+ const updatedPoints = await db.collection('jjx-user-points').doc(user_id).get();
+ await db.collection('jjx-points-log').add({
+ user_id: user_id,
+ type: 'spend',
+ amount: -pointsUsed,
+ balance_after: updatedPoints.data[0] ? updatedPoints.data[0].balance : 0,
+ reason: '合同审查 — ' + (file_name || '') + '(' + tokenCount + ' token)',
+ biz_type: 'contract_review',
+ biz_id: record_id,
+ _add_time: new Date()
+ });
+ }
+
+ // 保存 HTML 报告到云存储
+ let htmlUrl = '';
+ try {
+ let htmlContent = answer;
+ const htmlMatch = answer.match(/```html\s*([\s\S]*?)```/);
+ if (htmlMatch && htmlMatch[1]) htmlContent = htmlMatch[1].trim();
+
+ if (htmlContent.includes(' {
+ // 先把卡在 processing 超过10分钟的记录重置为 reviewing(超时保护)
+ const tenMinutesAgo = new Date(Date.now() - 10 * 60 * 1000);
+ const stuckRecords = await db.collection('jjx-review-records')
+ .where({ status: 'processing', update_time: _.lt(tenMinutesAgo) })
+ .get();
+ for (const stuck of stuckRecords.data) {
+ await db.collection('jjx-review-records').doc(stuck._id).update({
+ status: 'reviewing',
+ update_time: new Date()
+ });
+ }
+
+ // 查询所有 reviewing 状态的记录(超过1分钟的,避免重复触发)
+ const oneMinuteAgo = new Date(Date.now() - 60 * 1000);
+
+ const pendingRecords = await db.collection('jjx-review-records')
+ .where({
+ status: 'reviewing',
+ review_started_at: _.lt(oneMinuteAgo)
+ })
+ .limit(5) // 每次最多处理5条
+ .get();
+
+ if (!pendingRecords.data.length) {
+ return { code: 0, msg: '无待处理记录' };
+ }
+
+ console.log('发现待处理审查记录:', pendingRecords.data.length, '条');
+
+ // 逐条处理(串行,避免并发问题)
+ const results = [];
+ for (const record of pendingRecords.data) {
+ // 先标记为 processing,避免重复触发
+ await db.collection('jjx-review-records').doc(record._id).update({
+ status: 'processing',
+ update_time: new Date()
+ });
+
+ try {
+ const result = await doReview(record);
+ results.push({ record_id: record._id, ...result });
+ } catch (e) {
+ // 审查失败,重置为 reviewing 以便下次重试
+ await db.collection('jjx-review-records').doc(record._id).update({
+ status: 'reviewing',
+ update_time: new Date()
+ });
+ console.error('审查异常,已重置状态:', record._id, e.message);
+ results.push({ record_id: record._id, code: -1, msg: e.message });
+ }
+ }
+
+ return { code: 0, msg: '处理完成', data: results };
+};
\ No newline at end of file
diff --git a/uniCloud-tcb/cloudfunctions/jjxReviewTimer/jjxReviewTimer.param.json b/uniCloud-tcb/cloudfunctions/jjxReviewTimer/jjxReviewTimer.param.json
new file mode 100644
index 0000000..0f56b5c
--- /dev/null
+++ b/uniCloud-tcb/cloudfunctions/jjxReviewTimer/jjxReviewTimer.param.json
@@ -0,0 +1,4 @@
+// 本文件中的json内容将在云函数【运行】时作为参数传给云函数。
+// 配置教程参考:https://uniapp.dcloud.net.cn/uniCloud/rundebug.html#runparam
+{
+}
\ No newline at end of file
diff --git a/uniCloud-tcb/cloudfunctions/jjxReviewTimer/package.json b/uniCloud-tcb/cloudfunctions/jjxReviewTimer/package.json
new file mode 100644
index 0000000..3ba759e
--- /dev/null
+++ b/uniCloud-tcb/cloudfunctions/jjxReviewTimer/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "jjxReviewTimer",
+ "version": "1.0.0",
+ "description": "合同审查定时任务",
+ "main": "index.js",
+ "cloudfunction-config": {
+ "memorySize": 256,
+ "timeout": 600,
+ "triggers": [
+ {
+ "name": "myTrigger",
+ "type": "timer",
+ "config": "0 */1 * * * * *"
+ }
+ ],
+ "path": "",
+ "runtime": "Nodejs16"
+ }
+}
\ No newline at end of file
diff --git a/uniCloud-tcb/cloudfunctions/router/package-lock.json b/uniCloud-tcb/cloudfunctions/router/package-lock.json
index 9f76028..3efc7fd 100644
--- a/uniCloud-tcb/cloudfunctions/router/package-lock.json
+++ b/uniCloud-tcb/cloudfunctions/router/package-lock.json
@@ -35,63 +35,64 @@
},
"node_modules/charenc": {
"version": "0.0.2",
- "license": "BSD-3-Clause",
+ "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
+ "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==",
"engines": {
"node": "*"
}
},
"node_modules/crypt": {
"version": "0.0.2",
- "license": "BSD-3-Clause",
+ "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
+ "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==",
"engines": {
"node": "*"
}
},
"node_modules/crypto-js": {
"version": "4.2.0",
- "license": "MIT"
+ "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
+ "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==",
+ "deprecated": "Active development of CryptoJS has been discontinued. This library is no longer maintained."
},
"node_modules/dayjs": {
- "version": "1.11.10",
- "license": "MIT"
- },
- "node_modules/debug": {
- "version": "3.2.7",
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.1"
- }
+ "version": "1.11.23",
+ "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.23.tgz",
+ "integrity": "sha512-QDTCU0M0MxR3hQfnlDJfwekQiaanm1ubOD231u73WBckQ/fsamwRLiE2GBz6D3a/xF1NgfiDLJjXBa1hYOYTtQ=="
},
"node_modules/is-buffer": {
"version": "1.1.6",
- "license": "MIT"
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
+ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
},
"node_modules/md5": {
"version": "2.3.0",
- "license": "BSD-3-Clause",
+ "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz",
+ "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==",
"dependencies": {
"charenc": "0.0.2",
"crypt": "0.0.2",
"is-buffer": "~1.1.6"
}
},
- "node_modules/ms": {
- "version": "2.1.3",
- "license": "MIT"
- },
"node_modules/node-ensure": {
"version": "0.0.0",
- "license": "MIT"
+ "resolved": "https://registry.npmjs.org/node-ensure/-/node-ensure-0.0.0.tgz",
+ "integrity": "sha512-DRI60hzo2oKN1ma0ckc6nQWlHU69RH6xN0sjQTjMpChPfTYvKZdcQFfdYK2RWbJcKyUizSIy/l8OTGxMAM1QDw=="
},
"node_modules/pdf-parse": {
- "version": "1.1.1",
- "license": "MIT",
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/pdf-parse/-/pdf-parse-1.1.4.tgz",
+ "integrity": "sha512-XRIRcLgk6ZnUbsHsYXExMw+krrPE81hJ6FQPLdBNhhBefqIQKXu/WeTgNBGSwPrfU0v+UCEwn7AoAUOsVKHFvQ==",
"dependencies": {
- "debug": "^3.1.0",
"node-ensure": "^0.0.0"
},
"engines": {
"node": ">=6.8.1"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/mehmet-kozan"
}
},
"node_modules/uni-config-center": {
diff --git a/uniCloud-tcb/cloudfunctions/router/package.json b/uniCloud-tcb/cloudfunctions/router/package.json
index 55135b8..5f2cfc7 100644
--- a/uniCloud-tcb/cloudfunctions/router/package.json
+++ b/uniCloud-tcb/cloudfunctions/router/package.json
@@ -23,7 +23,7 @@
"concurrency": 1,
"memorySize": 512,
"path": "/http/router",
- "timeout": 60,
+ "timeout": 600,
"triggers": [],
"keepRunningAfterReturn": false
},
diff --git a/uniCloud-tcb/cloudfunctions/router/service/admin/finance/balance/sys/getActionLog.js b/uniCloud-tcb/cloudfunctions/router/service/admin/finance/balance/sys/getActionLog.js
index f40266a..4e9c3e9 100644
--- a/uniCloud-tcb/cloudfunctions/router/service/admin/finance/balance/sys/getActionLog.js
+++ b/uniCloud-tcb/cloudfunctions/router/service/admin/finance/balance/sys/getActionLog.js
@@ -8,17 +8,15 @@ module.exports = {
* @param {String} accNo 账号
* @param {String} reqDate 请求日期,格式:yyyyMMdd
* @param {String} reqJournalNo 请求流水号
- * @param {String} logId 可选,流水ID,传入后会回写状态
*/
main: async (event) => {
let { data = {}, util } = event;
let { vk } = util;
- let { accNo, reqDate, reqJournalNo, logId } = data;
+ let { accNo, reqDate, reqJournalNo } = data;
accNo = String(accNo || '').trim();
reqDate = String(reqDate || '').trim();
reqJournalNo = String(reqJournalNo || '').trim();
- logId = String(logId || '').trim();
if (!accNo) {
return {
@@ -48,83 +46,43 @@ module.exports = {
};
}
- let res = await balanceUtil.requestActionLog({
- vk,
- accNo,
- reqDate,
- reqJournalNo
+ let res = await vk.request({
+ url: 'http://jjx-union-pay.gxwebsoft.com/api/union-pay-jjx/getActionLog',
+ method: 'POST',
+ dataType: 'json',
+ header: {
+ 'content-type': 'application/json; charset=utf-8'
+ },
+ data: {
+ accNo,
+ reqDate,
+ reqJournalNo
+ },
+ timeout: 60000
});
if (res && typeof res === 'object' && typeof res.code === 'undefined') {
res.code = 0;
}
- const status = balanceUtil.getActionStatus(res);
- const statusText = balanceUtil.getActionStatusTextByStatus(status);
-
- if (status !== null) {
- let logInfo = null;
- if (logId) {
- logInfo = await vk.baseDao.findByWhereJson({
- dbName: 'jjx-balance-log',
- whereJson: {
- _id: logId
- }
- });
- }
- if (!logInfo) {
- logInfo = await vk.baseDao.findByWhereJson({
- dbName: 'jjx-balance-log',
- whereJson: {
- srcReqId: reqJournalNo,
- merchantId: accNo
- }
- });
- }
-
- if (logInfo && logInfo._id) {
- logId = logInfo._id;
- const prevStatus = Number(logInfo.status);
- const nextStatus = Number(status);
- const updateData = {
- status: nextStatus
- };
-
- // 新提现单(balanceDeducted === false):成功时再扣减余额缓存
- // 旧提现单无该字段:创建时已扣减,这里只更新状态
- const needDeductOnSuccess = logInfo.balanceDeducted === false
- && prevStatus === 1
- && nextStatus === 2;
-
- if (needDeductOnSuccess) {
- const amount = balanceUtil.normalizeAmount(logInfo.amount);
- await balanceUtil.applyMerchantBalanceDelta({
- vk,
- merchantId: accNo,
- delta: -amount,
- lastLogId: logId
- });
- updateData.balanceDeducted = true;
- }
-
- await vk.baseDao.updateById({
- dbName: 'jjx-balance-log',
- id: logId,
- dataJson: updateData
- });
- }
- }
-
const responseObjects = balanceUtil.getRemoteResponseObjects(res);
+ const list = balanceUtil.pickFirstDefined([
+ Array.isArray(res.list) ? res.list : undefined,
+ Array.isArray(res.rows) ? res.rows : undefined,
+ Array.isArray(res.data) ? res.data : undefined,
+ Array.isArray(res.data && res.data.list) ? res.data.list : undefined,
+ Array.isArray(res.data && res.data.rows) ? res.data.rows : undefined,
+ Array.isArray(res.info && res.info.list) ? res.info.list : undefined,
+ Array.isArray(res.info && res.info.rows) ? res.info.rows : undefined,
+ Array.isArray(res.content && res.content.list) ? res.content.list : undefined,
+ Array.isArray(res.content && res.content.rows) ? res.content.rows : undefined
+ ]) || [];
+
if (responseObjects.data && typeof responseObjects.data === 'object' && !Array.isArray(responseObjects.data)) {
- responseObjects.data.status = status;
- responseObjects.data.statusText = statusText;
- responseObjects.data.logId = logId;
+ responseObjects.data.list = list;
} else if (res && typeof res === 'object') {
res.data = {
- status,
- statusText,
- logId
+ list
};
}
diff --git a/uniCloud-tcb/cloudfunctions/router/service/admin/finance/balance/sys/getSummary.js b/uniCloud-tcb/cloudfunctions/router/service/admin/finance/balance/sys/getSummary.js
index e5ec531..3bc896e 100644
--- a/uniCloud-tcb/cloudfunctions/router/service/admin/finance/balance/sys/getSummary.js
+++ b/uniCloud-tcb/cloudfunctions/router/service/admin/finance/balance/sys/getSummary.js
@@ -1,5 +1,5 @@
'use strict';
-const { BALANCE_DOC_ID, getBalanceInfoList, getAvailableWithdrawBalance, normalizeAmount } = require('./util.js');
+const { BALANCE_DOC_ID, getBalanceInfoList } = require('./util.js');
module.exports = {
/**
@@ -25,24 +25,6 @@ module.exports = {
}
const balanceInfoList = getBalanceInfoList(info.balanceInfo || {});
- const enrichedList = [];
- for (let i = 0; i < balanceInfoList.length; i++) {
- const item = balanceInfoList[i];
- const {
- pendingAmount,
- availableBalance
- } = await getAvailableWithdrawBalance({
- vk,
- merchantId: item.accNo,
- merchantBalance: item.balance
- });
- enrichedList.push({
- ...item,
- balance: normalizeAmount(item.balance),
- pendingAmount,
- availableBalance
- });
- }
return {
code: 0,
@@ -50,7 +32,7 @@ module.exports = {
data: {
balance: Number(Number(info.balance || 0).toFixed(2)),
updateTime: info.updateTime || info._update_time_str || info._add_time_str || '',
- balanceInfoList: enrichedList
+ balanceInfoList
}
};
}
diff --git a/uniCloud-tcb/cloudfunctions/router/service/admin/finance/balance/sys/util.js b/uniCloud-tcb/cloudfunctions/router/service/admin/finance/balance/sys/util.js
index c69236d..5fef182 100644
--- a/uniCloud-tcb/cloudfunctions/router/service/admin/finance/balance/sys/util.js
+++ b/uniCloud-tcb/cloudfunctions/router/service/admin/finance/balance/sys/util.js
@@ -213,64 +213,6 @@ function getActionStatusTextByStatus(status) {
return statusMap[Number(status)] || '';
}
-async function applyMerchantBalanceDelta({
- vk,
- transaction,
- merchantId,
- delta,
- lastLogId = '',
- updateTime = dayjs().format('YYYY-MM-DD HH:mm:ss')
-}) {
- const accNo = String(merchantId || '').trim();
- if (!accNo) {
- throw new Error('提现账号不能为空');
- }
- const changeAmount = normalizeAmount(delta);
- const balanceRecord = await ensureBalanceRecord({ transaction, vk });
- const balanceInfoMap = buildBalanceInfoMap(balanceRecord.balanceInfo || {});
- const currentMerchantInfo = balanceInfoMap[accNo] || {
- accNo,
- merchantName: getMerchantLabel(accNo),
- balance: 0,
- updateTime: ''
- };
- const currentMerchantBalance = normalizeAmount(currentMerchantInfo.balance || 0);
- const nextMerchantBalance = normalizeAmount(currentMerchantBalance + changeAmount);
- if (nextMerchantBalance < 0) {
- throw new Error('余额不足');
- }
- const nextBalanceInfoMap = {
- ...balanceInfoMap,
- [accNo]: {
- accNo,
- merchantName: currentMerchantInfo.merchantName || getMerchantLabel(accNo),
- balance: nextMerchantBalance,
- updateTime
- }
- };
- const nextTotalBalance = normalizeAmount(Number(balanceRecord.balance || 0) + changeAmount);
- const dataJson = {
- balance: nextTotalBalance,
- balanceInfo: nextBalanceInfoMap,
- updateTime
- };
- if (lastLogId) {
- dataJson.lastLogId = lastLogId;
- }
- await vk.baseDao.updateById({
- db: transaction,
- dbName: 'jjx-balance',
- id: balanceRecord._id,
- dataJson
- });
- return {
- balance: nextTotalBalance,
- merchantBalance: nextMerchantBalance,
- updateTime,
- balanceInfoMap: nextBalanceInfoMap
- };
-}
-
async function syncMerchantBalance({
vk,
accNo,
@@ -381,49 +323,6 @@ async function checkBalanceAvailable({ transaction, vk, changeAmount, excludeLog
};
}
-/**
- * 统计指定商户处理中的提现金额(status=1)
- */
-async function getPendingWithdrawAmount({ transaction, vk, merchantId }) {
- const accNo = String(merchantId || '').trim();
- if (!accNo) {
- return 0;
- }
- const res = await vk.baseDao.select({
- db: transaction,
- dbName: 'jjx-balance-log',
- whereJson: {
- merchantId: accNo,
- type: 'expense',
- source: 'withdraw',
- status: 1
- },
- pageIndex: 1,
- pageSize: 1000,
- getCount: false
- });
- const rows = (res && res.rows) || [];
- return normalizeAmount(rows.reduce((sum, item) => sum + Number(item.amount || 0), 0));
-}
-
-/**
- * 可提现余额 = 当前余额 - 处理中提现金额
- */
-async function getAvailableWithdrawBalance({ transaction, vk, merchantId, merchantBalance }) {
- const pendingAmount = await getPendingWithdrawAmount({
- transaction,
- vk,
- merchantId
- });
- const balance = normalizeAmount(merchantBalance);
- const availableBalance = normalizeAmount(Math.max(0, balance - pendingAmount));
- return {
- balance,
- pendingAmount,
- availableBalance
- };
-}
-
async function updateBalanceWithDelta({ transaction, vk, delta, lastLogId = '' }) {
const balanceInfo = await ensureBalanceRecord({ transaction, vk });
const nextBalance = Number((Number(balanceInfo.balance || 0) + Number(delta || 0)).toFixed(2));
@@ -473,13 +372,10 @@ module.exports = {
parseOrderSet,
getActionStatus,
getActionStatusTextByStatus,
- applyMerchantBalanceDelta,
syncMerchantBalance,
ensureBalanceRecord,
recalculateBalance,
checkBalanceAvailable,
- getPendingWithdrawAmount,
- getAvailableWithdrawBalance,
updateBalanceWithDelta,
rollbackTransaction
};
diff --git a/uniCloud-tcb/cloudfunctions/router/service/admin/finance/balance/sys/withdraw.js b/uniCloud-tcb/cloudfunctions/router/service/admin/finance/balance/sys/withdraw.js
index 3cadddc..8163780 100644
--- a/uniCloud-tcb/cloudfunctions/router/service/admin/finance/balance/sys/withdraw.js
+++ b/uniCloud-tcb/cloudfunctions/router/service/admin/finance/balance/sys/withdraw.js
@@ -1,4 +1,5 @@
'use strict';
+const dayjs = require('dayjs');
const balanceUtil = require('./util.js');
const merchantMap = balanceUtil.MERCHANT_MAP;
@@ -33,28 +34,11 @@ module.exports = {
updateTime: ''
};
const merchantBalance = balanceUtil.normalizeAmount(merchantBalanceInfo.balance || 0);
- const {
- pendingAmount,
- availableBalance
- } = await balanceUtil.getAvailableWithdrawBalance({
- transaction,
- vk,
- merchantId,
- merchantBalance
- });
- if (availableBalance <= 0) {
- throw new Error(
- pendingAmount > 0
- ? `可提现余额不足(处理中提现¥${pendingAmount.toFixed(2)}已占用)`
- : '当前企业余额小于等于0,无法提现'
- );
+ if (merchantBalance <= 0) {
+ throw new Error('当前企业余额小于等于0,无法提现');
}
- if (amount > availableBalance) {
- throw new Error(
- pendingAmount > 0
- ? `提现金额不能大于可提现余额¥${availableBalance.toFixed(2)}(已排除处理中提现¥${pendingAmount.toFixed(2)})`
- : '提现金额不能大于当前企业余额'
- );
+ if (amount > merchantBalance) {
+ throw new Error('提现金额不能大于当前企业余额');
}
const description = String(data.description || '').trim() || `余额提现-${merchantMap[merchantId]}`;
@@ -96,28 +80,52 @@ module.exports = {
merchantId,
merchantName: merchantMap[merchantId],
srcReqId: requestRes.srcReqId,
- status: 1,
- // 新逻辑:处理中不扣余额,成功后再扣;用于兼容旧数据(旧单默认已扣)
- balanceDeducted: false
+ status: 1
};
+ const changeAmount = balanceUtil.buildBalanceChange(logData);
- await vk.baseDao.add({
+ await balanceUtil.checkBalanceAvailable({
+ transaction,
+ vk,
+ changeAmount
+ });
+
+ const addRes = await vk.baseDao.add({
db: transaction,
dbName: 'jjx-balance-log',
dataJson: logData
});
- // 处理中提现不立刻扣减余额缓存,避免与远端余额同步互相覆盖;
- // 可提现额度通过「余额 - 处理中提现」计算,防止重复提现超总额。
+ const updateTime = dayjs().format('YYYY-MM-DD HH:mm:ss');
+ const nextMerchantBalance = Number((merchantBalance + changeAmount).toFixed(2));
+ const nextBalance = Number((Number(balanceRecord.balance || 0) + changeAmount).toFixed(2));
+ await vk.baseDao.updateById({
+ db: transaction,
+ dbName: 'jjx-balance',
+ id: balanceRecord._id,
+ dataJson: {
+ balance: nextBalance,
+ lastLogId: addRes.id,
+ updateTime,
+ balanceInfo: {
+ ...balanceInfoMap,
+ [merchantId]: {
+ accNo: merchantId,
+ merchantName: merchantMap[merchantId],
+ balance: nextMerchantBalance,
+ updateTime
+ }
+ }
+ }
+ });
+
await transaction.commit();
return {
code: 0,
msg: '',
data: {
- balance: merchantBalance,
- pendingAmount: balanceUtil.normalizeAmount(pendingAmount + amount),
- availableBalance: balanceUtil.normalizeAmount(Math.max(0, availableBalance - amount)),
- merchantBalance
+ balance: nextBalance,
+ merchantBalance: nextMerchantBalance
}
};
} catch (err) {
diff --git a/uniCloud-tcb/cloudfunctions/router/service/client/ai/pub/chat.js b/uniCloud-tcb/cloudfunctions/router/service/client/ai/pub/chat.js
index 6ee3733..c43baba 100644
--- a/uniCloud-tcb/cloudfunctions/router/service/client/ai/pub/chat.js
+++ b/uniCloud-tcb/cloudfunctions/router/service/client/ai/pub/chat.js
@@ -25,7 +25,7 @@ module.exports = {
}
});
- let answer = '暂时无法回复,请拨打 400-686-5559 咨询';
+ let answer = '';
let conversationId = '';
const text = res.data || '';
const lines = text.split('\n');
@@ -37,10 +37,11 @@ module.exports = {
const p = JSON.parse(d);
if (p.conversation_id) conversationId = p.conversation_id;
if (p.type === 'answer' && p.content) {
- answer = p.content;
+ answer += p.content; // 拼接,不是覆盖
}
} catch (e) { /* 跳过 */ }
}
+ if (!answer) answer = '暂时无法回复,请拨打 400-686-5559 咨询';
return {
code: 0,
diff --git a/uniCloud-tcb/cloudfunctions/router/service/client/ai/pub/doReviewTimer.js b/uniCloud-tcb/cloudfunctions/router/service/client/ai/pub/doReviewTimer.js
new file mode 100644
index 0000000..397658b
--- /dev/null
+++ b/uniCloud-tcb/cloudfunctions/router/service/client/ai/pub/doReviewTimer.js
@@ -0,0 +1,53 @@
+// client/ai/pub/doReviewTimer.js
+module.exports = {
+ main: async (event) => {
+ let { data = {} } = event;
+ const { record_id, file_id, user_id, file_name, file_size } = data;
+
+ if (!record_id || !file_id) return { code: -1, msg: '参数错误' };
+
+ const db = uniCloud.database();
+
+ // 检查是否已经在审查中(防止重复触发)
+ const record = await db.collection('jjx-review-records').doc(record_id).get();
+ if (record.data.length && record.data[0].status === 'reviewing') {
+ // 检查是否刚触发(5分钟内不重复触发)
+ const lastUpdate = record.data[0].update_time;
+ if (lastUpdate && (Date.now() - new Date(lastUpdate).getTime() < 5 * 60 * 1000)) {
+ return { code: 0, msg: '已在审查中,跳过' };
+ }
+ }
+
+ // 更新状态为审查中
+ await db.collection('jjx-review-records').doc(record_id).update({
+ status: 'reviewing',
+ review_started_at: new Date(),
+ update_time: new Date()
+ });
+
+ // 调用实际的审查逻辑
+ try {
+ const doReview = require('./jjxDoReview.js');
+ await doReview.main({
+ data: {
+ record_id,
+ file_id,
+ user_id: user_id || 'anonymous',
+ file_name: file_name || '',
+ file_size: file_size || 0
+ }
+ });
+ return { code: 0, msg: '审查完成' };
+ } catch (e) {
+ console.error('审查失败:', e);
+ // 更新状态为失败
+ await db.collection('jjx-review-records').doc(record_id).update({
+ status: 'failed',
+ result: '审查失败: ' + e.message,
+ error: e.message,
+ update_time: new Date()
+ });
+ return { code: -1, msg: '审查失败: ' + e.message };
+ }
+ }
+};
\ No newline at end of file
diff --git a/uniCloud-tcb/cloudfunctions/router/service/client/ai/pub/getMyReports.js b/uniCloud-tcb/cloudfunctions/router/service/client/ai/pub/getMyReports.js
new file mode 100644
index 0000000..8b8cdf8
--- /dev/null
+++ b/uniCloud-tcb/cloudfunctions/router/service/client/ai/pub/getMyReports.js
@@ -0,0 +1,30 @@
+module.exports = {
+ main: async (event) => {
+ let { data = {}, userInfo, util } = event;
+ let { db } = util;
+ const { pageIndex = 1, pageSize = 50 } = data;
+
+ const uid = (data && data.uid) || '';
+ if (!uid) return { code: -1, msg: '未登录' };
+
+ const where = { user_id: uid };
+
+ const countRes = await db.collection('jjx-review-records').where(where).count();
+
+ const listRes = await db.collection('jjx-review-records')
+ .where(where)
+ .orderBy('_add_time', 'desc')
+ .skip((pageIndex - 1) * pageSize)
+ .limit(pageSize)
+ .get();
+
+ return {
+ code: 0,
+ msg: 'ok',
+ data: {
+ total: countRes.total,
+ rows: listRes.data
+ }
+ };
+ }
+};
\ No newline at end of file
diff --git a/uniCloud-tcb/cloudfunctions/router/service/client/ai/pub/getReviewResult.js b/uniCloud-tcb/cloudfunctions/router/service/client/ai/pub/getReviewResult.js
new file mode 100644
index 0000000..266c9bf
--- /dev/null
+++ b/uniCloud-tcb/cloudfunctions/router/service/client/ai/pub/getReviewResult.js
@@ -0,0 +1,27 @@
+// 云函数:client/ai/pub/getReviewResult
+module.exports = {
+ main: async (event) => {
+ let { data = {}, userInfo, util } = event;
+ let { db } = util;
+ const { record_id } = data;
+
+ if (!record_id) return { code: -1, msg: '缺少 record_id' };
+
+ const record = await db.collection('jjx-review-records').doc(record_id).get();
+ if (!record.data.length) return { code: -1, msg: '记录不存在' };
+
+ const rec = record.data[0];
+
+ return {
+ code: 0,
+ data: {
+ status: rec.status,
+ result: rec.result || '',
+ points_used: rec.points_used || 0,
+ html_url: rec.html_url || '',
+ contract_info: rec.contract_info || {},
+ error: rec.error || ''
+ }
+ };
+ }
+};
\ No newline at end of file
diff --git a/uniCloud-tcb/cloudfunctions/router/service/client/ai/pub/jjxDoReview.js b/uniCloud-tcb/cloudfunctions/router/service/client/ai/pub/jjxDoReview.js
new file mode 100644
index 0000000..86b3b98
--- /dev/null
+++ b/uniCloud-tcb/cloudfunctions/router/service/client/ai/pub/jjxDoReview.js
@@ -0,0 +1,217 @@
+// client/ai/pub/jjxDoReview.js
+module.exports = {
+ main: async (event) => {
+ let { data = {}, userInfo } = event;
+ const { record_id, file_id, user_id, file_name, file_size } = data;
+
+ if (!record_id || !file_id) return { code: -1, msg: '参数错误' };
+
+ const COZE_TOKEN = 'sat_OiXlI4Da5Q99fxmul6pNfExmA6hR898UbxBDwrvjZ638APxRSlFnXbz6zyFedKNG';
+ const BOT_ID = '7689821072762011698';
+ const db = uniCloud.database();
+ const _ = db.command;
+
+ try {
+ // 构造请求
+ const objectString = JSON.stringify([
+ { type: 'file', file_id: file_id },
+ { type: 'text', text: '请审查这份合同' }
+ ]);
+
+ // 调用 Coze 接口(流式模式)
+ const chatRes = await uniCloud.httpclient.request('https://api.coze.cn/v3/chat', {
+ method: 'POST',
+ dataType: 'stream', // 关键:改成 stream
+ contentType: 'json',
+ timeout: 300000, // 5分钟超时
+ headers: {
+ 'Authorization': 'Bearer ' + COZE_TOKEN,
+ 'Accept': 'text/event-stream'
+ },
+ data: {
+ bot_id: BOT_ID,
+ user_id: user_id || 'anonymous',
+ stream: true,
+ auto_save_history: true,
+ additional_messages: [{
+ role: 'user',
+ content: objectString,
+ content_type: 'object_string'
+ }]
+ }
+ });
+
+ // 从流中逐步解析
+ const stream = chatRes.data || chatRes.res;
+ let buffer = '';
+ let answer = '';
+ let conversationId = '';
+ let lastUpdateLen = 0;
+
+ // 返回 Promise,等待流结束
+ return await new Promise((resolve, reject) => {
+
+ stream.on('data', (chunk) => {
+ buffer += chunk.toString();
+ const blocks = buffer.split('\n\n');
+ buffer = blocks.pop();
+
+ for (const block of blocks) {
+ const lines = block.split('\n');
+ let eventName = '';
+ let dataStr = '';
+
+ for (const line of lines) {
+ if (line.startsWith('event:')) eventName = line.slice(6).trim();
+ if (line.startsWith('data:')) dataStr = line.slice(5).trim();
+ }
+
+ if (!dataStr || dataStr === '[DONE]' || dataStr === '"[DONE]"') continue;
+
+ try {
+ const p = JSON.parse(dataStr);
+ if (p.conversation_id) conversationId = p.conversation_id;
+
+ // 处理回答内容
+ if (p.type === 'answer' && p.content) {
+ answer += p.content;
+
+ // 每积累 200 字更新一次数据库(前端轮询能看到进度)
+ if (answer.length - lastUpdateLen >= 200) {
+ lastUpdateLen = answer.length;
+ db.collection('jjx-review-records').doc(record_id).update({
+ result: answer,
+ status: 'reviewing',
+ update_time: new Date()
+ }).catch(() => {});
+ }
+ }
+
+ // 处理工具响应(报告下载链接)
+ if (p.type === 'tool_response' && p.content) {
+ try {
+ const toolData = JSON.parse(p.content);
+ if (toolData.url) answer += '\n\n📎 报告下载:' + toolData.url;
+ } catch (e) {
+ const urlMatch = p.content.match(/https?:\/\/[^\s\)\]\"\<]+\.pdf[^\s\)\]\"\<]*/i);
+ if (urlMatch) answer += '\n\n📎 报告下载:' + urlMatch[0];
+ }
+ }
+ } catch (e) {}
+ }
+ });
+
+ stream.on('end', async () => {
+ if (!answer) {
+ answer = '审查未能获取结果,请稍后重试';
+ }
+
+ try {
+ // 计算消耗积分
+ const pointsUsed = Math.max(150, Math.ceil((file_size || 0) / (100 * 1024)) * 150);
+
+ // 扣积分
+ const userPoints = await db.collection('jjx-user-points').doc(user_id).get();
+ if (userPoints.data.length && userPoints.data[0].balance >= pointsUsed) {
+ await db.collection('jjx-user-points').doc(user_id).update({
+ balance: _.inc(-pointsUsed),
+ total_spent: _.inc(pointsUsed),
+ update_time: new Date()
+ });
+
+ const updatedPoints = await db.collection('jjx-user-points').doc(user_id).get();
+ await db.collection('jjx-points-log').add({
+ user_id: user_id,
+ type: 'spend',
+ amount: -pointsUsed,
+ balance_after: updatedPoints.data[0] ? updatedPoints.data[0].balance : 0,
+ reason: '合同审查 — ' + (file_name || ''),
+ biz_type: 'contract_review',
+ biz_id: record_id,
+ _add_time: new Date()
+ });
+ }
+
+ // 保存 HTML 报告到云存储
+ let htmlUrl = '';
+ try {
+ // 如果回复本身就是 HTML(包含 或 )
+ let htmlContent = answer;
+ const htmlMatch = answer.match(/```html\s*([\s\S]*?)```/);
+ if (htmlMatch && htmlMatch[1]) {
+ htmlContent = htmlMatch[1].trim();
+ }
+
+ if (htmlContent.includes(' {
+ await db.collection('jjx-review-records').doc(record_id).update({
+ status: 'failed',
+ result: '审查失败: ' + err.message,
+ error: err.message,
+ update_time: new Date()
+ });
+ reject(err);
+ });
+
+ });
+
+ } catch (e) {
+ // 审查失败,更新记录
+ await db.collection('jjx-review-records').doc(record_id).update({
+ status: 'failed',
+ result: '审查失败: ' + e.message,
+ error: e.message,
+ update_time: new Date()
+ });
+ return { code: -1, msg: '审查失败: ' + e.message };
+ }
+ }
+};
\ No newline at end of file
diff --git a/uniCloud-tcb/cloudfunctions/router/service/client/ai/pub/review.js b/uniCloud-tcb/cloudfunctions/router/service/client/ai/pub/review.js
new file mode 100644
index 0000000..ced0ff8
--- /dev/null
+++ b/uniCloud-tcb/cloudfunctions/router/service/client/ai/pub/review.js
@@ -0,0 +1,252 @@
+// client/ai/pub/review.js
+// Markdown 转 HTML
+function mdToHtml(md) {
+ if (!md) return '';
+ let html = md;
+ // 标题
+ html = html.replace(/^####\s+(.+)$/gm, '
$1
');
+ html = html.replace(/^###\s+(.+)$/gm, '$1
');
+ html = html.replace(/^##\s+(.+)$/gm, '$1
');
+ html = html.replace(/^#\s+(.+)$/gm, '$1
');
+ // 加粗
+ html = html.replace(/\*\*(.*?)\*\*/g, '$1');
+ // 链接
+ html = html.replace(/(https?:\/\/[^\s\)\]\"\<]+)/g, '$1');
+ // 代码块
+ html = html.replace(/```[\w]*\n([\s\S]*?)```/g, '$1
');
+ // 行内代码
+ html = html.replace(/`([^`]+)`/g, '$1');
+ // 列表
+ html = html.replace(/^[\-\•]\s+(.+)$/gm, '$1');
+ html = html.replace(/^(\d+)\.\s+(.+)$/gm, '$1. $2');
+ // 换行
+ html = html.replace(/\n\n/g, '');
+ html = html.replace(/\n/g, '
');
+ html = '
' + html + '
';
+ // 清理空段落
+ html = html.replace(/\s*<\/p>/g, '');
+ return html;
+}
+
+// 生成精美 HTML 报告
+function generateHtmlReport(markdown, fileName, reportDate, info) {
+ const riskLevel = info.risk_level || '待评估';
+ const riskSummary = info.risk_summary || '';
+ const riskCount = info.risk_count || { high: 0, medium: 0, low: 0 };
+ const contractName = info.contract_name || fileName || '合同审查报告';
+ const contractType = info.contract_type || '';
+
+ // 风险等级颜色
+ let riskColor = '#67c23a';
+ if (riskLevel.includes('高')) riskColor = '#f56c6c';
+ else if (riskLevel.includes('中高')) riskColor = '#e6a23c';
+ else if (riskLevel.includes('中')) riskColor = '#e6a23c';
+ else if (riskLevel.includes('中低')) riskColor = '#409eff';
+
+ return `
+
+
+
+
+${contractName} - 审查报告
+
+
+
+
+
+
+
+
综合风险等级
+
${riskLevel}
+
+
+
风险统计
+
${riskCount.high||0} / ${riskCount.medium||0} / ${riskCount.low||0}
+
+
+
合同类型
+
${contractType || '待识别'}
+
+
+
+
总体评估
+
${riskSummary || '暂无总体评估'}
+
详细审查意见
+ ${mdToHtml(markdown.replace(/```json[\s\S]*?```/, ''))}
+
+ ⚠️ 免责声明:本报告由 AI 系统自动生成,仅供参考,不构成正式法律意见。涉及重大金额或复杂交易的合同,建议咨询执业律师。
+
+
+
+
+
+`;
+}
+
+module.exports = {
+ main: async (event) => {
+ let { data = {}, userInfo } = event;
+ const COZE_TOKEN = 'sat_OiXlI4Da5Q99fxmul6pNfExmA6hR898UbxBDwrvjZ638APxRSlFnXbz6zyFedKNG';
+ const BOT_ID = '7689821072762011698';
+ const db = uniCloud.database();
+ const _ = db.command;
+
+ let fileId = data.file_id;
+
+ // ===== 模式1:上传文件 + 创建待审查记录 =====
+ if (data.upload_only && data.file_base64) {
+ const fileBuffer = Buffer.from(data.file_base64, 'base64');
+ const boundary = '----FormBoundary' + Date.now();
+ const header = Buffer.from(
+ '--' + boundary + '\r\n' +
+ 'Content-Disposition: form-data; name="file"; filename="' + (data.file_name || 'file') + '"\r\n' +
+ 'Content-Type: application/octet-stream\r\n\r\n'
+ );
+ const footer = Buffer.from('\r\n--' + boundary + '--\r\n');
+ const uploadBody = Buffer.concat([header, fileBuffer, footer]);
+
+ try {
+ const uploadRes = await uniCloud.httpclient.request('https://api.coze.cn/v1/files/upload', {
+ method: 'POST',
+ dataType: 'json',
+ headers: {
+ 'Authorization': 'Bearer ' + COZE_TOKEN,
+ 'Content-Type': 'multipart/form-data; boundary=' + boundary,
+ 'Content-Length': uploadBody.length.toString()
+ },
+ content: uploadBody,
+ timeout: 600000
+ });
+ if (uploadRes.data.code !== 0) {
+ return { code: -1, msg: '文件上传失败: ' + uploadRes.data.msg };
+ }
+ fileId = uploadRes.data.data.id;
+
+ // 同时上传到 uniCloud 云存储(用于查看/下载)
+ let fileUrl = '';
+ try {
+ const cloudPath = 'review-files/' + Date.now() + '_' + (data.file_name || 'file');
+ const uploadFileRes = await uniCloud.uploadFile({
+ cloudPath: cloudPath,
+ fileContent: fileBuffer
+ });
+ if (uploadFileRes.fileID) {
+ const urlRes = await uniCloud.getTempFileURL({
+ fileList: [uploadFileRes.fileID]
+ });
+ if (urlRes.fileList && urlRes.fileList[0]) {
+ fileUrl = urlRes.fileList[0].tempFileURL;
+ }
+ }
+ } catch (e) {
+ console.log('云存储上传失败(不影响审查):', e.message);
+ }
+
+ // 创建待审查记录
+ const recordRes = await db.collection('jjx-review-records').add({
+ user_id: (data && data.uid) || 'anonymous',
+ file_name: data.file_name || '',
+ file_size: data.file_size || 0,
+ file_id: fileId,
+ file_url: fileUrl,
+ chat_id: '',
+ conversation_id: '',
+ status: 'uploaded',
+ result: '',
+ points_used: 0,
+ _add_time: new Date(),
+ update_time: new Date()
+ });
+
+ return { code: 0, msg: 'ok', data: { file_id: fileId, record_id: recordRes.id, file_url: fileUrl } };
+ } catch (e) {
+ return { code: -1, msg: '上传异常: ' + e.message };
+ }
+ }
+
+ // ===== 模式2:发起审查(只更新状态,立即返回)=====
+ if (!fileId) return { code: -1, msg: '缺少 file_id' };
+
+ const recordId = data.record_id;
+ if (!recordId) return { code: -1, msg: '缺少 record_id' };
+
+ // 检查当前状态
+ const record = await db.collection('jjx-review-records').doc(recordId).get();
+ if (record.data.length) {
+ const rec = record.data[0];
+
+ // 已完成,直接返回结果
+ if (rec.status === 'completed') {
+ return {
+ code: 0, msg: 'ok',
+ data: {
+ record_id: recordId,
+ status: 'completed',
+ result: rec.result,
+ points_used: rec.points_used,
+ html_url: rec.html_url || '',
+ contract_info: rec.contract_info || {}
+ }
+ };
+ }
+
+ // 正在审查中,直接返回
+ if (rec.status === 'reviewing') {
+ return { code: 0, msg: 'ok', data: { record_id: recordId, status: 'reviewing' } };
+ }
+
+ // 失败,返回错误
+ if (rec.status === 'failed') {
+ return { code: 0, msg: 'ok', data: { record_id: recordId, status: 'failed', result: rec.result || '审查失败' } };
+ }
+ }
+
+ // 更新状态为审查中
+ await db.collection('jjx-review-records').doc(recordId).update({
+ file_id: fileId,
+ file_name: data.file_name || '',
+ file_size: data.file_size || 0,
+ status: 'reviewing',
+ review_started_at: new Date(),
+ update_time: new Date()
+ });
+
+ // ===== 立即返回,审查由定时任务触发 =====
+ return {
+ code: 0, msg: 'ok',
+ data: { record_id: recordId, status: 'reviewing' }
+ };
+ }
+};
\ No newline at end of file
diff --git a/uniCloud-tcb/cloudfunctions/router/service/client/payment.js b/uniCloud-tcb/cloudfunctions/router/service/client/payment.js
index 01a6c3d..0d07e87 100644
--- a/uniCloud-tcb/cloudfunctions/router/service/client/payment.js
+++ b/uniCloud-tcb/cloudfunctions/router/service/client/payment.js
@@ -1,28 +1,36 @@
'use strict';
const dayjs = require("dayjs");
-const { BALANCE_DOC_ID, normalizeAmount } = require("../admin/finance/balance/sys/util.js");
+const {
+ BALANCE_DOC_ID,
+ normalizeAmount
+} = require("../admin/finance/balance/sys/util.js");
const notaryPay = require("./order/kh/util/notaryPay");
var vk; // 全局vk实例
const orderTypeList = [
- '商城订单',
- '金久信自营增值服务',
- '保证金',
- '会员升级',
- '询价订单',
- '加工订单',
- '旧货订单',
- '报价次数',
- '现货广告',
- '旧货加急',
- '旧货次数',
- '金久信学堂下载次数',
- '司法调证',
- '金久信签约宝次数',
- '金久信自营询价',
+ '商城订单',
+ '金久信自营增值服务',
+ '保证金',
+ '会员升级',
+ '询价订单',
+ '加工订单',
+ '旧货订单',
+ '报价次数',
+ '现货广告',
+ '旧货加急',
+ '旧货次数',
+ '金久信学堂下载次数',
+ '司法调证',
+ '金久信签约宝次数',
+ '金久信自营询价',
+ '',
+ '',
+ '',
+ '',
+ 'AI积分充值'
];
// 涉及的表名
const dbName = {
- //test: "vk-test", // 测试表
+ //test: "vk-test", // 测试表
};
var db = uniCloud.database(); // 全局数据库引用
@@ -30,21 +38,21 @@ var _ = db.command; // 数据库操作符
var $ = _.aggregate; // 聚合查询操作符
function pickFirstDefined() {
- for (let i = 0; i < arguments.length; i++) {
- const value = arguments[i];
- if (value !== undefined && value !== null && value !== '') {
- return value;
- }
- }
- return undefined;
+ for (let i = 0; i < arguments.length; i++) {
+ const value = arguments[i];
+ if (value !== undefined && value !== null && value !== '') {
+ return value;
+ }
+ }
+ return undefined;
}
function toFenAmount(value) {
- const amount = Number(value);
- if (!Number.isFinite(amount)) {
- throw new Error(`支付金额格式不正确: ${value}`);
- }
- return Number((amount / 100).toFixed(2));
+ const amount = Number(value);
+ if (!Number.isFinite(amount)) {
+ throw new Error(`支付金额格式不正确: ${value}`);
+ }
+ return Number((amount / 100).toFixed(2));
}
/**
@@ -52,612 +60,698 @@ function toFenAmount(value) {
* 文档地址:https://vkdoc.fsq.pub/client/uniCloud/cloudfunctions/cloudObject.html#内置权限
*/
var cloudObject = {
- isCloudObject: true, // 标记为云对象模式
- /**
- * 请求前处理,主要用于调用方法之前进行预处理,一般用于拦截器、统一的身份验证、参数校验、定义全局对象等。
- * 文档地址:https://vkdoc.fsq.pub/client/uniCloud/cloudfunctions/cloudObject.html#before-预处理
- */
- _before: async function () {
- vk = this.vk; // 将vk定义为全局对象
- // let { customUtil, uniID, config, pubFun } = this.getUtil(); // 获取工具包
- },
- /**
- * 请求后处理,主要用于处理本次调用方法的返回结果或者抛出的错误
- * 文档地址:https://vkdoc.fsq.pub/client/uniCloud/cloudfunctions/cloudObject.html#after-后处理
- */
- _after: async function (options) {
- let {err, res} = options;
- if (err) {
- return; // 如果方法抛出错误,直接return;不处理
- }
- return res;
- },
- /**
- * 获取列表
- * @url client/payment.getList 前端调用的url参数地址
- */
- pub_payNotify: async function (data) {
- let res = {code: 0, msg: ''};
- // 业务逻辑开始-----------------------------------------------------------
- console.log("pay notify data", data)
- let orderNo = data.billNo
- if (!orderNo) orderNo = data.merOrderId
- let payTime = null, payAmount = null, payNo = null
- if (data.billPayment) {
- const billPayment = JSON.parse(data.billPayment)
- payTime = pickFirstDefined(billPayment.payTime, data.payTime, data.createTime)
- payAmount = toFenAmount(pickFirstDefined(billPayment.receiptAmount, billPayment.totalAmount, data.receiptAmount, data.totalAmount))
- payNo = pickFirstDefined(billPayment.paySeqId, billPayment.seqId, data.seqId, data.targetOrderId)
- } else {
- payTime = pickFirstDefined(data.payTime, data.createTime)
- payAmount = toFenAmount(pickFirstDefined(data.receiptAmount, data.totalAmount))
- payNo = pickFirstDefined(data.seqId, data.targetOrderId)
- }
- const isPaid = data.billStatus === 'PAID' || data.status === 'TRADE_SUCCESS'
+ isCloudObject: true, // 标记为云对象模式
+ /**
+ * 请求前处理,主要用于调用方法之前进行预处理,一般用于拦截器、统一的身份验证、参数校验、定义全局对象等。
+ * 文档地址:https://vkdoc.fsq.pub/client/uniCloud/cloudfunctions/cloudObject.html#before-预处理
+ */
+ _before: async function() {
+ vk = this.vk; // 将vk定义为全局对象
+ // let { customUtil, uniID, config, pubFun } = this.getUtil(); // 获取工具包
+ },
+ /**
+ * 请求后处理,主要用于处理本次调用方法的返回结果或者抛出的错误
+ * 文档地址:https://vkdoc.fsq.pub/client/uniCloud/cloudfunctions/cloudObject.html#after-后处理
+ */
+ _after: async function(options) {
+ let {
+ err,
+ res
+ } = options;
+ if (err) {
+ return; // 如果方法抛出错误,直接return;不处理
+ }
+ return res;
+ },
+ /**
+ * 获取列表
+ * @url client/payment.getList 前端调用的url参数地址
+ */
+ pub_payNotify: async function(data) {
+ console.log("=== 支付回调触发 ===", JSON.stringify(data));
+ console.log("订单号:", data.billNo || data.merOrderId);
+ console.log("支付状态:", data.billStatus);
+ let res = {
+ code: 0,
+ msg: ''
+ };
+ // 业务逻辑开始-----------------------------------------------------------
+ console.log("pay notify data", data)
+ let orderNo = data.billNo
+ if (!orderNo) orderNo = data.merOrderId
+ let payTime = null,
+ payAmount = null,
+ payNo = null
+ if (data.billPayment) {
+ const billPayment = JSON.parse(data.billPayment)
+ payTime = pickFirstDefined(billPayment.payTime, data.payTime, data.createTime)
+ payAmount = toFenAmount(pickFirstDefined(billPayment.receiptAmount, billPayment.totalAmount, data
+ .receiptAmount, data.totalAmount))
+ payNo = pickFirstDefined(billPayment.paySeqId, billPayment.seqId, data.seqId, data.targetOrderId)
+ } else {
+ payTime = pickFirstDefined(data.payTime, data.createTime)
+ payAmount = toFenAmount(pickFirstDefined(data.receiptAmount, data.totalAmount))
+ payNo = pickFirstDefined(data.seqId, data.targetOrderId)
+ }
+ const isPaid = data.billStatus === 'PAID' || data.status === 'TRADE_SUCCESS'
- const payRecord = await vk.baseDao.findByWhereJson({
- dbName: "jjx-order-pay-list",
- whereJson: {
- orderNo
- }
- });
- if (payRecord && isPaid) {
- const order = await vk.baseDao.findById({
- dbName: "jjx-order",
- id: payRecord.orderId
- });
- if (!payRecord.hasPaid) {
- await vk.baseDao.updateById({
- dbName: "jjx-order-pay-list",
- id: payRecord._id,
- dataJson: {
- hasPaid: true,
- paidAt: payTime,
- payAmount,
- payNo,
- paidVoucher: "",
- buyerRemark: payRecord.isNotaryFullPay ? "公证支付" : payRecord.buyerRemark,
- sellerConfirm: payRecord.isNotaryFullPay ? true : payRecord.sellerConfirm,
- confirmAt: payRecord.isNotaryFullPay ? payTime : payRecord.confirmAt,
- payNotifyData: data
- }
- });
- if (payRecord.isNotaryFullPay) {
- await notaryPay.notifyPaySuccess({
- vk,
- payRecord,
- order
- });
- }
- }
- if (order) {
- const orderUpdate = {
- orderStatus: order.orderStatus > 0 ? order.orderStatus : 1,
- payTime,
- payAmount,
- payNo,
- };
- if (payRecord.isNotaryFullPay) {
- orderUpdate.notaryPayStatus = 1;
- orderUpdate.notaryPaidAt = payTime;
- orderUpdate.notaryPaidAmount = payAmount;
- orderUpdate.notaryPayListId = payRecord._id;
- orderUpdate.notaryPayOrderNo = orderNo;
- orderUpdate.payStatus = 2;
- }
- await vk.baseDao.updateById({
- dbName: "jjx-order",
- id: order._id,
- dataJson: orderUpdate
- });
- }
- return res;
- }
+ const payRecord = await vk.baseDao.findByWhereJson({
+ dbName: "jjx-order-pay-list",
+ whereJson: {
+ orderNo
+ }
+ });
+ if (payRecord && isPaid) {
+ const order = await vk.baseDao.findById({
+ dbName: "jjx-order",
+ id: payRecord.orderId
+ });
+ if (!payRecord.hasPaid) {
+ await vk.baseDao.updateById({
+ dbName: "jjx-order-pay-list",
+ id: payRecord._id,
+ dataJson: {
+ hasPaid: true,
+ paidAt: payTime,
+ payAmount,
+ payNo,
+ paidVoucher: "",
+ buyerRemark: payRecord.isNotaryFullPay ? "公证支付" : payRecord.buyerRemark,
+ sellerConfirm: payRecord.isNotaryFullPay ? true : payRecord.sellerConfirm,
+ confirmAt: payRecord.isNotaryFullPay ? payTime : payRecord.confirmAt,
+ payNotifyData: data
+ }
+ });
+ if (payRecord.isNotaryFullPay) {
+ await notaryPay.notifyPaySuccess({
+ vk,
+ payRecord,
+ order
+ });
+ }
+ }
+ if (order) {
+ const orderUpdate = {
+ orderStatus: order.orderStatus > 0 ? order.orderStatus : 1,
+ payTime,
+ payAmount,
+ payNo,
+ };
+ if (payRecord.isNotaryFullPay) {
+ orderUpdate.notaryPayStatus = 1;
+ orderUpdate.notaryPaidAt = payTime;
+ orderUpdate.notaryPaidAmount = payAmount;
+ orderUpdate.notaryPayListId = payRecord._id;
+ orderUpdate.notaryPayOrderNo = orderNo;
+ orderUpdate.payStatus = 2;
+ }
+ await vk.baseDao.updateById({
+ dbName: "jjx-order",
+ id: order._id,
+ dataJson: orderUpdate
+ });
+ }
+ return res;
+ }
- let order = await vk.baseDao.findByWhereJson({
- dbName: "jjx-order",
- whereJson: {
- orderNo
- }
- });
+ let order = await vk.baseDao.findByWhereJson({
+ dbName: "jjx-order",
+ whereJson: {
+ orderNo
+ }
+ });
- // type 0: 商品
- // type 1: 自营
- // type 2: 保证金
- // type 3: 会员升级
- // type 7: 询价次数
+ // type 0: 商品
+ // type 1: 自营
+ // type 2: 保证金
+ // type 3: 会员升级
+ // type 7: 询价次数
- // orderStatus 0待支付 1已支付待发货 2已发货待收货 3已收货待完成 4已完成 5退款审核中 6退款成功 7退款失败
- if (order && order.orderStatus === 0 && isPaid) {
+ // orderStatus 0待支付 1已支付待发货 2已发货待收货 3已收货待完成 4已完成 5退款审核中 6退款成功 7退款失败
+ if (order && order.orderStatus === 0 && isPaid) {
- await vk.baseDao.updateById({
- dbName: "jjx-order",
- id: order._id,
- dataJson: {
- orderStatus: 1,
- payTime,
- payAmount,
- payNo,
- }
- });
+ await vk.baseDao.updateById({
+ dbName: "jjx-order",
+ id: order._id,
+ dataJson: {
+ orderStatus: 1,
+ payTime,
+ payAmount,
+ payNo,
+ }
+ });
- if (![4, 5, 6].includes(order.type)) {
- const incomeAmount = normalizeAmount(payAmount);
- const orderTypeName = orderTypeList[order.type] || `类型${order.type}`;
- const balanceInfo = await vk.baseDao.findByWhereJson({
- dbName: "jjx-balance",
- whereJson: {
- _id: BALANCE_DOC_ID
- }
- });
- if (!balanceInfo) {
- throw new Error(`jjx-balance余额记录不存在: ${BALANCE_DOC_ID}`);
- }
+ if (![4, 5, 6].includes(order.type)) {
+ const incomeAmount = normalizeAmount(payAmount);
+ const orderTypeName = orderTypeList[order.type] || `类型${order.type}`;
+ const balanceInfo = await vk.baseDao.findByWhereJson({
+ dbName: "jjx-balance",
+ whereJson: {
+ _id: BALANCE_DOC_ID
+ }
+ });
+ if (!balanceInfo) {
+ throw new Error(`jjx-balance余额记录不存在: ${BALANCE_DOC_ID}`);
+ }
- await vk.baseDao.add({
- dbName: "jjx-balance-log",
- dataJson: {
- description: `${orderTypeName}-订单支付入账-${order.orderNo}`,
- amount: incomeAmount,
- type: "income",
- logTime: payTime ? new Date(payTime).getTime() : Date.now(),
- operatorId: order.uid || "",
- source: "order_pay",
- orderId: order._id,
- orderNo: order.orderNo
- }
- });
+ await vk.baseDao.add({
+ dbName: "jjx-balance-log",
+ dataJson: {
+ description: `${orderTypeName}-订单支付入账-${order.orderNo}`,
+ amount: incomeAmount,
+ type: "income",
+ logTime: payTime ? new Date(payTime).getTime() : Date.now(),
+ operatorId: order.uid || "",
+ source: "order_pay",
+ orderId: order._id,
+ orderNo: order.orderNo
+ }
+ });
- await vk.baseDao.updateById({
- dbName: "jjx-balance",
- id: BALANCE_DOC_ID,
- dataJson: {
- balance: _.inc(incomeAmount),
- _update_time: Date.now()
- }
- });
- }
+ await vk.baseDao.updateById({
+ dbName: "jjx-balance",
+ id: BALANCE_DOC_ID,
+ dataJson: {
+ balance: _.inc(incomeAmount),
+ _update_time: Date.now()
+ }
+ });
+ }
- if ([2, 3, 11, 12, 13].includes(order.type)) {
- await vk.baseDao.updateById({
- dbName: "jjx-order",
- id: order._id,
- dataJson: {
- orderStatus: 4
- }
- });
- }
+ if ([2, 3, 11, 12, 13,20].includes(order.type)) {
+ await vk.baseDao.updateById({
+ dbName: "jjx-order",
+ id: order._id,
+ dataJson: {
+ orderStatus: 4
+ }
+ });
+ }
- if (order.type === 0) {
- const goodsList = await vk.baseDao.select({
- dbName: "jjx-order-goods",
- whereJson: {
- orderId: order._id,
- }
- });
- for (let i = 0; i < goodsList.rows.length; i++) {
- const orderGoods = goodsList.rows[i];
- const goods = await vk.baseDao.findByWhereJson({
- dbName: "jjx-goods",
- whereJson: {
- _id: orderGoods.goodsId,
- }
- });
- if (!goods.sales) {
- await vk.baseDao.updateById({
- dbName: "jjx-goods",
- id: orderGoods.goodsId,
- dataJson: {
- sales: 1,
- }
- });
- } else {
- await vk.baseDao.updateById({
- dbName: "jjx-goods",
- id: orderGoods.goodsId,
- dataJson: {
- sales: _.inc(1),
- }
- })
- }
- }
- }
- if (order.type === 2) {
- const goods = await vk.baseDao.findByWhereJson({
- dbName: "jjx-order-goods",
- whereJson: {
- orderId: order._id,
- }
- });
- const user = await vk.baseDao.findByWhereJson({
- dbName: "uni-id-users",
- whereJson: {
- _id: order.uid,
- }
- });
- if (!user.paidEnsure) {
- await vk.baseDao.updateById({
- dbName: "uni-id-users",
- id: order.uid,
- dataJson: {
- paidEnsure: goods.goodsPrice,
- }
- });
- } else {
- await vk.baseDao.updateById({
- dbName: "uni-id-users",
- id: order.uid,
- dataJson: {
- paidEnsure: _.inc(goods.goodsPrice),
- }
- });
- }
- } else if (order.type === 3) {
- const goods = await vk.baseDao.findByWhereJson({
- dbName: "jjx-order-goods",
- whereJson: {
- orderId: order._id,
- }
- });
- const userLevelConfig = await vk.baseDao.findByWhereJson({
- dbName: "jjx-user-level",
- whereJson: {
- level: goods.goodsId,
- }
- });
- let paidEnsure = 0
- if (goods.goodsId === 3) {
- paidEnsure = [50000, 100000, 200000][order.ensureIndex]
- }
- const dataJson = {
- userLevel: goods.goodsId,
- userGrade: 0,
- upgradeExpiredAt: vk.pubfn.getOffsetTime(new Date(), {
- year: 1,
- mode: "after",
- }),
- paidEnsure: _.inc(paidEnsure),
- }
- if (userLevelConfig.ensureAmount && userLevelConfig.ensureAmount > 0) {
- dataJson.paidEnsure = _.inc(userLevelConfig.ensureAmount)
- }
- const user = await vk.baseDao.updateAndReturn({
- dbName: "uni-id-users",
- whereJson: {
- _id: order.uid,
- },
- dataJson
- });
- const messageConfig = await vk.baseDao.findByWhereJson({
- dbName: "jjx-message-config",
- dataJson: {
- uid: order.uid
- }
- });
+ if (order.type === 0) {
+ const goodsList = await vk.baseDao.select({
+ dbName: "jjx-order-goods",
+ whereJson: {
+ orderId: order._id,
+ }
+ });
+ for (let i = 0; i < goodsList.rows.length; i++) {
+ const orderGoods = goodsList.rows[i];
+ const goods = await vk.baseDao.findByWhereJson({
+ dbName: "jjx-goods",
+ whereJson: {
+ _id: orderGoods.goodsId,
+ }
+ });
+ if (!goods.sales) {
+ await vk.baseDao.updateById({
+ dbName: "jjx-goods",
+ id: orderGoods.goodsId,
+ dataJson: {
+ sales: 1,
+ }
+ });
+ } else {
+ await vk.baseDao.updateById({
+ dbName: "jjx-goods",
+ id: orderGoods.goodsId,
+ dataJson: {
+ sales: _.inc(1),
+ }
+ })
+ }
+ }
+ }
+ if (order.type === 2) {
+ const goods = await vk.baseDao.findByWhereJson({
+ dbName: "jjx-order-goods",
+ whereJson: {
+ orderId: order._id,
+ }
+ });
+ const user = await vk.baseDao.findByWhereJson({
+ dbName: "uni-id-users",
+ whereJson: {
+ _id: order.uid,
+ }
+ });
+ if (!user.paidEnsure) {
+ await vk.baseDao.updateById({
+ dbName: "uni-id-users",
+ id: order.uid,
+ dataJson: {
+ paidEnsure: goods.goodsPrice,
+ }
+ });
+ } else {
+ await vk.baseDao.updateById({
+ dbName: "uni-id-users",
+ id: order.uid,
+ dataJson: {
+ paidEnsure: _.inc(goods.goodsPrice),
+ }
+ });
+ }
+ } else if (order.type === 3) {
+ const goods = await vk.baseDao.findByWhereJson({
+ dbName: "jjx-order-goods",
+ whereJson: {
+ orderId: order._id,
+ }
+ });
+ const userLevelConfig = await vk.baseDao.findByWhereJson({
+ dbName: "jjx-user-level",
+ whereJson: {
+ level: goods.goodsId,
+ }
+ });
+ let paidEnsure = 0
+ if (goods.goodsId === 3) {
+ paidEnsure = [50000, 100000, 200000][order.ensureIndex]
+ }
+ const dataJson = {
+ userLevel: goods.goodsId,
+ userGrade: 0,
+ upgradeExpiredAt: vk.pubfn.getOffsetTime(new Date(), {
+ year: 1,
+ mode: "after",
+ }),
+ paidEnsure: _.inc(paidEnsure),
+ }
+ if (userLevelConfig.ensureAmount && userLevelConfig.ensureAmount > 0) {
+ dataJson.paidEnsure = _.inc(userLevelConfig.ensureAmount)
+ }
+ const user = await vk.baseDao.updateAndReturn({
+ dbName: "uni-id-users",
+ whereJson: {
+ _id: order.uid,
+ },
+ dataJson
+ });
+ const messageConfig = await vk.baseDao.findByWhereJson({
+ dbName: "jjx-message-config",
+ dataJson: {
+ uid: order.uid
+ }
+ });
- if (!messageConfig) {
- await vk.baseDao.add({
- dbName: "jjx-message-config",
- dataJson: {
- uid: order.uid,
- article: true,
- process: true,
- old: true,
- enquiry: true,
- productList: [],
- addressRegion: {}
- }
- })
- }
- } else if (order.type === 7) {
- await vk.baseDao.updateById({
- dbName: "jjx-order",
- id: order._id,
- dataJson: {
- orderStatus: 4
- }
- });
+ if (!messageConfig) {
+ await vk.baseDao.add({
+ dbName: "jjx-message-config",
+ dataJson: {
+ uid: order.uid,
+ article: true,
+ process: true,
+ old: true,
+ enquiry: true,
+ productList: [],
+ addressRegion: {}
+ }
+ })
+ }
+ } else if (order.type === 7) {
+ await vk.baseDao.updateById({
+ dbName: "jjx-order",
+ id: order._id,
+ dataJson: {
+ orderStatus: 4
+ }
+ });
- const goods = await vk.baseDao.findByWhereJson({
- dbName: "jjx-order-goods",
- whereJson: {
- orderId: order._id,
- }
- });
- // 单次
- if (goods.goodsId === '6587b034466d41a591df881c') {
- // 仅针对单条询价次数
- if (goods.isSingleQuote) {
- const checkData = await vk.baseDao.findByWhereJson({
- dbName: "jjx-user-quote-num",
- whereJson: {
- uid: order.uid,
- type: goods.type,
- sheetId: goods.sheetId,
- }
- });
- await vk.baseDao.add({
- dbName: "jjx-user-quote-num",
- dataJson: {
- num: checkData ? _.inc(1) : 1,
- type: goods.type,
- sheetId: goods.sheetId,
- uid: order.uid,
- }
- });
- } else {
- await vk.baseDao.updateById({
- dbName: "uni-id-users",
- id: order.uid,
- dataJson: {
- remainQuoteNum: _.inc(1)
- }
- });
- }
- } else {
- const amountItem = await vk.baseDao.findById({
- dbName: 'jjx-quote-num-config',
- id: goods.goodsId
- })
- const user = await vk.baseDao.findById({
- dbName: "uni-id-users",
- id: order.uid
- });
- let quoteExpiredAt = vk.pubfn.getOffsetTime(new Date(), {
- day: amountItem.day,
- mode: "after",
- })
- if (user.quoteExpiredAt) {
- if (dayjs(user.quoteExpiredAt).isAfter(dayjs())) {
- vk.pubfn.getOffsetTime(dayjs(user.quoteExpiredAt).toDate(), {
- day: amountItem.day,
- mode: "after",
- })
- }
- }
- await vk.baseDao.updateById({
- dbName: "uni-id-users",
- id: order.uid,
- dataJson: {
- quoteExpiredAt
- }
- });
- }
- } else if (order.type === 8) {
- await vk.baseDao.updateById({
- dbName: "jjx-order",
- id: order._id,
- dataJson: {
- orderStatus: 4
- }
- });
+ const goods = await vk.baseDao.findByWhereJson({
+ dbName: "jjx-order-goods",
+ whereJson: {
+ orderId: order._id,
+ }
+ });
+ // 单次
+ if (goods.goodsId === '6587b034466d41a591df881c') {
+ // 仅针对单条询价次数
+ if (goods.isSingleQuote) {
+ const checkData = await vk.baseDao.findByWhereJson({
+ dbName: "jjx-user-quote-num",
+ whereJson: {
+ uid: order.uid,
+ type: goods.type,
+ sheetId: goods.sheetId,
+ }
+ });
+ await vk.baseDao.add({
+ dbName: "jjx-user-quote-num",
+ dataJson: {
+ num: checkData ? _.inc(1) : 1,
+ type: goods.type,
+ sheetId: goods.sheetId,
+ uid: order.uid,
+ }
+ });
+ } else {
+ await vk.baseDao.updateById({
+ dbName: "uni-id-users",
+ id: order.uid,
+ dataJson: {
+ remainQuoteNum: _.inc(1)
+ }
+ });
+ }
+ } else {
+ const amountItem = await vk.baseDao.findById({
+ dbName: 'jjx-quote-num-config',
+ id: goods.goodsId
+ })
+ const user = await vk.baseDao.findById({
+ dbName: "uni-id-users",
+ id: order.uid
+ });
+ let quoteExpiredAt = vk.pubfn.getOffsetTime(new Date(), {
+ day: amountItem.day,
+ mode: "after",
+ })
+ if (user.quoteExpiredAt) {
+ if (dayjs(user.quoteExpiredAt).isAfter(dayjs())) {
+ vk.pubfn.getOffsetTime(dayjs(user.quoteExpiredAt).toDate(), {
+ day: amountItem.day,
+ mode: "after",
+ })
+ }
+ }
+ await vk.baseDao.updateById({
+ dbName: "uni-id-users",
+ id: order.uid,
+ dataJson: {
+ quoteExpiredAt
+ }
+ });
+ }
+ } else if (order.type === 8) {
+ await vk.baseDao.updateById({
+ dbName: "jjx-order",
+ id: order._id,
+ dataJson: {
+ orderStatus: 4
+ }
+ });
- const goods = await vk.baseDao.findByWhereJson({
- dbName: "jjx-order-goods",
- whereJson: {
- orderId: order._id,
- }
- });
+ const goods = await vk.baseDao.findByWhereJson({
+ dbName: "jjx-order-goods",
+ whereJson: {
+ orderId: order._id,
+ }
+ });
- const hasData = await vk.baseDao.findByWhereJson({
- dbName: "jjx-spot-ad",
- whereJson: {
- uid: order.uid,
- province: goods.goodsId
- }
- })
+ const hasData = await vk.baseDao.findByWhereJson({
+ dbName: "jjx-spot-ad",
+ whereJson: {
+ uid: order.uid,
+ province: goods.goodsId
+ }
+ })
- let topExpiredAt = vk.pubfn.getOffsetTime(new Date(), {
- month: goods.num,
- mode: "after",
- })
+ let topExpiredAt = vk.pubfn.getOffsetTime(new Date(), {
+ month: goods.num,
+ mode: "after",
+ })
- if (!hasData) {
- await vk.baseDao.add({
- dbName: "jjx-spot-ad",
- dataJson: {
- uid: order.uid,
- province: goods.goodsId,
- topExpiredAt
- }
- })
- } else {
- topExpiredAt = vk.pubfn.getOffsetTime(hasData.topExpiredAt, {
- month: goods.num,
- mode: "after",
- })
- await vk.baseDao.updateById({
- dbName: "jjx-spot-ad",
- id: hasData._id,
- dataJson: {
- topExpiredAt
- }
- })
- }
+ if (!hasData) {
+ await vk.baseDao.add({
+ dbName: "jjx-spot-ad",
+ dataJson: {
+ uid: order.uid,
+ province: goods.goodsId,
+ topExpiredAt
+ }
+ })
+ } else {
+ topExpiredAt = vk.pubfn.getOffsetTime(hasData.topExpiredAt, {
+ month: goods.num,
+ mode: "after",
+ })
+ await vk.baseDao.updateById({
+ dbName: "jjx-spot-ad",
+ id: hasData._id,
+ dataJson: {
+ topExpiredAt
+ }
+ })
+ }
- await vk.baseDao.updateById({
- dbName: "jjx-spot-goods",
- id: goods.pk,
- dataJson: {
- topExpiredAt
- }
- });
- } else if (order.type === 9) {
- await vk.baseDao.updateById({
- dbName: "jjx-order",
- id: order._id,
- dataJson: {
- orderStatus: 4
- }
- });
+ await vk.baseDao.updateById({
+ dbName: "jjx-spot-goods",
+ id: goods.pk,
+ dataJson: {
+ topExpiredAt
+ }
+ });
+ } else if (order.type === 9) {
+ await vk.baseDao.updateById({
+ dbName: "jjx-order",
+ id: order._id,
+ dataJson: {
+ orderStatus: 4
+ }
+ });
- const goods = await vk.baseDao.findByWhereJson({
- dbName: "jjx-order-goods",
- whereJson: {
- orderId: order._id,
- }
- });
+ const goods = await vk.baseDao.findByWhereJson({
+ dbName: "jjx-order-goods",
+ whereJson: {
+ orderId: order._id,
+ }
+ });
- await vk.baseDao.updateById({
- dbName: "jjx-old",
- id: goods.goodsId,
- dataJson: {
- isUrgent: true
- }
- })
- } else if (order.type === 10) {
- await vk.baseDao.updateById({
- dbName: "jjx-order",
- id: order._id,
- dataJson: {
- orderStatus: 4
- }
- });
+ await vk.baseDao.updateById({
+ dbName: "jjx-old",
+ id: goods.goodsId,
+ dataJson: {
+ isUrgent: true
+ }
+ })
+ } else if (order.type === 10) {
+ await vk.baseDao.updateById({
+ dbName: "jjx-order",
+ id: order._id,
+ dataJson: {
+ orderStatus: 4
+ }
+ });
- await vk.baseDao.updateById({
- dbName: "uni-id-users",
- id: order.uid,
- dataJson: {
- remainOldNum: _.inc(1)
- }
- });
- } else if (order.type === 11) {
- await vk.baseDao.updateById({
- dbName: "uni-id-users",
- id: order.uid,
- dataJson: {
- remainArticleDownloadNum: _.inc(1)
- }
- });
- } else if (order.type === 12) {
- const goods = await vk.baseDao.findByWhereJson({
- dbName: "jjx-order-goods",
- whereJson: {
- orderId: order._id,
- }
- });
- const contract = await vk.baseDao.findById({
- dbName: "jjx-contract",
- id: goods.goodsId
- });
- const canDownloadUidList = []
- if (!contract.canDownloadUidList) {
- canDownloadUidList.push(order.uid)
- } else {
- canDownloadUidList.push(...contract.canDownloadUidList, order.uid)
- }
- await vk.baseDao.updateById({
- dbName: "jjx-contract",
- id: goods.goodsId,
- dataJson: {
- canDownloadUidList
- }
- });
- await vk.baseDao.update({
- dbName: "jjx-certificate",
- whereJson: {
- contractId: goods.goodsId
- },
- dataJson: {
- status: 1,
- paidConfirm: true
- }
- });
+ await vk.baseDao.updateById({
+ dbName: "uni-id-users",
+ id: order.uid,
+ dataJson: {
+ remainOldNum: _.inc(1)
+ }
+ });
+ } else if (order.type === 11) {
+ await vk.baseDao.updateById({
+ dbName: "uni-id-users",
+ id: order.uid,
+ dataJson: {
+ remainArticleDownloadNum: _.inc(1)
+ }
+ });
+ } else if (order.type === 12) {
+ const goods = await vk.baseDao.findByWhereJson({
+ dbName: "jjx-order-goods",
+ whereJson: {
+ orderId: order._id,
+ }
+ });
+ const contract = await vk.baseDao.findById({
+ dbName: "jjx-contract",
+ id: goods.goodsId
+ });
+ const canDownloadUidList = []
+ if (!contract.canDownloadUidList) {
+ canDownloadUidList.push(order.uid)
+ } else {
+ canDownloadUidList.push(...contract.canDownloadUidList, order.uid)
+ }
+ await vk.baseDao.updateById({
+ dbName: "jjx-contract",
+ id: goods.goodsId,
+ dataJson: {
+ canDownloadUidList
+ }
+ });
+ await vk.baseDao.update({
+ dbName: "jjx-certificate",
+ whereJson: {
+ contractId: goods.goodsId
+ },
+ dataJson: {
+ status: 1,
+ paidConfirm: true
+ }
+ });
- const certificate = await vk.baseDao.findByWhereJson({
- dbName: "jjx-certificate",
- whereJson: {
- contractId: goods.goodsId,
- uid: order.uid
- }
- });
- if (certificate.invoiceTypes !== 'none') {
- await vk.baseDao.updateById({
- dbName: "jjx-order",
- id: order._id,
- dataJson: {
- isInvoice: 1,
- name: certificate.companyName,
- code: certificate.taxNumber,
- phone: certificate.phone,
- }
- });
- }
+ const certificate = await vk.baseDao.findByWhereJson({
+ dbName: "jjx-certificate",
+ whereJson: {
+ contractId: goods.goodsId,
+ uid: order.uid
+ }
+ });
+ if (certificate.invoiceTypes !== 'none') {
+ await vk.baseDao.updateById({
+ dbName: "jjx-order",
+ id: order._id,
+ dataJson: {
+ isInvoice: 1,
+ name: certificate.companyName,
+ code: certificate.taxNumber,
+ phone: certificate.phone,
+ }
+ });
+ }
- } else if (order.type === 13) {
- const goods = await vk.baseDao.findByWhereJson({
- dbName: "jjx-order-goods",
- whereJson: {
- orderId: order._id,
- }
- });
- const qyb = await vk.baseDao.findByWhereJson({
- dbName: "jjx-qyb",
- whereJson: {
- _id: goods.goodsId
- }
- });
- const user = await vk.baseDao.findByWhereJson({
- dbName: "uni-id-users",
- whereJson: {
- _id: order.uid,
- }
- });
- const remainNum = user.qybNum + qyb.getNum;
- await vk.baseDao.updateById({
- dbName: "uni-id-users",
- id: order.uid,
- dataJson: {
- qybNum: _.inc(qyb.getNum)
- }
- });
- await vk.baseDao.updateById({
- dbName: "jjx-order",
- id: order._id,
- dataJson: {
- remainNum
- }
- });
- }
- }
- if (!order) {
- console.log('order not found')
- order = await vk.baseDao.findByWhereJson({
- dbName: "jjx-order-pay-list",
- whereJson: {
- orderNo
- }
- });
- console.log('pay list order data', order)
- if (order && !order.sellerConfirm && (data.billStatus === 'PAID' || data.status === 'TRADE_SUCCESS')) {
- await vk.baseDao.updateById({
- dbName: "jjx-order-pay-list",
- id: order._id,
- dataJson: {
- // sellerConfirm: true,
- hasPaid: true,
- paidAt: vk.pubfn.timeFormat(new Date(), "yyyy-MM-dd hh:mm:ss"),
- // confirmAt: vk.pubfn.timeFormat(new Date(), "yyyy-MM-dd hh:mm:ss"),
- }
- });
- }
- }
- // 业务逻辑结束-----------------------------------------------------------
- return res;
- },
- /**
- * 模板函数
- * @url client/payment.test 前端调用的url参数地址
- */
- test: async function (data) {
- let res = {code: 0, msg: ''};
- let {uid} = this.getClientInfo(); // 获取客户端信息
- // 业务逻辑开始-----------------------------------------------------------
+ } else if (order.type === 13) {
+ const goods = await vk.baseDao.findByWhereJson({
+ dbName: "jjx-order-goods",
+ whereJson: {
+ orderId: order._id,
+ }
+ });
+ const qyb = await vk.baseDao.findByWhereJson({
+ dbName: "jjx-qyb",
+ whereJson: {
+ _id: goods.goodsId
+ }
+ });
+ const user = await vk.baseDao.findByWhereJson({
+ dbName: "uni-id-users",
+ whereJson: {
+ _id: order.uid,
+ }
+ });
+ const remainNum = user.qybNum + qyb.getNum;
+ await vk.baseDao.updateById({
+ dbName: "uni-id-users",
+ id: order.uid,
+ dataJson: {
+ qybNum: _.inc(qyb.getNum)
+ }
+ });
+ await vk.baseDao.updateById({
+ dbName: "jjx-order",
+ id: order._id,
+ dataJson: {
+ remainNum
+ }
+ });
+ } else if (order.type === 20) {
+ // 积分充值到账
+ const goods = await vk.baseDao.findByWhereJson({
+ dbName: "jjx-order-goods",
+ whereJson: {
+ orderId: order._id
+ }
+ });
+ if (goods) {
+ const points = parseInt(goods.goodsName.replace('积分充值_', '').replace('积分', ''));
+ if (points > 0) {
+ // 增加积分余额
+ const userPoints = await vk.baseDao.findByWhereJson({
+ dbName: "jjx-user-points",
+ whereJson: {
+ _id: order.uid
+ }
+ });
+ if (userPoints) {
+ await vk.baseDao.updateById({
+ dbName: "jjx-user-points",
+ id: order.uid,
+ dataJson: {
+ balance: _.inc(points),
+ total_earned: _.inc(points),
+ update_time: new Date()
+ }
+ });
+ } else {
+ await vk.baseDao.add({
+ dbName: "jjx-user-points",
+ dataJson: {
+ _id: order.uid,
+ balance: points,
+ total_earned: points,
+ total_spent: 0,
+ gift: 0,
+ update_time: new Date()
+ }
+ });
+ }
+ // 记录流水
+ const updatedPoints = await vk.baseDao.findById({
+ dbName: "jjx-user-points",
+ id: order.uid
+ });
+ await vk.baseDao.add({
+ dbName: "jjx-points-log",
+ dataJson: {
+ user_id: order.uid,
+ type: 'earn',
+ amount: points,
+ balance_after: updatedPoints ? updatedPoints.balance : points,
+ reason: '积分充值(' + order.offAmount + '元)',
+ biz_type: 'recharge',
+ biz_id: order._id,
+ _add_time: new Date()
+ }
+ });
+ }
+ }
+ await vk.baseDao.updateById({
+ dbName: "jjx-order",
+ id: order._id,
+ dataJson: {
+ orderStatus: 4
+ }
+ });
+ }
+ }
+ if (!order) {
+ console.log('order not found')
+ order = await vk.baseDao.findByWhereJson({
+ dbName: "jjx-order-pay-list",
+ whereJson: {
+ orderNo
+ }
+ });
+ console.log('pay list order data', order)
+ if (order && !order.sellerConfirm && (data.billStatus === 'PAID' || data.status ===
+ 'TRADE_SUCCESS')) {
+ await vk.baseDao.updateById({
+ dbName: "jjx-order-pay-list",
+ id: order._id,
+ dataJson: {
+ // sellerConfirm: true,
+ hasPaid: true,
+ paidAt: vk.pubfn.timeFormat(new Date(), "yyyy-MM-dd hh:mm:ss"),
+ // confirmAt: vk.pubfn.timeFormat(new Date(), "yyyy-MM-dd hh:mm:ss"),
+ }
+ });
+ }
+ }
+ // 业务逻辑结束-----------------------------------------------------------
+ return res;
+ },
+ /**
+ * 模板函数
+ * @url client/payment.test 前端调用的url参数地址
+ */
+ test: async function(data) {
+ let res = {
+ code: 0,
+ msg: ''
+ };
+ let {
+ uid
+ } = this.getClientInfo(); // 获取客户端信息
+ // 业务逻辑开始-----------------------------------------------------------
- // 业务逻辑结束-----------------------------------------------------------
- return res;
- },
+ // 业务逻辑结束-----------------------------------------------------------
+ return res;
+ },
};
-module.exports = cloudObject;
+module.exports = cloudObject;
\ No newline at end of file
diff --git a/uniCloud-tcb/cloudfunctions/router/service/client/points/kh/createOrder.js b/uniCloud-tcb/cloudfunctions/router/service/client/points/kh/createOrder.js
new file mode 100644
index 0000000..576481a
--- /dev/null
+++ b/uniCloud-tcb/cloudfunctions/router/service/client/points/kh/createOrder.js
@@ -0,0 +1,39 @@
+//创建积分充值订单
+module.exports = {
+ main: async (event) => {
+ let { data = {}, userInfo, util } = event;
+ let { vk, db, _ } = util;
+ const { points, price } = data; // points=积分数量 price=价格(分→元)
+
+ const amount = price / 100; // 分转元
+ const offAmount = amount;
+ const orderId = await vk.baseDao.add({
+ dbName: "jjx-order",
+ dataJson: {
+ orderNo: vk.pubfn.createOrderNo("39XU", 20),
+ uid: userInfo.uid,
+ orderStatus: 0,
+ type: 20, // 20=积分充值
+ amount,
+ offAmount,
+ saveAmount: 0,
+ offRate: 100,
+ }
+ });
+
+ await vk.baseDao.add({
+ dbName: "jjx-order-goods",
+ dataJson: {
+ orderId,
+ goodsId: 'points_recharge',
+ num: 1,
+ goodsName: `积分充值_${points}积分`,
+ goodsPrice: amount,
+ goodsCover: null,
+ }
+ });
+
+ return { code: 0, msg: 'ok', data: orderId };
+ }
+};
+
diff --git a/uniCloud-tcb/cloudfunctions/router/service/client/points/kh/deduct.js b/uniCloud-tcb/cloudfunctions/router/service/client/points/kh/deduct.js
new file mode 100644
index 0000000..d91c361
--- /dev/null
+++ b/uniCloud-tcb/cloudfunctions/router/service/client/points/kh/deduct.js
@@ -0,0 +1,47 @@
+//扣积分(合同审查用)
+module.exports = {
+ main: async (event) => {
+ let { data = {}, userInfo, util } = event;
+ let { db, _ } = util;
+ const { amount, reason, biz_type, biz_id } = data;
+
+ if (!amount || amount <= 0) {
+ return { code: -1, msg: '扣减数量无效' };
+ }
+
+ // 查余额
+ const userRes = await db.collection('jjx-user-points').doc(userInfo.uid).get();
+ if (!userRes.data.length) {
+ return { code: -1, msg: '积分账户不存在,请先获取积分' };
+ }
+
+ const currentBalance = userRes.data[0].balance;
+ if (currentBalance < amount) {
+ return { code: -1, msg: '积分不足', data: { balance: currentBalance, needed: amount } };
+ }
+
+ const newBalance = currentBalance - amount;
+
+ // 扣减余额
+ await db.collection('jjx-user-points').doc(userInfo.uid).update({
+ balance: newBalance,
+ total_spent: _.inc(amount),
+ update_time: new Date()
+ });
+
+ // 记录流水
+ await db.collection('jjx-points-log').add({
+ user_id: userInfo.uid,
+ type: 'spend',
+ amount: -amount,
+ balance_after: newBalance,
+ reason: reason || '积分消耗',
+ biz_type: biz_type || 'other',
+ biz_id: biz_id || '',
+ _add_time: new Date()
+ });
+
+ return { code: 0, msg: 'ok', data: { balance: newBalance } };
+ }
+};
+
diff --git a/uniCloud-tcb/cloudfunctions/router/service/client/points/kh/getBalance.js b/uniCloud-tcb/cloudfunctions/router/service/client/points/kh/getBalance.js
new file mode 100644
index 0000000..7d39a54
--- /dev/null
+++ b/uniCloud-tcb/cloudfunctions/router/service/client/points/kh/getBalance.js
@@ -0,0 +1,37 @@
+// 获取积分余额
+module.exports = {
+ main: async (event) => {
+ let { data = {}, userInfo, util } = event;
+ let { db, _ } = util;
+ //默认积分500
+ let amount = 500;
+
+ const res = await db.collection('jjx-user-points').doc(userInfo.uid).get();
+
+ if (res.data.length === 0) {
+ // 新用户,自动赠送 500 积分
+ await db.collection('jjx-user-points').add({
+ _id: userInfo.uid,
+ balance: amount,
+ total_earned: amount,
+ total_spent: 0,
+ gift: amount,
+ update_time: new Date()
+ });
+
+ await db.collection('jjx-points-log').add({
+ user_id: userInfo.uid,
+ type: 'earn',
+ amount: amount,
+ balance_after: amount,
+ reason: '新用户注册赠送',
+ biz_type: 'gift',
+ _add_time: new Date()
+ });
+
+ return { code: 0, msg: 'ok', data: { balance: amount, total_earned: amount, total_spent: 0, gift: amount } };
+ }
+
+ return { code: 0, msg: 'ok', data: res.data[0] };
+ }
+};
diff --git a/uniCloud-tcb/cloudfunctions/router/service/client/points/kh/getLog.js b/uniCloud-tcb/cloudfunctions/router/service/client/points/kh/getLog.js
new file mode 100644
index 0000000..b9d7e61
--- /dev/null
+++ b/uniCloud-tcb/cloudfunctions/router/service/client/points/kh/getLog.js
@@ -0,0 +1,40 @@
+//查询积分流水
+module.exports = {
+ main: async (event) => {
+ let { data = {}, userInfo, util } = event;
+ let { vk, db, _ } = util;
+ const { pageIndex = 1, pageSize = 50, biz_type } = data;
+
+ console.log('getLog uid:', userInfo.uid);
+
+ // 先查全部看看有没有数据
+ const allRes = await db.collection('jjx-points-log').limit(5).get();
+ console.log('getLog all data count:', allRes.data.length);
+ if (allRes.data.length > 0) {
+ console.log('getLog first record:', JSON.stringify(allRes.data[0]));
+ }
+
+ const where = { user_id: userInfo.uid };
+ if (biz_type) where.biz_type = biz_type;
+
+ const countRes = await db.collection('jjx-points-log').where(where).count();
+ console.log('getLog filtered count:', countRes.total);
+
+ const listRes = await db.collection('jjx-points-log')
+ .where(where)
+ .orderBy('_add_time', 'desc')
+ .skip((pageIndex - 1) * pageSize)
+ .limit(pageSize)
+ .get();
+
+ return {
+ code: 0,
+ msg: 'ok',
+ data: {
+ total: countRes.total,
+ rows: listRes.data
+ }
+ };
+ }
+};
+
diff --git a/uniCloud-tcb/cloudfunctions/router/service/client/points/kh/recharge.js b/uniCloud-tcb/cloudfunctions/router/service/client/points/kh/recharge.js
new file mode 100644
index 0000000..0a10527
--- /dev/null
+++ b/uniCloud-tcb/cloudfunctions/router/service/client/points/kh/recharge.js
@@ -0,0 +1,55 @@
+//积分充值
+module.exports = {
+ main: async (event) => {
+ let { data = {}, userInfo, util } = event;
+ let { db, _ } = util;
+ const { order_id } = data;
+
+ if (!order_id) {
+ return { code: -1, msg: '缺少订单ID' };
+ }
+
+ // 查订单
+ const orderRes = await db.collection('vk-pay-orders').doc(order_id).get();
+ if (!orderRes.data.length) {
+ return { code: -1, msg: '订单不存在' };
+ }
+
+ const order = orderRes.data[0];
+ if (order.status !== 1) {
+ return { code: -1, msg: '订单未支付' };
+ }
+
+ // 解析充值积分数量
+ const attachedData = JSON.parse(order.attached_data || '{}');
+ const points = attachedData.points;
+ if (!points) {
+ return { code: -1, msg: '订单数据异常' };
+ }
+
+ // 增加余额
+ await db.collection('jjx-user-points').doc(userInfo.uid).update({
+ balance: _.inc(points),
+ total_earned: _.inc(points),
+ update_time: new Date()
+ });
+
+ // 查最新余额
+ const userRes = await db.collection('jjx-user-points').doc(userInfo.uid).get();
+ const balance = userRes.data[0].balance;
+
+ // 记录流水
+ await db.collection('jjx-points-log').add({
+ user_id: userInfo.uid,
+ type: 'earn',
+ amount: points,
+ balance_after: balance,
+ reason: '积分充值(' + (order.total_fee / 100) + '元)',
+ biz_type: 'recharge',
+ biz_id: order_id,
+ _add_time: new Date()
+ });
+
+ return { code: 0, msg: 'ok', data: { balance, points_added: points } };
+ }
+};
diff --git a/uniCloud-tcb/cloudfunctions/router/service/common/公共逻辑(公共接口).md b/uniCloud-tcb/cloudfunctions/router/service/common/公共逻辑(公共接口).md
new file mode 100644
index 0000000..e69de29
diff --git a/uniCloud-tcb/cloudfunctions/router/service/template/test/sys/test.js b/uniCloud-tcb/cloudfunctions/router/service/template/test/sys/test.js
new file mode 100644
index 0000000..0fec407
--- /dev/null
+++ b/uniCloud-tcb/cloudfunctions/router/service/template/test/sys/test.js
@@ -0,0 +1,20 @@
+module.exports = {
+ /**
+ * 此函数名称
+ * @url template/test/sys/test 前端调用的url参数地址
+ * data 请求参数 说明
+ * res 返回参数说明
+ * @params {Number} code 错误码,0表示成功
+ * @params {String} msg 详细信息
+ */
+ main: async (event) => {
+ let { data = {}, userInfo, util, originalParam } = event;
+ let { uniID, config, pubFun, vk , db, _ } = util;
+ let { uid } = data;
+ let res = { code : 0, msg : '' };
+ // 业务逻辑开始-----------------------------------------------------------
+ res.msg = "ok";
+ // 业务逻辑结束-----------------------------------------------------------
+ return res;
+ }
+}
diff --git a/uniCloud-tcb/database/jjx-points-log.schema.json b/uniCloud-tcb/database/jjx-points-log.schema.json
new file mode 100644
index 0000000..20e7ebd
--- /dev/null
+++ b/uniCloud-tcb/database/jjx-points-log.schema.json
@@ -0,0 +1 @@
+{"bsonType":"object","required":[],"properties":{"_id":{"description":"记录ID,系统自动生成","bsonType":"string"},"user_id":{"bsonType":"string","title":"用户ID","description":"关联 uni-id-users._id","foreignKey":"uni-id-users._id"},"type":{"bsonType":"string","title":"变动类型","description":"earn=获得 spend=消耗","enum":[{"text":"获得","value":"earn"},{"text":"消耗","value":"spend"}]},"amount":{"bsonType":"number","title":"变动数量","description":"正数为获得,负数为消耗"},"balance_after":{"bsonType":"number","title":"变动后余额","description":"本次变动后的积分余额"},"reason":{"bsonType":"string","title":"变动原因","description":"如:新用户赠送、积分充值、合同审查消耗","trim":"both"},"biz_type":{"bsonType":"string","title":"业务类型","description":"关联的业务类型","enum":[{"text":"赠送","value":"gift"},{"text":"充值","value":"recharge"},{"text":"合同审查","value":"contract_review"},{"text":"签到","value":"checkin"},{"text":"其他","value":"other"}]},"biz_id":{"bsonType":"string","title":"业务ID","description":"关联的业务记录ID(如订单ID)"},"_add_time":{"bsonType":"date","title":"创建时间","description":"记录创建时间,系统自动填充"}}}
\ No newline at end of file
diff --git a/uniCloud-tcb/database/jjx-review-records.schema.json b/uniCloud-tcb/database/jjx-review-records.schema.json
new file mode 100644
index 0000000..6bbfb24
--- /dev/null
+++ b/uniCloud-tcb/database/jjx-review-records.schema.json
@@ -0,0 +1 @@
+{"bsonType":"object","required":[],"properties":{"_id":{"description":"记录ID,系统自动生成","bsonType":"string"},"user_id":{"bsonType":"string","title":"用户ID","description":"关联 uni-id-users._id","foreignKey":"uni-id-users._id"},"file_name":{"bsonType":"string","title":"文件名","description":"上传的合同文件名","trim":"both"},"file_size":{"bsonType":"number","title":"文件大小","description":"文件大小(字节)"},"file_id":{"bsonType":"string","title":"扣子文件ID","description":"上传到扣子后的 file_id"},"file_url":{"bsonType":"string","title":"文件预览地址","description":"uniCloud 云存储临时链接,用于查看\/下载原文件"},"html_url":{"bsonType":"string","title":"HTML报告地址","description":"精美 HTML 审查报告链接"},"pdf_url":{"bsonType":"string","title":"PDF报告地址","description":"PDF 审查报告下载链接"},"contract_info":{"bsonType":"object","title":"合同结构化信息","description":"AI 返回的合同名称、风险等级、风险统计等"},"chat_id":{"bsonType":"string","title":"对话ID","description":"扣子对话 chat_id"},"conversation_id":{"bsonType":"string","title":"会话ID","description":"扣子会话 conversation_id"},"status":{"bsonType":"string","title":"审查状态","description":"uploaded=待审查 reviewing=等待处理 processing=审查中 completed=已完成 failed=失败","enum":[{"text":"待审查","value":"uploaded"},{"text":"等待处理","value":"reviewing"},{"text":"审查中","value":"processing"},{"text":"已完成","value":"completed"},{"text":"失败","value":"failed"}]},"result":{"bsonType":"string","title":"审查结果","description":"AI 返回的审查结果内容"},"points_used":{"bsonType":"number","title":"消耗积分","description":"本次审查消耗的积分数量(token_count \/ 100)"},"token_count":{"bsonType":"number","title":"Token消耗","description":"扣子返回的 token 总消耗"},"_add_time":{"bsonType":"date","title":"创建时间","description":"记录创建时间"},"update_time":{"bsonType":"date","title":"更新时间","description":"最后更新时间"}}}
\ No newline at end of file
diff --git a/uniCloud-tcb/database/jjx-user-points.schema.json b/uniCloud-tcb/database/jjx-user-points.schema.json
new file mode 100644
index 0000000..5d77842
--- /dev/null
+++ b/uniCloud-tcb/database/jjx-user-points.schema.json
@@ -0,0 +1 @@
+{"bsonType":"object","required":[],"properties":{"_id":{"description":"用户ID(与 uni-id-users._id 对应)","bsonType":"string"},"balance":{"bsonType":"number","title":"积分余额","description":"当前可用积分","defaultValue":0,"minimum":0},"total_earned":{"bsonType":"number","title":"累计获得","description":"累计获得的积分总数","defaultValue":0},"total_spent":{"bsonType":"number","title":"累计消耗","description":"累计消耗的积分总数","defaultValue":0},"gift":{"bsonType":"number","title":"赠送积分","description":"新用户赠送的积分数量","defaultValue":500},"update_time":{"bsonType":"date","title":"更新时间","description":"最后更新时间"}}}
\ No newline at end of file