微信内网页开发 - 公众号发红包_lzqustc的博客-CSDN博客

mikel阅读(376)

来源: 微信内网页开发 – 公众号发红包_lzqustc的博客-CSDN博客

接口文档:

https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=13_4&index=3

 

一、开通现金红包权限

二、下载API证书

三、充值

以上步骤请参考:https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=13_3&index=2

 

服务端逻辑:

1、服务端先根据用户在APP中获得的红包金额,创建一条数据库记录(分配红包兑换码,用户信息,金额等等)

2、用户关注公众号,点击公众号菜单:红包兑换,菜单的链接可以是授权登录的链接,点击菜单经过服务器授权登录,获得用户的openid,然后重定向到H5兑换页面(openid作为页面参数)

 

3、用户输入兑换码,H5页面把openid和兑换码信息提交给服务器的Perl CGI脚本,例如https:/xxxx/cgi-bin/pay.pl?do=GetRedPack&redpack_code=xxxxxxx&openid=xxxxxx

 

脚本处理

if ($cgi->param(‘do’) eq “GetRedPack”) {

}

4、CGI脚本根据兑换码,调用服务端接口获取数据库记录,然后组合相关参数调用微信普通红包接口,给用户发红包

 

代码片段:

use CGI;

use warnings;

use JSON;

use utf8;

use Digest::MD5 qw/md5_hex/;

use HTTP::Request;

use HTTP::Headers;

use LWP::UserAgent;

use Encode;

use XML::Simple;

use Data::Dumper;

 

if ($cgi->param(‘do’) eq “GetRedPack”) { # ?do=GetRedPack&openid=xxx&redpack_code=xxx

my $openid = $cgi->param(‘openid’);

my $redpack_code=$cgi->param(‘redpack_code’);

 

my $redpack_info = get_redpack_by_code($redpack_code);

 

my $now_t = time();

my $wx_order_info;

$wx_order_info->{mch_id}=$MCH_ID;

$wx_order_info->{nonce_str}=nonce_str();

my $mch_billno=$MCH_ID.formateTime($now_t).$now_t;

if (length($mch_billno) > 28) {

$mch_billno = substr($mch_billno, 0, 28);

}

my $total_rmb = $redpack_info->{money_rmb} + 0;

my $total_num = $redpack_info->{total_num} + 0;

$wx_order_info->{mch_billno} = $mch_billno;

$wx_order_info->{wxappid}=$APPID;

$wx_order_info->{send_name}=”Tester”;

$wx_order_info->{re_openid}=$openid;

$wx_order_info->{total_amount}=100*$total_rmb;

$wx_order_info->{total_num}=1;

$wx_order_info->{wishing}=$redpack_info->{wishing}; #”happy new year”;

$wx_order_info->{client_ip}=”1.1.1.1″; #需要填写服务器ip

$wx_order_info->{act_name}=$redpack_info->{act_name};

$wx_order_info->{remark}=$redpack_info->{remark}; #”throw more get more”;

 

#红包金额大于200时,请求参数scene_id必传

$wx_order_info->{scene_id}=”PRODUCT_3″;

#write_log(“req_json:”.Dumper($wx_order_info));

 

$wx_order_info->{sign} = sign($wx_order_info, “false”);

#write_log(“req_sign:”.Dumper($wx_order_info));

 

my $request_xml = create_xml_data($wx_order_info);

#write_log(“req_xml:”.$request_xml);

my $header = HTTP::Headers->new( Content_Type => ‘text/xml; charset=utf8’, );

my $http_request = HTTP::Request->new( POST => “https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack”, $header, $request_xml);

my $ua = LWP::UserAgent->new(

ssl_opts => {

verify_hostname => 0,

#SSL_verify_mode => 0x00,

#SSL_ca_file => ‘/var/redpack/rootca.pem’

SSL_use_cert => 1,

SSL_cert_file => ‘/var/redpack/apiclient_cert.pem’,

SSL_key_file => ‘/var/redpack/apiclient_key.pem’,

SSL_passwd_cb => sub { $MCH_ID },

},

); #本接口需要上传证书

my $response = $ua->request($http_request);

my $response_json;

if ($response->message ne “OK” && $response->is_success ne “1”) { #出错,或者timeout了

$response_json->{return_code} = “99999”;

$response_json->{return_msg} = $response->message;

$response_json->{err_code} = $response->is_success;

} else {

my $decode_rsp = $response->decoded_content();

write_log(“\nrsp_xml_utf8:”.$decode_rsp);

$response_json = parse_xml_response( $decode_rsp);

}

 

my $ret = update_redpack_by_code($redpack_code);

print_html_notify_rsp(“success”);

if ($response_json->{return_code} eq “SUCCESS” && $response_json->{result_code} eq “SUCCESS”) {

$redirect_url = “http://xxxx/redpack_success.html?redpack_code=$redpack_code”;

print $cgi->redirect($redirect_url); #发送成功,跳转到成功页面

} else {

$redirect_url = “http://xxxx/redpack_failed.html?redpack_code=$redpack_code”;

print $cgi->redirect($redirect_url); #发送失败,跳转到成功页面

}

exit;

}
————————————————
版权声明:本文为CSDN博主「lzqustc」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/lzqustc/article/details/84878304

【实践篇】手把手教你落地DDD - 京东云技术团队 - 博客园

mikel阅读(337)

来源: 【实践篇】手把手教你落地DDD – 京东云技术团队 – 博客园

1. 前言

常见的DDD实现架构有很多种,如经典四层架构、六边形(适配器端口)架构、整洁架构(Clean Architecture)、CQRS架构等。架构无优劣高下之分,只要熟练掌握就都是合适的架构。本文不会逐个去讲解这些架构,感兴趣的读者可以自行去了解。

本文将带领大家从日常的三层架构出发,精炼推导出我们自己的应用架构,并且将这个应用架构实现为Maven Archetype,最后使用我们Archetype创建一个简单的CMS项目作为本文的落地案例。

需要明确的是,本文只是给读者介绍了DDD应用架构,还有许多概念没有涉及,例如实体、值对象、聚合、领域事件等,如果读者对完整落地DDD感兴趣,可以到本文最后了解更多。

2. 应用架构演化

我们很多项目是基于三层架构的,其结构如图:

我们说三层架构,为什么还画了一层 Model 呢?因为 Model 只是简单的 Java Bean,里面只有数据库表对应的属性,有的应用会将其单独拎出来作为一个
Maven Module,但实际上可以合并到 DAO 层。

接下来我们开始对这个三层架构进行抽象精炼。

2.1 第一步、数据模型与DAO层合并

为什么数据模型要与DAO层合并呢?

首先,数据模型是贫血模型,数据模型中不包含业务逻辑,只作为装载模型属性的容器;

其次,数据模型与数据库表结构的字段是一一对应的,数据模型最主要的应用场景就是DAO层用来进行 ORM,给 Service 层返回封装好的数据模型,供Service 获取模型属性以执行业务;

最后,数据模型的 Class 或者属性字段上,通常带有 ORM 框架的一些注解,跟DAO层联系非常紧密,可以认为数据模型就是DAO层拿来查询或者持久化数据的,数据模型脱离了DAO层,意义不大。

2.2 第二步、Service层抽取业务逻辑

下面是一个常见的 Service 方法的伪代码,既有缓存、数据库的调用,也有实际的业务逻辑,整体过于臃肿,要进行单元测试更是无从下手。

public class Service {

    @Transactional
    public void bizLogic(Param param) {

        checkParam(param);//校验不通过则抛出自定义的运行时异常

        Data data = new Data();//或者是mapper.queryOne(param);

        data.setId(param.getId());

        if (condition1 == true) {
            biz1 = biz1(param.getProperty1());
            data.setProperty1(biz1);
        } else {
            biz1 = biz11(param.getProperty1());
            data.setProperty1(biz1);
        }

        if (condition2 == true) {
            biz2 = biz2(param.getProperty2());
            data.setProperty2(biz2);
        } else {
            biz2 = biz22(param.getProperty2());
            data.setProperty2(biz2);
        }

        //省略一堆set方法
        mapper.updateXXXById(data);
    }
}

这是典型的事务脚本的代码:先做参数校验,然后通过 biz1、biz2 等子方法做业务,并将其结果通过一堆 Set 方法设置到数据模型中,再将数据模型更新到数据库。

由于所有的业务逻辑都在 Service 方法中,造成 Service 方法非常臃肿,Service 需要了解所有的业务规则,并且要清楚如何将基础设施串起来。同样的一条规则,例如if(condition1=true),很有可能在每个方法里面都出现。

专业的事情就该让专业的人干,既然业务逻辑是跟具体的业务场景相关的,我们想办法把业务逻辑提取出来,形成一个模型,让这个模型的对象去执行具体的业务逻辑。这样Service方法就不用再关心里面的 if/else 业务规则,只需要通过业务模型执行业务逻辑,并提供基础设施完成用例即可。

将业务逻辑抽象成模型,这样的模型就是领域模型。

要操作领域模型,必须先获得领域模型,但此时我们先不管领域模型怎么得到,假设是通过loadDomain方法获得的。通过 Service方法的入参,我们调用loadDomain方法得到一个模型,我们让这个模型去做业务逻辑,最后执行的结果也都在模型里,我们再将模型回写数据库。当然,怎么写数据库的我们也先不管,假设是通过saveDomain方法。

Service层的方法经过抽取之后,将得到如下的伪代码:

public class Service {

    public void bizLogic(Param param) {

        //如果校验不通过,则抛一个运行时异常
        checkParam(param);
        //加载模型
        Domain domain = loadDomain(param);
        //调用外部服务取值
	    SomeValue someValue=this.getSomeValueFromOtherService(param.getProperty2());
        //模型自己去做业务逻辑,Service不关心模型内部的业务规则
        domain.doBusinessLogic(param.getProperty1(), someValue);
        //保存模型
        saveDomain(domain);
    }
}

根据代码,我们已经将业务逻辑抽取出来了,领域相关的业务规则封闭在领域模型内部。此时 Service方法非常直观,就是获取模型、执行业务逻辑、保存模型,再协调基础设施完成其余的操作。

抽取完领域模型后,我们工程的结构如下图:

2.3 第三步、维护领域对象生命周期

在上一步中,loadDomainsaveDomain 这两个方法还没有得到讨论,这两个方法跟领域对象的生命周期息息相关。

关于领域对象的生命周期的详细知识,读者可以自行学习了解。

不管是 loadDomain 还是 saveDomain,我们一般都要依赖于数据库,所以这两个方法对应的逻辑,肯定是要跟 DAO 产生联系的。

保存或者加载领域模型,我们可以抽象成一种组件,通过这种组件进行封装模型加载、保存的操作,这种组件就是Repository。

注意,Repository 是对加载或者保存领域模型(这里指的是聚合根,因为只有聚合根才会有Repository)的抽象,必须对上层屏蔽领域模型持久化的细节,因此其方法的入参或者出参,一定是基本数据类型或者领域模型,不能是数据库表对应的数据模型。

以下是 Repository 的伪代码:

public interface DomainRepository {

    void save(AggregateRoot root);

