微服务-MybatisPlus下

news2024/11/18 3:29:11

微服务-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层
    @Override
    public 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为Vo
        UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);
        //3.2 转地址VO
        if(CollUtil.isNotEmpty(list)){
            userVO.setAddressList(BeanUtil.copyToList(list, AddressVO.class));
        }
        return userVO;
    }

第二题:

    @Override
    public 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 转换地址VO
        List<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为VO
            UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);
            list.add(userVO);
            //3.2 转换为VO
            userVO.setAddressList(addressMap.get(user.getId()));
        }
        return list;
    }

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

1.3 逻辑删除

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

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

在这里插入图片描述

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

在这里插入图片描述

    @Autowired
    protected AddressService addressService;

    @Test
    void 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,"冻结"),
    ;

    @EnumValue
    private 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 {
    @Test
    void 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=true
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: 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层
    @Override
    public 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层
    @Override
    public 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.coloradmin.cn/o/1955750.html

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

相关文章

哪里可以查找短视频素材?6个素材查找下载渠道分享!

在短视频的风靡浪潮中&#xff0c;不少创作者纷纷投身于这一领域&#xff0c;无论是分享生活点滴还是进行商业宣传&#xff0c;高质量的短视频内容总能吸引众多观众的目光。然而&#xff0c;精良的短视频制作离不开优质的素材支持。本文将为大家介绍6个优秀的高质量短视频素材下…

ProxmoxPVE虚拟化平台--U盘挂载、硬盘直通

界面说明 ### 网络设置 ISO镜像文件 虚拟机中使用到的磁盘 挂载USB设备 这个操作比较简单&#xff0c;不涉及命令 选中需要到的虚拟机&#xff0c;然后选择&#xff1a; 添加->USB设置选择使用USB端口&#xff1a;选择对应的U盘即可 硬盘直通 通常情况下我们需要将原有…

前端Long类型精度丢失:后端处理策略

文章目录 精度丢失的具体原因解决方法1. 使用 JsonSerialize 和 ToStringSerializer2. 使用 JsonFormat 注解3. 全局配置解决方案 结论 开发商城管理系统的品牌管理界面时&#xff0c;发现一个问题&#xff0c;接口返回品牌Id和页面展示的品牌Id不一致&#xff0c;如接口返回的…

C/C++大雪纷飞代码

目录 写在前面 C语言简介 EasyX简介 大雪纷飞 运行结果 写在后面 写在前面 本期博主给大家带来了C/C实现的大雪纷飞代码&#xff0c;一起来看看吧&#xff01; 系列推荐 序号目录直达链接1爱心代码https://want595.blog.csdn.net/article/details/1363606842李峋同款跳…

Prime Land(牛客)

计算出n-1 对n-1进行质因数分解 // Problem: Prime Land // Contest: NowCoder // URL: https://ac.nowcoder.com/acm/contest/21094/D // Memory Limit: 524288 MB // Time Limit: 2000 ms // // Powered by CP Editor (https://cpeditor.org)#include<iostream> #in…

PAT1060它们是否相等

感谢松鼠爱葡萄 大佬代码太简洁了ilil #include <iostream> #include <cstring>using namespace std;string change(string a, int n) { // 找到小数点的位置&#xff0c;从0开始计数int k a.find("."); // 如果字符串中没有 "."&…

Linux 理解文件系统

查看文件信息 ls -l 每行包含7列&#xff1a; 模式硬链接数文件所有者组大小最后修改时间文件名 stat查看更多信息 硬盘抽象理解 注意&#xff1a; 一个block的大小是由格式化的时候确定的&#xff0c;并且不可以更改mke2fs的-b选项可以设定block大小为1024、2048或4096字节…

光盘文件系统 (iso9660) 格式解析

越简单的系统, 越可靠, 越不容易出问题. 光盘文件系统 (iso9660) 十分简单, 只需不到 200 行代码, 即可实现定位读取其中的文件. 参考资料: https://wiki.osdev.org/ISO_9660 相关文章: 《光盘防水嘛 ? DVDR 刻录光盘泡水实验》 https://blog.csdn.net/secext2022/article/d…

GD 32 UNIX时间戳

前言 ... UINX时间戳定义 UNIX时间戳是一种表示时间的方法&#xff0c;广泛用于计算机系统和网络协议中。它定义的时间起点是1970年1月1日午夜&#xff08;协调世界时UTC&#xff09;&#xff0c;也就是所谓的“UNIX纪元”开始的时刻。 Unix 时间戳(Unix Timestamp)定义为从U…

DjangoRF实战-2-apps-users

1、用户模块 创建一个用户模块子应用&#xff0c;用来管理用户&#xff0c;和认证和授权。 1.1根目录创建apps&#xff0c; 为了使用方便&#xff0c;还需要再pycharm中设置一下资源路径&#xff0c;就可以自动提示 1.2注册子应用 1.3添加应用根目录到环境变量path python导…

搭建cool-admin-java(前端vue)项目

为什么选择 Cool Admin&#xff1f;​ 随着技术不断地发展&#xff0c;特别是最近 Ai 相关的技术发展&#xff0c;以往的框架已经越来越不能满足现代化的开发需求。 Cool Admin 做为后来者有后发优势&#xff0c;主要特点&#xff1a; Ai 编码&#xff0c;从页面到后端代码&…

成为git砖家(5): 理解 HEAD

文章目录 1. git rev-parse 命令2. 什么是 HEAD2.1 创建分支当并未切换&#xff0c; HEAD 不变2.2 切换分支&#xff0c;HEAD 改变2.3 再次切换分支&#xff0c; HEAD 再次改变 3. detached HEAD4. HEAD 表示分支、表示 detached HEAD 有什么区别&#xff1f;区别相同点 5. HEA…

【大模型学习】1:基于通用大语言模型的构建方法

基于通用大语言模型的构建 目录 前言 一、大模型是什么&#xff1f; 二、如何构建大语言模型&#xff1f; 1.基本介绍 2.数据 3.模型构建 总结 前言 本人之前没接触过大模型&#xff0c;研究生的研究方向也不是这一块的&#xff0c;所以是以工程的心态快速上手做到工科领域的不…

数据库实验:SQL Server创建数据库及基本表

一、实验目的&#xff1a; 1、掌握使用SQL SERVER Management Studio工具连接数据库引擎&#xff1b; 2、掌握使用CREATE TABLE 创建基本表的用法&#xff1b; 3、掌握使用ALTER TABLE 修改基本表的用法&#xff1b; 4、掌握使用DROP TABLE删除基本表的用法&#xff1b; 二…

【C++指南】类和对象(中)

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;《数据结构与算法》 期待您的关注

Python的输入规则

Python的输入特别有意思&#xff0c;它和C的输入不一样&#xff0c;它的输入的原型是类似于C的string类型&#xff0c;但是对于一些有意思的算法题来说&#xff0c;光是读入string型的内容并不容易解题&#xff0c;于是我们可以从两个方面来将输入给转化。 1. 先使用函数input…

【Node.js基础05】包的理解与使用

一&#xff1a;包的理解与简介 1 什么是包 包是一个将模块、代码、以及其他资料聚合成的文件夹 2 包的分类 项目包&#xff1a;编写项目代码的文件夹 软件包&#xff1a;封装工具和方法供开发者使用 3 为什么要在软件包中编写package.json文件 记录包的清单信息 二&…

用 node 搭建基于 gotenberg、LibreOffice 或者 onlyoffice 文档转换服务

1. 使用 gotenberg 和 LibreOffice a. 开启 docker&#xff0c;运行以下指令 docker run --rm -p 3000:3000 gotenberg/gotenberg:8 gotenbderg 默认运行在本地 3000 端口 b. 项目中添加如下依赖 npm install chromiumly dotenv -D chromiumly 是用来连接 gotenberg 服务的包…

深入理解计算机系统 CSAPP 练习题12.4

我们每次都用read_set初始化ready_set是因为我们每次都处理read_set里的描述符,这是我们希望服务器做的事情.每次一有描述符3或描述符0,select函数会更新ready_set ,我们判断更新后ready_set的情况.然后干对应的事. 由此可以看到select函数的神奇之处,它把一个复杂的事情简单化…

烯牛数据JS逆向:MD5数据加密?不存在的!

&#x1f50d; 步骤与思路详解 &#x1f575;️ 抓包数据接口 使用抓包工具捕获烯牛数据的接口请求&#xff0c;仔细观察请求体和响应体&#xff0c;发现数据均进行了加密处理。 &#x1f510; 定位到加密位置 分析抓取到的JS文件&#xff0c;找到负责加密的代码块。 &am…