当前位置: 首页 > news >正文

10CQRS

本系列包含以下文章:

  1. DDD入门
  2. DDD概念大白话
  3. 战略设计
  4. 代码工程结构
  5. 请求处理流程
  6. 聚合根与资源库
  7. 实体与值对象
  8. 应用服务与领域服务
  9. 领域事件
  10. CQRS(本文)

案例项目介绍 #

既然DDD是“领域”驱动,那么我们便不能抛开业务而只讲技术,为此让我们先从业务上了解一下贯穿本文章系列的案例项目 —— 码如云(不是马云,也不是码云)。如你已经在本系列的其他文章中了解过该案例,可跳过。

码如云是一个基于二维码的一物一码管理平台,可以为每一件“物品”生成一个二维码,并以该二维码为入口展开对“物品”的相关操作,典型的应用场景包括固定资产管理、设备巡检以及物品标签等。

在使用码如云时,首先需要创建一个应用(App),一个应用包含了多个页面(Page),也可称为表单,一个页面又可以包含多个控件(Control),比如单选框控件。应用创建好后,可在应用下创建多个实例(QR)用于表示被管理的对象(比如机器设备)。每个实例均对应一个二维码,手机扫码便可对实例进行相应操作,比如查看实例相关信息或者填写页面表单等,对表单的一次填写称为提交(Submission);更多概念请参考码如云术语。

在技术上,码如云是一个无代码平台,包含了表单引擎、审批流程和数据报表等多个功能模块。码如云全程采用DDD完成开发,其后端技术栈主要有Java、Spring Boot和MongoDB等。

码如云的源代码是开源的,可以通过以下方式访问:

码如云源代码:GitHub - mryqr-com/mry-backend: 本代码库为码如云后端代码。码如云是一个基于二维码的一物一码管理平台,可以为每一件“物品”生成一个二维码,手机扫码即可查看物品信息并发起相关业务操作,操作内容可由你自己定义,典型的应用场景包括固定资产管理、设备巡检以及物品标签等。在技术上,码如云是一个无代码平台,全程采用DDD、整洁架构和事件驱动架构思想完成开发。

CQRS #

CQRS(Command Query Responsibility Segregation)直译成中文叫命令查询职责分离,可不要被这个读起来有些拗口的名字吓到了,事实上就是读写分离的意思,不过这里的读写分离和我们通常所理解的数据库级别的读写分离是两个不同的概念,CQRS指的读写分离是指在应用程序内部的代码级别的读写分离,在本文中,我将对此做出详细解释。

简单来讲,CQRS的提出是基于这么一种现象:软件中写数据的操作和读数据的操作是两个很不一样的过程,它们各有各的特点,因此可以并且应该将它们作为两个单独的关注点分别进行处理。“写数据”的过程也被称为“命令(Command)”,即表示外界通过向软件发送一些列的命令达到更新软件内部数据的目的,比如更新用户偏好设置、向电商网站下单等;“读数据”的过程也被称为“查询(Query)”,即从软件中获取数据,比如查看订单信息等。读和写的不同主要体现在以下几个方面:

  • 业务逻辑的运用主要是在写数据一侧,也就是说,我们在本系列的其他文章中讲到的聚合根,实体,值对象,领域服务等领域模型中的概念主要用于“写数据”的过程,相比之下“读数据”一侧的业务逻辑则相对较少,主要是数据展现逻辑;
  • 读数据是幂等的,即无论通过什么方式,都不应该修改系统中的数据,也即读数据相对安全,而在写数据时则需要始终保证数据的正确性和一致性,否则将导致严重Bug;
  • 导致读数据和写数据过程发生变更的归因不同,对写数据侧的变更主要基于业务逻辑的变化,而读数据侧的变更则更多基于UI需求的变化,比如根据不同的屏幕尺寸返回不同的数据等;
  • 读数据和写数据的频率往往各不相同,对于多数业务来说写数据的频率往往低于读数据的频率。

事实上,读写分离这种思想早在上世纪80年代末便由Bertrand Meyer提出,在他的《Object-Oriented Software Construction》一书中指出:

Every method should either be a command that performs an action, or a query that returns data to the caller, but never both. (一个方法要么作为一个“命令”执行一个操作,要么作为一次“查询”向调用方返回数据,但两者不能共存。)

可以看出,Bertrand Meyer所谓的读写分离主要用于对象中的方法(Method),而CQRS将这种思想扩大到了软件架构层面,接下来让我们分别看看CQRS中的各种读写分离模式。

