Redis实现消息队列 - 简书

mikel阅读(562)

来源: Redis实现消息队列 – 简书

Redis实现轻量级的消息队列与消息中间件相比,没有高级特性也没有ACK保证,无法做到数据不重不漏,如果业务简单而且对消息的可靠性不是那么严格可以尝试使用。

Redis实现消息队列

列表类型

队列

Redis中列表List类型是按照插入顺序排序的字符串链表,和数据结构中的普通链表一样,可以在头部left和尾部right添加新的元素。插入时如果键不存在Redis将为该键创建一个新的链表。如果链表中所有元素均被删除,那么该键也会被删除。

Redis List

Redis的列表List可以包含的最大元素数量为4294967295,从元素插入和删除的效率来看,如果是在链表的两头插入或删除元素将是非常高效的操作。即使链表中已经存储了数百万条记录,该操作也能在常量时间内完成。然后需要说明的是,如果元素插入或删除操作是作用于链表中间,那将是非常低效的。

Redis中对列表List的操作命令中,L表示从左侧头部开始插入和弹出,R表示从右侧尾部开始插入和弹出。

Redis提供了两种方式来做消息队列,一种是生产消费模式,另一种是发布订阅模式。

生产消费模式

生产消费模式会让一个或多个客户端监听消息队列,一旦消息到达,消费者马上消费,谁先抢到算谁的。如果队列中没有消息,消费者会继续监听。

PUSH/POP

Redis数据结构的列表List提供了pushpup命令,遵循着先入先出FIFO的原则。使用push/pop方式的优点在于消息可以持久化,缺点是一条消息只能被一个消费者接收,消费者完全靠手速来获取,是一种比较简陋的消息队列。

Redis的队列list是有序的且可以重复的,作为消息队列使用时可使用rpush/lpush操作入队,使用lpop/rpop操作出队。当发布消息是执行lpush命令,将消息从列表左侧加入队列。消息接收方执行rpop命令从列表右侧弹出消息。

如果队列空了怎么办呢?

如果队列空了,消费者会陷入pop死循环,即使没有数据也不会停止。空轮询不但消耗消费者的CPU资源还会影响Redis的性能。傻瓜式的做法是让消费者的线程按照一定的时间间隔不停的循环和监控队列,虽然可行但显然会造成不必要的资源浪费,而且循环周期也很难确定。

对于消费者而言存在一个问题,需要不停的调用rpop查看列表中是否有待处理的消息。每调用一次都会发起一次连接,势必造成不必要的资源浪费。如果使用休眠的方式让消费者线程间隔一段时间再消费,但这样做也有两个问题:

  • 如果生产者速度大于消费者消费的速度,消息队列长度会一直增大,时间久了会占用大量内存空间。
  • 如果休眠时间过长,就无法处理一些时效性的消息。如果休眠时间过短也会在连接上造成比较大的开销。

LPOP返回一个元素给客户端时会从List中将该元素移除,这意味着该元素只存在于客户端的上下文中。如果客户端在处理这个返回元素的过程中崩溃了,这个元素就会永远的丢失掉。

LPUSH/BRPOP

LPUSH BRPOP

使用brpopblpop实现阻塞读取

由于需要一直调用rpop/lpop才可以实现不停的监听且消费消息,为解决这个问题,Redis提供了阻塞命令brpop/blpop。使用brpop会阻塞队列,而且每次只会弹出一个消息,如果没有消息则会阻塞。

Redis列表List支持带阻塞的命令,生产者从列表左侧lpush加入消息到队列,消费者使用brpop命令从列表右侧弹出消息并设置超时时间,如果列表中没有消息则一直阻塞直到超时。这样做的目的在于减小Redis的压力。

对于Redis来说提供了blpop/brpop阻塞读,阻塞读在队列没有数据时会立即进入休眠状态,一旦数据到来则立即被唤醒,消息的延迟几乎为零。需要注意的是如果线程一直阻塞在那里,连接就会被服务器主动断开来减少资源占用,这时blpop/brpop会抛出异常,所以编写消费段时需要注意异常的处理。

BRPOP key [key ...] timeout

BLPOP/BRPOP 列表阻塞式弹出

当给定列表内没有任何元素可供弹出时,连接将被BRPOP命令阻塞,直到等待超时或发现可弹出元素为止。当给定多个key参数时,按参数key的先后顺序依次检查各个列表,弹出第一个非空列表的尾部元素。另外,BRPOP除了弹出元素的位置和BLPOP不同之处,其他表现一致。

列表的阻塞式弹出特点是如果列表中没有任务时,连接将会被阻塞。连接的阻塞存在一个超时时间,当超时时间设置为0时刻无限等待直到弹出消息。

Redis的PUSH/POP机制,利用Redis的列表list数据结构,生产者lpush消息,消费者brpop消息并设定超时时间以减少Redis压力。这种方案相对于发布订阅模式的好处是数据可靠性提高了,只有在Redis宕机且数据没有持久化的情况下会丢失数据。可以根据业务通过AOF和缩短持久化间隔来保证较高的可靠性,也可以通过多个客户端来提高消息速度。但相对于专业的消息队列中间件,发布订阅模式的状态过于简单(没有状态),而且没有ACK机制,消息取出后消费失败依赖于客户端记录日志或重新push到队列中。

Redis中实现生产者和消费者模型,可使用LPUSHRPOP来实现该功能。不过当列表为空时消费者就需要轮询来获取消息,这样会增加Redis的访问压力和消费者的CPU时间,另外很多访问也是无用的。为此Redis提供了阻塞式访问BRPOPBLPOP命令,消费者可以在获取数据时指定如果数据不存在阻塞的时间,如果在时限内获得数据则立即返回,如果超时还没有数据则返回NULL,可使用0表示一直阻塞。同时Redis会为所有阻塞的消费者以先后顺序排序。