    AggregateRoot load(EntityId id);
}

接下来我们要考虑在哪里实现DomainRepository。既然 DomainRepository 与底层数据库有关联,但是我们现在 DAO 层并没有引入 Domain 这个包,DAO 层自然无法提供 DomainRepository的实现,我们初步考虑是不是可以将 DomainRepository 实现在 Service 层。

但是,如果我们在 Service 中实现DomainRepository,势必需要在 Service 层操作数据模型:查询出来数据模型再封装为领域模型、或者将领域模型转为数据模型再通过ORM 保存,这个过程不该是 Service 层关心的。

因此,我们决定在 DAO 层直接引入 Domain 包,并在 DAO 层提供 DomainRepository 接口的实现,DAO 层查询出数据模型之后,封装成领域模型供DomainRepository 返回。

这样调整之后, DAO 层不再向 Service 返回数据模型,而是返回领域模型,这就隐藏了数据库交互的细节,我们也把DAO层换个名字称之为Repository。

现在,我们项目的架构图是这样的了:

由于数据模型属于贫血模型,自身没有业务逻辑,并且只有Repository这个包会用到,因此我们将之合并到Repository中,接下来不再单独列举。

2.4 第四步、泛化抽象

在第三步中,我们的架构图已经跟经典四层架构非常相似了,我们再对某些层进行泛化抽象。

  • Infrastructure

Repository 仓储层其实属于基础设施层,只不过其职责是持久化和加载聚合,所以,我们将 Repository层改名为 infrastructure-persistence,可以理解为基础设施层持久化包。

之所以采取这种 infrastructure-XXX 的格式进行命名,是由于 Infrastructure 可能会有很多的包,分别提供不同的基础设施支持。

例如:一般的项目,还有可能需要引入缓存,我们就可以再加一个包,名字叫infrastructure-cache

对于外部的调用,DDD中有防腐层的概念,将外部模型通过防腐层进行隔离,避免污染本地上下文的领域模型。我们使用入口(Gateway)来封装对外部系统或资源的访问(详细见《企业应用架构模式》,18.1入口(Gateway)),因此将对外调用这一层称之为infrastructure-gateway

注意:Infrastructure 层的门面接口都应先在Domain 层定义,其方法的入参、出参,都应该是领域模型(实体、值对象)或者基本类型。

  • User Interface

Controller 层其实就是用户接口层,即 User Interface 层,我们在项目简称 ui。当然了可能很多开发者会觉得叫UI好像很别扭,认为 UI就是 UI 设计师设计的图形界面。

Controller 层的名字有很多,有的叫 Rest,有的叫 Resource,考虑到我们这一层不只是有 Rest 接口,还可能还有一系列 Web相关的拦截器,所以我一般称之为 Web。因此,我们将其改名为 ui-web,即用户接口层的 Web 包。

同样,我们可能会有很多的用户接口,但是他们通过不同的协议对外提供服务,因而被划分到不同的包中。

我们如果有对外提供的 RPC服务,那么其服务实现类所在的包就可以命名为 ui-provider

有时候引入某个中间件会同时增加 Infrastructure 和 User Interface。

例如,如果引入 Kafka 就需要考虑一下,如果是给 Service 层提供调用的,例如逻辑执行完发送消息通知下游,那么我们再加一个包infrastructure-publisher;如果是消费 Kafka 的消息,然后调用 Service 层执行业务逻辑的,那么就可以命名为 ui-subscriber

  • Application

至此,Service 层目前已经没有业务逻辑了,业务逻辑都在 Domain 层去执行了,Service 只是协调领域模型、基础设施层完成业务逻辑。

所以,我们把 Service 层改名为 Application Service 层。

经过第四步的抽象,其架构图为:

2.5 第五步、完整的包结构

我们继续对第四步中出现的包进行整理,此时还需要考虑一个问题,我们的启动类应该放在哪里?

由于有很多的 User Interface,所以启动类放在任意一个User Interface中都不合适,放置在Application Service中也不合适,因此,启动类应该存放在单独的模块中。又因为 application这个名字被应用层占用了,所以将启动类所在的模块命名为 launcher,一个项目可以存在多个launcher,按需引用User Interface。

加入启动包,我们就得到了完整的 maven 包结构。

包结构如图所示:

至此,DDD 项目的整体结构基本讲完了。

2.6 精炼后的思考

在经过前面五步精炼得到这个架构图中,经典四层架构的四层都出现了,而且长得跟六边形架构也很像。这是为什么呢?

其实,不管是经典四层架构、还是六边形架构,亦或者整洁架构,都是对系统应用的描述,也许描述的侧重点不一样,但是描述的是同一个事物。既然描述的是同一个事物,长得像才是理所当然的,不可能只是换一个描述方式,系统就从根本上发生了改变。

对于任何一个应用,都可以看成“输入-处理-输出”的过程。

“输入”环节:通过某种协议对外暴露领域的能力,这些协议可能是 REST、可能是 RPC、可能是 MQ 的订阅者,也可能是WebSocket,也可能是一些任务调度的 Task;

”处理“环节:处理环节是整个应用的核心,代表了应用具备的核心能力,是应用的价值所在,应用在这个环节执行业务逻辑,贫血模型由Service执行业务处理,充血模型则是由模型进行业务处理。

“输出”环节,业务逻辑执行完成之后将结果输出到外部。

不管我们采用的什么架构,其描述的应用的核心都是这个过程,不必生搬硬套非得用什么应用架构。

正如《金刚经》所言:一切有为法,如梦幻泡影,如露亦如电,应作如是观;凡所有相,皆是虚妄;若见诸相非相,即见如来。

3. ddd-archetype

3.1 Maven Archetype介绍

Maven Archetype是一个Maven插件,可以帮助开发人员快速创建项目的基础结构,大大减少开发人员在创建项目时所需的时间和精力,并且可以确保项目结构的一致性和可重用性,从而提高代码质量和可维护性。

我们在介绍DDD应用架构时,对项目的结构进行了介绍。我们将项目分为多个Maven Module,如果每个项目都手工创建一次,是比较繁琐的工作,也不利项目结构的统一。

我们使用Maven Archetype创建DDD项目初始化的脚手架,使其在初始化时完整实现上文第五步的应用架构。

3.2 ddd-archetype的使用

3.2.1 项目介绍

ddd-archetype是一个Maven Archetype的原型工程,我们将其克隆到本地之后,可以安装为Maven Archetype,帮助我们快速创建DDD项目脚手架。

项目链接:

https://github.com/feiniaojin/ddd-archetype

3.2.2 安装过程

以下将以IDEA为例展示ddd-archetype的安装使用过程,主要过程是:

克隆项目–>archetype:create-from-project–>install–>archetype:crawl

3.2.3 克隆项目

将项目克隆到本地:

git clone https://github.com/feiniaojin/ddd-archetype.git

直接使用主分支即可,然后使用IDEA打开该项目

3.2.4 archetype:create-from-project

配置打开IDEA的run/Debug configurations窗口,如下:

选择add new configurations,弹出以下窗口:

其中,上图中1~4各个标识的值为:

标识1 – 选择”+”号;

标识2 – 选择”Maven”;

标识3 – 命令为:

archetype:create-from-project -Darchetype.properties=archetype.properties

注意,在IDEA中添加的命令默认不需要加mvn

标识4 – 选择ddd-archetype的根目录

以上配置完成后,点击执行该命令。

3.2.5 install

上一步执行完成且无报错之后,配置install命令。

其中,上图中1~2各个标识的值为:

标识1 – 值为install

标识2 – 值为上一步运行的结果,路径为:

ddd-archetype/target/generated-sources/archetype

install配置完成之后,点击执行。

3.2.6 archetype:crawl

install执行完成且无报错,接着配置archetype:crawl命令。

其中,标识1中的值为:

archetype:crawl

配置完成,点击执行即可。

3.3 使用ddd-archetype初始化项目

  • 创建项目时,点击manage catalogs
  • 将本地的maven私服中的archetype-catalog.xml加入到catalogs中:

添加成功,如下:

  • 创建项目时,选择本地archetype-catalog,并且选择ddd-archetype,填入项目信息并创建项目:
  • 项目创建完成后:

4. 代码案例

本文提供了配套的代码案例,该案例使用DDD和本文的应用架构实现了简单的CMS系统。案例项目采用前后端分离的方式,因此有后端和前端两个代码库。

4.1 后端

后端项目使用本文的ddd-archetype创建,实现了部分CMS的功能,并落地部分DDD的概念。

GitHub链接:https://github.com/feiniaojin/ddd-example-cms

实现的DDD概念有:实体、值对象、聚合根、Factory、Repository、CQRS。

技术栈:

  • Spring Boot
  • H2内存数据库
  • Spring Data JDBC

无外部中间件依赖 ,clone到本地即可编译运行,非常方便。

4.2 前端

前端项目基于vue-element-admin开发,详细安装方式见代码库的README。

GitHub链接:https://github.com/feiniaojin/ddd-example-cms-front

4.3 运行截图

5. 总结以及进一步学习

本文通过对贫血三层架构进行精炼,推导出适合我们落地的应用架构,并且将之实现为Maven Archetype以应用到实际开发,然而应用架构只是落地DDD的一个知识点,要完整落地DDD还必须体系化地掌握限界上下文、上下文映射、充血模型、实体、值对象、领域服务、Factory、Repository等知识点。

作者:京东物流 覃玉杰

内容来源:京东云开发者社区

小米手机微信聊天记录如何导出到电脑Word文档:免Root

mikel阅读(514)

来源: 小米手机微信聊天记录如何导出到电脑Word文档:免Root

小米手机微信聊天记录导出到电脑的问题困扰了很多微信用户,其实小米手机app产生的数据,都可以通过系统自带的备份功能,将其导出到电脑上。微信app产生的聊天记录当然也不例外,接下来我来教大家如何把小米手机上的微信聊天记录保存到电脑上(含文字,图片,语音,视频等)。

小米手机微信聊天记录导出到电脑视频教程:

1,打开小米手机上的“设置”,点击“更多设置”,“备份和重置”。
说明:如果小米手机安装的是最新MIUI11系统,则进入手机“设置”后,再点击“我的设备”,“备份和重置”即可。

2,点击“本地备份”,按提示输入锁屏密码后,点击“下一步”,最后点击“新建备份”按钮。

3,将“系统数据“及“软件程序”前面的勾去掉,再点击软件程序右边的小箭头,仅勾选上“微信”后,开始进行备份。

4,备份结束后,将小米手机用数据线连接到电脑上,手机上询问USB的用途,请选择“传输文件”,如果设置为默认的“仅限充电”的话,将无法在电脑上读取手机中的文件。

5,进入手机所在盘符下的“MIUI/backup/AllBackup/备份日期时间”目录,将该目录下的微信(com.tencent.mm).bak文件复制到电脑上。

6,右击刚复制出来的“微信(com.tencent.mm).bak”文件,然后使用7-zip软件打开该压缩包。
说明:如果电脑上没有安装7-zip软件的话,需要先在网上搜索并下载安装该软件。

7,进入“apps/com.tencent.mm/r”目录,将该目录下的MicroMsg文件夹提取出来。

