谷粒商城—分布式高级②.md

news2025/4/8 12:34:00

认证服务

1. 环境搭建

创建gulimall-auth-server模块,导依赖,引入login.htmlreg.html,并把静态资源放到nginx的static目录下

2. 注册功能

(1) 验证码倒计时

在这里插入图片描述

//点击发送验证码按钮触发下面函数
$("#sendCode").click(function () {
   
		//如果有disabled,说明最近已经点过,则什么都不做
		if($(this).hasClass("disabled")){
   

		}else {
   
            //调用函数使得当前的文本进行倒计时功能
			timeOutChangeStyle();
			//发送验证码
			var phone=$("#phoneNum").val();
			$.get("/sms/sendCode?phone="+phone,function (data){
   
				if (data.code!=0){
   
					alert(data.msg);
				}
			})
		}
	})

	let time = 60;
	function timeOutChangeStyle() {
   
		//开启倒计时后设置标志属性disable,使得该按钮不能再次被点击
		$("#sendCode").attr("class", "disabled");
        //当时间为0时,说明倒计时完成,则重置
		if(time==0){
   
			$("#sendCode").text("点击发送验证码");
			time=60;
			$("#sendCode").attr("class", "");
		}else {
   
            //每秒调用一次当前函数,使得time--
			$("#sendCode").text(time+"s后再次发送");
			time--;
			setTimeout("timeOutChangeStyle()", 1000);
		}
	}
(2) 整合短信服务

在阿里云网页购买试用的短信服务

gulimall-third-party中编写发送短信组件,其中hostpathappcode可以在配置文件中使用前缀spring.cloud.alicloud.sms进行配置

@Data
@ConfigurationProperties(prefix = "spring.cloud.alicloud.sms")
@Controller
public class SmsComponent {
   

    private String host;
    private String path;
    private String appcode;

    public void sendCode(String phone,String code) {
   
//        String host = "http://dingxin.market.alicloudapi.com";
//        String path = "/dx/sendSms";
        String method = "POST";
//        String appcode = "你自己的AppCode";
        Map<String, String> headers = new HashMap<String, String>();
        //最后在header中的格式(中间是英文空格)为Authorization:APPCODE 83359fd73fe94948385f570e3c139105
        headers.put("Authorization", "APPCODE " + appcode);
        Map<String, String> querys = new HashMap<String, String>();
        querys.put("mobile",phone);
        querys.put("param", "code:"+code);
        querys.put("tpl_id", "TP1711063");
        Map<String, String> bodys = new HashMap<String, String>();


        try {
   
            /**
             * 重要提示如下:
             * HttpUtils请从
             * https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/src/main/java/com/aliyun/api/gateway/demo/util/HttpUtils.java
             * 下载
             *
             * 相应的依赖请参照
             * https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/pom.xml
             */
            HttpResponse response = HttpUtils.doPost(host, path, method, headers, querys, bodys);
            System.out.println(response.toString());
            //获取response的body
            //System.out.println(EntityUtils.toString(response.getEntity()));
        } catch (Exception e) {
   
            e.printStackTrace();
        }
    }
}

编写controller,给别的服务提供远程调用发送验证码的接口

@Controller
@RequestMapping(value = "/sms")
public class SmsSendController {
   

    @Resource
    private SmsComponent smsComponent;

    /**
     * 提供给别的服务进行调用
     * @param phone 电话号码
     * @param code 验证码
     * @return
     */
    @ResponseBody
    @GetMapping(value = "/sendCode")
    public R sendCode(@RequestParam("phone") String phone, @RequestParam("code") String code) {
   

        //发送验证码
        smsComponent.sendCode(phone,code);
        System.out.println(phone+code);
        return R.ok();
    }
}
(3) 接口防刷

由于发送验证码的接口暴露,为了防止恶意攻击,我们不能随意让接口被调用。

  • 在redis中以phone-code将电话号码和验证码进行存储并将当前时间与code一起存储
    • 如果调用时以当前phone取出的v不为空且当前时间在存储时间的60s以内,说明60s内该号码已经调用过,返回错误信息
    • 60s以后再次调用,需要删除之前存储的phone-code
    • code存在一个过期时间,我们设置为10min,10min内验证该验证码有效