使用Redis的列表来实现一个任务队列,开启两个程序,一个作为生产者使用LPUSH写队列,一个作为消费者使用RPOP读队列。由于消费者并不知道什么时候会有消息过来,所以消费者需要一直循环读取数据。两者的消息可以使用JSON进行封装协议传输。由于消费者在没有读到数据的情况下,会一直循环读取,对系统来说十分耗费资源,此时可利用Redis提供的阻塞读取命令BRPOP进行改进。使用BRPOP改进后,消费者不会一直循环读取,而是一直阻塞等到有消息过来时才读取。

发布订阅模式

发布订阅模式是一个或多个客户端订阅消息频道,只要发布者发布消息,所有订阅者都能收到消息,订阅者都是平等的。

PUB/SUB

Redis自带pub/sub机制即发布订阅模式,此模式中生产者producer和消费者consumer之间的关系是一对多的,也就是一条消息会被多个消费者所消费,当只有一个消费者时可视为一对一的消息队列。

发布订阅机制模型

首先发布者将消息发布到频道,客户端订阅频道后就能获得频道的消息。

发布订阅模式命令

  • psubscribe 订阅一个或多个符合给定模式的频道
  • publish 将消息发布到指定的频道
  • pubsub查看订阅与发布系统状态
  • pubsub channels pattern 列出当前的活跃频道
  • pubsub numsub channel-1 channel-n 获取给定频道的订阅者数量
  • pubsub numpat 获取订阅模式的数量
  • punsubscribe 指示客户端退订所有给定模式
  • subscribe 订阅给定的一个或多个频道的消息
  • unsubscribe 指示客户端退订给定的频道

实现

使用PHP+Redis实现消息队列

操作流程

  1. PHP接收请求和数据
  2. PHP将数据写入Redis队列(入队)
  3. Shell定时调用PHP读取队列数据并写入数据库(出队)

入队inqueue.php

$result = $redis->rpush("queue", json_encode($data));
if($result){
  echo "inqueue success";
}

出队 outqueue.php

#! /usr/bin/php
<?php
$result = $redis->lpop("queue");
if($result){
  $data = json_decode($result, true);
}

定时任务:process.sh

# 每分钟调用一次定时脚本
* * * * * /scripts/process.sh

定时脚本:process.sh

#! /bin/bash
#filename : process.sh
php /scripts/outqueue.php

出队采用死循环方式,感谢 行走平凡 的指正。

$ vim outqueue.php

while(true){
  $result = $redis->lpop("queue");
  if($result){
    $data = json_decode($result, true);
  }
}

作者:JunChow520
链接:https://www.jianshu.com/p/02a923fea175
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

C#实现消息队列_Shuai_Sir的博客-CSDN博客_c# 消息队列

mikel阅读(614)

来源: C#实现消息队列_Shuai_Sir的博客-CSDN博客_c# 消息队列

C#实现消息队列
1.认识消息队列
消息队列 (MSMQ Microsoft Message Queuing)是MS提供的服务,也就是Windows操作系统的功能,并不是.Net提供的。

2.什么是消息队列
消息队列一般简称为 MQ (Messges Queue),是指利用高效可靠的消息传递机制进行与平台无关的数据交流,并基于数据通信来进行分布式系统的集成,是在消息的传输过程中保存消息的容器。消息队列本质上是一个队列,而队列中存放的是一个个消息。

队列是一个数据结构,具有先进先出的特点。而消息队列就是将消息放到队列里,用队列做存储消息的介质。消息的发送放称为生产者,消息的接收方称为消费者。

消息队列由 Broker(消息服务器,核心部分)、Producer(消息生产者)、Consumer(消息消费者)、Topic(主题)、Queue(队列)和Message(消息体)组成。

3. 消息队列特点
消息队列有三个作用,分别是削峰、解耦和异步。

流量削峰:主要用于在高并发情况下,业务异步处理,提供高峰期业务处理能力,避免系统瘫痪。

假设系统只能处理1000个请求,但这时突然来了3000个请求,如果不加以限制就会造成系统瘫痪。使用消息队列做缓冲,将多余的请求存放在消息队列中,等系统根据自己处理请求的能力去消息队列去。

应用解耦:主要用于当一个业务需要多个模块共同实现,或者一条消息有多个系统需要对应处理时,只需要主业务完成以后,发送一条MQ,其余模块消费MQ消息,即可实现业务,降低模块之间的耦合。

假设某个服务 A 需要调用服务 B,但是服务 B 突然出现问题,这样会导致服务 A 也会出现问题。如果使用消息队列,当服务 A 执行完成之后,发送一条消息到队列中,服务 B 读取到这条消息,那么它立刻开始进行业务的执行。

异步通信:主业务执行结束后从属业务通过MQ,异步执行,减低业务的响应时间,提高用户体验。

假设有一个业务,要先执行服务 A ,然后服务 A 去调用服务 B ,当服务 B 完成之后,服务 A 调用服务 C,这个业务需要一步步走下去。当使用了消息队列之后,服务 A 完成之后,可以同时执行服务 B 和 服务 C ,这样就减低业务的响应时间,提高用户体验。

消息队列简单例子
一下是WIN10的操作步骤:
1.打开运行,输入”OptionalFeatures”,钩上Microsoft Message Queue(MSMQ)服务器。

