const { Client } = require('ssh2'); const conn = new Client(); conn.on('ready', () => { conn.exec(`cat > /www/huishou/server/server.js << 'ENDOFFILE' const express = require('express') const cors = require('cors') const bodyParser = require('body-parser') const path = require('path') const fs = require('fs') const multer = require('multer') const XLSX = require('xlsx') const app = express() const port = process.env.PORT || 4000 app.use(cors({ origin: '*', methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization'] })) app.use(bodyParser.json()) app.use(bodyParser.urlencoded({ extended: true })) app.use(express.static(path.join(__dirname, '../dist/build/h5'))) app.use('/admin', express.static(path.join(__dirname, '../admin'))) app.use('/wx', express.static(path.join(__dirname, '../dist/build/mp-weixin'))) const iconDir = path.join(__dirname, 'icons') if (!fs.existsSync(iconDir)) { fs.mkdirSync(iconDir, { recursive: true }) } const storage = multer.diskStorage({ destination: function (req, file, cb) { cb(null, iconDir) }, filename: function (req, file, cb) { const ext = path.extname(file.originalname) const filename = 'icon_' + Date.now() + ext cb(null, filename) } }) const upload = multer({ storage: storage, limits: { fileSize: 5 * 1024 * 1024 }, fileFilter: function (req, file, cb) { const allowedTypes = ['image/png', 'image/jpeg', 'image/jpg', 'image/gif'] if (allowedTypes.includes(file.mimetype)) { cb(null, true) } else { cb(new Error('Only PNG, JPG, JPEG, GIF images are allowed')) } } }) app.use('/api/icons', express.static(iconDir)) const dataDir = path.join(__dirname, 'data') if (!fs.existsSync(dataDir)) { fs.mkdirSync(dataDir, { recursive: true }) } const pricesPath = path.join(dataDir, 'prices.json') const ordersPath = path.join(dataDir, 'orders.json') const usersPath = path.join(dataDir, 'users.json') const citiesPath = path.join(dataDir, 'cities.json') const districtsPath = path.join(dataDir, 'districts.json') const stationsPath = path.join(dataDir, 'stations.json') const siteUsersPath = path.join(dataDir, 'site_users.json') const regionsPath = path.join(dataDir, 'regions.json') const adminPath = path.join(dataDir, 'admin.json') const loadData = (filePath, defaultData) => { try { if (fs.existsSync(filePath)) { const content = fs.readFileSync(filePath, 'utf-8') return JSON.parse(content) } } catch (err) { console.error('Failed to load data:', err) } return defaultData } const saveData = (filePath, data) => { try { fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8') } catch (err) { console.error('Failed to save data:', err) } } const defaultPrices = [ { category: '001', name: '纸类', unit: 'kg', price: 1.2, icon: '' }, { category: '002', name: '金属', unit: 'kg', price: 2.5, icon: '' }, { category: '003', name: '易拉罐', unit: 'piece', price: 0.1, icon: '' }, { category: '004', name: '塑料', unit: 'kg', price: 3.0, icon: '' }, { category: '005', name: '织物', unit: 'kg', price: 0.8, icon: '' }, { category: '006', name: '塑料瓶', unit: 'piece', price: 0.05, icon: '' } ] const defaultOrders = [] const defaultUsers = [ { id: '1', name: 'Admin', phone: '18698102228', password: '123456', role: 'admin', createTime: '2024-01-01' }, { id: '2', name: 'Collector Wang', phone: '18698102228', password: '123456', role: 'collector', createTime: '2024-01-15' } ] const defaultCities = [ { id: '1', name: 'Tianjin' }, { id: '2', name: 'Beijing' }, { id: '3', name: 'Shanghai' }, { id: '4', name: 'Guangzhou' } ] const defaultDistricts = [ { id: 'd1', cityId: '1', name: 'Gaoxin' }, { id: 'd2', cityId: '1', name: 'Xiqing' }, { id: 'd3', cityId: '1', name: 'Baoshui' }, { id: 'd4', cityId: '2', name: 'Dongcheng' }, { id: 'd5', cityId: '2', name: 'Xicheng' }, { id: 'd6', cityId: '2', name: 'Chaoyang' }, { id: 'd7', cityId: '2', name: 'Haidian' }, { id: 'd8', cityId: '3', name: 'Huangpu' }, { id: 'd9', cityId: '3', name: 'Xuhui' }, { id: 'd10', cityId: '3', name: 'Pudong' }, { id: 'd11', cityId: '4', name: 'Tianhe' }, { id: 'd12', cityId: '4', name: 'Haizhu' }, { id: 'd13', cityId: '4', name: 'Yuexiu' } ] const defaultStations = [ { id: 's1', cityId: '1', districtId: 'd1', name: 'Ronghui Jingyuan' }, { id: 's2', cityId: '1', districtId: 'd1', name: 'Jinhui Jiangshan Mingzhu' }, { id: 's3', cityId: '1', districtId: 'd2', name: 'Huaxing Li' }, { id: 's4', cityId: '1', districtId: 'd2', name: 'Huaxing Nan Li' }, { id: 's5', cityId: '1', districtId: 'd3', name: 'Yijing Lanting' } ] const defaultSiteUsers = [ { id: 'su1', stationId: 's1', name: 'Xiwei Laoshan', type: 'merchant' }, { id: 'su2', stationId: 's1', name: 'New Merchant', type: 'merchant' }, { id: 'su3', stationId: 's1', name: 'New Individual', type: 'individual' }, { id: 'su4', stationId: 's2', name: 'New Merchant', type: 'merchant' }, { id: 'su5', stationId: 's2', name: 'New Individual', type: 'individual' }, { id: 'su6', stationId: 's3', name: 'New Merchant', type: 'merchant' }, { id: 'su7', stationId: 's3', name: 'New Individual', type: 'individual' }, { id: 'su8', stationId: 's4', name: 'New Merchant', type: 'merchant' }, { id: 'su9', stationId: 's4', name: 'New Individual', type: 'individual' }, { id: 'su10', stationId: 's5', name: 'New Merchant', type: 'merchant' }, { id: 'su11', stationId: 's5', name: 'New Individual', type: 'individual' } ] const defaultRegions = [] const defaultAdmin = [ { id: 1, username: 'admin', phone: '18698102228', password: '123456' } ] let prices = loadData(pricesPath, defaultPrices) let orders = loadData(ordersPath, defaultOrders) let users = loadData(usersPath, defaultUsers) let cities = loadData(citiesPath, defaultCities) let districts = loadData(districtsPath, defaultDistricts) let stations = loadData(stationsPath, defaultStations) let siteUsers = loadData(siteUsersPath, defaultSiteUsers) let regions = loadData(regionsPath, defaultRegions) let admin = loadData(adminPath, defaultAdmin) console.log('Database initialized') console.log('Prices:', JSON.stringify(prices, null, 2)) app.get('/', (req, res) => { res.sendFile(path.join(__dirname, '../dist/build/h5/index.html')) }) app.get('/api', (req, res) => { res.send(\` Yi Fen Bao - Backend Service

Yi Fen Bao

Backend Service Running

Frontend Applications

GET/H5 App
GET/adminAdmin Panel
GET/wxWeChat Mini Program

API Endpoints

GET/api/pricesGet price list
GET/api/ordersGet orders
POST/api/ordersCreate order
PUT/api/orders/:idUpdate order
DELETE/api/orders/:idDelete order
GET/api/usersGet users
POST/api/users/loginUser login
POST/api/usersRegister user
POST/api/admin/loginAdmin login
GET/api/citiesGet cities
GET/api/districts/:cityIdGet districts
GET/api/stations/:cityId/:districtIdGet stations
GET/api/site_users/:stationIdGet site users
\`) }) app.get('/api/prices', (req, res) => { res.json({ date: new Date().toISOString().split('T')[0], prices: prices }) }) app.get('/api/statistics', (req, res) => { const totalOrders = orders.length const totalAmount = orders.reduce((sum, order) => sum + (order.totalAmount || 0), 0) const totalWeight = orders.reduce((sum, order) => { return sum + order.items.reduce((itemSum, item) => itemSum + (item.weight || 0), 0) }, 0) const avgAmount = totalOrders > 0 ? totalAmount / totalOrders : 0 const statusStats = { pending: orders.filter(o => o.status === 'pending').length, completed: orders.filter(o => o.status === 'completed').length, cancelled: orders.filter(o => o.status === 'cancelled').length } const categoryStats = {} orders.forEach(order => { order.items.forEach(item => { const category = item.category || item.name || '其他' if (!categoryStats[category]) { categoryStats[category] = { name: item.name || category, orders: 0, weight: 0, amount: 0 } } categoryStats[category].orders++ categoryStats[category].weight += item.weight || 0 categoryStats[category].amount += item.amount || 0 }) }) const categoryStatsArray = Object.values(categoryStats).map(cat => ({ ...cat, weightPercent: totalWeight > 0 ? (cat.weight / totalWeight * 100) : 0, amountPercent: totalAmount > 0 ? (cat.amount / totalAmount * 100) : 0 })) res.json({ data: { totalOrders, totalAmount, totalWeight, avgAmount, statusStats, categoryStats: categoryStatsArray } }) }) app.get('/api/orders', (req, res) => { const processedOrders = orders.map(order => { const items = order.items.map(item => { const weight = item.weight || item.count || 0 const price = item.price || 0 return { ...item, amount: item.amount !== undefined ? item.amount : weight * price } }) const totalAmount = order.totalAmount !== undefined ? order.totalAmount : items.reduce((sum, item) => sum + (item.amount || 0), 0) return { ...order, items, totalAmount } }) res.json({ data: processedOrders.sort((a, b) => new Date(b.createTime) - new Date(a.createTime)) }) }) app.get('/api/orders/:id', (req, res) => { const order = orders.find(o => o.id === req.params.id) res.json(order || {}) }) app.post('/api/orders', (req, res) => { const { items, totalWeight, totalPrice, city, district, community, user, phone, address } = req.body const id = Date.now().toString() + Math.random().toString(36).substr(2, 9) const createTime = new Date().toISOString() const processedItems = items.map(item => { const weight = item.weight || item.count || 0 const price = item.price || 0 return { ...item, amount: weight * price } }) const totalAmount = processedItems.reduce((sum, item) => sum + (item.amount || 0), 0) const newOrder = { id, items: processedItems, totalAmount, totalWeight, totalPrice, city, district, community, user, phone, address, status: 'pending', createTime } orders.push(newOrder) saveData(ordersPath, orders) res.json({ success: true, order: newOrder }) }) app.put('/api/orders/:id', (req, res) => { const id = req.params.id const index = orders.findIndex(o => o.id === id) if (index === -1) { res.json({ success: false, message: 'Not found' }) return } const { status, items, totalWeight, totalPrice } = req.body if (status !== undefined) orders[index].status = status if (items !== undefined) orders[index].items = items if (totalWeight !== undefined) orders[index].totalWeight = totalWeight if (totalPrice !== undefined) orders[index].totalPrice = totalPrice saveData(ordersPath, orders) res.json({ success: true, message: 'Updated' }) }) app.delete('/api/orders/:id', (req, res) => { const id = req.params.id const index = orders.findIndex(o => o.id === id) if (index === -1) { res.json({ success: false, message: 'Not found' }) return } orders.splice(index, 1) saveData(ordersPath, orders) res.json({ success: true, message: 'Deleted' }) }) app.get('/api/users', (req, res) => { res.json(users.map(u => ({ id: u.id, name: u.name, phone: u.phone, role: u.role, createTime: u.createTime }))) }) app.post('/api/users/login', (req, res) => { const { phone, password } = req.body const user = users.find(u => u.phone === phone && u.password === password) if (user) { res.json({ success: true, data: { id: user.id, name: user.name, phone: user.phone, role: user.role } }) } else { res.json({ success: false, message: 'Wrong credentials' }) } }) app.post('/api/users', (req, res) => { const { name, phone, password, role = 'collector' } = req.body const existing = users.find(u => u.phone === phone) if (existing) { res.json({ success: false, message: 'Phone exists' }) return } const id = Date.now().toString() const createTime = new Date().toISOString().split('T')[0] const newUser = { id, name, phone, password, role, createTime } users.push(newUser) saveData(usersPath, users) res.json({ success: true, data: newUser }) }) app.post('/api/admin/login', (req, res) => { const { phone, password } = req.body const adminUser = admin.find(a => a.phone === phone && a.password === password) if (adminUser) { res.json({ success: true, data: { id: adminUser.id, username: adminUser.username, phone: adminUser.phone } }) } else { res.json({ success: false, message: '手机号或密码错误' }) } }) app.get('/api/cities', (req, res) => { res.json(cities) }) app.get('/api/districts/:cityId', (req, res) => { res.json(districts.filter(d => d.cityId === req.params.cityId)) }) app.get('/api/stations/:cityId/:districtId', (req, res) => { res.json(stations.filter(s => s.cityId === req.params.cityId && s.districtId === req.params.districtId)) }) app.get('/api/site_users/:stationId', (req, res) => { res.json(siteUsers.filter(u => u.stationId === req.params.stationId)) }) app.get('/api/regions/cities', (req, res) => { res.json({ success: true, data: cities }) }) app.post('/api/regions/cities', (req, res) => { const { name } = req.body if (!name) { res.json({ success: false, message: '请输入城市名称' }) return } const existing = cities.find(c => c.name === name) if (existing) { res.json({ success: true, data: existing }) return } const newCity = { id: Date.now().toString(36), name } cities.push(newCity) saveData(citiesPath, cities) res.json({ success: true, data: newCity }) }) app.put('/api/regions/cities/:id', (req, res) => { const { id } = req.params const { name } = req.body const index = cities.findIndex(c => c.id === id) if (index === -1) { res.json({ success: false, message: '城市不存在' }) return } cities[index].name = name saveData(citiesPath, cities) res.json({ success: true, data: cities[index] }) }) app.delete('/api/regions/cities/:id', (req, res) => { const { id } = req.params const index = cities.findIndex(c => c.id === id) if (index === -1) { res.json({ success: false, message: '城市不存在' }) return } cities.splice(index, 1) districts = districts.filter(d => d.cityId !== id) stations = stations.filter(s => s.cityId !== id) siteUsers = siteUsers.filter(u => { const station = stations.find(s => s.id === u.stationId) return station !== undefined }) saveData(citiesPath, cities) saveData(districtsPath, districts) saveData(stationsPath, stations) saveData(siteUsersPath, siteUsers) res.json({ success: true }) }) app.get('/api/regions/districts', (req, res) => { const result = districts.map(d => { const city = cities.find(c => c.id === d.cityId) return { ...d, cityName: city ? city.name : '' } }) res.json({ success: true, data: result }) }) app.post('/api/regions/districts', (req, res) => { const { cityId, name } = req.body if (!cityId || !name) { res.json({ success: false, message: '请填写完整信息' }) return } const existing = districts.find(d => d.cityId === cityId && d.name === name) if (existing) { res.json({ success: true, data: existing }) return } const newDistrict = { id: Date.now().toString(36), cityId, name } districts.push(newDistrict) saveData(districtsPath, districts) res.json({ success: true, data: newDistrict }) }) app.put('/api/regions/districts/:id', (req, res) => { const { id } = req.params const { cityId, name } = req.body const index = districts.findIndex(d => d.id === id) if (index === -1) { res.json({ success: false, message: '区域不存在' }) return } districts[index] = { ...districts[index], cityId, name } saveData(districtsPath, districts) res.json({ success: true, data: districts[index] }) }) app.delete('/api/regions/districts/:id', (req, res) => { const { id } = req.params const index = districts.findIndex(d => d.id === id) if (index === -1) { res.json({ success: false, message: '区域不存在' }) return } districts.splice(index, 1) stations = stations.filter(s => s.districtId !== id) siteUsers = siteUsers.filter(u => { const station = stations.find(s => s.id === u.stationId) return station !== undefined }) saveData(districtsPath, districts) saveData(stationsPath, stations) saveData(siteUsersPath, siteUsers) res.json({ success: true }) }) app.get('/api/regions/stations', (req, res) => { const result = stations.map(s => { const city = cities.find(c => c.id === s.cityId) const district = districts.find(d => d.id === s.districtId) return { ...s, cityName: city ? city.name : '', districtName: district ? district.name : '' } }) res.json({ success: true, data: result }) }) app.post('/api/regions/stations', (req, res) => { const { cityId, districtId, name } = req.body if (!cityId || !districtId || !name) { res.json({ success: false, message: '请填写完整信息' }) return } const newStation = { id: Date.now().toString(36), cityId, districtId, name } stations.push(newStation) saveData(stationsPath, stations) res.json({ success: true, data: newStation }) }) app.put('/api/regions/stations/:id', (req, res) => { const { id } = req.params const { cityId, districtId, name } = req.body const index = stations.findIndex(s => s.id === id) if (index === -1) { res.json({ success: false, message: '站点不存在' }) return } stations[index] = { ...stations[index], cityId, districtId, name } saveData(stationsPath, stations) res.json({ success: true, data: stations[index] }) }) app.delete('/api/regions/stations/:id', (req, res) => { const { id } = req.params const index = stations.findIndex(s => s.id === id) if (index === -1) { res.json({ success: false, message: '站点不存在' }) return } stations.splice(index, 1) siteUsers = siteUsers.filter(u => u.stationId !== id) saveData(stationsPath, stations) saveData(siteUsersPath, siteUsers) res.json({ success: true }) }) app.get('/api/regions/site-users', (req, res) => { const result = siteUsers.map(u => { const station = stations.find(s => s.id === u.stationId) return { ...u, stationName: station ? station.name : '' } }) res.json({ success: true, data: result }) }) app.post('/api/regions/site-users', (req, res) => { const { stationId, name, type = 'merchant' } = req.body if (!stationId || !name) { res.json({ success: false, message: '请填写完整信息' }) return } const newUser = { id: Date.now().toString(36), stationId, name, type } siteUsers.push(newUser) saveData(siteUsersPath, siteUsers) res.json({ success: true, data: newUser }) }) app.put('/api/regions/site-users/:id', (req, res) => { const { id } = req.params const { stationId, name, type } = req.body const index = siteUsers.findIndex(u => u.id === id) if (index === -1) { res.json({ success: false, message: '用户不存在' }) return } siteUsers[index] = { ...siteUsers[index], stationId, name, type } saveData(siteUsersPath, siteUsers) res.json({ success: true, data: siteUsers[index] }) }) app.delete('/api/regions/site-users/:id', (req, res) => { const { id } = req.params const index = siteUsers.findIndex(u => u.id === id) if (index === -1) { res.json({ success: false, message: '用户不存在' }) return } siteUsers.splice(index, 1) saveData(siteUsersPath, siteUsers) res.json({ success: true }) }) app.get('/api/regions', (req, res) => { res.json(regions) }) app.post('/api/regions', (req, res) => { const { city, district, community } = req.body const existing = regions.find(r => r.city === city && r.district === district && r.community === community) if (existing) { res.json({ success: true, message: 'Exists' }) return } regions.push({ id: regions.length + 1, city, district, community }) saveData(regionsPath, regions) res.json({ success: true, message: 'Added' }) }) app.delete('/api/regions/:id', (req, res) => { const id = parseInt(req.params.id) const index = regions.findIndex(r => r.id === id) if (index === -1) { res.json({ success: false, message: 'Not found' }) return } regions.splice(index, 1) saveData(regionsPath, regions) res.json({ success: true, message: 'Deleted' }) }) const importStorage = multer.diskStorage({ destination: function (req, file, cb) { cb(null, dataDir) }, filename: function (req, file, cb) { const ext = path.extname(file.originalname) const filename = 'import_' + Date.now() + ext cb(null, filename) } }) const importUpload = multer({ storage: importStorage, limits: { fileSize: 10 * 1024 * 1024 }, fileFilter: function (req, file, cb) { const allowedTypes = ['application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.ms-excel'] const allowedExtensions = ['.xlsx', '.xls'] const ext = path.extname(file.originalname).toLowerCase() if (allowedTypes.includes(file.mimetype) || allowedExtensions.includes(ext)) { cb(null, true) } else { cb(new Error('只支持 Excel 文件 (.xlsx, .xls)')) } } }) app.post('/api/regions/import', importUpload.single('file'), (req, res) => { try { if (!req.file) { res.json({ success: false, message: '请选择文件' }) return } const filePath = path.join(dataDir, req.file.filename) const workbook = XLSX.readFile(filePath) const sheetName = workbook.SheetNames[0] const worksheet = workbook.Sheets[sheetName] const jsonData = XLSX.utils.sheet_to_json(worksheet) let addedCities = 0 let addedDistricts = 0 let addedStations = 0 let skippedCities = 0 let skippedDistricts = 0 let skippedStations = 0 for (const row of jsonData) { const cityName = row['城市'] || row['city'] || row['City'] || '' const districtName = row['区域'] || row['district'] || row['District'] || row['区'] || '' const stationName = row['站点'] || row['station'] || row['Station'] || row['社区'] || row['community'] || '' if (!cityName) continue let cityId = cities.find(c => c.name === cityName)?.id if (!cityId) { cityId = Date.now().toString(36) + Math.random().toString(36).substr(2, 5) cities.push({ id: cityId, name: cityName }) addedCities++ } else { skippedCities++ } if (districtName) { let districtId = districts.find(d => d.cityId === cityId && d.name === districtName)?.id if (!districtId) { districtId = Date.now().toString(36) + Math.random().toString(36).substr(2, 5) districts.push({ id: districtId, cityId, name: districtName }) addedDistricts++ } else { skippedDistricts++ } if (stationName) { const existsStation = stations.find(s => s.cityId === cityId && s.districtId === districtId && s.name === stationName) if (!existsStation) { const stationId = Date.now().toString(36) + Math.random().toString(36).substr(2, 5) stations.push({ id: stationId, cityId, districtId, name: stationName }) addedStations++ } else { skippedStations++ } } } } saveData(citiesPath, cities) saveData(districtsPath, districts) saveData(stationsPath, stations) fs.unlinkSync(filePath) res.json({ success: true, message: `导入成功!新增城市:${addedCities}, 新增区域:${addedDistricts}, 新增站点:${addedStations}`, data: { addedCities, addedDistricts, addedStations, skippedCities, skippedDistricts, skippedStations } }) } catch (error) { console.error('导入失败:', error) res.json({ success: false, message: '导入失败: ' + error.message }) } }) app.listen(port, '0.0.0.0', () => { console.log(`Server running on http://0.0.0.0:${port}`) }) ENDOFFILE`, (err, stream) => { if (err) { console.log('ERR:', err); conn.end(); return; } stream.on('close', () => { console.log('=== 重启后端容器 ==='); conn.exec('cd /www/huishou && docker-compose restart backend', (err2, stream2) => { if (err2) { console.log('ERR2:', err2); conn.end(); return; } stream2.on('data', (d) => console.log(d.toString())); stream2.on('close', () => { setTimeout(() => { console.log('\n=== 测试管理员登录 ==='); conn.exec('curl -s -X POST http://127.0.0.1:4000/api/admin/login -H "Content-Type: application/json" -d \'{"phone":"18698102228","password":"123456"}\'', (err3, stream3) => { if (err3) { console.log('ERR3:', err3); conn.end(); return; } stream3.on('data', (d) => console.log(d.toString())); stream3.on('close', () => conn.end()); }); }, 3000); }); }); }); }); }); conn.connect({ host: 'hs.yifenbao.cn', port: 22, username: 'root', password: 'Torch1112' });