Implementation of limited time rush purchase
Use Redis to record the time of second kill goods and reject requests that have expired!
Put the second kill goods into Redis and set the expiration time
Use String type to set a certain expiration time in the form of kill + commodity id as key and commodity id as value (180 seconds here)
Introducing Redis dependency
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
Configure Redis
spring.redis.database=0 spring.redis.port=6379 spring.redis.host=localhost
Introduce the StringRedisTemplate object into the service
@Autowired private StringRedisTemplate stringRedisTemplate;
During second kill, first judge whether the goods of second kill have expired. If not, rush purchase can be carried out.
@Override public int kill(Integer id) { //Verify whether the second kill commodity times out if (!stringRedisTemplate.hasKey("kill" + id)) { throw new RuntimeException("The rush buying activity of current goods has ended"); } //Verify inventory Stock stock = checkStock(id); //Update inventory updateSale(stock); //Create order return createOrder(stock); }
Hiding of snap up interface
If the interface is not hidden, the user can get the connection of our rush purchase interface when entering the console in the client browser, write a crawler code, and directly request the interface to complete the order in the code before the second kill, which will cause harm. Therefore, the interface needs to be hidden.
- Each time you click the second kill button, first obtain a second kill verification value from the server (determine whether the second kill time is reached in the interface).
- Redis caches the verification value with the cached user ID and commodity ID as the key and the seckill address as the value.
- When users request second kill products, they should bring the second kill verification value for verification.
Add database table User
SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for user -- ---------------------------- DROP TABLE IF EXISTS `user`; CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'Primary key', `name` varchar(80) DEFAULT NULL COMMENT 'user name', `password` varchar(40) DEFAULT NULL COMMENT 'User password', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; SET FOREIGN_KEY_CHECKS = 1;
Create User entity class
@Data public class User { private Integer id; private String name; private String password; }
Create UserDao and UserDaoMapper
@Mapper public interface UserDao { User findById(Integer id); }
<mapper namespace="com.lany.miaosha.dao.UserDao"> <select id="findById" parameterType="Integer" resultType="User"> select id, name, password from miaosha.user where id = #{id} </select> </mapper>
Write business code
@Service @Transactional @Slf4j public class OrderServiceImpl implements OrderService { @Autowired private StockDao stockDao; @Autowired private OrderDao orderDao; @Autowired private UserDao userDao; @Autowired private StringRedisTemplate stringRedisTemplate; @Override public String getMd5(Integer id, Integer userid) { //Verify the legitimacy of users User user = userDao.findById(userid); if (user == null) throw new RuntimeException("User information does not exist!"); log.info("User information:[{}]", user.toString()); //Legal line of inspection Stock stock = stockDao.checkStock(id); if (stock == null) throw new RuntimeException("Illegal commodity information!"); log.info("Commodity information:[{}]", stock.toString()); //Generate hashkey String hashKey = "KEY_" + userid + "_" + id; //Generate md5 / / here! QS # is a randomly generated salt String key = DigestUtils.md5DigestAsHex((userid + id + "!Q*jS#").getBytes()); stringRedisTemplate.opsForValue().set(hashKey, key, 3600, TimeUnit.SECONDS); log.info("Redis write in:[{}] [{}]", hashKey, key); return key; } }
Write controller code and generate verification code
@RequestMapping("stock") @RestController @Slf4j public class StockController { @Autowired private OrderService orderService; //Method of generating md5 value @RequestMapping("md5") public String getMd5(Integer id, Integer userid) { String md5; try { md5 = orderService.getMd5(id, userid); } catch (Exception e) { e.printStackTrace(); return "obtain md5 fail: " + e.getMessage(); } return "obtain md5 Information is: " + md5; }
The foreground generates a string of verification codes by calling stock/md5 and stores them in Redis, and then carries the verification for second kill.
Write second kill interface
OrderController class
//Develop a second kill method optimistic lock to prevent oversold + token bucket algorithm to limit current @GetMapping("killtokenmd5") public String killtoken(Integer id,Integer userid,String md5) { System.out.println("Second kill commodity id = " + id); //Current limiting measures for adding token bucket if (!rateLimiter.tryAcquire(3, TimeUnit.SECONDS)) { log.info("Discard request: Rush purchase failed,The current second kill activity is too hot,Please try again"); return "Rush purchase failed,The current second kill activity is too hot,Please try again!"; } try { //Call the seckill business according to the seckill commodity id int orderId = orderService.kill(id,userid,md5); return "Second kill success,order id by: " + String.valueOf(orderId); } catch (Exception e) { e.printStackTrace(); return e.getMessage(); } }
OrderServiceImpl class
@Override public int kill(Integer id, Integer userid, String md5) { //Verify whether the second kill commodity in redis times out // if(!stringRedisTemplate.hasKey("kill"+id)) // throw new RuntimeException("the rush buying activity of the current commodity has ended ~ ~); //Verify the signature first String hashKey = "KEY_" + userid + "_" + id; String s = stringRedisTemplate.opsForValue().get(hashKey); if (s == null) throw new RuntimeException("No verification signature is carried,Illegal request!"); if (!s.equals(md5)) throw new RuntimeException("The current request data is illegal,Please try again later!"); //Verify inventory Stock stock = checkStock(id); //Update inventory updateSale(stock); //Create order return createOrder(stock); }
Single user restricted access frequency
Redis can be used to make access statistics for each user, or even bring a commodity id to make access statistics for a single commodity.
When the user applies for an order, check whether the user's access times exceed the specified access times. If they exceed the specified access times, they will not be allowed to place an order.
controller code
//Develop a second kill method optimistic lock to prevent oversold + token bucket algorithm to limit current @GetMapping("killtokenmd5limit") public String killtokenlimit(Integer id,Integer userid,String md5) { //Current limiting measures for adding token bucket if (!rateLimiter.tryAcquire(3, TimeUnit.SECONDS)) { log.info("Discard request: Rush purchase failed,The current second kill activity is too hot,Please try again"); return "Rush purchase failed,The current second kill activity is too hot,Please try again!"; } try { //Add single user limit call frequency int count = userService.saveUserCount(userid); log.info("The number of user visits up to this time is: [{}]", count); boolean isBanned = userService.getUserCount(userid); if (isBanned) { log.info("Purchase failed,Frequency limit exceeded!"); return "Purchase failed, frequency limit exceeded!"; } //Call the seckill business according to the seckill commodity id int orderId = orderService.kill(id,userid,md5); return "Second kill success,order id by: " + String.valueOf(orderId); } catch (Exception e) { e.printStackTrace(); return e.getMessage(); } }
Service interface and Implementation
public interface UserService { //Write user access times to redis int saveUserCount(Integer userId); //Judge the number of calls per unit time boolean getUserCount(Integer userId); }
realization
@Service @Transactional @Slf4j public class UserServiceImpl implements UserService{ @Autowired private StringRedisTemplate stringRedisTemplate; @Override public int saveUserCount(Integer userId) { //Generate key s for the number of calls according to different user IDs String limitKey = "LIMIT" + "_" + userId; //Gets the number of calls to the specified key in redis String limitNum = stringRedisTemplate.opsForValue().get(limitKey); int limit =-1; if (limitNum == null) { //The first call is put into redis and set to 0 stringRedisTemplate.opsForValue().set(limitKey, "0", 3600, TimeUnit.SECONDS); } else { //Not the first call, every time + 1 limit = Integer.parseInt(limitNum) + 1; stringRedisTemplate.opsForValue().set(limitKey, String.valueOf(limit), 3600, TimeUnit.SECONDS); } return limit;//Returns the number of calls } @Override public boolean getUserCount(Integer userId) { String limitKey = "LIMIT"+ "_" + userId; //The number of calls in the redis is obtained from the number of calls to the library user key. String limitNum = stringRedisTemplate.opsForValue().get(limitKey); if (limitNum == null) { //If it is empty, the key will be discarded directly, indicating that an exception occurred log.error("The user did not access the application verification value record, which is suspected to be abnormal"); return true; } return Integer.parseInt(limitNum) > 10; //false means no more than true means more than } }
Reference article: programming bad people