分类 其它 下的文章

SQL Server 中的 NUL 设备/NIL设备


在 SQL Server 中,有一个特殊的设备叫做 NUL(注意,不是 NULL),它类似于文件系统中的“黑洞”。NUL 设备类似于 Linux 系统中的 /dev/null,所有写入到 NUL 的数据都会被直接丢弃。

我们可以利用这个特性,在不需要实际生成备份文件的情况下进行测试或模拟备份操作。


完整备份的常规操作
通常情况下,我们对某个数据库进行完整备份时,可以使用以下最简单的 SQL 语句:

BACKUP DATABASE AdventureWorks2012 TO DISK = N'AdventureWorks2012.BAK'

执行后,SQL Server 会显示备份的结果

已为数据库 'AdventureWorks2012',文件 'AdventureWorks2012' (位于文件 1 上)处理了 24328页。
已为数据库
'AdventureWorks2012',文件 'AdventureWorks2012_log' (位于文件 1 上)处理了 2页。BACKUP DATABASE 成功处理了 24330 页,花费 4.103 秒(46.326 MB/秒)。



备份文件将存储在实例的默认备份路径中。可以通过以下 SQL 查询来确认该路径:

DECLARE @BackupDest VARCHAR(200)EXEC master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE', N'Software\Microsoft\MSSQLServer\MSSQLServer', N'BackupDirectory', @BackupDestOUTPUT;SELECT @BackupDest;



使用 NUL 设备进行备份
如果不想实际生成备份文件,但需要测试备份操作或了解备份时间等,可以将备份数据写入 NUL 设备。
SQL Server 支持两种方式将备份写入 NUL 设备:使用 NUL 或 NUL:。

BACKUP DATABASE AdventureWorks2012 TO DISK = N'NUL' 
GO
BACKUP DATABASE AdventureWorks2012 TO DISK = N'NUL:'
GO

执行后,SQL Server 同样会显示备份的结果

已为数据库 'AdventureWorks2012',文件 'AdventureWorks2012' (位于文件 1 上)处理了 24328页。
已为数据库
'AdventureWorks2012',文件 'AdventureWorks2012_log' (位于文件 1 上)处理了 2页。BACKUP DATABASE 成功处理了 24330 页,花费 1.499 秒(126.803 MB/秒)。


NUL 设备还可以通过备份判断磁盘的读吞吐量,即使你备份至多个NUL设备。备份至NUL设备主要是读取数据,写数据并没有写入具体的磁盘中。

BACKUP DATABASE AdventureWorks2012 TO DISK = N'NUL',DISK = N'NUL',DISK = N'NUL',DISK = N'NUL' ,DISK = N'NUL'



总结
备份至 NUL 设备的主要用途包括:
1、备份吞吐量验证:NUL 设备可以用来测试数据库的
读 IOPS 性能
,帮助验证备份时的读写吞吐量。
2、没有日志备份导致磁盘满:如果日志文件长期未备份而变得非常大导致磁盘空间不足,可以通过将
日志备份写入 NUL 设备
来快速释放日志空间,之后再进行日志文件收缩或者设置简单模式收缩文件。
3、在高可用配置:特别是 SQL Server2016或者以上版本, AlwaysOn AG 场景中,使用
automatic seeding 功能
配置可用性数据库前需要备份,可将数据库备份至NUL设备后再初始化同步。
4、COPY_ONLY 备份:在不希望中断备份链(不truncate日志文件里的日志)但需要在生产环境中进行测试时,可以使用
COPY_ONLY 参数
来创建临时备份,避免对生产环境的备份链产生影响。

需要注意的是,备份至 NUL 设备时的读操作对性能的影响与实际备份相似,因此在生产环境中进行测试时,仍需谨慎评估其影响。



参考文章
https://www.modb.pro/db/452106
https://blog.csdn.net/lnotime/article/details/104847946
https://cloud.tencent.com.cn/developer/information/%E6%AD%A3%E5%9C%A8%E8%BF%98%E5%8E%9F%E6%95%B0%E6%8D%AE%E5%BA%93SQL%20Server%20-%E6%95%B0%E6%8D%AE%E4%B8%BAnull%E4%B8%8D%E8%83%BD%E5%AF%B9null%E5%80%BC%E8%B0%83%E7%94%A8%E6%AD%A4%E6%96%B9%E6%B3%95%E6%88%96%E5%B1%9E%E6%80%A7


本文版权归作者所有,未经作者同意不得转载。

Java领域要说让我最服气的RPC框架当属Dubbo,原因有许多,但是最吸引我的还是它把远程调用这个事情设计得很有艺术。

1、Dubbo优点较多,我只钟情其一

1.1、优点

