中文亚洲精品无码_熟女乱子伦免费_人人超碰人人爱国产_亚洲熟妇女综合网

當(dāng)前位置: 首頁(yè) > news >正文

網(wǎng)頁(yè)動(dòng)畫(huà)制作軟件網(wǎng)站seo專(zhuān)員

網(wǎng)頁(yè)動(dòng)畫(huà)制作軟件,網(wǎng)站seo專(zhuān)員,中山專(zhuān)業(yè)做網(wǎng)站公司,平臺(tái)推廣方案策劃書(shū)構(gòu)思了很多種講述這個(gè)簡(jiǎn)易版的秒殺項(xiàng)目的思路,比如按照功能分類(lèi),按照項(xiàng)目亮點(diǎn)串起來(lái)講述,總覺(jué)得不適合基礎(chǔ)薄弱的同學(xué)來(lái)學(xué)習(xí),所以本項(xiàng)目按照從搭建開(kāi)始,過(guò)程中需要什么來(lái)學(xué)習(xí)什么。 技術(shù)棧 SpringBootmybatisPlus&am…

????????構(gòu)思了很多種講述這個(gè)簡(jiǎn)易版的秒殺項(xiàng)目的思路,比如按照功能分類(lèi),按照項(xiàng)目亮點(diǎn)串起來(lái)講述,總覺(jué)得不適合基礎(chǔ)薄弱的同學(xué)來(lái)學(xué)習(xí),所以本項(xiàng)目按照從搭建開(kāi)始,過(guò)程中需要什么來(lái)學(xué)習(xí)什么。

技術(shù)棧

SpringBoot+mybatisPlus,MySQL,Redis,RabbitMQ

項(xiàng)目地址

ma/seckill

亮點(diǎn)

1.自定義注解(Spring AOP)

2.用戶密碼兩次MD5加密

????????第一次MD5加密:防止用戶明文密碼在網(wǎng)絡(luò)進(jìn)行傳輸

????????第二次MD5加密:防止數(shù)據(jù)庫(kù)被盜,避免通過(guò)MD5反推出密碼,雙重保險(xiǎn)

3.redis分布式鎖保證秒殺業(yè)務(wù)場(chǎng)景下的正確性

4.秒殺操作后的秒殺成功信息進(jìn)入RabbitMQ進(jìn)行排隊(duì)

?項(xiàng)目代碼結(jié)構(gòu)圖

準(zhǔn)備

創(chuàng)建SpringBoot項(xiàng)目,寫(xiě)入Maven文件,導(dǎo)入sql文件。

數(shù)據(jù)庫(kù)介紹

使用MYSQL

goods代表貨物信息表

order_info訂單詳情表

seckill_good秒殺商品表

seckill_order秒殺商品訂單表

user用戶表

Redis服務(wù)啟動(dòng)

這里使用的是Redis-windows版本,無(wú)密碼

RabbitMQ服務(wù)啟動(dòng)

如果你不會(huì)安裝RabbitMQ,請(qǐng)查看windows環(huán)境下安裝RabbitMQ(超詳細(xì))_windows安裝rabbitmq-CSDN博客

http://localhost:15672/#/保證本機(jī)RabbitMQ服務(wù)啟動(dòng)(賬號(hào)是默認(rèn)賬號(hào)guest/guest)

http://localhost:15672/#/

Coding

用戶登錄模塊

對(duì)應(yīng)LoginController.java

一共有兩個(gè)方法,一個(gè)是界面跳轉(zhuǎn)方法,略過(guò)

    @RequestMapping("/do_login")@ResponseBodypublic Result<String> doLogin(@Valid LoginParam loginParam, HttpServletResponse response){System.out.println(loginParam);//登陸String token = userService.login(response, loginParam);return Result.success(token);}

首先來(lái)說(shuō)一下整個(gè)方法的返回值Result.java

public class Result<T> {private int code;private String msg;private T data;private Result(T data) {this.code = CodeMsg.SUCCESS.getCode();this.msg = CodeMsg.SUCCESS.getMsg();this.data = data;}public boolean isSuccess(){return this.code==CodeMsg.SUCCESS.getCode();}public static <T> Result<T> success(T data){return new Result<T>(data);}public static <T> Result<T> error(CodeMsg codeMsg){return new Result<T>(codeMsg);}private Result(int code, String msg) {this.code = code;this.msg = msg;}private Result(CodeMsg codeMsg) {if(codeMsg != null) {this.code = codeMsg.getCode();this.msg = codeMsg.getMsg();}}public int getCode() {return code;}public void setCode(int code) {this.code = code;}public String getMsg() {return msg;}public void setMsg(String msg) {this.msg = msg;}public T getData() {return data;}public void setData(T data) {this.data = data;}
}

里面有三個(gè)變量,分別代表著結(jié)果代碼,結(jié)果信息,結(jié)果中返回的數(shù)據(jù),T代表泛型的意思,不懂的可以百度;CodeMsg代表的具體定義的一些成功和異常信息。

接下來(lái)我們?cè)俜祷豅oginController里面的doLogin方法,可以注意到在函數(shù)參數(shù)中使用了@Valid注解,代表著LoginParam需要進(jìn)行參數(shù)檢查,我們進(jìn)入LoginParam.java

