微服务-MybatisPlus下

微服务-MybatisPlus下

文章目录

  • 微服务-MybatisPlus下
    • 1 MybatisPlus扩展功能
      • 1.1 代码生成
      • 1.2 静态工具
      • 1.3 逻辑删除
      • 1.4 枚举处理器
      • 1.5 JSON处理器
        • **1.5.1.定义实体**
        • **1.5.2.使用类型处理器**
      • **1.6 配置加密(选学)**
        • 1.6.1.生成秘钥
        • **1.6.2.修改配置**
        • **1.6.3.测试**
    • 2 插件功能
      • 2.1 分页插件
      • 2.2 通用分页实体
        • 2.2.1 简单分页查询
        • 2.2.2 通用分页查询

1 MybatisPlus扩展功能

1.1 代码生成

自己从头开始,则步骤如下图所示


在这里插入图片描述

在这里插入图片描述

1.2 静态工具

有的时候Service之间也会相互调用,为了避免出现循环依赖问题,MybatisPlus提供一个静态工具类:Db,其中的一些静态方法IService中方法 签名基本一致。主要差别就是由于是静态的,之前没有指定过实体对象,所以除save、update以外的函数几乎都要传入实体类,让底层调用反射机制,得到实体类。而save、update由于本身参数就需要传入一个实体类对象,此对象就可通过反射得到实体类,所以不需要再传入实体类。

实现CRUD功能:

案例:静态工具查询

需求:

  1. 改造根据id查询用户的接口,查询用户的同时,查询出用户对应的所有地址
  2. 改造根据id批量查询用户的接口,查询用户的同时,查询出用户对应的所有地址
  3. 实现根据用户id查询收货地址功能,需要验证用户状态,冻结用户抛出异常(练习)

可以看到以上需求一般都需要在注入了userService之后,再注入addressService才能实现(当然你用多表联合或者注入mapper当我没说)。那这就涉及到了多个Service的相互调用,出现循环依赖问题,这种情况下就可以使用静态工具来解决。而且静态工具不需要注入,这样就避免了循环依赖

