若依-导出后端解析

news2025/3/12 3:41:16

针对若依框架微服务版本学习

若依导入导出功能的具体使用详见:后台手册 | RuoYi

1.导出逻辑:

导出文件的逻辑是先创建一个临时文件,等待前端请求下载结束后马上删除这个临时文件。但是有些下载插件,例如迅雷(他们是二次下载),这个时候文件已经删除,会导致异常,找不到文件。可强制把所有的导出都改成流的形式返回给前端,不采用临时文件的方法,具体方法后台手册都有(目前下载的代码为流的形式)

2.代码详解

export 导出的入口函数

1.controller层

调用userService.selectUserList(user)方法查询数据,util.exportExcel()方法以流的形式返回前端文件

@Log(title = "用户管理", businessType = BusinessType.EXPORT)
    @RequiresPermissions("system:user:export")
    @PostMapping("/export")
    public void export(HttpServletResponse response, SysUser user)
    {
        List<SysUser> list = userService.selectUserList(user);
// 创建 ExcelUtil<SysUser>对象,入参为 SysUser.class
        ExcelUtil<SysUser> util = new ExcelUtil<SysUser>(SysUser.class);
        util.exportExcel(response, list, "用户数据");
    }

2.调用ExcelUtil类

 /**
     * 对list数据源将其里面的数据导入到excel表单
     *
     * @param response 返回数据
     * @param list 导出数据集合
     * @param sheetName 工作表的名称
     * @param title 标题
     * @return 结果
     */
    public void exportExcel(HttpServletResponse response, List<T> list, String sheetName, String title)
    {
//告诉浏览器或客户端,响应的内容是一个 Excel 文件(XLSX 格式)通常用于文件下载
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
//设置 HTTP 响应的字符编码为 UTF-8
        response.setCharacterEncoding("utf-8");
//调用init方法对数据处理
        this.init(list, sheetName, title, Type.EXPORT);
//对list数据源将其里面的数据导入到excel表单
        exportExcel(response);
    }
1.init方法
public void init(List<T> list, String sheetName, String title, Type type)
    {
//  需要导出的数据交给list
        if (list == null)
        {
            list = new ArrayList<T>();
        }
        this.list = list;
// 生成execl的sheet名称
        this.sheetName = sheetName;
// 类型(0:导出导入;1:仅导出;2:仅导入)
        this.type = type;
//赋值标题
        this.title = title;
// 主要完成对 List<Object[]> fields 属性的赋值。
        createExcelField();
// 创建一个 Workbook 工作簿对象  
        createWorkbook();
//创建excel第一行标题
        createTitle();
 //创建对象的子列表名称
        createSubHead();
    }
