254 lines
6.7 KiB
TypeScript
254 lines
6.7 KiB
TypeScript
import { Pool } from 'pg'
|
|
import crypto from 'crypto'
|
|
|
|
// 创建数据库连接池
|
|
const pool = new Pool({
|
|
connectionString: process.env.DATABASE_URL,
|
|
max: 20, // 最大连接数
|
|
idleTimeoutMillis: 30000,
|
|
connectionTimeoutMillis: 2000,
|
|
})
|
|
|
|
// 测试连接
|
|
pool.on('connect', () => {
|
|
console.log('✅ Database connected')
|
|
})
|
|
|
|
pool.on('error', (err) => {
|
|
console.error('❌ Unexpected database error:', err)
|
|
})
|
|
|
|
export default pool
|
|
|
|
// 用户接口
|
|
export interface User {
|
|
id: number
|
|
name: string
|
|
email: string
|
|
password: string
|
|
email_verified: boolean
|
|
created_at: Date
|
|
updated_at: Date
|
|
provider?: string
|
|
wechat_unionid?: string | null
|
|
wechat_openid?: string | null
|
|
}
|
|
|
|
// 订单接口
|
|
export interface Order {
|
|
id: number
|
|
order_no: string
|
|
user_id: number
|
|
product_type: string // 'standard' | 'deployment' | 'custom'
|
|
product_name: string
|
|
amount: number
|
|
status: string // 'pending' | 'paid' | 'failed' | 'cancelled'
|
|
payment_method?: string
|
|
trade_no?: string
|
|
callback_data?: string
|
|
created_at: Date
|
|
updated_at: Date
|
|
paid_at?: Date
|
|
}
|
|
|
|
// 数据库查询函数
|
|
export const db = {
|
|
// 根据邮箱查找用户
|
|
async findUserByEmail(email: string): Promise<User | null> {
|
|
const result = await pool.query(
|
|
'SELECT * FROM users WHERE email = $1',
|
|
[email]
|
|
)
|
|
return result.rows[0] || null
|
|
},
|
|
|
|
// 根据ID查找用户
|
|
async findUserById(id: number): Promise<User | null> {
|
|
const result = await pool.query(
|
|
'SELECT * FROM users WHERE id = $1',
|
|
[id]
|
|
)
|
|
return result.rows[0] || null
|
|
},
|
|
|
|
// 创建新用户
|
|
async createUser(
|
|
name: string,
|
|
email: string,
|
|
password: string
|
|
): Promise<User> {
|
|
const result = await pool.query(
|
|
'INSERT INTO users (name, email, password, email_verified, created_at, updated_at) VALUES ($1, $2, $3, $4, NOW(), NOW()) RETURNING *',
|
|
[name, email, password, false]
|
|
)
|
|
return result.rows[0]
|
|
},
|
|
|
|
// 更新用户
|
|
async updateUser(id: number, data: Partial<User>): Promise<User> {
|
|
const fields = []
|
|
const values = []
|
|
let index = 1
|
|
|
|
if (data.name !== undefined) {
|
|
fields.push(`name = $${index++}`)
|
|
values.push(data.name)
|
|
}
|
|
if (data.email !== undefined) {
|
|
fields.push(`email = $${index++}`)
|
|
values.push(data.email)
|
|
}
|
|
if (data.password !== undefined) {
|
|
fields.push(`password = $${index++}`)
|
|
values.push(data.password)
|
|
}
|
|
if (data.email_verified !== undefined) {
|
|
fields.push(`email_verified = $${index++}`)
|
|
values.push(data.email_verified)
|
|
}
|
|
|
|
fields.push(`updated_at = NOW()`)
|
|
values.push(id)
|
|
|
|
const query = `UPDATE users SET ${fields.join(', ')} WHERE id = $${index} RETURNING *`
|
|
const result = await pool.query(query, values)
|
|
return result.rows[0]
|
|
},
|
|
|
|
// 删除用户
|
|
async deleteUser(id: number): Promise<boolean> {
|
|
const result = await pool.query('DELETE FROM users WHERE id = $1', [id])
|
|
return (result.rowCount ?? 0) > 0
|
|
},
|
|
|
|
// 获取所有用户(用于管理)
|
|
async getAllUsers(): Promise<User[]> {
|
|
const result = await pool.query(
|
|
'SELECT id, name, email, email_verified, created_at FROM users ORDER BY created_at DESC'
|
|
)
|
|
return result.rows
|
|
},
|
|
|
|
// ========== 微信用户相关 ==========
|
|
async findUserByWechat(unionid?: string | null, openid?: string | null): Promise<User | null> {
|
|
if (unionid) {
|
|
const byUnion = await pool.query('SELECT * FROM users WHERE wechat_unionid = $1', [unionid])
|
|
if (byUnion.rows[0]) return byUnion.rows[0]
|
|
}
|
|
if (openid) {
|
|
const byOpen = await pool.query('SELECT * FROM users WHERE wechat_openid = $1', [openid])
|
|
if (byOpen.rows[0]) return byOpen.rows[0]
|
|
}
|
|
return null
|
|
},
|
|
|
|
async upsertWechatUser(params: {
|
|
name?: string | null
|
|
email?: string | null
|
|
unionid?: string | null
|
|
openid?: string | null
|
|
hashedPassword: string
|
|
}): Promise<User> {
|
|
const { name, email, unionid, openid, hashedPassword } = params
|
|
|
|
const existing = await this.findUserByWechat(unionid ?? undefined, openid ?? undefined)
|
|
if (existing) {
|
|
return existing
|
|
}
|
|
|
|
const result = await pool.query(
|
|
`INSERT INTO users (name, email, password, email_verified, provider, wechat_unionid, wechat_openid, created_at, updated_at)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7, NOW(), NOW())
|
|
RETURNING *`,
|
|
[
|
|
name || '微信用户',
|
|
email || `${openid || unionid || crypto.randomUUID()}@wechat.local`,
|
|
hashedPassword,
|
|
true,
|
|
'wechat',
|
|
unionid || null,
|
|
openid || null,
|
|
]
|
|
)
|
|
return result.rows[0]
|
|
},
|
|
|
|
// ========== 订单相关操作 ==========
|
|
|
|
// 创建订单
|
|
async createOrder(
|
|
orderNo: string,
|
|
userId: number,
|
|
productType: string,
|
|
productName: string,
|
|
amount: number
|
|
): Promise<Order> {
|
|
const result = await pool.query(
|
|
'INSERT INTO orders (order_no, user_id, product_type, product_name, amount, status, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6, NOW(), NOW()) RETURNING *',
|
|
[orderNo, userId, productType, productName, amount, 'pending']
|
|
)
|
|
return result.rows[0]
|
|
},
|
|
|
|
// 根据订单号查找订单
|
|
async findOrderByOrderNo(orderNo: string): Promise<Order | null> {
|
|
const result = await pool.query(
|
|
'SELECT * FROM orders WHERE order_no = $1',
|
|
[orderNo]
|
|
)
|
|
return result.rows[0] || null
|
|
},
|
|
|
|
// 根据ID查找订单
|
|
async findOrderById(id: number): Promise<Order | null> {
|
|
const result = await pool.query(
|
|
'SELECT * FROM orders WHERE id = $1',
|
|
[id]
|
|
)
|
|
return result.rows[0] || null
|
|
},
|
|
|
|
// 更新订单状态
|
|
async updateOrderStatus(
|
|
orderNo: string,
|
|
status: string,
|
|
tradeNo?: string,
|
|
paymentMethod?: string,
|
|
callbackData?: string
|
|
): Promise<Order> {
|
|
const updates = ['status = $2', 'updated_at = NOW()']
|
|
const values: any[] = [orderNo, status]
|
|
let index = 3
|
|
|
|
if (tradeNo) {
|
|
updates.push(`trade_no = $${index++}`)
|
|
values.push(tradeNo)
|
|
}
|
|
if (paymentMethod) {
|
|
updates.push(`payment_method = $${index++}`)
|
|
values.push(paymentMethod)
|
|
}
|
|
if (callbackData) {
|
|
updates.push(`callback_data = $${index++}`)
|
|
values.push(callbackData)
|
|
}
|
|
if (status === 'paid') {
|
|
updates.push('paid_at = NOW()')
|
|
}
|
|
|
|
const query = `UPDATE orders SET ${updates.join(', ')} WHERE order_no = $1 RETURNING *`
|
|
const result = await pool.query(query, values)
|
|
return result.rows[0]
|
|
},
|
|
|
|
// 获取用户的订单列表
|
|
async getUserOrders(userId: number): Promise<Order[]> {
|
|
const result = await pool.query(
|
|
'SELECT * FROM orders WHERE user_id = $1 ORDER BY created_at DESC',
|
|
[userId]
|
|
)
|
|
return result.rows
|
|
},
|
|
}
|
|
|