我正在使用knex.js作为我的数据库,我有一个取决于前一个查询的查询。
例:
用户表
|用户名(pk)| first_name | last_name |
登录表
|用户名(pk / fk)|哈希|
过程是:
插入用户>插入登录
login依赖于用户,因此如果尚未完成对用户的插入,则会返回错误。
这是我的代码:
const handleSignup = (req, res, db, logger, bcrypt) => {
const {
username,
password,
firstName,
lastName,
} = req.body;
const hash = bcrypt.hashSync(password);
if (username || !firstName || !lastName ) {
res.json({
haveEmpty: true
});
return;
} else {
db.transaction((trx) => {
db.select('*').from('user').where('username', '=', username)
.then(data => {
if (!data[0]) {
db('user')
.returning('*')
.insert({
username: username,
first_name: firstName,
last_name: lastName,
})
.then(user => {
db('login')
.returning('*')
.insert({
username: username,
hash: hash
})
.then(login => {
if (login[0]) {
res.json({
isSuccess: true
});
return;
} else {
res.json({
isSuccess: false
});
return;
}
})
.then(trx.commit)
.catch(err => {
logger.error(err);
trx.rollback;
res.render('pages/error-500');
});
})
.then(trx.commit)
.catch(err => {
logger.error(err);
trx.rollback;
res.render('pages/error-500');
});
} else {
res.json('User already Exist!');
return;
}
})
.then(trx.commit)
.catch(err => {
logger.error(err);
trx.rollback;
res.render('pages/error-500');
});
})
.catch(err => logger.error(err));
}
}
我不知道我是否正确使用交易。但这就是我的想法。之前,当我将查询分成两个promise时,我收到一个错误,因为它似乎没有完成第一个插入(用户)。
这段代码正在运行,但我知道有一种更正确的编码方式。
根据我的经验,一旦你停止尝试将它们全部塞入同一个功能,承诺就会开始变得更加自然! (但是我们所有人都可能在某个时候写过类似于你的例子的东西,不要担心。)
较小的代码块也倾向于更容易测试和调试。例如,如果您知道对请求正文中的变量的检查是正确的,那么问题可能就在堆栈的下方。
这是一个使用小型中间件堆栈的示例。这允许将操作分解成一口大小的块,同时仍然保证一件事会在另一件事之前发生。
const bcrypt = require("bcrypt");
const express = require("express");
const knex = require("knex");
const config = require("./knexfile").development;
const app = express();
app.use(express.json());
const db = knex(config);
const detailValidator = (req, res, next) => {
// You can do more robust validation here, of course
if (!req.body.firstName || !req.body.lastName) {
return next(new Error("Missing user details."));
}
next();
};
const userUniqueValidator = (req, res, next) => {
db("users")
.where("username", req.body.username)
.then(users => {
if (users.length !== 0) {
return next(new Error("User exists."));
}
next();
});
};
const userCreator = (req, res, next) => {
const { username, password, firstName, lastName } = req.body;
const hash = bcrypt.hashSync(password, 10);
db.transaction(trx =>
trx("users")
.insert({
username,
first_name: firstName,
last_name: lastName
})
.then(([userId]) => trx("auth").insert({ user_id: userId, hash }))
.then(() => res.json({ success: true }))
).catch(err => next(err));
};
app.post("/", detailValidator, userUniqueValidator, userCreator);
app.use((err, req, res, next) => res.json({ error: err.message }));
app.listen(4000, () => console.log("yup"));
关于Knex中的事务:如果使用上面的语法,你根本不需要调用commit
。但是,您需要使用trx
参数作为查询构建器。该文档还提出了另一种选择,即transacting
语法:请参阅docs。
最后,我不建议您使用您的用户名作为主键。它们经常需要更改,并且始终存在意外泄漏URL或日志中的风险的风险。不过,我建议包括一个独特的约束。也许这样的事情?
exports.up = knex =>
knex.schema.createTable("users", t => {
t.increments("id");
t.string("username").unique();
t.string("first_name");
t.string("last_name");
});
exports.up = knex =>
knex.schema.createTable("auth", t => {
t.increments("id");
t.integer("user_id").references("users.id");
t.string("hash");
});
值得注意的是,我使用SQLite3作为这个快速示例,它仅支持在插入后返回行id(因此在用户插入后[ userId ]
子句中的then
)。
在then then回调中返回Promise会一个接一个地执行promises,如下所示:
const handleSignup = (req, res, db, logger, bcrypt) => {
const {
username,
password,
firstName,
lastName,
} = req.body;
const hash = bcrypt.hashSync(password);
if (username || !firstName || !lastName) {
res.json({
haveEmpty: true
});
return;
}
db.transaction((trx) => {
db.select('*').from('user').where('username', '=', username)
.then(data => {
if (data[0]) {
res.json('User already Exist!');
return;
}
return db('user')
.returning('*')
.insert({
username: username,
first_name: firstName,
last_name: lastName,
});
})
.then(user => {
return db('login')
.returning('*')
.insert({
username: username,
hash: hash
});
})
.then(login => {
if (!login[0]) {
res.json({
isSuccess: false
});
return;
}
res.json({
isSuccess: true
});
})
.then(trx.commit)
.then(trx.commit)
.then(trx.commit)
.catch(err => {
logger.error(err);
trx.rollback;
res.render('pages/error-500');
});
})
.catch(err => logger.error(err));
}
我对你的代码不是100%肯定的事实是你只会回滚最后一个查询而不是所有查询。留意这一点。