1.createExcelField();
    private void createExcelField()
    {
        //获取字段注解信息
        this.fields = getFields();
        //通过 Java 的 Stream API 对 fields 进行排序
        this.fields = this.fields.stream().sorted(Comparator.comparing(objects -> ((Excel) objects[1]).sort())).collect(Collectors.toList());
        //根据注解获取最大行高
        this.maxHeight = getRowHeight();
    }


 /**
     * 获取字段注解信息
     */
    public List<Object[]> getFields()
    {
        List<Object[]> fields = new ArrayList<Object[]>();
        List<Field> tempFields = new ArrayList<>();
        //获得传入实体类的父类的所有声明字段
        tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields()));
        //获得传入实体类所有声明字段
        tempFields.addAll(Arrays.asList(clazz.getDeclaredFields()));
        //是否需要自定义显示属性列showColumn设置,详见官网文档
        if (StringUtils.isNotEmpty(includeFields))
        {
            //遍历临时字段集合
            for (Field field : tempFields)
            {
                //判断字段是否在自定义显示属性列中
                //判断字段是否有@Excels注解
                if (ArrayUtils.contains(this.includeFields, field.getName()) || field.isAnnotationPresent(Excels.class))
                {
                    // 添加字段信息
                    addField(fields, field);
                }
            }
        }
        //hideColumn()自定义隐藏Excel中列属性
        else if (StringUtils.isNotEmpty(excludeFields))
        {
            for (Field field : tempFields)
            {
                if (!ArrayUtils.contains(this.excludeFields, field.getName()))
                {
                    addField(fields, field);
                }
            }
        }
        else
        {
            for (Field field : tempFields)
            {
                addField(fields, field);
            }
        }
        return fields;
    }


    /**
     * 添加字段信息
     */
    public void addField(List<Object[]> fields, Field field)
    {
        // 单注解
        if (field.isAnnotationPresent(Excel.class))
        {
            //获得注解实例
            Excel attr = field.getAnnotation(Excel.class);
            if (attr != null && (attr.type() == Type.ALL || attr.type() == type))
            {
                //创建一个包含两个元素的数组,其中数组的类型是 Object,元素类型会根据它们的实际类型而定
                fields.add(new Object[] { field, attr });
            }
            //判断field的类型是否是collection集合类型
            if (Collection.class.isAssignableFrom(field.getType()))
            {
                //获得对象的子列表方法
                subMethod = getSubMethod(field.getName(), clazz);
                // getGenericType()方法获取某个字段的泛型类型的方法
                ParameterizedType pt = (ParameterizedType) field.getGenericType();
                //getActualTypeArguments()方法从一个泛型类型中获取实际的类型参数,并将其转换为 Class<?> 类型
                Class<?> subClass = (Class<?>) pt.getActualTypeArguments()[0];
                //通过FieldUtils.getFieldsListWithAnnotationf()方法中反射,找到类subClass中所有带有Excel注解的字段,并将它们存储在 subFields 变量
                this.subFields = FieldUtils.getFieldsListWithAnnotation(subClass, Excel.class);
            }
        }

        // 多注解
        if (field.isAnnotationPresent(Excels.class))
        {
            Excels attrs = field.getAnnotation(Excels.class);
            Excel[] excels = attrs.value();
            for (Excel attr : excels)
            {
                if (StringUtils.isNotEmpty(includeFields))
                {
                    if (ArrayUtils.contains(this.includeFields, field.getName() + "." + attr.targetAttr())
                            && (attr != null && (attr.type() == Type.ALL || attr.type() == type)))
                    {
                        fields.add(new Object[] { field, attr });
                    }
                }
                else
                {
                    if (!ArrayUtils.contains(this.excludeFields, field.getName() + "." + attr.targetAttr())
                            && (attr != null && (attr.type() == Type.ALL || attr.type() == type)))
                    {
                        fields.add(new Object[] { field, attr });
                    }
                }
            }
        }
    }



 /**
     * 获取对象的子列表方法
     * 
     * @param name 名称
     * @param pojoClass 类对象
     * @return 子列表方法
     *
     */
    //通过字段名(name)获取给定类(pojoClass)的 getter 方法。
    // 这个方法根据字段名生成对应的 getter 方法名称,然后使用反射来查找该方法
    public Method getSubMethod(String name, Class<?> pojoClass)
    {
        //将字段名 name 转换为标准的 getter 方法名
        StringBuffer getMethodName = new StringBuffer("get");
        //将字段的第一个字母转为大写
        getMethodName.append(name.substring(0, 1).toUpperCase());
        //提取 name 字符串从索引位置 1 开始到字符串结束的部分
        getMethodName.append(name.substring(1));
        Method method = null;
        try
        {
            //pojoClass.getMethod() 方法通过反射查找指定的方法。
            // getMethodName.toString() 返回的是动态生成的 getter 方法名
            //new Class[] {} 表示该方法不接受任何参数(即没有输入参数)
            method = pojoClass.getMethod(getMethodName.toString(), new Class[] {});
        }
        catch (Exception e)
        {
            log.error("获取对象异常{}", e.getMessage());
        }
        return method;
    }

