Springboot仿抖音app开发之用短视频务模块后端复盘及相关业务知识总结

Springboot仿抖音app开发之用户业务模块后端复盘及相关业务知识总结

 

 

BO类和VO类的区别

BO (Business Object) - 业务对象

  • 定义: 业务对象是包含业务逻辑的领域模型
  • 用途: 主要用于封装业务逻辑相关的数据,在业务层(Service层)之间传递
  • 特点:
    • 与业务处理密切相关
    • 通常包含业务处理所需的数据
    • 在服务层之间或与DAO层之间传输数据
    • 可能包含一些业务计算方法

VO (View Object) - 视图对象

  • 定义: 视图对象是专门用于展示层的数据载体
  • 用途: 主要用于封装视图展示所需的数据,在控制层(Controller)和视图层(前端)之间传递
  • 特点:
    • 与界面展示密切相关
    • 通常只包含页面显示所需的数据
    • 可能是对多个实体的组合,以适应页面展示需求
    • 通常不包含业务逻辑方法

从您提供的代码示例来看:

  1. VlogBO - 业务对象:

    public class VlogBO {private String id;private String vlogerId;private String url;private String cover;private String title;private Integer width;private Integer height;private Integer likeCounts;private Integer commentsCounts;
    }
    
    • 包含视频博客的核心业务数据,如ID、URL、尺寸、点赞数等
    • 可能用于视频上传、处理等业务操作
  2. VlogerVO - 视图对象:

    public class VlogerVO {private String vlogerId;private String nickname;private String face;private boolean isFollowed = true;
    }
    
    • 专注于前端展示需要的数据,如视频创作者ID、昵称、头像和关注状态
    • 简化了数据结构,只包含UI层需要的信息
    • 添加了UI特定的字段,如isFollowed,这与前端交互相关

在实际应用中的区别

  1. 使用场景:

    • BO: 主要在service层使用,封装业务逻辑所需的数据
    • VO: 主要在controller层使用,向前端返回数据
  2. 转换关系:

    • 通常会有DO(Data Object) → BO → VO的转换过程
    • DO是与数据库表结构一一对应的对象
    • BO可能组合多个DO,添加业务处理
    • VO进一步调整BO,使其适合前端展示
  3. 职责分离:

    • 使用这些不同类型的对象有助于实现关注点分离
    • 每一层专注于自己的职责,使代码更易于维护

保存视频信息入库

1. 控制层接收请求 (VlogController)

首先,前端向 /vlog/publish 端点发送 POST 请求,携带视频信息:

@PostMapping("publish")
public GraceJSONResult publish(@RequestBody VlogBO vlogBO) {vlogService.createVlog(vlogBO);return GraceJSONResult.ok();
}

这个控制器方法:

  • 使用 @RequestBody 注解接收 JSON 格式的请求体,并自动反序列化为 VlogBO 对象
  • 调用 vlogService.createVlog(vlogBO) 处理业务逻辑
  • 返回成功响应给前端

2. 服务层处理业务逻辑 (VlogServiceImpl)

接下来,控制器调用服务层的 createVlog 方法:

@Transactional
@Override
public void createVlog(VlogBO vlogBO) {String vid = sid.nextShort();Vlog vlog = new Vlog();BeanUtils.copyProperties(vlogBO, vlog);vlog.setId(vid);vlog.setLikeCounts(0);vlog.setCommentsCounts(0);vlog.setIsPrivate(YesOrNo.NO.type);vlog.setCreatedTime(new Date());vlog.setUpdatedTime(new Date());vlogMapper.insert(vlog);
}

