精华内容
下载资源
问答
  • 之后在根据我们的产品进行各种配置、搭建环境等。 当产品小批量量产时,可以直接树莓派系统下使用dd命令将当前系统完整地拷贝外接的ft卡中。完整系统拷贝,实际就是将整个tf卡的分区数据全部拷贝一次,32G的tf卡,就

    初次使用树莓派3/4安装系统时,需要先在官网下载img系统文件,再使用Win32DiskImager工具写入早tf卡中。之后在根据我们的产品进行各种配置、搭建环境等。

    当产品小批量量产时,可以直接树莓派系统下使用dd命令将当前系统完整地拷贝外接的ft卡中。完整系统拷贝,实际就是将整个tf卡的分区数据全部拷贝一次,32G的tf卡,就需要拷贝32G数据,不论实际使用空间为多少。

    但是当产品大批量量产时,这种操作的效率就极其低下,系统tf卡容量更大时表现更明显,并且外接的tf卡容量不能小于当前tf卡容量。

    这里,我们借助DiskGenius的克隆分区功能、,介绍两种高效的方式。

    1、DiskGenius版本

    从v5.3开始,DiskGenius增加了克隆分区。因此需要选择v5.3及以上版本。由于在windows下,linux下的文件系统下某些字符不能识别,导致复制时提示文件不存在的情况,如下文件

    /usr/share/ca-certificates/mozilla/NetLock_Arany_=Class_Gold=_Főtanúsítvány.crt
    

    若出现问题,更换其他版本尝试。当前测试使用 v5.4.2.1239 x64 中文版本。

    克隆分区时的三个选项:

    • 复制所有扇区:复制所有扇区,类似linux下的dd命令,速度慢,要求两个分区大小一致
      在这里插入图片描述
    • 按文件系统原样复制:要求分区大小一致,仅复制有效扇区,速度最快。
      在这里插入图片描述
    • 按文件复制:要求目标分区容量大于当前文件数据总量,仅复制文件(分区参数?
      在这里插入图片描述

    第二种方式,可以直接对一个ft写入,保持分区结构一致,插入树莓派卡槽可以直接启动。相对第一种已经快了很多。

    我们介绍使用第三种,直接一个最小的img文件,分发后使用Win32DiskImager写入,或者其他批量写入工具。

    2、制作最小的img镜像

    目的是最小的img镜像(产品使用的基本软件、环境都已经安装好),仅复制文件和有效数据,也就是第三种方式。

    2.1、查看现有树莓派系统tf卡的信息

    使用diskgenius查看,具有两个分区boot和rootfs,关注文件系统、标识、柱面、磁头、扇区等信息。
    在这里插入图片描述
    boot分区详细信息如下,启动分区,大小为256M
    在这里插入图片描述

    rootfs分区详细信息,linux下的文件系统,使用大小为1.9G
    在这里插入图片描述

    2.2、创建img文件

    菜单栏中,依次选择"硬盘"=>“新建虚拟磁盘文件”=>“新建.img文件”。

    通过前面两个分区,root分区大小为256M,固定的;rootfs实际使用了1.9G。总的容量,我们设置稍微大一些,避免分区时占用一些空间,保证剩余控件大于1.9G,如2.5G,因此设置如下图,
    在这里插入图片描述

    等待结束,将增加了一个虚拟的磁盘。接着删除掉Img(0)分区。
    在这里插入图片描述

    2.3、创建分区

    (1)创建一个root分区

    保证大小一致,分区参数一致(参看上一节的截图信息)。
    在这里插入图片描述

    点解确定,将出现一个为格式化(0)的分区,分区参数和前面的一致。
    在这里插入图片描述
    点击保存修改,之后再修改盘符为boot。

    (2)创建rootfs分区

    接着,将剩余空闲的2.2G空间建立一个分区即可,不需要设置具体参数,注意设置Ext4文件系统。
    在这里插入图片描述

    确定后,保存,修改盘符为rootfs
    在这里插入图片描述

    2.3、复制分区文件

    原有的系统tf卡显示名称为 RD2:MassStorageDevice(15GB),img镜像文件的显示名称为 VD0:raspberry.img(3GB)。分别使用克隆分区功能,将RD2的两个分区数据复制到VD0的对应分区中。

    右键 RD2的boot分区,点击克隆分区,选择目标分区VD0的boot,选择任意一个选项即可,开始复制等待结束。
    在这里插入图片描述

    右键 RD2的rootfs分区,点击克隆分区,选择目标分区VD0的rootfs,选择按文件复制,开始等待结束。
    在这里插入图片描述

    2.4、最小img镜像测试

    类似官方img文件烧写系统到tf中,使用Win32ImageManager将我们制作的raspberry.img文件写入到新的tf卡,之后树莓派卡槽中上电启动。linux下的脚本为sudo dd if=raspberry.img of=/dev/sdX bs=4M conv=fsync,其中/dev/sdx是插入的tf卡设备。

    开机后,查看空间如下,tf卡实际空间有29.7G,但是rootfs分区分配了2.3G。

    pi@raspberrypi:~ $ sudo lsblk
    NAME        MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
    mmcblk0     179:0    0 29.7G  0 disk
    ├─mmcblk0p1 179:1    0  256M  0 part /boot
    └─mmcblk0p2 179:2    0  2.3G  0 part /
    
    pi@raspberrypi:~ $ df -h
    Filesystem      Size  Used Avail Use% Mounted on
    /dev/root       2.2G  1.6G  460M  79% /
    devtmpfs        1.8G     0  1.8G   0% /dev
    tmpfs           1.9G     0  1.9G   0% /dev/shm
    tmpfs           1.9G  8.4M  1.9G   1% /run
    tmpfs           5.0M  4.0K  5.0M   1% /run/lock
    tmpfs           1.9G     0  1.9G   0% /sys/fs/cgroup
    /dev/mmcblk0p1  253M   48M  205M  19% /boot
    tmpfs           378M     0  378M   0% /run/user/1000
    

    需要 sudo raspi-config 扩展 SD 卡使用空间,依次执行 sudo raspi-config => 6 Advanced Options => A1 Expand Filesystem,确认之后重启,再次查看

    pi@raspberrypi:~ $ sudo lsblk
    NAME        MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
    mmcblk0     179:0    0 29.7G  0 disk
    ├─mmcblk0p1 179:1    0  256M  0 part /boot
    └─mmcblk0p2 179:2    0 29.5G  0 part /
    

    2.5、可能遇到的问题

    卡在boot界面,或者能进入但是没有文件系统等,大概率是因为加载分区有问题。关注partuuid,命令为

    pi@raspberrypi:~ $ sudo blkid
    /dev/mmcblk0p1: LABEL_FATBOOT="boot" LABEL="boot" UUID="5DE4-665C" TYPE="vfat" PARTUUID="15a061d1-01"
    /dev/mmcblk0p2: LABEL="rootfs" UUID="7295bbc3-bbc2-4267-9fa0-099e10ef5bf0" TYPE="ext4" PARTUUID="15a061d1-02"
    /dev/mmcblk0: PTUUID="15a061d1" PTTYPE="dos"
    

    可以看到,boot分区的PARTUUID=“15a061d1-01”,rootfs分区的PARTUUID=“15a061d1-02”

    系统启动时,需要从 /boot/cmdline.txt 中加载root分区,并且需要通过 /etc/fstab 正确挂载文件系统分区rootfs。需确保上述两个文件中的uuid部分一致。

    pi@raspberrypi:~ $ cat /boot/cmdline.txt
    console=tty1 root=PARTUUID=15a061d1-02 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait
    
    pi@raspberrypi:~ $ cat /etc/fstab
    proc            /proc           proc    defaults          0       0
    PARTUUID=15a061d1-01  /boot           vfat    defaults          0       2
    PARTUUID=15a061d1-02  /               ext4    defaults,noatime  0       1
    # a swapfile is not a swap partition, no line here
    #   use  dphys-swapfile swap[on|off]  for that
    

    如果partuuid配置不正确,可以先将tf卡挂载在其他linux下查看uuid,再通过mount修改对应文件。

    或者使用后一节的方式。

    3、分区的 PARTUUID

    前面不能启动的原因,是因为我们制作新的tf卡系统时,分区的partuuid发生了变化,但是 /boot/cmdline.txt 以及 /etc/fstab 中的内容还是源系统中的partuuid,因为文件是直接复制的。本节不修改这些文件内容,而是修改分区partuuid。

    3.1、linux下使用fdisk修改

    在linux系统下,放入一个不能正常启动的tf系统卡,挂载后查看分区信息、文件信息

    pi@raspberrypi:~ $ sudo blkid | grep sd
    /dev/sda1: LABEL_FATBOOT="boot" LABEL="boot" UUID="7581-8A48" TYPE="vfat" PARTUUID="c2dfa3eb-01"
    /dev/sda2: LABEL="rootfs" UUID="fa37d505-e741-4d35-bcec-4580aef395e1" TYPE="ext4" PARTUUID="c2dfa3eb-02"
    
    pi@raspberrypi:~ $ cat boot_dir/cmdline.txt
    console=tty1 root=PARTUUID=e8f831da-02 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait
    
    pi@raspberrypi:~ $ cat root_dir/etc/fstab
    proc            /proc           proc    defaults          0       0
    PARTUUID=e8f831da-01  /boot           vfat    defaults          0       2
    PARTUUID=e8f831da-02  /               ext4    defaults,noatime  0       1
    

    可以看到,当前插入的tf卡的分区PARTUUID为"c2dfa3eb",但是文件中要求分区PARTUUID为"e8f831da",因此需要将当前分区PARTUUID从"c2dfa3eb"修改为"e8f831da"。直接使用fdisk工具即可,先用命令p查看分区信息,可以看到"Disk identifier: 0xc2dfa3eb",十六进制的字符就是当前分区的PARTUUID。命令x进入专家模式,命令i修改,输入十六进制字符串"0xe8f831da",命令r返回主命令界面后使用命令w保存。

    pi@raspberrypi:~ $ sudo fdisk /dev/sda
    
    Welcome to fdisk (util-linux 2.33.1).
    Changes will remain in memory only, until you decide to write them.
    Be careful before using the write command.
    
    
    Command (m for help): p
    Disk /dev/sda: 29.7 GiB, 31914983424 bytes, 62333952 sectors
    Disk model: Storage Device
    Units: sectors of 1 * 512 = 512 bytes
    Sector size (logical/physical): 512 bytes / 512 bytes
    I/O size (minimum/optimal): 512 bytes / 512 bytes
    Disklabel type: dos
    Disk identifier: 0xc2dfa3eb
    
    Device     Boot  Start     End Sectors  Size Id Type
    /dev/sda1  *      8192  532479  524288  256M  b W95 FAT32
    /dev/sda2       532480 6553599 6021120  2.9G 83 Linux
    
    Command (m for help): x
    Expert command (m for help): i
    Enter the new disk identifier: 0xe8f831da
    Disk identifier changed from 0xc2dfa3eb to 0xe8f831da.
    
    Expert command (m for help): r
    Command (m for help): w
    The partition table has been altered.
    Syncing disks.
    

    重新查看,修改成功。

    pi@raspberrypi:~ $ sudo blkid | grep sd
    /dev/sda1: LABEL_FATBOOT="boot" LABEL="boot" UUID="7581-8A48" TYPE="vfat" PARTUUID="e8f831da-01"
    /dev/sda2: LABEL="rootfs" UUID="fa37d505-e741-4d35-bcec-4580aef395e1" TYPE="ext4" PARTUUID="e8f831da-02"
    

    当前卡作为系统卡启动,正常。

    3.1、windows下使用diskpart修改

    在windows下,我们将上面修改过的td卡插入win系统,使用diskpart修改partuuid,win+R,cmd,diskpart进入命令行。

    DISKPART> list disk
    
      磁盘 ###  状态           大小     可用     Dyn  Gpt
      --------  -------------  -------  -------  ---  ---
      磁盘 0    联机              476 GB  5120 KB        *
      磁盘 1    联机              931 GB  1024 KB        *
      磁盘 2    联机               29 GB    24 GB
    
    DISKPART> select disk2
    磁盘 2 现在是所选磁盘。
    
    DISKPART> detail disk
    
    Mass Storage Device USB Device
    磁盘 ID: C2DFA3EB
    类型   : USB
    状态 : 联机
    路径   : 0
    目标 : 0
    LUN ID : 0
    位置路径 : UNAVAILABLE
    当前只读状态: 否
    只读: 否
    启动磁盘: 否
    页面文件磁盘: 否
    休眠文件磁盘: 否
    故障转储磁盘: 否
    群集磁盘  : 否
    
      卷 ###      LTR  标签         FS     类型        大小     状态       信息
      ----------  ---  -----------  -----  ----------  -------  ---------  --------
      卷     8     G   boot         FAT32  可移动          256 MB  正常
    

    可以看到磁盘 ID: C2DFA3EB,正是修改过的。使用命令"set uniqueid“修改磁盘ID,例如修改成"E8F831DA”,之后再次查看,修改成功。

    DISKPART> uniqueid disk id=E8F831DA
    
    DISKPART> detail disk
    
    Mass Storage Device USB Device
    磁盘 ID: E8F831DA
    类型   : USB
    状态 : 联机
    路径   : 0
    目标 : 0
    LUN ID : 0
    位置路径 : UNAVAILABLE
    当前只读状态: 否
    只读: 否
    启动磁盘: 否
    页面文件磁盘: 否
    休眠文件磁盘: 否
    故障转储磁盘: 否
    群集磁盘  : 否
    
      卷 ###      LTR  标签         FS     类型        大小     状态       信息
      ----------  ---  -----------  -----  ----------  -------  ---------  --------
      卷     8     G   boot         FAT32  可移动          256 MB  正常
    
    DISKPART>
    

    4、使用Win32ImgManager / dd备份、pishrink.sh压缩

    4.1、备份tf中的系统为img镜像

    将需要备份系统的tf插入电脑,使用Win32ImgManager进行读取操作保存为img到本地磁盘上,同linux下使用dd命令类似。保存后的文件比较大,大小同tf卡的容量。
    在这里插入图片描述
    树莓派系统下,直接使用命令 dd bs=4M if=/dev/mmcblk0 of=test.img,等待结束即可。

    4.2、调整img文件大小

    当前我们备份的系统img镜像大小为16G左右。将镜像放在linux下,使用pishrink.sh脚本执行命令sudo ./pishrink.sh -s test.img raspipos_shrink.img (这里的-s表示第一次启动不进行文件系统扩展,后续可能需要手动扩容),运行结果为

    pi@raspberrypi:~ $ sudo ./pishrink.sh -s test.img  raspipos_shrink.img
    pishrink.sh v0.1.2
    pishrink.sh: Copying test.img to raspipos_shrink.img... ...
    pishrink.sh: Gathering data ...
    Skipping autoexpanding process...
    pishrink.sh: Checking filesystem ...
    rootfs: recovering journal
    rootfs: 72076/949104 files (0.3% non-contiguous), 915995/3822976 blocks
    resize2fs 1.44.5 (15-Dec-2018)
    pishrink.sh: Shrinking filesystem ...
    resize2fs 1.44.5 (15-Dec-2018)
    Resizing the filesystem on /dev/loop0 to 1108704 (4k) blocks.
    Begin pass 2 (max = 111809)
    Relocating blocks             XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    Begin pass 3 (max = 117)
    Scanning inode table          XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    Begin pass 4 (max = 8772)
    Updating inode references     XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    The filesystem on /dev/loop0 is now 1108704 (4k) blocks long.
    
    pishrink.sh: Shrinking image ...
    pishrink.sh: Shrunk raspipos_shrink.img from 15G to 4.5G ...
    

    img文件压缩为大小为4.5G。进一步,可以使用命令zip raspipos_shrink.img.zip raspipos_shrink.img进行压缩,三个文件的大小如下

    pi@raspberrypi:~ $ ls -l
    -rwxr-xr-x 1 pi pi       10904 Nov 20 14:07 pishrink.sh
    -rw-r--r-- 1 pi pi  4813881856 Nov 20 15:30 raspipos_shrink.img
    -rw-r--r-- 1 pi pi  1724249564 Nov 20 15:58 raspipos_shrink.img.zip
    -rw-r--r-- 1 pi pi 15931539456 Nov 20 15:12 test.img
    

    4.3、系统卡制作测试

    直接将raspipos_shrink.img通过Win32ImgManager写入到新的tf卡中。linux下的脚本为sudo dd if=raspberry.img of=/dev/sdX bs=4M conv=fsync,其中/dev/sdx是插入的tf卡设备。

    不同于上面使用DiskGenius的按文件复制后需要修改partuuid,这里的方式可以直接运行开机。

    但是不同于官方img,磁盘的可用空间不是特别大,根据需需要开机手动进行扩容操作。或者再系统卡制作后先手动进行扩容,例如windows下使用DiskGenius的调整分区大小、扩容分区功能,如下
    在这里插入图片描述
    当然,也可以进入系统后,使用sudo raspi-config扩容;可以去掉pishrink.sh脚本执行时使用的-s参数,见前章节。

    展开全文
  • StreamConverter:将Stream流与集合/数组之间的转换,必要时转换元素类型 这三个比较特殊,属于“最后的”“兜底类”类型转换器: ObjectToObjectConverter:通用的将原对象转换为目标对象(通过工厂方法or构造器)...

    在这里插入图片描述

    分享、成长,拒绝浅藏辄止。关注公众号【BAT的乌托邦】,回复关键字专栏有Spring技术栈、中间件等小而美的原创专栏供以免费学习。本文已被 https://www.yourbatman.cn 收录。

    ✍前言

    你好,我是YourBatman。

    上篇文章 大篇幅把Spring全新一代类型转换器介绍完了,已经至少能够考个及格分。在介绍Spring众多内建的转换器里,我故意留下一个尾巴,放在本文专门撰文讲解。

    为了让自己能在“拥挤的人潮中”显得不(更)一(突)样(出),A哥特意准备了这几个特殊的转换器助你破局,穿越拥挤的人潮,踏上Spring已为你制作好的高级赛道。

    版本约定

    • Spring Framework:5.3.1
    • Spring Boot:2.4.0

    在这里插入图片描述

    ✍正文

    本文的焦点将集中在上文留下的4个类型转换器上。

    • StreamConverter:将Stream流与集合/数组之间的转换,必要时转换元素类型

    这三个比较特殊,属于“最后的”“兜底类”类型转换器:

    • ObjectToObjectConverter:通用的将原对象转换为目标对象(通过工厂方法or构造器)
    • IdToEntityConverter本文重点。给个ID自动帮你兑换成一个Entity对象
    • FallbackObjectToStringConverter:将任何对象调用toString()转化为String类型。当匹配不到任何转换器时,它用于兜底

    默认转换器注册情况

    Spring新一代类型转换内建了非常多的实现,这些在初始化阶段大都被默认注册进去。注册点在DefaultConversionService提供的一个static静态工具方法里:

    static静态方法具有与实例无关性,我个人觉得把该static方法放在一个xxxUtils里统一管理会更好,放在具体某个组件类里反倒容易产生语义上的误导性

    DefaultConversionService:
    
    	public static void addDefaultConverters(ConverterRegistry converterRegistry) {
    		// 1、添加标量转换器(和数字相关)
    		addScalarConverters(converterRegistry);
    		// 2、添加处理集合的转换器
    		addCollectionConverters(converterRegistry);
    
    		// 3、添加对JSR310时间类型支持的转换器
    		converterRegistry.addConverter(new ByteBufferConverter((ConversionService) converterRegistry));
    		converterRegistry.addConverter(new StringToTimeZoneConverter());
    		converterRegistry.addConverter(new ZoneIdToTimeZoneConverter());
    		converterRegistry.addConverter(new ZonedDateTimeToCalendarConverter());
    
    		// 4、添加兜底转换器(上面处理不了的全交给这几个哥们处理)
    		converterRegistry.addConverter(new ObjectToObjectConverter());
    		converterRegistry.addConverter(new IdToEntityConverter((ConversionService) converterRegistry));
    		converterRegistry.addConverter(new FallbackObjectToStringConverter());
    		converterRegistry.addConverter(new ObjectToOptionalConverter((ConversionService) converterRegistry));
    	}
    
    	}
    

    该静态方法用于注册全局的、默认的转换器们,从而让Spring有了基础的转换能力,进而完成绝大部分转换工作。为了方便记忆这个注册流程,我把它绘制成图供以你保存:
    在这里插入图片描述
    特别强调:转换器的注册顺序非常重要,这决定了通用转换器的匹配结果(谁在前,优先匹配谁)。

    针对这幅图,你可能还会有疑问:

    1. JSR310转换器只看到TimeZone、ZoneId等转换,怎么没看见更为常用的LocalDate、LocalDateTime等这些类型转换呢?难道Spring默认是不支持的?
      1. 答:当然不是。 这么常见的场景Spring怎能会不支持呢?不过与其说这是类型转换,倒不如说是格式化更合适。所以会在后3篇文章格式化章节在作为重中之重讲述
    2. 一般的Converter都见名之意,但StreamConverter有何作用呢?什么场景下会生效
      1. 答:本文讲述
    3. 对于兜底的转换器,有何含义?这种极具通用性的转换器作用为何
      1. 答:本文讲述

    StreamConverter

    用于实现集合/数组类型到Stream类型的互转,这从它支持的Set<ConvertiblePair> 集合也能看出来:

    @Override
    public Set<ConvertiblePair> getConvertibleTypes() {
    	Set<ConvertiblePair> convertiblePairs = new HashSet<ConvertiblePair>();
    	convertiblePairs.add(new ConvertiblePair(Stream.class, Collection.class));
    	convertiblePairs.add(new ConvertiblePair(Stream.class, Object[].class));
    	convertiblePairs.add(new ConvertiblePair(Collection.class, Stream.class));
    	convertiblePairs.add(new ConvertiblePair(Object[].class, Stream.class));
    	return convertiblePairs;
    }
    

    它支持的是双向的匹配规则:

    在这里插入图片描述

    代码示例

    /**
     * {@link StreamConverter}
     */
    @Test
    public void test2() {
        System.out.println("----------------StreamConverter---------------");
        ConditionalGenericConverter converter = new StreamConverter(new DefaultConversionService());
    
        TypeDescriptor sourceTypeDesp = TypeDescriptor.valueOf(Set.class);
        TypeDescriptor targetTypeDesp = TypeDescriptor.valueOf(Stream.class);
        boolean matches = converter.matches(sourceTypeDesp, targetTypeDesp);
        System.out.println("是否能够转换:" + matches);
    
        // 执行转换
        Object convert = converter.convert(Collections.singleton(1), sourceTypeDesp, targetTypeDesp);
        System.out.println(convert);
        System.out.println(Stream.class.isAssignableFrom(convert.getClass()));
    }
    

    运行程序,输出:

    ----------------StreamConverter---------------
    是否能够转换:true
    java.util.stream.ReferencePipeline$Head@5a01ccaa
    true
    

    关注点:底层依旧依赖DefaultConversionService完成元素与元素之间的转换。譬如本例Set -> Stream的实际步骤为:

    在这里插入图片描述

    也就是说任何集合/数组类型是先转换为中间状态的List,最终调用list.stream()转换为Stream流的;若是逆向转换先调用source.collect(Collectors.<Object>toList())把Stream转为List后,再转为具体的集合or数组类型。

    说明:若source是数组类型,那底层实际使用的就是ArrayToCollectionConverter,注意举一反三

    使用场景

    StreamConverter它的访问权限是default,我们并不能直接使用到它。通过上面介绍可知Spring默认把它注册进了注册中心里,因此面向使用者我们直接使用转换服务接口ConversionService便可。

    @Test
    public void test3() {
        System.out.println("----------------StreamConverter使用场景---------------");
        ConversionService conversionService = new DefaultConversionService();
        Stream<Integer> result = conversionService.convert(Collections.singleton(1), Stream.class);
    
        // 消费
        result.forEach(System.out::println);
        // result.forEach(System.out::println); //stream has already been operated upon or closed
    }
    

    运行程序,输出:

    ----------------StreamConverter使用场景---------------
    1
    

    再次特别强调:流只能被读(消费)一次

    因为有了ConversionService提供的强大能力,我们就可以在基于Spring/Spring Boot做二次开发时使用它,提高系统的通用性和容错性。如:当方法入参是Stream类型时,你既可以传入Stream类型,也可以是Collection类型、数组类型,是不是瞬间逼格高了起来。

    在这里插入图片描述

    兜底转换器

    按照添加转换器的顺序,Spring在最后添加了4个通用的转换器用于兜底,你可能平时并不关注它,但它实时就在发挥着它的作用。

    在这里插入图片描述

    ObjectToObjectConverter

    将源对象转换为目标类型,非常的通用:Object -> Object:

    @Override
    public Set<ConvertiblePair> getConvertibleTypes() {
    	return Collections.singleton(new ConvertiblePair(Object.class, Object.class));
    }
    

    虽然它支持的是Object -> Object,看似没有限制但其实是有约定条件的:

    @Override
    public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
    	return (sourceType.getType() != targetType.getType() &&
    			hasConversionMethodOrConstructor(targetType.getType(), sourceType.getType()));
    }
    

    是否能够处理的判断逻辑在于hasConversionMethodOrConstructor方法,直译为:是否有转换方法或者构造器。代码详细处理逻辑如下截图:
    在这里插入图片描述

    此部分逻辑可分为两个part来看:

    • part1:从缓存中拿到Member,直接判断Member的可用性,可用的话迅速返回
    • part2:若part1没有返回,就执行三部曲,尝试找到一个合适的Member,然后放进缓存内(若没有就返回null)

    part1:快速返回流程

    当不是首次进入处理时,会走快速返回流程。也就是第0步isApplicable判断逻辑,有这几个关注点:

    1. Member包括Method或者Constructor
    2. Method:若是static静态方法,要求方法的第1个入参类型必须是源类型sourceType;若不是static方法,则要求源类型sourceType必须是method.getDeclaringClass()的子类型/相同类型
    3. Constructor:要求构造器的第1个入参类型必须是源类型sourceType

    在这里插入图片描述

    创建目标对象的实例,此转换器支持两种方式:

    1. 通过工厂方法/实例方法创建实例(method.invoke(source)
    2. 通过构造器创建实例(ctor.newInstance(source)

    以上case,在下面均会给出代码示例。

    part2:三部曲流程

    对于首次处理的转换,就会进入到详细的三部曲逻辑:通过反射尝试找到合适的Member用于创建目标实例,也就是上图的1、2、3步。

    step1:determineToMethod,从sourceClass里找实例方法,对方法有如下要求:

    • 方法名必须叫"to" + targetClass.getSimpleName(),如toPerson()
    • 方法的访问权限必须是public
    • 该方法的返回值必须是目标类型或其子类型

    step2:determineFactoryMethod,找静态工厂方法,对方法有如下要求:

    • 方法名必须为valueOf(sourceClass) 或者 of(sourceClass) 或者from(sourceClass)
    • 方法的访问权限必须是public

    step3:determineFactoryConstructor,找构造器,对构造器有如下要求:

    • 存在一个参数,且参数类型是sourceClass类型的构造器
    • 构造器的访问权限必须是public

    特别值得注意的是:此转换器支持Object.toString()方法将sourceType转换为java.lang.String。对于toString()支持,请使用下面介绍的更为兜底的FallbackObjectToStringConverter

    代码示例

    • 实例方法
    // sourceClass
    @Data
    public class Customer {
        private Long id;
        private String address;
    
        public Person toPerson() {
            Person person = new Person();
            person.setId(getId());
            person.setName("YourBatman-".concat(getAddress()));
            return person;
        }
    
    }
    
    // tartgetClass
    @Data
    public class Person {
        private Long id;
        private String name;
    }
    

    书写测试用例:

    @Test
    public void test4() {
        System.out.println("----------------ObjectToObjectConverter---------------");
        ConditionalGenericConverter converter = new ObjectToObjectConverter();
    
        Customer customer = new Customer();
        customer.setId(1L);
        customer.setAddress("Peking");
    
        Object convert = converter.convert(customer, TypeDescriptor.forObject(customer), TypeDescriptor.valueOf(Person.class));
        System.out.println(convert);
    
        // ConversionService方式(实际使用方式)
        ConversionService conversionService = new DefaultConversionService();
        Person person = conversionService.convert(customer, Person.class);
        System.out.println(person);
    }
    

    运行程序,输出:

    ----------------ObjectToObjectConverter---------------
    Person(id=1, name=YourBatman-Peking)
    Person(id=1, name=YourBatman-Peking)
    
    • 静态工厂方法
    // sourceClass
    @Data
    public class Customer {
        private Long id;
        private String address;
    }
    
    // targetClass
    @Data
    public class Person {
    
        private Long id;
        private String name;
    
        /**
         * 方法名称可以是:valueOf、of、from
         */
        public static Person valueOf(Customer customer) {
            Person person = new Person();
            person.setId(customer.getId());
            person.setName("YourBatman-".concat(customer.getAddress()));
            return person;
        }
    }
    

    测试用例完全同上,再次运行输出:

    ----------------ObjectToObjectConverter---------------
    Person(id=1, name=YourBatman-Peking)
    Person(id=1, name=YourBatman-Peking)
    

    方法名可以为valueOf、of、from任意一种,这种命名方式几乎是业界不成文的规矩,所以遵守起来也会比较容易。但是:建议还是注释写好,防止别人重命名而导致转换生效。

    • 构造器

    基本同静态工厂方法示例,略

    使用场景

    基于本转换器可以完成任意对象 -> 任意对象的转换,只需要遵循方法名/构造器默认的一切约定即可,在我们平时开发书写转换层时是非常有帮助的,借助ConversionService可以解决这一类问题。

    对于Object -> Object的转换,另外一种方式是自定义Converter<S,T>,然后注册到注册中心。至于到底选哪种合适,这就看具体应用场景喽,本文只是多给你一种选择

    IdToEntityConverter

    Id(S) --> Entity(T)。通过调用静态查找方法将实体ID兑换为实体对象。Entity里的该查找方法需要满足如下条件find[EntityName]([IdType])

    1. 必须是static静态方法
    2. 方法名必须为find + entityName。如Person类的话,那么方法名叫findPerson
    3. 方法参数列表必须为1个
    4. 返回值类型必须是Entity类型

    说明:此方法可以不必是public,但建议用public。这样即使JVM的Security安全级别开启也能够正常访问

    支持的转换Pair如下:ID和Entity都可以是任意类型,能转换就成

    @Override
    public Set<ConvertiblePair> getConvertibleTypes() {
    	return Collections.singleton(new ConvertiblePair(Object.class, Object.class));
    }
    

    判断是否能执行准换的条件是:存在符合条件的find方法,且source可以转换为ID类型(注意source能转换成id类型就成,并非目标类型哦)

    @Override
    public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
    	Method finder = getFinder(targetType.getType());
    	return (finder != null 
    		&& this.conversionService.canConvert(sourceType, TypeDescriptor.valueOf(finder.getParameterTypes()[0])));
    }
    

    根据ID定位到Entity实体对象简直太太太常用了,运用好此转换器的提供的能力,或许能让你事半功倍,大大减少重复代码,写出更优雅、更简洁、更易于维护的代码。

    代码示例

    Entity实体:准备好符合条件的findXXX方法

    @Data
    public class Person {
    
        private Long id;
        private String name;
    
        /**
         * 根据ID定位一个Person实例
         */
        public static Person findPerson(Long id) {
            // 一般根据id从数据库查,本处通过new来模拟
            Person person = new Person();
            person.setId(id);
            person.setName("YourBatman-byFindPerson");
            return person;
        }
    
    }
    

    应用IdToEntityConverter,书写示例代码:

    @Test
    public void test() {
        System.out.println("----------------IdToEntityConverter---------------");
        ConditionalGenericConverter converter = new IdToEntityConverter(new DefaultConversionService());
    
        TypeDescriptor sourceTypeDesp = TypeDescriptor.valueOf(String.class);
        TypeDescriptor targetTypeDesp = TypeDescriptor.valueOf(Person.class);
        boolean matches = converter.matches(sourceTypeDesp, targetTypeDesp);
        System.out.println("是否能够转换:" + matches);
    
        // 执行转换
        Object convert = converter.convert("1", sourceTypeDesp, targetTypeDesp);
        System.out.println(convert);
    }
    

    运行程序,正常输出:

    ----------------IdToEntityConverter---------------
    是否能够转换:true
    Person(id=1, name=YourBatman-byFindPerson)
    

    示例效果为:传入字符串类型的“1”,就能返回得到一个Person实例。可以看到,我们传入的是字符串类型的的1,而方法入参id类型实际为Long类型,但因为它们能完成String -> Long转换,因此最终还是能够得到一个Entity实例的。

    使用场景

    这个使用场景就比较多了,需要使用到findById()的地方都可以通过它来代替掉。如:

    Controller层:

    @GetMapping("/ids/{id}")
    public Object getById(@PathVariable Person id) {
        return id;
    }
    
    @GetMapping("/ids")
    public Object getById(@RequestParam Person id) {
        return id;
    }
    

    Tips:在Controller层这么写我并不建议,因为语义上没有对齐,势必在代码书写过程中带来一定的麻烦。

    Service层:

    @Autowired
    private ConversionService conversionService;
    
    public Object findById(String id){
        Person person = conversionService.convert(id, Person.class);
    
        return person;
    }
    

    Tips:在Service层这么写,我个人觉得还是OK的。用类型转换的领域设计思想代替了自上而下的过程编程思想。

    FallbackObjectToStringConverter

    通过简单的调用Object#toString()方法将任何支持的类型转换为String类型,它作为底层兜底。

    @Override
    public Set<ConvertiblePair> getConvertibleTypes() {
    	return Collections.singleton(new ConvertiblePair(Object.class, String.class));
    }
    

    该转换器支持CharSequence/StringWriter等类型,以及所有ObjectToObjectConverter.hasConversionMethodOrConstructor(sourceClass, String.class)的类型。

    说明:ObjectToObjectConverter不处理任何String类型的转换,原来都是交给它了

    代码示例

    略。

    ObjectToOptionalConverter

    将任意类型转换为一个Optional<T>类型,它作为最最最最最底部的兜底,稍微了解下即可。

    代码示例

    @Test
    public void test5() {
        System.out.println("----------------ObjectToOptionalConverter---------------");
        ConversionService conversionService = new DefaultConversionService();
        Optional<Integer> result = conversionService.convert(Arrays.asList(2), Optional.class);
    
        System.out.println(result);
    }
    

    运行程序,输出:

    ----------------ObjectToOptionalConverter---------------
    Optional[[2]]
    

    使用场景

    一个典型的应用场景:在Controller中可传可不传的参数中,我们不仅可以通过@RequestParam(required = false) Long id来做,还是可以这么写:@RequestParam Optional<Long> id

    ✍总结

    本文是对上文介绍Spring全新一代类型转换机制的补充,因为关注得人较少,所以才有机会突破。

    针对于Spring注册转换器,需要特别注意如下几点:

    1. 注册顺序很重要。先注册,先服务(若支持的话)
    2. 默认情况下,Spring会注册大量的内建转换器,从而支持String/数字类型转换、集合类型转换,这能解决协议层面的大部分转换问题。
      1. 如Controller层,输入的是JSON字符串,可用自动被封装为数字类型、集合类型等等
      2. 如@Value注入的是String类型,但也可以用数字、集合类型接收

    对于复杂的对象 -> 对象类型的转换,一般需要你自定义转换器,或者参照本文的标准写法完成转换。总之:Spring提供的ConversionService专注于类型转换服务,是一个非常非常实用的API,特别是你正在做基于Spring二次开发的情况下。

    当然喽,关于ConversionService这套机制还并未详细介绍,如何使用?如何运行?如何扩展?带着这三个问题,咱们下篇见。


    ✔✔✔推荐阅读✔✔✔

    【Spring类型转换】系列:

    【Jackson】系列:

    【数据校验Bean Validation】系列:

    【新特性】系列:

    【程序人生】系列:

    还有诸如【Spring配置类】【Spring-static】【Spring数据绑定】【Spring Cloud Netflix】【Feign】【Ribbon】【Hystrix】…更多原创专栏,关注BAT的乌托邦回复专栏二字即可全部获取,也可加我fsx1056342982,交个朋友。

    有些已完结,有些连载中。我是A哥(YourBatman),咱们下期再见


    ♥关注A哥♥

    AuthorA哥(YourBatman)
    个人站点www.yourbatman.cn
    E-mailyourbatman@qq.com
    微 信fsx1056342982
    活跃平台
    公众号BAT的乌托邦(ID:BAT-utopia)
    知识星球BAT的乌托邦
    每日文章推荐每日文章推荐

    BAT的乌托邦

    展开全文
  • ​UReport2是一款基于架构在Spring之上纯Java的高性能报表引擎,通过迭代单元格可以实现任意复杂的中国式报表。...1、产品界面 2、存储路径 在这个窗口,我们只要输入报表名称,同时再选择报表的存.

    ​ UReport2是一款基于架构在Spring之上纯Java的高性能报表引擎,通过迭代单元格可以实现任意复杂的中国式报表。

    ​ 在UReport2中,提供了全新的基于网页的报表设计器,可以在Chrome、Firefox、Edge等各种主流浏览器运行(IE浏览器除外)。使用UReport2,打开浏览器即可完成各种复杂报表的设计制作。

    ​ UReport2是第一款基于Apache-2.0开源协议的中式报表引擎。

    1、产品界面

    2、存储路径

    在这个窗口,我们只要输入报表名称,同时再选择报表的存储目的地就,可以保存当前报表文件。可以看到,UReport2默认给我们提供的存储目的地是“服务器文件系统 ”,实际上就是我们项目中WEB-INF目录下的“ureportfiles”目录,这个目录是系统默认自动生成的,如果需要我们可以添加一个属性来更改这个目录位置。

    3、数据源

    对数据源有三种加载方式

    (1)直接连接数据库

    ​ 直接连接数据库比较简单,就是在项目的classpath中添加好相应数据库的驱动Jar包后,在弹出的窗口中配置数据源连接信息即可,如下图所示:

    (2)Spring Bean

    ​ Spring Bean类型的数据源可以选择Spring上下文中定义好的一个Bean来作为数据源,点击图标,在弹出的窗口中输入数据源名称及要采用的Bean的ID,如下图所示:

    ​ 保存后,就可以在这个数据源下添加具体的数据集,添加方法就是在这个数据源下右键,在弹出的菜单中选择添加数据集,在弹出的窗口中定义数据集名称、对应的方法名以及返回对象类型,如下图所示:

    ​ 在Spring bean数据集配置中,方法名我们可以点击右侧的“选择方法”按钮来选择当前Bean对应的类中定义的方法,但这里对方法的要求是:方法必须要有三个参数,依次是String,String,Map,比如我们上面定义的testBean里就包含两个合法的方法,如下所示:

    (3)内置数据源

    ​ 这种类型的数据源,要示我们实现BuildinDatasource接口,同时将BuildinDatasource接口实现类配置到Spring即可,BuildinDatasource接口源码如下:

    ​ BuildinDatasource接口实现类配置到Spring中后,UReport2会自动检测到,这样在报表设计器中,点击数据源页签中的按钮,在弹出的窗口中就可以选择定义好的内置数据源,如下图所示:

    ​ 对于UReport2提供的三种类型的数据源,各有其特点及适用场景,对于使用者来说,要根据它们的特点灵活选择。

    4、报表类型

    (1)普通列表

     

    (2)简单分组

    (3)统计分组报表

    (4)分栏展示

    (5)错行分组

     

    (6)报表的同比与环比

    (7)交叉报表

    5、图标类型

    (1)饼图

    (2)环图

    (3)曲线图

    (4)柱状图

    (5)雷达图

    (6)极坐标图

    6、套打

    要为报表模版设置背景图,可以点击工具栏上的设置图标,在弹出窗口中定义要采用的背景图URL即可,如下图所示:

    ​ 有一名为print-demo.jpg图片位置当前WEB项目根images目录下,如果我们希望采用它作为模版的背景图,那么可以输入地址:../images/print-demo.jpg,输入完成之后,就可以在设计器中看到张背景图;当然指定背景图片的URL时,也可以以http开头来定位一张位于互联网上的图片。

    ​ 对于套打而言,一般情况下,我们首先需要拿到具体的实物单据,然后进行扫描,比如可以以300dpi的分辨率进行扫描,扫描后的图片用可以用PHOTOSHOP之类的图片处理软件打开,将图片分辨率设置为96dpi即可,最后将这张96dpi的图片作为报表模版的背景图同时将报表模版的页面尺寸与扫描件的尺寸对应上,并设置上下左右的页边距为为0,这样就可以开始套打报表模版的设计工作了。

    ​ 对于UReport2而言,一旦报表模版设置了背景图片,在HTML预览时会显示背景图片,但在导出PDF、WORD、EXCEL等格式的文件时则不会输出对应的背景图片,这样直接打印PDF、WORD之类输出文件,就可以实现报表功能。下图中是一张带背景图的报表模版文件:

    ​ 在这张报表模版当中,我们用的数据都是静态的,当然对于来自数据集或表达式中的动态数据设置方法也是一样,点击HTML预览按钮,可以看到如下图所示效果:

    ​ 在需要套打的报表模版设计过程中,选择好背景图后,一般我们需要先从最为密集行列部位开始设计,最后才是最简单的部位,实际使用时,具体位置还需要我们慢慢微调方可达到最佳位置效果。

    7、报表的打印与导出

    (1)在线打印

    (2)pdf在线预览打印

     

    转载: https://www.pianshen.com/article/9937788396/

     

     

     

    (3)导出

    导出支持PDF、WORD、EXCEL;

    展开全文
  • 目前局域网构建已经极为普遍,在不知不觉之间,小型局域网的踪影在我们周围无处不在,例如家庭局域网、游戏网吧、校院局域网和小型 ...网络产品的购买一定要看清产品再买,假的产品或质量次的产品会直接影响到网络...

    目前局域网构建已经极为普遍,在不知不觉之间,小型局域网的踪影在我们周围无处不在,例如家庭局域网、游戏网吧、校院局域网和小型 办公室 网等。虽然它们组网方式各式各样,但是万变不离其宗,这些有线局域网都需要通过线缆连接。

    DIY的热潮中使得更多的人开始自己动手搭建属于自己的网络,在这过程中首先是制定规划,然后购买产品,最后就是制作。网络产品的购买一定要看清产品再买,假的产品或质量次的产品会直接影响到网络的稳定,在选购好了产品后就该动手搭建它了。a6a20123edc8f3d088d7ed1d8b202a73.png

    在搭建网络的时候, 网线 的制作是一大重点,整个过程都要准确到位,排序的错误和压制的不到位都将直接影响网线的使用,出现网络不通或者网速慢。接下来笔者将简单的介绍一下制作标准的网线的制作。

    一、工具和材料的认识

    在制作网线前,大家必须准备相应的工具和材料。首要的工具是RJ-45工具钳,该工具上有三处不同的功能,最前端是剥线口,它用来剥开双绞线外壳。中间是压制RJ-45头工具槽,这里可将RJ-45头与双绞线合成。离手柄最近端是锋利的切线刀,此处可以用来切断双绞线。

    接下来需要的材料是RJ-45头和双绞线。由于RJ-45头像 水晶 一样晶莹透明,所以也被俗称为水晶头,每条双绞线两头通过安装RJ-45水晶头来与网卡和集线器(或 交换机 )相连。而双绞线是指封装在绝缘外套里的由两根绝缘导线相互扭绕而成的四对线缆,它们相互扭绕是为了降低传输信号之间的干扰。

    俗话说:“工欲善其事,必先利其器”。在这里我们要向大家介绍如何使你的工具很“利”,以达到事半功倍的效果。像上面我们看到的RJ-45工具钳,有时会出现制作出不合格的网线,这是因为工具钳的齿口没有对准水晶头上的金属片,从而导致金属片不能与网线正确接触,因此就出现网线连不通等现象。所以在选择RJ-45工具钳时,一定要注意工具钳压下来后它上面的每个齿口都能与水晶头上的金属片一一对应好,这样才能保证制作出合格的网线。a6a20123edc8f3d088d7ed1d8b202a73.png

    二、网线的标准,和连接方法:

    双交线做法有两种国际标准:EIA/TIA568A和EIA/TIA568B,而双交线的连接方法也主要有两种:直通线缆和交叉线缆。直通线缆的水镜头两端都遵循568A或568B标准,双交线的每组线在两端是一一对应的,颜色相同的在两端水晶头的相应槽中保持一致。它主要用在交换机(或集线器)Uplink口连接交换机(或集线器)普通端口或交换机普通端口连接计算机网卡上。而交叉线缆的水晶头一端遵循568A,而另一端则采用568B标准,即A水晶头的1、2对应B水晶头的3、6,而A水晶头的3、6对应B水晶头的1、2,它主要用在交换机(或集线器)普通端口连接到交换机(或集线器)普通端口或网卡连网卡上。a6a20123edc8f3d088d7ed1d8b202a73.png

    一、平行线制作:

    线序像568B

    一 端:白橙/橙/白绿/蓝/白蓝/绿/白棕/棕

    另一端:白橙/橙/白绿/蓝/白蓝/绿/白棕/棕

    二、交叉网线制作

    在线序上,采用了1-3, 2-6交换的方式,一头使用568B制作,另外一头使用568A制作

    一 端:白绿/绿/白橙/蓝/白蓝/橙/白棕/棕

    另一端:白橙/橙/白绿/蓝/白蓝/绿/白棕/棕

    568A标准:a6a20123edc8f3d088d7ed1d8b202a73.png

    绿白——1,绿——2,橙白——3,蓝——4,蓝白——5,橙——6,棕白——7,棕——8

    568B标准:

    橙白——1,橙——2,绿白——3,蓝——4,蓝白——5, 绿——6,棕白——7,棕——8

    ("橙白"是指浅橙色,或者白线上有橙色的色点或色条的线缆,绿白、棕白、蓝白亦同)。

    双绞线的顺序与RJ45头的引脚序号要—一对应。

    在实践中,一般可以这么理解:

    1、同种类型设备之间使用交叉线连接,不同类型设备之间使用直通线连接;

    2、路由器和PC属于DTE类型设备,交换机和HUB属于DCE类型设备;

    3、RJ45网络接头做法一般有568A和568B两种标准做法,按同一标准即直通线,不同标准即交叉线。

    不管如何接线,最后完成后用RJ-45测线仪测试时,8个 指示灯 都应依次闪烁。

    上面向大家介绍了常用的T568A、T568B国际标准网线制作方法,希望无论是初学者还是网络老手都能够按照标准的方法去制作网线,这样对以后网络的维护会带来许多便利。

    另:买线时注意一下,用的是5类线,3类线只能达到16M,4类线20M,只有5类线以及超5类等才能到达100M的.线的长度不能超过100米。

    展开全文
  • 无论你有多少经验,硬件产品的开发都比你认为的时间要长得多,本文的目的是帮助你更精确地缩小预测的误差范围。 确定产品上市所需时间有两个关键变量,分别是产品的复杂性和你的需求,这包括你的技能、你的资金、...
  • 制作网线图解ppt课件

    2020-12-29 21:35:50
    不同生产商的产品标识可能不同,但一般包括双绞线类型、NEC/UL防火测试和级别、CSA防火测试、长度标志、生产日期、双绞线的生产商和产品号码等信息 VCOM V2-073725-1 CABLE UTP ANSI TIA/EIA-568A 24AWG(4PR) OR ISO...
  • 产品设计五部曲 第一步:产品架构图 产品架构图,真的很重要。 通过产品架构图,可以从宏观角度去梳理整个产品的重要组成部分及如何组成。 通过产品架构图,来判断我们所设想的系统本身架构是否合理,与公司已有...
  • -66- 新产品要不要开广告 新产品开不开广告,要看产品的具体类型,这个不能一概而论。 有些产品不开广告也可以日出几十单甚至上百单。 有些产品每天广告烧几十上百美金,也才勉强只够广告费。 就大众化的产品来讲,...
  • 不同产品类型的访问时长不等,社交肯定长于工具类产品,内容平台肯定长于金融理财,如果分析师发现做内容的产品大部分用户访问时长只有几十秒,那么最好分析一下原因。 功能使用率 除了关注活跃,运营和数据分析师也...
  • 测绘中的4D产品

    2021-10-31 17:35:29
    本文介绍了测绘中的4D产品(DEM | DOM | DLG | DRG)
  • 什么是产品经理? 1.历史上的产品经理 消费时代的产品经理 1926年,宝洁推出了一款叫卡玫尔(Camay)的香皂,跟宝洁公司自家的另一款象牙牌(Ivory)香皂很相似,但销量一直不佳。 象牙皂是宝洁非常成功的一款核心...
  • 如果你有一个好的创意,你需要首先核实现状,确认它是否是你真正想做的产品,而初步计划可以帮助你做出选择。 在这个阶段,你最关心的两个问题分别是: 这个创意真的有意义吗? 最终获得的回报会大于付出吗? ...
  • 不管是产品新人、还是传统IT人员,甚至其他非互联网行业的伙伴,如果你想踏入产品经理这一行,但是又不知道该如何走出第一步,请先看看本篇文章,希望能给你们带来一些帮助。 【目录】 一、为什么要做产品经理 二...
  • 哪吒人生信条:如果你所学的东西 处于喜欢...jQuery实现的链式调用可以节约代码,所返回的都是一个对象,可以提高开发效率。 40.jQuery中的美元符号 $(document).ready(function(){ //... }); jQuery(document).ready...
  • 服务器类型操作系统

    2021-08-10 09:33:15
    如果制作的是鲲鹏服务器镜像,“架构类型”必须选择“ARM”,并且请确保所在Region可以创建鲲鹏云服务器。如果制作的是x86服务器镜像,若用于支持V6 CPU,请设置启动方式为“UEFI”。待注册文件必须为iso格式示例:...
  • 选择优化的数据类型MySQL支持的数据类型很是多, 选择正确的数据类型对于得到高性能相当重要。... 例如, 整型比字符操做代价更低, 由于字符集和校对规则(排序规则 )使字符比较比整型比较更复杂。优化尽可能避免NUL...
  • 有朋友询问:如何编写产品手册?公司客户说不会用我们的网站,市场部让我们产品产品手册,老大让我写,可是我没有写过这个东西,不知道产品手册到底要写什么,求指导。产品手册,是一个非常典型的市场...
  • 人工智能已经逐渐渗透到了各行各业,开始影响着我们的衣食住行,未来这种影响将会更加的明显,可以说无AI不时髦,如果不懂得使用AI的产品,那么将会被这个时代遗弃。 计算机视觉领域的市场与人才需求 AI...
  • 服务器设置mime类型

    2021-08-08 08:39:00
    服务器设置mime类型 内容精选换一换在高可用部署场景下需要创建SFS Turbo提供文件共享功能。请参考表1在公有云平台创建SFS Turbo。登录Windows弹性云服务器如图1所示。(可选)使用密钥文件解析密码。对于密钥方式鉴权...
  • 为什么选择产品这一职位 1、挑战和成就感,产品经理要负责协调沟通各方面、开发、测试、运营、 UE 。繁多的事情,会给产品经理带来重大的挑战。 好的产品设计会带来团队和用户的认可。但是如果对需求的把握和功能的...
  • 网站服务器类型查询

    2021-08-08 00:30:22
    网站服务器类型查询 内容精选换一换当您想要直接使用域名访问网站、Web应用程序或者云服务器时,可以通过为域名增加A类型记录集实现。更多关于记录集类型的介绍,请参见记录集类型及配置规则。已经完成网站或云...
  • 4.1 电视产品体系图 4.2 收视用户体系图 4.3 用户收视信息分析 4.3.1 用户收视信息 4.3.2 用户回看信息 4.3.3 用户点播信息 4.3.4 用户单片点播信息 4.4 电视产品信息数据分析 4.5 用户基本信息分析 4.6 产品与用户...
  • 广州的用户扫描二维码,显示的是链接C… 2,按照扫描时间来设置,一个二维码: 用户在7:00am-10:00am 扫描二维码,看到的是美妆类的产品促销页面;用户在12:00am-2:00pm 扫描二维码,看到的是户外运用类产品促销...
  • 比:某个周期的时段与上一个周期的相同时段比较,如今年的6月比去年的6月,本周的周一比上周的周一等等。环比:某个时段与其上一个时长相等的时段做比较,比如本周环比上周等等。1)环比增长率=(本期数-上期数)/...
  • 什么是产品经理?

    2021-01-27 10:33:58
    第一个产品经理,是来自宝洁(卖肥皂)最后成为了国防部部长。 工作具有横向性和重叠性,什么都要知道一点,借别人的手完成事情,硬性意义缺失,价值定义与边界模糊,只是把各方面协调在一起,让这个事情做得更成功...
  • 电子产品的可靠性设计,是指在不增加或少量增加成本的前提下,避免不期望的产品缺陷,可以达到预设的可靠性要求,减少发生故障的时间,降低产品寿命周期的使用成本,更能预防安全事故发生。在电子产品设计阶段,避免...
  • 上一篇,我们详细介绍了Adapt需求漏斗模型,通过对一个真实需求管理案例的剖析,我们分析了什么样的需求流动才是相对合理的。在需求管理中,需求做不完、需求永远大于研发容量才是正常的。那么,...
  • 服务器查看操作系统类型 内容精选换一换云耀云服务器(Hyper Elastic Cloud Server,HECS)是可以快速搭建简单应用的新一代云服务器,具备独立、完整的操作系统和网络功能。提供快速应用部署和简易的管理能力,适用于...
  • 《网络产品安全漏洞管理规定》2021年7月12日已由工业和信息化部、国家互联网信息办公室、公安部三部门联合印发,现予公布,自2021年9月1日起施行。 一、解读《网络产品安全漏洞管理规定》 制定目的 为了规范网络...
  • 然后修改“分区方案和目标系统类型”为“用于UEFI计算机的MBR分区方案”,并修改“文件系统”为“FAT32”,其他保持默认。 点击“开始”后会出现清空U盘数据确认框,再点“确认”继续。 等待文件复制完毕,启动U盘...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 106,685
精华内容 42,674
关键字:

同类型产品作比较