2. 消息队列分为以下几种,每种队列的路径表示形式如下:

公用队列 MachineName\QueueName

专用队列 MachineName\Private$\QueueName

日记队列 MachineName\QueueName\Journal$

计算机日记队列 MachineName\Journal$

计算机死信队列 MachineName\Deadletter$

计算机事务性死信队列 MachineName\XactDeadletter$

这里的MachineName可以用 “.”代替,代表当前计算机

3.打开“此电脑”–>左上角点击“计算机”–>点击“管理”

注意:如果没看到“消息队列”这个选项,那就是这东西在这电脑上还没使用过,没关系,一会编程的时候创建就会显示出来了(我印象中一开始我也没有这个选项,写了代码后就有了)

知道这东西在哪里后,开始进入正题。C#中使用消息队列需要添加新的引用System.Messaging

4.添加引用后在代码里添加命名空间就可以开始了

这里我们新建一个控制台程序:

存入数据:

static void Main()
{
//存
string msgPath = “.\\Private$\\MyMsg”;
//指定路径一个名称为“MyMsg”的专用队列的路径字符串
string studentName = “hello word”;
// 要写入消息消息队列的信息
SendMsg(msgPath, studentName);
Console.WriteLine(“发送成功” + studentName);
Console.ReadLine();
}
/// 存
/// </summary>
/// <param name=”mQPath”></param>
/// <param name=”studentName”></param>
public static void SendMsg (string mQPath,string studentName)
{
//先判断这个消息队列是否存在,如果存在则直接实例化对象,如果不存在则创建该消息队列
MessageQueue mq = (MessageQueue.Exists(mQPath)) ? (new MessageQueue(mQPath)) : (MessageQueue.Create(mQPath));
mq.Send(studentName);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
读取数据:

static void Main()
{
//读取数据
string msgPath = “.\\Private$\\MyMsg”;
ReceiveMsg(msgPath);//异步读取
// ReceiveMsgT(msgPath); 同步读取
Console.ReadLine();

}
///异步读取
private static void ReceiveMsg(string msgPath) {
if (MessageQueue.Exists(msgPath))
{
MessageQueue mq=new MessageQueue(msgPath);
mq.ReceiveCompleted += new ReceiveCompletedEventHandler(ReceiveMethon);
mq.BeginReceive(MessageQueue.InfiniteTimeout);
Console.WriteLine(“异步已接收数据”);
}

}
static readonly XmlMessageFormatter f=new XmlMessageFormatter(new Type[] { typeof(string)}); //格式
private static void ReceiveMethon(object sender,ReceiveCompletedEventArgs e) {
Message m=e.Message;
m.Formatter = f;//转换格式
Console.WriteLine(m.Body.ToString());
}
//同步读取
private static void ReceiveMsgT(string msgPath) {
if (MessageQueue.Exists(msgPath))
{
MessageQueue mq= new MessageQueue(msgPath);
Message m = mq.Receive();
m.Formatter= f;//格式转换
Console.WriteLine(m.Body.ToString());
Console.WriteLine(“同步已接受数据”);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
这里简单说一下两种方式读取的区别,同步读取数据的话,会堵塞当前线程,直到读到数据为止才继续运行之后的程序,异步读取的话就避免了这个问题。如果读取的数据量小、速度快,为了编写代码方便可选择同步读取,如果读取的数据量大、速度慢,则建议使用异步读取。

消息队列有点像我们常见的数据库,能进行数据的存取,但是与数据库其中一点不同的就是消息队里中的消息被读取后就会被销毁,不需要我们手动删除。
————————————————
版权声明:本文为CSDN博主「Shuai_Sir」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Shuai_Sir/article/details/127899893

c#之Redis队列 - wolfy - 博客园

mikel阅读(500)

来源: c#之Redis队列 – wolfy – 博客园

摘要

这两天一直在考虑redis队列:一个生产者,多个消费者的情况,这里弄了一个demo进行测试。

一个例子

关于如何引用Redisclient 可以参考之前的这篇文章:c#之Redis实践list,hashtable

生产者一个线程,然后开启多个线程用来消费数据。

代码如下:

复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NServiceKit.Redis;
using NServiceKit.ServiceClient;
using System.Threading;
namespace RedisDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            Thread thread = new Thread(run);
            thread.Start();

            Thread[] threads = new Thread[10];
            for (int i = 0; i < threads.Length; i++)
            {
                threads[i] = new Thread(Pull);
                threads[i].Start();
            }

            Console.Read();
        }
        private static void Pull()
        {
            IRedisClientFactory factory = RedisClientFactory.Instance;
            using (IRedisClient client = factory.CreateRedisClient("192.168.1.37", 6379))
            {
                client.Password = "wolfy";
                while (true)
                {
                    if (client.GetListCount("Myqueue") > 0)
                    {
                        string result = client.DequeueItemFromList("Myqueue");
                        //如果获取的内容为空,将当前线程挂起1s
                        if (string.IsNullOrEmpty(result))
                        {
                            Thread.SpinWait(1000);
                        }
                        else
                        {
                            Console.WriteLine("Threadid:" + Thread.CurrentThread.ManagedThreadId.ToString() + "\t" + result);
                        }
                    }
                    else
                    {
                        //如果当前队列为空,挂起1s
                        Thread.SpinWait(1000);
                    }
                }
            }

        }
        private static void run()
        {
            IRedisClientFactory factory = RedisClientFactory.Instance;
            using (IRedisClient client = factory.CreateRedisClient("192.168.1.37", 6379))
            {
                client.Password = "wolfy";
                while (true)
                {
                    client.EnqueueItemOnList("Myqueue", DateTime.Now.ToString());
                }
            }


        }
    }
}
复制代码