import com.lgc.SeckillProject.vaildator.IsMobile;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.hibernate.validator.constraints.Length;import javax.validation.constraints.NotNull;@Getter
@Setter
@ToString
public class LoginParam {@NotNull(message = "手機(jī)號(hào)不能為空")@IsMobileprivate String mobile;@NotNull@Length(min = 23,message = "密碼長(zhǎng)度需要在7個(gè)字以?xún)?nèi)")private String password;
}

上面的代碼特意粘貼了使用注解來(lái)源于哪個(gè)包,明確一下注解的來(lái)源。

代碼中的兩個(gè)字段mobile和password兩個(gè)字段分別用@NotNull注解修飾,意思是兩個(gè)字段傳入的時(shí)候不允許為空。

另外password對(duì)字段的長(zhǎng)度進(jìn)行了限制.

拓展:

我們還注意到他使用了@IsMobile注解,這是一個(gè)自定義的注解,也是本項(xiàng)目中的一個(gè)亮點(diǎn)。

我們需要自定義注解,首先創(chuàng)建一個(gè)自定義注解類(lèi)

@Target({METHOD,FIELD,ANNOTATION_TYPE,CONSTRUCTOR,PARAMETER}) //注解的作用范圍 :方法,字段、枚舉的常量,注解,構(gòu)造函數(shù),方法參數(shù)
@Retention(RetentionPolicy.RUNTIME)   //注解的生命周期:默認(rèn)是class,RUNTIME:運(yùn)行時(shí)存在。RUNTIME>class>source
@Documented   //如果一個(gè)注解@B,被@Documented標(biāo)注,那么被@B修飾的類(lèi),生成文檔時(shí),會(huì)顯示@B。如果@B沒(méi)有被@Documented標(biāo)準(zhǔn),最終生成的文檔中就不會(huì)顯示@B。
@Constraint(validatedBy = {IsMobileValidator.class})//自定義約束
public @interface IsMobile {boolean required() default true;String message() default "手機(jī)號(hào)碼格式錯(cuò)誤";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};}

從上面看,有很多陌生的注解,我們一個(gè)一個(gè)來(lái)分析

首先是@Target注解,里面寫(xiě)入了這個(gè)注解的使用范圍,包括可以在方法上使用,字段、枚舉的常量,注解,構(gòu)造函數(shù),方法參數(shù)

@Retention(RetentionPolicy.RUNTIME)注解的生命周期,一般是RUNTIME,默認(rèn)是class;RUNTIME>CLASS>SOURCE

@Document .如果一個(gè)注解@B,被@Documented標(biāo)注,那么被@B修飾的類(lèi),生成文檔時(shí),會(huì)顯示@B。如果@B沒(méi)有被@Documented標(biāo)準(zhǔn),最終生成的文檔中就不會(huì)顯示@B

@Constraint 自定義約束,IsMobileValidator.class是自定義的約束類(lèi)

自定義注解中有幾個(gè)方法,并且每個(gè)方法中都有默認(rèn)的值。

接下來(lái)我們來(lái)看一下注解中自定義約束類(lèi)IsMobileValidator.java

public class IsMobileValidator implements ConstraintValidator<IsMobile,String> {private boolean required=false;@Overridepublic void initialize(IsMobile constraintAnnotation) {required=constraintAnnotation.required();}@Overridepublic boolean isValid(String value, ConstraintValidatorContext context) {if (required){return VaildatorUtil.isMobile(value);}else{if (StringUtils.isEmpty(value)){return true;}else{return VaildatorUtil.isMobile(value);}}}
}

首先實(shí)現(xiàn)接口ConstraintValidator,參數(shù)為自定義注解和String

