|
@@ -1,20 +1,20 @@
|
|
|
-const db = require('../../plugin/DataBase/db')
|
|
|
|
|
-const { axiosWithQgOutbound } = require('../Lepao/qgOutboundAxios')
|
|
|
|
|
-const Logger = require('../Logger')
|
|
|
|
|
-const path = require('path')
|
|
|
|
|
-const QxsBookList = require('../../apis/QuXuanShu/GetBookList').GetBookList
|
|
|
|
|
-const EmailTemplate = require('../../plugin/Email/emailTemplate')
|
|
|
|
|
-
|
|
|
|
|
-const ASSISTANT_UUID = 'e4fe0277-0b1a-41a1-b25f-8b6e4cec3281'
|
|
|
|
|
-
|
|
|
|
|
-function required(values) {
|
|
|
|
|
- return values.some(v => v === '' || v === null || v === undefined)
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-function isProvided(value) {
|
|
|
|
|
- return value !== '' && value !== null && value !== undefined
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
|
|
+const db = require('../../plugin/DataBase/db')
|
|
|
|
|
+const { axiosWithQgOutbound } = require('../Lepao/qgOutboundAxios')
|
|
|
|
|
+const Logger = require('../Logger')
|
|
|
|
|
+const path = require('path')
|
|
|
|
|
+const QxsBookList = require('../../apis/QuXuanShu/GetBookList').GetBookList
|
|
|
|
|
+const EmailTemplate = require('../../plugin/Email/emailTemplate')
|
|
|
|
|
+
|
|
|
|
|
+const ASSISTANT_UUID = 'e4fe0277-0b1a-41a1-b25f-8b6e4cec3281'
|
|
|
|
|
+
|
|
|
|
|
+function required(values) {
|
|
|
|
|
+ return values.some(v => v === '' || v === null || v === undefined)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function isProvided(value) {
|
|
|
|
|
+ return value !== '' && value !== null && value !== undefined
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
function pageArgs({ current = 1, pagesize = 10 } = {}, max = 20) {
|
|
function pageArgs({ current = 1, pagesize = 10 } = {}, max = 20) {
|
|
|
const c = Math.max(1, Number.parseInt(current, 10) || 1)
|
|
const c = Math.max(1, Number.parseInt(current, 10) || 1)
|
|
|
const p = Math.min(Math.max(1, Number.parseInt(pagesize, 10) || 10), max)
|
|
const p = Math.min(Math.max(1, Number.parseInt(pagesize, 10) || 10), max)
|
|
@@ -65,52 +65,58 @@ function formatBeijingTime(value) {
|
|
|
function parseJson(value, fallback = []) {
|
|
function parseJson(value, fallback = []) {
|
|
|
if (!value) return fallback
|
|
if (!value) return fallback
|
|
|
if (Array.isArray(value)) return value
|
|
if (Array.isArray(value)) return value
|
|
|
- try {
|
|
|
|
|
- const parsed = JSON.parse(value)
|
|
|
|
|
- return parsed == null ? fallback : parsed
|
|
|
|
|
- } catch (_) {
|
|
|
|
|
- return fallback
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-function stateText(value, map) {
|
|
|
|
|
- return map[value] || String(value)
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
|
|
+ try {
|
|
|
|
|
+ const parsed = JSON.parse(value)
|
|
|
|
|
+ return parsed == null ? fallback : parsed
|
|
|
|
|
+ } catch (_) {
|
|
|
|
|
+ return fallback
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function stateText(value, map) {
|
|
|
|
|
+ return map[value] || String(value)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
function normalizeText(value, max = 2000) {
|
|
function normalizeText(value, max = 2000) {
|
|
|
return String(value || '').trim().slice(0, max)
|
|
return String(value || '').trim().slice(0, max)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-function normalizeRunArea(area) {
|
|
|
|
|
- if (!isProvided(area)) return ''
|
|
|
|
|
- const value = String(area).trim()
|
|
|
|
|
- if (value === '随机分配') return ''
|
|
|
|
|
- const allowed = new Set([
|
|
|
|
|
- '兰花湖校区跑区',
|
|
|
|
|
- '主校区北跑区',
|
|
|
|
|
- '主校区南跑区',
|
|
|
|
|
- '重庆工商大学茶园校区',
|
|
|
|
|
- ''
|
|
|
|
|
- ])
|
|
|
|
|
- return allowed.has(value) ? value : null
|
|
|
|
|
|
|
+function normalizeNumber(value) {
|
|
|
|
|
+ if (!isProvided(value)) return null
|
|
|
|
|
+ const numberValue = Number(value)
|
|
|
|
|
+ return Number.isFinite(numberValue) ? numberValue : null
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+function normalizeRunArea(area) {
|
|
|
|
|
+ if (!isProvided(area)) return ''
|
|
|
|
|
+ const value = String(area).trim()
|
|
|
|
|
+ if (value === '随机分配') return ''
|
|
|
|
|
+ const allowed = new Set([
|
|
|
|
|
+ '兰花湖校区跑区',
|
|
|
|
|
+ '主校区北跑区',
|
|
|
|
|
+ '主校区南跑区',
|
|
|
|
|
+ '重庆工商大学茶园校区',
|
|
|
|
|
+ ''
|
|
|
|
|
+ ])
|
|
|
|
|
+ return allowed.has(value) ? value : null
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
function mapLepaoAccount(row) {
|
|
function mapLepaoAccount(row) {
|
|
|
- return {
|
|
|
|
|
- student_num: row.student_num,
|
|
|
|
|
- name: row.name || '',
|
|
|
|
|
- state: stateText(row.state, { 0: '需登录', 1: '正常', 2: '异常' }),
|
|
|
|
|
- area: row.area || '随机分配',
|
|
|
|
|
- auto_run: row.auto_run === 1,
|
|
|
|
|
- auto_time: row.auto_time,
|
|
|
|
|
- today_auto_time: row.today_auto_time,
|
|
|
|
|
- target_count: row.target_count,
|
|
|
|
|
- total_num: row.total_num,
|
|
|
|
|
- term_num: row.term_num,
|
|
|
|
|
- auto_day: parseJson(row.auto_day, []),
|
|
|
|
|
- notice_type: row.notice_type,
|
|
|
|
|
- email: row.email || '',
|
|
|
|
|
- notes: row.notes || ''
|
|
|
|
|
|
|
+ return {
|
|
|
|
|
+ student_num: row.student_num,
|
|
|
|
|
+ name: row.name || '',
|
|
|
|
|
+ state: stateText(row.state, { 0: '需登录', 1: '正常', 2: '异常' }),
|
|
|
|
|
+ area: row.area || '随机分配',
|
|
|
|
|
+ auto_run: row.auto_run === 1,
|
|
|
|
|
+ auto_time: row.auto_time,
|
|
|
|
|
+ today_auto_time: row.today_auto_time,
|
|
|
|
|
+ target_count: row.target_count,
|
|
|
|
|
+ total_num: row.total_num,
|
|
|
|
|
+ term_num: row.term_num,
|
|
|
|
|
+ auto_day: parseJson(row.auto_day, []),
|
|
|
|
|
+ notice_type: row.notice_type,
|
|
|
|
|
+ email: row.email || '',
|
|
|
|
|
+ notes: row.notes || ''
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -162,38 +168,38 @@ function mapPowerRecord(row) {
|
|
|
change_time: formatBeijingTime(row.change_time)
|
|
change_time: formatBeijingTime(row.change_time)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
-class AiAssistantMcp {
|
|
|
|
|
- constructor() {
|
|
|
|
|
- this.logger = new Logger(path.join(__dirname, '../../logs/AiAssistantMcp.log'), 'INFO')
|
|
|
|
|
- this.emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
|
|
|
|
|
- this.banEmailList = ['icloud.com']
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- async list_lepao_accounts(args) {
|
|
|
|
|
- const { sender, student_num, name, area, state, auto_run, current, pagesize } = args
|
|
|
|
|
- if (required([sender])) return '缺少 sender'
|
|
|
|
|
- const page = pageArgs({ current, pagesize })
|
|
|
|
|
- const where = ['create_user = ?']
|
|
|
|
|
- const params = [sender]
|
|
|
|
|
- if (student_num) {
|
|
|
|
|
- where.push('student_num LIKE ?')
|
|
|
|
|
- params.push(`%${student_num}%`)
|
|
|
|
|
- }
|
|
|
|
|
- if (name) {
|
|
|
|
|
- where.push('name LIKE ?')
|
|
|
|
|
- params.push(`%${name}%`)
|
|
|
|
|
- }
|
|
|
|
|
- if (area !== undefined && area !== null && area !== '') {
|
|
|
|
|
- const normalizedArea = normalizeRunArea(area)
|
|
|
|
|
- if (normalizedArea === null) return '跑区不合法'
|
|
|
|
|
- where.push('area = ?')
|
|
|
|
|
- params.push(normalizedArea)
|
|
|
|
|
- }
|
|
|
|
|
- if (state !== undefined && state !== null && state !== '' && Number(state) !== -1) {
|
|
|
|
|
- where.push('state = ?')
|
|
|
|
|
- params.push(state)
|
|
|
|
|
- }
|
|
|
|
|
|
|
+
|
|
|
|
|
+class AiAssistantMcp {
|
|
|
|
|
+ constructor() {
|
|
|
|
|
+ this.logger = new Logger(path.join(__dirname, '../../logs/AiAssistantMcp.log'), 'INFO')
|
|
|
|
|
+ this.emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
|
|
|
|
|
+ this.banEmailList = ['icloud.com']
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async list_lepao_accounts(args) {
|
|
|
|
|
+ const { sender, student_num, name, area, state, auto_run, current, pagesize } = args
|
|
|
|
|
+ if (required([sender])) return '缺少 sender'
|
|
|
|
|
+ const page = pageArgs({ current, pagesize })
|
|
|
|
|
+ const where = ['create_user = ?']
|
|
|
|
|
+ const params = [sender]
|
|
|
|
|
+ if (student_num) {
|
|
|
|
|
+ where.push('student_num LIKE ?')
|
|
|
|
|
+ params.push(`%${student_num}%`)
|
|
|
|
|
+ }
|
|
|
|
|
+ if (name) {
|
|
|
|
|
+ where.push('name LIKE ?')
|
|
|
|
|
+ params.push(`%${name}%`)
|
|
|
|
|
+ }
|
|
|
|
|
+ if (area !== undefined && area !== null && area !== '') {
|
|
|
|
|
+ const normalizedArea = normalizeRunArea(area)
|
|
|
|
|
+ if (normalizedArea === null) return '跑区不合法'
|
|
|
|
|
+ where.push('area = ?')
|
|
|
|
|
+ params.push(normalizedArea)
|
|
|
|
|
+ }
|
|
|
|
|
+ if (state !== undefined && state !== null && state !== '' && Number(state) !== -1) {
|
|
|
|
|
+ where.push('state = ?')
|
|
|
|
|
+ params.push(state)
|
|
|
|
|
+ }
|
|
|
if (auto_run !== undefined && auto_run !== null && auto_run !== '') {
|
|
if (auto_run !== undefined && auto_run !== null && auto_run !== '') {
|
|
|
where.push('auto_run = ?')
|
|
where.push('auto_run = ?')
|
|
|
params.push(Number(auto_run))
|
|
params.push(Number(auto_run))
|
|
@@ -207,38 +213,38 @@ class AiAssistantMcp {
|
|
|
const rows = await db.query(
|
|
const rows = await db.query(
|
|
|
`SELECT student_num, name, state, area, auto_run, auto_time, today_auto_time,
|
|
`SELECT student_num, name, state, area, auto_run, auto_time, today_auto_time,
|
|
|
target_count, total_num, term_num, auto_day, notice_type, email, notes
|
|
target_count, total_num, term_num, auto_day, notice_type, email, notes
|
|
|
- FROM lepao_account
|
|
|
|
|
- WHERE ${where.join(' AND ')}
|
|
|
|
|
- ORDER BY update_time DESC
|
|
|
|
|
|
|
+ FROM lepao_account
|
|
|
|
|
+ WHERE ${where.join(' AND ')}
|
|
|
|
|
+ ORDER BY update_time DESC
|
|
|
LIMIT ? OFFSET ?`,
|
|
LIMIT ? OFFSET ?`,
|
|
|
[...params, String(page.pagesize), String(page.offset)]
|
|
[...params, String(page.pagesize), String(page.offset)]
|
|
|
)
|
|
)
|
|
|
if (!rows || !totalRows) return '查询失败'
|
|
if (!rows || !totalRows) return '查询失败'
|
|
|
return asJson({ list: rows.map(mapLepaoAccount), pagination: pagination(page, totalRows) })
|
|
return asJson({ list: rows.map(mapLepaoAccount), pagination: pagination(page, totalRows) })
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- async list_lepao_records(args) {
|
|
|
|
|
- const { sender, lepao_account, name, start_time, end_time, result, current, pagesize } = args
|
|
|
|
|
- if (required([sender])) return '缺少 sender'
|
|
|
|
|
- const page = pageArgs({ current, pagesize })
|
|
|
|
|
- const where = ['(r.uuid = ? OR a.create_user = ?)']
|
|
|
|
|
- const params = [sender, sender]
|
|
|
|
|
- if (lepao_account) {
|
|
|
|
|
- where.push('r.lepao_account LIKE ?')
|
|
|
|
|
- params.push(`%${lepao_account}%`)
|
|
|
|
|
- }
|
|
|
|
|
- if (name) {
|
|
|
|
|
- where.push('a.name LIKE ?')
|
|
|
|
|
- params.push(`%${name}%`)
|
|
|
|
|
- }
|
|
|
|
|
- if (result) {
|
|
|
|
|
- where.push('r.result LIKE ?')
|
|
|
|
|
- params.push(`%${result}%`)
|
|
|
|
|
- }
|
|
|
|
|
- if (start_time) {
|
|
|
|
|
- where.push('r.time >= ?')
|
|
|
|
|
- params.push(start_time)
|
|
|
|
|
- }
|
|
|
|
|
|
|
+
|
|
|
|
|
+ async list_lepao_records(args) {
|
|
|
|
|
+ const { sender, lepao_account, name, start_time, end_time, result, current, pagesize } = args
|
|
|
|
|
+ if (required([sender])) return '缺少 sender'
|
|
|
|
|
+ const page = pageArgs({ current, pagesize })
|
|
|
|
|
+ const where = ['(r.uuid = ? OR a.create_user = ?)']
|
|
|
|
|
+ const params = [sender, sender]
|
|
|
|
|
+ if (lepao_account) {
|
|
|
|
|
+ where.push('r.lepao_account LIKE ?')
|
|
|
|
|
+ params.push(`%${lepao_account}%`)
|
|
|
|
|
+ }
|
|
|
|
|
+ if (name) {
|
|
|
|
|
+ where.push('a.name LIKE ?')
|
|
|
|
|
+ params.push(`%${name}%`)
|
|
|
|
|
+ }
|
|
|
|
|
+ if (result) {
|
|
|
|
|
+ where.push('r.result LIKE ?')
|
|
|
|
|
+ params.push(`%${result}%`)
|
|
|
|
|
+ }
|
|
|
|
|
+ if (start_time) {
|
|
|
|
|
+ where.push('r.time >= ?')
|
|
|
|
|
+ params.push(start_time)
|
|
|
|
|
+ }
|
|
|
if (end_time) {
|
|
if (end_time) {
|
|
|
where.push('r.time < ?')
|
|
where.push('r.time < ?')
|
|
|
params.push(end_time)
|
|
params.push(end_time)
|
|
@@ -253,34 +259,34 @@ class AiAssistantMcp {
|
|
|
const rows = await db.query(
|
|
const rows = await db.query(
|
|
|
`SELECT r.public_id, r.time, r.result, r.run_mode, r.lepao_account, a.name
|
|
`SELECT r.public_id, r.time, r.result, r.run_mode, r.lepao_account, a.name
|
|
|
FROM lepao_record r
|
|
FROM lepao_record r
|
|
|
- LEFT JOIN lepao_account a ON r.lepao_account = a.student_num
|
|
|
|
|
- WHERE ${where.join(' AND ')}
|
|
|
|
|
- ORDER BY r.id DESC
|
|
|
|
|
|
|
+ LEFT JOIN lepao_account a ON r.lepao_account = a.student_num
|
|
|
|
|
+ WHERE ${where.join(' AND ')}
|
|
|
|
|
+ ORDER BY r.id DESC
|
|
|
LIMIT ? OFFSET ?`,
|
|
LIMIT ? OFFSET ?`,
|
|
|
[...params, String(page.pagesize), String(page.offset)]
|
|
[...params, String(page.pagesize), String(page.offset)]
|
|
|
)
|
|
)
|
|
|
if (!rows || !totalRows) return '查询失败'
|
|
if (!rows || !totalRows) return '查询失败'
|
|
|
return asJson({ list: rows.map(mapLepaoRecord), pagination: pagination(page, totalRows) })
|
|
return asJson({ list: rows.map(mapLepaoRecord), pagination: pagination(page, totalRows) })
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- async list_lepao_count_ledger(args) {
|
|
|
|
|
- const { sender, biz_type, remark, start_time, end_time, current, pagesize } = args
|
|
|
|
|
- if (required([sender])) return '缺少 sender'
|
|
|
|
|
- const page = pageArgs({ current, pagesize })
|
|
|
|
|
- const where = ['user_uuid = ?']
|
|
|
|
|
- const params = [sender]
|
|
|
|
|
- if (biz_type) {
|
|
|
|
|
- where.push('biz_type = ?')
|
|
|
|
|
- params.push(biz_type)
|
|
|
|
|
- }
|
|
|
|
|
- if (remark) {
|
|
|
|
|
- where.push('remark LIKE ?')
|
|
|
|
|
- params.push(`%${remark}%`)
|
|
|
|
|
- }
|
|
|
|
|
- if (start_time) {
|
|
|
|
|
- where.push('created_at >= FROM_UNIXTIME(? / 1000)')
|
|
|
|
|
- params.push(start_time)
|
|
|
|
|
- }
|
|
|
|
|
|
|
+
|
|
|
|
|
+ async list_lepao_count_ledger(args) {
|
|
|
|
|
+ const { sender, biz_type, remark, start_time, end_time, current, pagesize } = args
|
|
|
|
|
+ if (required([sender])) return '缺少 sender'
|
|
|
|
|
+ const page = pageArgs({ current, pagesize })
|
|
|
|
|
+ const where = ['user_uuid = ?']
|
|
|
|
|
+ const params = [sender]
|
|
|
|
|
+ if (biz_type) {
|
|
|
|
|
+ where.push('biz_type = ?')
|
|
|
|
|
+ params.push(biz_type)
|
|
|
|
|
+ }
|
|
|
|
|
+ if (remark) {
|
|
|
|
|
+ where.push('remark LIKE ?')
|
|
|
|
|
+ params.push(`%${remark}%`)
|
|
|
|
|
+ }
|
|
|
|
|
+ if (start_time) {
|
|
|
|
|
+ where.push('created_at >= FROM_UNIXTIME(? / 1000)')
|
|
|
|
|
+ params.push(start_time)
|
|
|
|
|
+ }
|
|
|
if (end_time) {
|
|
if (end_time) {
|
|
|
where.push('created_at <= FROM_UNIXTIME(? / 1000)')
|
|
where.push('created_at <= FROM_UNIXTIME(? / 1000)')
|
|
|
params.push(end_time)
|
|
params.push(end_time)
|
|
@@ -294,25 +300,25 @@ class AiAssistantMcp {
|
|
|
const rows = await db.query(
|
|
const rows = await db.query(
|
|
|
`SELECT delta, balance_before, balance_after, biz_type, remark,
|
|
`SELECT delta, balance_before, balance_after, biz_type, remark,
|
|
|
UNIX_TIMESTAMP(created_at) * 1000 AS created_at
|
|
UNIX_TIMESTAMP(created_at) * 1000 AS created_at
|
|
|
- FROM lepao_count_ledger
|
|
|
|
|
- WHERE ${where.join(' AND ')}
|
|
|
|
|
- ORDER BY id DESC
|
|
|
|
|
|
|
+ FROM lepao_count_ledger
|
|
|
|
|
+ WHERE ${where.join(' AND ')}
|
|
|
|
|
+ ORDER BY id DESC
|
|
|
LIMIT ? OFFSET ?`,
|
|
LIMIT ? OFFSET ?`,
|
|
|
[...params, String(page.pagesize), String(page.offset)]
|
|
[...params, String(page.pagesize), String(page.offset)]
|
|
|
)
|
|
)
|
|
|
if (!rows || !totalRows) return '查询失败'
|
|
if (!rows || !totalRows) return '查询失败'
|
|
|
return asJson({ list: rows.map(mapCountLedger), pagination: pagination(page, totalRows) })
|
|
return asJson({ list: rows.map(mapCountLedger), pagination: pagination(page, totalRows) })
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- async list_goods(args) {
|
|
|
|
|
- const { keyword, category, current, pagesize } = args
|
|
|
|
|
- const page = pageArgs({ current, pagesize })
|
|
|
|
|
- const where = ['state = 1']
|
|
|
|
|
- const params = []
|
|
|
|
|
- if (keyword) {
|
|
|
|
|
- where.push('name LIKE ?')
|
|
|
|
|
- params.push(`%${keyword}%`)
|
|
|
|
|
- }
|
|
|
|
|
|
|
+
|
|
|
|
|
+ async list_goods(args) {
|
|
|
|
|
+ const { keyword, category, current, pagesize } = args
|
|
|
|
|
+ const page = pageArgs({ current, pagesize })
|
|
|
|
|
+ const where = ['state = 1']
|
|
|
|
|
+ const params = []
|
|
|
|
|
+ if (keyword) {
|
|
|
|
|
+ where.push('name LIKE ?')
|
|
|
|
|
+ params.push(`%${keyword}%`)
|
|
|
|
|
+ }
|
|
|
if (category) {
|
|
if (category) {
|
|
|
where.push('category = ?')
|
|
where.push('category = ?')
|
|
|
params.push(category)
|
|
params.push(category)
|
|
@@ -326,8 +332,8 @@ class AiAssistantMcp {
|
|
|
const rows = await db.query(
|
|
const rows = await db.query(
|
|
|
`SELECT name, price, num, \`limit\`, description, category, features
|
|
`SELECT name, price, num, \`limit\`, description, category, features
|
|
|
FROM goods
|
|
FROM goods
|
|
|
- WHERE ${where.join(' AND ')}
|
|
|
|
|
- ORDER BY id DESC
|
|
|
|
|
|
|
+ WHERE ${where.join(' AND ')}
|
|
|
|
|
+ ORDER BY id DESC
|
|
|
LIMIT ? OFFSET ?`,
|
|
LIMIT ? OFFSET ?`,
|
|
|
[...params, String(page.pagesize), String(page.offset)]
|
|
[...params, String(page.pagesize), String(page.offset)]
|
|
|
)
|
|
)
|
|
@@ -376,155 +382,183 @@ class AiAssistantMcp {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async save_lepao_account(args) {
|
|
async save_lepao_account(args) {
|
|
|
- const { sender, student_num } = args
|
|
|
|
|
- if (required([sender, student_num])) return '缺少 sender 或 student_num'
|
|
|
|
|
-
|
|
|
|
|
- const existRows = await db.query(
|
|
|
|
|
- `SELECT id, create_user, student_num, email, area, auto_time, auto_run, target_count, auto_day, notice_type, notes
|
|
|
|
|
|
|
+ const { sender, student_num } = args
|
|
|
|
|
+ if (required([sender, student_num])) return '缺少 sender 或 student_num'
|
|
|
|
|
+
|
|
|
|
|
+ const existRows = await db.query(
|
|
|
|
|
+ `SELECT id, create_user, student_num, email, area, auto_time, auto_run, target_count, total_num, auto_day, notice_type, notes
|
|
|
FROM lepao_account
|
|
FROM lepao_account
|
|
|
- WHERE student_num = ?
|
|
|
|
|
- LIMIT 1`,
|
|
|
|
|
- [student_num]
|
|
|
|
|
- )
|
|
|
|
|
- if (!existRows) return '查询账号失败'
|
|
|
|
|
- if (existRows.length > 0 && existRows[0].create_user && existRows[0].create_user !== sender) {
|
|
|
|
|
- return '该乐跑账号已被其他用户绑定'
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ WHERE student_num = ?
|
|
|
|
|
+ LIMIT 1`,
|
|
|
|
|
+ [student_num]
|
|
|
|
|
+ )
|
|
|
|
|
+ if (!existRows) return '查询账号失败'
|
|
|
|
|
+ if (existRows.length > 0 && existRows[0].create_user && existRows[0].create_user !== sender) {
|
|
|
|
|
+ return '该乐跑账号已被其他用户绑定'
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const existing = existRows[0] || {}
|
|
|
|
|
+ const targetCount = isProvided(args.target_count)
|
|
|
|
|
+ ? normalizeNumber(args.target_count)
|
|
|
|
|
+ : (existRows.length > 0 ? normalizeNumber(existing.target_count) : 30)
|
|
|
|
|
+ if (targetCount === null || targetCount < 0 || targetCount > 99) return '乐跑目标次数不在合法范围内'
|
|
|
|
|
+ args.target_count = targetCount
|
|
|
|
|
+ const noticeType = isProvided(args.notice_type) ? args.notice_type : (existing.notice_type || 'email')
|
|
|
|
|
+ const email = isProvided(args.email) ? String(args.email).trim() : (existing.email || '')
|
|
|
|
|
+ if (noticeType === 'email') {
|
|
|
|
|
+ if (!this.emailRegex.test(email)) return '邮箱格式不正确'
|
|
|
|
|
+ const emailDomain = email.split('@')[1].toLowerCase()
|
|
|
|
|
+ if (this.banEmailList.includes(emailDomain)) return `暂不支持使用 ${emailDomain} 域名的邮箱,请更换其他邮箱后重试`
|
|
|
|
|
+ } else if (email && !this.emailRegex.test(email)) {
|
|
|
|
|
+ return '邮箱格式不正确'
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const autoRun = isProvided(args.auto_run) ? Number(args.auto_run) : (existRows.length > 0 ? Number(existing.auto_run) : 1)
|
|
|
|
|
+ if (![0, 1].includes(autoRun)) return 'auto_run 只能为 0 或 1'
|
|
|
|
|
+ args.auto_run = autoRun
|
|
|
|
|
+
|
|
|
|
|
+ const autoDay = isProvided(args.auto_day)
|
|
|
|
|
+ ? args.auto_day
|
|
|
|
|
+ : (existRows.length > 0 ? parseJson(existing.auto_day, []) : [0, 1, 2, 3, 4, 5, 6])
|
|
|
|
|
+ if (autoRun === 1 && (!Array.isArray(autoDay) || !autoDay.every(v => Number.isInteger(v) && v >= 0 && v <= 6))) return '自动乐跑星期不合法'
|
|
|
|
|
+ if (isProvided(args.auto_day)) args.auto_day = autoDay
|
|
|
|
|
+
|
|
|
|
|
+ const totalNum = normalizeNumber(existing.total_num) || 0
|
|
|
|
|
+ if (autoRun === 1 && totalNum >= targetCount && targetCount !== 0) return '该账号累计跑步次数已达到预设目标次数,请尝试增大目标次数后再试'
|
|
|
|
|
|
|
|
const fields = []
|
|
const fields = []
|
|
|
- const params = []
|
|
|
|
|
- const mutable = [
|
|
|
|
|
- ['email', 'email'],
|
|
|
|
|
- ['auto_time', 'auto_time'],
|
|
|
|
|
- ['auto_run', 'auto_run'],
|
|
|
|
|
- ['target_count', 'target_count'],
|
|
|
|
|
- ['notice_type', 'notice_type'],
|
|
|
|
|
- ['notes', 'notes']
|
|
|
|
|
- ]
|
|
|
|
|
-
|
|
|
|
|
- if (isProvided(args.area)) {
|
|
|
|
|
- const area = normalizeRunArea(args.area)
|
|
|
|
|
- if (area === null) return '跑区不合法'
|
|
|
|
|
- fields.push('area = ?')
|
|
|
|
|
- params.push(area)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (isProvided(args.email) && !this.emailRegex.test(args.email)) return '邮箱格式不正确'
|
|
|
|
|
- if (isProvided(args.notice_type) && !['email', 'none', 'bot'].includes(args.notice_type)) return '通知方式不合法'
|
|
|
|
|
- if (isProvided(args.auto_time)) {
|
|
|
|
|
- const autoTime = Number(args.auto_time)
|
|
|
|
|
- if (!Number.isInteger(autoTime) || autoTime < -1 || autoTime > 23) return '自动乐跑时间不合法'
|
|
|
|
|
- args.auto_time = autoTime
|
|
|
|
|
- }
|
|
|
|
|
- if (isProvided(args.auto_run) && ![0, 1].includes(Number(args.auto_run))) return 'auto_run 只能为 0 或 1'
|
|
|
|
|
- if (isProvided(args.auto_day)) {
|
|
|
|
|
- if (!Array.isArray(args.auto_day) || !args.auto_day.every(v => Number.isInteger(v) && v >= 0 && v <= 6)) return '自动乐跑星期不合法'
|
|
|
|
|
- fields.push('auto_day = ?')
|
|
|
|
|
- params.push(JSON.stringify(args.auto_day))
|
|
|
|
|
- }
|
|
|
|
|
- for (const [argName, column] of mutable) {
|
|
|
|
|
- if (isProvided(args[argName])) {
|
|
|
|
|
- fields.push(`${column} = ?`)
|
|
|
|
|
- params.push(args[argName])
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const now = Date.now()
|
|
|
|
|
- let result
|
|
|
|
|
- if (existRows.length > 0) {
|
|
|
|
|
- if (fields.length === 0) return '没有需要修改的字段'
|
|
|
|
|
- fields.push('update_time = ?', 'create_user = ?')
|
|
|
|
|
- params.push(now, sender)
|
|
|
|
|
- result = await db.query(
|
|
|
|
|
- `UPDATE lepao_account SET ${fields.join(', ')} WHERE student_num = ? AND (create_user = ? OR create_user IS NULL)`,
|
|
|
|
|
- [...params, student_num, sender]
|
|
|
|
|
- )
|
|
|
|
|
- } else {
|
|
|
|
|
- const area = isProvided(args.area) ? normalizeRunArea(args.area) : ''
|
|
|
|
|
- if (area === null) return '跑区不合法'
|
|
|
|
|
- const autoDay = isProvided(args.auto_day) ? args.auto_day : [0, 1, 2, 3, 4, 5, 6]
|
|
|
|
|
- if (!Array.isArray(autoDay) || !autoDay.every(v => Number.isInteger(v) && v >= 0 && v <= 6)) return '自动乐跑星期不合法'
|
|
|
|
|
- const email = args.email || ''
|
|
|
|
|
- if (email && !this.emailRegex.test(email)) return '邮箱格式不正确'
|
|
|
|
|
- result = await db.query(
|
|
|
|
|
- `INSERT INTO lepao_account
|
|
|
|
|
- (student_num, email, area, auto_time, auto_run, target_count, create_user, create_time, update_time, notes, auto_day, notice_type)
|
|
|
|
|
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
|
|
|
- [
|
|
|
|
|
- student_num,
|
|
|
|
|
- email,
|
|
|
|
|
- area,
|
|
|
|
|
- isProvided(args.auto_time) ? Number(args.auto_time) : -1,
|
|
|
|
|
- isProvided(args.auto_run) ? Number(args.auto_run) : 1,
|
|
|
|
|
- isProvided(args.target_count) ? args.target_count : 30,
|
|
|
|
|
- sender,
|
|
|
|
|
- now,
|
|
|
|
|
- now,
|
|
|
|
|
- args.notes || '',
|
|
|
|
|
- JSON.stringify(autoDay),
|
|
|
|
|
- args.notice_type || 'email'
|
|
|
|
|
- ]
|
|
|
|
|
- )
|
|
|
|
|
- const bindCode = Math.random().toString(36).slice(2, 8).toUpperCase()
|
|
|
|
|
- await db.query('INSERT IGNORE INTO lepao_extra (student_num, bind_code) VALUES (?, ?)', [student_num, bindCode])
|
|
|
|
|
- }
|
|
|
|
|
- if (!result || result.affectedRows < 1) return '保存失败或无权修改'
|
|
|
|
|
- return asJson({ success: true, student_num })
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- async save_power_task(args) {
|
|
|
|
|
- const { sender, task_id, area, building, room } = args
|
|
|
|
|
- if (required([sender])) return '缺少 sender'
|
|
|
|
|
-
|
|
|
|
|
- const now = Date.now()
|
|
|
|
|
- let existing = []
|
|
|
|
|
- if (task_id) {
|
|
|
|
|
- existing = await db.query('SELECT id, area, building, room FROM power_task WHERE id = ? AND create_user = ? LIMIT 1', [task_id, sender])
|
|
|
|
|
- } else if ([area, building, room].every(isProvided)) {
|
|
|
|
|
- existing = await db.query(
|
|
|
|
|
- 'SELECT id, area, building, room FROM power_task WHERE create_user = ? AND area = ? AND building = ? AND room = ? LIMIT 1',
|
|
|
|
|
- [sender, area, building, room]
|
|
|
|
|
- )
|
|
|
|
|
- }
|
|
|
|
|
- if (!existing) return '查询电费任务失败'
|
|
|
|
|
-
|
|
|
|
|
- const fields = []
|
|
|
|
|
- const params = []
|
|
|
|
|
- for (const key of ['area', 'building', 'room', 'email', 'lowest', 'notes']) {
|
|
|
|
|
- if (isProvided(args[key])) {
|
|
|
|
|
- fields.push(`${key} = ?`)
|
|
|
|
|
- params.push(args[key])
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- let result
|
|
|
|
|
- if (existing.length > 0) {
|
|
|
|
|
- if (fields.length === 0) return '没有需要修改的字段'
|
|
|
|
|
- fields.push('is_notice = 0', 'update_time = ?')
|
|
|
|
|
- params.push(now)
|
|
|
|
|
- result = await db.query(
|
|
|
|
|
- `UPDATE power_task SET ${fields.join(', ')} WHERE id = ? AND create_user = ?`,
|
|
|
|
|
- [...params, existing[0].id, sender]
|
|
|
|
|
- )
|
|
|
|
|
- } else {
|
|
|
|
|
- if (required([area, building, room, args.lowest])) return '新增电费任务需提供 area、building、room、lowest'
|
|
|
|
|
- let balance = ''
|
|
|
|
|
- let koufeiDate = ''
|
|
|
|
|
|
|
+ const params = []
|
|
|
|
|
+ const mutable = [
|
|
|
|
|
+ ['email', 'email'],
|
|
|
|
|
+ ['auto_time', 'auto_time'],
|
|
|
|
|
+ ['auto_run', 'auto_run'],
|
|
|
|
|
+ ['target_count', 'target_count'],
|
|
|
|
|
+ ['notice_type', 'notice_type'],
|
|
|
|
|
+ ['notes', 'notes']
|
|
|
|
|
+ ]
|
|
|
|
|
+
|
|
|
|
|
+ if (isProvided(args.area)) {
|
|
|
|
|
+ const area = normalizeRunArea(args.area)
|
|
|
|
|
+ if (area === null) return '跑区不合法'
|
|
|
|
|
+ fields.push('area = ?')
|
|
|
|
|
+ params.push(area)
|
|
|
|
|
+ }
|
|
|
|
|
+ if (isProvided(args.notice_type) && !['email', 'none', 'bot'].includes(args.notice_type)) return '通知方式不合法'
|
|
|
|
|
+ if (isProvided(args.auto_time)) {
|
|
|
|
|
+ const autoTime = Number(args.auto_time)
|
|
|
|
|
+ if (!Number.isInteger(autoTime) || autoTime < -1 || autoTime > 23) return '自动乐跑时间不合法'
|
|
|
|
|
+ args.auto_time = autoTime
|
|
|
|
|
+ }
|
|
|
|
|
+ if (isProvided(args.auto_day)) {
|
|
|
|
|
+ fields.push('auto_day = ?')
|
|
|
|
|
+ params.push(JSON.stringify(args.auto_day))
|
|
|
|
|
+ }
|
|
|
|
|
+ for (const [argName, column] of mutable) {
|
|
|
|
|
+ if (isProvided(args[argName])) {
|
|
|
|
|
+ fields.push(`${column} = ?`)
|
|
|
|
|
+ params.push(args[argName])
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const now = Date.now()
|
|
|
|
|
+ let result
|
|
|
|
|
+ if (existRows.length > 0) {
|
|
|
|
|
+ if (fields.length === 0) return '没有需要修改的字段'
|
|
|
|
|
+ fields.push('update_time = ?', 'create_user = ?')
|
|
|
|
|
+ params.push(now, sender)
|
|
|
|
|
+ result = await db.query(
|
|
|
|
|
+ `UPDATE lepao_account SET ${fields.join(', ')} WHERE student_num = ? AND (create_user = ? OR create_user IS NULL)`,
|
|
|
|
|
+ [...params, student_num, sender]
|
|
|
|
|
+ )
|
|
|
|
|
+ } else {
|
|
|
|
|
+ const area = isProvided(args.area) ? normalizeRunArea(args.area) : ''
|
|
|
|
|
+ if (area === null) return '跑区不合法'
|
|
|
|
|
+ const autoDay = isProvided(args.auto_day) ? args.auto_day : [0, 1, 2, 3, 4, 5, 6]
|
|
|
|
|
+ if (!Array.isArray(autoDay) || !autoDay.every(v => Number.isInteger(v) && v >= 0 && v <= 6)) return '自动乐跑星期不合法'
|
|
|
|
|
+ const email = args.email || ''
|
|
|
|
|
+ if (email && !this.emailRegex.test(email)) return '邮箱格式不正确'
|
|
|
|
|
+ result = await db.query(
|
|
|
|
|
+ `INSERT INTO lepao_account
|
|
|
|
|
+ (student_num, email, area, auto_time, auto_run, target_count, create_user, create_time, update_time, notes, auto_day, notice_type)
|
|
|
|
|
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
|
|
|
+ [
|
|
|
|
|
+ student_num,
|
|
|
|
|
+ email,
|
|
|
|
|
+ area,
|
|
|
|
|
+ isProvided(args.auto_time) ? Number(args.auto_time) : -1,
|
|
|
|
|
+ isProvided(args.auto_run) ? Number(args.auto_run) : 1,
|
|
|
|
|
+ isProvided(args.target_count) ? args.target_count : 30,
|
|
|
|
|
+ sender,
|
|
|
|
|
+ now,
|
|
|
|
|
+ now,
|
|
|
|
|
+ args.notes || '',
|
|
|
|
|
+ JSON.stringify(autoDay),
|
|
|
|
|
+ args.notice_type || 'email'
|
|
|
|
|
+ ]
|
|
|
|
|
+ )
|
|
|
|
|
+ const bindCode = Math.random().toString(36).slice(2, 8).toUpperCase()
|
|
|
|
|
+ await db.query('INSERT IGNORE INTO lepao_extra (student_num, bind_code) VALUES (?, ?)', [student_num, bindCode])
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!result || result.affectedRows < 1) return '保存失败或无权修改'
|
|
|
|
|
+ return asJson({ success: true, student_num })
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async save_power_task(args) {
|
|
|
|
|
+ const { sender, task_id, area, building, room } = args
|
|
|
|
|
+ if (required([sender])) return '缺少 sender'
|
|
|
|
|
+
|
|
|
|
|
+ const now = Date.now()
|
|
|
|
|
+ let existing = []
|
|
|
|
|
+ if (task_id) {
|
|
|
|
|
+ existing = await db.query('SELECT id, area, building, room FROM power_task WHERE id = ? AND create_user = ? LIMIT 1', [task_id, sender])
|
|
|
|
|
+ } else if ([area, building, room].every(isProvided)) {
|
|
|
|
|
+ existing = await db.query(
|
|
|
|
|
+ 'SELECT id, area, building, room FROM power_task WHERE create_user = ? AND area = ? AND building = ? AND room = ? LIMIT 1',
|
|
|
|
|
+ [sender, area, building, room]
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!existing) return '查询电费任务失败'
|
|
|
|
|
+
|
|
|
|
|
+ const fields = []
|
|
|
|
|
+ const params = []
|
|
|
|
|
+ for (const key of ['area', 'building', 'room', 'email', 'lowest', 'notes']) {
|
|
|
|
|
+ if (isProvided(args[key])) {
|
|
|
|
|
+ fields.push(`${key} = ?`)
|
|
|
|
|
+ params.push(args[key])
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ let result
|
|
|
|
|
+ if (existing.length > 0) {
|
|
|
|
|
+ if (fields.length === 0) return '没有需要修改的字段'
|
|
|
|
|
+ fields.push('is_notice = 0', 'update_time = ?')
|
|
|
|
|
+ params.push(now)
|
|
|
|
|
+ result = await db.query(
|
|
|
|
|
+ `UPDATE power_task SET ${fields.join(', ')} WHERE id = ? AND create_user = ?`,
|
|
|
|
|
+ [...params, existing[0].id, sender]
|
|
|
|
|
+ )
|
|
|
|
|
+ } else {
|
|
|
|
|
+ if (required([area, building, room, args.lowest])) return '新增电费任务需提供 area、building、room、lowest'
|
|
|
|
|
+ let balance = ''
|
|
|
|
|
+ let koufeiDate = ''
|
|
|
try {
|
|
try {
|
|
|
const endpoint = `https://hqpay.ctbu.edu.cn/weixin/ashx/frmuser.ashx?test=lastlist&pid=${room}&dyid=${building}`
|
|
const endpoint = `https://hqpay.ctbu.edu.cn/weixin/ashx/frmuser.ashx?test=lastlist&pid=${room}&dyid=${building}`
|
|
|
const apiRes = await axiosWithQgOutbound({ method: 'get', url: endpoint, timeout: 15000, logger: this.logger, scene: 'AiAssistantPowerAccount' })
|
|
const apiRes = await axiosWithQgOutbound({ method: 'get', url: endpoint, timeout: 15000, logger: this.logger, scene: 'AiAssistantPowerAccount' })
|
|
|
- balance = apiRes?.data?.[0]?.[1] ?? ''
|
|
|
|
|
- koufeiDate = apiRes?.data?.[0]?.[2] ?? ''
|
|
|
|
|
- } catch (_) { }
|
|
|
|
|
- result = await db.query(
|
|
|
|
|
- 'INSERT INTO power_task (create_user, create_time, update_time, area, building, room, email, lowest, notes, balance, koufei_date) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
|
|
|
|
- [sender, now, now, area, building, room, args.email || '', args.lowest, args.notes || '', balance, koufeiDate]
|
|
|
|
|
- )
|
|
|
|
|
- }
|
|
|
|
|
- if (!result || result.affectedRows < 1) return '保存失败或无权修改'
|
|
|
|
|
- return asJson({ success: true, area, building, room })
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
|
|
+ if (!apiRes || !apiRes.data || !apiRes.data[0]) return '获取电费信息失败,请稍后再试'
|
|
|
|
|
+ balance = apiRes.data[0][1]
|
|
|
|
|
+ koufeiDate = apiRes.data[0][2]
|
|
|
|
|
+ } catch (_) {
|
|
|
|
|
+ return '获取电费信息失败,请稍后再试'
|
|
|
|
|
+ }
|
|
|
|
|
+ result = await db.query(
|
|
|
|
|
+ 'INSERT INTO power_task (create_user, create_time, update_time, area, building, room, email, lowest, notes, balance, koufei_date) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
|
|
|
|
+ [sender, now, now, area, building, room, args.email || '', args.lowest, args.notes || '', balance, koufeiDate]
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!result || result.affectedRows < 1) return '保存失败或无权修改'
|
|
|
|
|
+ return asJson({ success: true, area, building, room })
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
async list_power_bills(args) {
|
|
async list_power_bills(args) {
|
|
|
const { sender, area, building, room, current, pagesize } = args
|
|
const { sender, area, building, room, current, pagesize } = args
|
|
|
if (required([sender])) return 'missing sender'
|
|
if (required([sender])) return 'missing sender'
|
|
@@ -629,51 +663,51 @@ class AiAssistantMcp {
|
|
|
} catch (err) {
|
|
} catch (err) {
|
|
|
return err.message || '查询趣选书书单失败'
|
|
return err.message || '查询趣选书书单失败'
|
|
|
}
|
|
}
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- async submit_work_order(args) {
|
|
|
|
|
- const { sender, title, content, email = '', files = [] } = args
|
|
|
|
|
- if (required([sender, title, content])) return '缺少 sender、title 或 content'
|
|
|
|
|
- if (email && !this.emailRegex.test(email)) return '邮箱格式不正确'
|
|
|
|
|
- if (email && this.banEmailList.includes(email.split('@')[1].toLowerCase())) return '暂不支持该邮箱域名'
|
|
|
|
|
-
|
|
|
|
|
- const safeTitle = normalizeText(title, 80)
|
|
|
|
|
- const safeContent = normalizeText(content, 4000)
|
|
|
|
|
- const safeFiles = Array.isArray(files) ? files.map(item => String(item || '').trim()).filter(Boolean).slice(0, 6) : []
|
|
|
|
|
- if (!safeTitle || !safeContent) return '标题或内容不能为空'
|
|
|
|
|
-
|
|
|
|
|
- const time = Date.now()
|
|
|
|
|
- const msg = [
|
|
|
|
|
- {
|
|
|
|
|
- time,
|
|
|
|
|
- content: safeContent,
|
|
|
|
|
- files: safeFiles,
|
|
|
|
|
- uuid: sender,
|
|
|
|
|
- type: 'user'
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- time,
|
|
|
|
|
- content: '您的问题已收到,我们将尽快处理,请耐心等待。',
|
|
|
|
|
- uuid: ASSISTANT_UUID,
|
|
|
|
|
- type: 'system'
|
|
|
|
|
- }
|
|
|
|
|
- ]
|
|
|
|
|
- const result = await db.query(
|
|
|
|
|
- 'INSERT INTO work_order (title, email, msg, create_user, create_time, update_time) VALUES (?, ?, ?, ?, ?, ?)',
|
|
|
|
|
- [safeTitle, email, msg, sender, time, time]
|
|
|
|
|
- )
|
|
|
|
|
- if (!result || result.affectedRows !== 1) return '提交工单失败'
|
|
|
|
|
-
|
|
|
|
|
- const kefuRows = await db.query(`
|
|
|
|
|
- SELECT email FROM users
|
|
|
|
|
- WHERE JSON_CONTAINS(permission, '"admin"') OR JSON_CONTAINS(permission, '"service"')
|
|
|
|
|
- `)
|
|
|
|
|
- const emails = [...new Set((kefuRows || []).map(row => row.email).filter(Boolean))]
|
|
|
|
|
- for (const targetEmail of emails) {
|
|
|
|
|
- await EmailTemplate.orderNewReply(targetEmail, { id: result.insertId, content: safeContent, files: safeFiles })
|
|
|
|
|
- }
|
|
|
|
|
- return asJson({ success: true, message: '工单提交成功,可在工单列表查看处理进度' })
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-module.exports.AI_ASSISTANT_MCP = new AiAssistantMcp()
|
|
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async submit_work_order(args) {
|
|
|
|
|
+ const { sender, title, content, email = '', files = [] } = args
|
|
|
|
|
+ if (required([sender, title, content])) return '缺少 sender、title 或 content'
|
|
|
|
|
+ if (email && !this.emailRegex.test(email)) return '邮箱格式不正确'
|
|
|
|
|
+ if (email && this.banEmailList.includes(email.split('@')[1].toLowerCase())) return '暂不支持该邮箱域名'
|
|
|
|
|
+
|
|
|
|
|
+ const safeTitle = normalizeText(title, 80)
|
|
|
|
|
+ const safeContent = normalizeText(content, 4000)
|
|
|
|
|
+ const safeFiles = Array.isArray(files) ? files.map(item => String(item || '').trim()).filter(Boolean).slice(0, 6) : []
|
|
|
|
|
+ if (!safeTitle || !safeContent) return '标题或内容不能为空'
|
|
|
|
|
+
|
|
|
|
|
+ const time = Date.now()
|
|
|
|
|
+ const msg = [
|
|
|
|
|
+ {
|
|
|
|
|
+ time,
|
|
|
|
|
+ content: safeContent,
|
|
|
|
|
+ files: safeFiles,
|
|
|
|
|
+ uuid: sender,
|
|
|
|
|
+ type: 'user'
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ time,
|
|
|
|
|
+ content: '您的问题已收到,我们将尽快处理,请耐心等待。',
|
|
|
|
|
+ uuid: ASSISTANT_UUID,
|
|
|
|
|
+ type: 'system'
|
|
|
|
|
+ }
|
|
|
|
|
+ ]
|
|
|
|
|
+ const result = await db.query(
|
|
|
|
|
+ 'INSERT INTO work_order (title, email, msg, create_user, create_time, update_time) VALUES (?, ?, ?, ?, ?, ?)',
|
|
|
|
|
+ [safeTitle, email, msg, sender, time, time]
|
|
|
|
|
+ )
|
|
|
|
|
+ if (!result || result.affectedRows !== 1) return '提交工单失败'
|
|
|
|
|
+
|
|
|
|
|
+ const kefuRows = await db.query(`
|
|
|
|
|
+ SELECT email FROM users
|
|
|
|
|
+ WHERE JSON_CONTAINS(permission, '"admin"') OR JSON_CONTAINS(permission, '"service"')
|
|
|
|
|
+ `)
|
|
|
|
|
+ const emails = [...new Set((kefuRows || []).map(row => row.email).filter(Boolean))]
|
|
|
|
|
+ for (const targetEmail of emails) {
|
|
|
|
|
+ await EmailTemplate.orderNewReply(targetEmail, { id: result.insertId, content: safeContent, files: safeFiles })
|
|
|
|
|
+ }
|
|
|
|
|
+ return asJson({ success: true, message: '工单提交成功,可在工单列表查看处理进度' })
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+module.exports.AI_ASSISTANT_MCP = new AiAssistantMcp()
|