我在Firebase Functions上有一个带有HTTP终结点的API。它负责对接收到的数据进行一些处理,然后将这些数据存储到Firebase实时数据库中,但是我一直遇到一个问题,对象的一部分会覆盖整个对象,从而使数据无效且无用。问题是,尽管我使用了location
并设置了完整的对象路径,但是Google Geocoding完成后,该对象已经在数据库中,并且会被创建的.update()
对象覆盖。通过添加一些console.log()
函数,然后查看“函数”日志,我已经确认数据已经存在。
用于接收数据并对其进行处理的函数(已删除某些内容,例如图像处理,否则该函数太长而无法在此处粘贴):
exports = module.exports = (req, res, admin) => {
// allowed domains for this API endpoint
let domains = [
'http://localhost:4200',
'http://localhost:4300',
'http://localhost.local',
'http://www.localhost.local'
];
// make sure that only above domains are accessing it, otherwise return error
if (typeof req.headers.origin !== 'undefined') {
if (domains.indexOf(req.headers.origin) === -1) {
return res.status(403).send({
'status': 'error',
'type': 'not-authorized',
'message': 'You\'re not authorized to use this method!'
});
}
} else {
return res.status(403).send({
'status': 'error',
'type': 'malformed-request',
'message': 'Your request is missing CORS origin header!'
});
}
// errors
let errors = [];
// process uploaded form
const busboy = new Busboy({headers: req.headers});
// process attached fields
busboy.on('field', function(fieldname, val) {
req.body[fieldname] = val;
});
// now process the results
busboy.on('finish', () => {
let report_object = {},
report_object_location = {},
report_key = admin.database().ref().child('/reports/').push().key;
// check "location" and process if
if (typeof req.body.report_address !== 'undefined') {
if (req.body.report_address !== '') {
report_object['reports/' + report_key + '/location/address'] = req.body.report_address;
report_object['reports/' + report_key + '/location/unknown'] = false;
if (typeof req.body.report_latitude !== 'undefined') {
report_object['reports/' + report_key + '/location/latitude'] = parseFloat(req.body.report_latitude);
}
if (typeof req.body.report_longitude !== 'undefined') {
report_object['reports/' + report_key + '/location/longitude'] = parseFloat(req.body.report_longitude);
}
// Google Maps API for geocoding and reverse geocoding
const googleMapsClient = require('@google/maps').createClient({
key: 'xxx'
});
console.log('address: ', req.body.report_address);
if ((typeof req.body.report_latitude === 'undefined' || req.body.report_latitude === '0' || req.body.report_latitude === '' || req.body.report_latitude === 0) && typeof req.body.report_address !== 'undefined') {
console.log('geocoding executed');
googleMapsClient.geocode({'address': req.body.report_address, 'components': {'country':'XX'}}, function(error, response) {
if (!error) {
console.log('formatted: ' + response.json.results[0].formatted_address);
report_object_location['address'] = response.json.results[0].formatted_address.replace(', <country name>', '');
report_object_location['latitude'] = response.json.results[0].geometry.location.lat;
report_object_location['longitude'] = response.json.results[0].geometry.location.lng;
// added so that the data is saved directly and was hoping it won't be overwritten
report_object['reports/' + report_key + '/location/address'] = response.json.results[0].formatted_address.replace(', <country>', '');
report_object['reports/' + report_key + '/location/latitude'] = response.json.results[0].geometry.location.lat;
report_object['reports/' + report_key + '/location/longitude'] = response.json.results[0].geometry.location.lng;
response.json.results[0].address_components.forEach(result => {
if (typeof result.types !== 'undefined') {
if (result.types[0] === 'locality') {
report_object_location['city'] = result.long_name;
report_object['reports/' + report_key + '/location/city'] = result.long_name;
}
}
});
console.log('geocoding complete', new Date().getTime());
admin.database().ref('/reports/' + report_key + '/location').update(report_object_location);
} else {
console.log(error);
}
});
}
} else {
errors.push({
'field': 'report_address',
'type': 'missing',
'message': 'Please enter address'
});
}
} else {
errors.push({
'field': 'report_address',
'type': 'missing',
'message': 'Please enter address'
});
}
// check category and process it
if (typeof req.body.process !== 'undefined') {
if (req.body.process !== '') {
report_object['reports/' + report_key + '/category'] = utils.firebaseKeyEncode(req.body.process);
} else {
errors.push({
'field': 'process',
'type': 'missing',
'message': 'Please select process'
});
}
} else {
errors.push({
'field': 'process',
'type': 'missing',
'message': 'Please select process'
});
}
// check "subject" and process it
if (typeof req.body.subject !== 'undefined') {
if (req.body.subject !== '') {
report_object['reports/' + report_key + '/subject'] = utils.firebaseKeyEncode(req.body.subject);
}
}
// check "reporter" and process if
if (typeof req.body.reporter_name !== 'undefined' && req.body.reporter_name !== '') {
report_object['reports/' + report_key + '/reporter_name'] = req.body.reporter_name;
}
if (typeof req.body.reporter_address !== 'undefined' && req.body.reporter_address !== '') {
report_object['reports/' + report_key + '/reporter_address'] = req.body.reporter_address;
}
if (typeof req.body.reporter_phone !== 'undefined' && req.body.reporter_phone !== '') {
report_object['reports/' + report_key + '/reporter_phone'] = req.body.reporter_phone;
}
if (typeof req.body.reporter_notify !== 'undefined' && req.body.reporter_notify !== '') {
report_object['reports/' + report_key + '/reporter_notify'] = true;
}
if (typeof req.body.reporter_email !== 'undefined' && req.body.reporter_email !== '') {
const emailValidator = require('email-validator');
if (emailValidator.validate(req.body.reporter_email)) {
report_object['reports/' + report_key + '/reporter_email'] = req.body.reporter_email;
} else {
errors.push({
'field': 'reporter_email',
'type': 'invalid',
'message': 'Entered email is not valid!'
});
}
}
// check "note" and copy it
if (typeof req.body.notes !== 'undefined') {
if (req.body.notes !== '') {
report_object['reports/' + report_key + '/notes'] = req.body.notes;
}
}
// add current user
report_object['reports/' + report_key + '/created_user_display_name'] = 'Website';
// add created date & statuses
report_object['reports/' + report_key + '/datetime'] = admin.database.ServerValue.TIMESTAMP;
report_object['reports/' + report_key + '/status'] = 'open';
report_object['reports/' + report_key + '/status_updates/open'] = admin.database.ServerValue.TIMESTAMP;
// any errors?
if (errors.length > 0) {
return res.status(400).send({
'status': 'error',
'type': 'invalid-data',
'message': 'Please fix the data you provided data and re-submit.',
'errors': errors
});
}
// trigger function that saves the data
return exports.saveReportDetails(report_object, report_key, res, admin);
});
// required, otherwise the upload hangs
busboy.end(req.rawBody);
req.pipe(busboy);
};
将数据保存到数据库的功能:
exports.saveReportDetails = function(report_object, report_key, res, admin) {
// add icon marker
admin.database().ref('/settings').once('value', settingsData => {
admin.database().ref('/categories/' + report_object['reports/' + report_key + '/category']).once('value', categoryData => {
let settings = settingsData.val(),
category = categoryData.val(),
description = (typeof category.subjects[report_object['reports/' + report_key + '/subject']] !== 'undefined' && typeof category.subjects[report_object['reports/' + report_key + '/subject']].description !== 'undefined' ? category.subjects[report_object['reports/' + report_key + '/subject']].description : '');
report_object['reports/' + report_key + '/marker_icon'] = {
url: category.icon,
color: category.marker_color,
scaledSize: {
width: settings.map_marker_icon_size,
height: settings.map_marker_icon_size
}
};
let report_history_key = admin.database().ref().child('/reports_history/' + report_key + '/history').push().key;
report_object['reports_history/' + report_key + '/' + report_history_key + '/action'] = 'created';
report_object['reports_history/' + report_key + '/' + report_history_key + '/datetime'] = parseInt(moment().format('x'), 10);
report_object['reports_history/' + report_key + '/' + report_history_key + '/user_display_name'] = 'Website';
report_object['categories_reports/' + report_object['reports/' + report_key + '/category'] + '/' + report_key] = true;
if (report_object['reports/' + report_key + '/subject'] !== 'undefined') {
if (typeof category.subjects !== 'undefined') {
if (typeof category.subjects[report_object['reports/' + report_key + '/subject']] !== 'undefined') {
let subject = category.subjects[report_object['reports/' + report_key + '/subject']];
if (typeof subject.handling_days !== 'undefined') {
report_object['reports/' + report_key + '/handling_days'] = subject.handling_days;
}
if (typeof subject.user_key !== 'undefined' && typeof subject.user_display_name !== 'undefined') {
// report should be assigned to user
let report_history_key2 = admin.database().ref().child('/reports_history/' + report_key + '/history').push().key;
report_object['reports/' + report_key + '/assigned_user_key'] = subject.user_key;
report_object['reports/' + report_key + '/assigned_user_display_name'] = subject.user_display_name;
report_object['reports/' + report_key + '/assigned_user_type'] = subject.user_type;
report_object['reports/' + report_key + '/assigned_datetime'] = admin.database.ServerValue.TIMESTAMP;
report_object['reports/' + report_key + '/status'] = 'assigned';
report_object['reports/' + report_key + '/status_updates/assigned'] = admin.database.ServerValue.TIMESTAMP;
report_object['reports_history/' + report_key + '/' + report_history_key2 + '/action'] = 'assigned';
report_object['reports_history/' + report_key + '/' + report_history_key2 + '/assigned_user_key'] = subject.user_key;
report_object['reports_history/' + report_key + '/' + report_history_key2 + '/assigned_user_display_name'] = subject.user_display_name;
report_object['reports_history/' + report_key + '/' + report_history_key2 + '/datetime'] = admin.database.ServerValue.TIMESTAMP;
report_object['reports_history/' + report_key + '/' + report_history_key2 + '/user_key'] = false;
report_object['reports_history/' + report_key + '/' + report_history_key2 + '/user_display_name'] = 'auto-assigned';
report_object['users_assigned_reports/' + subject.user_key + '/' + report_key] = true;
}
}
}
}
if (typeof report_object['reports/' + report_key + '/subject'] !== 'undefined') {
report_object['subjects_reports/' + report_object['reports/' + report_key + '/subject'] + '/' + report_key] = true;
}
let year = moment().format('Y');
admin.database().ref('/reports_count/' + year).once('value', data => {
let value = data.val();
let number = 0;
if (value !== null) {
number = parseInt(value, 10) + 1;
} else {
number = 1;
}
report_object['reports/' + report_key + '/unique_number'] = year + '#' + number;
// assume all files have uploaded and push data into firebase
admin.database().ref('/').update(report_object);
console.log('save report', new Date().getTime());
});
// send confirmation email?
console.log(report_object['reports/' + report_key + '/reporter_email']);
if (typeof report_object['reports/' + report_key + '/reporter_email'] !== 'undefined') {
if (report_object['reports/' + report_key + '/reporter_email'] !== '' && report_object['reports/' + report_key + '/reporter_email'] !== false) {
const emails = require('./../utils/mailTransportModule');
emails.mailTransport.verify(error => {
if (error) {
console.log(error);
} else {
admin.database().ref('/settings').once('value', function(settings_data) {
let settings = settings_data.val(),
webapp_name = (typeof settings.webapp_name !== 'undefined' ? (settings.webapp_name !== '' ? settings.webapp_name : '<webapp name>') : '<webapp name>'),
webapp_url = (typeof settings.webapp_url !== 'undefined' ? (settings.webapp_url !== '' ? settings.webapp_url : '<webapp url>') : '<webapp url>'),
support_email = (typeof settings.support_email !== 'undefined' ? (settings.support_email !== '' ? settings.support_email : '<webapp email>') : '<webapp email>'),
support_phone = (typeof settings.support_phone !== 'undefined' ? (settings.support_phone !== '' ? settings.support_phone : '-') : '-'),
message = {
from: `"${webapp_name}" <${support_email}>`,
to: report_object['reports/' + report_key + '/reporter_email'],
replyTo: '<replyTo email>',
subject: `<subject>`,
text: emails.emails.newReportConfirmationText(webapp_name, report_object['reports/' + report_key + '/datetime'], utils.firebaseKeyDecode(report_object['reports/' + report_key + '/category']), (report_object['reports/' + report_key + '/subject'] !== false && typeof report_object['reports/' + report_key + '/subject'] !== 'undefined' ? utils.firebaseKeyDecode(report_object['reports/' + report_key + '/subject']) : ''), (description !== 'undefined' ? description : ''), (report_object['reports/' + report_key + '/location/address'] !== false ? report_object['reports/' + report_key + '/location/address'] : '-'), support_email, support_phone),
html: emails.emails.newReportConfirmationHTML(webapp_name, webapp_url, report_object['reports/' + report_key + '/datetime'], utils.firebaseKeyDecode(report_object['reports/' + report_key + '/category']), (report_object['reports/' + report_key + '/subject'] !== false && typeof report_object['reports/' + report_key + '/subject'] !== 'undefined' ? utils.firebaseKeyDecode(report_object['reports/' + report_key + '/subject']) : ''), (description !== 'undefined' ? description : ''), (report_object['reports/' + report_key + '/location/address'] !== false ? report_object['reports/' + report_key + '/location/address'] : '-'), support_email, support_phone),
attachments: []
};
let images = _.filter(report_object, function(v, k){
return _.includes(k, '/images');
});
// check if any image or audio is available and attach them to the message
if (images.length) {
images.forEach((image, index) => {
if (image.startsWith('https://')) {
message.attachments.push({
filename: 'image_' + index + '.jpg',
href: image
});
}
});
}
emails.mailTransport.sendMail(message).then(() => {
}).catch(error => {
return Promise.reject('sendMail error: ' + error, message);
});
});
}
});
}
}
return res.status(200).send({
'status': 'success',
'type': 'report-saved',
'message': ' Report was successfully saved.'
});
});
});
};
我希望我错过了一些基本的知识,并且有人可以给我一些启示,因为我迷失了其他事情。
为功能包装JSON:
{
"name": "<name>",
"version": "0.0.1",
"description": "<description>",
"dependencies": {
"@google-cloud/storage": "^1.5.2",
"@google/maps": "^0.4.5",
"busboy": "^0.2.14",
"connect-busboy": "0.0.2",
"email-validator": "^1.1.1",
"express": "^4.16.2",
"firebase-admin": "^5.8.1",
"firebase-functions": "^0.8.1",
"lodash": "^4.17.4",
"moment": "^2.20.1",
"nodemailer": "^4.4.1",
"sharp": "^0.19.0",
"uuid-v4": "^0.1.0"
},
"scripts": {
"start": "node index.js",
"build": ""
},
"private": true
}
更新: 地理编码之前的示例对象:
{
"assigned_datetime" : 1536661321150,
"assigned_user_display_name" : "<name>",
"assigned_user_key" : "<key>",
"assigned_user_type" : "<type>",
"category" : "<category>",
"created_user_display_name" : "<name>",
"created_user_key" : "<key>",
"datetime" : 1536661321150,
"location" : {
"address" : "<full address>",
"city" : "<city>",
"latitude" : <decimal>,
"longitude" : <decimal>,
"unknown" : false
},
"marker_icon" : {
"color" : "#2962ff",
"scaledSize" : {
"height" : 38,
"width" : 38
},
"url" : "assets/img/icons/blue.png"
},
"notes" : "<notes>",
"printed" : true,
"reporter_address" : "<address>",
"reporter_email" : "<email>",
"reporter_name" : "<name>",
"reporter_notified" : 1537282713509,
"reporter_phone" : "<phone>",
"send_email" : true,
"status" : "resolved",
"status_updates" : {
"assigned" : 1536667369830,
"open" : 1536661321150,
"resolved" : 1537282713367
},
"subject" : "<subject>",
"unique_number" : "<number>"
}
经过地理编码并保存到reports/<report_key>/location
中的示例对象:
{
"location" : {
"address" : "<full address>",
"city" : "<city>",
"latitude" : <decimal>,
"longitude" : <decimal>,
"unknown" : false
}
}