测试

总结

关于队列有考虑过rabbitmq,msmq等,考虑到公司有现成的redis服务器,所以就考虑使用redis队列。既然实现了一生产者,多个消费者,那么接下来,想实现一种多队列,然后设置队列的容量,通过容量,生产者在入队的时候,根据队列是否满,然后对数据进行分发的情况。

Windows电脑反编译微信小程序含分包详细操作 - 掘金

mikel阅读(592)

来源: Windows电脑反编译微信小程序含分包详细操作 – 掘金

现在网上也有很多关于小程序反编译的教程,随时间的流逝或许随着微信的更新,有出现编译不成功的现象。

本篇文章总结一下最新的编译过程,已成功获得小程序源码(有分包的小程序)

环境准备

1、 node 环境准备

下载链接:nodejs.org/en/

image-20210901154759757

安装后将nodejs设置为环境变量。
打开cmd,测试是否安装成功:在命令行输入node -v 出现版本号说明已经安装成功。

2、反编译工具

项目地址来自于: github.com/xuedingmiao…

通过下面链接下载:

链接:pan.baidu.com/s/1p-wnX-mX…
提取码:z06a

下载下来解压到某个位置就可以了,一定要通过网盘下载,里面有解密包的工具和安装后的npm环境,直接使用即可

具体操作

1、微信PC获取小程序

在通过微信PC打开小程序前,我们最好先找到缓存到本地的小程序包路径,一般都是在 微信PC安装目录\WeChat Files\WeChat Files\Applet

比如我的就是安装到 D盘根目录的,所以路径为: D:\WeChat\WeChat Files\WeChat Files\Applet

image-20210901160649022

上图中每个文件夹代表一个小程序,一般最新打开的小程序都是在第一个,如果不确定可以排序一下修改日期

找到路径了我们就可以用微信PC打开小程序了,打开后就会发现当前目录新增了一个文件夹,里面存放的就是加密后的小程序包

image-20210901161153138

2、解密包

刚获取到的包我们还不能进行反编译,必须要通过 解密软件 修改一下才能反编译

image-20210901161556010

本篇就演示一个主包和一个分包反编译的过程就可以了,先通过解密软件修改一下主包

image-20210901161936026

解密的主包自动到 wxpack 这个包里面来了,同样的步骤解密一个分包,下图是我解密好的两个,并且修改了一下名称,好区分

image-20210901162219406

3、反编译

进入 wxpack 的同级目录 wxappUnpacker-master,在路径栏输入 cmd 自动打开当前目录的 命令窗口了

image-20210901162538413

先反编译一下主包,把反编译后的文件夹放到 wxpack 同级目录中

node wuWxapkg.js ..\wxpack\master-app.wxapkg

复制代码

image-20210901163011475

再反编译分包,把反编译后的文件夹放到 wxpack 同级目录中

node wuWxapkg.js -s=..\ ..\wxpack\_pages_app.wxapkg

复制代码
  • -s 表示分包
  • 第一个..\ 表示输出位置
  • ..\wxpack\_pages_app.wxapkg 需要反编译的分包位置

image-20210901163627239

好了剩下的就是自己组合一下包的架构目录了~~~~

如果本篇文章给予了您一点帮助,还请点个赞收藏一下~~

谢谢您的支持!!!

作者:LexKun
链接:https://juejin.cn/post/7002889906582192158
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

[node.js]PC端微信小程序包解密_基尔酱的博客-CSDN博客_pc微信小程序一键解密

mikel阅读(519)

来源: [node.js]PC端微信小程序包解密_基尔酱的博客-CSDN博客_pc微信小程序一键解密

小程序的源码在哪里
PC端打开过的小程序会被缓存到本地微信文件的默认保存位置,可以通过微信PC端=>更多=>设置查看:

 

进入默认保存位置下的/WeChat Files/WeChat Files/Applet文件夹,可以看到该目录下有一系列前缀为wx的文件(文件名其实是小程序的appid),这些就是我们打开过的小程序啦:

 

进入其中某个小程序的文件夹,我们可以看到一个名字为一串数字的文件夹。点进这个文件夹, 就可以看到一个__APP__.wxapkg文件,也就是小程序对应的代码啦:

 

然而,当我们打开这个文件之后却发现是这样的:

 

WTF 这能看出来个🔨。很明显,这个文件是经过加密的,需要解密才能看到我们想看到的东西。

 

PC端小程序是怎么被加密的
这里参考了一位大佬用Go语言写的PC端wxapkg解密代码。整理一下的话,加密流程是这样的:

 

首先将明文代码在第1024字节处一分为二,前半部分使用CBC模式的AES加密,后半部分则直接进行异或。最后,将加密后的两节拼接起来,并在最前边写入固定的字符串:”V1MMWX”。

所以,我们打开__APP__.wxapkg文件看到的就是加密后的代码,如果想还原回去的话,需要从后往前逐步推回去。

 

解密思路
预处理
我们使用node.js去写一个解码的程序。根据上边加密的流程,我们首先读取加密文件,把前6个字节的固定字符串去除。由于AES加密和异或前后数据的位数是相同的,我们可以据此获取到加密后的头部1024字节和加密后的尾部部分:

const fs = require(‘fs’).promises;

const buf = await fs.readFile(pkgsrc); // 读取原始Buffer
const bufHead = buf.slice(6, 1024 + 6);
const bufTail = buf.slice(1024 + 6);
加密后的头部部分
为了得到这1024个字节的明文,我们需要知道AES加密的初始向量iv,以及一个32位的密钥。已知16字节的初始向量iv是字符串:“the iv: 16 bytes”,我们接下来需要计算出这个由pbkdf2算法导出的32位的密钥。