1.clazz.getSuperclass().getDeclaredFields()
clazz:是一个 Class 类型的对象,它表示一个 Java 类。
getSuperclass():是 Class 类中的方法,返回该类的父类(即继承自哪个类)。如果该类没有父类(例如 Object 类),则返回 null。
getDeclaredFields():是 Class 类中的方法,返回该类所有声明的字段,包括私有字段和保护字段等(不包括继承的字段)。
例如:Dog类继承Animal类
clazz.getSuperclass() 获取的是 Dog 类的父类 Animal 类。
getDeclaredFields() 获取了 Animal 类中声明的字段(name、age 和 species),它们的访问修饰符分别是 private、protected 和 public。
getDeclaredFields() 不会返回父类继承的字段,如果需要获取父类的字段(包括继承的),可以使用 getFields()。
getDeclaredFields() 回的是类中所有声明的字段,包括 private、protected、public 和默认访问权限(包私有)的字段。Java的private字段访问权限是受保护的,即使反射获取到私有字段,也无法直接访问它,需要使用 setAccessible(true) 来允许访问该字段
2.public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
ava 反射 API 中的方法,用于检查某个字段是否被特定的注解所标注。它属于 java.lang.reflect.Field 类。通过该方法,你可以判断某个字段是否使用了某个注解
对于反射操作非常有用,尤其是在需要处理动态注解或者特定配置时
3.public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
field.getAnnotation 是 Java 反射 API 中 java.lang.reflect.Field 类的一个方法,用于获取指定字段上的特定注解实例。与 isAnnotationPresent 方法不同,getAnnotation 返回注解的实例,如果字段上没有该注解,则返回 null。
4.public boolean isAssignableFrom(Class<?> cls)
Java 中 Class 类的一个方法,用于判断某个类或接口的对象是否可以赋值给另一个类或接口的变量。它在反射或类型检查的场景中非常常见。
检查当前类是否可以接受传入类的实例(即目标类的对象能否赋值给当前类的变量)
参数:
cls:要与当前类进行比较的类或接口的 Class 对象。
返回值:
如果当前类可以接受 cls 类型的实例(即 cls 类型的对象可以赋值给当前类的引用变量),则返回 true。
否则,返回 false。

用于在运行时进行类型兼容性检查的工具,特别适用于需要动态判断类型关系的情况
类型检查:检查某个类或接口是否可以赋值给另一种类型的变量。
反射:在使用反射时,可能需要验证某个对象是否可以赋给特定类型的变量。
动态类型检查:在运行时检查一个对象能否安全地被强制转换为另一类型,避免出现 ClassCastException报错。
5.ParameterizedType pt = (ParameterizedType) field.getGenericType();
getGenericType() 是用于获取某个字段(Field 对象)的泛型类型的方法。返回一个 Type 对象
常见的返回类型:
原始类型: 如果字段没有泛型,getGenericType() 返回的 Type 是该字段的原始类型(例如 int, String 等)。
ParameterizedType: 如果字段的类型是一个带有泛型的类型(如 List<String>),getGenericType() 返回的是 ParameterizedType,它表示该类型的具体泛型参数。
GenericArrayType: 如果字段是泛型数组类型(如 T[] 或 List<String>[]),则返回 GenericArrayType。

ParameterizedType:ParameterizedType 是 Type 接口的一个实现,表示带有类型参数的泛型类型
2.createWorkbook()
 /**
     * 创建一个工作簿
     */
    public void createWorkbook()
    {
        // 创建一个SXSSFWorkbook工作簿,最大行数为500
        this.wb = new SXSSFWorkbook(500);
        // 在工作簿中创建一个工作表(Sheet)
        this.sheet = wb.createSheet();
        // 设置工作表的名称
        wb.setSheetName(0, sheetName);
        // 创建样式
        this.styles = createStyles(wb);
    }
1.SXSSFWorkbook类
  • SXSSFWorkbook 是 Apache POI 库中的一个类,专门用于处理大型 Excel 文件。它支持在内存中流式写入数据,而不是将整个工作簿保存在内存中,因此可以处理非常大的文件,而不容易导致内存溢出。采用“滚动式写入”技术,将数据按需写入磁盘
  • 500 作为构造函数的参数表示在内存中同时保留的最大行数。在这个例子中,它设置每次最多将 500 行加载到内存中,超出 500 行的数据将被写入磁盘。