流程分离 #

最简单的读写分离模式莫过于读写流程的分离了,事实上这也是我们一直在用的一种方式,是的没错,你已经在用CQRS了。为此,让我们来看看一个具体的例子,在码如云中,有权限的成员(Member)可以更新表单(Submission),也可以查看表单详情数据,前者是一个写数据的过程,后者则是一个读数据的过程。更新表单的应用服务代码如下:


//SubmissionCommandService@Transactional
public void updateSubmission(String submissionId, UpdateSubmissionCommand command, User user) {Submission submission = submissionRepository.byIdAndCheckTenantShip(submissionId, user);AppedQr appedQr = qrRepository.appedQrById(submission.getQrId());App app = appedQr.getApp();QR qr = appedQr.getQr();Page page = app.pageById(submission.getPageId());SubmissionPermissions submissionPermissions = submissionPermissionChecker.permissionsFor(user,app,submission.getGroupId());submissionDomainService.updateSubmission(submission,app,page,qr,command.getAnswers(),submissionPermissions.getPermissions(),user);submissionRepository.houseKeepSave(submission, app);log.info("Updated submission[{}].", submissionId);
}

源码出处:com/mryqr/core/submission/command/SubmissionCommandService.java

应用服务方法SubmissionCommandService.updateSubmission()通过调用领域服务SubmissionDomainService.updateSubmission()完成对表单的更新,然后再通过SubmissionRepository.houseKeepSave()完成对表单的持久化。

在查看表单详情时的应用服务代码如下:

//SubmissionQueryServicepublic QDetailedSubmission fetchDetailedSubmission(String submissionId, User user) {Submission submission = submissionRepository.byIdAndCheckTenantShip(submissionId, user);//将领域对象Submission转为展现对象QDetailedSubmissionreturn toSubmissionDetail(submission, user);
}

源码出处:com/mryqr/core/submission/query/SubmissionQueryService.java

SubmissionQueryService.fetchDetailedSubmission()方法中,先获取到需要查询的表单聚合根对象Submission,然后调用toSubmissionDetail()Submission转换为展现对象QDetailedSubmission

在上述2个代码例子中,写数据和读数据使用了不同的应用服务方法,也即流程分离了。你可能会说“我平时就是这么做的呀!”,的确如此,这种方式正是大家平时的编码实现,但是这里我们更希望强调的原则在于:写数据的SubmissionCommandService.updateSubmission()返回的是void,也即不会返回任何数据,而读数据的SubmissionQueryService.fetchDetailedSubmission()则只是获取数据而未修改任何数据。

此外,虽然SubmissionCommandServiceSubmissionQueryService均表示应用服务,但是在编码实现中被分成了2个单独的类以示分离。事实上,在码如云我们在代码的分包层面也做了相应的对读写分离的支持,所有与写数据相关的代码被组织在了command包下,而所有与读数据相关的代码则被放在了query包下。

在查询数据时,先获取到聚合根对象Submission,再将其转化为展现对象QDetailedSubmission,也就是说读数据和写数据的过程共享了同一个聚合根对象Submission。这种方式对于简单的查询场景没有多大问题,但是对于一些复杂的查询场景来说并不合适,一是使得读数据侧对写数据侧存在依赖,二是在跨表查询的时候,需要将多个聚合根对象分别从数据库中加载到内存,导致对数据库的多次访问,在高并发场景下,这可能影响系统性能。

模型分离 #

既然业务逻辑主要作用于写数据侧,而读数据侧主要处理的是展现逻辑,那是不是在读数据时可以绕过领域模型(上例中的Submission)呢?当然可以,这就是模型分离。模型分离的主要特点是:在写数据时,依然严格按照领域模型对业务逻辑的请求处理流程,但是在读数据时,可以绕过领域模型,直接从数据库创建相应的读模型对象。落到编码层面,在写数据侧可能需要通过ORM等工具完成对聚合根的持久化,但是在读数据侧则不见得,我们全然可以通过直接的SQL语句从数据库中加载所需查询的数据。

在码如云,租户管理员可以查看租户下所有的成员,其查询实现如下:

