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
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' });