@GetMapping("/sms/sendCode")
@ResponseBody
public R sendCode(@RequestParam("phone")String phone) {
   
   //接口防刷,在redis中缓存phone-code
    ValueOperations<String, String> ops = redisTemplate.opsForValue();
    String prePhone = AuthServerConstant.SMS_CODE_CACHE_PREFIX + phone;
    String v = ops.get(prePhone);
    if (!StringUtils.isEmpty(v)) {
   
        long pre = Long.parseLong(v.split("_")[1]);
        //如果存储的时间小于60s,说明60s内发送过验证码
        if (System.currentTimeMillis() - pre < 60000) {
   
            return R.error(BizCodeEnum.SMS_CODE_EXCEPTION.getCode(), BizCodeEnum.SMS_CODE_EXCEPTION.getMsg());
        }
    }
    //如果存在的话,删除之前的验证码
    redisTemplate.delete(prePhone);
    //获取到6位数字的验证码
    String code = String.valueOf((int)((Math.random() + 1) * 100000));
    //在redis中进行存储并设置过期时间
    ops.set(prePhone,code+"_"+System.currentTimeMillis(),10, TimeUnit.MINUTES);
    thirdPartFeignService.sendCode(phone, code);
    return R.ok();
}
(4) 注册接口编写

gulimall-auth-server服务中编写注册的主体逻辑

  • 若JSR303校验未通过,则通过BindingResult封装错误信息,并重定向至注册页面
  • 若通过JSR303校验,则需要从redis中取值判断验证码是否正确,正确的话通过会员服务注册
  • 会员服务调用成功则重定向至登录页,否则封装远程服务返回的错误信息返回至注册页面

注: RedirectAttributes可以通过session保存信息并在重定向的时候携带过去

 @PostMapping("/register")
    public String register(@Valid UserRegisterVo registerVo, BindingResult result, RedirectAttributes attributes) {
   
        //1.判断校验是否通过
        Map<String, String> errors = new HashMap<>();
        if (result.hasErrors()){
   
            //1.1 如果校验不通过,则封装校验结果
            result.getFieldErrors().forEach(item->{
   
                errors.put(item.getField(), item.getDefaultMessage());
                //1.2 将错误信息封装到session中
                attributes.addFlashAttribute("errors", errors);
            });
            //1.2 重定向到注册页
            return "redirect:http://auth.gulimall.com/reg.html";
        }else {
   
            //2.若JSR303校验通过
            //判断验证码是否正确
            String code = redisTemplate.opsForValue().get(AuthServerConstant.SMS_CODE_CACHE_PREFIX + registerVo.getPhone());
            //2.1 如果对应手机的验证码不为空且与提交上的相等-》验证码正确
            if (!StringUtils.isEmpty(code) && registerVo.getCode().equals(code.split("_")[0])) {
   
                //2.1.1 使得验证后的验证码失效
                redisTemplate.delete(AuthServerConstant.SMS_CODE_CACHE_PREFIX + registerVo.getPhone());

                //2.1.2 远程调用会员服务注册
                R r = memberFeignService.register(registerVo);
                if (r.getCode() == 0) {
   
                    //调用成功,重定向登录页
                    return "redirect:http://auth.gulimall.com/login.html";
                }else {
   
                    //调用失败,返回注册页并显示错误信息
                    String msg = (String) r.get("msg");
                    errors.put("msg", msg);
                    attributes.addFlashAttribute("errors", errors);
                    return "redirect:http://auth.gulimall.com/reg.html";
                }
            }else {
   
                //2.2 验证码错误
                errors.put("code", "验证码错误");
                attributes.addFlashAttribute("errors", errors);
                return "redirect:http://auth.gulimall.com/reg.html";
            }
        }
    }

通过gulimall-member会员服务注册逻辑

  • 通过异常机制判断当前注册会员名和电话号码是否已经注册,如果已经注册,则抛出对应的自定义异常,并在返回时封装对应的错误信息
  • 如果没有注册,则封装传递过来的会员信息,并设置默认的会员等级、创建时间
 @RequestMapping("/register")
    public R register(@RequestBody MemberRegisterVo registerVo) {
   
        try {
   
            memberService.register(registerVo);
            //异常机制:通过捕获对应的自定义异常判断出现何种错误并封装错误信息
        } catch (UserExistException userException) {
   
            return R.error(BizCodeEnum.USER_EXIST_EXCEPTION.getCode(), BizCodeEnum.USER_EXIST_EXCEPTION.getMsg());
        } catch (PhoneNumExistException phoneException) {
   
            return R.error(BizCodeEnum.PHONE_EXIST_EXCEPTION.getCode(), BizCodeEnum.PHONE_EXIST_EXCEPTION.getMsg());
        }
        return R.ok();
    }