业内对于微服务之间调用的框架选择较多,主流是Spring Cloud的Rest方式 和 Dubbo方式,我使用Dubbo方式居多。Dubbo工业级可用,稳定又高效,深受各大公司研发同学的喜爱。

Dubbo的优点较多,比如:

  • 高性能
    :Dubbo 使用的是基于 Netty 的自定义通信协议,提供了高效的二进制数据传输,使得远程服务调用性能大幅提升。
  • 模块化设计
    :Dubbo 的架构非常模块化,主要由五大核心模块组成:远程调用模块(RPC)、集群模块、负载均衡模块、容错模块和注册中心模块。
  • 每个部件都支持多协议
    :每个部件都支持多种协议,比如注册中心,支持ZK、Redis、Nacos等等。
  • 负载均衡和容错
    :Dubbo 提供了多种容错机制,比如失败重试、失败转移等。还支持多种负载均衡,比如随机、轮询、一致性哈希等。
  • 服务注册和发现
    :Dubbo引入了注册中心的概念,实现了服务的自动注册和发现。
  • SPI 扩展机制
    :在
    背八股文
    场景下,Dubbo被提及最多的就是使用了类似Java的SPI机制,提高了扩展性,这一点仁者见仁智者见智吧。

1.2、钟情其一

但是,Dubbo最吸引人的,
半支烟
觉得反而倒是它的RPC调用。Dubbo的定位是一个RPC框架,这是它的核心和立足之地,所以Dubbo将RPC的调用过程透明化,使得开发者可以专注于业务逻辑,而不用关注底层通信问题。

一个RPC框架只有聚焦于先做好它的RPC调用过程这个模块,才会有人关注,其余的优点都是在这之后,慢慢迭代而来。

作者将RPC调用的这个过程,抽象成
一种协议消息的传输机制
,再通过
控制好线程的等待和唤醒
,来实现远程方法调用。这一设计思路真是美妙,充分体验了作者的智慧。

2、RPC简易示例

学Dubbo,首先就是要学习作者这种设计理念和思路。基于此,来实现一个简易的远程方法调用,将Dubbo的RPC过程简易化。

2.1、示例步骤

简易的RPC过程步骤如下,大致分5步,依旧使用Netty作用Socket通讯工具。

  1. 使用2个Java进程来模拟2个系统之间的调用,A进程 和 B进程。
  2. A进程的某个方法,使用网络请求调用B进程的某个方法。
  3. 然后A进程的方法就处于等待状态。
  4. 等B进程的方法执行完之后,在利用网络通知到A进程。
  5. 然后A进程的方法被唤醒,继续往下执行。

2.2、示例代码

  • B进程作为服务端,启动网络服务
public class BProcessServer {
    private final int port;
    public BProcessServer(int port) {
        this.port = port;
    }

    public void start() throws InterruptedException {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) {
                            ch.pipeline().addLast(new BProcessServerHandler());
                        }
                    });

            ChannelFuture future = bootstrap.bind(port).sync();
            System.out.println("B启动了服务,端口号: " + port);
            future.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new BProcessServer(8088).start();
    }
}
  • B进程接受网络请求参数,反序列化之后,执行对应的方法,再将执行结果返回:
public class BProcessServerHandler extends SimpleChannelInboundHandler<ByteBuf> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
        String reqData = msg.toString(CharsetUtil.UTF_8);
        System.out.println("B进程接受到了请求数据: " + reqData);

        executeMethod(ctx);
    }

    /**
     * 执行方法
     *
     * @param ctx
     * @throws InterruptedException
     */
    private void executeMethod(ChannelHandlerContext ctx) throws InterruptedException {
        // TODO 将请求消息按照某种规则解析成方法名、方法参数等,其实就是反序列化的过程。
        System.out.println("对接受的数据做反序列化,然后开始执行 消息体里指定的方法...");

        // 模拟方法执行
        Thread.sleep(2000);
        System.out.println("执行完毕,返回结果...");

        // 将结果 通知给 A 进程
        ByteBuf dataByteBuf = ctx.alloc().buffer().writeBytes("Task completed".getBytes(CharsetUtil.UTF_8));
        ctx.writeAndFlush(dataByteBuf);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
}
  • A进程启动Netty客户端,建立与B进程的通信,然后发起远程调用,处于等待状态。
public class AProcessClient {

    private final String host;
    private final int port;
    private final Object lock = new Object();  // 监视器对象

    public AProcessClient(String host, int port) {
        this.host = host;
        this.port = port;
    }

