文件
jw-beauty/server/src/dao/user.dao.js
T

217 行
6.5 KiB
JavaScript

const { query, one, transaction, limitClause } = require('../lib/db')
/**
* Find user by WeChat openid
* @param {string} openid
* @returns {Promise<Object|null>}
*/
async function findByOpenid(openid) {
return one(
'SELECT * FROM users WHERE openid = :openid',
{ openid }
)
}
/**
* Create a new user from WeChat login
* @param {string} openid
* @returns {Promise<Object>} query result with insertId
*/
async function create(openid) {
return query(
'INSERT INTO users (openid, nickname, avatar, status) VALUES (:openid, :nickname, :avatar, 1)',
{ openid, nickname: '', avatar: '' }
)
}
/**
* Find user by user_id
* @param {number} userId
* @returns {Promise<Object|null>}
*/
async function findById(userId) {
return one(
'SELECT * FROM users WHERE user_id = :user_id',
{ user_id: userId }
)
}
/**
* Find active user by user_id (status=1)
* @param {number} userId
* @returns {Promise<Object|null>}
*/
async function findActiveById(userId) {
return one(
'SELECT * FROM users WHERE user_id = :user_id AND status = 1',
{ user_id: userId }
)
}
/**
* Update user profile fields (nickname, avatar, gender)
* @param {number} userId
* @param {Object} fields
* @param {string|null} [fields.nickname]
* @param {string|null} [fields.avatar]
* @param {number|null} [fields.gender]
* @returns {Promise<Array>} query result
*/
async function updateProfile(userId, fields) {
return query(
'UPDATE users SET nickname = COALESCE(:nickname, nickname), avatar = COALESCE(:avatar, avatar), gender = COALESCE(:gender, gender) WHERE user_id = :user_id',
{
user_id: userId,
nickname: fields.nickname || null,
avatar: fields.avatar || null,
gender: fields.gender === undefined ? null : fields.gender
}
)
}
/**
* Update user phone number
* @param {number} userId
* @param {string} phone
* @returns {Promise<Array>} query result
*/
async function updatePhone(userId, phone) {
return query(
'UPDATE users SET phone = :phone WHERE user_id = :user_id',
{ user_id: userId, phone }
)
}
/**
* Admin paginated user list with device/treatment/subscription subquery stats
* @param {Object} opts
* @param {string} [opts.keyword] - search nickname, phone, or exact user_id
* @param {number} opts.pageSize
* @param {number} opts.offset
* @returns {Promise<{records: Array, total: number}>}
*/
async function listAdmin({ keyword, pageSize, offset }) {
let where = ''
const params = {}
if (keyword) {
where = ' WHERE u.nickname LIKE :kw OR u.phone LIKE :kw OR u.user_id = :keyword'
params.kw = '%' + keyword + '%'
params.keyword = keyword
}
const total = await query('SELECT COUNT(*) AS total FROM users u' + where, params)
const records = await query(
'SELECT u.*,' +
' (SELECT COUNT(*) FROM bindings WHERE user_id = u.user_id AND bind_status = 1) AS device_count,' +
' (SELECT COUNT(*) FROM treatment_records WHERE user_id = u.user_id) AS treatment_count,' +
' COALESCE((SELECT status FROM subscriptions WHERE user_id = u.user_id AND status = 1 AND expire_time > NOW() ORDER BY expire_time DESC LIMIT 1), 0) AS subscription_status' +
' FROM users u' + where + ' ORDER BY u.created_at DESC' + limitClause(pageSize, offset),
params
)
return { records, total: total[0].total }
}
/**
* Count admin users with optional keyword
* @param {Object} opts
* @param {string} [opts.keyword]
* @returns {Promise<number>}
*/
async function countAdmin({ keyword }) {
let where = ''
const params = {}
if (keyword) {
where = ' WHERE u.nickname LIKE :kw OR u.phone LIKE :kw OR u.user_id = :keyword'
params.kw = '%' + keyword + '%'
params.keyword = keyword
}
const rows = await query('SELECT COUNT(*) AS total FROM users u' + where, params)
return rows[0].total
}
/**
* Admin detail view: user + bound devices, recent treatments, subscription, stats
* @param {number} userId
* @returns {Promise<Object|null>} enriched user object or null
*/
async function findByIdAdmin(userId) {
const user = await one('SELECT * FROM users WHERE user_id = :user_id', { user_id: userId })
if (!user) return null
const devices = await query(
'SELECT d.device_id, d.device_name FROM bindings b JOIN devices d ON d.device_id = b.device_id WHERE b.user_id = :user_id AND b.bind_status = 1',
{ user_id: userId }
)
const treatments = await query(
'SELECT * FROM treatment_records WHERE user_id = :user_id ORDER BY created_at DESC LIMIT 5',
{ user_id: userId }
)
const subscription = await one(
'SELECT plan, status, start_time, expire_time FROM subscriptions WHERE user_id = :user_id AND status = 1 AND expire_time > NOW() ORDER BY expire_time DESC LIMIT 1',
{ user_id: userId }
)
const stats = await one(
'SELECT COUNT(*) AS treatment_count, COALESCE(SUM(total_duration_ms), 0) AS total_duration FROM treatment_records WHERE user_id = :user_id',
{ user_id: userId }
)
return Object.assign({}, user, {
devices,
recent_treatments: treatments,
subscription_status: subscription ? subscription.status : 0,
subscription_type: subscription ? subscription.plan : null,
subscription_expire: subscription ? subscription.expire_time : null,
treatment_count: stats ? stats.treatment_count : 0,
total_duration: stats ? stats.total_duration : 0
})
}
/**
* Update user status.
* @param {number} userId
* @param {number} status - 1=active, 2=disabled, 3=cancelled
* @returns {Promise<Array>} query result
*/
async function updateStatus(userId, status) {
return query(
'UPDATE users SET status = :status WHERE user_id = :user_id',
{ user_id: userId, status }
)
}
/**
* Soft-cancel a user account and unbind all active devices.
* Historical treatment/subscription/log rows are kept.
* @param {number} userId
* @returns {Promise<{userAffectedRows: number, unboundRows: number}>}
*/
async function deactivate(userId) {
return transaction(async conn => {
const [userResult] = await conn.execute(
'UPDATE users SET status = 3 WHERE user_id = ? AND status <> 3',
[userId]
)
const [unbindResult] = await conn.execute(
'UPDATE bindings SET bind_status = 2, unbind_time = NOW() WHERE user_id = ? AND bind_status = 1',
[userId]
)
return {
userAffectedRows: userResult.affectedRows || 0,
unboundRows: unbindResult.affectedRows || 0
}
})
}
module.exports = {
findByOpenid,
create,
findById,
findActiveById,
updateProfile,
updatePhone,
listAdmin,
countAdmin,
findByIdAdmin,
updateStatus,
deactivate
}