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
这个提交包含在:
+1
-26
@@ -2,7 +2,6 @@ const crypto = require('crypto')
|
||||
const jwt = require('jsonwebtoken')
|
||||
const bcrypt = require('bcryptjs')
|
||||
const config = require('../config')
|
||||
const { one } = require('./db')
|
||||
|
||||
function hashPasswordLegacy(password, salt) {
|
||||
return crypto.createHash('sha256').update(String(password) + ':' + salt).digest('hex')
|
||||
@@ -34,28 +33,4 @@ function readBearer(headers) {
|
||||
return match ? match[1] : ''
|
||||
}
|
||||
|
||||
async function requireUser(ctx) {
|
||||
const token = readBearer(ctx.headers)
|
||||
if (!token) return null
|
||||
try {
|
||||
const payload = jwt.verify(token, config.jwt.secret)
|
||||
if (payload.type !== 'user') return null
|
||||
return await one('SELECT * FROM users WHERE user_id = :user_id AND status = 1', { user_id: payload.user_id })
|
||||
} catch (err) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
async function requireAdmin(ctx) {
|
||||
const token = readBearer(ctx.headers)
|
||||
if (!token) return null
|
||||
try {
|
||||
const payload = jwt.verify(token, config.jwt.adminSecret)
|
||||
if (payload.type !== 'admin') return null
|
||||
return await one('SELECT * FROM admin_accounts WHERE admin_id = :admin_id AND status = 1', { admin_id: payload.admin_id })
|
||||
} catch (err) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { hashPassword, hashPasswordLegacy, verifyPassword, randomHex, signUser, signAdmin, readBearer, requireUser, requireAdmin }
|
||||
module.exports = { hashPassword, hashPasswordLegacy, verifyPassword, randomHex, signUser, signAdmin, readBearer }
|
||||
|
||||
+2
-16
@@ -1,16 +1,2 @@
|
||||
const { query } = require('./db')
|
||||
|
||||
async function writeLog(options) {
|
||||
await query(
|
||||
'INSERT INTO operation_logs (user_id, admin_id, action, detail, ip) VALUES (:user_id, :admin_id, :action, :detail, :ip)',
|
||||
{
|
||||
user_id: options.user_id || null,
|
||||
admin_id: options.admin_id || null,
|
||||
action: options.action,
|
||||
detail: options.detail || '',
|
||||
ip: options.ip || ''
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
module.exports = { writeLog }
|
||||
const logDao = require('../dao/log.dao')
|
||||
module.exports = { writeLog: logDao.write }
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
function normalizeHeaders(headers) {
|
||||
const result = {}
|
||||
Object.keys(headers || {}).forEach(key => {
|
||||
result[key] = headers[key]
|
||||
result[key.toLowerCase()] = headers[key]
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
function parseBody(event) {
|
||||
if (!event.body) return {}
|
||||
if (typeof event.body === 'object') return event.body
|
||||
const raw = event.isBase64Encoded ? Buffer.from(event.body, 'base64').toString('utf8') : event.body
|
||||
if (!raw) return {}
|
||||
try { return JSON.parse(raw) } catch (err) { return {} }
|
||||
}
|
||||
|
||||
function parseQuery(event) {
|
||||
if (event.queryStringParameters) return event.queryStringParameters || {}
|
||||
if (event.query) return event.query || {}
|
||||
return {}
|
||||
}
|
||||
|
||||
function getPath(event) {
|
||||
return event.path || event.Path || event.requestContext && event.requestContext.path || '/'
|
||||
}
|
||||
|
||||
function getMethod(event) {
|
||||
return String(event.httpMethod || event.method || event.requestContext && event.requestContext.httpMethod || 'GET').toUpperCase()
|
||||
}
|
||||
|
||||
function createContext(event) {
|
||||
return {
|
||||
event,
|
||||
method: getMethod(event),
|
||||
path: getPath(event),
|
||||
headers: normalizeHeaders(event.headers),
|
||||
query: parseQuery(event),
|
||||
body: parseBody(event),
|
||||
params: {},
|
||||
ip: event.requestContext && event.requestContext.sourceIp || (event.headers && (event.headers['x-forwarded-for'] || event.headers['X-Forwarded-For'] || '').split(',')[0].trim()) || ''
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { createContext }
|
||||
+1
-15
@@ -6,18 +6,4 @@ function fail(code, message, data) {
|
||||
return { code, message, data: data || {} }
|
||||
}
|
||||
|
||||
function http(statusCode, body, headers) {
|
||||
return {
|
||||
isBase64Encoded: false,
|
||||
statusCode,
|
||||
headers: Object.assign({
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Device-Id, X-App-Version, X-Platform',
|
||||
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS'
|
||||
}, headers || {}),
|
||||
body: JSON.stringify(body)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { ok, fail, http }
|
||||
module.exports = { ok, fail }
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
class Router {
|
||||
constructor() {
|
||||
this.routes = []
|
||||
}
|
||||
|
||||
add(method, pattern, handler) {
|
||||
const keys = []
|
||||
const regex = new RegExp('^' + pattern.replace(/\/:(\w+)/g, function (_, key) {
|
||||
keys.push(key)
|
||||
return '/([^/]+)'
|
||||
}) + '$')
|
||||
this.routes.push({ method, regex, keys, handler })
|
||||
}
|
||||
|
||||
get(pattern, handler) { this.add('GET', pattern, handler) }
|
||||
post(pattern, handler) { this.add('POST', pattern, handler) }
|
||||
put(pattern, handler) { this.add('PUT', pattern, handler) }
|
||||
delete(pattern, handler) { this.add('DELETE', pattern, handler) }
|
||||
|
||||
match(method, path) {
|
||||
for (const route of this.routes) {
|
||||
if (route.method !== method) continue
|
||||
const match = path.match(route.regex)
|
||||
if (!match) continue
|
||||
const params = {}
|
||||
route.keys.forEach((key, index) => { params[key] = decodeURIComponent(match[index + 1]) })
|
||||
return { handler: route.handler, params }
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Router
|
||||
@@ -0,0 +1,63 @@
|
||||
const http = require('http')
|
||||
|
||||
module.exports = function serverless(app) {
|
||||
return async function handler(event) {
|
||||
const method = String(event.httpMethod || event.method || 'GET').toUpperCase()
|
||||
const path = event.path || '/'
|
||||
const headers = event.headers || {}
|
||||
const qs = event.queryStringParameters || {}
|
||||
const qsStr = Object.keys(qs).map(k => encodeURIComponent(k) + '=' + encodeURIComponent(qs[k])).join('&')
|
||||
const url = path + (qsStr ? '?' + qsStr : '')
|
||||
|
||||
let rawBody = event.body || ''
|
||||
if (event.isBase64Encoded && rawBody) rawBody = Buffer.from(rawBody, 'base64').toString('utf8')
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const req = new http.IncomingMessage()
|
||||
req.method = method
|
||||
req.url = url
|
||||
req.headers = {}
|
||||
Object.keys(headers).forEach(k => { req.headers[k.toLowerCase()] = headers[k] })
|
||||
if (event.requestContext && event.requestContext.sourceIp) {
|
||||
req.headers['x-forwarded-for'] = req.headers['x-forwarded-for'] || event.requestContext.sourceIp
|
||||
}
|
||||
|
||||
const res = new http.ServerResponse(req)
|
||||
let body = ''
|
||||
const resHeaders = {}
|
||||
|
||||
res.writeHead = function (statusCode, reasonOrHeaders, maybeHeaders) {
|
||||
res.statusCode = statusCode
|
||||
const h = maybeHeaders || (typeof reasonOrHeaders === 'object' ? reasonOrHeaders : {})
|
||||
Object.assign(resHeaders, h)
|
||||
}
|
||||
|
||||
const originalSetHeader = res.setHeader.bind(res)
|
||||
res.setHeader = function (name, value) {
|
||||
resHeaders[name.toLowerCase()] = value
|
||||
originalSetHeader(name, value)
|
||||
}
|
||||
|
||||
res.end = function (chunk) {
|
||||
if (chunk) body += chunk
|
||||
resolve({
|
||||
isBase64Encoded: false,
|
||||
statusCode: res.statusCode || 200,
|
||||
headers: Object.assign({
|
||||
'content-type': 'application/json; charset=utf-8'
|
||||
}, resHeaders),
|
||||
body
|
||||
})
|
||||
}
|
||||
|
||||
res.write = function (chunk) { body += chunk }
|
||||
|
||||
if (rawBody) {
|
||||
req.push(rawBody)
|
||||
}
|
||||
req.push(null)
|
||||
|
||||
app(req, res)
|
||||
})
|
||||
}
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户