重寫(xiě)里面的initialize方法還有isValid方法,require代表的是需不需要進(jìn)行驗(yàn)證,isValid是驗(yàn)證方法,如果是需要的驗(yàn)證的話,調(diào)用isValid方法,isValid里面又使用ValidatorUtil.isMobild方法,這個(gè)方法的內(nèi)部是使用正則表達(dá)式進(jìn)行實(shí)現(xiàn)的。

至此,自定義注解講解完成。

我們返回LoginController繼續(xù),我們來(lái)看一下userService.login這個(gè)方法,進(jìn)入U(xiǎn)serServiceImpl中l(wèi)ogin方法。

我們來(lái)分析一下上面的紅框中的代碼,為了防止從瀏覽器中輸入的密碼在網(wǎng)絡(luò)中明文傳輸,我們使用了MD5進(jìn)行加密,在從瀏覽器的輸入中獲取密碼后加入“鹽”使用MD5.formPassToDBPass進(jìn)行加密處理,然后再與數(shù)據(jù)庫(kù)中已加密的密碼進(jìn)行比較。

以上是加密的細(xì)節(jié)。

返回UserServiceImpl,繼續(xù)看login方法后半段生成cookie部分

使用UUIDUtil工具類(lèi)生成隨機(jī)的token ,進(jìn)入addCookie方法

//生成cookie
String token = UUIDUtil.uuid();
addCookie(response,token,user);
return token;

在Cookie方法中,把生成的token作為key的后半段,UserKey.token作為前半段,拼接構(gòu)成key值,user對(duì)象作為value值傳入Redis,并且生成一個(gè)Cookie對(duì)象放入response中。

private void addCookie(HttpServletResponse response,String token,User user){redisService.set(UserKey.token,token,user,UserKey.TOKEN_EXPIRE);Cookie cookie = new Cookie(COOKI_NAME_TOKEN, token);cookie.setMaxAge(UserKey.TOKEN_EXPIRE);cookie.setPath("/");response.addCookie(cookie);}

這里重點(diǎn)講解一下redisService.set方法的內(nèi)部邏輯