8,因为后面还需要在小米手机上导出另外一个MicroMsg文件夹,为了防止发生覆盖,先将该MicroMsg文件夹重命名为“数据库MicroMsg”,该文件夹中保存了小米手机微信聊天记录的数据库文件。

9,再次回到手机上进行操作,打开手机上的“文件管理”,点击右上角的“手机”,进入tencent目录,长按选中该目录下的MicroMsg文件夹后,对其进行压缩。
2020年7月27日更新:新版微信(V7.0.16及以后版本)资源文件不再保存在“tencent/MicroMsg”文件夹中,因些如果是新版微信,则需要打开文件管理后,进入“Android/data/com.tencent.mm”目录,并长按该目录下的MicroMsg文件夹,并对其进行压缩。

10,在电脑上进入手机所在盘符,如果是老版本微信,将tencent目录下的MicroMsg.zip复制到电脑上,并将其解压到当前文件夹。如果是V7.0.16及以后版本的微信,则需要将”Android/data/com.tencent.mm”目录下的MicroMsg.zip复制到电脑,并解压到当前文件夹。

11,最后打开楼月微信聊天记录导出恢复助手(点击此处下载),微信版本设置为“安卓版微信”,数据目录设置为从7-zip软件中提取出来的“数据库MicroMsg”,资源目录设置为解压得到的另外一个MicroMsg文件夹,最后点击“读取数据”按钮,如果软件无法从数据库目录中获取到手机的IMEI码的话,会要求输入该手机的IMEI码。

12,在手机拨号界面上,按下*#06#,即可获取到该小米手机的IMEI码,将该码输入到软件界面上即可。但有一些手机,需要输入MEID码才可以解密小米手机微信聊天记录。如果输入IMEI码后,无法解密查看微信聊天记录的话,请尝试输入MEID码,或输入另外一个IMEI码。

13,最后选择一个想查看的微信号后,点击“查看记录”按钮。

14,在界面上选中要导出的小米手机微信聊天记录,再在键盘上按Ctrl+C进行复制。

15,然后将复制的微信聊天记录粘贴到电脑上的Word文档中就可以了。

16,另外,也可以通过软件的微信聊天记录导出功能,来批量导出小米手机微信聊天记录中的文字消息,图片,语音消息,视频信息等。

以上就是小米手机微信聊天记录导出到电脑的全过程,导出到电脑后,可在电脑上查看所有聊天内容,也可对小米手机微信聊天记录进行打印。

ChatGPT 国内镜像站汇总

mikel阅读(1876)

人能力有限,搜集到的不多,求大家多多贡献啊!众人拾柴火焰高!

介绍

汇总所有 chatgpt 镜像站,帮助国内在正常网络下的同学访问gpt。

欢迎大家一起贡献,请直接提交 issue 或者 pr (只能提交普通网络下就可以访问成功的)

无需登录直接可用的:

登录后可以免费使用的:

需要付费的:

需要自己提供 key 的:

开源的,可以自己部署到本地的:

插件:

国内自研大模型汇总:

国外其它大模型汇总:

其它多模态 AI 技术:

AI工具集导航网站:

聊一聊redis十种数据类型及底层原理 - __Ray - 博客园

mikel阅读(261)

来源: 聊一聊redis十种数据类型及底层原理 – __Ray – 博客园

概述

Redis 是一个开源的高性能键值数据库,它支持多种数据类型,可以满足不同的业务需求。本文将介绍 Redis 的10种数据类型,分别是

  • string(字符串)
  • hash(哈希)
  • list(列表)
  • set(集合)
  • zset(有序集合)
  • stream(流)
  • geospatial(地理)
  • bitmap(位图)
  • bitfield(位域)
  • hyperloglog(基数统计)

String

概述

string 是 Redis 最基本的数据类型,它可以存储任意类型的数据,比如文本、数字、图片或者序列化的对象。一个 string 类型的键最大可以存储 512 MB 的数据。

string 类型的底层实现是 SDS(simple dynamic string),它是一个动态字符串结构,由长度、空闲空间和字节数组三部分组成。SDS有3种编码类型:

  • embstr:占用64Bytes的空间,存储44Bytes的数据
  • raw:存储大于44Bytes的数据
  • int:存储整数类型

embstr和raw存储字符串数据,int存储整型数据

应用场景

string 类型的应用场景非常广泛,比如:

  • 缓存数据,提高访问速度和降低数据库压力。
  • 计数器,利用 incr 和 decr 命令实现原子性的加减操作。
  • 分布式锁,利用 setnx 命令实现互斥访问。
  • 限流,利用 expire 命令实现时间窗口内的访问控制。

底层原理

embstr结构

embstr 结构存储小于等于44个字节的字符串,embstr 每次开辟64个byte的空间

  • 前19个byte用于存储embstr 结构
  • 中间的44个byte存储数据
  • 最后为\0符号
    image

raw结构

image

embstr和raw的转换

在存储字符串的时候,redis会根据数据的长度判断使用哪种结构

  • 如果长度小于等于44个字节,就会选择embstr 结构
  • 如果长度大于44个byte,就会选择raw结构
    image
127.0.0.1:6379> object encoding str
"embstr"
# str赋值44个字节的字符串
127.0.0.1:6379> set str 1234567890123456789012345678901234567890abcd
OK
127.0.0.1:6379> object encoding str
"embstr"
# str2赋值45个字节的字符串
127.0.0.1:6379> set str2 1234567890123456789012345678901234567890abcde
OK
127.0.0.1:6379> object encoding str2
"raw"
127.0.0.1:6379> set num 123
OK
127.0.0.1:6379> object encoding num
"int"

Hash

概述

hash 是一个键值对集合,它可以存储多个字段和值,类似于编程语言中的 map 对象。一个 hash 类型的键最多可以存储 2^32 – 1 个字段。

Hash类型的底层实现有三种:

  • ziplist:压缩列表,当hash达到一定的阈值时,会自动转换为hashtable结构
  • listpack:紧凑列表,在Redis7.0之后,listpack正式取代ziplist。同样的,当hash达到一定的阈值时,会自动转换为hashtable结构
  • hashtable:哈希表,类似map

应用场景

hash 类型的应用场景主要是存储对象,比如:

  • 用户信息,利用 hset 和 hget 命令实现对象属性的增删改查。
  • 购物车,利用 hincrby 命令实现商品数量的增减。
  • 配置信息,利用 hmset 和 hmget 命令实现批量设置和获取配置项。

底层原理

Redis在存储hash结构的数据,为了达到内存和性能的平衡,也针对少量存储和大量存储分别设计了两种结构,分别为:

  • ziplist(redis7.0之前使用)和listpack(redis7.0之后使用)
  • hashTable

ziplist/listpack编码转换为hashTable编码是通过判断元素数量单个元素Key或Value的长度决定的:

  • hash-max-ziplist-entries:表示当hash中的元素数量小于或等于该值时,使用ziplist编码,否则使用hashtable编码。ziplist是一种压缩列表,它可以节省内存空间,但是访问速度较慢。hashtable是一种哈希表,它可以提高访问速度,但是占用内存空间较多。默认值为512
  • hash-max-ziplist-value:表示当 hash中的每个元素的 key和 value的长度都小于或等于该值时,使用 ziplist编码,否则使用 hashtable编码。默认值为 64

ziplist与listpack

ziplist/listpack都是hash结构用来存储少量数据的结构。从Redis7.0后,hash默认使用listpack结构。因为 ziplist有一个致命的缺陷,就是连锁更新,当一个节点的长度发生变化时,可能会导致后续所有节点的长度字段都要重新编码,这会造成极差的性能

ziplist结构

ziplist是一种紧凑的链表结构,它将所有的字段和值顺序地存储在一块连续的内存中。
image
Redis中ziplist源码

typedef struct {
  /* 当使用字符串时,slen表示为字符串长度 */
  unsigned char *sval;
  unsigned int slen;
  /* 当使用整形时,sval为NULL,lval为ziplistEntry的value */
  long long lval;
} ziplistEntry;

listpack结构

image

zipList的连锁更新问题

ziplist的每个entry都包含previous_entry_length来记录上一个节点的大小,长度是1个或5个byte:

  • 如果前一节点的长度小于254个byte,则采用1个byte来保存这个长度值
  • 如果前一节点的长度大于等于254个byte,则采用5个byte来保存这个长度值,第一个byte为0xfe,后四个byte才是真实长度数据

假设,现有有N个连续、长度为250~253个byte的entry,因此entry的previous_entry_length属性占用1个btye

image

当第一节长度大于等于254个bytes,导致第二节previous_entry_length变为5个bytes,第二节的长度由250变为254。而第二节长度的增加必然会影响第三节的previous_entry_length。ziplist这种特殊套娃的情况下产生的连续多次空间扩展操作成为连锁更新。新增、删除都可能导致连锁更新的产生。

listpack是如何解决的

image

  1. 由于ziplist需要倒着遍历,所以需要用previous_entry_length记录前一个entry的长度。而listpack可以通过total_bytes和end计算出来。所以previous_entry_length不需要了。
  2. listpack 的设计彻底消灭了 ziplist 存在的级联更新行为,元素与元素之间完全独立,不会因为一个元素的长度变长就导致后续的元素内容会受到影响。
  3. 与ziplist做对比的话,牺牲了内存使用率,避免了连锁更新的情况。从代码复杂度上看,listpack相对ziplist简单很多,再把增删改统一做处理,从listpack的代码实现上看,极简且高效。

hashTable

hashTable是一种散列表结构,它将字段和值分别存储在两个数组中,并通过哈希函数计算字段在数组中的索引

image

Redis中hashTable源码

struct dict {
    dictType *type;
    dictEntry **ht_table[2];
    unsigned long ht_used[2];
    long rehashidx; /* 当进行rehash时,rehashidx为-1 */
    int16_t pauserehash; /* 如果rehash暂停,pauserehash则大于0,(小于0表示代码错误)*/
    signed char ht_size_exp[2]; /* 哈希桶的个数(size = 1<<exp) */
};

typedef struct dict {
    dictEntry **table;
    dictType *type;
    unsigned long size;
    unsigned long sizemask;
    unsigned long used;
    void *privdata;
} dict;

typedef struct dictEntry {
    void *key;
    void *val;
    struct dictEntry *next;
} dictEntry;

ziplist和hashTable的转换

image

127.0.0.1:6379> hset h1 id 123456789012345678901234567890123456789012345678901234567890abcd
(integer) 1
127.0.0.1:6379> object encoding h1
"ziplist"
127.0.0.1:6379> hset h2 id 123456789012345678901234567890123456789012345678901234567890abcde
(integer) 1
127.0.0.1:6379> object encoding h2
"hashtable"

ziplist的废弃

显然是ziplist在field个数太大、key太长、value太长三者有其一的时候会有以下问题:

  1. ziplist每次插入都有开辟空间,连续的
  2. 查询的时候,需要从头开始计算,查询速度变慢

hashTable变得越来越长怎么办

扩容,hashTabel是怎么扩容的?使用的是渐进式扩容rehashrehash会重新计算哈希值,且将哈希桶的容量扩大。