//MemberQueryServicepublic PagedList<QListMember> listMyManagedMembers(ListMyManagedMembersQuery queryCommand, User user) {String tenantId = user.getTenantId();Pagination pagination = pagination(queryCommand.getPageIndex(), queryCommand.getPageSize());String departmentId = queryCommand.getDepartmentId();String search = queryCommand.getSearch();Query query = new Query(buildMemberQueryCriteria(tenantId, departmentId, search));long count = mongoTemplate.count(query, Member.class);if (count == 0) {return pagedList(pagination, 0, List.of());}query.skip(pagination.skip()).limit(pagination.limit()).with(sort(queryCommand));//绕过Member,直接将从数据库中查到的数据创建为QListMemberquery.fields().include("name").include("avatar").include("role").include("mobile").include("wxUnionId").include("wxNickName").include("email").include("active").include("createdAt").include("departmentIds");List<QListMember> members = mongoTemplate.find(query, QListMember.class, MEMBER_COLLECTION);return pagedList(pagination, (int) count, members);
}

源码出处:com/mryqr/core/member/query/MemberQueryService.java

可以看到,在查询成员列表时,直接通过mongotTemplate(码如云使用的是MongoDB)将从数据库中所查询到的数据创建为了读模型QListMember,省去了加载Member并从Member转化为QListMember的过程。

数据源分离 #

模型分离可以解决很大一部分读写分离的问题,不过它依然是一种相对简单的CQRS实现方式,对于更加复杂的查询场景来说则显得有些力不从心,主要有以下原因:

  1. 模型分离事实上只是代码层面模型的分离,底层的数据库模型并未分离,依然是读写共享的,对于主要服务于写数据一侧的数据库来说,可能由于对读数据一侧的“照料不周”而无法满足某些查询需求;
  2. 模型分离只能用于在同一个进程空间之内的查询,也即所查询的数据均位于同一个数据库的场景,但是对于诸如微服务这种需要跨进程查询的情况则无法满足,比如对于一个采用微服务架构的电商系统,在用户首页需要同时查看用户基本信息和积分,但是前者位于“用户”服务中,而后者来自于“积分”服务,此时需要分别从2个服务中获取数据并返回给前端;
  3. 查询所需数据不一定能够直接映射到数据库中的字段,而是有可能需要做一些额外的加工,比如将省份(province)城市(city)详细地址(detailAddress)拼接为最终的地址值等。

数据源分离便是用来解决这个问题的,在这种方式下,我们为数据查询侧单独创建一个数据库,这个数据库存在的目的仅仅是为了方便查询用,可以说是为读数据侧量身定制的,该数据库中的数据依然来自于写数据一侧,只是经过了一些预先的加工,比如根据查询端(前端)所需摒弃了一些无用的字段,或者将多个字段合并成单个字段便于前端的直接显示等。那么,数据又如何从写数据一侧传递到读数据一侧呢?答案是领域事件。

在写数据时,对业务数据的变更将通过领域事件的形式发布到消息队列(Kafka)中, 读数据侧作为一个独立的模块通过消费这些领域事件完成对读模型数据库的相应更新,之后在查询数据时,则采用与“模型分离”相似的模式直接从数据库构建读模型,最后返回给查询方(前端)。

在技术栈的选择上,读数据侧的数据库不必与写数据库保持一致,比如写数据侧可以采用诸如MySQL这种强事务一致性的数据库(为了保证业务数据的正确性),但是读数据侧可以采用更有利于数据查询的数据库,比如ElasticSearch等。

事实上,以上3种CQRS的实现模式并不是彼此互斥的,而是可以同时存在,哪种方式相对简单则采用哪种方式。比如,在码如云我们便同时采用了3种方式。

总结 #

CQRS即是读写分离的意思,它将软件中的写数据过程和读数据过程分开处理,各司其职,是一种可以在很大程度上简化软件架构的编程模式。在这种模式下,写数据的过程严格遵循DDD的各种原则,而读数据的过程则可以绕开DDD中的领域模型(主要是聚合根),直接从数据库构建需要查询的数据模型。根据具体场景的不同,可以采用不同的CQRS实现模式。

相关文章:

10CQRS

本系列包含以下文章&#xff1a; DDD入门DDD概念大白话战略设计代码工程结构请求处理流程聚合根与资源库实体与值对象应用服务与领域服务领域事件CQRS&#xff08;本文&#xff09; 案例项目介绍 # 既然DDD是“领域”驱动&#xff0c;那么我们便不能抛开业务而只讲技术&…...

DAZ To UMA⭐一.DAZ简单使用教程