pbkdf2(Password-Based Key Derivation Function)是一个用来生成密钥的函数,它使用一个伪随机函数,将原文密码和salt作为输入,通过不断的迭代得到密钥。在crypto库中,pbkdf2函数是这样的:

const crypto = require(‘crypto’);

crypto.pbkdf2(password, salt, iterations, keylen, digest, callback)

其中参数分别是:原文密码、盐值、迭代次数、密钥长度、散列算法、回调函数。已知salt是”saltiest”,原文密码为微信小程序的id(也就是wx开头的那个文件夹名),迭代次数为1000,散列算法为sha1。因此,我们可以写出计算密钥的代码:

crypto.pbkdf2(wxid, salt, 1000, 32, ‘sha1’, (err, dk) => {
if (err) {
// 错误
}
// dk即为计算得到的密钥
})

密钥和初始向量iv都有了之后,我们可以开始对密文进行解密了。AES加密算法是一种非对称加密算法,它的密钥分成公开的公钥和只有自己知道的私钥,任何人都可以使用公钥进行加密,但是只有持有私钥的人解密得到明文。

小程序使用的加密算法是CBC(Cipher Block Chaining, 密码分组链接)模式的AES,也就是它在加密的时候,首先把明文进行分块,然后将每一块与前一块加密后的密文进行异或,再使用公钥进行加密,得到每一块的密文。对于第一块明文,由于它不存在前一块明文,因此它会与初始向量iv进行异或,再进行公钥加密。在实现的时候,我们只需要调用crypto提供的解密函数就可以啦。

我们知道,AES算法根据密钥长度的不同有AES128, AES192和AES256。回顾上边,我们的密钥是32字节,也就是256位的,因此显然我们应该使用的是AES256。综上,我们可以写出来解密的代码:

const decipher = crypto.createDecipheriv(‘aes-256-cbc’, dk, iv);
const originalHead = Buffer.alloc(1024, decipher.update(bufHead));

其中originalHead就是我们要的前1024字节的明文啦。我们可以打印出来看看:

 

嗯…… 有那么点意思了。

加密后的尾部部分
这一部分就很简单啦。由于异或运算是具有自反性的,因此只需要简单的判断一下小程序id的位数获得异或的xorKey,再把它与密文进行异或,就可以得到原文了:

const xorKey = wxid.length < 2 ? 0x66 : wxid.charCodeAt(wxid.length – 2);
const tail = [];
for(let i = 0; i < bufTail.length; ++i){
tail.push(xorKey ^ bufTail[i]);
}
const originalTail = Buffer.from(tail);

将头部部分的明文与尾部部分的明文进行拼接,再以二进制形式写入文件,就可以得到最终的明文啦。

 

再漂亮点
根据上边的描述,我们可以把我们整个的解密过程封装成一个黑盒子:

 

commander
我们可以使用commander库让程序直接从命令行读取小程序的id和密文包。commander是一个nodejs命令行界面的解决方案,可以很方便的定义自己的cli命令。比如说对于下面这一串代码:

 

const program = require(‘commander’);

program
.command(‘decry <wxid> <src> [dst]’)
.description(‘解码PC端微信小程序包’)
.action((wxid, src, dst) => {
wxmd(wxid, src, dst);
})

program.version(‘1.0.0’)
.usage(“decry <wxid> <src> [dst]”)
.parse(process.argv);

我定义了一个”decry <wxid> <src> [dst]”的命令,其中尖括号代表必选参数,方括号代表可选参数。description内是关于这个命令的描述文本,action则是执行这段命令。在控制台使用node执行代码之后,可以看到如下界面:

 

于是我们就可以根据提示,输入参数进行解密啦。commander.js的中文文档在这里。

chalk
为了让我们的控制台多一抹颜色,我们可以使用chalk.js来美化输出。chalk的基本用法也比较简单:

const chalk = require(‘chalk’);

console.log(chalk.green(‘绿了’))
这样我们就可以在黑白的控制台上填上一抹绿色,替大熊猫实现梦想:

 

除此之外,我们还可以使用es6的字符串标签模板更方便的使用chalk。具体的参考chalk官方文档吧。

 

源代码
代码发布到github和gitee啦,可以给大家参考一下下~

github点这里, gitee点这里

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

宝塔 workerman wss 反向代理 socket合法域名 小程序聊天 去掉端口_我是高手高手高高手的博客-CSDN博客_socket合法域名

mikel阅读(609)

来源: 宝塔 workerman wss 反向代理 socket合法域名 小程序聊天 去掉端口_我是高手高手高高手的博客-CSDN博客_socket合法域名

一、带端口的配置(不在这里说配置,有其它说明教程)
小程序中聊天,需要设置socket合法域名:wss://www.xxx.com:2000

二、不带端口的设置
宝塔配置域名,处理wss通信服务
1、去域名服务商添加域名 wss.xxx.com
2、服务器申请SSL证书,并下载
3、宝塔添加域名,选纯静态即可

4、宝塔对应域名的设置,配置SSL证书,把刚刚下载的证书包打开,填充对应的内容,保存

5、宝塔对应域名的设置->配置文件,在server{ }内结尾处添加下面的内容