rehash 步骤

image

扩展哈希和收缩哈希都是通过执行rehash来完成,这其中就涉及到了空间的分配和释放,主要经过以下五步:

  1. 为字典dict的ht[1]哈希表分配空间,其大小取决于当前哈希表已保存节点数(即:ht[0].used):
    • 如果是扩展操作则ht[1]的大小为2的n次方中第一个大于等于ht[0].used * 2属性的值(比如used=3,此时ht[0].used * 2=6,故2的3次方为8就是第一个大于used * 2的值(2 的 2 次方 6))。
    • 如果是收缩操作则ht[1]大小为 2 的 n 次方中第一个大于等于ht[0].used的值
  2. 将字典中的属性rehashidx的值设置为0,表示正在执行rehash操作
  3. 将ht[0]中所有的键值对依次重新计算哈希值,并放到ht[1]数组对应位置,每完成一个键值对的rehash之后rehashidx的值需要自增1
  4. 当ht[0]中所有的键值对都迁移到ht[1]之后,释放ht[0],并将ht[1]修改为ht[0],然后再创建一个新的ht[1]数组,为下一次rehash做准备
  5. 将字典中的属性rehashidx设置为-1,表示此次rehash操作结束,等待下一次rehash

渐进式 rehash

Redis中的这种重新哈希的操作因为不是一次性全部rehash,而是分多次来慢慢的将ht[0]中的键值对rehash到ht[1],故而这种操作也称之为渐进式rehash。渐进式rehash可以避免集中式rehash带来的庞大计算量,是一种分而治之的思想。

在渐进式rehash过程中,因为还可能会有新的键值对存进来,此时Redis的做法是新添加的键值对统一放入ht[1]中,这样就确保了ht[0]键值对的数量只会减少。

当正在执行rehash操作时,如果服务器收到来自客户端的命令请求操作,则会先查询ht[0],查找不到结果再到ht[1]中查询

List

概述

list 是一个有序的字符串列表,它按照插入顺序排序,并且支持在两端插入或删除元素。一个 list 类型的键最多可以存储 2^32 – 1 个元素。

redis3.2以后,list 类型的底层实现只有一种结构,就是quicklist。版本不同时,底层实现是不同的,下面会讲解。

应用场景

list 类型的应用场景主要是实现队列和栈,比如:

  • 消息队列,利用 lpush 和 rpop 命令实现生产者消费者模式。
  • 最新消息,利用 lpush 和 ltrim 命令实现固定长度的时间线。
  • 历史记录,利用 lpush 和 lrange 命令实现浏览记录或者搜索记录。

底层原理

在讲解list结构之前,需要先说明一下list结构编码的更替,如下

  • Redis3.2之前,list使用的是linkedlistziplist
  • Redis3.2~Redis7.0之间,list使用的是quickList,是linkedlistziplist的结合
  • Redis7.0之后,list使用的也是quickList,只不过将ziplist转为listpack,它是listpack、linkedlist结合版

linkedlist与ziplist

Redis3.2之前,linkedlistziplist两种编码可以选择切换,它们之间的转换关系如图
image

同样地,ziplist转为linkedlist的条件可在redis.conf配置

list-max-ziplist-entries 512
list-max-ziplist-value 64

quickList(ziplist、linkedlist结合版)

quicklist存储了一个双向列表,每个列表的节点是一个ziplist,所以实际上quicklist并不是一个新的数据结构,它就是linkedlistziplist的结合,然后被命名为快速列表。
image
ziplist内部entry个数可在redis.conf配置

list-max-ziplist-size -2
# -5: 每个ziplist最多为 64 kb  <-- 影响正常负载,不推荐
# -4: 每个ziplist最多为 32 Kb  <-- 不推荐
# -3: 每个ziplist最多为 16 Kb  <-- 最好不要使用
# -2: 每个ziplist最多为 8 Kb   <-- 好
# -1: 每个ziplist最多为 4 Kb   <-- 好
# 正数为ziplist内部entry个数

ziplist通过特定的LZF压缩算法来将节点进行压缩存储,从而更进一步的节省空间,而很多场景都是两端元素访问率最高,我们可以通过配置list-compress-depth来排除首尾两端不压缩的entry个数。

list-compress-depth 0
# - 0:不压缩(默认值)
# - 1:首尾第 1 个元素不压缩
# - 2:首位前 2 个元素不压缩
# - 3:首尾前 3 个元素不压缩
# - 以此类推

quickList(listpack、linkedlist结合版)

和Hash结构一样,因为ziplist有连锁更新问题,redis7.0ziplist替换为listpack,下面是新quickList的结构图

image

Redis中listpack源码

typedef struct quicklist {
    quicklistNode *head;
    quicklistNode *tail;
    unsigned long count;        /* 所有列表包中所有条目的总数,占用16 bits,最大65536 */
    unsigned long len;          /* quicklistNode 的数量 */
    signed int fill : QL_FILL_BITS;       /* 单个节点的填充因子 */
    unsigned int compress : QL_COMP_BITS; /* 不压缩的端节点深度;0=off */
    unsigned int bookmark_count: QL_BM_BITS;
    quicklistBookmark bookmarks[];
} quicklist;
typedef struct quicklistNode {
    struct quicklistNode *prev;
    struct quicklistNode *next;
    unsigned char *entry;
    size_t sz;             /* 当前entry占用字节 */
    unsigned int count : 16;     /* listpack元素个数,最大65535 */
    unsigned int encoding : 2;   /* RAW==1 or LZF==2 */
    unsigned int container : 2;  /* PLAIN==1 or PACKED==2 */
    unsigned int recompress : 1; /* 当前listpack是否需要再次压缩 */
    unsigned int attempted_compress : 1; /* 测试用 */
    unsigned int extra : 10; /* 备用 */
} quicklistNode;

listpack内部entry个数可在redis.conf配置

List-Max-listpack-size -2
# -5: 每个listpack最多为 64 kb  <-- 影响正常负载,不推荐
# -4: 每个listpack最多为 32 Kb  <-- 不推荐
# -3: 每个listpack最多为 16 Kb  <-- 最好不要使用
# -2: 每个listpack最多为 8 Kb   <-- 好
# -1: 每个listpack最多为 4 Kb   <-- 好
# 正数为listpack内部entry个数

Set

概述

set是一个无序的字符串集合,它不允许重复的元素。一个 set类型的键最多可以存储 2^32 – 1 个元素。

set类型的底层实现有两种:

  • intset,整数集合
  • hashtable(哈希表)。哈希表和 hash 类型的哈希表相同,它将元素存储在一个数组中,并通过哈希函数计算元素在数组中的索引

Redis 会根据 set 中元素的数量和类型来选择合适的编码方式,当 set 达到一定的阈值时,会自动转换编码方式。


typedef struct intset {
    uint32_t encoding;
    uint32_t length;
    int8_t contents[];
} intset;

应用场景

set 类型的应用场景主要是利用集合的特性,比如:

  • 去重,利用 sadd 和 scard 命令实现元素的添加和计数。
  • 交集,并集,差集,利用 sinter,sunion 和 sdiff 命令实现集合间的运算。
  • 随机抽取,利用 srandmember 命令实现随机抽奖或者抽样。

底层原理

在讲解set结构之前,需要先说明一下set结构编码的更替,如下

  • Redis7.2之前,set使用的是intsethashtable
  • Redis7.2之后,set使用的是intsetlistpackhashtable

intset

intset是一种紧凑的数组结构,它只保存int类型的数据,它将所有的元素按照从小到大的顺序存储在一块连续的内存中。intset会根据传入的数据大小,encoding分为int16_tint32_tint64_t
image

127.0.0.1:6379> sadd set 123
(integer) 1
127.0.0.1:6379> object encoding set
"intset"
127.0.0.1:6379> sadd set abcd
(integer) 1
127.0.0.1:6379> object encoding set
"hashtable"

intset 和 hashtable 的转换

Redis7.2之前,当一个集合满足以下两个条件时,Redis 会选择使用intset编码:

  • 集合对象保存的所有元素都是整数值
  • 集合对象保存的元素数量小于等于512个(默认)

intset最大元素数量可在redis.conf配置

set-max-intset-entries 512

为什么加入了listpack

redis7.2之前,sds类型的数据会直接放入到编码结构式为hashtableset中。其中,sds其实就是redis中的string类型。

而在redis7.2之后,sds类型的数据,首先会使用listpack结构当 set达到一定的阈值时,才会自动转换为hashtable

添加listpack结构是为了提高内存利用率和操作效率,因为 hashtable 的空间开销和碰撞概率都比较高。

hashtable 的空间开销高

hashtable 的空间开销高是因为它需要预先分配一个固定大小的数组来存储键值对,而这个数组的大小通常要大于实际存储的元素个数,以保证较低的装载因子。装载因子是指 hashtable 中已经存储的元素个数和数组大小的比值,它反映了 hashtable 的空间利用率

  • 如果装载因子过高,那么 hashtable 的性能会下降,因为碰撞的概率会增加
  • 如果装载因子过低,那么 hashtable 的空间利用率会下降,因为数组中会有很多空闲的位置

因此,hashtable 需要在装载因子和空间利用率之间做一个平衡,通常装载因子的推荐值是 0.75

hashtable 的碰撞概率高

hashtable 的碰撞概率高是因为它使用了一个散列函数来将任意长度的键映射到一个有限范围内的整数,作为数组的索引

散列函数的设计很重要,它应该尽可能地保证不同的键能够均匀地分布在数组中,避免出现某些位置过于拥挤,而其他位置过于稀疏的情况。然而,由于散列函数的输出范围是有限的,而键的取值范围是无限的,所以不可能完全避免两个不同的键被散列到同一个位置上,这就产生了碰撞。碰撞会影响 hashtable 的性能,因为它需要额外的处理方式来解决冲突,比如开放寻址法或者链地址法

举例说明,假设有一个大小为8的hashtable,使用取模运算作为散列函数,即h(k) = k mod 8。现在有四个键:5,13,21,29,它们都被散列到索引1

image

这就是一个碰撞的例子,因为四个键都映射到了同一个索引。这种情况可能是由于以下原因造成的:

  • 散列函数的选择不合适,没有充分利用hashtable的空间。
  • 键的分布不均匀,有些区间的键出现的频率更高。
  • hashtable的大小太小,不能容纳所有的键。

为了解决碰撞,redis采用了链地址法。就是在每个索引处维护一个链表,存储所有散列到该索引的键。但是,如果链表过长,查找效率会降低。因此,一般建议保持hashtable的负载因子(即键的数量除以hashtable的大小)在一定范围内,比如0.5到0.75之间。如果负载因子过高或过低,可以通过扩容或缩容来调整hashtable的大小

intset 、listpack和hashtable的转换

