Verification code generation and verification technology
Before packaging the second kill token, the verification code is required to stagger the peak.
code:
Generate verification code
package com.miaoshaProject.util; import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; import java.awt.image.RenderedImage; import java.io.FileOutputStream; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.Random; /** * @author aric * @create 2021-08-11-17:05 * @fun */ public class CodeUtil { private static int width = 90; //Define picture width private static int height = 20; //Define picture height private static int codeCount = 4; //Defines the number of verification codes displayed on the picture private static int xx = 15; private static int fontHeight = 18; private static int codeY = 16; private static char[] codeSequence={'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q', 'R','S','T','U','V','W','X','Y','Z','0','1','2','3','4','5','6','7','8','9'}; /** * Generate a map collection * code Verification code generated for * codePic BufferedImage object for generated verification code * @return */ public static Map<String,Object> generateCodeAndPic(){ //Define picture buffer BufferedImage buffImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); //Graphics2D gd = buffImg.createGraphics(); //Graphics2D gd = (Graphics2D)buffImg.getGraphics(); Graphics gd = buffImg.getGraphics(); //Create a random number generator class Random random = new Random(); //Fill the image with white gd.setColor(Color.WHITE); gd.fillRect(0,0,width,height); //Create a font. The size of the font should be determined according to the height of the picture Font font = new Font("Fixedsys",Font.BOLD,fontHeight); //Set font gd.setFont(font); //Draw border gd.setColor(Color.BLACK); gd.drawRect(0,0,width-1,height-1); //40 interference lines are randomly generated, so that the authentication code in the image is not easy to be detected by other programs gd.setColor(Color.BLACK); for (int i = 0; i < 30; i++) { int x = random.nextInt(width); int y = random.nextInt(height); int xl = random.nextInt(12); int yl = random.nextInt(12); gd.drawLine(x,y,x+xl,y+yl); } //randomCode is used to save the randomly generated verification code so that users can verify after logging in StringBuffer randomCode = new StringBuffer(); int red = 0,green = 0,blue = 0; //Verification code of randomly generated codeCount number for (int i = 0; i < codeCount; i++) { //Get the randomly generated verification code number String code = String.valueOf(codeSequence[random.nextInt(36)]); //Generate random color components to construct color values, so that the color values of each array output will be different red = random.nextInt(255); green = random.nextInt(255); blue = random.nextInt(255); //Draw the verification code into the image with randomly generated colors gd.setColor(new Color(red,green,blue)); gd.drawString(code,(i+1)*xx,codeY); //Combine the generated four random numbers together randomCode.append(code); } Map<String,Object> map = new HashMap<>(); //Store verification code map.put("code",randomCode); //BufferedImage object for storing generated verification code map.put("codePic",buffImg); return map; } public static void main(String[] args) throws IOException { //Create file output stream object FileOutputStream out = new FileOutputStream("/Desktop/javaworkspace/miaoshaStable/" + System.currentTimeMillis()+".jpg"); Map<String, Object> map = CodeUtil.generateCodeAndPic(); ImageIO.write((RenderedImage) map.get("codePic"),"jepg",out); //Draw on the page System.out.println(""+map.get("code")); } }
Add verification code to the controller layer:
package com.miaoshaProject.controller; import com.miaoshaProject.error.BusinessException; import com.miaoshaProject.error.EmBusinessError; import com.miaoshaProject.mq.MqProducer; import com.miaoshaProject.response.CommonReturnType; import com.miaoshaProject.service.ItemService; import com.miaoshaProject.service.OrderService; import com.miaoshaProject.service.PromoService; import com.miaoshaProject.service.model.UserModel; import com.miaoshaProject.util.CodeUtil; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; import javax.annotation.PostConstruct; import javax.imageio.ImageIO; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.awt.image.RenderedImage; import java.io.IOException; import java.util.Map; import java.util.concurrent.*; /** * @author aric * @create 2021-07-02-18:41 * @fun */ @Controller("order") @RequestMapping("/user") @CrossOrigin(origins = {"*"}, allowedHeaders = "true") public class orderController extends BaseController { @Autowired private OrderService orderService; @Autowired private HttpServletRequest httpServletRequest; @Autowired private RedisTemplate redisTemplate; @Autowired private MqProducer mqProducer; @Autowired private ItemService itemService; @Autowired private PromoService promoService; private ExecutorService executorService; @PostConstruct public void init(){ executorService = Executors.newFixedThreadPool(20); //new comes out of a thread pool. Only 20 threads can be assigned to executorService } //Generate verification code @RequestMapping(value = "/generateverifycode", method = {RequestMethod.GET,RequestMethod.POST}) @ResponseBody public void generateverifycode(HttpServletResponse response) throws BusinessException, IOException { //Get the user's login information, and then read it from Redis String token = httpServletRequest.getParameterMap().get("token")[0]; //It can also be obtained from parameters if (StringUtils.isEmpty(token)) { throw new BusinessException(EmBusinessError.USER_NOT_LOGIN, "The user is not logged in,Unable to generate verification code"); } //Get user login information UserModel userModel = (UserModel) redisTemplate.opsForValue().get(token); if (userModel == null) { //To expire throw new BusinessException(EmBusinessError.USER_NOT_LOGIN, "The user is not logged in,Unable to generate verification code"); } Map<String, Object> map = CodeUtil.generateCodeAndPic(); redisTemplate.opsForValue().set("verify_code_"+userModel.getId(),map.get("code")); //Bind the verification code to the user Id redisTemplate.expire("verify_code_"+userModel.getId(),5,TimeUnit.MINUTES); ImageIO.write((RenderedImage) map.get("codePic"),"jepg",response.getOutputStream()); //Write to HttpServletResponse and return to the front end } //Generate second kill token @RequestMapping(value = "/generatetoken", method = {RequestMethod.POST}, consumes = {CONTENT_TYPE_FORMED}) @ResponseBody public CommonReturnType generatetoken(@RequestParam(name = "itemId") Integer itemId, @RequestParam(name = "promoId") Integer promoId, @RequestParam(name = "verifyCode")String verifyCode) throws BusinessException { //Get the user's login information, and then read it from Redis String token = httpServletRequest.getParameterMap().get("token")[0]; //It can also be obtained from parameters if (StringUtils.isEmpty(token)) { throw new BusinessException(EmBusinessError.USER_NOT_LOGIN, "The user is not logged in,Can't place an order"); } //Get user login information UserModel userModel = (UserModel) redisTemplate.opsForValue().get(token); if (userModel == null) { //To expire throw new BusinessException(EmBusinessError.USER_NOT_LOGIN, "The user is not logged in,Can't place an order"); } //Verify the validity of the verification code through verifycode String redisVerifyCode = (String)redisTemplate.opsForValue().get("verify_code_" + userModel.getId()); if(StringUtils.isEmpty(redisVerifyCode)){ throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR,"Illegal request"); } if(!redisVerifyCode.equalsIgnoreCase(verifyCode)){ throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR,"Illegal request, verification code error!"); } //Get second kill access token String promoToken = promoService.generateSecondKillToken(promoId, itemId, userModel.getId()); if (promoToken == null) { throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, "Failed to generate token"); } return CommonReturnType.create(promoToken); } //Encapsulate order request @RequestMapping(value = "/createorder", method = {RequestMethod.POST}, consumes = {CONTENT_TYPE_FORMED}) @ResponseBody public CommonReturnType createOrder(@RequestParam(name = "itemId") Integer itemId, @RequestParam(name = "promoId", required = false) Integer promoId, @RequestParam(name = "amount") Integer amount, @RequestParam(name = "promoToken") String promoToken) throws BusinessException { //Get the user's login information, and then read it from Redis // Boolean isLogin = (Boolean)httpServletRequest.getSession().getAttribute("IS_LOGIN"); String token = httpServletRequest.getParameterMap().get("token")[0]; //It can also be obtained from parameters if (StringUtils.isEmpty(token)) { throw new BusinessException(EmBusinessError.USER_NOT_LOGIN, "The user is not logged in,Can't place an order"); } //Get user login information UserModel userModel = (UserModel) redisTemplate.opsForValue().get(token); if (userModel == null) { //To expire throw new BusinessException(EmBusinessError.USER_NOT_LOGIN, "The user is not logged in,Can't place an order"); } //Verify whether the second kill order is correct if (promoId != null) { String promoTokenInRedis = (String) redisTemplate.opsForValue().get("promo_token_" + promoId + "_userid_" + userModel.getId() + "_itemid_" + itemId); if (promoTokenInRedis == null) { throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, "Second kill token verification failed"); } if (!StringUtils.equals(promoToken, promoTokenInRedis)) { throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, "Second kill token verification failed"); } } //Synchronously call the submit method of the thread pool and the asynchronous thread of new Callable //A waiting queue with a congestion window of 20 is used for flood discharge Future<Object> future = executorService.submit(new Callable<Object>() { @Override public Object call() throws Exception { //Add inventory flow init status String stockLogId = itemService.initStockLog(itemId, amount); //Then complete the corresponding single transaction message mechanism if (!mqProducer.transactionAsyncReduceStock(userModel.getId(), itemId, promoId, amount, stockLogId)) { throw new BusinessException(EmBusinessError.UNKNOWN_ERROR, "Order failed"); } return null; } }); try { future.get(); } catch (InterruptedException e) { throw new BusinessException(EmBusinessError.UNKNOWN_ERROR); } catch (ExecutionException e) { throw new BusinessException(EmBusinessError.UNKNOWN_ERROR); } return CommonReturnType.create(null); } }
Principle and implementation of current limiting
Principle: the flow is much more than you think. It's better for the system to live than to hang up. You'd rather let only a few people use it than let everyone not use it
Scheme: 1 Concurrent 2 Token bucket algorithm (limited TPS) 3. Leaky bucket algorithm (smoothing network traffic, providing network services at a fixed rate, unable to cope with sudden traffic)
Token bucket algorithm:
Only 10 (several clients) tokens are issued every second. The timer prevents 10 tokens from entering the bucket every second. The corresponding client can enter 10 corresponding traffic in 1s and the next 10 in the next second. In this way, the performance of limiting the traffic in each interface is about 10TPS.
Leaky bucket algorithm:
Only one drop of water is allowed to pass at a time. When it is full, it will refuse and can't deal with emergencies.
Current limiting force: 1 Interface dimension 2 Total dimension
Interface dimension: set token bucket algorithm for each interface
Total dimension: each interface * 80% of the total number of interfaces is taken as the general dimension
Current limiting range: 1 Cluster current limiting 2 Single machine current limiting
Cluster current limiting: relying on redis or other middleware technologies as unified counters often leads to performance bottlenecks
Single machine current limiting: under the premise of load balancing, the average current limiting effect of single machine is better
code:
/** * @author aric * @create 2021-07-02-18:41 * @fun */ @Controller("order") @RequestMapping("/user") @CrossOrigin(origins = {"*"}, allowedHeaders = "true") public class orderController extends BaseController { private RateLimiter orderCreateRateLimiter; @PostConstruct public void init(){ orderCreateRateLimiter = RateLimiter.create(300); //Initializing current limiting, 300 times per second for a single machine (depending on the number of voltage measuring TPS per second) } //Encapsulate order request @RequestMapping(value = "/createorder", method = {RequestMethod.POST}, consumes = {CONTENT_TYPE_FORMED}) @ResponseBody public CommonReturnType createOrder(@RequestParam(name = "itemId") Integer itemId, @RequestParam(name = "promoId", required = false) Integer promoId, @RequestParam(name = "amount") Integer amount, @RequestParam(name = "promoToken") String promoToken) throws BusinessException { if(orderCreateRateLimiter.tryAcquire()){ //The underlying implementation is similar to the token bucket algorithm, but it can advance the request at the next time throw new BusinessException(EmBusinessError.RATELIMIT); } } }
Cattle control technology
Anti brushing Technology: queuing and current limiting tokens can only control the total flow, but can not control the flow of scalpers.
Traditional anti brush:
1. Limit the number of interface calls of a session (session_id, token) in the same second / minute. Disadvantages: multi session intervention bypass is invalid.
2. Limit the number of interface calls of an ip in the same second / minute. Disadvantages: the quantity is not easy to control and is easy to be injured by mistake.
Why yellow cattle are difficult to prevent:
1. Simulator cheating: simulate hardware equipment and modify equipment information
2. Equipment: a batch of mobile devices in the studio
3. Manual cheating: attract part-time personnel to swipe bills by commission
Set fingerprint:
1. Collect various parameters of the terminal equipment and generate a unique device fingerprint when starting the application
2. Guess the probability of suspicious equipment such as simulator according to the parameters of corresponding equipment fingerprint.
3. Issue the certificate according to the fingerprint of the equipment
4. Take the certificate on the key service link and verify it on the certificate server by the business system
5. The certificate server determines the suspicious degree score of the corresponding certificate according to the equipment fingerprint parameters equivalent to the corresponding certificate and the real-time behavior risk control system
6. If the score is lower than a certain value, the business system returns a fixed error code, pulls up the front-end verification code for self-examination, and adds the corresponding score of the voucher server after the self-examination is successful.