3.createTitle()
   /**
     * 创建excel第一行标题
     */
    public void createTitle()
    {
        if (StringUtils.isNotEmpty(title))
        {
            //计算标题行的最后一列的列号,默认是 fields 列表的大小减去 1
            int titleLastCol = this.fields.size() - 1;
            //判断是否有对象的子属性列表
            if (isSubList())
            {
                // 如果有子属性列表,标题的最后一列索引要加上子属性的大小
                titleLastCol = titleLastCol + subFields.size() - 1;
            }
            //创建一个新的行,作为标题行
            Row titleRow = sheet.createRow(rownum == 0 ? rownum++ : 0);
            // 设置标题行的高度为30磅
            titleRow.setHeightInPoints(30);
            //在标题行的第一个单元格(列索引为 0)创建一个单元格对象。这是标题文本所在的单元格
            Cell titleCell = titleRow.createCell(0);
            //设置标题单元格的样式
            titleCell.setCellStyle(styles.get("title"));
            //设置标题单元格的内容为 title
            titleCell.setCellValue(title);
            //合并单元格:通过 sheet.addMergedRegion() 方法合并标题行的单元格
            sheet.addMergedRegion(new CellRangeAddress(titleRow.getRowNum(), titleRow.getRowNum(), 0, titleLastCol));
        }
    }
4.createSubHead()
   /**
     * 创建对象的子列表名称
     */
    public void createSubHead()
    {
        if (isSubList())
        {
            //创建一个新行,作为子表头。rownum 是当前的行号,表示标题行之后的行
            Row subRow = sheet.createRow(rownum);
            int column = 0;
            //计算子字段的大小
            int subFieldSize = subFields != null ? subFields.size() : 0;
            //遍历fields数组。每一项包含一个 Field 对象和一个 Excel 注解对象
            for (Object[] objects : fields)
            {
                //获取字段对象
                Field field = (Field) objects[0];
                //获取与该字段相关的Excel注解,通常用于指定字段如何映射到Excel表格的列,比如列名、颜色等。
                Excel attr = (Excel) objects[1];
                //检查当前字段是否为 Collection 类型(如 List, Set 等)
                if (Collection.class.isAssignableFrom(field.getType()))
                {
                    //为子表头行的当前列创建一个单元格
                    Cell cell = subRow.createCell(column);
                    //将 Excel 注解中的 name 值设置为单元格的内容
                    cell.setCellValue(attr.name());
                    //设置单元格样式
                    cell.setCellStyle(styles.get(StringUtils.format("header_{}_{}", attr.headerColor(), attr.headerBackgroundColor())));
                    //如果子字段的数量大于 1,意味着该字段需要跨越多个列,因此需要合并单元格
                    if (subFieldSize > 1)
                    {
                        //创建一个单元格范围对象
                        CellRangeAddress cellAddress = new CellRangeAddress(rownum, rownum, column, column + subFieldSize - 1);
                        //将合并区域添加到表格中
                        sheet.addMergedRegion(cellAddress);
                    }
                    //更新列索引,跳过已合并的子字段列
                    column += subFieldSize;
                }
                else
                {
                    Cell cell = subRow.createCell(column++);
                    cell.setCellValue(attr.name());
                    cell.setCellStyle(styles.get(StringUtils.format("header_{}_{}", attr.headerColor(), attr.headerBackgroundColor())));
                }
            }
            rownum++;
        }
    }