public void register(MemberRegisterVo registerVo) {
   
    //1 检查电话号是否唯一
    checkPhoneUnique(registerVo.getPhone());
    //2 检查用户名是否唯一
    checkUserNameUnique(registerVo.getUserName());
    //3 该用户信息唯一,进行插入
    MemberEntity entity = new MemberEntity();
    //3.1 保存基本信息
    entity.setUsername(registerVo.getUserName());
    entity.setMobile(registerVo.getPhone());
    entity.setCreateTime(new Date());
    //3.2 使用加密保存密码
    BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
    String encodePassword = passwordEncoder.encode(registerVo.getPassword());
    entity.setPassword(encodePassword);
    //3.3 设置会员默认等级
    //3.3.1 找到会员默认登记
    MemberLevelEntity defaultLevel = memberLevelService.getOne(new QueryWrapper<MemberLevelEntity>().eq("default_status", 1));
    //3.3.2 设置会员等级为默认
    entity.setLevelId(defaultLevel.getId());

    // 4 保存用户信息
    this.save(entity);
}

private void checkUserNameUnique(String userName) {
   
    Integer count = baseMapper.selectCount(new QueryWrapper<MemberEntity>().eq("username", userName));
    if (count > 0) {
   
        throw new UserExistException();
    }
}

private void checkPhoneUnique(String phone) {
   
    Integer count = baseMapper.selectCount(new QueryWrapper<MemberEntity>().eq("mobile", phone));
    if (count > 0) {
   
        throw new PhoneNumExistException();
    }
}

3. 用户名密码登录

gulimall-auth-server模块中的主体逻辑

  • 通过会员服务远程调用登录接口
    • 如果调用成功,重定向至首页
    • 如果调用失败,则封装错误信息并携带错误信息重定向至登录页
@RequestMapping("/login")
public String login(UserLoginVo vo,RedirectAttributes attributes){
   
    R r = memberFeignService.login(vo);
    if (r.getCode() == 0) {
   
        return "redirect:http://gulimall.com/";
    }else {
   
        String msg = (String) r.get("msg");
        Map<String, String> errors = new HashMap<>();
        errors.put("msg", msg);
        attributes.addFlashAttribute("errors", errors);
        return "redirect:http://auth.gulimall.com/login.html";
    }
}

gulimall-member模块中完成登录

  • 当数据库中含有以当前登录名为用户名或电话号且密码匹配时,验证通过,返回查询到的实体
  • 否则返回null,并在controller返回用户名或密码错误
@RequestMapping("/login")
public R login(@RequestBody MemberLoginVo loginVo) {
   
    MemberEntity entity=memberService.login(loginVo);
    if (entity!=null){
   
        return R.ok();
    }else {
   
        return R.error(BizCodeEnum.LOGINACCT_PASSWORD_EXCEPTION.getCode(), BizCodeEnum.LOGINACCT_PASSWORD_EXCEPTION.getMsg());
    }
}

	@Override
    public MemberEntity login(MemberLoginVo loginVo) {
   
        String loginAccount = loginVo.getLoginAccount();
        //以用户名或电话号登录的进行查询
        MemberEntity entity = this.getOne(new QueryWrapper<MemberEntity>().eq("username", loginAccount).or().eq("mobile", loginAccount));
        if (entity!=null){
   
            BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
            boolean matches = bCryptPasswordEncoder.matches(loginVo.getPassword(), entity.getPassword());
            if (matches){
   
                entity.setPassword("");
                return entity;
            }
        }
        return null;
    }

4. 社交登录

(1) oauth2.0

在这里插入图片描述

(2) 在微博开放平台创建应用

在这里插入图片描述

(3) 在登录页引导用户至授权页
GET
https://api.weibo.com/oauth2/authorize?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=YOUR_REGISTERED_REDIRECT_URI
  • client_id: 创建网站应用时的app key
  • YOUR_REGISTERED_REDIRECT_URI: 认证完成后的跳转链接(需要和平台高级设置一致)

在这里插入图片描述

如果用户同意授权,页面跳转至 YOUR_REGISTERED_REDIRECT_URI/?code=CODE

code是我们用来换取令牌的参数