    public void start() throws InterruptedException {
        EventLoopGroup group = new NioEventLoopGroup();

        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) {
                            ch.pipeline().addLast(new AProcessClientHandler(lock));
                        }
                    });

            ChannelFuture future = bootstrap.connect(host, port).sync();
            System.out.println("A进程与B进程建立了通信连接");

            Channel channel = future.channel();

            // 发起远程调用
            callRemoteMethod(channel);

            channel.closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
    }

    /**
     * 执行方法
     *
     * @param channel
     * @throws InterruptedException
     */
    private void callRemoteMethod(Channel channel) throws InterruptedException {
        //TODO 此处需要将调用的方法和参数,按照协议进行序列化。这次暂且省去此过程。
        System.out.println("A进程将 请求的方法和参数 进行序列化,然后向B进程发起网络调用...");

        ByteBuf dataByteBuf = channel.alloc().buffer().writeBytes("Start call method".getBytes(CharsetUtil.UTF_8));

        channel.writeAndFlush(dataByteBuf);

        // 使用wait等待B进程通知
        synchronized (lock) {
            System.out.println("A进程等待B进程的响应...");
            lock.wait();  // 等待通知
        }

        System.out.println("A进程收到了B进程的响应通知,继续往下...");
    }

    public static void main(String[] args) throws InterruptedException {
        new AProcessClient("localhost", 8088).start();
    }
}
  • A进程接受B进程的响应,同时被唤醒,然后以上
    lock.wait()
    以后的代码得以继续执行。
public class AProcessClientHandler extends SimpleChannelInboundHandler<ByteBuf> {

    private final Object lock;