文章目录 &#x1f7e5; DAZ快捷键&#x1f7e7; DAZ界面介绍 &#x1f7e5; DAZ快捷键 移动物体:ctrlalt鼠标左键 旋转物体:ctrlalt鼠标右键 导入模型:双击左侧模型UI &#x1f7e7; DAZ界面介绍 Files:显示全部文件 Products:显示全部产品 Figures:安装的全部人物 Wardrobe…...

面试题 —— Java集合篇(23题)

文章目录 1.Java中常见集合有哪些 &#xff1f;2. 说说你对Java集合是怎么理解的&#xff1f;3.请你说一下List&#xff0c;Set&#xff0c;Map三者的特点是 &#xff1f;4.在实际开发过程中如何更好的选择集合 &#xff1f;5. ArrayList和Vector区别 &#xff1f;6. ArrayList…...

SpringBoot2.7.14整合Swagger3.0的详细步骤及容易踩坑的地方

&#x1f9d1;‍&#x1f4bb;作者名称&#xff1a;DaenCode &#x1f3a4;作者简介&#xff1a;啥技术都喜欢捣鼓捣鼓&#xff0c;喜欢分享技术、经验、生活。 &#x1f60e;人生感悟&#xff1a;尝尽人生百味&#xff0c;方知世间冷暖。 &#x1f4d6;所属专栏&#xff1a;Sp…...

题解:ABC321D - Set Menu

题解&#xff1a;ABC321D - Set Menu 题目 链接&#xff1a;Atcoder。 链接&#xff1a;洛谷。 难度 算法难度&#xff1a;B。 思维难度&#xff1a;C。 调码难度&#xff1a;B。 综合评价&#xff1a;见洛谷链接。 算法 枚举二分查找。 思路 先对b升序排序&#x…...

什么是Progressive Web App(PWA)?它们有哪些特点?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 渐进式Web App简介⭐ PWAs的主要特点⭐ 总结⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入…...

MySQL的高级SQL语句

目录 一、高级SQL语句 1、select 查询表中一个或多个字段的数据 2、distinct 不显示重复的数据记录 3、where 有条件查询 4、and与or 且与或 5、in 显示在某个范围值内 的字段的信息 6、between 显示两个值范围内的数据记录 7、order by 对字…...

基于人脸5个关键点的人脸对齐(人脸纠正)

摘要&#xff1a;人脸检测模型输出人脸目标框坐标和5个人脸关键点&#xff0c;在进行人脸比对前&#xff0c;需要对检测得到的人脸框进行对齐&#xff08;纠正&#xff09;&#xff0c;本文将通过5个人脸关键点信息对人脸就行对齐&#xff08;纠正&#xff09;。 一、输入图像…...

vue3中两个el-select下拉框选项相互影响

vue3中两个el-select下拉框选项相互影响 1、开发需求2、代码2.1 定义hooks文件2.2 在组件中使用 1、开发需求 如图所示&#xff0c;在项目开发过程中&#xff0c;遇到这样一个需求&#xff0c;常规时段中选中的月份在高峰时段中是禁止选择的状态&#xff0c;反之亦然。 2、代…...

博弈论——反应函数

反应函数 1 引言 谢老师的《经济博弈论》书中对反应函数并没有给出一般笼统的定义&#xff0c;而是将其应用与古诺模型并给出了相关解释&#xff1a;反应函数是指在无限策略的古诺博弈模型中&#xff0c;博弈方的策略有无限多种&#xff0c;因此各个博弈方的最佳对策也有无限…...

UE5读取json文件

一、下载插件 在工程中启用 二、定义读取外部json文件的函数&#xff0c;参考我之前的文章 ue5读取外部文件_艺菲的博客-CSDN博客 三、读取文件并解析为json对象 这里Load Text就是自己定义的函数&#xff0c;ResourceBundle为一个字符串常量&#xff0c;通常是读取的文件夹…...

Vue中的插槽--组件复用,内容自定义

插槽 文章目录 插槽插槽-默认插槽插槽-后备内容&#xff08;设置默认值&#xff09;插槽-具名插槽插槽–作用域插槽 插槽-默认插槽 作用&#xff1a;让组件内部的一些结构支持自定义 需求&#xff1a;要在页面中显示一个对话框,封装成一个组件&#xff08;对话框有很多功能是类…...

完全指南:mv命令用法、示例和注意事项 | Linux文件移动与重命名

