feat: security hardening, wechat pay v3 platform cert integration, companion certification revocation, and audit closed-loop

这个提交包含在:
xcbyy
2026-10-16 17:38:17 +08:00
父节点 3bf38db8b6
当前提交 78117bb4e1
共有 58 个文件被更改,包括 12971 次插入 和 952 次删除

查看文件

@@ -27,6 +27,7 @@ export async function initDatabase(): Promise<void> {
title VARCHAR(255) NOT NULL,
game_category VARCHAR(64) NOT NULL,
location VARCHAR(255),
start_time VARCHAR(64) DEFAULT '',
pay_type INT NOT NULL DEFAULT 1,
total_fee_cents INT DEFAULT 0,
per_person_fee_cents INT DEFAULT 0,
@@ -38,6 +39,12 @@ export async function initDatabase(): Promise<void> {
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
`);
try {
await conn.query(`ALTER TABLE activities ADD COLUMN start_time VARCHAR(64) DEFAULT '' AFTER location`);
} catch (_) {
// 字段已存在忽略
}
try {
await conn.query(`ALTER TABLE activities ADD COLUMN status VARCHAR(32) DEFAULT 'RECRUITING' AFTER price_adjust_rule`);
} catch (_) {
@@ -57,10 +64,24 @@ export async function initDatabase(): Promise<void> {
verified_by INT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_activity (activity_id),
INDEX idx_verify_code (verify_code)
INDEX idx_verify_code (verify_code),
UNIQUE KEY uq_activity_user (activity_id, user_id),
UNIQUE KEY uq_activity_verify (activity_id, verify_code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
`);
// 历史库补丁:为已存在的 participants 表补充唯一约束(防重复报名 / 防核销码冲突)
for (const uqSql of [
'ALTER TABLE participants ADD UNIQUE KEY uq_activity_user (activity_id, user_id)',
'ALTER TABLE participants ADD UNIQUE KEY uq_activity_verify (activity_id, verify_code)'
]) {
try {
await conn.query(uqSql);
} catch (_) {
// 约束已存在或历史数据存在重复,忽略(应用层仍会二次校验)
}
}
// 3. 陪玩大神档案表
await conn.query(`
CREATE TABLE IF NOT EXISTS companions (
@@ -131,7 +152,9 @@ export async function initDatabase(): Promise<void> {
"ALTER TABLE orders ADD COLUMN coupon_discount DECIMAL(10,2) DEFAULT 0.00 AFTER amount",
"ALTER TABLE orders ADD COLUMN paid_at DATETIME NULL AFTER created_at",
"ALTER TABLE orders ADD COLUMN finish_applied_at DATETIME NULL AFTER paid_at",
"ALTER TABLE orders ADD COLUMN refund_amount DECIMAL(10,2) DEFAULT 0.00 AFTER coupon_discount"
"ALTER TABLE orders ADD COLUMN refund_amount DECIMAL(10,2) DEFAULT 0.00 AFTER coupon_discount",
"ALTER TABLE orders ADD COLUMN claimed_by INT NULL AFTER expert_id",
"ALTER TABLE orders ADD COLUMN claimed_at DATETIME NULL AFTER claimed_by"
];
for (const colSql of extraOrderCols) {
try {
@@ -194,6 +217,26 @@ export async function initDatabase(): Promise<void> {
await conn.query('ALTER TABLE withdrawals ADD COLUMN reason VARCHAR(255) DEFAULT ""');
} catch (_) {}
// 7.5 微信支付支付意图与对账表 (幂等入账的唯一依据)
await conn.query(`
CREATE TABLE IF NOT EXISTS payment_intents (
id VARCHAR(64) PRIMARY KEY,
out_trade_no VARCHAR(64) UNIQUE NOT NULL,
scene VARCHAR(32) NOT NULL DEFAULT 'ORDER',
user_id INT NOT NULL DEFAULT 0,
order_id VARCHAR(64) DEFAULT '',
title VARCHAR(128) DEFAULT '',
amount_cents INT NOT NULL DEFAULT 0,
bonus_cents INT NOT NULL DEFAULT 0,
status VARCHAR(32) NOT NULL DEFAULT 'PENDING',
transaction_id VARCHAR(64) DEFAULT '',
credited TINYINT NOT NULL DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
paid_at DATETIME NULL,
INDEX idx_user_scene (user_id, scene, status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
`);
// 8. 线下合规场所库
await conn.query(`
CREATE TABLE IF NOT EXISTS safety_places (
@@ -292,10 +335,18 @@ export async function initDatabase(): Promise<void> {
reply_time DATETIME,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_order (order_id),
INDEX idx_expert (expert_id)
INDEX idx_expert (expert_id),
UNIQUE KEY uq_order_review (order_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
`);
// 历史库补丁:同一订单仅允许一条评价,防止重复评价反复触发结算
try {
await conn.query('ALTER TABLE order_reviews ADD UNIQUE KEY uq_order_review (order_id)');
} catch (_) {
// 约束已存在或历史数据存在重复,忽略(应用层仍会二次校验)
}
try {
await conn.query('ALTER TABLE order_reviews ADD COLUMN reply_content TEXT AFTER content');
} catch (_) {}
@@ -895,6 +946,32 @@ async function seedInitialDataIfEmpty(conn: any): Promise<void> {
remark: '微信小程序订阅消息与服务通知模版ID配置'
});
const wechatPayConfigVal = JSON.stringify({
mode: 'AUTO',
appId: 'wx0c929f7e54024e67',
appSecret: '63a412f3f73af8e73f6923943bf9683c',
mchId: '',
apiV3Key: '',
serialNo: '',
privateKey: '',
certPem: '',
platformPublicKeyId: '',
platformPublicKey: '',
notifyUrl: 'https://peiww.gynhc.com/api/pay/wechat/notify',
remark: '微信支付V3直连商户参数配置(配置完整后自动或手动切换至实盘JSAPI支付)'
});
const mapLocationConfigVal = JSON.stringify({
provider: 'tencent',
mapKey: '',
secretKey: '',
serviceName: '腾讯位置服务 (Tencent LBS)',
enableChooseLocation: 1,
enableDistanceCalculation: 1,
defaultCity: '北京市',
remark: '腾讯位置服务 Key(需在 lbs.qq.com 申请并勾选 WebServiceAPI 与微信小程序 AppID: wx0c929f7e54024e67)'
});
const [cfgItemRows]: any = await conn.query('SELECT COUNT(*) as count FROM platform_configs');
if (cfgItemRows[0].count === 0) {
const customerServiceVal = JSON.stringify({
@@ -938,12 +1015,16 @@ async function seedInitialDataIfEmpty(conn: any): Promise<void> {
('home_popup', '小程序首页运营全局弹窗', ?, '节日活动推广、新用户专享首单优惠全局浮层弹窗', 1),
('expert_rules', '大神技能标签库与定价区间规范', ?, '陪玩大神入驻审核、定价上下限限制与官方推荐技能标签库', 1),
('finance_rules', '平台抽成与推广分销规则', ?, '平台佣金抽成比例、二级分销返润与佣金提现门槛限制', 1),
('activity_rules', '同城组局发单与预算预设规范', ?, '招募席位上下限、快捷单人赏金预设与包场预算梯队', 1);
('activity_rules', '同城组局发单与预算预设规范', ?, '招募席位上下限、快捷单人赏金预设与包场预算梯队', 1),
('message_template_rules', '微信服务通知与模板消息配置', ?, '微信订阅消息通知与状态模板配置', 1),
('wechat_pay_config', '微信支付商户号与证书配置', ?, '微信支付V3商户号、APIv3密钥与证书参数配置', 1),
('map_location_config', '地图定位与LBS服务配置', ?, '腾讯位置服务Key、选点与周边测距参数配置', 1);
`, [
customerServiceVal, safetyNoticeVal, protocolVal,
communityTopicsVal, communityNoticeVal, faqListVal,
securityWordsVal, homePopupVal, expertRulesVal,
financeRulesVal, activityRulesVal
financeRulesVal, activityRulesVal, messageTemplateRulesVal,
wechatPayConfigVal, mapLocationConfigVal
]);
} else {
// 增量保证各核心配置项完整就绪
@@ -1003,6 +1084,40 @@ async function seedInitialDataIfEmpty(conn: any): Promise<void> {
VALUES ('message_template_rules', '微信服务通知与模板消息配置', ?, '微信订阅消息通知与状态模板配置', 1)
`, [messageTemplateRulesVal]);
}
if (!existingKeys.has('wechat_pay_config')) {
await conn.query(`
INSERT INTO platform_configs (config_key, name, value_json, description, status)
VALUES ('wechat_pay_config', '微信支付商户号与证书配置', ?, '微信支付V3商户号、APIv3密钥与证书参数配置', 1)
`, [wechatPayConfigVal]);
} else {
const [wpRows]: any = await conn.query("SELECT value_json FROM platform_configs WHERE config_key = 'wechat_pay_config'");
if (wpRows && wpRows.length > 0) {
let curVal = wpRows[0].value_json;
if (typeof curVal === 'string') {
try { curVal = JSON.parse(curVal); } catch (_) {}
}
if (curVal && typeof curVal === 'object') {
let updated = false;
if (!curVal.appId || curVal.appId === 'wx_peiwan_miniapp') {
curVal.appId = 'wx0c929f7e54024e67';
updated = true;
}
if (!curVal.appSecret) {
curVal.appSecret = '63a412f3f73af8e73f6923943bf9683c';
updated = true;
}
if (updated) {
await conn.query("UPDATE platform_configs SET value_json = ? WHERE config_key = 'wechat_pay_config'", [JSON.stringify(curVal)]);
}
}
}
}
if (!existingKeys.has('map_location_config')) {
await conn.query(`
INSERT INTO platform_configs (config_key, name, value_json, description, status)
VALUES ('map_location_config', '地图定位与LBS服务配置', ?, '腾讯位置服务Key、选点与周边测距参数配置', 1)
`, [mapLocationConfigVal]);
}
if (existingKeys.has('platform_protocol')) {
const [protoRows]: any = await conn.query("SELECT value_json FROM platform_configs WHERE config_key = 'platform_protocol'");
if (protoRows && protoRows.length > 0) {

文件差异内容过多而无法显示 加载差异