如何为账户系统实现Discord式四位数字标签登录机制?
Got it, let's walk through implementing this Discord-style discriminator system for your user account setup—since you're working with MariaDB and the C++ REST SDK (Casablanca), I'll tailor every part to your tech stack:
First, you need a user table that enforces uniqueness for the username + discriminator pair, while allowing duplicate usernames. Here's a MariaDB schema that fits:
CREATE TABLE users ( id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL, discriminator CHAR(4) NOT NULL, -- Stores the 4-digit tag (e.g., "1234") password_hash VARCHAR(255) NOT NULL, -- NEVER store plaintext passwords! created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- Critical unique constraint to prevent duplicate username+tag pairs UNIQUE KEY idx_username_discriminator (username, discriminator) );
The idx_username_discriminator index ensures no two users share the exact same username and discriminator, while letting multiple users have the same username.
When a user signs up with a username, your system needs to generate a unique 4-digit tag for them. Here's how to implement this in C++ (assuming you're using the official MariaDB C++ connector):
#include <random> #include <unordered_set> #include <mysql/jdbc.h> std::string generateUniqueDiscriminator(const std::string& username, sql::Connection* dbConn) { // Fetch all existing discriminators for the target username sql::PreparedStatement* stmt = dbConn->prepareStatement( "SELECT discriminator FROM users WHERE username = ?" ); stmt->setString(1, username); sql::ResultSet* results = stmt->executeQuery(); std::unordered_set<std::string> usedTags; while (results->next()) { usedTags.insert(results->getString("discriminator")); } // Clean up DB resources delete results; delete stmt; // Generate a random 4-digit tag (with leading zeros) std::random_device rd; std::mt19937 gen(rd()); std::uniform_int_distribution<> dist(0, 9999); while (true) { int tagNum = dist(gen); char tagBuf[5]; snprintf(tagBuf, sizeof(tagBuf), "%04d", tagNum); // Format as 4-digit string (e.g., 0012) std::string candidateTag(tagBuf); if (usedTags.find(candidateTag) == usedTags.end()) { return candidateTag; } // Handle edge case: all 10000 tags are taken for this username if (usedTags.size() >= 10000) { throw std::runtime_error("No available discriminators for this username—please choose a different one."); } } }
This generates a random tag, checks if it's already in use for the username, and repeats until it finds an unused one.
Integrate the discriminator logic into your Casablanca-based signup endpoint. Don't forget to hash passwords with a secure algorithm like bcrypt or Argon2 (never store plaintext!):
#include <cpprest/http_listener.h> #include <cpprest/json.h> using namespace web; using namespace web::http; using namespace web::http::experimental::listener; // Assume you have a global/available DB connection pointer: sql::Connection* dbConn; // Assume you have functions: std::string hashPassword(const std::string&); and uint32_t getLastInsertId(sql::Connection*); void setupSignupEndpoint(http_listener& listener) { listener.support(methods::POST, "/api/auth/signup", [](http_request request) { request.extract_json().then([](json::value requestBody) { try { // Extract and validate input std::string username = requestBody.at(U("username")).as_string(); std::string password = requestBody.at(U("password")).as_string(); if (username.empty() || username.length() > 50 || password.empty()) { throw std::invalid_argument("Invalid username or password."); } if (username.find('#') != std::string::npos) { throw std::invalid_argument("Username cannot contain the '#' character."); } // Hash the password (use a battle-tested library like bcryptcpp) std::string passwordHash = hashPassword(password); // Generate unique discriminator std::string discriminator = generateUniqueDiscriminator(username, dbConn); // Insert user into DB sql::PreparedStatement* stmt = dbConn->prepareStatement( "INSERT INTO users (username, discriminator, password_hash) VALUES (?, ?, ?)" ); stmt->setString(1, username); stmt->setString(2, discriminator); stmt->setString(3, passwordHash); stmt->executeUpdate(); uint32_t userId = getLastInsertId(dbConn); delete stmt; // Return success response json::value response; response[U("id")] = json::value::number(userId); response[U("username")] = json::value::string(username); response[U("discriminator")] = json::value::string(discriminator); request.reply(status_codes::OK, response); } catch (const std::exception& ex) { request.reply(status_codes::BadRequest, json::value::string(ex.what())); } }); }); }
When a user logs in with username#1234, split the input to validate against the correct user:
struct User { uint32_t id; std::string username; std::string discriminator; }; // Assume you have a function: bool verifyPassword(const std::string& inputPass, const std::string& storedHash); bool validateLogin(const std::string& loginIdentifier, const std::string& password, sql::Connection* dbConn, User& outUser) { // Split the identifier into username and discriminator size_t hashPos = loginIdentifier.find('#'); if (hashPos == std::string::npos || hashPos == 0 || hashPos == loginIdentifier.length() - 1) { return false; // Invalid format (no #, or # at start/end) } std::string username = loginIdentifier.substr(0, hashPos); std::string discriminator = loginIdentifier.substr(hashPos + 1); // Validate discriminator is exactly 4 digits if (discriminator.length() != 4 || !std::all_of(discriminator.begin(), discriminator.end(), ::isdigit)) { return false; } // Fetch user from DB sql::PreparedStatement* stmt = dbConn->prepareStatement( "SELECT id, password_hash FROM users WHERE username = ? AND discriminator = ?" ); stmt->setString(1, username); stmt->setString(2, discriminator); sql::ResultSet* results = stmt->executeQuery(); if (results->next()) { std::string storedHash = results->getString("password_hash"); outUser.id = results->getUInt("id"); outUser.username = username; outUser.discriminator = discriminator; bool passwordValid = verifyPassword(password, storedHash); delete results; delete stmt; return passwordValid; } delete results; delete stmt; return false; // No matching user found }
- Race Conditions: If two signup requests for the same username hit your API at the same time, they might generate the same discriminator. Fix this by catching MariaDB's duplicate key error (error code 1062) during insertion, then re-generating the discriminator and trying again.
- Tag Generation Efficiency: For usernames with many existing users, random generation might cause collisions. You can alternatively query the highest existing discriminator, increment it, and wrap around to 0000 if you hit 9999 (though random is more aligned with Discord's behavior).
- Index Performance: The
idx_username_discriminatorunique index will speed up login queries significantly, as it's used to look up users during validation.
内容的提问来源于stack exchange,提问作者Panakotta00