文章目录 mv命令使用指南1. 简介什么是mv命令&#xff1f;mv命令的作用和功能是什么&#xff1f; 2. 基本用法基本语法格式如何移动文件&#xff1f;如何重命名文件&#xff1f;如何移动和重命名目录&#xff1f; 3. 高级用法使用通配符进行批量移动和重命名使用选项进行文件移…...

gitee生成公钥和远程仓库与本地仓库使用验证

参考文档&#xff1a; https://help.gitee.com/base/account/SSH%E5%85%AC%E9%92%A5%E8%AE%BE%E7%BD%AE(1)通过命令ssh-keygen 生成SSH key -t key类型 -c注释 ssh-keygen -t ed25519 -C "Gitee SSH Key" (2)按三次回车 (3)查看生成的 SSH 公钥和私钥&#xff1a; …...

请求后端接口413

当在进行HTTP请求时出现"413 Request Entity Too Large"错误时&#xff0c;通常是因为请求体的大小超过了服务器的配置限制。这个错误提示表明服务器拒绝接受过大的请求。 此时一般还未到后端服务&#xff0c;是被后端的ngnix代理服务器拦截的&#xff0c;所以可以检…...

HarmonyOS之 开发环境搭建

一 鸿蒙简介&#xff1a; 1.1 HarmonyOS是华为自研的一款分布式操作系统&#xff0c;兼容Android&#xff0c;但又区别Android&#xff0c;不仅仅定位于手机系统。更侧重于万物物联和智能终端&#xff0c;目前已更新到4.0版本。 1.2 HarmonyOS软件编程语言是ArkTS&#xff0c…...

QTC++ day12

注册登录界面 widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QIcon> #include <QPushButton> #include <QLineEdit> #include <QLabel> #include <QDebug> #include <QMessageBox>//消息对话框类 #inc…...

Vue3中使用Proxy API取代defineProperty API的原因

目录 一、前言 二、defineProperty API的限制和问题 三、Proxy API的优势和特性 四、Vue3.0中使用Proxy API的原因 五、Proxy API的局限性和注意事项 一、前言 Vue3.0是Vue.js框架的最新版本&#xff0c;它在底层进行了许多重要的改进。其中最引人注目的变化之一是它转而…...

构建工具Webpack简介

一、构建工具 当我们习惯了Node中使用ES模块化编写代码以后&#xff0c;用原生的HTML、CSS、JS这些东西会感觉到各种不便。比如&#xff1a;不能放心的使用模块化规范&#xff08;浏览器兼容性问题&#xff09;、即使可以使用模块化规范也会面临模块过多时的加载问题。 这时候…...

Docker部署单点Elasticsearch与Kibana

一 、 创建网络 因为需要部署kibana容器&#xff0c;因此需要让es和kibana容器互联。这里创建一个网络&#xff1a; docker network create es-net # 创建一个网络名称为:es-net 二 、拉取并加载镜像 方式一 docker pull elasticsearch:7.12.1 版本为elasticsearch的7…...

opencv实现仿射变换和透射变换

##1&#xff0c; 什么是仿射变换&#xff1f; 代码实现 import numpy as np import cv2 as cv import matplotlib.pyplot as plt#设置字体 from pylab import mpl mpl.rcParams[font.sans-serif] [SimHei]#图像的读取 img cv.imread("lena.png")#仿射变换 row…...

抖音seo账号矩阵源码系统

1. 开通多个抖音账号&#xff0c;并将它们归纳为一个账号矩阵系统。 2. 建立一个统一的账号管理平台&#xff0c;以便对这些账号进行集中管理&#xff0c;包括账号信息、内容发布、社区交互等。 3. 招募专业的运营团队&#xff0c;对每个账号进行精细化运营&#xff0c;包括内…...

性能优化之防抖

方法1&#xff1a;利用lodash库提供的防抖来处理 方法2&#xff1a;手写一个防抖函数来处理 需求&#xff1a;鼠标在盒子上移动&#xff0c;鼠标停止500ms之后&#xff0c;里面的数字才会变化1 方法一&#xff1a;利用lodash库实现防抖 <!DOCTYPE html> <html lang&…...

postgresql用户和角色

postgresql用户和角色 简述创建角色角色属性登录特权超级用户创建数据库创建角色启动复制密码修改角色属性 对象授权撤销授权组和成员删除角色 简述 PostgreSQL 通过角色的概念来控制数据库的访问权限。角色又包含了两种概念&#xff0c;具有登录 权限的角色称为用户&#xff…...

