怎么创建网站校园表白墙江西网站设计欣赏

张小明 2026/1/9 10:13:16
怎么创建网站校园表白墙,江西网站设计欣赏,医院网站建设方案ppt,网站联盟day10放行拦截领取优惠卷地址其中所指的两个类#xff0c;分别是用户信息拦截器#xff08;只是存储用户信息#xff0c;不登录不报错#xff09;和登录校验拦截器#xff08;不登录会报错#xff09;/*** ****用户信息拦截器 ***/ public class UserInfoInterceptor imp…day10放行拦截领取优惠卷地址其中所指的两个类分别是用户信息拦截器只是存储用户信息不登录不报错和登录校验拦截器不登录会报错/*** ****用户信息拦截器 ***/ public class UserInfoInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 1.尝试获取头信息中的用户信息 String authorization request.getHeader(JwtConstants.USER_HEADER); // 2.判断是否为空 if (authorization null) { return true;//没有用户信息直接放行 } // 3.转为用户id并保存 try { Long userId Long.valueOf(authorization); UserContext.setUser(userId); return true; } catch (NumberFormatException e) { log.error(用户身份信息格式不正确{}, 原因{}, authorization, e.getMessage()); return true; } } Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // 清理用户信息 UserContext.removeUser(); } }可以看到这个拦截器就是判断用户是否登录未登录会直接拦截并且返回错误码。不过这个拦截器是通过UserContext.getUser()方法来判断用户是否登录的。也就是说它依赖于UserInfoInterceptor因此两个拦截器是有先后顺序的不能搞错。/*** ****登录拦截器 ***/ public class LoginAuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 1.尝试获取用户信息 Long userId UserContext.getUser(); // 2.判断是否登录 if (userId null) { response.setStatus(401); response.sendError(401, 未登录用户无法访问); // 2.3.未登录直接拦截 return false; } // 3.登录则放行 return true; } }那么问题来了为什么我们要把登录用户信息获取、登录拦截分别写到两个拦截器呢这是因为并不是所有的接口都对登录用户有需要有些接口可能登录或未登录都能访问。比如我们的查询发放中的优惠券功能。而有些接口则是要求必须登录才能访问。如果把所有功能放在一个拦截器也就意味着所有接口要么做拦截要求必须登录并且可以获取用户信息要么不做拦截无法获取登录用户信息。这不符合实际需求所以我们将两个拦截器分离。要知道拦截器定义好了以后要想生效必须经过SpringMVC的配置并且设置要拦截的路径。其中图片标注的依次为拦截路径和放行路径可以看出这里是一个典型的springboot的配置属性我们完全可以通过配置文件来修改。我们只要把需要放行的接口路径通过tj.auth.resource.excludeLoginPaths配置进去即可。该路径的层级与上述配置中的层级一一对应实现领取优惠券功能/*** ****思路分析首先查询优惠券信息从该信息中校验发放时间结束发放时间库存以及每个人 ****限制领取的数量当一切满足后将coupon的已经领取数量加1并在用户券表中将对应的数据写入。 ****需要注意的是当前使用的是mapper为了防止循环依赖。 ***/ Transactional public void receiveCoupon(Long couponId) { // 1.查询优惠券 Coupon coupon couponMapper.selectById(couponId); if (coupon null) { throw new BadRequestException(优惠券不存在); } // 2.校验发放时间 LocalDateTime now LocalDateTime.now(); if (now.isBefore(coupon.getIssueBeginTime()) || now.isAfter(coupon.getIssueEndTime())) { throw new BadRequestException(优惠券发放已经结束或尚未开始); } // 3.校验库存 if (coupon.getIssueNum() coupon.getTotalNum()) { throw new BadRequestException(优惠券库存不足); } Long userId UserContext.getUser(); // 4.校验并生成用户券 checkAndCreateUserCoupon(coupon, userId); } private void saveUserCoupon(Coupon coupon, Long userId) { // 1.基本信息 UserCoupon uc new UserCoupon(); uc.setUserId(userId); uc.setCouponId(coupon.getId()); // 2.有效期信息 LocalDateTime termBeginTime coupon.getTermBeginTime(); LocalDateTime termEndTime coupon.getTermEndTime(); if (termBeginTime null) { termBeginTime LocalDateTime.now(); termEndTime termBeginTime.plusDays(coupon.getTermDays()); } uc.setTermBeginTime(termBeginTime); uc.setTermEndTime(termEndTime); // 3.保存 save(uc); } private void checkAndCreateUserCoupon(Coupon coupon, Long userId){ // 1.校验每人限领数量 // 1.1.统计当前用户对当前优惠券的已经领取的数量 Integer count lambdaQuery() .eq(UserCoupon::getUserId, userId) .eq(UserCoupon::getCouponId, coupon.getId()) .count(); // 1.2.校验限领数量 if(count ! null count coupon.getUserLimit()){ throw new BadRequestException(超出领取数量); } // 2.更新优惠券的已经发放的数量 1 couponMapper.incrIssueNum(coupon.getId()); // 3.新增一个用户券 saveUserCoupon(coupon, userId); }public interface CouponMapper extends BaseMapperCoupon { Update(update coupon set issue_num issue_num 1 where id #{couponId} ) int incrIssueNum(Param(couponId) Long couponId); }实现兑换码兑换优惠券功能/*** ****思路分析先将兑换码解码拿到优惠券id再去redis中使用setbit去判断是否使用返回值为1为使用****0为未使用spring中显示的就是true和false先直接设置为使用拿到返回值判断是否为未使用如 ***果为未使用则去根据优惠券id查询判断是否过期最后再去校验一些列信息以及生成用户券。注这里 ***由于需要修改redis中的兑换信息因此校验和生成用户券的方法还需要添加一个参数优惠券id ****/ Transactional public void exchangeCoupon(String code) { // 1.校验并解析兑换码 long serialNum CodeUtil.parseCode(code); // 2.校验是否已经兑换 SETBIT KEY 4 1 这里直接执行setbit通过返回值来判断是否兑换过 boolean exchanged codeService.updateExchangeMark(serialNum, true); if (exchanged) { throw new BizIllegalException(兑换码已经被兑换过了); } try { // 3.查询兑换码对应的优惠券id ExchangeCode exchangeCode codeService.getById(serialNum); if (exchangeCode null) { throw new BizIllegalException(兑换码不存在); } // 4.是否过期 LocalDateTime now LocalDateTime.now(); if (now.isAfter(exchangeCode.getExpireTime()) { throw new BizIllegalException(兑换码已经过期); } // 5.校验并生成用户券 // 5.1.查询优惠券 Coupon coupon couponMapper.selectById(exchangeCode.getCouponId()); // 5.2.查询用户 Long userId UserContext.getUser(); // 5.3.校验并生成用户券更新兑换码状态 checkAndCreateUserCoupon(coupon, userId, serialNum); } catch (Exception e) { // 重置兑换的标记 0 codeService.updateExchangeMark(serialNum, false); throw e; } }/*** ****实现使用setbit去查询或者修改redis中兑换码的兑换状态 ****由于setbit只能使用opsForValue去调用因此不能一劳永逸的绑定key ***/ private final StringRedisTemplate redisTemplate; private BoundValueOperationsString, String serialOps; public ExchangeCodeServiceImpl(StringRedisTemplate redisTemplate) { this.redisTemplate redisTemplate; this.serialOps redisTemplate.boundValueOps(COUPON_CODE_SERIAL_KEY); } public boolean updateExchangeMark(long serialNum, boolean mark) { Boolean boo redisTemplate.opsForValue().setBit(COUPON_CODE_MAP_KEY, serialNum, mark); return boo ! null boo; }/*** ****校验并生成用户券的方法修改 ***/ private void checkAndCreateUserCoupon(Coupon coupon, Long userId, Integer serialNum){ // 1.校验每人限领数量 // 1.1.统计当前用户对当前优惠券的已经领取的数量 Integer count lambdaQuery() .eq(UserCoupon::getUserId, userId) .eq(UserCoupon::getCouponId, coupon.getId()) .count(); // 1.2.校验限领数量 if(count ! null count coupon.getUserLimit()){ throw new BadRequestException(超出领取数量); } // 2.更新优惠券的已经发放的数量 1 couponMapper.incrIssueNum(coupon.getId()); // 3.新增一个用户券 saveUserCoupon(coupon, userId); // 4.更新兑换码状态 if (serialNum ! null) { codeService.lambdaUpdate() .set(ExchangeCode::getUserId, userId) .set(ExchangeCode::getStatus, ExchangeCodeStatus.USED) .eq(ExchangeCode::getId, serialNum) .update(); } }解决并发安全问题领券的过程中有大量的校验这些校验逻辑在高并发的场景下很容易出现问题。因此我们必须对领券功能做并发测试看看是否会出现并发安全问题。并发测试比较常见的一种工具就是Jemeter了。可以根据资料总的jmx文件去测试。针对超卖问题经过测试确实出现了超卖或超发的现象优惠只有100个库存结果发放了100多张券已经严重的超出库存的限制。1.分析原因这里采用的是先查询再判断再更新的方案而以上三步操作并不具备原子性。单线程的情况下确实没有问题。但如果是多线程并发运行如果N个线程同时去查询N大于剩余库存此时大概率查询到的库存是充足的然后判断库存自然没问题。最后一起更新库存自然就会超卖。如下图的步骤总结一下原因是​多线程并行运行​多行代码操作共享资源但不具备原子性​。这就是典型的线程并发安全问题。而对应的解决方案自然很容易的想到——加锁。但是锁又有两种悲观锁和乐观锁。悲观锁是一种独占和排他的锁机制保守地认为数据会被其他事务修改所以在整个数据处理过程中将数据处于锁定状态。乐观锁是一种较为乐观的并发控制方法假设多用户并发的不会产生安全问题因此无需独占和锁定资源。但在更新数据前会先检查是否有其他线程修改了该数据如果有则认为可能有风险会放弃修改操作。而对于悲观锁来说很显然该锁能解决安全性问题但是该锁会影响性能。乐观锁采用CASCompare And Set思想在更新数据前先判断数据与我之前查询到的是否一致不一致则证明有其它线程也在更新。为了避免出现安全问题放弃本次更新或者重新尝试一次。举个例子假设有A,B两个线程一起来抢夺仓库中的最后的一个库存AB线程先后进行查询操作当A线程修改的时候会判断数据库中的库存数量是否和查出来的一致如果一致则插入然后轮到B线程然后B线程也进行查询操作由于A线程已经插入已经数据库的数量和已经查出来的数量不一致最后插入失败乐观锁优点就是性能好、安全性也好缺点就是并发较高时可能出现更新成功率较低的问题并行的N个线程只会有1个成功。2.解决操作对更新成功率低的问题在优惠券库存这个业务中有一个乐观锁的改进方案​​我们无需判断issue_num是否与原来一致只要判断issue_num是否小于total_num即可。这样只issue_num小于total_num不管有多少线程来执行都会成功。​public interface CouponMapper extends BaseMapperCoupon { Update(update coupon set issue_num issue_num 1 where id #{couponId} AND issue_num total_num ) int incrIssueNum(Param(couponId) Long couponId); }再检查和保存用户券的逻辑上添加这段逻辑:​针对锁失效的问题除了优惠券库存判断领券时还有对于用户限领数量的判断。这部分的方法的代码逻辑也是按照三步骤去走的查询数据库、判断是否超出限领数量、新增用户券。那这里能不能用乐观锁呢很显然是不可以的乐观锁一般用在更新操作当中。而且这里用户和优惠券的关系并不具备唯一性因此新增时无法基于乐观锁做判断。因此只能使用悲观锁。也就是Synchronized或者Lock。1.锁的对象问题用户限领数量判断是针对单个用户的因此锁的范围不需要是整个方法只要锁定某个用户即可。所以这里建议采用Synchronized的代码块而不是同步方法。并且同步代码块的锁指定为用户id那么同一个用户并发操作时会被锁定不同用户互相没有影响整体效率也是可以接受的。加了上述锁之后会发现锁依然没有生效。加了锁但锁没生效可能的原因是什么答案是用了不同的锁。是因为toString方法底层是new 一个新的string因此每把锁都不一样。因此还需要使用String类中提供了一个intern方法。其原理就是获取字符串字面值对应到常量池中的字符串常量。因此需要做如下改造。经过测试后发现问题依然存在用户还是会超领。接着做如下分析2.事务边界问题其实这次的问题并不是由于锁导致的而是由于事务的隔离导致。​要知道整个领券发放是加了事务的,而在发放内部我们加锁处理限领数量的判断。整体业务流程是这样的开启事务、获取锁、统计用户已领券的数量、判断是否超出限领数量、如果没超新增一条用户券、释放锁、提交事务。这里是先开启事务再获取锁而业务执行完毕后是先释放锁再提交事务。接下来就能举个例子说流程了假设有两个进程A,BA进程先开启事务获取锁此时B进程也开启事务但是由于锁被获取了因此在等待。线程A做了一系列操作显而易见很轻松的成功了然后释放锁但是还没有提交事务哦此时B立马获取锁做了一些列操作由于A进程还没有提交事务那么进程B读取不到未提交数据。因此认为当前用户没有领券。判断限领数量通过于是也新增一条券安全问题发生了。总结由于锁过早释放导致了事务尚未提交判断出现错误最终导致并发安全问题发生。​这其实就是事务边界和锁边界的问题。因此解决的办法很简单在事务提交后再释放锁就行了。如下在检查和创建用户券的方法外上锁就行了。这里加事务同时事务修饰的方法必须为public方法总结在事务和锁并行存在时一定要考虑事务和锁的边界问题。由于事务的隔离级别问题可能会导致不同事务之间数据不可见往往会产生一些不可预期的现象。针对事务失效问题虽然解决了并发安全问题但其实我们的改造却埋下了另一个隐患。我们在领券业务的最后故意抛出一个异常。运行后发现事务并没有进行回滚。1.分析原因事务失效无非就以下原因很显然此处的问题就是非事务方法调用事务方法了而其事务失效的原因是方法内部调用走的是this而不是代理对象。那我们只要想办法获取代理对象不就可以了嘛。这里我们可以借助AspectJ来实现。2.解决方法1.添加依赖!--aspecj-- dependency groupIdorg.aspectj/groupId artifactIdaspectjweaver/artifactId /dependency2.启动类上添加注解3.使用代理对象练习实现查询我的优惠券功能/*** ****思路分析先从数据库中拿到用户券的分页记录发现还缺少很多VO所需要的coupon属性 ****因此将记录中的所有couponI的收集起来并去coupon表中查询对应的coupon信息最后 ****再将查出来的coupon信息已couponId为key封装成map方便后续查询然后遍历record ****将record信息复制给vo并填上缺失的coupon信息最后统一返回。 ****/ public PageDTOCouponVO queryMyCoupon(CouponQuery query) { // 根据用户ID和未使用状态查询用户优惠券分页数据 PageUserCoupon page lambdaQuery().eq(UserCoupon::getUserId, UserContext.getUser()) .eq(UserCoupon::getStatus, UserCouponStatus.UNUSED) .page(query.toMpPage()); // 获取分页记录 ListUserCoupon records page.getRecords(); // 如果记录为空返回空分页结果 if (CollUtils.isEmpty(records)) return PageDTO.empty(page); // 创建优惠券VO列表 ArrayListCouponVO list new ArrayList(records.size()); // 提取优惠券ID列表 ListLong couponIds records.stream().map(UserCoupon::getCouponId).collect(Collectors.toList()); //拿到对应的优惠券 ListCoupon coupons couponMapper.selectBatchIds(couponIds); MapLong, Coupon couponMap coupons.stream().filter(Objects::nonNull).collect(Collectors.toMap(Coupon::getId, coupon - coupon)); for (UserCoupon record : records) { CouponVO vo BeanUtils.copyBean(record, CouponVO.class); //补全缺失的属性 Coupon coupon couponMap.get(record.getCouponId()); vo.setName(coupon.getName()); vo.setSpecific(coupon.getSpecific()); vo.setDiscountType(coupon.getDiscountType()); vo.setThresholdAmount(coupon.getThresholdAmount()); vo.setMaxDiscountAmount(coupon.getMaxDiscountAmount()); vo.setDiscountValue(coupon.getDiscountValue()); list.add(vo); } return PageDTO.of(page, list); }完善兑换优惠券功能在原来的代码上加上获取代理对象的操作在用这个代理对象去调用checkAndCreateCoupon方法实现优惠券过期提醒功能/** * 处理过期优惠券的定时任务方法 * 使用XXL-JOB注解标记该定时任务处理器 * 具体来说这是个定时任务如果使用定时任务来实现的话那这个定时任务需要一直开启 *感觉性能不太行后续看有没有机会修改这部分代码吧 */ XxlJob(expireCouponJobHandler) public void expireCouponJobHandler() { //生产者消费者在message模块那边我也不知道咋实现 //查询过期前三天还未使用的优惠券 ListUserCoupon list userCouponService.lambdaQuery().eq(UserCoupon::getStatus, UserCouponStatus.UNUSED) .lt(UserCoupon::getTermEndTime, LocalDateTime.now().plusDays(3)) .list(); if(CollUtils.isEmpty(list)) return; //拿到用户id ListLong userIds list.stream().map(UserCoupon::getUserId).collect(Collectors.toList()); MapLong, UserDTO userMap userClient.queryUserByIds(userIds).stream().collect(Collectors.toMap(UserDTO::getId, u - u)); //拿到所有用户电话 ListString phones userIds.stream().map(userMap::get).map(UserDTO::getCellPhone).collect(Collectors.toList()); SmsInfoDTO message new SmsInfoDTO(); //设置电话 message.setPhones(phones); //设置模版字符串 message.setTemplateCode(expire_coupon); mqHelper.send(SMS_EXCHANGE, SMS_MESSAGE, message); }
版权声明:本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