2.exportExcel(HttpServletResponse response)
    /**
     * 对list数据源将其里面的数据导入到excel表单
     * 
     * @return 结果
     */
    public void exportExcel(HttpServletResponse response)
    {
        try
        {
            writeSheet();//将数据写入Excel表单
            //将工作簿写入HttpServletResponse输出流,以便客户端下载
            wb.write(response.getOutputStream());
        }
        catch (Exception e)
        {
            log.error("导出Excel异常{}", e.getMessage());
        }
        finally
        {
            IOUtils.closeQuietly(wb);
        }
    }



   /**
     * 创建写入数据到Sheet
     */
    public void writeSheet()
    {
        // 取出一共有多少个sheet.
        //Math.ceil() 计算总页数,并确保至少有一个工作表(即 Math.max(1, ...))
        //list.size() 是数据列表的大小,sheetSize 是每个工作表的最大行数。
        int sheetNo = Math.max(1, (int) Math.ceil(list.size() * 1.0 / sheetSize));
        for (int index = 0; index < sheetNo; index++)
        {
            // 创建新的工作表
            createSheet(sheetNo, index);

            // 产生一行
            Row row = sheet.createRow(rownum);
            // 初始化列索引
            int column = 0;
            // 写入各个字段的列头名称
            for (Object[] os : fields)
            {
                Field field = (Field) os[0]; // 获取字段对象
                Excel excel = (Excel) os[1]; // 获取Excel注解
                if (Collection.class.isAssignableFrom(field.getType()))
                {
                    for (Field subField : subFields)
                    {
                        // 获取子字段的Excel注解
                        Excel subExcel = subField.getAnnotation(Excel.class);
                        // 创建子字段表头单元格
                        this.createHeadCell(subExcel, row, column++);
                    }
                }
                else // 如果字段不是集合类型
                {
                    // 创建普通字段的表头单元格
                    this.createHeadCell(excel, row, column++);
                }
            }
            //判断type类型是导出
            if (Type.EXPORT.equals(type))
            {
                // 填充数据到Excel行
                fillExcelData(index, row);
                // 添加统计行
                addStatisticsRow();
            }
        }
    }


  /**
     * 填充excel数据
     * 
     * @param index 序号
     * @param row 单元格行
     */
    //@SuppressWarnings 注解用于抑制编译器产生的警告信息
    //unchecked 消除 "类型不安全" 警告
    @SuppressWarnings("unchecked")
    public void fillExcelData(int index, Row row)
    {
        //根据当前工作表的索引和每个工作表的最大行数,确定当前工作表需要填充的数据范围。
        int startNo = index * sheetSize;
        int endNo = Math.min(startNo + sheetSize, list.size());
        int currentRowNum = rownum + 1; // 从标题行后开始
        //遍历数据列表并填充 Excel 行
        for (int i = startNo; i < endNo; i++)
        {
            row = sheet.createRow(currentRowNum);
            T vo = (T) list.get(i);
            int column = 0;
            //获取子列表最大数
            int maxSubListSize = getCurrentMaxSubListSize(vo);
            for (Object[] os : fields)
            {
                Field field = (Field) os[0];
                Excel excel = (Excel) os[1];
                if (Collection.class.isAssignableFrom(field.getType()))
                {
                    try
                    {
                        //如果字段是集合类型(如 List 或 Set),首先通过 getTargetValue() 方法获取该字段的值(即集合对象)
                        Collection<?> subList = (Collection<?>) getTargetValue(vo, field, excel);
                        //如果集合不为空,遍历集合中的每个元素 subVo(即子对象),并为每个子对象创建一个子行 subRow
                        if (subList != null && !subList.isEmpty())
                        {
                            int subIndex = 0;
                            for (Object subVo : subList)
                            {
                                Row subRow = sheet.getRow(currentRowNum + subIndex);
                                if (subRow == null)
                                {
                                    subRow = sheet.createRow(currentRowNum + subIndex);
                                }
                                //在子行中,遍历子字段 subFields,为每个子字段创建一个单元格。
                                int subColumn = column;
                                for (Field subField : subFields)
                                {
                                    Excel subExcel = subField.getAnnotation(Excel.class);
                                    //调用 addCell() 方法填充每个子字段的值到 Excel 单元格中
                                    addCell(subExcel, subRow, (T) subVo, subField, subColumn++);
                                }
                                subIndex++;
                            }
                            //column 变量在处理完一个集合字段后增加,跳到下一个字段的位置
                            column += subFields.size();
                        }
                    }
                    catch (Exception e)
                    {
                        log.error("填充集合数据失败", e);
                    }
                }
                else
                {
                    // 创建单元格并设置值
                    addCell(excel, row, vo, field, column);
                    if (maxSubListSize > 1 && excel.needMerge())
                    {
                        //如果该字段需要合并单元格(excel.needMerge() 返回 true),并且该字段有多个子集合项(maxSubListSize > 1),
                        // 则通过 sheet.addMergedRegion() 方法合并相应的单元格。
                        sheet.addMergedRegion(new CellRangeAddress(currentRowNum, currentRowNum + maxSubListSize - 1, column, column));
                    }
                    column++;
                }
            }
            //更新当前行号
            currentRowNum += maxSubListSize;
        }
    }

总结:以上是若依导出功能的关键代码,理解的可能不太全面,仅供个人参考

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

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

相关文章

华为OD机试九日集训第1期 - 按算法分类,由易到难,循序渐进,提升编程能力和解题技巧,从而提高机试通过率(Python/JS/C/C++)