    public AProcessClientHandler(Object lock) {
        this.lock = lock;
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
        String resData = msg.toString(CharsetUtil.UTF_8);
        System.out.println("A进程接受到了响应数据: " + resData);

        // B 进程任务完成,使用 notify 唤醒等待的线程
        synchronized (lock) {
            lock.notify();  // 唤醒 A 进程
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
}

3、总结

Dubbo的优秀设计思路有许多,我只钟情其一,那就是RPC的调用过程。以上是一个简易的RPC远程调用的示例,用于理解Dubbo的原理和源码,希望对你有帮助!

本篇完结!欢迎 关注、加V(yclxiao)交流、全网可搜(程序员半支烟)

原文链接:
https://mp.weixin.qq.com/s/J0fzDH-iqGnnnjqaXMLs-A

Hugging Face NLP课程学习记录 - 0. 安装transformers库 & 1. Transformer 模型

说明:

0. 安装transformers库

创建conda环境并安装包:

conda create -n hfnlp python=3.12
conda install pytorch==2.3.1 torchvision==0.18.1 torchaudio==2.3.1 pytorch-cuda=12.1 -c pytorch -c nvidia
pip install transformers==4.44.2

# More
pip install seqeval
pip install sentencepiece

使用Hugging Face镜像(见
https://hf-mirror.com/
):

export HF_ENDPOINT=https://hf-mirror.com

或者在python中设置Hugging Face镜像:

import os
os.environ["HF_ENDPOINT"] = "https://hf-mirror.com"

1. Transformer 模型

Transformers 能做什么?

使用pipelines

Transformers 库中最基本的对象是
pipeline()
函数。它将模型与其必要的预处理和后处理步骤连接起来,使我们能够通过直接输入任何文本并获得最终的答案:

from transformers import pipeline

classifier = pipeline("sentiment-analysis")
classifier("I've been waiting for a HuggingFace course my whole life.")

提示:

No model was supplied, defaulted to distilbert/distilbert-base-uncased-finetuned-sst-2-english and revision af0f99b ....
[{'label': 'POSITIVE', 'score': 0.9598047137260437}]

输出:

[{'label': 'POSITIVE', 'score': 0.9598047137260437}]

输入多个句子:

classifier(
    ["I've been waiting for a HuggingFace course my whole life.", "I hate this so much!"]
)
[{'label': 'POSITIVE', 'score': 0.9598047137260437},
 {'label': 'NEGATIVE', 'score': 0.9994558691978455}]

将一些文本传递到pipeline时涉及三个主要步骤:

  1. 文本被预处理为模型可以理解的格式。
  2. 预处理的输入被传递给模型。
  3. 模型处理后输出最终人类可以理解的结果。

零样本分类

from transformers import pipeline

classifier = pipeline("zero-shot-classification")
classifier(
    "This is a course about the Transformers library",
    candidate_labels=["education", "politics", "business"],
)

提示:

No model was supplied, defaulted to facebook/bart-large-mnli and revision c626438 ([https://hf-mirror.com/facebook/bart-large-mnli](https://hf-mirror.com/facebook/bart-large-mnli)).
Using a pipeline without specifying a model name and revision in production is not recommended.

输出:

{'sequence': 'This is a course about the Transformers library', 'labels': ['education', 'business', 'politics'], 'scores': [0.8445952534675598, 0.11197696626186371, 0.043427806347608566]}

此pipeline称为zero-shot,因为您不需要对数据上的模型进行微调即可使用它

文本分类

现在让我们看看如何使用pipeline来生成一些文本。这里的主要使用方法是您提供一个提示,模型将通过生成剩余的文本来自动完成整段话。

from transformers import pipeline

generator = pipeline("text-generation")
generator("In this course, we will teach you how to")

提示:

No model was supplied, defaulted to openai-community/gpt2 and revision 6c0e608 (https://hf-mirror.com/openai-community/gpt2).
Using a pipeline without specifying a model name and revision in production is not recommended.

输出:

[{'generated_text': 'In this course, we will teach you how to create a simple Python script that uses the default Python scripts for the following tasks, such as adding a linker at the end of a file to a file, editing an array, etc.\n\n'}]

在pipeline中使用 Hub 中的其他模型

前面的示例使用了默认模型,但您也可以从 Hub 中选择特定模型以在特定任务的pipeline中使用 - 例如,文本生成。转到
模型中心(hub)
并单击左侧的相应标签将会只显示该任务支持的模型。
例如这样

让我们试试
distilgpt2
模型吧!以下是如何在与以前相同的pipeline中加载它:

from transformers import pipeline

generator = pipeline("text-generation", model="distilgpt2")
generator(
    "In this course, we will teach you how to",
    max_length=30,
    num_return_sequences=2
)
[{'generated_text': 'In this course, we will teach you how to make your world better. Our courses focus on how to make an improvement in your life or the things'},
 {'generated_text': 'In this course, we will teach you how to properly design your own design using what is currently in place and using what is best in place. By'}]

Mask filling

您将尝试的下一个pipeline是
fill-mask
。此任务的想法是填充给定文本中的空白:

from transformers import pipeline

unmasker = pipeline("fill-mask")
unmasker("This course will teach you all about <mask> models.", top_k=2)
[{'score': 0.19198445975780487,
  'token': 30412,
  'token_str': ' mathematical',
  'sequence': 'This course will teach you all about mathematical models.'},
 {'score': 0.04209190234541893,
  'token': 38163,
  'token_str': ' computational',
  'sequence': 'This course will teach you all about computational models.'}]

top_k
参数控制要显示的结果有多少种。请注意,这里模型填充了特殊的<
mask
>词,它通常被称为掩码标记。其他掩码填充模型可能有不同的掩码标记,因此在探索其他模型时要验证正确的掩码字是什么。

命名实体识别

命名实体识别 (NER) 是一项任务,其中模型必须找到输入文本的哪些部分对应于诸如人员、位置或组织之类的实体。让我们看一个例子:

from transformers import pipeline

ner = pipeline("ner", grouped_entities=True)
ner("My name is Sylvain and I work at Hugging Face in Brooklyn.")
No model was supplied, defaulted to dbmdz/bert-large-cased-finetuned-conll03-english ...
[{'entity_group': 'PER',
  'score': 0.9981694,
  'word': 'Sylvain',
  'start': 11,
  'end': 18},
 {'entity_group': 'ORG',
  'score': 0.9796019,
  'word': 'Hugging Face',
  'start': 33,
  'end': 45},
 {'entity_group': 'LOC',
  'score': 0.9932106,
  'word': 'Brooklyn',
  'start': 49,
  'end': 57}]

我们在pipeline创建函数中传递选项
grouped_entities=True
以告诉pipeline将对应于同一实体的句子部分重新组合在一起:这里模型正确地将“Hugging”和“Face”分组为一个组织,即使名称由多个词组成。

命名实体识别(中文)

运行来自
https://huggingface.co/shibing624/bert4ner-base-chinese
README的代码

pip install seqeval
import os
import torch
from transformers import AutoTokenizer, AutoModelForTokenClassification
from seqeval.metrics.sequence_labeling import get_entities

os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE"

# Load model from HuggingFace Hub
tokenizer = AutoTokenizer.from_pretrained("shibing624/bert4ner-base-chinese")
model = AutoModelForTokenClassification.from_pretrained("shibing624/bert4ner-base-chinese")
label_list = ['I-ORG', 'B-LOC', 'O', 'B-ORG', 'I-LOC', 'I-PER', 'B-TIME', 'I-TIME', 'B-PER']

sentence = "王宏伟来自北京,是个警察,喜欢去王府井游玩儿。"

def get_entity(sentence):
    tokens = tokenizer.tokenize(sentence)
    inputs = tokenizer.encode(sentence, return_tensors="pt")
    with torch.no_grad():
        outputs = model(inputs).logits
    predictions = torch.argmax(outputs, dim=2)
    char_tags = [(token, label_list[prediction]) for token, prediction in zip(tokens, predictions[0].numpy())][1:-1]
    print(sentence)
    print(char_tags)

    pred_labels = [i[1] for i in char_tags]
    entities = []
    line_entities = get_entities(pred_labels)
    for i in line_entities:
        word = sentence[i[1]: i[2] + 1]
        entity_type = i[0]
        entities.append((word, entity_type))

    print("Sentence entity:")
    print(entities)


get_entity(sentence)
王宏伟来自北京,是个警察,喜欢去王府井游玩儿。
[('宏', 'B-PER'), ('伟', 'I-PER'), ('来', 'I-PER'), ('自', 'O'), ('北', 'O'), ('京', 'B-LOC'), (',', 'I-LOC'), ('是', 'O'), ('个', 'O'), ('警', 'O'), ('察', 'O'), (',', 'O'), ('喜', 'O'), ('欢', 'O'), ('去', 'O'), ('王', 'O'), ('府', 'B-LOC'), ('井', 'I-LOC'), ('游', 'I-LOC'), ('玩', 'O'), ('儿', 'O')]
Sentence entity:
[('王宏伟', 'PER'), ('北京', 'LOC'), ('王府井', 'LOC')]

或者通过使用
nerpy
库来使用 shibing624/bert4ner-base-chinese 这个模型。

另外,可以使用的ltp来做中文命名实体识别,其Github仓库
https://github.com/HIT-SCIR/ltp
有4.9K的星

问答系统

from transformers import pipeline

question_answerer = pipeline("question-answering")
question_answerer(
    question="Where do I work?",
    context="My name is Sylvain and I work at Hugging Face in Brooklyn",
)
{'score': 0.6949753761291504, 'start': 33, 'end': 45, 'answer': 'Hugging Face'}

请注意,此pipeline通过从提供的上下文中提取信息来工作;它不会凭空生成答案。

文本摘要

文本摘要是将文本缩减为较短文本的任务,同时保留文本中的主要(重要)信息。下面是一个例子:

from transformers import pipeline

summarizer = pipeline("summarization", device=0)
summarizer(
    """
    America has changed dramatically during recent years. Not only has the number of 
    graduates in traditional engineering disciplines such as mechanical, civil, 
    electrical, chemical, and aeronautical engineering declined, but in most of 
    the premier American universities engineering curricula now concentrate on 
    and encourage largely the study of engineering science. As a result, there 
    are declining offerings in engineering subjects dealing with infrastructure, 
    the environment, and related issues, and greater concentration on high 
    technology subjects, largely supporting increasingly complex scientific 
    developments. While the latter is important, it should not be at the expense 
    of more traditional engineering.

    Rapidly developing economies such as China and India, as well as other 
    industrial countries in Europe and Asia, continue to encourage and advance 
    the teaching of engineering. Both China and India, respectively, graduate 
    six and eight times as many traditional engineers as does the United States. 
    Other industrial countries at minimum maintain their output, while America 
    suffers an increasingly serious decline in the number of engineering graduates 
    and a lack of well-educated engineers.
"""
)

与文本生成一样,您指定结果的
max_length

min_length

翻译

对于翻译,如果您在任务名称中提供语言对(例如“
translation_en_to_fr
”),则可以使用默认模型,但最简单的方法是在
模型中心(hub)
选择要使用的模型。在这里,我们将尝试从法语翻译成英语:

pip install sentencepiece
from transformers import pipeline

translator = pipeline("translation", model="Helsinki-NLP/opus-mt-fr-en", device=0)
translator("Ce cours est produit par Hugging Face.")
[{'translation_text': 'This course is produced by Hugging Face.'}]

将英语翻译成中文:

from transformers import pipeline

translator = pipeline("translation", model="Helsinki-NLP/opus-mt-en-zh", device=0)
translator("America has changed dramatically during recent years.")
[{'translation_text': '近年来,美国发生了巨大变化。'}]

偏见和局限性

如果您打算在正式的项目中使用经过预训练或经过微调的模型。请注意:虽然这些模型是很强大,但它们也有局限性。其中最大的一个问题是,为了对大量数据进行预训练,研究人员通常会搜集所有他们能找到的内容,中间可能夹带一些意识形态或者价值观的刻板印象。

为了快速解释清楚这个问题,让我们回到一个使用BERT模型的pipeline的例子:

from transformers import pipeline

unmasker = pipeline("fill-mask", model="bert-base-uncased", device=0)
result = unmasker("This man works as a [MASK].")
print([r["token_str"] for r in result])

result = unmasker("This woman works as a [MASK].")
print([r["token_str"] for r in result])
['carpenter', 'lawyer', 'farmer', 'businessman', 'doctor']
['nurse', 'maid', 'teacher', 'waitress', 'prostitute']

当要求模型填写这两句话中缺少的单词时,模型给出的答案中,只有一个与性别无关(服务员/女服务员)。

《Redis 入门》系列文章总算完成了,希望这个系列文章可以想入门或刚入门的同学提供帮助,希望能让你形成学习Redis系统性概念。

当时为什么要写这个系列文章,是因为我自己就是迷迷糊糊一路踩坑走过来的,我踩完的坑就踩完了,希望你能少踩一些。

我们简单来回顾一下《Redis 入门》系列内容,希望通过这次回顾能总结一些学习新知识的经验,学习如何更快的掌握新知识,如何形成系统性概念。

1.首先我们了解了Redis基本信息,是什么,有什么,能干什么;

2.然后我们学习如何安装Redis,在不同的操作系统上(Windows、Linux、Docker),通过不同的方式(脚本、安装包、包管理器方式、源码编译等)安装;

3.接着我们寻找了Redis可视化工具,我们介绍了四大类可视化工具(命令行工具、桌面客户端工具、网页工具、插件工具),10来种软件,各有千秋。

4.然后我们正式进入Redis知识学习,了解Redis五大基础数据类型(字符串、集合、有序集合、列、哈希),并熟悉了相关指令。

5.然后我们继续结合具体编程语言C#/.NET Core,了解了6款C#/.NET Core接入Redis客户端库(ServiceStack.Redis、StackExchange.Redis、CSRedisCore、FreeRedis、NewLife.Redis、BeetleX.Redis),并对6款库应用场景做了基本了解,还分别学习了如何使用这6款库。

6.最后我们一起封装了一个简单的Redis C#/.NET Core客户端库Nuget,以达到和现有项目解耦,方便后期扩展的目的。

在整个过程中,很多时候我们会有很多选择,到底应该做什么,我相信第一次接触Redis的,很多时候都会感到很迷茫。然后只能不停的在网上找答案,不停的去尝试,踩了无数次坑以后,最后才发现原来是这样啊,瞬间恍然大悟。

我自己也是踩了很多坑走过来的,因此我想把我的经验分享给大家,然后可以系统给大家介绍如何学习Redis,可以让大家少踩一些坑,在学习的早期就形成系统性概念,从而提升学习效率。

后面我想继续和大家分享《Redis 进阶》,开始深入学习Redis相关知识,希望能给你带来一些收获,如果你有什么想法,也可以给我留言。


:测试方法代码以及示例源码都已经上传至代码库,有兴趣的可以看看。
https://gitee.com/hugogoos/Planner

前端部分(Vue 3 + Element Plus)

1. 修改
MPS002HList.vue
(主生产计划列表)

a. 添加查询表单

在模板中添加查询表单,包含产品料号、品名、规格和年月的输入项。

<template>
  <div>
    <!-- 查询表单 -->
    <el-form :inline="true" :model="filters" class="demo-form-inline">
      <el-form-item label="产品料号">
        <el-input v-model="filters.bo_no" placeholder="请输入产品料号"></el-input>
      </el-form-item>
      <el-form-item label="品名">
        <el-input v-model="filters.item_name" placeholder="请输入品名"></el-input>
      </el-form-item>
      <el-form-item label="规格">
        <el-input v-model="filters.item_spec" placeholder="请输入规格"></el-input>
      </el-form-item>
      <el-form-item label="年月">
        <el-date-picker
v
-model="filters.mps_ym"type="month"placeholder="选择年月"format="yyyy-MM"value-format="yyyy-MM" /> </el-form-item> <el-form-item> <el-button type="primary" @click="fetchMpsList">查询</el-button> <el-button @click="resetFilters">重置</el-button> </el-form-item> </el-form> <!-- 生产计划列表 --> <el-table :data="mpsList" style="width: 100%" v-loading="loading"> <el-table-column prop="mps_no" label="单号" width="180"> <template #default="{ row }"> <el-button type="text" @click="showMpsDetails(row.mps_no)">{{ row.mps_no }}</el-button> </template> </el-table-column> <el-table-column prop="mps_date" label="单据时间" width="180" /> <el-table-column prop="fa_no_name" label="厂别" width="180" /> <el-table-column prop="bo_no" label="产品料号" width="180" /> <el-table-column prop="bo_no_name" label="品名" width="180" /> <el-table-column prop="bo_no_spec" label="规格" width="180" /> <el-table-column prop="mps_ym" label="年月" width="100" /> <el-table-column prop="mps_qty" label="数量" width="100" /> </el-table> <!-- 分页 --> <el-pagination
v
-if="mpsList.length"background
:current
-page="page":page-size="pageSize"layout="total, prev, pager, next":total="total"@current-change="handlePageChange" /> <!-- 详情对话框 --> <el-dialog :visible.sync="showDetails" width="80%"> <template #header> <h3>主生产计划详情</h3> </template> <MPS002HDetail :mps_no="selectedMpsNo" /> </el-dialog> </div> </template>

b. 修改脚本部分


setup
函数中,添加
filters
数据,并修改
fetchMpsList
函数以包含查询参数。

<script>import { ref, onMounted } from'vue';
import { getMPS002 } from
'@/api/mpsApp/MPS002HModel';
import MPS002HDetail from
'./MPS002HDetail.vue';

export
default{
components: { MPS002HDetail },
setup() {
const mpsList
=ref([]);
const page
= ref(1);
const pageSize
= ref(10);
const total
= ref(0);
const loading
= ref(false);
const showDetails
= ref(false);
const selectedMpsNo
= ref(null);

const filters
=ref({
bo_no:
'',
item_name:
'',
item_spec:
'',
mps_ym:
'',
});

const fetchMpsList
= async () =>{
loading.value
= true;try{
const params
={
page: page.value,
page_size: pageSize.value,
bo_no: filters.value.bo_no,
item_name: filters.value.item_name,
item_spec: filters.value.item_spec,
mps_ym: filters.value.mps_ym,
};
const response
=await getMPS002(params);
mpsList.value
=response.data.results;
total.value
=response.data.count;
}
catch(error) {
console.error(
'Error fetching MPS002 list:', error);
}
finally{
loading.value
= false;
}
};

const resetFilters
= () =>{
filters.value
={
bo_no:
'',
item_name:
'',
item_spec:
'',
mps_ym:
'',
};
fetchMpsList();
};

const showMpsDetails
= (mps_no) =>{
selectedMpsNo.value
=mps_no;
showDetails.value
= true;
};

const handlePageChange
= (newPage) =>{
page.value
=newPage;
fetchMpsList();
};

onMounted(fetchMpsList);
return{
mpsList,
page,
pageSize,
total,
loading,
showDetails,
selectedMpsNo,
filters,
fetchMpsList,
resetFilters,
showMpsDetails,
handlePageChange,
};
},
};
</script>

2. 修改
MPS002D1List.vue
(物料需求明细列表)

a. 添加查询表单

<template>
  <div>
    <!-- 查询表单 -->
    <el-form :inline="true" :model="filters" class="demo-form-inline">
      <el-form-item label="料号">
        <el-input v-model="filters.item_no" placeholder="请输入料号"></el-input>
      </el-form-item>
      <el-form-item label="品名">
        <el-input v-model="filters.item_name" placeholder="请输入品名"></el-input>
      </el-form-item>
      <el-form-item label="规格">
        <el-input v-model="filters.item_spec" placeholder="请输入规格"></el-input>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="fetchMpsD1List">查询</el-button>
        <el-button @click="resetFilters">重置</el-button>
      </el-form-item>
    </el-form>

    <!-- 物料需求明细列表 -->
    <el-table :data="mpsD1List" style="width: 100%" v-loading="loading">
      <el-table-column prop="item_no" label="料号" width="180" />
      <el-table-column prop="item_name" label="品名" width="180" />
      <el-table-column prop="item_spec" label="规格" width="180" />
      <el-table-column prop="item_qty" label="需求数量" width="180" />
      <!-- 添加更多列 -->
    </el-table>

    <!-- 分页 -->
    <el-pagination
v
-if="mpsD1List.length"background
:current
-page="page":page-size="pageSize"layout="total, prev, pager, next":total="total"@current-change="handlePageChange" /> </div> </template>

b. 修改脚本部分

<script>import { ref, onMounted } from'vue';
import { getMPS002D1 } from
'@/api/mpsApp/MPS002D1Model';

export
default{
setup() {
const mpsD1List
=ref([]);
const page
= ref(1);
const pageSize
= ref(10);
const total
= ref(0);
const loading
= ref(false);

const filters
=ref({
item_no:
'',
item_name:
'',
item_spec:
'',
});

const fetchMpsD1List
= async () =>{
loading.value
= true;try{
const params
={
page: page.value,
page_size: pageSize.value,
item_no: filters.value.item_no,
item_name: filters.value.item_name,
item_spec: filters.value.item_spec,
};
const response
=await getMPS002D1(params);
mpsD1List.value
=response.data.results;
total.value
=response.data.count;
}
catch(error) {
console.error(
'Error fetching MPS002D1 list:', error);
}
finally{
loading.value
= false;
}
};

const resetFilters
= () =>{
filters.value
={
item_no:
'',
item_name:
'',
item_spec:
'',
};
fetchMpsD1List();
};

const handlePageChange
= (newPage) =>{
page.value
=newPage;
fetchMpsD1List();
};

onMounted(fetchMpsD1List);
return{
mpsD1List,
page,
pageSize,
total,
loading,
filters,
fetchMpsD1List,
resetFilters,
handlePageChange,
};
},
};
</script>

后端部分(Django REST Framework)

为了支持前端的查询功能,需要在后端的视图中添加筛选功能。

1. 修改
MPS002HModel
的视图

from rest_framework import viewsets, filters
from django_filters.rest_framework import DjangoFilterBackend
from .models import MPS002HModel
from .serializers import MPS002HSerializer

class MPS002HViewSet(viewsets.ModelViewSet):
queryset
= MPS002HModel.objects.all().order_by('-mps_date')
serializer_class
=MPS002HSerializer
filter_backends
=[DjangoFilterBackend, filters.SearchFilter]
filterset_fields
= ['mps_ym']
search_fields
= ['bo_no__item_no', 'bo_no__item_name', 'bo_no__item_spec']

说明

  • filter_backends
    :使用
    DjangoFilterBackend

    SearchFilter
    ,可以实现精确过滤和模糊搜索。
  • filterset_fields
    :精确过滤的字段,这里包括
    mps_ym
  • search_fields
    :模糊搜索的字段,包括关联的
    bo_no
    (产品料号)的
    item_no

    item_name

    item_spec

2. 修改
MPS002D1Model
的视图

from rest_framework import viewsets, filters
from django_filters.rest_framework import DjangoFilterBackend
from .models import MPS002D1Model
from .serializers import MPS002D1Serializer

class MPS002D1ViewSet(viewsets.ModelViewSet):
queryset
=MPS002D1Model.objects.all()
serializer_class
=MPS002D1Serializer
filter_backends
=[DjangoFilterBackend, filters.SearchFilter]
search_fields
= ['item_no__item_no', 'item_no__item_name', 'item_no__item_spec']

说明

  • search_fields
    :对于物料需求明细,可以根据
    item_no
    (料号)、
    item_name
    (品名)、
    item_spec
    (规格)进行模糊搜索。

3. 安装和配置
django-filter

如果还没有安装
django-filter
,需要先安装:

pip install django-filter

并在
settings.py
中添加:

REST_FRAMEWORK ={'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend']
}

更新序列化器

确保您的序列化器包含必要的字段,以便前端能够正确接收数据。

MPS002HSerializer

from rest_framework import serializers
from .models import MPS002HModel

class MPS002HSerializer(serializers.ModelSerializer):
fa_no_name
= serializers.CharField(source='fa_no.fa_name', read_only=True)
bo_no_name
= serializers.CharField(source='bo_no.item_name', read_only=True)
bo_no_spec
= serializers.CharField(source='bo_no.item_spec', read_only=True)
bo_no
= serializers.CharField(source='bo_no.item_no', read_only=True)

class Meta:
model
=MPS002HModel
fields
= ['id', 'mps_no', 'mps_date', 'fa_no', 'fa_no_name', 'bo_no', 'bo_no_name', 'bo_no_spec', 'mps_ym', 'mps_qty']

MPS002D1Serializer

from rest_framework import serializers
from .models import MPS002D1Model

class MPS002D1Serializer(serializers.ModelSerializer):
item_name
= serializers.CharField(source='item_no.item_name', read_only=True)
item_spec
= serializers.CharField(source='item_no.item_spec', read_only=True)
item_no
= serializers.CharField(source='item_no.item_no', read_only=True)

class Meta:
model
=MPS002D1Model
fields
= ['id', 'mps_no', 'item_no', 'item_name', 'item_spec', 'item_qty', 'rmk']

更新 API 请求

1. 更新前端 API 调用

在您的 API 请求文件中,确保查询参数能够正确传递。

MPS002HModel
API

import request from '@/utils/request';
const baseUrl
= '/mpsApp/MPS002HModel/';

export
functiongetMPS002(params) {returnrequest({
url: baseUrl,
method:
'get',
params,
});
}

MPS002D1Model
API

import request from '@/utils/request';
const baseUrl
= '/mpsApp/MPS002D1Model/';

export
functiongetMPS002D1(params) {returnrequest({
url: baseUrl,
method:
'get',
params,
});
}


总结

通过上述步骤,我们实现了:

  • 前端
    :在
    MPS002HModel

    MPS002D1Model
    的列表页面中添加了查询表单,可以根据指定的字段进行筛选,并将查询条件传递给后端。

  • 后端
    :在 Django REST Framework 的视图中,使用
    django-filter

    SearchFilter
    实现了对指定字段的精确过滤和模糊搜索。

  • 序列化器
    :更新了序列化器,以便在返回数据时包含关联字段的信息,如品名和规格。

  • API 请求
    :确保查询参数能够正确地通过前端 API 请求发送到后端。

这样,用户就可以在前端界面上根据产品料号、品名、规格和年月对主生产计划列表进行查询,也可以在物料需求明细列表中根据料号、品名、规格进行筛选。