成都工程网站建设我要推广

准备好迎接企业级应用开发的极速体验了吗?JeecgBoot作为一款功能强大的低代码开发平台,让您能够在短时间内构建出专业级的企业应用系统。无论您是初学者还是资深开发者,这份指南都将带您轻松掌握平台核心功能。 【免费下载链接】jeecg-boot …

张小明 2026/1/5 22:26:35 网站建设

网站seo诊断报告例子推广学校网站怎么做

从零开始搭建ESP32开发环境:Arduino IDE完整配置实战指南 你是不是也遇到过这样的情况?买了一块ESP32开发板,插上电脑却发现设备管理器里根本没有COM口;或者好不容易装好了Arduino IDE,一打开“开发板管理器”就卡在99…

张小明 2026/1/3 14:46:27 网站建设

建设婚恋网站用什么搭建苏州新闻

Langchain-Chatchat 0.3.1 Windows 部署实战指南 在企业知识管理日益智能化的今天,如何让内部文档“活起来”,成为员工随问随答的智能助手?一个安全、可控、本地化运行的知识库问答系统显得尤为关键。而 Langchain-Chatchat 正是这一需求下的…

张小明 2026/1/3 22:32:41 网站建设

冠县网站建设gxsh沧州市任丘建设局网站

简介 文章介绍了大模型场景下Human In The Loop (HITL)人机协作机制的重要性,详细讲解了LangChain的HumanInTheLoopMiddleWare如何通过中断机制实现人工审核,包括批准、修改或拒绝三种操作方式。作者分享了在AgentHub项目中实现HITL的具体技术改动&…

张小明 2026/1/8 0:25:32 网站建设

重庆建设网站甘肃省级建设主管部门网站

中国互联网络信息中心(CNNIC)最新发布的《生成式人工智能应用发展报告(2025)》,以详实数据和技术洞察勾勒出行业全貌,无论是 AI 研发从业者、技术决策者还是学习者,都能从中捕捉关键机遇。本文将…

张小明 2026/1/5 3:08:53 网站建设

做期货要关注哪些网站wordpress 写插件

效果视频:非洲动物检测yolo检测(https://mbd.pub/o/bread/mbd-ZpaYk51q)_哔哩哔哩_bilibili 资源包含可视化的非洲动物检测系统,基于最新的YOLOv8训练的非洲动物检测模型,和基于PyQt5制作的可视化非洲动物检测系统&am…

张小明 2026/1/5 5:11:21 网站建设