intset 、listpack和hashtable这三者的转换时根据要添加的数据、当前set的编码和阈值决定的。

  • 如果要添加的数据是整型,且当前set的编码为intset,如果超过阈值由intset直接转为hashtable

    阈值条件为:
    set-max-intset-entries ,intset最大元素个数,默认512

  • 如果要添加的数据是字符串,分为三种情况
    • 当前set的编码为intset:如果没有超过阈值,转换为listpack;否则,直接转换为hashtable
    • 当前set的编码为listpack:如果超过阈值,就转换为hashtable
    • 当前set的编码为hashtable:直接插入,编码不会进行转换

    阈值条件为:
    set-max-listpack-entries:最大元素个数,默认128
    set_max_listpack_value:最大元素大小,默认64
    以上两个条件需要同时满足才能进行编码转换

ZSet

概述

Redis中的 zset是一种有序集合类型,它可以存储不重复的字符串元素,并且给每个元素赋予一个排序权重值(score)。Redis通过权重值来为集合中的元素进行从小到大的排序。zset 的成员是唯一的,但权重值可以重复。一个 zset类型的键最多可以存储 2^32 – 1 个元素。

Redis中zset源码

typedef struct zskiplistNode {
    sds ele;
    double score;
    struct zskiplistNode *backward;
    struct zskiplistLevel {
        struct zskiplistNode *forward;
        unsigned long span;
    } level[];
} zskiplistNode;

typedef struct zskiplist {
    struct zskiplistNode *header, *tail;
    unsigned long length;
    int level;
} zskiplist;

typedef struct zset {
    dict *dict;
    zskiplist *zsl;
} zset;

应用场景

zset 类型的应用场景主要是利用分数和排序的特性,比如:

  • 排行榜,利用 zadd 和 zrange 命令实现分数的更新和排名的查询
  • 延时队列,利用 zadd 和 zpopmin 命令实现任务的添加和执行,并且可以定期地获取已经到期的任务
  • 访问统计,可以使用 zset 来存储网站或者文章的访问次数,并且可以按照访问量进行排序和筛选。

底层原理

Redis在存储zset结构的数据,为了达到内存和性能的平衡,针对少量存储和大量存储分别设计了两种结构,分别为:

  • ziplistredis7.0之前使用)和listpack(redis7.0之后使用)
  • skiplist

当 zset中的元素个数和元素值的长度比较小的时候,Redis使用ziplist/listpack来节省内存空间。当 zset中的元素个数和元素值的长度达到一定阈值时,Redis会自动将ziplist/listpack转换为skiplist,以提高操作效率

具体来说,当 zset同时满足以下两个条件时,会使用 listpack作为底层结构:

  • 元素个数小于 zset_max_listpack_entries,默认值为 128
  • 元素值的长度小于zset_max_listpack_value,默认值为 64

当 zset中不满足以上两个条件时,会使用 skiplist作为底层结构。

skiplist

跳跃表是一种随机化的数据结构,实质就是一种可以进行二分查找的有序链表。跳跃表在原有的有序链表上面增加了多级索引,通过索引来实现快速查找。跳跃表不仅能提高搜索性能,同时也可以提高插入和删除操作的性能

image

跳跃表相比于其他平衡树结构,有以下几个优点和缺点:

优点:

  • 实现简单,易于理解和调试
  • 插入和删除操作只需要修改局部节点的指针,不需要像平衡树那样进行全局调整
  • 可以利用空间换时间,通过增加索引层来提高查找效率
  • 支持快速的范围查询,可以方便地返回指定区间内的所有元素

缺点:

  • 空间复杂度较高,需要额外存储多级索引
  • 随机性太强,性能不稳定,最坏情况下可能退化成链表
  • 不支持快速的倒序遍历,需要额外的指针来实现

redis的skiplist

skiplist有一个层数上的问题,当层数过多,会影响查询效率。而Redis使用了一个随机函数来决定每个节点的层数,这个随机函数的期望值是 1/(1-p) ,其中 p是一个概率常数,Redis中默认为 0.25。这样可以保证跳跃表的平均高度为 log (1/p) n ,其中 n是节点数。Redis 还限制了跳跃表的最大层数为 32 ,这样可以避免过高的索引层造成空间浪费

Stream

概述

stream 是一个类似于日志的数据结构,它可以记录一系列的键值对,每个键值对都有一个唯一的 ID。一个 stream 类型的键最多可以存储 2^64 – 1 个键值对。

stream 类型的底层实现是 rax(基数树),它是一种压缩的前缀树结构,它将所有的键值对按照 ID 的字典序存储在一个树形结构中。rax 可以快速地定位、插入、删除任意位置的键值对

应用场景

stream 类型的应用场景主要是实现事件驱动的架构,比如:

  • 消息队列,利用 xadd 和 xread 命令实现生产者消费者模式。
  • 操作日志,利用 xadd 和 xrange 命令实现操作记录和回放。
  • 数据同步,利用 xadd 和 xreadgroup 命令实现多个消费者组之间的数据同步。

底层原理

Rax Tree

rax tree是一种基于基数树(radix tree)的变体,也叫做压缩前缀树(compressed prefix tree),它被应用于redis stream中,用来存储streamID,其数据结构为

typedef struct raxNode {
    uint32_t iskey:1;     /* Does this node contain a key? */
    uint32_t isnull:1;    /* Associated value is NULL (don't store it). */
    uint32_t iscompr:1;   /* 前缀是否压缩 */
    uint32_t size:29;     /* Number of children, or compressed string len. */
    unsigned char data[];
} raxNode;
  • iskey:是否包含key
  • isnull:是否存储value值
  • iscompr:前缀是否压缩。决定了size存储的是什么和data的数据结构
  • size
    • iscompr=0:节点为非压缩节点size是孩子节点的数量
    • iscompr=1:节点为压缩节点size是已压缩的字符串长度
  • data
    • iscompr=0:节点为非压缩节点,数据格式为[header strlen=0][abc][a-ptr][b-ptr][c-ptr](value-ptr?)。其有size个字符,
    • iscompr=1:节点为压缩节点,数据格式为[header strlen=3][xyz][z-ptr](value-ptr?)

为了便于理解,设定一些场景举例说明

场景一:只插入foot

数据结构为:

image

其中,z-ptr指向的叶子节点的iskey=1,标识foot这个key。下图为使用树状图的形式来展现其数据结构
image

场景二:插入foot后,插入footer

数据结构为:

image

其插入过程为:

  1. foot节点中每个字符进行比较,获得最大公共前缀foot
  2. er作为foot的子节点,其iskey=1,标识foot这个key
  3. er的子节点的iskey=1,标识footer这个key

下图为使用树状图的形式来展现其数据结构
image

场景三:插入foot后,插入fo

数据结构为:

image

其插入过程为:

  1. foot节点中每个字符进行比较,获得最大公共前缀fo
  2. foot拆成foot
  3. ot作为fo的子节点,其iskey=1,标识fo这个key
  4. 设置ot的子节点的iskey=1,标识foot这个key

下图为使用树状图的形式来展现其数据结构
image

场景四:插入foot后,插入foobar

数据结构为:

image

其插入过程为:

  1. foot节点中每个字符进行比较,获得最大公共前缀foo
  2. foot拆成foot
  3. footbar拆成foobar
  4. tb作为foo的子节点
  5. 设置ot的子节点的iskey=1,标识foot这个key
  6. ar作为b的子节点
  7. 设置ar的子节点的iskey=1,标识footbar这个key

下图为使用树状图的形式来展现其数据结构
image

Stream

stream的底层使用了rax treelistpack两种结构,rax tree用来存储streamID,而listpack用来存储对应的值,结构图如下:

image

Hyperloglog

概述

HyperLogLog 是一种概率数据结构,用于在恒定的内存大小下估计集合的基数(不同元素的个数)。它不是一个独立的数据类型,而是一种特殊的 string 类型,它可以使用极小的空间来统计一个集合中不同元素的数量,也就是基数。一个 hyperloglog 类型的键最多可以存储 12 KB 的数据

hyperloglog 类型的底层实现是 SDS(simple dynamic string),它和 string 类型相同,只是在操作时会使用一种概率算法来计算基数。hyperloglog 的误差率为 0.81%,也就是说如果真实基数为 1000,那么 hyperloglog 计算出来的基数可能在 981 到 1019 之间

应用场景

hyperloglog 类型的应用场景主要是利用空间换时间和精度,比如:

  • 统计网站的独立访客数(UV)
  • 统计在线游戏的活跃用户数(DAU)
  • 统计电商平台的商品浏览量
  • 统计社交网络的用户关注数
  • 统计日志分析中的不同事件数

假如需要统计某商品的用户关注数,可以通过以下方式:

> PFADD goodA "1"
1
> PFADD goodA "2"
1
> PFADD goodA "3"
1
> PFCOUNT goodA
3

GEO

概述

geospatial 是一种用于存储和查询地理空间位置的数据类型,它基于 sorted set 数据结构实现,利用 geohash 算法将经纬度编码为二进制字符串,并作为 sorted set 的 score 值。Redis geospatial 提供了一系列的命令来添加、删除、搜索和计算地理空间位置,例如:

  • GEOADD key longitude latitude member [longitude latitude member …]:将一个或多个地理空间位置(经度、纬度、名称)添加到指定的 key 中
  • GEOPOS key member [member …]:返回一个或多个地理空间位置的经纬度
  • GEODIST key member1 member2 [unit]:返回两个地理空间位置之间的距离,可以指定单位(m, km, mi, ft)
  • GEORADIUS key longitude latitude radius unit [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]:返回指定圆心和半径内的地理空间位置,可以指定返回坐标、距离、哈希值、数量、排序方式等,也可以将结果存储到另一个 key 中
  • GEORADIUSBYMEMBER key member radius unit [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]: 返回以指定成员为圆心的指定半径内的地理空间位置,其他参数同 GEORADIUS

应用场景

geospatial 的应用是地理位置搜索、分析和展示,例如地图应用、导航应用、位置服务应用等。例如,可以使用 geospatial 来实现以下功能:

  • 统计某个区域内的商家或用户数量
  • 查询某个位置附近的餐馆或酒店
  • 计算两个位置之间的距离或行驶时间
  • 显示某个位置周围的景点或活动

Bitmap

概述

bitmap不是一个独立的数据类型,而是一种特殊的 string 类型,它可以将一个 string 类型的值看作是一个由二进制位组成的数组,并提供了一系列操作二进制位的命令。一个 bitmap 类型的键最多可以存储 2^32 – 1 个二进制位。

bitmap 类型的底层实现是 SDS(simple dynamic string),它和 string 类型相同,只是在操作时会将每个字节拆分成 8 个二进制位。

应用场景

bitmap 类型的应用场景主要是利用二进制位的特性,比如:

  • 统计用户活跃度,利用 setbit 和 bitcount 命令实现每天或每月用户登录次数的统计。
  • 实现布隆过滤器,利用 setbit 和 getbit 命令实现快速判断一个元素是否存在于一个集合中。
  • 实现位图索引,利用 bitop 和 bitpos 命令实现对多个条件进行位运算和定位

假如需要统计每个用户的当天登录次数统计。

首先,需要规定bitmap的格式,假设为{userid}:{年份}:{第几天} {秒数} {是否登录}

userid为100的用户,记录他在2024年第100天中第1秒,是否登录

SETBIT 1000:2024:100 1 1
0

userid为100的用户,记录他在2024年第100天中第10240 秒,是否登录