设计模式之备忘录模式

文章目录 游戏角色状态恢复问题传统方案解决游戏角色恢复传统的方式的问题分析备忘录模式基本介绍游戏角色恢复状态实例备忘录模式的注意事项和细节 游戏角色状态恢复问题 游戏角色有攻击力和防御力&#xff0c;在大战 Boss 前保存自身的状态(攻击力和防御力)&#xff0c;当大…...

大数据Flink(八十八):Interval Join(时间区间 Join)

文章目录 Interval Join&#xff08;时间区间 Join&#xff09; Interval Join&#xff08;时间区间 Join&#xff09; Interval Join 定义&#xff08;支持 Batch\Streaming&#xff09;&#xff1a;Interval Join 在离线的概念中是没有的。Interval Join 可以让一条流去 Jo…...

数字IC笔试千题解--判断题篇(五)

前言 出笔试题汇总&#xff0c;是为了总结秋招可能遇到的问题&#xff0c;做题不是目的&#xff0c;在做题的过程中发现自己的漏洞&#xff0c;巩固基础才是目的。 所有题目结果和解释由笔者给出&#xff0c;答案主观性较强&#xff0c;若有错误欢迎评论区指出&#xff0c;资料…...

Kubernetes(k8s)上搭建一主两从的mysql8集群

Kubernetes上搭建一主两从的mysql8集群 环境准备搭建nfs服务器安装NFS暴露nfs目录开启nfs服务器 安装MySQL集群创建命名空间创建MySQL密码的Secret安装MySQL主节点创建pv和pvc主节点的配置文件部署mysql主节点 安装第一个MySQL Slave节点创建pv和pvc第一个从节点配置文件部署my…...

MySQL备份与恢复

MySQL备份与恢复一、备份1、数据备份的重要性2、数据备份分类2.1 物理备份2.2 逻辑备份 3、数据库备份策略4、常用的备份方法和工具5、数据库上云迁移 二、数据库完全备份1、简介2、物理冷备份与恢复2.1 物理冷备份2.2 备份恢复2.3 补充知识date 3、mysqldump备份与恢复3.1 完全…...

【RTOS学习】单片机中的C语言

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;专栏&#xff1a;《RTOS学习》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; 本喵默认各位小伙伴都会C语言&#xff0c;我们平时学习C语言都是在Windows环境下学习的&#xff0…...

wordpress ftp 上传到 那个文件夹/冬镜seo

前言 我们知道&#xff0c;在 MVC 应用程序中&#xff0c;有一部分约定的内容。其中关于 Controller 的约定是这样的。 每个 Controller 类的名字以 Controller 结尾&#xff0c;并且放置在 Controllers 目录中。Controller 使用的视图是在 Views 主目录的一个子目录中&#xf…...

wordpress 调用多媒体/资源优化排名网站

解决vue不兼容ie浏览器的方法参考文章&#xff1a; &#xff08;1&#xff09;解决vue不兼容ie浏览器的方法 &#xff08;2&#xff09;https://www.cnblogs.com/maibao666/p/11081188.html 备忘一下。...

delphi WordPress/深圳高端seo公司助力企业

启动页的作用 在我遇到这个实际问题之前&#xff0c;我一直认为启动页的作用是美化产品&#xff0c;提升软件逼格。但实际上&#xff0c;它更重要的是起到了一个拦截器的作用。比如&#xff0c;当App首页需要动态得从网上加载数据时&#xff0c;就必须请求网络权限&#xff0c…...

音乐网站设计外国/seo营销怎么做

Go Web编程--SecureCookie实现客户端Session管理在Web应用开发中Session是在用户和服务器之间进行交换的非持久化交互信息。当用户登录时&#xff0c;可以在用户和服务器之间生成Session&#xff0c;然后来回交换数据&#xff0c;并在用户登出时销毁Session。gorilla/sessions软…...

mysql数据库网站/seo博客是什么意思

什么是注册表注册表是用于存储Windows系统用户&#xff0c;硬件和软件的存储配置信息的数据库。虽然注册表是为了配置系统而设计的&#xff0c;但它可以跟踪用户的活动&#xff0c;连接到系统的设备&#xff0c;什么时间什么软件被使用过等都将被记录在案。所有这些都可用于取证…...

网站建设价格/青岛网络优化哪家专业

搬到了gipsyh.icu...