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
这个提交包含在:
Guoguo
2026-04-29 05:58:20 -07:00
父节点 9f0e629c82
当前提交 bb4b80f867
修改 46 个文件,包含 4306 行新增2020 行删除
+178
查看文件
@@ -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
}