(4) 换取token
POST
https://api.weibo.com/oauth2/access_token?client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET&grant_type=authorization_code&redirect_uri=YOUR_REGISTERED_REDIRECT_URI&code=CODE
  • client_id: 创建网站应用时的app key
  • client_secret: 创建网站应用时的app secret
  • YOUR_REGISTERED_REDIRECT_URI: 认证完成后的跳转链接(需要和平台高级设置一致)
  • code:换取令牌的认证码

返回数据如下

在这里插入图片描述

(5) 获取用户信息

https://open.weibo.com/wiki/2/users/show

结果返回json

(6) 代码编写

认证接口

  • 通过HttpUtils发送请求获取token,并将token等信息交给member服务进行社交登录
  • 若获取token失败或远程调用服务失败,则封装错误信息重新转回登录页
@Controller
public class OauthController {
   

    @Autowired
    private MemberFeignService memberFeignService;

    @RequestMapping("/oauth2.0/weibo/success")
    public String authorize(String code, RedirectAttributes attributes) throws Exception {
   
        //1. 使用code换取token,换取成功则继续2,否则重定向至登录页
        Map<String, String> query = new HashMap<>();
        query.put("client_id", "2144***074");
        query.put("client_secret", "ff63a0d8d5*****29a19492817316ab");
        query.put("grant_type", "authorization_code");
        query.put("redirect_uri", "http://auth.gulimall.com/oauth2.0/weibo/success");
        query.put("code", code);
        //发送post请求换取token
        HttpResponse response = HttpUtils.doPost("https://api.weibo.com", "/oauth2/access_token", "post", new HashMap<String, String>(), query, new HashMap<String, String>());
        Map<String, String> errors = new HashMap<>();
        if (response.getStatusLine().getStatusCode() == 200) {
   
            //2. 调用member远程接口进行oauth登录,登录成功则转发至首页并携带返回用户信息,否则转发至登录页
            String json = EntityUtils.toString(response.getEntity());
            SocialUser socialUser = JSON.parseObject(json, new TypeReference<SocialUser>() {
   
            });
            R login = memberFeignService.login(socialUser);
            //2.1 远程调用成功,返回首页并携带用户信息
            if (login.getCode() == 0) {
   
                String jsonString = JSON.toJSONString(login.get("memberEntity"));
                MemberResponseVo memberResponseVo = JSON.parseObject(jsonString, new TypeReference<MemberResponseVo>() {
   
                });
                attributes.addFlashAttribute("user", memberResponseVo);
                return "redirect:http://gulimall.com";
            }else {
   
                //2.2 否则返回登录页
                errors.put("msg", "登录失败,请重试");
                attributes.addFlashAttribute("errors", errors);
                return "redirect:http://auth.gulimall.com/login.html";
            }
        }else {
   
            errors.put("msg", "获得第三方授权失败,请重试");
            attributes.addFlashAttribute("errors", errors);
            return "redirect:http://auth.gulimall.com/login.html";
        }
    }

登录接口

  • 登录包含两种流程,实际上包括了注册和登录
  • 如果之前未使用该社交账号登录,则使用token调用开放api获取社交账号相关信息,注册并将结果返回
  • 如果之前已经使用该社交账号登录,则更新token并将结果返回
@RequestMapping("/oauth2/login")
public R login(@RequestBody SocialUser socialUser) {
   
    MemberEntity entity=memberService.login(socialUser);
    if (entity!=null){
   
        return R.ok().put("memberEntity",entity);
    }else {
   
        return R.error();
    }
}