目录 一、适合人群二、本期训练时间三、如何参加四、数据结构与算法大纲五、华为OD九日集训第1期第1天、逻辑分析第2天、数组第3天、双指针第4天、map与list第5天、队列第6天、栈第7天、滑动窗口第8天、二叉树第9天、矩阵 六、国内直接使用满血ChatGPT4o、o1、o3-mini-high、Cl…

webshell一些上传心得

我们以upload-labs为基础 一、前端拦截&#xff1a; 如第一关 工作方式&#xff1a; 直接在前端拦截 绕过方式&#xff1a; 因为没有限制后端&#xff0c;所有可以用bs 绕过前端修改格式即可 将需要上传的php文件改成jpg格式 使用burp suite 拦截上传后&#xff0c;使用re…

ROS实践(二)构建Gazebo机器人模型文件urdf

目录 一、基础语法 1. urdf文件组成 2. robot根标签 3. link 和 joint标签 4. sensor标签 二、 实验&#xff1a;使用launch文件启动rviz查看机器人模型 1. 编写机器人模型的urdf文件。 2. 编写launch文件。 3. 运行launch&#xff0c;查看效果。 URDF&#xff08;Unifi…

Linux 入门:常用命令速查手册

目录 一.指令 1.pwd&#xff08;显示所在路径&#xff09; 2.ls&#xff08;列出所有子目录与文件&#xff09; 3.touch&#xff08;创建文件&#xff09; 4.mkdir&#xff08;创建目录&#xff09; 5.cd&#xff08;改变所处位置&#xff09; 6.rm&#xff08;删除&…

2路模拟量同步输出卡、任意波形发生器卡—PCIe9100数据采集卡

品牌&#xff1a;阿尔泰科技 型号&#xff1a; PCIe9100、PCIe9101、PXIe9100、PXIe9101 产品系列&#xff1a;任意波形发生器 支持操作系统&#xff1a;XP、Win7、Win8、Win10 简要介绍&#xff1a; 910X 系列是阿尔泰科技公司推出的 PCIe、PXIe 总线的任意波形发生器&…

Facebook 隐私保护技术的发展与未来趋势

Facebook 隐私保护技术的发展与未来趋势 在这个数字化时代&#xff0c;个人隐私保护已成为全球关注的焦点。Facebook&#xff0c;作为全球最大的社交网络平台之一&#xff0c;其在隐私保护技术的发展上扮演着重要角色。本文将探讨 Facebook 在隐私保护技术方面的进展&#xff…

Python基于Django的医用耗材网上申领系统【附源码、文档说明】

博主介绍&#xff1a;✌Java老徐、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;&…

⭐LeetCode(数学分类) 48. 旋转图像——优美的数学法转圈(原地修改)⭐

⭐LeetCode(数学分类) 48. 旋转图像——优美的数学法转圈(原地修改)⭐ 示例 1&#xff1a; 输入&#xff1a;root [5,3,6,2,4,null,8,1,null,null,null,7,9] 输出&#xff1a;[1,null,2,null,3,null,4,null,5,null,6,null,7,null,8,null,9] 示例 2&#xff1a; 输入&#xff1…

深度学习PyTorch之13种模型精度评估公式及调用方法

深度学习pytorch之22种损失函数数学公式和代码定义 深度学习pytorch之19种优化算法&#xff08;optimizer&#xff09;解析 深度学习pytorch之4种归一化方法&#xff08;Normalization&#xff09;原理公式解析和参数使用 深度学习pytorch之简单方法自定义9类卷积即插即用 实时…

tomcat单机多实例部署

一、部署方法 多实例可以运行多个不同的应用&#xff0c;也可以运行相同的应用&#xff0c;类似于虚拟主机&#xff0c;但是他可以做负载均衡。 方式一&#xff1a; 把tomcat的主目录挨个复制&#xff0c;然后把每台主机的端口给改掉就行了。 优点是最简单最直接&#xff0c;…

Java开发者如何接入并使用DeepSeek