# 反向代理 转移到本地的websocket地址,wss://127.0.0.1:2000 ,对应 wss://card.xxxx.com:2000
location / {
proxy_redirect off;
proxy_pass https://127.0.0.1:2000;
proxy_set_header Host $host;
proxy_set_header X-Real_IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr:$remote_port;
proxy_http_version 1.1;
# 添加wss协议头专用的字段
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection “upgrade”;
}
主要是这两行:

proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection “upgrade”;

参考:

workerman配置

https://blog.csdn.net/haibo0668/article/details/106681142

https://blog.csdn.net/haibo0668/article/details/108245320

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

小程序中常用的标签_BUG创建者的博客-CSDN博客_小程序标签

mikel阅读(621)

来源: 小程序中常用的标签_BUG创建者的博客-CSDN博客_小程序标签

小程序中的常用标签也是就是我们常用的这些

view(视图容器)、rich-text(富文本)、swiper(滑块视图容器)、icon(图标)、text(文字)、progress(进度条)、button(按钮)、form(表单)、input(输入框)、checkbox(多项选择器)、radio(单项选择器)、picker(列表选择器)、slider(滚动选择器)、switch(开关选择器)、textarea(多行输入框)、label(标签)、navigator(应用链接)、audio(音频)、image(图片)、video(视频)、camera(系统相机)、map(地图)、scroll-view(可滚动视图容器)、picker-view(内嵌列表选择器)、canvas(画布)movable-area(可移动区域)、movable-view(可移动的视图容器)、cover-view(覆盖视图)、cover-image(覆盖图片)、functional-page-navigator(跳转到插件功能页)、live-player(实时音视频播放)、live-pusher(实时音视频录制)

1、view
div和view都是盒模型,默认display:block。
盒模型在布局过程中,一般推荐display:flex的写法,配合justify-content:center;align-items:center;的定义实现盒模型在横向和纵向的居中。
2、text
除了text文本节点以外的其他节点都无法长按选中。。
截止到0.10.102800的开发者工具text支持嵌套text,不过有class的text会被面板过滤,样式不影响。
3、 icon
icon可以直接用微信组件默认的图标,默认是iconfont格式的,从WeUI那边沿袭过来的一种做法。
自定义的icon推荐svg sprite格式或者iconfont。
目前来看,市面上还没有很好的自动合并单个svg为svg sprite的工具,需要手动拼图。
4、input
input 的类型,有效值:text, number, idcard, digit, time, date 。
input不需要设置line-height或padding来纵向居中,默认placeholder的文字是居中的。
小程序把checkbox和radio都单独做成了组件,默认的input只支持输入文本。
上传文件在小程序里需要调用chooseImage事件完成;
5、picker
picker默认支持普通、日期和时间三种选择器
picker通过bindchange事件来调取range中自定义的数据数据,并展示到页面中,调用的是系统原生的select。
这里小程序废弃了select组件,考虑到的是这个组件的交互不适合移动场景,最终用picker代替了。
选择 北京 上海
6、 navigator
navigator支持相对路径和绝对路径的跳转,默认是打开新页面,当前页面打开需要加redirect;
navigator仅支持5级页面的跳转;
navigator不可跳转到小程序外的链接地址;
在小程序开发工具里,默认打开新页面,工具左上角有返回按钮。加上redirect,当前页打开,不出现返回按钮。
7、image
小程序的image与HTML5的img最大的区别在于:小程序的image是按照background-image来实现的。
默认image的高宽是320*240。必须通过样式定义去覆盖这个默认高宽,auto在这里不生效。(开发者说这样设置的原因是:如果设置 auto ,页面布局会因为图片加载的过程有一个闪的现象(例如高度从 0 到 height ),所以要求一定要设置一个宽度和高度。)
最新的api支持获取图片的高宽。不过这里返回的高宽是px单位,不支持屏幕自适应;
图片包括三种缩放模式scaleToFill、aspectFit、aspectFill和9种裁剪模式,三种缩放模式的实现原理对应如下:

background-size:100% 100%;//不保持纵横比缩放图片,使图片的宽高完全拉伸至填满 image 元素

background-size:contain;//保持纵横比缩放图片,使图片的长边能完全显示出来。也就是说,可以完整地将图片显示出来。

background-size:cover;//保持纵横比缩放图片,只保证图片的短边能完全显示出来。也就是说,图片通常只在水平或垂直方向是完整的,另一个方向将会发生截取。
8、button
额外补充下button的实现方式,button的边框是用:after方式实现的,用户如果在button上定义边框会出现两条线,需用:after的方式去覆盖默认值。不过这个应该会在最近的开发者工具中修复。

小程序不支持button:active这种样式的写法,button的点击态通过.button-hover{}的样式覆盖,也可修改hover-class为自定义的样式名。
9、css3动画
最新版的开发工具已经支持transition和keyframes动画,不用js苦哈哈的写动画队列了。
除了官方公布的小程序的组件之外,有一些标签比如,span、em、strong、b也是支持的,只是官方并不推荐使用。

小程序 a标签_微信小程序设计你必须知道的规范总结(内附资源)_weixin_39546092的博客-CSDN博客

mikel阅读(631)

来源: 小程序 a标签_微信小程序设计你必须知道的规范总结(内附资源)_weixin_39546092的博客-CSDN博客

作者: 阿倩

前不久做了一个微信小程序的项目,总结了一些小程序的设计规范,如果对小程序不够了解就开始设计,设计完程序小哥肯定就要暴走了~,为了挽留程序小哥,我吐血整理了这篇设计规范,还给大家准备了新版小程序控件(一定要看到底哦~)。

本篇纲要:
小程序尺寸
官方小程序菜单
标题导航栏设计(Title bar)
标签分页导航设计(Tab bar)
内容设计
加载样式
全局操作反馈
一、小程序尺寸