在这个方法中执行了以下操作:

  1. 事务管理

    • 使用 @Transactional 注解确保整个操作在一个事务中完成
  2. ID生成

    • 使用 sid.nextShort() 生成唯一的视频ID
    • Sid 是一个分布式ID生成器,确保生成的ID在分布式环境中唯一
  3. 对象转换

    • 创建数据库实体对象 Vlog
    • 使用 BeanUtils.copyProperties() 将 VlogBO 的属性复制到 Vlog 实体
  4. 设置默认值

    • 设置视频ID:vlog.setId(vid)
    • 初始化点赞数和评论数为0
    • 设置视频为公开状态:vlog.setIsPrivate(YesOrNo.NO.type)
    • 设置创建时间和更新时间为当前时间
  5. 数据库插入

    • 调用 vlogMapper.insert(vlog) 将视频信息插入数据库

3. 数据访问层执行SQL (VlogMapper)

最后,vlogMapper 执行数据库插入操作:

vlogMapper.insert(vlog);

这里的 VlogMapper 是一个 MyBatis 接口,会将 Vlog 对象映射为 SQL 插入语句并执行。

实现数据层mybatis自定义mapper与sql

实现查询短视频列表与分页功能

1. 前端请求入口

前端通过HTTP GET请求访问/indexList接口,传入以下参数:

  • userId:当前用户ID(可选)
  • search:搜索关键词(可选)
  • page:页码
  • pageSize:每页显示条数
@GetMapping("indexList")
public GraceJSONResult indexList(@RequestParam(defaultValue = "") String userId,@RequestParam(defaultValue = "") String search,@RequestParam Integer page,@RequestParam Integer pageSize) {if (page == null) {page = COMMON_START_PAGE;}if (pageSize == null) {pageSize = COMMON_PAGE_SIZE;}PagedGridResult list = vlogService.queryIndexVlogList(userId, search, page, pageSize);return GraceJSONResult.ok(list);
}

2. 服务层处理

服务层实现分页查询逻辑:

@Override
public PagedGridResult queryIndexVlogList(String userId,String search,Integer page,Integer pageSize) {PageHelper.startPage(page, pageSize);Map<String, Object> map = new HashMap<>();if (StringUtils.isNotBlank(search)) {map.put("search", search);}List<IndexVlogVO> list = vlogMapperCustom.getIndexVlogList(map);// 注释掉的代码处理关注和点赞信息return setterPagedGrid(list, page);
}

这里有几个关键点:

  • 使用PageHelper.startPage(page, pageSize)启动分页功能
  • 将查询参数封装到Map中
  • 调用自定义Mapper执行查询
  • 使用setterPagedGrid方法封装分页结果

3. 数据访问层查询

自定义Mapper接口定义查询方法:

public List<IndexVlogVO> getIndexVlogList(@Param("paramMap") Map<String, Object> map);

对应的XML映射文件:

xml

<select id="getIndexVlogList" resultType="com.imooc.vo.IndexVlogVO" parameterType="map">SELECTv.id as vlogId,v.vloger_id as vlogerId,u.face as vlogerFace,u.nickname as vlogerName,v.title as content,v.url as url,v.cover as cover,v.width as width,v.height as height,v.like_counts as likeCounts,v.comments_counts as commentsCounts,v.is_private as isPrivateFROMvlog vLEFT JOINusers uONv.vloger_id = u.idWHEREv.is_private = 0<if test="paramMap.search != null and paramMap.search != ''">and v.title LIKE '%${paramMap.search}%'</if>ORDER BYv.created_timeDESC</select>

