How to generate Unique Order Id (just to show touser) with actual Order Id?
EDIT AGAIN : I don't want to create another question, so asking here. I have the same situation. But this time I need the algo in C language. Can any body help me.
I have following table.
CREATE TABLE IF NOT EXISTS `j741_order` (
`order_id` int(11) NOT NULL AUTO_INCREMENT,
`buyer_id` int(11) NOT NULL,
`subtotal` decimal(15,5) DEFAULT '0.00000',
`discount` decimal(15,5) NOT NULL DEFAULT '0.00000',
`shipping` decimal(15,5) DEFAULT '0.00000',
`tax` decimal(15,5) DEFAULT '0.00000',
`total` decimal(15,5) NOT NULL DEFAULT '0.00000',
`currency` char(3) DEFAULT NULL,
`status` int(11) NOT NULL DEFAULT '0',
`created_date` datetime NOT NULL,
`modified_date` datetime NOT NULL,
PRIMARY KEY (`order_id`),
KEY `idx_buyer_id` (`buyer_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
I want to generate a unique Order Id, (just to show to user) so that user can not guess what will be the next Order Id.
How can I get that Unique Random Order Id from original Order If
and get back original order Id from that Random Order Id开发者_如何学Go?
EDIT : I don't want to create any other field.
If your requirements are:
- It must be reversible (i.e. given just the "random" ID, you can find the original order_id)
- No extra columns
- You don't want to show the original/internal order_id to the user at all
then I would recommend some kind of two-way encryption. Hashing won't work as you can't find the original value from a hash.
I'm also adding that it should be human-friendly e.g. someone can call it out over the phone to you
I'm going to use a very simple two way encryption class located here, which was written by Tony Marston.
We want the solution to be human-friendly so let's remove some of the scramble chars. I've left only uppercase characters, numbers and the space and dash symbols. All of these can be easily communicated using the standard phonetic alphabet, and the forced use of uppercase removes any confusion as to what a character is.
These are the scramble strings I used (I used this online word scrambler rather than trying to scramble the string myself):
$this->scramble1 = '0123456789-ABCDEFGHIJKLMNOPQRSTUVWXYZ ';
$this->scramble2 = 'UKAH652LMOQ FBDIEG03JT17N4C89XPV-WRSYZ';
So the code to create our human-friendly order id is:
<?php
include 'encryption_class.php';
$crypt = new encryption_class();
$key = "A-COMPLETELY-RANDOM-KEY-THAT-I-HAVE-USED";
// Min length of 8 for encrypted string
$min_length = 8;
$order_id = 123456789;
print "Original: " . $order_id . PHP_EOL;
$encrypt_result = $crypt->encrypt($key, $order_id, $min_length);
print "Encrypted: " . $encrypt_result . PHP_EOL;
// DECRYPT
$decrypt_result = $crypt->decrypt($key, $encrypt_result);
print "Decrypted: " . $decrypt_result . PHP_EOL;
?>
(You need to download and save the *encryption_class* file locally, and include it).
I ran that code from the command line and received the following output:
Original: 123456789
Encrypted: 2UD5UIK9S
Decrypted: 123456789
Now we have our short, human-friendly order_id, which can be used in a URL such as http://myapp.example.com/order/view/2UD5UIK9S, and you never need to display or communicate the internal order_id to your users.
Notes:
The encrypted code will be unique once your order_id is unique (since it's a PK it will be)
This should not be used as a password encryption/decryption routine - don't store passwords, store hashes.
Make sure your secret key is random, complex and contains only the characters in your $scramble variables.
It obfuscates the order_id only.
Edit:
Although padding the input string (order_id) generates a certain amount of ramdomness, you could combine this with @biakaveron's answer to create a URL like http://myapp.example.com/order/view/5cc46aea44e898c3b4e1303eb18d8161302cd367/2UD5UIK9S
- Create your secret key (any string) and save in your config files (or DB config).
- Create unique ID:
$newId = hash_hmac('sha1', $orderId, $secret_key).'-'.$orderId;
. So, your order pages will looks likehttp://example.com/order/show/123456...absdef-123
. - You can quickly get original order ID and check it:
list($hash, $original) = explode($newId, '-', 2); if (hash_hmac('sha1', $original, $secret_key).'-'.$original === $hash) { // its a correct ID } else { // wrong hash, ignore it! }
This way original ID is public, but user cant replace it in site address because of unknown hashed string (first part of unique ID).
First of all, you should keep your order_id
as a physical identifier in your database : that field is an integer, it works well (your code is designed to use that -- and integers give better performances than string keys).
But you can add another field that would act as an identifier for the user :
- A
varchar(something)
orchar(something)
field, that would get a better display and would be harder to guess, - It would be displayed to the user,
- There would be a
UNIQUE
index on it, - But it would have no technical meaning for your code.
A GUID might be an idea -- but I have the fealing it might be a bit too long...
What about something based on the first letter of the user's name, the date, and some random number ?
It would be hard to guess, and still have a bit of meaning for the user...
Of course, you will not be able to calculate the order_id from that string identifier -- but if that one is unique, a simple query and you'll get the order_id back :
select order_id from your_table where nice_looking_id = '...';
You can kinda fudge it, but I would totally recommend adding another column. Here's how I'd do it with PHP.
// do your insert
// Retrieve last insert id
$orderId = mysql_insert_id();
// get the current timestamp
$time = time();
// Intersperse the $orderId into the $time to get a new "hash"
$orderId = explode("", (string)$orderId);
$time = explode("", (string)$time);
$orderIdLength = sizeof($orderId);
$newOrderId = "";
for ($i = 0; $i < $orderIdLength; ++$i) {
$newOrderId .= $orderId[$i] . $time[$i];
}
$newOrderId = (int)$newOrderId;
So, if your actual order id is 489, and the current timestamp is 1300778794, you end up getting an order id that looks like 418390. If your customer then uses that order id for anything, you simply have to break it back down:
$newOrderId = explode("", (string)$newOrderId);
$length = sizeof($newOrderId);
$oldOrderId = "";
for ($i = 0; $i < $length; $i = $i + 2) {
$oldOrderId .= $newOrderId[$i];
}
$oldOrderId = (int)$oldOrderId;
This isn't a very sophisticated approach and isn't 100% fool-proof. But for the sake of gently obfuscating your order ids, I think it does the job sufficiently.
Edit: just as a quick aside, you can use some other method of generating some semi-random number for padding the id other than time()
. For example, you could do rand(pow(10, log10($orderId)), pow(10, log10($orderId)+1))
, which would always return some random number that's the same length as the orderId.
Another, really simple method, is to base64 encode the OrderID. You pass the base64 encoded ID to the client instead of the actual ID and then decode the ID when it comes back. I would recommend removing the equal signs from the end of the encoded string.
The advantages:
- Really quick and simple obfuscation of the actual ID
Disadvantages:
- You have to remember to decode it
- A clever user would easily figure it out
$original_id = [whatever];
$salt = [very big private number/string/value];
$checksum = hexdec(substr(md5($original_id . $salt)), 0, 4); // Generate 16 bit checksum
while(strlen($checksum) < 5) // make sure it's five digits long
$checksum = "0" . $checksum;
$user_facing_id = $checksum . $original_id; // Prepend original with checksum
You can get the original ID back with substr($user_facing_id, 5), and you can even check if it's a valid order ID without interrogating your database by checking equality between dechex(substr($user_facing_id, 0, 5)) and substr(md5(substr($user_facing_id, 5) . $salt).
As others have said you can simply generate a hash of the order id with a known salt - while the computers don't really care if the order number is
854bf1176798d77ecaf6b66cbe71a8fc1b0c1847
or
36698
There's a big difference when it comes to wetware.
so that user can not guess what will be the next Order Id.
What are you really trying to avoid here? I would have thought that you just want to prevent users from seeing other peoples orders - in which case you just need to use the combination of order id and the user identifier for selecting orders.
Certainly the total volume of orders might be considered sensitive information - in which case, just use a sequence number tied to the user id. Although you didn't explicitly state which DBMS you are using, I assume it's mysql or a derivative based on the engine - but the following also applies to most relational DBMS:
If you add a column to the table describing users with a default value of 0, then you can add triggers on the order table to automatically fetch, increment and update this value for each insert on the order table - i.e. no change required to the code elsewhere except for supporting the 'external' order reference. Of course you'd need to seed this value with the relevant number of orders already placed - something like....
ALTER TABLE buyers ADD COLUMN buy_ref_last INTEGER DEFAULT 0;
ALTER TABLE orders ADD COLUMN buyer_order_ref INTEGER;
UPDATE orders o SET buyer_order_ref = (SELECT COUNT(*)
FROM orders r WHERE r.buyer_id=o.buyer_id
AND r.order_id<o.order_id);
(note I suspect the above won't run directly in MySQL, it doesn't like subselects on the same table as an update/delete - you'll need to unroll the logic into a procedure)
and....
UPDATE buyers b SET buy_ref_last=(SELECT MAX(buyer_order_ref)
FROM orders o WHERE o.buyer_id=b.id);
精彩评论