refactor: restructure entire project for human maintainability
Server: - Add Express framework, replace custom router/request parser - Create DAO layer (12 files) centralizing all 73 SQL queries - Rewrite 7 route files as thin Express controllers calling DAOs - Add SCF-to-Express adapter (lib/serverless.js) - Add auth middleware (middleware/auth.js) - Remove dead code from lib/auth.js Admin console: - Extract DataTable component (table + pagination) - Extract ConfirmModal component (modal + form styles) - Create listMixin for paginated list pages - Move form styles to common.css for slot compatibility - Refactor device + subscription pages as examples Miniprogram: - Split 734-line BLE monolith into 4 focused modules (protocol, connection, commands, barrel index) - Create API module (utils/api.js) with named functions - Create page utilities (utils/page.js) - Refactor index + profile pages to use API module
这个提交包含在:
@@ -0,0 +1,178 @@
|
||||
const { query, one, 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
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
findByOpenid,
|
||||
create,
|
||||
findById,
|
||||
findActiveById,
|
||||
updateProfile,
|
||||
updatePhone,
|
||||
listAdmin,
|
||||
countAdmin,
|
||||
findByIdAdmin
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户