程序开发尺寸是rpx(responsive pixel),可以实现一稿适配所有屏幕尺寸,小程序编译后,rpx会做一次px换算。换算是以375个物理像素为基准,也就是在一个宽度为375物理像素的屏幕下,1rpx = 1px。这里了解就行,设计稿尺寸375px或750px都没问题。

二、官方小程序菜单

a. 全局性的,不可自定义,位置固定

官方小程序菜单位于所有界面(包括小程序内嵌的网页和插件),且位置固定,开发者不可对其内容进行自定义,只能选择深浅两种配色方案,设计时要预留出该位置区域,若要在小程序菜单附近放置可交互元素,要注意与小程序菜单不冲突,可识别,易操作。

b. 常见的三种状态

小程序菜单常见的三种状态:全局菜单、调用录音、获取地理位置。

三、标题导航栏设计(Title bar)

标题导航样式设计时要与微信小程序菜单样式保持一定差异性,便于区分。另外标题导航的背景可以自定义背景颜色或图片(这里注意给开发小哥时需要把图切出来),还可以设置不透明度,是不是很帅。

二级页面要设计有返回键,要让用户知道自己在哪,怎么回去。如果页面层级太多有个一键返回首页按钮在体验上会友好很多,如知乎二级页面。

四、标签分页导航设计(Tab bar)
标签分页导航分为:顶部标签导航和底部标签导航,官方建议标签数量为2-5个。不过对顶部导航来说关系不大可以根据具体项目需要来设计,底部导航最好还是按照规范来。

a. 顶部标签导航

顶部标签导航可以自定义颜色和样式,可以点击也支持沿X轴滑动,也就是说点击不是顶部标签栏唯一的交互方式。

b. 底部标签导航

底部标签导航提供了四种不同图形的设计规范,满足了圆形、方形、高矩形和宽矩形四种形状类型。根据规范设计可以更方便进行视觉修正,保持图标的统一性。数量上3-4个最常见。或者直接去掉底部标签导航,如微信读书、36氪。

五、内容设计
内容部分和app设计差不多,app设计规范在这里也适用。

要点:

a. 小程序弹窗优先级并不是最高的

官方小程序菜单的层级优先级是最高的。即使在app中优先级最高的弹窗到这里层级也得在小程序菜单之下,即便沉浸式体验也不例外。而且标题栏和底部标签栏优先级也是在弹窗之上的。

b. 视频位置

小程序内视频是固定位置浮动的,滑动页面视频不会沿Y轴移动,如优酷视频。不过也有技术比较厉害的突破了这一限制支持视频Y轴滑动,如腾讯视频、爱奇艺。测试了六款主流视频app,全都是固定位置。所以,实在不好搞就不要难为程序小哥了。

六、加载样式

要点:

若载入时间较长,应提供取消操作,并使用进度条显示载入的进度。
载入过程中,应保持动画效果 ; 无动画效果的加载很容易让人产生该界面已经卡死的错觉。
不要在同一个页面同时使用超过1个加载动画。
模态的加载样式将覆盖整个页面的,由于无法明确告知具体加载的位置或内容将可能引起用户的焦虑感,应谨慎使用。
“页面内加载反馈”可以自定义,其余加载样式微信提供统一标准,无需自行开发。
七、全局操作结果反馈

要点:

图标型弹出提示适用于轻量级的成功提示,1.5秒后自动消失,并不打断流程,对用户影响较小,适用于不需要强调的操作提醒,例如成功提示。
文字型弹出提示适用于需要轻量化地用文字解释当前状态或提醒不严重的错误。1.5秒后自动消失,不打断流程,对用户影响较小。
对于需要用户明确知晓的操作结果状态可通过模态对话框来提示,并可附带下一步操作指引。
对于操作结果已经是当前流程的终结的情况,可使用操作结果页来反馈。

全文总结

a. 不可修改部分:

官方小程序菜单(含有三种状态)
小程序加载样式(除“页面内加载反馈”)
b. 可修改部分概要

标题导航栏(Title bar)可以自定义背景,二级页面要有返回键
标签分页导航(Tab bar):顶部标签导航交互方式不唯一,底部标签导航个数控制在2-5个,或可完全去除。
官方小程序菜单层级优先级最高,标题栏和标签栏优先级高于弹窗。
视频位置固定,可更改,但技术要求较高。
页面内加载反馈可自定义。
熟悉了这些设计规范,相信你对小程序设计已经胸有成竹了。为了帮助小伙伴们提高设计效率,给大家整理了新版微信小程序控件,关注公众号「UI设计进阶」后台回复”小程序“即可获得。

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

微信小程序怎么一排放置多个view_#Amark的博客-CSDN博客_微信小程序怎么让两个view并排

mikel阅读(668)

来源: 微信小程序怎么一排放置多个view_#Amark的博客-CSDN博客_微信小程序怎么让两个view并排

view布局效果图:

 

 

.wxml

<view class=”content”>
<view class=’t1′></view>
<view class=’t2′></view>
<view class=’t1′></view>
<view class=’t2′></view>
<view class=’t2′></view>
<view class=’t1′></view>
<view class=’t2′></view>
<view class=’t2′></view>
<view class=’t2′></view>
<view class=’t1′></view>
<view class=’t2′></view>
<view class=’t1′></view>
</view>

<view class=”content”>
<view class=’t2′></view>
<view class=’t2′></view>
<view class=’t2′></view>
<view class=’t2′></view>
<view class=’t2′></view>
<view class=’t2′></view>
<view class=’t2′></view>
<view class=’t2′></view>
<view class=’t1′></view>
<view class=’t1′></view>
<view class=’t1′></view>
<view class=’t1′></view>
</view>
.wcss