SETBIT 1000:2024:100 10240 1
0

userid为100的用户,记录他在2024年第100天中第86400 秒,是否登录

SETBIT 1000:2024:100 86400 1
0

统计userid为100的用户,在2024年第100天的登录次数

BITCOUNT 1000:2024:100
3

Bitfield

概述

bitfield结构是基于字符串类型的一种扩展,可以让你对一个字符串中的任意位进行设置,增加和获取操作,就像一个位数组一样

可以操作任意位长度的整数,从无符号的1位整数有符号的63位整数。这些值是使用二进制编码的Redis字符串来存储的

bitfield结构支持原子的读,写和增加操作,使它们成为管理计数器和类似数值的好选择

使用场景

Bitfield的使用场景与bitmap类似,主要是一些需要用不同位长度的整数来表示状态或属性的场合,例如:

  • 用一个32位的无符号整数来表示用户的金币数量,用一个32位的无符号整数来表示用户杀死的怪物数量,可以方便地对这些数值进行设置,增加和获取
  • 用一个16位的有符号整数来表示用户的等级,用一个16位的有符号整数来表示用户的经验值,可以方便地对这些数值进行设置,增加和获取
  • 用一个8位的无符号整数来表示用户的性别,用一个8位的无符号整数来表示用户的年龄,可以方便地对这些数值进行设置,增加和获取

bitfieldbitmap都是基于string类型的位操作,但是有一些区别:

  • bitmap只能操作1位的无符号整数,而bitfield可以操作任意位长度的有符号或无符号整数
  • bitmap只能设置或获取指定偏移量上的位,而bitfield可以对指定偏移量上的位进行增加或减少操作
  • bitmap可以对多个字符串进行位运算,而bitfield只能对单个字符串进行位操作
  • bitmap的偏移量是从0开始的,而bitfield的偏移量是从最高有效位开始的

例如,使用bitfield存储用户的个人信息,

  • 用一个8位的无符号整数来表示用户的性别,0表示男,1表示女
  • 用一个8位的无符号整数来表示用户的年龄,范围是0-255
  • 用一个16位的无符号整数来表示用户的身高,单位是厘米,范围是0-65535
  • 用一个16位的无符号整数来表示用户的体重,单位是克,范围是0-65535

假设有一个用户,性别是女,年龄是25,身高是165厘米,体重是50千克,可以用以下命令来存储和获取这些信息:

> BITFIELD user:1:info SET u8 #0 1 SET u8 #1 25 SET u16 #2 165 SET u16 #3 50000
0
0
0
0

然后,获取这个用户的信息,性别、年龄、身高、体重

> BITFIELD user:1:info GET u8 #0 GET u8 #1 GET u16 #2 GET u16 #3
1
25
165
50000

百度飞桨(PaddlePaddle)-数字识别 - VipSoft - 博客园

mikel阅读(341)

来源: 百度飞桨(PaddlePaddle)-数字识别 – VipSoft – 博客园

手写数字识别任务 用于对 0 ~ 9 的十类数字进行分类,即输入手写数字的图片,可识别出这个图片中的数字。

使用 pip 工具安装 matplotlib 和 numpy

python -m pip install matplotlib numpy -i https://mirror.baidu.com/pypi/simple
python -m pip install paddlepaddle==2.4.2 -i https://pypi.tuna.tsinghua.edu.cn/simple

D:\OpenSource\PaddlePaddle>python -m pip install matplotlib numpy -i https://mirror.baidu.com/pypi/simple
Looking in indexes: https://mirror.baidu.com/pypi/simple
Collecting matplotlib
  Downloading https://mirror.baidu.com/pypi/packages/92/01/2c04d328db6955d77f8f60c17068dde8aa66f153b2c599ca03c2cb0d5567/matplotlib-3.7.1-cp38-cp38-win_amd64.whl (7.6 MB)
     |████████████████████████████████| 7.6 MB ...
Requirement already satisfied: numpy in d:\program files\python38\lib\site-packages (1.24.3)
Collecting packaging>=20.0
  Downloading https://mirror.baidu.com/pypi/packages/ab/c3/57f0601a2d4fe15de7a553c00adbc901425661bf048f2a22dfc500caf121/packaging-23.1-py3-none-any.whl (48 kB)
     |████████████████████████████████| 48 kB 1.2 MB/s
Collecting cycler>=0.10
  Downloading https://mirror.baidu.com/pypi/packages/5c/f9/695d6bedebd747e5eb0fe8fad57b72fdf25411273a39791cde838d5a8f51/cycler-0.11.0-py3-none-any.whl (6.4 kB)
Requirement already satisfied: pillow>=6.2.0 in d:\program files\python38\lib\site-packages (from matplotlib) (9.5.0)
Collecting python-dateutil>=2.7
  Downloading https://mirror.baidu.com/pypi/packages/36/7a/87837f39d0296e723bb9b62bbb257d0355c7f6128853c78955f57342a56d/python_dateutil-2.8.2-py2.py3-none-any.whl (247 kB)
     |████████████████████████████████| 247 kB ...
Collecting importlib-resources>=3.2.0
  Downloading https://mirror.baidu.com/pypi/packages/38/71/c13ea695a4393639830bf96baea956538ba7a9d06fcce7cef10bfff20f72/importlib_resources-5.12.0-py3-none-any.whl (36 kB)
Collecting fonttools>=4.22.0
  Downloading https://mirror.baidu.com/pypi/packages/16/07/1c7547e27f559ec078801d522cc4d5127cdd4ef8e831c8ddcd9584668a07/fonttools-4.39.3-py3-none-any.whl (1.0 MB)
     |████████████████████████████████| 1.0 MB ...
Collecting pyparsing>=2.3.1
  Downloading https://mirror.baidu.com/pypi/packages/6c/10/a7d0fa5baea8fe7b50f448ab742f26f52b80bfca85ac2be9d35cdd9a3246/pyparsing-3.0.9-py3-none-any.whl (98 kB)
     |████████████████████████████████| 98 kB 862 kB/s
Collecting contourpy>=1.0.1
  Downloading https://mirror.baidu.com/pypi/packages/08/ce/9bfe9f028cb5a8ee97898da52f4905e0e2d9ca8203ffdcdbe80e1769b549/contourpy-1.0.7-cp38-cp38-win_amd64.whl (162 kB)
     |████████████████████████████████| 162 kB ...
Collecting kiwisolver>=1.0.1
  Downloading https://mirror.baidu.com/pypi/packages/4f/05/59b34e788bf2b45c7157c3d898d567d28bc42986c1b6772fb1af329eea0d/kiwisolver-1.4.4-cp38-cp38-win_amd64.whl (55 kB)
     |████████████████████████████████| 55 kB 784 kB/s
Collecting zipp>=3.1.0
  Downloading https://mirror.baidu.com/pypi/packages/5b/fa/c9e82bbe1af6266adf08afb563905eb87cab83fde00a0a08963510621047/zipp-3.15.0-py3-none-any.whl (6.8 kB)
Requirement already satisfied: six>=1.5 in d:\program files\python38\lib\site-packages (from python-dateutil>=2.7->matplotlib) (1.16.0)
Installing collected packages: zipp, python-dateutil, pyparsing, packaging, kiwisolver, importlib-resources, fonttools, cycler, contourpy, matplotlib
Successfully installed contourpy-1.0.7 cycler-0.11.0 fonttools-4.39.3 importlib-resources-5.12.0 kiwisolver-1.4.4 matplotlib-3.7.1 packaging-23.1 pyparsing-3.0.9 python-dateutil-2.8.2 zipp-3.15.0
WARNING: You are using pip version 21.1.1; however, version 23.1.2 is available.
You should consider upgrading via the 'D:\Program Files\Python38\python.exe -m pip install --upgrade pip' command.

D:\OpenSource\PaddlePaddle>

创建 DigitalRecognition.py

官网代码少了 plt.show() # 要加上这句,才会显示图片

import paddle
import numpy as np
from paddle.vision.transforms import Normalize

transform = Normalize(mean=[127.5], std=[127.5], data_format='CHW')
# 下载数据集并初始化 DataSet
'''
飞桨在 paddle.vision.datasets 下内置了计算机视觉(Computer Vision,CV)领域常见的数据集,
如 MNIST、Cifar10、Cifar100、FashionMNIST 和 VOC2012 等。在本任务中,
先后加载了 MNIST 训练集(mode='train')和测试集(mode='test'),训练集用于训练模型,测试集用于评估模型效果。
'''
train_dataset = paddle.vision.datasets.MNIST(mode='train', transform=transform)
test_dataset = paddle.vision.datasets.MNIST(mode='test', transform=transform)
# 打印数据集里图片数量 60000 images in train_dataset, 10000 images in test_dataset
# print('{} images in train_dataset, {} images in test_dataset'.format(len(train_dataset), len(test_dataset)))

# 模型组网并初始化网络
lenet = paddle.vision.models.LeNet(num_classes=10)
model = paddle.Model(lenet)

# 模型训练的配置准备,准备损失函数,优化器和评价指标
model.prepare(paddle.optimizer.Adam(parameters=model.parameters()),
              paddle.nn.CrossEntropyLoss(),
              paddle.metric.Accuracy())

# 模型训练
model.fit(train_dataset, epochs=5, batch_size=64, verbose=1)
# 模型评估
model.evaluate(test_dataset, batch_size=64, verbose=1)

# 保存模型
model.save('./output/mnist')
# 加载模型
model.load('output/mnist')

# 从测试集中取出一张图片
img, label = test_dataset[0]
# 将图片shape从1*28*28变为1*1*28*28,增加一个batch维度,以匹配模型输入格式要求
img_batch = np.expand_dims(img.astype('float32'), axis=0)

# 执行推理并打印结果,此处predict_batch返回的是一个list,取出其中数据获得预测结果
out = model.predict_batch(img_batch)[0]
pred_label = out.argmax()
print('true label: {}, pred label: {}'.format(label[0], pred_label))
# 可视化图片
from matplotlib import pyplot as plt
plt.imshow(img[0])
plt.show()  # 要加上这句,才会显示图片

PyCharm运行(推荐,有错误能显示出来)

Python MatplotlibDeprecationWarning Matplotlib 3.6 and will be removed two minor releases later
File -> Settings -> Tools -> Python Scientific -> 取消 Show plots in tool window,
取消后,将不会看到红字警告提示
image

CMD 运行

D:\OpenSource\PaddlePaddle>python DigitalRecognition.py
image
image

如果碰到下列错误,需要加上 plt.show()
Python MatplotlibDeprecationWarning Matplotlib 3.6 and will be removed two minor releases later

MatplotlibDeprecationWarning: Support for FigureCanvases without a required_interactive_framework attribute was deprecated in Matplotlib 3.6 and will be removed two minor releases later.
  plt.imshow(img[0])

数据集定义与加载