第一题:

  • controller层
    @GetMapping("{id}")@ApiOperation(value = "根据id查询用户接口")public UserVO queryUserById(@ApiParam("用户id") @PathVariable("id") Long id){
//        User user = userService.getById(id);
//        UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);
//        return userVO;return userService.queryUserAndAddressById(id);}
  • service层
    @Overridepublic UserVO queryUserAndAddressById(Long id) {//1. 查询用户User user = getById(id);if(user == null || user.getStatus() == 2){throw new RuntimeException("用户状态异常");}//2. 查询地址//这里本来可以直接用本service的lambdaQuery函数 单位了避免循环依赖,就使用静态工具List<Address> list = Db.lambdaQuery(Address.class).eq(Address::getUserId, user.getId()).list();//3. 封装vo//3.1 转User的PO为VoUserVO userVO = BeanUtil.copyProperties(user, UserVO.class);//3.2 转地址VOif(CollUtil.isNotEmpty(list)){userVO.setAddressList(BeanUtil.copyToList(list, AddressVO.class));}return userVO;}

第二题:

    @Overridepublic List<UserVO> queryUserAndAddressByIds(List<Long> ids) {//1. 查询用户List<User> users = listByIds(ids);if(CollUtil.isEmpty(users)){return Collections.emptyList();}//2. 查询地址//2.1 获取用户id集合List<Long> userIds = users.stream().map(User::getId).collect(Collectors.toList());//2.2 根据用户id查询地址List<Address> addresses = Db.lambdaQuery(Address.class).in(Address::getUserId, userIds).list();//2.3 转换地址VOList<AddressVO> addressVOList = BeanUtil.copyToList(addresses, AddressVO.class);//2.4 用户地址集合分组处理、相同用户的放入一个集合(组)中Map<Long, List<AddressVO>> addressMap = new HashMap<>(0);if(CollUtil.isNotEmpty(addressVOList)) {addressMap = addressVOList.stream().collect(Collectors.groupingBy(AddressVO::getUserId));}//3. 转换为VO返回List<UserVO> list = new ArrayList<>(users.size());for(User user:users){//3.1 转换user的PO为VOUserVO userVO = BeanUtil.copyProperties(user, UserVO.class);list.add(userVO);//3.2 转换为VOuserVO.setAddressList(addressMap.get(user.getId()));}return list;}

也就是静态工具的使用和IService一样,只不过多了个实体类传入。当一个serivice中需要多个service时,就是用静态工具,用法类似,也有普通函数以及复杂条件查询函数,比如Db.lambdaQuery,Db.Db.lambdaUpdate。

1.3 逻辑删除

逻辑删除就是基于代码逻辑模拟删除效果,但并不会真正删除数据。思路如下:

  • 在表中添加一个字段标记数据是否被删除
  • 当删除数据时把标记置为1
  • 查询时只查询标记为0的数据

在这里插入图片描述

MybatisPlus提供了逻辑删除技能,无需改变方法调用的方法,而是在底层帮我们自动修改CRUD的语句。我们要做的就是在applicaiton.yaml文件中配置逻辑删除的字段名称和值即可。

在这里插入图片描述

    @Autowiredprotected AddressService addressService;@Testvoid testLogicDelete(){//1. 删除addressService.removeById(59L);//2. 查询Address address = addressService.getById(59L);System.out.println("address = " + address);}

虽然使用的remove方法,但是实际执行的是update方法。且在数据库中还存在,只不过deleted变成了true

在这里插入图片描述

1.4 枚举处理器

User类中有一个用户状态字段:

在这里插入图片描述

使用枚举表示状态,可以提高可读性。但是问题来了

在这里插入图片描述

数据库中status仍然是int类型,这就需要一个枚举类型到int类型的转化。

在这里插入图片描述

幸运的是Mybatis和mp帮我们解决了,如上图所示。只需要在枚举类中对应的字段上加上@EnumValue注释 mp就会自动把对应的字段值与数据库中列匹配

在这里插入图片描述

在applicaiton.yml中配置全局枚举类处理器

在这里插入图片描述

@JsonValue //用于枚举的返回值 数据库返回显示的值,比如此处是返回desc的值 也可以返回value的值 否则默认返回的是枚举的对象名 比如此处的NORMAL FROZEN

@Getter
public enum UserStatus {NORMAL(1,"正常"),FROZEN(2,"冻结"),;@EnumValueprivate final int value;@JsonValue  //用于枚举的返回值 数据库返回显示的值private final String desc;UserStatus(int value,String desc){this.value = value;this.desc = desc;}}

在这里插入图片描述

1.5 JSON处理器

数据库的user表中有一个info字段,是JSON类型:

在这里插入图片描述

格式像这样:

{"age": 20, "intro": "佛系青年", "gender": "male"}

而目前User实体类中却是String类型:

在这里插入图片描述

这样一来,我们要读取info中的属性时就非常不方便。如果要方便获取,info的类型最好是一个Map或者实体类

而一旦我们把info改为对象类型,就需要在写入数据库时手动转为String,再读取数据库时,手动转换为对象,这会非常麻烦。

意思就是对象的String字段可以被mp自动转换为数据库中的JSON,但是数据库中的JSON不能被mp自动帮我们转换为String或者相应的对象

因此MybatisPlus提供了很多特殊类型字段的类型处理器,解决特殊字段类型与数据库类型转换的问题。例如处理JSON就可以使用JacksonTypeHandler处理器。

在这里插入图片描述

1.5.1.定义实体

首先,我们定义一个单独实体类来与info字段的属性匹配:

在这里插入图片描述

代码如下:

package com.itheima.mp.domain.po;import lombok.Data;@Data
public class UserInfo {private Integer age;private String intro;private String gender;
}
1.5.2.使用类型处理器

接下来,将User类的info字段修改为UserInfo类型,并声明类型处理器:

在这里插入图片描述

测试可以发现,所有数据都正确封装到UserInfo当中了:

在这里插入图片描述

同时,为了让页面返回的结果也以对象格式返回,我们要修改UserVO中的info字段:

在这里插入图片描述

此时,在页面查询结果如下:

在这里插入图片描述

注意: 由于User类中嵌套了UserInfo类,出现了类的嵌套,那么就需要定义复杂的ResultMap,但是我们又不想定义,那么就直接在User类上加注释,autoResultMap = true 如下图所示

在这里插入图片描述

1.6 配置加密(选学)

目前我们配置文件中的很多参数都是明文,如果开发人员发生流动,很容易导致敏感信息的泄露。所以MybatisPlus支持配置文件的加密和解密功能。

我们以数据库的用户名和密码为例。

1.6.1.生成秘钥

首先,我们利用AES工具生成一个随机秘钥,然后对用户名、密码加密:

package com.itheima.mp;import com.baomidou.mybatisplus.core.toolkit.AES;
import org.junit.jupiter.api.Test;class MpDemoApplicationTests {@Testvoid contextLoads() {// 生成 16 位随机 AES 密钥String randomKey = AES.generateRandomKey();System.out.println("randomKey = " + randomKey);// 利用密钥对用户名加密String username = AES.encrypt("root", randomKey);System.out.println("username = " + username);// 利用密钥对用户名加密String password = AES.encrypt("MySQL123", randomKey);System.out.println("password = " + password);}
}

打印结果如下:

randomKey = 6234633a66fb399f
username = px2bAbnUfiY8K/IgsKvscg==
password = FGvCSEaOuga3ulDAsxw68Q==
1.6.2.修改配置

修改application.yaml文件,把jdbc的用户名、密码修改为刚刚加密生成的密文:

spring:datasource:url: jdbc:mysql://127.0.0.1:3306/mp?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=truedriver-class-name: com.mysql.cj.jdbc.Driverusername: mpw:QWWVnk1Oal3258x5rVhaeQ== # 密文要以 mpw:开头password: mpw:EUFmeH3cNAzdRGdOQcabWg== # 密文要以 mpw:开头
1.6.3.测试

在启动项目的时候,需要把刚才生成的秘钥添加到启动参数中,像这样:

–mpw.key=6234633a66fb399f

单元测试的时候不能添加启动参数,所以要在测试类的注解上配置:

在这里插入图片描述

然后随意运行一个单元测试,可以发现数据库查询正常。

2 插件功能

MyBatisPlus提供的内置拦截器有下面这些

2.1 分页插件

在未引入分页插件的情况下,MybatisPlus是不支持分页功能的,IServiceBaseMapper中的分页方法都无法正常起效。 所以,我们必须配置分页插件。

  • 首先,要在配置类中注册MyBatisPlus的核心插件,同时添加分页插件

  • 接着,就可以使用分页的API了

在这里插入图片描述

在这里插入图片描述

    void testPageQuery(){int pageNo = 1;int pageSize = 2;//1. 准备分页条件//1.1 分页条件Page<User> page = Page.of(pageNo, pageSize);//1.2 排序条件  可以指定多个 比如下面 余额相同则按id排序page.addOrder(new OrderItem("balance",true));page.addOrder(new OrderItem("id",true));//2. 分页查询Page<User> p = userService.page(page);//3. 解析long total = p.getTotal();  //总条数System.out.println("total = " + total);long pages = p.getPages();  //总页数System.out.println("pages = " + pages);List<User> records = p.getRecords();    //分页数据records.forEach(System.out::println);}

2.2 通用分页实体

2.2.1 简单分页查询

案例:简单分页查询案例

需求:遵循下面的接口规范,编写一个UserController接口,实现User的分页查询

在这里插入图片描述

  • 实体类
public class PageQuery {@ApiModelProperty("页码")private Integer pageNo;@ApiModelProperty("页大小")private Integer pageSize;@ApiModelProperty("排序字段")private String sortBy;@ApiModelProperty("是否升序")private Boolean isAsc;
}@Data
@EqualsAndHashCode(callSuper = true)
@ApiModel(description = "用户查询条件实体")
public class UserQuery extends PageQuery{@ApiModelProperty("用户名关键字")private String name;@ApiModelProperty("用户状态:1-正常,2-冻结")private Integer status;@ApiModelProperty("余额最小值")private Integer minBalance;@ApiModelProperty("余额最大值")private Integer maxBalance;
}
  • controller
    @GetMapping("/page")@ApiOperation(value = "根据条件分页查询用户接口")public PageDTO<UserVO> queryUsersPage(UserQuery query){return userService.queryUsersPage(query);}
  • impl层
    @Overridepublic PageDTO<UserVO> queryUsersPage(UserQuery query) {String name = query.getName();Integer status = query.getStatus();//1. 构建查询条件Page<User> page = Page.of(query.getPageNo(), query.getPageSize());if(StrUtil.isNotBlank(query.getSortBy())){//不为空page.addOrder(new OrderItem(query.getSortBy(),query.getIsAsc()));}else {//为空 默认按照更新时间排序page.addOrder(new OrderItem("update_time",false));}//2. 分页查询Page<User> p = lambdaQuery().like(name != null, User::getUsername, name).eq(status != null, User::getStatus, status)
//                .ge(minBalance != null, User::getBalance, minBalance)
//                .le(maxBalance != null, User::getBalance, maxBalance).page(page);//3. 封装VO结果PageDTO<UserVO> userVOPageDTO = new PageDTO<>();userVOPageDTO.setTotal(p.getTotal());userVOPageDTO.setPages(p.getPages());List<User> records = p.getRecords();if(CollUtil.isEmpty(records)){userVOPageDTO.setList(Collections.emptyList());}else {List<UserVO> userVOS = BeanUtil.copyToList(records, UserVO.class);userVOPageDTO.setList(userVOS);}//4. 返回return userVOPageDTO;}
2.2.2 通用分页查询

在这里插入图片描述

  • 改造PageQuery类
@Data
@ApiModel(description = "分页查询实体")
public class PageQuery {@ApiModelProperty("页码")private Integer pageNo = 1;@ApiModelProperty("页大小")private Integer pageSize = 5;@ApiModelProperty("排序字段")private String sortBy;@ApiModelProperty("是否升序")private Boolean isAsc = true;public <T>  Page<T> toMpPage(OrderItem ... orders){// 1.分页条件Page<T> p = Page.of(pageNo, pageSize);// 2.排序条件// 2.1.先看前端有没有传排序字段if (sortBy != null) {p.addOrder(new OrderItem(sortBy, isAsc));return p;}// 2.2.再看有没有手动指定排序字段if(orders != null){p.addOrder(orders);}return p;}public <T> Page<T> toMpPage(String defaultSortBy, boolean isAsc){return this.toMpPage(new OrderItem(defaultSortBy, isAsc));}public <T> Page<T> toMpPageDefaultSortByCreateTimeDesc() {return toMpPage("create_time", false);}public <T> Page<T> toMpPageDefaultSortByUpdateTimeDesc() {return toMpPage("update_time", false);}
}
  • 改造PageDTO类
@Data
@ApiModel(description = "分页结果")
@AllArgsConstructor
@NoArgsConstructor
public class PageDTO<T> {@ApiModelProperty("总条数")private Long total;@ApiModelProperty("总页数")private Long pages;@ApiModelProperty("总数据")private List<T> list;/*** 返回空分页结果* @param p MybatisPlus的分页结果* @param <V> 目标VO类型* @param <P> 原始PO类型* @return VO的分页对象*/public static <V, P> PageDTO<V> empty(Page<P> p){return new PageDTO<>(p.getTotal(), p.getPages(), Collections.emptyList());}/*** 将MybatisPlus分页结果转为 VO分页结果* @param p MybatisPlus的分页结果* @param voClass 目标VO类型的字节码* @param <V> 目标VO类型* @param <P> 原始PO类型* @return VO的分页对象*/public static <V, P> PageDTO<V> of(Page<P> p, Class<V> voClass) {// 1.非空校验List<P> records = p.getRecords();if (records == null || records.size() <= 0) {// 无数据,返回空结果return empty(p);}// 2.数据转换List<V> vos = BeanUtil.copyToList(records, voClass);// 3.封装返回return new PageDTO<>(p.getTotal(), p.getPages(), vos);}/*** 将MybatisPlus分页结果转为 VO分页结果,允许用户自定义PO到VO的转换方式* @param p MybatisPlus的分页结果* @param convertor PO到VO的转换函数* @param <V> 目标VO类型* @param <P> 原始PO类型* @return VO的分页对象*/public static <V, P> PageDTO<V> of(Page<P> p, Function<P, V> convertor) {// 1.非空校验List<P> records = p.getRecords();if (records == null || records.size() <= 0) {// 无数据,返回空结果return empty(p);}// 2.数据转换List<V> vos = records.stream().map(convertor).collect(Collectors.toList());// 3.封装返回return new PageDTO<>(p.getTotal(), p.getPages(), vos);}}
  • impl层
    @Overridepublic PageDTO<UserVO> queryUsersPage(UserQuery query) {String name = query.getName();Integer status = query.getStatus();//1. 构建查询条件Page<User> page = query.toMpPageDefaultSortByUpdateTimeDesc();Page<User> p = lambdaQuery().like(name != null, User::getUsername, name).eq(status != null, User::getStatus, status).page(page);return PageDTO.of(page, UserVO.class);}

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

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

相关文章

网络安全防御【IPsec VPN搭建】

目录 一、实验拓扑图 二、实验要求 三、实验思路 四、实验步骤&#xff1a; 修改双机热备的为主备模式&#xff1a; 2、配置交换机LSW6新增的配置&#xff1a; 3、防火墙&#xff08;FW4&#xff09;做相关的基础配置&#xff1a; 4、搭建IPsec VPN通道 &#xff08;1…

Java代码基础算法练习-求杨辉三角第n行的值-2024.07.27

任务描述&#xff1a; 给定一个非负整数n&#xff0c;生成「杨辉三角」的第n行。&#xff08;1<n<10&#xff09;在「杨辉三角」中&#xff0c;每 个数是它左上方和右上方的数的和。 &#xff08;提示&#xff0c;第一列数值为1&#xff0c;如数组下标用i,j表示&#xf…

独占电脑资源来执行一个应用

1. 背景 在人工智能时代&#xff0c;随着神经网络的发展&#xff0c;训练人工智能模型需要越来越多的硬件资源&#xff0c;例如&#xff0c;利用10万条棋局数据、使用一台PC电脑、完整地训练一次确定性神经网络五子棋模型&#xff0c;需要花费一年半的时间。随着训练数据的增长…

APP逆向 day23司小宝逆向

一.前言 今天也是讲最后一个基础知识点了&#xff0c;ptrace占坑&#xff0c;这个也算是一个坑&#xff0c;今天通过这个案例和大家讲一下&#xff0c;今天这个案例我们来整验证码登录&#xff0c;版本选择4.7.8 二.抓包分析 抓包发现&#xff0c;请求头里的东西通过改包发现…

Spring Boot:图书管理系统(一)

1.编写用户登录接口 代码&#xff1a; package com.example.demo;import jakarta.servlet.http.HttpSession; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotatio…

技术成神之路:设计模式(九)备忘录模式

介绍 备忘录模式&#xff08;Memento Pattern&#xff09;是一种行为设计模式&#xff0c;它允许在不破坏封装性的前提下捕获和恢复对象的内部状态。通过备忘录模式&#xff0c;可以在程序运行过程中保存和恢复对象的某个状态&#xff0c;从而实现“撤销”等功能。 1.定义 备忘…

【BUG】已解决:UnicodeDecodeError: ‘utf-8’ codec can’t decode bytes in position 10

UnicodeDecodeError: ‘utf-8’ codec can’t decode bytes in position 10 目录 UnicodeDecodeError: ‘utf-8’ codec can’t decode bytes in position 10 【常见模块错误】 【解决方案】 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页&#x…

使用python内置的虚拟环境

在一台机器上安装了太多的第三方python库&#xff0c;它们依赖相同的库可能版本不同&#xff0c;就会造成某些第三方库崩溃&#xff0c;之前可以使用的库可能就会坏掉不能用了&#xff0c;所以可以使用虚拟环境运行不同的程序&#xff0c;python有内置的虚拟环境&#xff1b; …

前端八股文 promise async await 的理解

promise是什么 Promise 是异步编程的一种解决方案&#xff0c;比传统的解决方案——回调函数和事件——更合理和更强大。 目的 解析 吴优编程 &#xff08;解决异步编程中的嵌套问题的&#xff0c;将嵌套的格式 用peomise 写成同步&#xff09; promise.then() 是成功后继…

Cocos Creator2D游戏开发(4)-飞机大战(2)-编辑器界面

编辑器几个重要板块 参考: https://docs.cocos.com/creator/3.8/manual/zh/editor/ (1) 场景编辑器: 仅看2D视图: 按钮作用依次是: 平移, 旋转,缩放,矩形变换,增量吸附工具,最后三个,前俩是变换工具,最后一个是布局组件 矩形变换: 中心点和锚点切换 以后用到慢慢整吧! (2)层…

AI服务器产业链研究分析

AI服务器产业链初探 一、AI服务器的技术架构与构成 AI服务器的主要构成包括&#xff1a; 芯片种类丰富&#xff0c;包括X86、ARM、MIPS等架构的CPU&#xff0c;以及GPU、FPGA、ASIC和NPU等。 内存&#xff1a;DRAM、HBM&#xff08;高带宽存储&#xff09;。 本地存储&#…

前端开发调试工具推荐分类整理

具体前往&#xff1a;前端调试工具分类整理汇总

黑马Java零基础视频教程精华部分_6_字符串

系列文章目录 文章目录 系列文章目录前言一、API是什么&#xff1f; API帮助文档案例&#xff1a;API文档练习Step1&#xff1a;查找文档中Scanner内容。Step2&#xff1a;学习文档中Scanner内容。 二、字符串String类1、String概述总结&#xff1a; 创建String对象的两种方式2…

java学习--String类StringBuffer类StringBuilder类

String类简介 关系图&#xff1a; value不可修改的是value指向的地址&#xff0c;因为可以value为一个数组&#xff0c;而数组名其实就相当于一个指针&#xff0c;指向着一块地址&#xff0c;然后在指向的地址里存放相应的值&#xff0c;值可以任意是什么&#xff0c;但是地址不…

RedHat Enterprise Linux 7 YUM源(本地/网络源)配置详解

目录 一、挂载 二、建立本地源 三、建立网络源 四、验证可行性 一、挂载 ——将光盘挂载到 /mnt 下 当/mnt中有如图内容时&#xff0c;即挂载成功 若挂载光驱/dev/sr0时报错&#xff1a;mount: no medium found on /dev/sr0 解决措施&#xff1a;查看该设备状态是否全部勾选…

MATLAB仿真:数字信号处理IIR数字滤波器设计

目录 1&#xff0e;实验目的 2&#xff0e;实验原理 3&#xff0e;实验仪器及设备 4. 实验内容及步骤 5&#xff0e;信号产生函数mstg清单 6.实验程序及波形如下&#xff1a; 1&#xff0e;实验目的 &#xff08;1&#xff09;熟悉用双线性变换法设计IIR数字滤波器的原理…

Python——Pandas(第三讲)

文章目录 修改替换变量值对应数值的替换指定数值范围的替换 虚拟变量变换数值变量分段数据分组基于拆分进行筛选 分组汇总使用 agg 函数进行汇总引用自定义函数 长宽格式转换转换为最简格式长宽型格式的自由互转 多个数据源的合并数据的横向合并concat 命令 处理缺失值认识缺失…

【题解】328. 奇偶链表(链表)

https://leetcode.cn/problems/odd-even-linked-list/description/?envTypestudy-plan-v2&envIdleetcode-75 class Solution { public:// 定义一个函数&#xff0c;用于将链表中的奇数和偶数节点分开ListNode* oddEvenList(ListNode* head) {// 创建两个哑节点&#xff…

python+vue3+onlyoffice在线文档系统实战20240723笔记,项目界面设计和初步开发

经过之前的学习,已经能够正常打开文档了。 目前为止,我们的代码能够实现: 打开文档编辑文档手动保存自动保存虽然功能依然比较少,但是我们已经基本实现了文档管理最核心的功能,而且我们有个非常大的优势,就是支持多人同时在线协同编辑。 现在我们要开发项目,我们得做基…

抽奖大转盘uni-push使用websocket协议实现uniapp+uniCloud实时推送在线互动抽奖项目打包H5微信小程序_咸虾米

嗨&#xff0c;大家好&#xff0c;我是爱搞知识的咸虾米&#xff0c;今天给大家带来的这们课程是使用uni-push实时推送&#xff0c;完成的在线互动抽奖综合项目。 常规的页面只有在手动刷新的时候&#xff0c;才能获取到服务端最新的数据&#xff0c;而websocket可以实现长连…