.content {
display: flex;
flex-direction: row;
margin-top: 20rpx;
margin-left: 70rpx;
}

.t1{
height: 20px;
width: 20px;
background-color: red;
margin-left: 10rpx;
}

.t2{
height: 20px;
width: 20px;
background-color: grey;
margin-left: 10rpx;
}

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

微信小程序中的图形验证码 - 拉风的小学生 - 博客园

mikel阅读(671)

来源: 微信小程序中的图形验证码 – 拉风的小学生 – 博客园

可以在utils中新建一个mcaptcha.js

代码如下:

module.exports = class Mcaptcha {
constructor(options) {
this.options = options;
this.fontSize = options.height * 3 / 6;
this.init();
this.refresh();
}
init() {
this.ctx = wx.createCanvasContext(this.options.el);
this.ctx.setTextBaseline(“middle”);
this.ctx.setFillStyle(this.randomColor(180, 240));
}
refresh() {
var code = ”;
var txtArr = [‘a’, ‘b’, ‘c’, ‘d’, ‘e’, ‘f’, ‘g’, ‘h’, ‘i’, ‘j’, ‘k’, ‘l’, ‘m’, ‘n’, ‘o’, ‘p’, ‘q’,’r’, ‘s’, ‘t’, ‘u’, ‘v’, ‘w’, ‘x’, ‘y’, ‘z’, ‘A’, ‘B’, ‘C’, ‘D’, ‘E’, ‘F’, ‘G’, ‘H’, ‘I’, ‘J’, ‘K’, ‘L’, ‘M’, ‘N’, ‘O’,’P’, ‘Q’, ‘R’, ‘S’, ‘T’, ‘U’, ‘V’, ‘W’, ‘X’, ‘Y’, ‘Z’,0,1,2,3,4,5,6,7,8,9]
for(var i=0;i<4;i++){
code += txtArr[this.randomNum(0, txtArr.length)];
}
this.options.createCodeImg = code;
let arr = (code + ”).split(”);
if (arr.length === 0) {
arr = [‘e’, ‘r’, ‘r’,’o’,’r’];
};
let offsetLeft = this.options.width * 0.6 / (arr.length – 1);
let marginLeft = this.options.width * 0.2;
arr.forEach((item, index) => {
this.ctx.setFillStyle(this.randomColor(0, 180));
let size = this.randomNum(24, this.fontSize);
this.ctx.setFontSize(size);
let dis = offsetLeft * index + marginLeft – size * 0.3;
let deg = this.randomNum(-30, 30);
this.ctx.translate(dis, this.options.height*0.5);
this.ctx.rotate(deg * Math.PI / 180);
this.ctx.fillText(item, 0, 0);
this.ctx.rotate(-deg * Math.PI / 180);
this.ctx.translate(-dis, -this.options.height * 0.5);
})
for (var i = 0; i < 4; i++) {
this.ctx.strokeStyle = this.randomColor(40, 180);
this.ctx.beginPath();
this.ctx.moveTo(this.randomNum(0, this.options.width), this.randomNum(0, this.options.height));
this.ctx.lineTo(this.randomNum(0, this.options.width), this.randomNum(0, this.options.height));
this.ctx.stroke();
}
for (var i = 0; i < this.options.width / 4; i++) {
this.ctx.fillStyle = this.randomColor(0, 255);
this.ctx.beginPath();
this.ctx.arc(this.randomNum(0, this.options.width), this.randomNum(0, this.options.height), 1, 0, 2 * Math.PI);
this.ctx.fill();
}
this.ctx.draw();
}
validate(code){
var code = code.toLowerCase();
var v_code = this.options.createCodeImg.toLowerCase();
console.log(code)
console.log(v_code.substring(v_code.length – 4))
if (code == v_code.substring(v_code.length – 4)) {
return true;
} else {
return false;
}
}
randomNum(min, max) {
return Math.floor(Math.random() * (max – min) + min);
}
randomColor(min, max) {
let r = this.randomNum(min, max);
let g = this.randomNum(min, max);
let b = this.randomNum(min, max);
return “rgb(” + r + “,” + g + “,” + b + “)”;
}
}
在对于页面的js中引入mcaptcha.js
var Mcaptcha = require(‘../../../utils/mcaptcha.js’);
/**
* 生命周期函数–监听页面初次渲染完成
*/
onReady: function () {
this.mcaptcha=new Mcaptcha({
el: ‘canvas’,
width: 80,
height: 35,
createCodeImg: “”
});
},
。。。
//刷新验证码
onTap(){
this.mcaptcha.refresh();
},
//验证验证码
var res = this.mcaptcha.validate(this.data.imgCode);
if (this.data.imgCode == ” || this.data.imgCode==null) {
toast.showToast({ title: ‘请输入图形验证码’ })
return;
}
if (!res) {
toast.showToast({ title: ‘图形验证码错误’ })
return;
}
wxml页面:
<input type=”text” id=”code” name=”codeImg” placeholder-class=’C3′ bindinput=’codeImg’ placeholder=”请输入图形验证码” maxlength=”4″ value='{{imgCode}}’/>
<view style=’position:relative;’ bindtap=”onTap”>
<canvas style=”width:160rpx;height:70rpx;position:absolute;right:0rpx;bottom:10rpx;text-align: center;z-index:9999;” canvas-id=”canvas”></canvas>
</view>
效果:
参考:https://www.jianshu.com/p/064a80a3561a