这里的SQL实现了:

  • 联表查询视频和用户信息
  • 只查询公开视频(is_private = 0
  • 条件性添加搜索条件(模糊查询标题)
  • 按创建时间降序排序

分析

  1. 参数注解

    • @Param("paramMap") 是MyBatis的参数注解
    • 它将传入的Map参数在MyBatis中命名为"paramMap"
    • 这个命名使得在XML映射文件中可以通过paramMap来引用这个Map
  2. Map参数

    • 方法接受一个Map<String, Object>类型的参数
    • 这个Map可以包含多个键值对,用于传递不同的查询条件
    • search是这个Map中的一个键,对应的值是搜索关键词
  3. 关联用户信息(通过LEFT JOIN users u)能够在一次查询中同时获取视频创作者的头像(u.face)和昵称(u.nickname),这样可以:

    在视频列表中直接显示创作者头像和名称

         减少前端需要发起的额外请求

         提升用户体验,用户可以一眼看到视频来源

4. 分页结果封装

将查询结果封装为统一的分页响应格式:

public PagedGridResult setterPagedGrid(List<?> list, Integer page) {PageInfo<?> pageList = new PageInfo<>(list);PagedGridResult gridResult = new PagedGridResult();gridResult.setRows(list);gridResult.setPage(page);gridResult.setRecords(pageList.getTotal());gridResult.setTotal(pageList.getPages());return gridResult;
}

这个方法利用PageInfo类(PageHelper提供)获取分页元数据,并封装到自定义的PagedGridResult对象中。

5. PageHelper工作原理

PageHelper通过ThreadLocal变量和拦截器实现分页功能:

  • 当调用PageHelper.startPage(page, pageSize)时,在ThreadLocal中记录分页参数
  • 在执行SQL前,PageHelper拦截器会修改SQL,添加分页语句(如MySQL的LIMIT)
  • 执行完SQL后,PageHelper会将查询结果封装到Page对象中
  • PageInfo构造函数会自动识别Page对象并提取分页信息

${}#{}占位符的区别 

and v.title LIKE '%${paramMap.search}%'

特点

  • 直接替换:${}字符串直接替换,会将参数值直接替换到SQL语句中
  • 无类型处理:不会添加任何类型处理或转义
  • 无预编译:不使用预编译占位符,而是在SQL字符串中直接替换

例子: 如果paramMap.search = "旅行",生成的SQL将是:

and v.title LIKE '%旅行%'

2. #{}占位符 - 参数绑定

WHERE v.id = #{paramMap.vlogId}

特点

  • 参数绑定:#{}参数绑定,会被替换为?预编译占位符
  • 自动类型处理:会根据参数类型进行适当的类型处理
  • 预编译:使用预编译语句,参数会作为PreparedStatement的参数传递
  • 自动转义:会自动处理特殊字符,防止SQL注入

例子: 如果paramMap.vlogId = "1001",实际执行时的SQL会先被处理为:

WHERE v.id = ?

然后值"1001"作为参数绑定到这个位置。

两者的主要区别

特性${}#{}
执行方式字符串替换参数绑定
SQL注入风险
类型处理自动
预编译支持不支持支持
性能对于重复查询较低支持语句缓存,性能更好
使用场景动态表名、列名、排序字段等一般的参数传递

为什么在这两种情况下使用不同的占位符

1. 使用${}的场景 (模糊查询)

and v.title LIKE '%${paramMap.search}%'

这里使用${}是因为:

  • LIKE语句中的百分号%需要与搜索词直接拼接
  • 使用#{}会将整个值(包括%)当作一个参数,无法实现通配符功能

注意:这种用法存在SQL注入风险,更安全的方式是:

and v.title LIKE CONCAT('%', #{paramMap.search}, '%')

2. 使用#{}的场景 (精确匹配)

WHERE v.id = #{paramMap.vlogId}

这里使用#{}是因为:

  • 是精确匹配查询,不需要字符串拼接
  • 需要防止SQL注入,提高安全性
  • 可以利用预编译提升性能

视频详情页展示的实现 

1. 控制层处理

@GetMapping("detail")
public GraceJSONResult detail(@RequestParam(defaultValue = "") String userId,@RequestParam String vlogId) {return GraceJSONResult.ok(vlogService.getVlogDetailById(userId, vlogId));
}

控制层接收两个关键参数:

  • vlogId:要查询的视频ID(必传)
  • userId:当前访问用户的ID(可选,默认为空)

这里直接调用服务层方法,并使用GraceJSONResult.ok()包装结果,提供统一的响应格式。

2. 服务层处理

@Override
public IndexVlogVO getVlogDetailById(String userId, String vlogId) {Map<String, Object> map = new HashMap<>();map.put("vlogId", vlogId);List<IndexVlogVO> list = vlogMapperCustom.getVlogDetailById(map);if (list != null && list.size() > 0 && !list.isEmpty()) {IndexVlogVO vlogVO = list.get(0);// return setterVO(vlogVO, userId);return vlogVO;}// 这里应该有返回null的逻辑
}

服务层主要逻辑:

  • 创建参数Map,将视频ID存入
  • 调用自定义Mapper执行数据库查询
  • 从查询结果列表中获取第一个元素作为结果
  • 注释掉的代码setterVO(vlogVO, userId)可能原本用于设置用户相关状态,如是否已点赞、是否已关注视频创作者等

3. 数据访问层查询

Mapper接口定义:

public List<IndexVlogVO> getVlogDetailById(@Param("paramMap") Map<String, Object> map);

XML映射文件中的SQL查询:

<select id="getVlogDetailById" parameterType="map" resultType="com.imooc.vo.IndexVlogVO">SELECTv.id as vlogId,v.vloger_id as vlogerId,u.face as vlogerFace,u.nickname as vlogerName,v.title as content,v.url as url,v.cover as cover,v.width as width,v.height as height,v.like_counts as likeCounts,v.comments_counts as commentsCounts,v.is_private as isPrivateFROMvlog vLEFT JOINusers uONv.vloger_id = u.idWHEREv.id = #{paramMap.vlogId}</select>

这个SQL查询:

  • 联合查询视频表(vlog)和用户表(users)
  • 通过LEFT JOIN关联视频创作者信息
  • 使用vlogId作为查询条件
  • 查询结果包含视频基本信息和创作者信息
  • 结果字段别名与VO对象属性名对应,实现自动映射

4. 数据结构设计

从SQL查询中可以看出,IndexVlogVO对象包含以下信息:

  • 视频信息

    • vlogId:视频ID
    • content:视频标题/内容
    • url:视频地址
    • cover:视频封面
    • width/height:视频尺寸
    • likeCounts:点赞数
    • commentsCounts:评论数
    • isPrivate:是否私有
  • 创作者信息

    • vlogerId:创作者ID
    • vlogerFace:创作者头像
    • vlogerName:创作者昵称

5. 业务处理特点

  1. 单一职责:各层次职责明确,控制层处理请求,服务层处理业务,数据访问层执行查询
  2. 数据封装:使用VO对象封装前端展示所需数据
  3. 关联查询:一次查询获取视频和创作者信息,减少数据库交互
  4. 参数传递:使用Map传递查询参数,灵活性高
  5. 状态设计:预留了设置用户与视频交互状态的逻辑(被注释掉的setterVO方法)

6. 实现特点与优势

  1. 代码复用:与首页视频列表使用类似的数据结构和查询逻辑
  2. 查询效率:通过ID直接查询,高效精准
  3. 结构清晰:各层次分工明确,易于维护
  4. 扩展性好:预留了添加用户交互状态的扩展点
  5. 统一响应:使用GraceJSONResult统一API响应格式

实现转为私密或公开视频

1. 控制层处理请求

应用提供了两个端点,分别用于将视频设为私密或公开:

@PostMapping("changeToPrivate")
public GraceJSONResult changeToPrivate(@RequestParam String vlogerId,@RequestParam String Id) {vlogService.changeToPrivateOrPublic(vlogerId,Id,YesOrNo.YES.type);return GraceJSONResult.ok();
}@PostMapping("changeToPublic")
public GraceJSONResult changeToPublic(@RequestParam String vlogerId,@RequestParam String Id) {vlogService.changeToPrivateOrPublic(vlogerId,Id,YesOrNo.NO.type);return GraceJSONResult.ok();
}

这两个方法的特点:

  • 都使用HTTP POST请求
  • 接收相同的参数:vlogerId(创作者ID)和Id(视频ID)
  • 调用同一个服务方法,但传入不同的状态值
  • 使用枚举YesOrNo来表示状态(YES表示私密,NO表示公开)
  • 返回统一的成功响应

2. 服务层实现业务逻辑

服务层实现了统一的方法来处理状态变更:

@Transactional
@Override
public void changeToPrivateOrPublic(String userId, String vlogId, Integer yesOrNo) {Example example = new Example(Vlog.class);Example.Criteria criteria = example.createCriteria();criteria.andEqualTo("id", vlogId);criteria.andEqualTo("vlogerId", userId);Vlog pendingVlog = new Vlog();pendingVlog.setIsPrivate(yesOrNo);vlogMapper.updateByExampleSelective(pendingVlog, example);
}

展示我的公开和私密视频 

1. 控制层处理请求

应用提供了两个端点,分别用于获取用户的公开和私密视频列表:

@GetMapping("myPublicList")
public GraceJSONResult myPublicList(@RequestParam String userId,@RequestParam Integer page,@RequestParam Integer pageSize) {if (page == null) {page = COMMON_START_PAGE;}if (pageSize == null) {pageSize = COMMON_PAGE_SIZE;}PagedGridResult gridResult = vlogService.queryMyVlogList(userId,page,pageSize,YesOrNo.NO.type);return GraceJSONResult.ok(gridResult);
}@GetMapping("myPrivateList")
public GraceJSONResult myPrivateList(@RequestParam String vlogerId,@RequestParam Integer page,@RequestParam Integer pageSize) {if (page == null) {page = COMMON_START_PAGE;}if (pageSize == null) {pageSize = COMMON_PAGE_SIZE;}PagedGridResult gridResult = vlogService.queryMyVlogList(vlogerId,page,pageSize,YesOrNo.YES.type);return GraceJSONResult.ok(gridResult);
}

这两个方法的特点:

  • 都使用HTTP GET请求
  • 接收相同的参数:用户ID、页码和每页大小
  • 对页码和每页大小提供默认值
  • 调用同一个服务方法,但传入不同的状态值
  • 返回统一格式的分页结果

2. 服务层实现业务逻辑

服务层实现了统一的方法来查询不同状态的视频:

@Override
public PagedGridResult queryMyVlogList(String userId, Integer page, Integer pageSize, Integer yesOrNo) {Example example = new Example(Vlog.class);Example.Criteria criteria = example.createCriteria();criteria.andEqualTo("vlogerId", userId);criteria.andEqualTo("isPrivate", yesOrNo);PageHelper.startPage(page, pageSize);List<Vlog> list = vlogMapper.selectByExample(example);return setterPagedGrid(list, page);
}

这个方法的关键点:

  • 查询条件:通过用户ID和私密状态筛选视频
  • 分页实现:使用PageHelper进行分页
  • 通用Mapper:使用MyBatis通用Mapper执行查询
  • 结果封装:调用setterPagedGrid方法封装分页结果

3. 分页结果封装

前面代码片段中有setterPagedGrid方法用于封装分页结果:

public PagedGridResult setterPagedGrid(List<?> list, Integer page) {PageInfo<?> pageList = new PageInfo<>(list);PagedGridResult gridResult = new PagedGridResult();gridResult.setRows(list);gridResult.setPage(page);gridResult.setRecords(pageList.getTotal());gridResult.setTotal(pageList.getPages());return gridResult;
}

该方法将原始列表和分页信息封装到PagedGridResult对象中,包括:

  • 数据行(rows)
  • 当前页码(page)
  • 总记录数(records)
  • 总页数(total)

4. 枚举使用

代码使用了YesOrNo枚举来表示视频状态:

  • YesOrNo.NO.type:表示公开视频(值可能为0)
  • YesOrNo.YES.type:表示私密视频(值可能为1)

 

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

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

相关文章

SQL-事务(2025.6.6-2025.6.7学习篇)

1、简介 事务是一组操作的集合&#xff0c;它是一个不可分割的工作单位&#xff0c;事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求&#xff0c;即这些操作要么同时成功&#xff0c;要么同时失败。 默认MySQL的事务是自动提交的&#xff0c;也就是说&#xff0…

《Ansys SIPI仿真技术笔记》 E-desk IBIS模型导入

技术笔记日期&#xff1a;20250611 00 背景和疑问 当在Circuit中准备载入IBIS时&#xff0c;工作界面会弹出如下界面&#xff1a; 那么具体Pin Import和Buffer Import有和区别&#xff1f; 何时该按哪个导入呢&#xff1f; 01 思考和记录 1. Buffer Import VS Pin Import…

uniapp的请求封装,如何避免重复提交请求

1、如何封装uniapp&#xff0c;并且如何使用uniapp的封装查看&#x1f449;uniapp请求封装_uni-app-x 请求封装-CSDN博客​​​​​​​ 2、声明一个请求记录的缓存&#xff0c;代码如下 // 存储请求记录 let requestRecords {}; // 重复请求拦截时间&#xff08;毫秒&#x…

【云原生】阿里云SLS日志自定义字段标签实现日志告警

把业务日志接入到阿里云SLS日志服务后,我们想自定义字段做为标签,在做日志告警的时候,可以做为查询结果使用 自定义标签 样例: 一个典型的java log初始化日志格式 [ywgy-app-service:10.10.6.100:30000] 2025-06-10 08:40:53.444 INFO 1[TID: N/A][uId:][sId:][tId:][po…

Linux下制作Nginx绿色免安装包

linux下安装nginx比较繁琐&#xff0c;遇到内网部署环境更是麻烦。根据经验将nginx打包一个绿色版进行使用。 大体思路&#xff0c;在一台正常的机器上面制造好安装包&#xff0c;然后上传到内网服务器&#xff0c;解压使用 安装包制作 安装依赖 yum install gcc-c pcre per…

脑机新手指南(七):OpenBCI_GUI:从环境搭建到数据可视化(上)

一、OpenBCI_GUI 项目概述 &#xff08;一&#xff09;项目背景与目标 OpenBCI 是一个开源的脑电信号采集硬件平台&#xff0c;其配套的 OpenBCI_GUI 则是专为该硬件设计的图形化界面工具。对于研究人员、开发者和学生而言&#xff0c;首次接触 OpenBCI 设备时&#xff0c;往…

【Zephyr 系列 18】分布式传感网络系统设计:从 BLE Mesh 到边缘网关的数据闭环

🧠关键词:Zephyr、BLE Mesh、边缘网关、分布式网络、状态同步、组播、数据聚合、远程控制 📌适合人群:希望实现 BLE Mesh 与网关联合控制、多设备组网协作、数据闭环采集的开发者 📊预计字数:5500+ 字 🧭 背景与系统目标 在工业、农业、仓储等场景中,我们常见以下…

【区块链基础】区块链的 Fork(分叉)深度解析:原理、类型、历史案例及共识机制的影响

区块链的 Fork(分叉)全面解析:原理、类型、历史案例及共识机制的影响 在区块链技术的发展过程中,Fork(分叉)现象是不可避免且极具影响力的一个环节。理解区块链分叉的形成原因、具体表现以及共识机制对分叉的作用,对于深入把握区块链技术架构及其治理机制至关重要。 本…

开源 java android app 开发(十一)调试、发布

文章的目的为了记录使用java 进行android app 开发学习的经历。本职为嵌入式软件开发&#xff0c;公司安排开发app&#xff0c;临时学习&#xff0c;完成app的开发。开发流程和要点有些记忆模糊&#xff0c;赶紧记录&#xff0c;防止忘记。 相关链接&#xff1a; 开源 java an…

数据的聚合

聚合可以实现对文档数据的统计&#xff0c;分析&#xff0c;运算&#xff0c;聚合常见有三类&#xff08;聚合的值一定不能是text类型的&#xff09;&#xff1a; 桶&#xff08;Bucket&#xff09;聚合&#xff1a;用来对文档做分组。 度量&#xff08;Metric&#xff09;聚合…

C++默认构造函数被隐式删除

一、 看cppreference时&#xff0c;发现被隐式删除的构造函数&#xff0c;查询做如下记录&#xff1a; struct F {int& ref; // reference memberconst int c; // const member// F::F() is implicitly defined as deleted };// user declared copy constructor (either …

6.ref创建对象类型的响应式数据

其实ref接收的数据可以是&#xff1a;基本类型、对象类型。若ref接收的是对象类型&#xff0c;内部其实也是调用了reactive函数。 <template><div class"person"><h2>汽车信息&#xff1a;一台{{ car.brand }}汽车&#xff0c;价值{{ car.price }…

如何设计一个用于大规模生产任务的人工智能AI系统

部署一个SOTA模型&#xff0c;让它服务数百万用户&#xff0c;处理TB级别的数据&#xff0c;并且7x24小时可靠运行是件非常有挑战性的工作。我们将探讨构建一个能够创建LLM、多模态模型以及各种其他AI产品的大规模AI系统所需的每个开发阶段。每个开发阶段如何相互关联&#xff…

国债与企业债:稳健与高收益的债券选择

债券市场是投资者获取稳定收益的重要渠道&#xff0c;而国债和企业债是最常见的两种债券类型。它们虽然都属于固定收益类产品&#xff0c;但在风险、收益和适用人群上有显著区别。 1. 概念对比&#xff1a;国家信用 vs. 企业信用 &#xff08;1&#xff09;国债&#xff08;政…

MySQL提升

事务 事务&#xff1a;在多个操作合在一起视为一个整体。要么就不做、要么就做完。 事务应该满足ACID A : 原子性。不可分割。C : 一致性。追求的目标&#xff0c;在开始到结束没有发生预定外的情况。I : 隔离性。不同的事务是独立的。D : 持久性。系统崩溃&#xff0c;数据依然…

MySQL用户和授权

开放MySQL白名单 可以通过iptables-save命令确认对应客户端ip是否可以访问MySQL服务&#xff1a; test: # iptables-save | grep 3306 -A mp_srv_whitelist -s 172.16.14.102/32 -p tcp -m tcp --dport 3306 -j ACCEPT -A mp_srv_whitelist -s 172.16.4.16/32 -p tcp -m tcp -…

基础篇:4. 页面渲染流程与性能优化

页面渲染流程与性能优化详解&#xff08;完整版&#xff09; 一、现代浏览器渲染流程&#xff08;详细说明&#xff09; 1. 构建DOM树 浏览器接收到HTML文档后&#xff0c;会逐步解析并构建DOM&#xff08;Document Object Model&#xff09;树。具体过程如下&#xff1a; (…

vue3 vite.config.js 引入bem.scss文件报错

[sass] Can’t find stylesheet to import. ╷ 1 │ use “/bem.scss” as *; │ ^^^^^^^^^^^^^^^^^^^^^^ ╵ src\App.vue 1:1 root stylesheet 分析 我们遇到了一个在Vue3项目中使用Vite时&#xff0c;在vite.config.js中引入bem.scss文件报错的问题。错误信息指出在App.vue…

python打卡第50天

知识点回顾&#xff1a; resnet结构解析CBAM放置位置的思考针对预训练模型的训练策略 差异化学习率三阶段微调 现在我们思考下&#xff0c;是否可以对于预训练模型增加模块来优化其效果&#xff0c;这里我们会遇到一个问题 预训练模型的结构和权重是固定的&#xff0c;如果修…

MySQL 并发控制和日志

MySQL 是一个广泛使用的关系数据库管理系统&#xff0c;在高并发环境中&#xff0c;如何有效地控制并发和管理日志至关重要。本文将详细介绍 MySQL 的并发控制机制和日志管理策略&#xff0c;以帮助开发人员和数据库管理员更好地理解和优化数据库性能。 一、并发控制 并发控制…