飞桨在 paddle.vision.datasets 下内置了计算机视觉(Computer Vision,CV)领域常见的数据集,如 MNIST、Cifar10、Cifar100、FashionMNIST 和 VOC2012 等。在本任务中,先后加载了 MNIST 训练集(mode=’train’)和测试集(mode=’test’),训练集用于训练模型,测试集用于评估模型效果。
飞桨除了内置了 CV 领域常见的数据集,还在 paddle.text 下内置了自然语言处理(Natural Language Processing,NLP)领域常见的数据集,并提供了自定义数据集与加载功能的 paddle.io.Dataset 和 paddle.io.DataLoader API,详细使用方法可参考『数据集定义与加载』 章节。

另外在 paddle.vision.transforms 下提供了一些常用的图像变换操作,如对图像的翻转、裁剪、调整亮度等处理,可实现数据增强,以增加训练样本的多样性,提升模型的泛化能力。本任务在初始化 MNIST 数据集时通过 transform 字段传入了 Normalize 变换对图像进行归一化,对图像进行归一化可以加快模型训练的收敛速度。该功能的具体使用方法可参考『数据预处理』 章节。

模型组网

https://www.paddlepaddle.org.cn/documentation/docs/zh/guides/beginner/quick_start_cn.html#moxingzuwang

模型训练与评估

https://www.paddlepaddle.org.cn/documentation/docs/zh/guides/beginner/quick_start_cn.html#moxingxunlianyupinggu

模型推理

https://www.paddlepaddle.org.cn/documentation/docs/zh/guides/beginner/quick_start_cn.html#moxingtuili

image

AI 绘画基础 - 细数 Stable Diffusion 中的各种常用模型 【🧙 魔导士装备图鉴】 - 嘿嘿不务正业 - 博客园

mikel阅读(328)

来源: AI 绘画基础 – 细数 Stable Diffusion 中的各种常用模型 【🧙 魔导士装备图鉴】 – 嘿嘿不务正业 – 博客园

AI 绘画新手魔导士在刚开始玩 Stable Diffusion 时总会遇到各种新的概念,让人困惑,其中就包括各种模型和他们之间的关系。

魔法师入门得先认识各种法师装备(各种模型),让我们遇到问题知道使用何种装备来协助自己发挥更大的效果。

safetensors

在了解各种模型之前,有必须先了解下 safetensors,玩过的应该都认识,就是很多模型的后缀。然而各种模型的后缀五花八门,但是总是能看到 safetensors 的影子,让人有些缭乱。

其实主要是因为 safetensors 支持各种 AI 模型,而在 safetensors 出现前,各种 AI 模型都有着自己独特的后缀。这就导致每种模型既可以使用 safetensors 又可以使用自己原有的后缀,所以入门的时候就会让人有点分不清。

其实 safetensors 是由 huggingface 研发的一种开源的模型格式,它有几种优势:

  • 足够安全,可以防止 DOS 攻击
  • 加载迅速
  • 支持懒加载
  • 通用性强

所以现在大部分的开源模型都会提供 safetensors 格式。

开源地址: https://github.com/huggingface/safetensors

模型分类

说完了 safetensors 我们进入正题,聊一聊在 Stable Diffusion 中常见的各种模型。

Checkpoint | Stable Diffusion

Checkpoint 是 Stable Diffusion 中最重要的模型,也是主模型,几乎所有的操作都要依托于主模型进行。而所有的主模型都是基于 Stable Diffusion 模型训练而来,所以有时会被称为 Stable Diffusion 模型。

主模型后缀一般为 .ckpt 或者 .safetensors,并且体积比较庞大,一般在 2G – 7G 之间。而要管理模型我们需要进入 WebUI 目录下的 models/Stable-diffusion 目录下。

在使用 WebUI 时左上角切换的就是主模型了。

picture 1

LoRA 和 LyCORIS

LoRA 是除了主模型外最常用的模型。LoRA 和 LyCORIS 都属于微调模型,一般用于控制画风、控制生成的角色、控制角色的姿势等等。

LoRA 和 LyCORIS 的后缀均为 .safetensors,体积较主模型要小得多,一般在 4M – 300M 之间。一般使用 LoRA 模型较多,而 LyCORIS 与 LoRA 相比可调节范围更大,但是需要额外的扩展才可使用。需要管理模型时我们可以进入 WebUI 目录下的 models/LoRA 目录下。

在 WebUI 中使用时,可通过点击左侧的小红灯,然后在 LoRA 菜单中点击使用。也可以直接使用 Prompt 调用。

picture 2

Textual Inversion

Textual Inversion 是文本编码器模型,用于改变文字向量。可以将其理解为一组 Prompt。

Textual Inversion 后缀为 .pt 或者 .safetensors,体积非常小,一般只有几 kb。模型所在的目录不在 models 下,而是在 WebUI 中的 embeddings 目录下。

在使用时同样可以使用小红灯中的 Textual Inversion,也可以使用 Prompt 调用。

picture 1

Hypernetworks

Hypernetworks 模型用于调整模型神经网络权重,进行风格的微调。

Hypernetworks 的后缀为 .pt 或者 .safetensors,体积一般在 20M – 200M 之间。模型的目录为 WebUI 下的 models/hypernetworks

在使用时同样可以使用小红灯中的 Hypernetworks。

picture 2

ControlNet

ControlNet 是一个及其强大的控制模型,它可以做到画面控制、动作控制、色深控制、色彩控制等等。使用时需要安装相应的扩展才可。

ControlNet 类模型的后缀为 .safetensors。模型的目录为 models/ControlNet

使用时我们需要先去 Extensions 页面搜索 ControlNet 扩展,然后 Install 并 Reload UI。然后便可以在 txt2img 和 img2img 菜单下找到:

picture 3

ControlNet 功能非常强大,还解决 AI 画手的噩梦问题。

VAE

VAE 模型一般用于图片亮度和饱和度的修正、画面较正和以及补光等。一般在绘图时如果出现图片亮度过低、发灰等问题时就需要用到。

VAE 模型的后缀为 .pt 或 .safetensors,体积一般为 335M 或 823M。模型的目录为 models/VAE

使用时需要到 Settings 页面找到 SD VAE 菜单切换。

picture 4

但是这样使用过于繁琐,所以如果使用到建议在 Quicksettings list 配置中添加 sd_vae。

picture 5

这样就可以在 WebUI 的顶部进行切换。

picture 6

CodeFormer

CodeFormer 模型一般用于图片的修复,比如提高图片的分辨率、将黑白照片修改成彩色照片、人脸修复等等。

CodeFormer 是一个开源项目,在 WebUI 中已经默认被整合,可以在 Extras 菜单中使用。如果需要修改 CodeFormer 版本可以将模型放到 models/codeformer

picture 7

一览表

模型名称 作用 后缀名 大小 在 WebUI 中的文件夹
Checkpoint 主模型 .ckpt 或 .safetensors 2G – 7G models/Stable-diffusion
LoRA 和 LyCORIS 微调模型,一般用于控制画风、控制生成的角色、控制角色的姿势等等 .safetensors 2G – 7G models/Stable-diffusion
Textual Inversion 文本编码器模型 .pt 或 .safetensors KB 级别 embeddings
Hypernetworks 调整模型神经网络权重,进行风格的微调 .pt 或 .safetensors 20M – 200M models/hypernetworks
ControlNet 强大的控制模型,可以进行画面控制、动作控制、色深控制、色彩控制等等 .safetensors KB 级别 models/ControlNet
VAE 图片亮度和饱和度的修正、画面较正和以及补光等 .pt 或 .safetensors 335M 或 823M models/VAE
CodeFormer 修复模型,修复人脸、提高分辨率等 models/codeformer

最后

上面几种模型就是 Stable Diffusion 中最常用的几种,通过 Checkpoint 控制图片的主要风格;通过 VAE 给图片补光、调亮;通过 LoRA | LyCORIS 对模型进行风格、角色控制;通过 Textual Inversion 简化 Prompt;通过 ControlNet 进行姿势、色彩控制,修复手部。

不过这并不是 Stable Diffusion 中所有的模型,其它的一些模型如果有空再整理下。

AI 绘画咒语入门 - Stable Diffusion Prompt 语法指南 【成为初级魔导士吧!】 - 嘿嘿不务正业 - 博客园

mikel阅读(361)

来源: AI 绘画咒语入门 – Stable Diffusion Prompt 语法指南 【成为初级魔导士吧!】 – 嘿嘿不务正业 – 博客园

要用好 Stable Diffusion,最最重要的就是掌握 Prompt(提示词)。由于提示词对于生成图的影响甚大,所以被称为魔法,用得好惊天动地,用不好魂飞魄散 🐶。

因此本篇整理下提示词的语法(魔法咒语)、如何使用(如何吟唱)、以及一些需要注意的细节问题(避免翻车)。

基础语法

在提示词中我们使用 , 对提示词进行分割,而每个部分的提示语可以有不同类型,比如:自然语法、标签语法、emoji 或者是颜文字。

自然语法

Stable Diffusion 的提示词支持自然语法,比如告诉它 a girl is touching a cat 可以得到如下图片:

picture 1

也支持一定程度的中文、日文,比如 睡觉的狗

picture 2

但是中文理解力支持十分有限,所以一般都会使用英文来作为提示词。

标签语法/Tag

除了自然语法外, 提示词中最常用的是标签语法,以单词或短语来给 AI 做提示,比如 masterpiece, best quality, 1dog, 1cat, sun, grass

picture 3

会得到一张有狗、猫、太阳和草坪的图片。

emoji

除了一般语言外,我们还可以使用 emoji 来作为提示词,比如使用 😭 可以让 Stable Diffusion 画出这样的表情:

picture 4

使用 😄 可以得到这样的图:

picture 5

想要使用 emoji 的可以参考 这个 wiki 查看各个 emoji 代表的意思。

颜文字

此外 Stable Diffusion 还支持使用颜文字来作为提示词,比如可以使用 XD 来让人物笑起来:

picture 6

用 T_T 让人物表现伤心:

picture 7

如果想要用颜文字可以参考 这个 wiki 查看颜文字代表的意思。

虽然 Stable Diffusion 的提示词支持多种写法,但是日常使用一般建议

  1. 以 标签语法为主,试需求使用自然语法
  2. 尽量使用英文而不是中文作为提示词
  3. 除了一些常见的提示词所有 model 都支持外,很多提示词需要 model 的支持
  4. 提示词一定要注意拼写,一旦拼写错误或者是用到了 AI 无法识别的提示词 AI 将会将其拆解成他可以理解的部分,甚至可能拆分成字母
  5. emoji 和颜文字实际测试使用效果很差,可能是我所使用的 model 的原因,一般不建议使用
  6. 提示词尽量越清晰越好

权重语法

除了基本语法外,我们还可以使用一些语法来调节每个提示词的权重。调节权重包含以下几种方式:

  1. 默认情况下越靠前的提示词权重越高
  2. 通过 (提示词:权重数值) 手动设置权重,比如: (1cat:1.3),(1dog:0.8)
  3. 通过 () {} [] 设置权重:
    • {提示词} 等价于 (提示词:1.05)
    • (提示词) 等价于 (提示词:1.1)
    • [提示词] 等价于 (提示词:0.952) 即 1/1.05

且 () {} [] 语法可嵌套使用,比如 (((提示词))) 就等价于 (提示词:1.331)

一般情况下建议使用 (提示词:权重数值) 语法,可读性、可控性更高。