目录 一、准备工作 二、添加DeepSeek SDK依赖 三、初始化DeepSeek客户端 四、数据上传与查询 五、数据处理与分析 六、实际应用案例 七、总结 【博主推荐】&#xff1a;最近发现了一个超棒的人工智能学习网站&#xff0c;内容通俗易懂&#xff0c;风格风趣幽默&#xff…

win10电脑鼠标速度突然变的很慢?

电脑鼠标突然变很慢&#xff0c;杀毒检测后没问题&#xff0c;鼠标设置也没变&#xff0c;最后发现可能是误触鼠标的“DPI”调节键。 DPI调节键在鼠标滚轮下方&#xff0c;再次点击即可恢复正常鼠标速度。 如果有和-的按键&#xff0c;速度变快&#xff0c;-速度变慢。 图源&…

第四次CCF-CSP认证(含C++源码)

第四次CCF-CSP认证 第一道&#xff08;easy&#xff09;思路及AC代码 第二道&#xff08;easy&#xff09;思路及AC代码遇到的问题 第三道&#xff08;mid&#xff09;思路及AC代码 第一道&#xff08;easy&#xff09; 题目链接 思路及AC代码 这题就是将这个矩阵旋转之后输出…

Netty基础—1.网络编程基础一

大纲 1.什么是OSI开放系统互连 2.OSI七层模型各层的作用 3.TCP/IP协议的简介 4.TCP和UDP的简介 5.TCP连接的三次握手 6.TCP连接的四次挥手 7.TCP/IP中的数据包 8.TCP通过确认应答与序列号提高可靠性 9.HTTP请求的传输过程 10.HTTP协议报文结构 11.Socket、短连接、长…

98.在 Vue3 中使用 OpenLayers 根据 Resolution 的不同显示不同的地图

在 Vue3 中使用 OpenLayers 根据 Resolution 的不同显示不同的地图 前言 在 Web GIS&#xff08;地理信息系统&#xff09;应用开发中&#xff0c;地图的 Resolution&#xff08;分辨率&#xff09;是一个重要的概念。不同的 Resolution 适用于不同的地图层级&#xff0c;有时…

unity学习64,第3个小游戏:一个2D跑酷游戏

目录 学习参考 素材资源导入 1 创建项目 1.1 创建1个2D项目 1.2 导入素材 2 背景图bg 2.0 bg素材 2.1 创建背景 2.2 修改素材&#xff0c;且修改摄像机等 2.2.1 修改导入的原始prefab素材 2.2.2 对应调整摄像机 2.2.3 弄好背景 2.3 背景相关脚本实现 2.3.1 错误…

在本地部署DeepSeek等大模型时,需警惕的潜在安全风险

在本地部署DeepSeek等大模型时&#xff0c;尽管数据存储在本地环境&#xff08;而非云端&#xff09;&#xff0c;但仍需警惕以下潜在安全风险&#xff1a; 1. 模型与数据存储风险 未加密的存储介质&#xff1a;若训练数据、模型权重或日志以明文形式存储&#xff0c;可能被物…

【redis】string类型相关操作:SET、GET、MSET、MGET、SETNX、SETEX、PSETEX

文章目录 二进制存储编码转换SET 和 GETSETGET MSET 和 MGETSETNX、SETEX 和 PSETEX Redis 所有的 key 都是字符串&#xff0c;value 的类型是存在差异的 二进制存储 Redis 中的字符串&#xff0c;直接就是按照二进制数据的方式存储的 不仅仅可以存储文本数据&#xff0c;还可…

GaussDB安全配置指南:从认证到防御的全方面防护

一、引言 随着企业数据规模的扩大和云端化进程加速&#xff0c;数据库安全性成为运维的核心挑战之一。GaussDB作为一款高性能分布式数据库&#xff0c;提供了丰富的安全功能。本文将从 ​认证机制、权限控制、数据加密、审计日志​ 等维度&#xff0c;系统性地讲解如何加固 Ga…

Ubuntu20.04搭建gerrit code review

一、环境准备 1. 安装 Java 环境‌ Gerrit 依赖 Java 运行环境&#xff08;推荐 JDK 8&#xff09;&#xff1a; sudo apt install openjdk-11-jdk 验证安装&#xff1a; java -version ‌2. 安装 Git sudo apt install git ‌3. 可选依赖 数据库‌&#xff1a;Gerrit …