/*** 設(shè)置對(duì)象** @param prefix 對(duì)象Prefix* @param key    鍵* @param value  值* @param exTime 過(guò)期時(shí)間* @param <T>    返回類(lèi)型* @return*/public <T> boolean set(KeyPrefix prefix,String key,T value,int exTime){String str = beanToString(value);if (str==null || str.length() <= 0){return false;}//生成唯一keyString realKey = prefix.getPrefix() + key;//設(shè)置過(guò)期時(shí)間if (exTime<=0){stringRedisTemplate.opsForValue().set(realKey,str);}else{return stringRedisTemplate.opsForValue().setIfAbsent(realKey,str,exTime, TimeUnit.SECONDS);}return true;}

為了保證整個(gè)秒殺業(yè)務(wù)中商品數(shù)量的正確性,關(guān)鍵的一個(gè)步驟是并發(fā)場(chǎng)景下,鎖的競(jìng)爭(zhēng),在這里使用了redis的分布式鎖機(jī)制,也就是setIfAbsent這個(gè)方法,我認(rèn)為也是整個(gè)項(xiàng)目的關(guān)鍵之一。在本文中的最后部分《redis分布式鎖解析》詳細(xì)介紹一下這個(gè)東西。

訂單模塊

對(duì)應(yīng)orderController.java

訂單模塊的控制層只包含一個(gè)方法,info 方法,參數(shù)為user以及訂單號(hào),返回值為訂單詳情。

該模塊大多數(shù)為增刪改查以及簡(jiǎn)單的業(yè)務(wù)邏輯,較為簡(jiǎn)單,自己順著代碼看看就可以

貨物模塊

對(duì)應(yīng)GoodsController.java

首先針對(duì)list方法進(jìn)行分析。

為了應(yīng)對(duì)在SpringBoot中的高并發(fā)及優(yōu)化訪問(wèn)速度,我們一般會(huì)把頁(yè)面上的數(shù)據(jù)查詢(xún)出來(lái),然后放到redis中進(jìn)行緩存,減少數(shù)據(jù)庫(kù)的壓力。如果再進(jìn)行改進(jìn)的話,可以對(duì)整個(gè)界面進(jìn)行緩存。

@RequestMapping("/list")@ResponseBodypublic String list(Model model, HttpServletRequest request, HttpServletResponse response){//取緩存String html = redisService.get(GoodsKey.getGoodsList, "", String.class);if (!StringUtils.isEmpty(html)){return html;}//獲取數(shù)據(jù)綁定到modelList<GoodsVo> goodsVos = goodService.listGoodVo();model.addAttribute("goodsVos",goodsVos);WebContext ctx = new WebContext(request, response, request.getServletContext(), request.getLocale(), model.asMap());//手動(dòng)渲染html = thymeleafViewResolver.getTemplateEngine().process("goods_list", ctx);if (!StringUtils.isEmpty(html)){redisService.set(GoodsKey.getGoodsList,"",html,60);}return html;}

goodsDetail和detailStatic思路也是一樣的,無(wú)非就是添加了秒殺狀態(tài)還有倒計(jì)時(shí)這倆,思路和上面的代碼是一致的。自己順著代碼理一遍就可以

秒殺模塊

對(duì)應(yīng)模塊SeckillController.java

實(shí)現(xiàn)了InitializingBean接口,需要重寫(xiě)afterPropertiesSet方法,系統(tǒng)啟動(dòng)后,進(jìn)行初始化,將熱點(diǎn)數(shù)據(jù)放入redis中。

這里在方法里還定義了一個(gè)map,我們知道m(xù)ap的存儲(chǔ)位置是內(nèi)存中,所以我們將秒殺的商品的Id放入內(nèi)存中,查詢(xún)速度會(huì)飛快。

下面介紹一下秒殺接口1.0以及改進(jìn)版本,從一開(kāi)始的QPS793經(jīng)過(guò)優(yōu)化后QPS1658

首先是1.0版本? ? QPS:793 * 線程:5000 * 10 * 進(jìn)行秒殺

@RequestMapping("/do_seckill")public String seckill(Model model, User user, @RequestParam("goodsId")long goodsId){model.addAttribute("user",user);if (user==null){return "login";}//庫(kù)存判斷GoodsVo goodsVo = goodsService.getGoodsVoById(goodsId);int stock = goodsVo.getStockCount();if (stock<=0){model.addAttribute("ErrorMsg", CodeMsg.MIAO_SHA_OVER.getMsg());return "seckill_fail";}//判斷是否秒殺到了SeckillOrder order = orderService.getOrderByUserIdGoodsId(user.getId(), goodsId);if (order!=null){model.addAttribute("ErrorMsg",CodeMsg.REPEATE_MIAOSHA.getMsg());return "seckill_fail";}//減庫(kù)存下訂單,寫(xiě)入秒殺訂單OrderInfo orderInfo = seckillService.seckill(user, goodsVo);model.addAttribute("orderInfo",orderInfo);model.addAttribute("goods",goodsVo);return "order_detail";}

核心是將處理好的數(shù)據(jù)放到model,界面查詢(xún)速度偏慢

2.0版本? ??* QPS:1206 * 線程:5000 * 10 * 訂單頁(yè)面靜態(tài)化

@RequestMapping(value = "/seckill",method = RequestMethod.POST)@ResponseBodypublic Result<OrderInfo> seckillStatic(User user,@RequestParam("goodsId")long goodsId){if (user==null){return Result.error(CodeMsg.SESSION_ERROR);}//判斷庫(kù)存GoodsVo goods = goodsService.getGoodsVoById(goodsId);int stock=goods.getStockCount();if (stock<=0){return Result.error(CodeMsg.MIAO_SHA_OVER);}//判斷是否已經(jīng)秒殺到了SeckillOrder order = orderService.getOrderByUserIdGoodsId(user.getId(), goodsId);if (order!=null){return Result.error(CodeMsg.REPEATE_MIAOSHA);}//減少庫(kù)存,寫(xiě)入秒殺訂單OrderInfo orderInfo = seckillService.seckill(user, goods);return Result.success(orderInfo);}

整體的流程和1.0沒(méi)有區(qū)別,區(qū)別在于將最后的結(jié)果封裝在Result中了。速度還可以再提升

3.0版 * QPS:1658 * 線程:5000 * 10 * 加入消息隊(duì)列

@RequestMapping(value = "/{path}/seckill_mq",method = RequestMethod.POST)@ResponseBodypublic Result<Integer> seckillMq(User user, @RequestParam("goodsId")long goodsId, @PathVariable("path")String path){if (user==null){return Result.error(CodeMsg.SESSION_ERROR);}//驗(yàn)證pathboolean checkPath = seckillService.checkPath(user, goodsId, path);if (!checkPath){return Result.error(CodeMsg.REQUEST_ILLEGAL);}//內(nèi)存標(biāo)記,減少Redis訪問(wèn)Boolean over = localOverMap.get(goodsId);if (over){return Result.error(CodeMsg.MIAO_SHA_OVER);}//預(yù)減庫(kù)存Long stock = redisService.decr(GoodsKey.getSeckillGoodStock, "" + goodsId);if (stock<0){localOverMap.put(goodsId,true);return Result.error(CodeMsg.MIAO_SHA_OVER);}//判斷是否秒殺到了SeckillOrder order = orderService.getOrderByUserIdGoodsId(user.getId(), goodsId);if (order!=null){return Result.error(CodeMsg.REPEATE_MIAOSHA);}//壓入消息隊(duì)列中//入隊(duì)SeckillMessage sm = new SeckillMessage();sm.setUser(user);sm.setGoodsId(goodsId);sender.sendSeckillMessage(sm);return Result.success(0);//排隊(duì)中}

3.0版本一個(gè)是引入了本地map減少了Redis的訪問(wèn),另外使用了預(yù)減庫(kù)存,秒殺成功后將秒殺成功信息發(fā)送到RabbitMQ中,進(jìn)入隊(duì)列中排隊(duì)。

從代碼中可以看出預(yù)減成功后也只是進(jìn)入到了RabbitMQ中進(jìn)行排隊(duì),不至于阻塞,至于最后入庫(kù),還需要對(duì)隊(duì)列進(jìn)行監(jiān)聽(tīng)。

值得說(shuō)一點(diǎn)的是,秒殺接口操作的層次僅僅只在Redis中,所有操作的數(shù)據(jù)都在Redis中,所以此過(guò)程不存在與數(shù)據(jù)庫(kù)的任何操作,也就是說(shuō)你如果在秒殺過(guò)程中失敗了,不會(huì)影響到數(shù)據(jù)庫(kù)中的數(shù)據(jù),這是極為巧妙的,也是值得學(xué)習(xí)的,只有當(dāng)秒殺成功后,秒殺成功的消息放入MQ,并且MQ監(jiān)聽(tīng)到的時(shí)候,此時(shí)監(jiān)聽(tīng)到的信息才會(huì)真正的創(chuàng)建訂單并存入數(shù)據(jù)庫(kù)。

 @RabbitListener(queues = MQConfig.SECKILL_QUEUE)public void receive(String message){log.info("receive message:"+message);SeckillMessage sm = redisService.stringToBean(message, SeckillMessage.class);User user = sm.getUser();long goodsId = sm.getGoodsId();GoodsVo goods = goodsService.getGoodsVoById(goodsId);int stock=goods.getStockCount();if (stock<=0){return;}//判斷是否秒殺到了SeckillOrder order = orderService.getOrderByUserIdGoodsId(user.getId(), goodsId);if (order!=null){return;}//減庫(kù)存 下訂單 寫(xiě)入秒殺訂單seckillService.seckill(user,goods);}

上面的方法是RabbitMQ監(jiān)聽(tīng)隊(duì)列SECKILL_QUEUE,按照順序,從隊(duì)列中讀取數(shù)據(jù)同步數(shù)據(jù)庫(kù)操作。

/*** 客戶端輪詢(xún)查詢(xún)是否下單成功* orderId:成功* -1:秒殺失敗* 0: 排隊(duì)中*/@RequestMapping(value = "/result",method = RequestMethod.GET)@ResponseBodypublic Result<Long> seckillResult(@RequestParam("goodsId")long goodsId,User user){if (user==null){return Result.error(CodeMsg.USER_NO_LOGIN);}long result = seckillService.getSeckillResult(user.getId(), goodsId);return Result.success(result);}

前端輪訓(xùn)查詢(xún)是否下單成功,最終查詢(xún)的是數(shù)據(jù)庫(kù)中的數(shù)據(jù),而不是Redis中的數(shù)據(jù)。

/*** 獲取秒殺地址* 自定義接口限流:5秒內(nèi)最多訪問(wèn)5次,并需要為登錄狀態(tài)* @param user* @param goodsId* @return*/@AccessLimit(seconds = 5,maxCount = 5,needLogin = true)@RequestMapping(value = "/path",method = RequestMethod.GET)@ResponseBodypublic Result<String> getSeckillPath(User user,@RequestParam("goodsId")long goodsId){if (user==null){return Result.error(CodeMsg.USER_NO_LOGIN);}String path = seckillService.createPath(user, goodsId);return Result.success(path);}

上面是獲取秒殺地址的接口,因?yàn)橹饕牟l(fā)壓力在這個(gè)接口,所以需要對(duì)這個(gè)接口進(jìn)行限流。

核心點(diǎn)在@AccessLimit這個(gè)自定義注解中,來(lái)看一下自定義注解的定義

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AccessLimit {int seconds();int maxCount();boolean needLogin() default true;
}

注解不解釋了,里面定義了三個(gè)方法,seconds(),maxCount(),needLogin(),除了第三個(gè)方法從字面意思上能知道是干啥用的,其余頭倆都不清楚,這就引出了Spring一個(gè)很重要的特性,面向切面編程。

找到AccessInterceptor.java這個(gè)類(lèi),實(shí)現(xiàn)HandlerInterceptor接口,實(shí)現(xiàn)preHandle方法

@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {if (handler instanceof  HandlerInterceptor){//獲取用戶,并保存User user = getUser(request, response);UserContext.setUser(user);//獲取限流注解HandlerMethod hm = (HandlerMethod) handler;AccessLimit accessLimit = hm.getMethodAnnotation(AccessLimit.class);if (accessLimit==null){return true;}//自定義接口限流:時(shí)間、訪問(wèn)數(shù)、是否需要登錄//在conttoller方法上加上@AccessLimit(second=5,maxCount=5,needLogin=true)int seconds = accessLimit.seconds();int maxCount = accessLimit.maxCount();boolean needLogin = accessLimit.needLogin();String key = request.getRequestURI();if (needLogin){if (user==null){render(response, CodeMsg.SESSION_ERROR);return false;}key+="_"+user.getId();}else{//do nothing}//根據(jù)限流鍵值獲取緩存AccessKey ak = AccessKey.withExpire();Integer count = redisService.get(ak, key, Integer.class);if (count==null){redisService.set(ak,key,1,seconds);} else if (count<maxCount) {redisService.incr(ak,key);}else{render(response,CodeMsg.ACCESS_LIMIT_REACHED);return false;}}return true;}

Spring中AOP的概念將在上面的代碼中體現(xiàn)的淋漓盡致,首先是if判斷是否繼承自HandlerInterceptor,然后從request中獲取token,進(jìn)而得到當(dāng)前的用戶,得到用戶后與ThreadLocal進(jìn)行綁定,Threadlocal的底層是一個(gè)Map的結(jié)構(gòu)。

隨后我們基于當(dāng)前的handler處理器得到方法的注解,通過(guò)這個(gè)對(duì)象的獲取,我們可以拿到注解中各個(gè)參數(shù)的值。

如果說(shuō)注解標(biāo)記的方法需要登錄后才能使用,恰巧獲取的當(dāng)前用戶為空,需要返回給界面一些提示信息 ,比如像下面代碼這樣寫(xiě)

/*** 把提示返回給客戶端* @param response* @param cm* @throws Exception*/private void render(HttpServletResponse response, CodeMsg cm) throws Exception{response.setContentType("application/json;charset=UTF-8");OutputStream out = response.getOutputStream();String str = JSON.toJSONString(Result.error(cm));out.write(str.getBytes("UTF-8"));out.flush();out.close();}

之后我們需要根據(jù)限流鍵值對(duì)從redis中獲取此時(shí)這個(gè)方法目前已被訪問(wèn)的次數(shù)。

值得一提的是redisService.incr以及redisService.decr都是原子方法。

至此切面寫(xiě)完了,我們需要把切面注入到Spring中

來(lái)到WebConfig.java,實(shí)現(xiàn)WebMvcConfigurer接口,需要重寫(xiě)addArgumentResolvers還有addInterceptors。

addInterceptors這個(gè)方法是將自定義的accessInterceptor注冊(cè)進(jìn)來(lái)就可以

@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(accessInterceptor);}

addArgumentReslvers這個(gè)是對(duì)Controller層傳入的參數(shù)進(jìn)行處理,將處理好的參數(shù)傳給Controller里面的方法。詳情請(qǐng)查看《WebMvcConfigurer中addArgumentResolvers方法的使用》

我們?cè)谶@里使用的是自己定義的UserArgumentResolver.java這個(gè)類(lèi)。

@Service
public class UserArgumentResolver implements HandlerMethodArgumentResolver {@Overridepublic boolean supportsParameter(MethodParameter parameter) {Class<?> clazz = parameter.getParameterType();return clazz== User.class;}@Overridepublic Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {return UserContext.getUser();}
}

我們來(lái)簡(jiǎn)單介紹一下這個(gè)實(shí)現(xiàn)了HandlerMethodArgumentResolver接口的自定義方法,只有參數(shù)是User.class時(shí)才會(huì)生效,生效后會(huì)返回一個(gè)User對(duì)象。要想深入理解可參考:

https://blog.csdn.net/ocean35/article/details/105892788

結(jié)束----

自定義全局異常

自定義異常類(lèi)在項(xiàng)目中會(huì)經(jīng)常遇到,主要幫助用戶拋出自定義的異常,方便用戶理解。

public class GlobalException extends  RuntimeException{private static final long serialVersionUID=1L;private CodeMsg cm;public GlobalException(CodeMsg cm){super(cm.toString());this.cm=cm;}public CodeMsg getCm(){return cm;}
}

因?yàn)槔^承自RuntimeException所以必須有個(gè)super方法。

Redis配置類(lèi)

這里使用了Jedis的直接讀且application.properties文件的方法,比較新穎,如果以后在項(xiàng)目中碰到業(yè)務(wù)場(chǎng)景需加入redis并且要求配置簡(jiǎn)便的情況下,可以考慮這種

首先是RedisConfig這個(gè)配置 類(lèi),主要是與application.properties中的配置字段對(duì)應(yīng)上。

@Data
@Component
@ConfigurationProperties(prefix = "redis")
public class RedisConfig {private String host;private int port;private int timeout;private String password;private int poolMaxTotal;private int poolMaxIdle;private int poolMaxWait;
}

@ConfigurationProperties(prefix="redis")這與application.properties相對(duì)應(yīng)。

接下來(lái)是jedisPool創(chuàng)建操作

@Service
public class RedisPoolFactory {@Autowiredprivate RedisConfig redisConfig;@Beanpublic JedisPool JedisPoolFactory(){JedisPoolConfig poolConfig = new JedisPoolConfig();poolConfig.setMaxIdle(redisConfig.getPoolMaxIdle());poolConfig.setMaxTotal(redisConfig.getPoolMaxTotal());poolConfig.setMaxWaitMillis(redisConfig.getPoolMaxWait()*1000);JedisPool jp = new JedisPool(poolConfig, redisConfig.getHost(), redisConfig.getPort(),redisConfig.getTimeout() * 1000, redisConfig.getPassword(), 0);return jp;}
}

核心的一點(diǎn)就是創(chuàng)建JedisPool操作。

redis分布式鎖解析

知識(shí)點(diǎn)

setIfAbsent(key,value,時(shí)長(zhǎng),時(shí)長(zhǎng)單位):設(shè)置之前先判斷key值是否存在

setIfAbsent? 是redis(setnx)在java中的用法

思路

1.根據(jù)秒殺的業(yè)務(wù)場(chǎng)景,我們需要對(duì)秒殺商品的庫(kù)存數(shù)生成一個(gè)鎖,更改庫(kù)存數(shù)的時(shí)候先判斷庫(kù)存鎖是否有效和存在

2.如果庫(kù)存鎖存在返回一個(gè)錯(cuò)誤提示

3.如果庫(kù)存鎖不存在,對(duì)庫(kù)存數(shù)量進(jìn)行操作

4.執(zhí)行完整個(gè)邏輯后刪除庫(kù)存鎖

鎖的設(shè)計(jì)

stringRedisTemplate.opsForValue().setIfAbsent(realKey,str,exTime, TimeUnit.SECONDS);

realKey作為key

str作為value

exTime代表過(guò)期時(shí)間

TimeUnit.SECONDs代表時(shí)間單位

WebMvcConfigurer中addArgumentResolvers方法的使用

????????在Springboot中的WebMvcConfigurer接口在Web開(kāi)發(fā)中經(jīng)常被使用,例如配置攔截器、配置ViewController、配置Cors跨域等。

本文主要講解另一個(gè)方法:addArgumentResolvers()在實(shí)例中的應(yīng)用。

一、方法作用 該方法可以用在對(duì)于Controller中方法參數(shù)傳入之前對(duì)該參數(shù)進(jìn)行處理。然后將處理好的參數(shù)在傳給Controller中的方法。 官方API文檔解釋:添加解析器以支持自定義控制器方法參數(shù)類(lèi)型。 這不會(huì)覆蓋對(duì)解析處理程序方法參數(shù)的內(nèi)置支持。要自定義對(duì)參數(shù)解析的內(nèi)置支持,請(qǐng)RequestMappingHandlerAdapter直接配置。

二、場(chǎng)景描述 在權(quán)限場(chǎng)景中,通常會(huì)有要求用戶登錄之后才能訪問(wèn)的場(chǎng)景。對(duì)于這些問(wèn)題可以多種解決方案,如:使用Cookie+Session的會(huì)話控制、使用攔截器、使用SpringSecurity或shiro等權(quán)限管理框架等。 這里使用Cookie+Session處理。處理的邏輯為: 用戶第一次登錄之后會(huì)得到一個(gè)cookie,在以后每次的訪問(wèn)過(guò)程中都會(huì)攜帶Cookie進(jìn)行訪問(wèn)。在后臺(tái)的Controller中對(duì)于需要登錄權(quán)限的訪問(wèn)接口都要先獲取Cookie中的Token,再使用Token從session中獲取用戶登錄信息來(lái)判斷用戶登錄情況決定是否放行。

http://m.risenshineclean.com/news/58733.html

相關(guān)文章:

  • 網(wǎng)站建設(shè)找金手指排名網(wǎng)站站點(diǎn)
  • 網(wǎng)頁(yè)設(shè)計(jì)與網(wǎng)站建設(shè)完全教程代做百度首頁(yè)排名價(jià)格
  • 四合一小說(shuō)網(wǎng)站搭建教程seo網(wǎng)站seo
  • 什么是網(wǎng)絡(luò)設(shè)計(jì)制作360搜索引擎優(yōu)化
  • 網(wǎng)站建設(shè)的7個(gè)基本流程新網(wǎng)站seo
  • 什么網(wǎng)站可以自己做房子設(shè)計(jì)圖搜索關(guān)鍵詞排名
  • 網(wǎng)站怎么做的黑客入侵網(wǎng)課
  • web網(wǎng)站開(kāi)發(fā)實(shí)訓(xùn)總結(jié)seo服務(wù)合同
  • wordpress本地搭建網(wǎng)站a開(kāi)魯網(wǎng)站seo站長(zhǎng)工具
  • 用div css做網(wǎng)站首頁(yè)網(wǎng)站優(yōu)化外包多少錢(qián)
  • wordpress播放網(wǎng)盤(pán)中山百度seo排名公司
  • 網(wǎng)創(chuàng)八步的第七步整站優(yōu)化報(bào)價(jià)
  • 北京市政建設(shè)集團(tuán)有限責(zé)任公司網(wǎng)站站長(zhǎng)友情鏈接平臺(tái)
  • 沭陽(yáng)做網(wǎng)站shy1z百度百科推廣費(fèi)用
  • 全國(guó)最大的網(wǎng)站建設(shè)公司以下屬于網(wǎng)站seo的內(nèi)容是
  • 我想做跑腿網(wǎng)站怎么做下列哪些店鋪適合交換友情鏈接
  • 邯鄲網(wǎng)站設(shè)計(jì)價(jià)格長(zhǎng)春百度網(wǎng)站優(yōu)化
  • h5做網(wǎng)站b2b網(wǎng)站大全
  • 網(wǎng)絡(luò)組建與維護(hù)試題seo搜索引擎優(yōu)化報(bào)價(jià)
  • 惠州建站公司seo建站的步驟
  • 無(wú)刷新網(wǎng)站b站推廣網(wǎng)站入口202
  • b2c網(wǎng)站服務(wù)內(nèi)容國(guó)家提供的免費(fèi)網(wǎng)課平臺(tái)
  • 心理測(cè)試用什么網(wǎng)站做上海最近3天疫情情況
  • 做網(wǎng)站做小時(shí)seo加盟
  • 有什么做3維的案例網(wǎng)站濟(jì)南網(wǎng)站seo
  • 點(diǎn)擊網(wǎng)絡(luò)怎么做網(wǎng)站合肥百度seo排名
  • 做產(chǎn)品批發(fā)生意用什么類(lèi)型的網(wǎng)站好備案域名查詢(xún)
  • 兩學(xué)一做注冊(cè)網(wǎng)站嗎搜索引擎營(yíng)銷(xiāo)的特點(diǎn)有
  • 唯美個(gè)人網(wǎng)站欣賞黃頁(yè)網(wǎng)站推廣效果
  • 青島網(wǎng)站開(kāi)發(fā)企業(yè)百度提交入口網(wǎng)址截圖