注意一般情况下权重不建议超过 1.5,不然会对画面造成巨大影响。

模型引用语法

除了基础提示语外,类似于 Lora 模型也是需要使用提示语来饮用的,语法: <lora:模型⽂件名:权重>

比如如果要使用知名的模型墨心,提示词是这样的 <lora:MoXinV1:1>

picture 14

需要注意模型的名称会按照实际的文件名来,可直接使用 WebUI 中的 Lora 面板来生成。

进阶语法

OR

OR 语法一般用于提示词的混合,比如在绘制头发时通过 [purple|sliver|green]_hair 可以绘制出这样的混色的发色:

picture 8

也可以搭配 multicolor hair 生成这样的头发:

picture 9

也可以使用 [horse|bird] 来生成长翅膀的马,不过细节很难控制:

picture 10

AND

AND 语法和 OR 语法十分类似,实战下来效果也差不多,可能是我姿势不对。

比如 purple hair AND sliver hair AND green hair 可以生成这样的发色:

picture 12

此外 AND 语法还支持为某个片段增加权重,比如 gold hair :1.2 AND sliver hair :0.8 AND green hair 可以让发色更多金色:

picture 13

使用 bird AND lion AND horse 可以生成:

picture 11

emm,不知道为啥鸟飞一边去了。

不过据说 DDIM 采样 不支持 AND 语法。

步骤控制语法

Stable Diffusion 还支持通过步骤控制语法来让某些元素从第几步开始绘制,到第几步结束。

比如 [cat:10] 指从第十步开始画猫,而 [cat::20] 表示在第二十步结束画猫。也可以组合使用,比如: [[cat::20]:10] 代表从第十步开始第二十步结束。

关键字占比控制

此外还有通过占比语法控制关键字的绘制占比的。

比如 [dog:girl:0.9] 表示总绘制步骤的前 90% 画狗,后面画女孩,而 [dog:girl:30] 则表示前三十步画狗,后面画女孩。

魔法宝典

网上有很多整理好的提示词字典,可以用于快速创建出不同的风格,有需要的可以进行参考。

Chrome高阶调试指南 - 知乎

mikel阅读(425)

来源: Chrome高阶调试指南 – 知乎

  • 调试 JavaScript
    • Preserve Log
    • 代码断点与单步执行
    • Debugger
    • Event Listener Breakpoin & 屏蔽无用 log
    • 提取断点内变量值
    • 调试 Immutable.js
    • 彩色 log
    • 输出 log 时添加时间戳
    • JS 执行计时
    • clear & filter
  • 调试元素
    • 设备模拟
    • 远程调试
    • 远程调试微信页面
    • 模拟元素状态
  • 性能分析
    • disable cache
    • 模拟弱网条件 & 录制屏幕
    • 调试动画
  • 神奇功能
    • 密码找回
    • source map 抓源码
    • Copy as Curl
  • 扩展资料

调试 JavaScript

Preserve Log

调试页面的时候经常会遇到页面跳转, 跳转完之后 Console & Network 面板里记录全部清空, 这在页面上存在 302 跳转时会很恶心.

不过 Chrome 提供 Preserve Log 选项, 勾上这个, 只要不关页面, 记录就可以永久保存

代码断点与单步执行

Debugger

除了手工加断点, 在源代码中加入debugger, 也可以起到断点的效果

注意: debugger 只能在本地测试的时候加, 线上要是有 debugger 的话用户的 js 就执行不了了

Event Listener Breakpoin & 屏蔽无用 log

或者, 也可以按事件去加

同样, 如果某些库绑了太多无用代码, 你可以选择屏蔽这整个库. 比如, 把这个库加到 黑盒 里, 黑盒里 js 的执行过程不会被显示出来, 略有用

demo =>

动图封面

提取断点内变量值

调试过程中有可能会需要将一些变量值保存下来, 这时候只要在想要保存的变量上点击右键, 选择Store as global variable, 就可以在Console面板里使用这个变量(temp1, temp2, temp3, …)

调试 Immutable.js

虽然将变量保存了下来, 但如果调试的是 Immutable.js, 打印出来的 Immutable 对象其实会很难看.

解决办法也简单, F12 打开开发者工具, 然后 F1 打开开发者工具的设置, 勾选Preference->Console -> Enable Customer Formatters

然后点击安装Immutable.js Object Formatter插件

重启一下控制台, 再打印一下 Immutable 对象看看?

彩色 log

Immutable 的原理是利用了 console.log API, console.log 实际上是支持在打印结果中添加 css 效果的

示例:

console.log(
  "%c    ",
  "padding:10% 50%background:ur('http://stcms.beisen.com/CmsPortal/107965/107965_mdias_2018514_2018514logo.png') no-repeatbackground-position:center center"
);
console.log(
  "确认过眼神,你就是对的人\n来到链家,你可以尽发挥自己的特长\n来到链家,你可以不断提高自己的技术\n加入家,加入贝壳,成就房产行业新的巅峰"
);
console.log(
  "请将简历发送至 %c guliming@lianjia.com(邮件标题请以“姓名-应聘XX职位-来自console”命名)",
  "color:red"
);
console.log(
  "%c前端,PHP,QA等等各个岗位各个级别都有在哦,期待你的加入!",
  "color:red"
);
console.log("职位介绍:http://join.lianjia.com/");

效果 =>

参考 => Chrome 开发者文档文档

输出 log 时添加时间戳

log 除了打印消息, 还有一个用途是拿来检测页面性能. 比如开发 React Native 时, 我们可以通过 Android Studio 输出的日志时间戳来判断 JS 的执行情况

其实这个功能 Chrome 也可以做到, 还是 F1 打开开发者工具设置, 勾选Preference->Console -> Show Timestamps

再看看日志, 是不是以后优化页面加载性能, 查看 js 执行瓶颈的时候就方便多了

JS 执行计时

说到 JS 执行计时, 其实不太需要看 log 日志, 看下边的动图就够了

动图封面

clear & filter

Console 面板里执行 clear() 清屏, 在 filter 中输入关键字可以按条件过滤日志, 选项里有一条 Preserve Log, 选中之后只要不关 DevTool, 即使页面刷新也不会清空日志, 勾选之后调试带 302 跳转的页面特别方便

调试元素

设备模拟

在大多数情况下, 网页/后端都是通过 UA 来判断设备类型的, 所以我们只要将 UA & 分辨率改成和移动端一样, 再把点击鼠标事件从 click 改成 touch, 就可以直接在浏览器里调试移动端页面

如果想要模拟 App 操作的话, 只要选择 Edit, 添加上 App 的 UA 就可以了(具体值用 Charles 抓)

一般来说靠这两步就可以解决 99%的移动端调试问题, 但是谷歌显然还觉得不够, 如果你想了解进一步模拟 DPI 修改, 媒体查询检测等功能的话, 在这里可以看到谷歌官方的说明(当然是汉语)

远程调试

当然, 模拟归模拟, 谷歌表示有些移动端的功能实在模拟不了(iOS7 不支持 WebGL, iOS 5 的方向缩放有 bug, etc). 所以 Chrome 也提供了远程调试功能.可以让我们在电脑上直接调试 App 内的网页.

方法如下:

  1. App 启用 WebView 调试模式(一般的测试包都支持, 这里以 Chrome 为例)
  2. 手机启用开发者模式, 打开 USB 调试开关
  3. 电脑装驱动(Windows), 然后用 USB 线把手机连到电脑上
  4. 在 Chrome 上打开 inspect/, 找到 App 内的页面, 点击 inspect, over

效果 =>

然后就跟正常调试页面一样了, 想打断点打断点, 想看 log 看 log, 比用 Charles 把线上 js 替换成本地 js, 然后一个一个的写 alert 效率高多了

详细说明见官方文档

PS 一句, iOS 下也可以这么干, 除了要用 Safari 而不是 Chrome 调试外, 其他地方都一样

远程调试微信页面

和 Chrome 远程调试一样, 微信也支持远程调试功能, 只是需要手工开启一下

  1. 微信内访问debugx5.qq.com/, 或者直接扫描二维码
  1. 如果是首次使用需要先装线上 TBS 内核
  1. 然后打开debugx5.qq.com/, 勾选 信息-TBS Setting- 打开 TBS 内核 inspect 调试功能
  2. 剩下的和 Chrome 远程调试一样, 折腾完打开inspect/, 完毕

模拟元素状态

讲完远程调试, 就可以讲讲检查元素的具体办法. 有时候我们会为元素的 hover 设置一个样式, 但是调试的时候只有把鼠标移上去才能看见样式, 很麻烦.

Chrome 里可以直接模拟这种状态

:hov 中可以模拟各种伪类, .cls 中可以为元素动态添加/删除 class

性能分析

disable cache

刷新页面的时候, 如果想访问到最新的资源(或者模拟初次打开页面), 除了使用隐身模式外, 还可以直接勾上disable cache这项, 效果一样

模拟弱网条件 & 录制屏幕

Chrome 还允许模拟 2G/3G 等弱网环境, 而且为了方便调试, 还提供了录屏功能, 可以录制页面的整个加载过程, 方便检查分析

调试动画

和 Network 一样, 动画也提供了录屏, 模拟移动端 CPU 效果(主动降速), 分析动画成分功能

神奇功能

密码找回

浏览器里我们看到的密码都是打过马赛克的, 解码的方式也很简单—-只要把 input 的 type 类型从 password 随便改成其他值, 就可以看见我们输入的密码

源代码下载

有些公司安全意识不强, 代码发布上线的时候还是 debug 版本(知乎), 甚至连 source map 都带上了. 如果是 debug 版本, 我们可以用 React/Vue 开发者工具查看他们的页面结构, 如果是 source map 都有的话, 加个插件, 可以直接还原出页面的源码 => 点我看原理

Copy as Curl

如果要在命令行中模拟 Http 操作的话, 可以直接在 NetWork 面板中点右键, 这在手工抓接口数据的时候比较有用

扩展资料

苹果CMS自动定时采集_兮动人的博客-CSDN博客

mikel阅读(255)

来源: (2条消息) 苹果CMS自动定时采集教程_苹果cms自动采集_兮动人的博客-CSDN博客

进入苹果CMS10后台,点击–采集

在采集页面,点击左侧 自定义资源库,鼠标右键点击需要采集的内容,如 采集当天、采集本周 、采集所有,右键选择复制链接地址。

复制链接地址后台选择 系统–定时任务,选择添加,添加一个定时任务。

状态选为: 启用,名称和备注:可任写,附加参数:粘贴刚刚复制的链接,执行周期和执行时间的设置点击下面的全选按钮即可。

找到刚才设置的任务后鼠标右键测试复制链接地址

复制刚才的链接进入宝塔后台界面找到计划任务,按照图片所示添加任务,注意url地址填写刚复制的测试采集页面的地址,选择访问URL任务,按自己的需求填写执行周期参数后保存。

最后,点击执行后,网站即可实现自动定时采集,相关过程可在日志中进行查看。一个完整的定时采集任务也已设置完成。

————————————————
版权声明:本文为CSDN博主「兮动人」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_41684621/article/details/119459887