 @Override
    public MemberEntity login(SocialUser socialUser){
   
        MemberEntity uid = this.getOne(new QueryWrapper<MemberEntity>().eq("uid", socialUser.getUid()));
        //1 如果之前未登陆过,则查询其社交信息进行注册
        if (uid == null) {
   
            Map<String, String> query = new HashMap<>();
            query.put("access_token",socialUser.getAccess_token());
            query.put("uid", socialUser.getUid());
            //调用微博api接口获取用户信息
            String json = null;
            try {
   
                HttpResponse response = HttpUtils.doGet("https://api.weibo.com", "/2/users/show.json", "get", new HashMap<>(), query);
                json = EntityUtils.toString(response.getEntity());
            } catch (Exception e) {
   
                e.printStackTrace();
            }
            JSONObject jsonObject = JSON.parseObject(json);
            //获得昵称,性别,头像
            String name = jsonObject.getString("name");
            String gender = jsonObject.getString("gender");
            String profile_image_url = jsonObject.getString("profile_image_url");
            //封装用户信息并保存
            uid = new MemberEntity();
            MemberLevelEntity defaultLevel = memberLevelService.getOne(new QueryWrapper<MemberLevelEntity>().eq("default_status", 1));
            uid.setLevelId(defaultLevel.getId());
            uid.setNickname(name);
            uid.setGender("m".equals(gender)?0:1);
            uid.setHeader(profile_image_url);
            uid.setAccessToken(socialUser.getAccess_token());
            uid.setUid(socialUser.getUid());
            uid.setExpiresIn(socialUser.getExpires_in());
            this.save(uid);
        }else {
   
            //2 否则更新令牌等信息并返回
            uid.setAccessToken(socialUser.getAccess_token());
            uid.setUid(socialUser.getUid());
            uid.setExpiresIn(socialUser.getExpires_in());
            this.updateById(uid);
        }
        return uid;
    }

5. SpringSession

(1) session 原理

jsessionid相当于银行卡,存在服务器的session相当于存储的现金,每次通过jsessionid取出保存的数据

问题:但是正常情况下session不可跨域,它有自己的作用范围

在这里插入图片描述

(2) 分布式下session共享问题

在这里插入图片描述

(3) 解决方案
1) session复制

在这里插入图片描述

2) 客户端存储

在这里插入图片描述

3) hash一致性

在这里插入图片描述

4) 统一存储

在这里插入图片描述

(4) SpringSession整合redis

通过SpringSession修改session的作用域

在这里插入图片描述

1) 环境搭建

导入依赖

    <dependency>
        <groupId>org.springframework.session</groupId>
        <artifactId>spring-session-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

修改配置

spring:
  redis:
    host: 192.168.56.102
  session:
    store-type: redis

添加注解

@EnableRedisHttpSession
public class GulimallAuthServerApplication {
   
2) 自定义配置
  • 由于默认使用jdk进行序列化,通过导入RedisSerializer修改为json序列化

  • 并且通过修改CookieSerializer扩大session的作用域至**.gulimall.com

@Configuration
public class GulimallSessionConfig {
   

    @Bean
    public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
   
        return new GenericJackson2JsonRedisSerializer();
    }

    @Bean
    public CookieSerializer cookieSerializer() {
   
        DefaultCookieSerializer serializer = new DefaultCookieSerializer();
        serializer.setCookieName("GULISESSIONID");
        serializer.setDomainName("gulimall.com");
        return serializer;
    }
}
(5) SpringSession核心原理 - 装饰者模式
  • 原生的获取session时是通过HttpServletRequest获取的
  • 这里对request进行包装,并且重写了包装request的getSession()方法
@Override
protected void doFilterInternal(HttpServletRequest request,
      HttpServletResponse response, FilterChain filterChain)
      throws ServletException, IOException {
   
   request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);

    //对原生的request、response进行包装
   SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(
         request, response, this.servletContext);
   SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(
         wrappedRequest, response);

   try {
   
      filterChain.doFilter(wrappedRequest, wrappedResponse);
   }
   finally {
   
      wrappedRequest.commitSession();
   }
}

购物车

1. 数据模型分析

(1) 数据存储

购物车是一个读多写多的场景,因此放入数据库并不合适,但购物车又是需要持久化,因此这里我们选用redis存储购物车数据。

(2) 数据结构

在这里插入图片描述

一个购物车是由各个购物项组成的,但是我们用List进行存储并不合适,因为使用List查找某个购物项时需要挨个遍历每个购物项,会造成大量时间损耗,为保证查找速度,我们使用hash进行存储

在这里插入图片描述

(3) VO编写

购物项vo

在这里插入图片描述

public class CartItemVo {
   

    private Long skuId;
	
    //是否选中
    private Boolean check = true;

    //标题
    private String title;
	
    //图片
    private String image;

    //商品套餐属性
    private List<String> skuAttrValues;

    //价格
    private BigDecimal price;

    //数量
    private Integer count;

    //总价
    private BigDecimal totalPrice;
    
      /**
     * 当前购物车项总价等于单价x数量
     * @return
     */
    public BigDecimal getTotalPrice() {
   
        return price.multiply(new BigDecimal(count));
    }

    public void setTotalPrice(BigDecimal totalPrice) {
   
        this.totalPrice = totalPrice;
    }

购物车vo

在这里插入图片描述

public class CartVo {
   

