谷粒商城—分布式高级②.md
认证服务
1. 环境搭建
创建gulimall-auth-server模块,导依赖,引入login.html和reg.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中编写发送短信组件,其中host、path、appcode可以在配置文件中使用前缀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 83359fd73fe94948385f570e3c139105headers.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 {@Resourceprivate 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-codeValueOperations<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());}
}@Overridepublic 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 keyYOUR_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 keyclient_secret: 创建网站应用时的app secretYOUR_REGISTERED_REDIRECT_URI: 认证完成后的跳转链接(需要和平台高级设置一致)code:换取令牌的认证码
返回数据如下

(5) 获取用户信息
https://open.weibo.com/wiki/2/users/show
结果返回json
(6) 代码编写
认证接口
- 通过
HttpUtils发送请求获取token,并将token等信息交给member服务进行社交登录 - 若获取
token失败或远程调用服务失败,则封装错误信息重新转回登录页
@Controller
public class OauthController {@Autowiredprivate 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请求换取tokenHttpResponse 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();}
}@Overridepublic 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.102session:store-type: redis
添加注解
@EnableRedisHttpSession
public class GulimallAuthServerApplication {
2) 自定义配置
-
由于默认使用jdk进行序列化,通过导入
RedisSerializer修改为json序列化 -
并且通过修改
CookieSerializer扩大session的作用域至**.gulimall.com
@Configuration
public class GulimallSessionConfig {@Beanpublic RedisSerializer<Object> springSessionDefaultRedisSerializer() {return new GenericJackson2JsonRedisSerializer();}@Beanpublic 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相关文章:
谷粒商城—分布式高级②.md
认证服务 1. 环境搭建 创建gulimall-auth-server模块,导依赖,引入login.html和reg.html,并把静态资源放到nginx的static目录下 2. 注册功能 (1) 验证码倒计时 //点击发送验证码按钮触发下面函数 $("#sendCode").click(function () {//如果有disabled,说明最近…...
阿里云ECS命名规则解析与规格选型实战指南
阿里云ECS实例的命名规则通常采用 “ecs.{实例族}.{规格大小}” 的结构,各部分含义如下: 命名字段说明ecs代表“弹性计算服务”(Elastic Compute Service)。{实例族}标识实例的用途和代次(如 g7、c7、r7),由字母+数字组成。{规格大小}表示实例的资源配置(如 large、2xl…...
Spring MVC 的核心以及执行流程
Spring MVC的核心 Spring MVC是Spring框架中的一个重要模块,它采用了经典的MVC(Model-View-Controller)设计模式。 MVC是一种软件架构的思想,它将软件按照模型(Model)、视图(View)…...
ai json处理提示词
在解析JSON数据时,提示词的设计需要明确任务目标、输入格式以及期望的输出格式。以下是一些常用的提示词示例,适用于不同的JSON解析场景: 1. 提取特定字段 用于从JSON中提取特定字段的值。 示例: 从以下JSON数据中提…...
2025开源数据工程全景图
作者 | Alireza Sadeghi 译自Practical Data Engineering 2025年开源数据工程领域呈现蓬勃创新与生态重构的双重态势,九大技术赛道在实时化、轻量化与云原生架构驱动下加速演进。一份来自外网的2025年开源数据工程全景图全面地展示了这一领域的发展态势与走向&…...
438. 找到字符串中所有字母异位词(LeetCode 热题 100)
题目来源: 438. 找到字符串中所有字母异位词 - 力扣(LeetCode) 题目内容: 给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词的子串,返回这些子串的起始索引。不考虑答案输出的顺序。 示例 1: 输入: s &…...
c++标准io与线程,互斥锁
封装一个 File 类, 用有私有成员 File* fp 实现以下功能 File f "文件名" 要求打开该文件 f.write(string str) 要求将str数据写入文件中 string str f.read(int size) 从文件中读取最多size个字节, 并将读取到的数据返回 析构函数 #…...
java简单实现请求deepseek
1.deepseek的api创建 deepseek官网链接 点击右上API开放平台后找到API keys 创建APIkey: 注意:创建好的apikey只能在创建时可以复制,要保存好 2.java实现请求deepseek 使用springbootmaven 2.1 pom文件: <?xml version&…...
Ext系列文件系统 -- 磁盘结构,磁盘分区,inode,ext文件系统,软硬链接
目录 1.理解硬盘 1.1 磁盘、服务器、机柜、机房 1.2 磁盘物理结构 1.3 磁盘的存储结构 1.4 磁盘的逻辑结构 1.4.1 理解逻辑结构 1.4.2 真实过程 1.5 CHS地址和LBA地址的相互转换 2.引入文件系统 2.1 “块”概念 2.2 “分区”概念 2.3 “inode”概念 3.ext2文件系…...
PyTorch Tensor 形状变化操作详解
PyTorch Tensor 形状变化操作详解 在深度学习中,Tensor 的形状变换是非常常见的操作。PyTorch 提供了丰富的 API 来帮助我们调整 Tensor 的形状,以满足模型输入、计算或数据处理的需求。本文将详细介绍 PyTorch 中常见的 Tensor 形状变换操作࿰…...
文字识别软件cnocr学习笔记
• 安装 pip install cnocr • 基础的使用方法 首次运行会下载安装模型,如果没有梯子,会报错: 在网络上查找cnocr的模型资源,并下载到本地。https://download.csdn.net/download/qq_33464428/89514689?ops_request_misc%257B%2…...
本地部署DeepSeek R1 + 界面可视化open-webui【ollama容器+open-webui容器】
本地部署DeepSeek R1 界面可视化open-webui 本文主要讲述如何用ollama镜像和open-webui镜像部署DeepSeek R1, 镜像比较方便我们在各个机器之间快速部署。 显卡推荐 模型版本CPU内存GPU显卡推荐1.5B4核8GB非必需4GBRTX1650、RTX20607B、8B8核16GB8GBRTX3070、RTX…...
macOS部署DeepSeek-r1
好奇,跟着网友们的操作试了一下 网上方案很多,主要参考的是这篇 DeepSeek 接入 PyCharm,轻松助力编程_pycharm deepseek-CSDN博客 方案是:PyCharm CodeGPT插件 DeepSeek-r1:1.5b 假设已经安装好了PyCharm PyCharm: the Pyth…...
基于STM32与BD623x的电机控制实战——从零搭建无人机/机器人驱动系统
系列文章目录 1.元件基础 2.电路设计 3.PCB设计 4.元件焊接 5.板子调试 6.程序设计 7.算法学习 8.编写exe 9.检测标准 10.项目举例 11.职业规划 文章目录 一、为什么选择这两个芯片?1.1 STM32微控制器1.2 ROHM BD623x电机驱动 二、核心控制原理详解2.1 H桥驱动奥…...
基于ffmpeg+openGL ES实现的视频编辑工具-字幕添加(六)
在视频编辑领域,字幕的添加是一项极为重要的功能,它能够极大地丰富视频内容,提升观众的观看体验。当我们深入探究如何实现这一功能时,FreeType 开源库成为了强大助力。本文将详细阐述借助 FreeType 库生成字幕数据的过程,以及如何实现字幕的缩放、移动、旋转、颜色修改、对…...
C++中const T为什么少见?它有什么用途?
在C中,右值引用(T&&)是移动语义和完美转发的核心特性之一,但你是否注意到,const T&&(const右值引用)却很少被使用?它到底有什么用途? 今天我们就来深入…...
Leetcode 位计算
3095. 或值至少 K 的最短子数组 I 3097. Shortest Subarray With OR at Least K II class Solution:def minimumSubarrayLength(self, nums: List[int], k: int) -> int:n len(nums)bits [0] * 30res infdef calc(bits):return sum(1 << i for i in range(30) if…...
SpringBoot3.x整合WebSocket
SpringBoot3.x整合WebSocket 本文主要介绍最新springboot3.x下如何整合WebSocket. WebSocket简述 WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,它允许在浏览器和服务器之间进行实时的、双向的通信。相对于传统的基于请求和响应的 HTTP 协议ÿ…...
猿大师办公助手对比其他WebOffice在线编辑Office插件有什么优势
1. 原生Office功能完整嵌入,排版一致性保障 猿大师办公助手直接调用本地安装的微软Office、金山WPS或永中Office,支持所有原生功能(如复杂公式、VBA宏等),确保网页编辑与本地打开的文档排版完全一致。 提供OLE嵌入和完…...
STM32创建静态库lib
创建静态库lib 1. 新建工程1.1 创建工程文件夹1.2 编写用户相关代码1.2.1 stm32f4xx_it.h1.2.2 stm32f4xx_it.c1.2.3 标准库配置:stm32f4xx_conf.h1.2.4 HAL库的配置:stm32f4xx_hal_conf.h1.2.5 LL库配置:stm32f4xx_ll_conf.h 1.3 移植通用文…...
工业安全零事故的智能守护者:一体化AI智能安防平台
前言: 通过AI视觉技术,为船厂提供全面的安全监控解决方案,涵盖交通违规检测、起重机轨道安全、非法入侵检测、盗窃防范、安全规范执行监控等多个方面,能够实现对应负责人反馈机制,并最终实现数据的统计报表。提升船厂…...
2025年能源电力系统与流体力学国际会议 (EPSFD 2025)
2025年能源电力系统与流体力学国际会议(EPSFD 2025)将于本年度在美丽的杭州盛大召开。作为全球能源、电力系统以及流体力学领域的顶级盛会,EPSFD 2025旨在为来自世界各地的科学家、工程师和研究人员提供一个展示最新研究成果、分享实践经验及…...
sqlserver 根据指定字符 解析拼接字符串
DECLARE LotNo NVARCHAR(50)A,B,C DECLARE xml XML ( SELECT <x> REPLACE(LotNo, ,, </x><x>) </x> ) DECLARE ErrorCode NVARCHAR(50) -- 提取 XML 中的值 SELECT value x.value(., VARCHAR(MAX))…...
基于matlab策略迭代和值迭代法的动态规划
经典的基于策略迭代和值迭代法的动态规划matlab代码,实现机器人的最优运输 Dynamic-Programming-master/Environment.pdf , 104724 Dynamic-Programming-master/README.md , 506 Dynamic-Programming-master/generalizedPolicyIteration.m , 1970 Dynamic-Programm…...
代理篇12|深入理解 Vite中的Proxy接口代理配置
在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...
React---day11
14.4 react-redux第三方库 提供connect、thunk之类的函数 以获取一个banner数据为例子 store: 我们在使用异步的时候理应是要使用中间件的,但是configureStore 已经自动集成了 redux-thunk,注意action里面要返回函数 import { configureS…...
HashMap中的put方法执行流程(流程图)
1 put操作整体流程 HashMap 的 put 操作是其最核心的功能之一。在 JDK 1.8 及以后版本中,其主要逻辑封装在 putVal 这个内部方法中。整个过程大致如下: 初始判断与哈希计算: 首先,putVal 方法会检查当前的 table(也就…...
佰力博科技与您探讨热释电测量的几种方法
热释电的测量主要涉及热释电系数的测定,这是表征热释电材料性能的重要参数。热释电系数的测量方法主要包括静态法、动态法和积分电荷法。其中,积分电荷法最为常用,其原理是通过测量在电容器上积累的热释电电荷,从而确定热释电系数…...
Go 并发编程基础:通道(Channel)的使用
在 Go 中,Channel 是 Goroutine 之间通信的核心机制。它提供了一个线程安全的通信方式,用于在多个 Goroutine 之间传递数据,从而实现高效的并发编程。 本章将介绍 Channel 的基本概念、用法、缓冲、关闭机制以及 select 的使用。 一、Channel…...
Java数值运算常见陷阱与规避方法
整数除法中的舍入问题 问题现象 当开发者预期进行浮点除法却误用整数除法时,会出现小数部分被截断的情况。典型错误模式如下: void process(int value) {double half = value / 2; // 整数除法导致截断// 使用half变量 }此时...