    /**
     * 购物车子项信息
     */
    List

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2301957.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C语言-----操作符的分类

1. 操作符的分类 •算术操作符&#xff1a; 、- 、 * 、/、% 移位操作符:<< >> 位操作符: & | ^ 赋值操作符: / 、 % 、 、- 、 *、/、 %、 <<、 >>、&、| 、 ^ 单⽬操作符&#xff1a;&#xff01;、 、- 、 & 、 * 、 、 …

PWM(脉宽调制)技术详解:从基础到应用实践示例

PWM&#xff08;脉宽调制&#xff09;技术详解&#xff1a;从基础到应用实践示例 目录 PWM&#xff08;脉宽调制&#xff09;技术详解&#xff1a;从基础到应用实践示例学前思考&#xff1a;一、PWM概述二、PWM的基本原理三、PWM的应用场景四、PWM的硬件配置与使用五、PWM的编程…

AI智能成长系统 | 应用探讨研究

研究背景 在现代家庭中&#xff0c;三岁宝宝的成长环境日益复杂。由于宝宝每天接触的人群多样&#xff0c;包括家庭成员、同龄小朋友以及可能的陌生人&#xff0c;其语言环境也相应地变得复杂多变。这种环境下&#xff0c;宝宝很容易接触到一些不适宜的语言&#xff0c;即俗称…

java 网络安全感知 网络安全学java

&#x1f345; 点击文末小卡片 &#xff0c;免费获取网络安全全套资料&#xff0c;资料在手&#xff0c;涨薪更快 实验五 java网络编程及安全 实验内容 1&#xff0e;掌握Socket程序的编写&#xff1b;2&#xff0e;掌握密码技术的使用&#xff1b;3&#xff0e;设计安全传输…

VisionMaster4.4 python脚本 图像处理 转换函数 爱之初体验

最近有接触过一丢丢VM4.3的模块开发. 一直有把python图像处理部分模块移植进来的打算 不过时间不够没来得及折腾.偶尔发现4.4支持py脚本 于是拿来折腾.一下午. 发现4.4支持python脚本,好开心. 首先安装VM4.4 注意一定要是4.4 打开后拖了一个模块. 但是发现import numpy imp…

python-leetcode 40.二叉树的层序遍历

题目&#xff1a; 给定二叉树的根节点root,返回其节点值得层序遍历&#xff08;即逐层从左到右访问所有节点&#xff09; 方法&#xff1a;广度优先搜索 # Definition for a binary tree node. # class TreeNode(object): # def __init__(self, val0, leftNone, rightNon…

蓝桥杯学习大纲

&#xff08;致酷德与热爱算法、编程的小伙伴们&#xff09; 在查阅了相当多的资料后&#xff0c;发现没有那篇博客、文章很符合我们备战蓝桥杯的学习路径。所以&#xff0c;干脆自己整理一篇&#xff0c;欢迎大家补充&#xff01; 一、蓝桥必备高频考点 我们以此为重点学习…

小米AX3000T 路由器如何开启 SSH 安装 OpenWRT 系统,不需要降级 v1.0.91 (2025)

小米AX3000T 路由器如何开启 SSH 安装 OpenWRT 系统&#xff0c;不需要降级 v1.0.91 &#xff08;2025&#xff09; 本文内容需要你有一定的 Linux 操作基础&#xff0c;最好是程序员那种&#xff0c;英文水平足够用才行。一般人不需要使用这么复杂的路由器操作系统&#xff0c…

水基试剂,湿式化学,清水,干式化学,干粉,卤烃清洁剂,二氧化碳灭火器UL8检测报告标准讲解:

水基试剂&#xff0c;湿式化学&#xff0c;清水&#xff0c;干式化学&#xff0c;干粉&#xff0c;卤烃清洁剂&#xff0c;二氧化碳灭火器UL检测报告标准讲解&#xff1a; 本政策涵盖的灭火器 水基试剂灭火器 水基试剂灭火器使用水基试剂带走燃烧三要素中的热量要素&#xf…

汽车免拆诊断案例 | 2010 款路虎揽胜车空调偶尔出风异常

故障现象  一辆2010款路虎揽胜车&#xff0c;搭载5.0 L发动机&#xff0c;累计行驶里程约为16万km。车主反映&#xff0c;接通空调开关后&#xff0c;有时出风忽大忽小&#xff0c;有时不出风&#xff0c;有时要等2 min左右才出风&#xff1b;有时两三天出现一次&#xff0c;…

Mac arm架构使用 Yarn 全局安装 Vue CLI

dgqdgqdeMacBook-Pro spid-admin % vue --version zsh: command not found: vue要使用 Yarn 安装 Vue CLI&#xff0c;你可以执行以下命令&#xff1a; yarn global add vue/cli这个命令会全局安装 Vue CLI&#xff0c;让你可以使用 vue 命令创建、管理 Vue.js 项目。以下是一…

成员函数定义后面加const是什么功能:C++中const成员函数的作用

成员函数定义后面加const是什么功能&#xff1a;C中const成员函数的作用 前言C中const成员函数的作用总结 前言 在PX4的代码中的位置控制模块中&#xff0c;有这样一个成员函数 void getAttitudeSetpoint(vehicle_attitude_setpoint_s &attitude_setpoint) const;该函数的…

DeepSeek智能测试助手:分类+推理+导出一站式工具

前言 测试开发工程师在日常工作中需要处理大量测试文档&#xff0c;并且这些文档需要被高效分类、清洗和管理&#xff0c;同时结合强大的 AI 推理能力&#xff08;如 DeepSeek 模型&#xff09;进行智能化处理和分析。为此&#xff0c;我们开发了一款基于 PyQt5 的 GUI 工具&a…

计算机毕业设计Python农产品推荐系统 农产品爬虫 农产品可视化 农产品大数据(源码+LW文档+PPT+讲解)

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

「正版软件」PDF Reader - 专业 PDF 编辑阅读工具软件

PDF Reader 轻松查看、编辑、批注、转换、数字签名和管理 PDF 文件&#xff0c;以提高工作效率并充分利用 PDF 文档。 像专业人士一样编辑 PDF 编辑 PDF 文本 轻松添加、删除或修改 PDF 文档中的原始文本以更正错误。自定义文本属性&#xff0c;如颜色、字体大小、样式和粗细。…

日期类(完全讲解版)

1. 类的设计思想 Date 类的设计目的是为了封装和处理日期信息&#xff0c;它提供了对日期的基本操作&#xff0c;如日期加减、日期比较、日期合法性检查等。类中的私有成员 int _year, int _month, int _day 存储了日期的年、月、日。 类的声明和构造 Date 类的声明&#xff1…

洛谷 P10726 [GESP202406 八级] 空间跳跃 C++ 完整题解

一、题目链接 P10726 [GESP202406 八级] 空间跳跃 - 洛谷 二、解题思路 我们要对输入的挡板进行排序&#xff0c;按高度从高到低&#xff08;从小到大&#xff09;。 排序之后s和t都要更新。 struct Baffle {int l, r;int h;int id; } b[1005];void Sort() {sort(b 1, b 1 n…

【设计模式精讲】创建型模式之工厂方法模式(简单工厂、工厂方法)

文章目录 第四章 创建型模式4.2 工厂方法模式4.2.1 需求: 模拟发放奖品业务4.2.2 原始开发方式4.2.3 简单工厂模式4.2.3.1 简单工厂模式介绍4.2.3.2 简单工厂原理4.2.3.3 简单工厂模式重构代码4.2.3.4 简单工厂模式总结 4.2.4 工厂方法模式4.2.4.1 工厂方法模式介绍4.2.4.2 工厂…

【ROS2】【ROS2】RViz2源码分析(八):Display中订阅ROS2消息(使用Qt信号和槽传递ROS2消息)

1、简述 RViz2 涵盖了 Qt 和 ROS2 的技术点,前面介绍 DisplaysPanel 时,主要分析了Qt相关部分,参见博客: 【ROS2】RViz2源码分析(七):DisplaysPanel 中的树状模型/视图 本篇博客,将会一起学习 RViz2 中如何使用 ROS2,以 Display 中订阅 ROS2 消息为例。 2、通过话题…

牛顿法:用泰勒级数求解平方根的秘籍

目录 一、引言二、牛顿法的理论基础——泰勒级数三、牛顿法的原理与推导3.1 原理概述3.2 推导过程3.3 几何解释 四、牛顿法的应用场景4.1 数值计算4.2 优化问题 五、牛顿法求平方根的具体案例5.1 原理推导5.2 具体步骤5.3 代码实现&#xff08;Python&#xff09;5.4 示例计算过…