风控系统之普通规则条件,使用LiteFlow实现
个人博客:无奈何杨(wnhyang)
个人语雀:wnhyang
共享语雀:在线知识共享
Github:wnhyang - Overview
提要
参考:智能风控筑基手册:全面了解风控决策引擎
前面有可配置输入参数的接口如何设计和风控系统指标计算/特征提取分析与实现01,Redis、Zset、模版方法两篇文章,分别提出:
1、风控系统服务动态选择,根据配置处理输入参数,转换为系统参数
2、使用Redis的zset结构完成简单的指标计算(特征提取)
他们都是一次风控决策流程的一部分,当然完成的风控系统,比较复杂,涉及的功能模块更多,以下仅仅是我的简单梳理。

如上,服务选择和入参处理可配置输入参数的接口如何设计是这篇文章讨论的内容,风控系统指标计算/特征提取分析与实现01,Redis、Zset、模版方法讨论的是规则集内普通指标计算。
本篇文章讨论通过LiteFlow这款规则引擎框架实现风控系统的普通规则条件。
规则条件
规则条件是什么?
我将规则划分如下(未来会逐渐完善),规则条件是规则的一部分。

需要注意的是规则条件应该都是灵活可配置,并不是上面这样并列,可以任意复杂的组合。
为什么只是规则条件灵活可配置呢?操作难道不是吗?
可以,但没必要。
如下,是规则(最近24小时转账次数>=10次)示例。

观察可知,其实右半边可以作为一个新的规则独立出去的,所以说,规则操没必要和规则条件混在一起。

规则引擎LiteFlow
规则引擎是为了解耦,编排而生。
LiteFlow官网:🍤LiteFlow简介 | LiteFlow
LiteFlow官方文档写的已经非常清晰,花费不到一上午的时间就可以了完全了解了,所以我也不多说些什么了。
为什么需要规则引擎
因为独立组件+灵活编排的需求和规则引擎不谋而合。
设计与实现
组件
组件是规则引擎中最重要的一部分,他是所有规则表达式最终业务的实现。
不使用规则引擎时
在不使用规则引擎时,针对前面普通规则条件,可以设计如下的结构。
一条规则关联一组规则条件,规则条件又最多分为两级,一级指明了二级规则条件“与或非”关系,二级是具体的规则条件。具体的规则条件关键字段是:系统字段(property)、字段类型(property_data_type)、操作(operator)、希望的值(value)。
有了如下的结构该怎么使用也很清晰了
1、查规则
2、查规则条件组
3、根据父条件,确定子条件关系
4、代码解析操作类型,返回条件结果
| id | uuid | rule_uuid | parent_uuid | logic_operator | property | property_data_type | operator | value |
|---|---|---|---|---|---|---|---|---|
| 1 | 270a8dc859a940008539f270ae596ad6 | 86cbd8adff914f67b576f0046b5b337d | ||||||
| 2 | bfbe53d6b5ae4895aef1c4c453e3e16e | 86cbd8adff914f67b576f0046b5b337d | 270a8dc859a940008539f270ae596ad6 | && | ||||
| 3 | cf46348d533a48db8f027e4db4f6bb7a | 86cbd8adff914f67b576f0046b5b337d | bfbe53d6b5ae4895aef1c4c453e3e16e | S_N_EVENTHOUR | INT | >= | 22 | |
| 4 | f759541121664847bbc7d944ad1a553f | 86cbd8adff914f67b576f0046b5b337d | bfbe53d6b5ae4895aef1c4c453e3e16e | S_N_EVENTHOUR | INT | <= | 24 | |
| 5 | ec1e79cc18734fa4ab3daa51fe8597c8 | 86cbd8adff914f67b576f0046b5b337d | bfbe53d6b5ae4895aef1c4c453e3e16e | C_S_FINANCIALCLIENTS | STRING | == | 是 | |
| 6 | 5a3b35c7d1d04466b16ae2da64383e21 | 86cbd8adff914f67b576f0046b5b337d | 270a8dc859a940008539f270ae596ad6 | && | ||||
| 7 | 1bed61e83d7e4ad0a813f2fa3bd7b8a9 | 86cbd8adff914f67b576f0046b5b337d | 5a3b35c7d1d04466b16ae2da64383e21 | S_N_EVENTHOUR | INT | >= | 0 | |
| 8 | 9446d7a9ec284c1ab52c600ac1cfad26 | 86cbd8adff914f67b576f0046b5b337d | 5a3b35c7d1d04466b16ae2da64383e21 | S_N_EVENTHOUR | INT | < | 6 | |
| 9 | 690e668a2e7c445c80b04ef5e30d3fa4 | 86cbd8adff914f67b576f0046b5b337d | 5a3b35c7d1d04466b16ae2da64383e21 | C_S_FINANCIALCLIENTS | STRING | == | 是 |
使用规则引擎后
首先我们定义组件可以完成字段的比较并返回true/false。
那么上面作为组件的只有id为3、4、5、7、8、9,然后将这些编排成如下表达式即可。
这里简单介绍一些IF表达式,一共三个参数,第一个为条件,后面两个为true执行,false执行,跟三元表达式一样。
IF(OR(AND(3,4,5),AND(7,8,9)),x,y)
是不是很简单,看着确实,但有一个设计必须要搞通,也就是下面我要说的数据上下文。
数据上下文
🍄说明 | LiteFlow
数据上下文这个概念在LiteFlow框架中非常重要,你所有的业务数据都是放在数据上下文中。
要做到可编排,一定是消除每个组件差异性的。如果每个组件出参入参都不一致,那就没法编排了。
LiteFlow对此有独特的设计理念,平时我们写瀑布流的程序时,A调用B,那A一定要把B所需要的参数传递给B,而在LiteFlow框架体系中,每个组件的定义中是不需要接受参数的,也无任何返回的。
每个组件只需要从数据上下文中获取自己关心的数据即可,而不用关心此数据是由谁提供的,同样的,每个组件也只要把自己执行所产生的结果数据放到数据上下文中即可,也不用关心此数据到底是提供给谁用的。这样一来,就从数据层面一定程度的解耦了。从而达到可编排的目的。关于这个理念,也在LiteFlow简介中的设计原则有提到过,给了一个形象的例子,大家可以再去看看。
一旦在数据上下文中放入数据,整个链路中的任一节点都是可以取到的。
我简单说明一下。
如下,表示瀑布流程,从开始到结束,每步调用都需要将数据传递给下一步调用者,完成整个流程。

而对于LiteFlow,更像是下面这样,整个流程存在这样的数据上下文,每个组件只需要去数据上下文中取自己关心的数据,结果也是一样,放进数据上下文即可。

此模式下,要非常注重数据上下文的管理,数据隔离和共享要非常注意。
相同组件数据问题
对于规则条件组件的问题在于:每个规则里的条件非常多,组件该怎么获取当前组件参数(如:appName==Phone)。如IF(OR(AND(3,4,5),AND(7,8,9)),x,y)此表达式转换为我们使用的规则表达式应该是这样的IF(OR(AND(ruleConditionIF,ruleConditionIF,ruleConditionIF),AND(ruleConditionIF,ruleConditionIF,ruleConditionIF)),x,y),ruleConditionIF为规则条件组件。一个表达式中有多个相同的组件意味着他们需要不同的处理,那么数据怎么获取?
LiteFlow提供了三种不同方式:
1、🍉组件参数 | LiteFlow,定义EL表达式时声明数据并传入组件
2、🍍组件标签 | LiteFlow,定义组件tag区别组件
3、🍕私有投递 | LiteFlow,用于私有独有数据传递
下面使用方式二(组件标签)来实现普通条件组件。
表结构与数据
chain表
create table de_chain
(id bigint auto_increment comment '主键'primary key,application_name varchar(32) default '' not null comment '应用名',chain_name varchar(64) default '' not null comment 'chain名',el_data text not null comment 'el数据',enable bit default b'0' not null comment 'chain状态',description varchar(64) charset utf8mb4 default '' null comment '描述',creator varchar(64) charset utf8mb4 default '' null comment '创建者',create_time datetime default CURRENT_TIMESTAMP not null comment '创建时间',updater varchar(64) charset utf8mb4 default '' null comment '更新者',update_time datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',deleted bit default b'0' not null comment '是否删除',constraint uk_codeunique (chain_name)
)comment 'chain表';INSERT INTO coolGuard.de_chain (id, application_name, chain_name, el_data, enable, description, creator, create_time, updater, update_time, deleted) VALUES (1, 'coolGuard', 'mainChain', 'THEN(chain1);', true, '', '', '2024-04-04 22:35:36', '', '2024-04-04 22:40:30', false);
INSERT INTO coolGuard.de_chain (id, application_name, chain_name, el_data, enable, description, creator, create_time, updater, update_time, deleted) VALUES (2, 'coolGuard', 'chain1', 'IF(OR(AND(ruleConditionIf.tag("1"),ruleConditionIf.tag("2")),ruleConditionIf.tag("3")),orderMode,worstMode);', true, '', '', '2024-04-04 22:31:16', '', '2024-04-05 13:25:49', false);
规则条件表
create table de_rule_condition
(id bigint auto_increment comment '主键'primary key,chain_name varchar(64) default '' not null comment 'chain名',field_name varchar(32) default '' not null comment '字段名',operate_type int default 0 not null comment '操作类型',expect_value varchar(32) default '' not null comment '期望值',description varchar(64) charset utf8mb4 default '' null comment '描述',creator varchar(64) charset utf8mb4 default '' null comment '创建者',create_time datetime default CURRENT_TIMESTAMP not null comment '创建时间',updater varchar(64) charset utf8mb4 default '' null comment '更新者',update_time datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',deleted bit default b'0' not null comment '是否删除'
)comment '规则条件表';INSERT INTO coolGuard.de_rule_condition (id, chain_name, field_name, operate_type, expect_value, description, creator, create_time, updater, update_time, deleted) VALUES (1, 'chain3', 'appName', 2, 'Phone', '', '', '2024-04-04 22:32:25', '', '2024-04-05 12:24:03', false);
INSERT INTO coolGuard.de_rule_condition (id, chain_name, field_name, operate_type, expect_value, description, creator, create_time, updater, update_time, deleted) VALUES (2, 'chain4', 'customerId', 2, '123456', '', '', '2024-04-05 12:24:03', '', '2024-04-05 12:24:03', false);
INSERT INTO coolGuard.de_rule_condition (id, chain_name, field_name, operate_type, expect_value, description, creator, create_time, updater, update_time, deleted) VALUES (3, 'chain5', 'money', 5, '15', '', '', '2024-04-05 12:24:03', '', '2024-04-05 12:24:03', false);
依赖
<dependency><groupId>com.yomahub</groupId><artifactId>liteflow-spring-boot-starter</artifactId><version>${liteflow.version}</version>
</dependency>
<dependency><groupId>com.yomahub</groupId><artifactId>liteflow-rule-sql</artifactId><version>${liteflow.version}</version>
</dependency>
<dependency><groupId>com.yomahub</groupId><artifactId>liteflow-script-groovy</artifactId><version>${liteflow.version}</version>
</dependency>
配置
liteflow:rule-source-ext-data-map:url: jdbc:mysql://localhost:3306/coolGuard?allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=truedriverClassName: com.mysql.cj.jdbc.Driverusername: wnhyangpassword: 123456applicationName: ${spring.application.name}#是否开启SQL日志sqlLogEnabled: true#是否开启SQL数据轮询自动刷新机制 默认不开启pollingEnabled: truepollingIntervalSeconds: 60pollingStartSeconds: 60#以下是chain表的配置,这个一定得有chainTableName: de_chainchainApplicationNameField: application_namechainNameField: chain_nameelDataField: el_datachainEnableField: enable#以下是script表的配置,如果你没使用到脚本,下面可以不配置# scriptTableName: script
# scriptApplicationNameField: application_name
# scriptIdField: script_id
# scriptNameField: script_name
# scriptDataField: script_data
# scriptTypeField: script_type
# scriptLanguageField: script_language
# scriptEnableField: enable
自定义数据上下文
可以改善,先这样贴出来。
FieldContext表示经过入参处理后的所有字段集合,后面的规则/指标都会用到的。
/*** @author wnhyang* @date 2024/4/3**/
public class FieldContext {private final Map<String, String> stringFields = new ConcurrentHashMap<>();private final Map<String, Integer> numberFields = new ConcurrentHashMap<>();private final Map<String, Boolean> booleanFields = new ConcurrentHashMap<>();private final Map<String, String> enumFields = new ConcurrentHashMap<>();private final Map<String, LocalDateTime> dateFields = new ConcurrentHashMap<>();private final Map<String, BigDecimal> floatFields = new ConcurrentHashMap<>();public void setStringData(String key, String value) {stringFields.put(key, value);}public String getStringData(String key) {return stringFields.get(key);}public boolean hasStringData(String key) {return stringFields.containsKey(key);}}
普通规则条件组件
当前还不是很完善,TODO已有说明。
对了,我使用的jdk17,所有switch表达式是下面这样,与jdk8有点区别。
/*** @author wnhyang* @date 2024/4/4**/
@Slf4j
@LiteflowComponent
@RequiredArgsConstructor
public class RuleConditionIf extends NodeIfComponent {private final RuleConditionMapper ruleConditionMapper;@Overridepublic boolean processIf() throws Exception {// 获取当前chainNameString tag = this.getTag();log.info("当前tag:{}", tag);// 获取当前chainName对应的条件RuleCondition ruleCondition = ruleConditionMapper.selectById(tag);log.info("当前chainName对应的条件:{}", ruleCondition);// 获取上下文FieldContext fieldContext = this.getContextBean(FieldContext.class);// 获取条件字段String fieldName = ruleCondition.getFieldName();log.info("条件字段:{}", fieldName);// 获取字段值// TODO 支持String、Integer、BigDecimal、Boolean等String stringData = fieldContext.getStringData(fieldName);log.info("字段值:{}", stringData);OperateType byType = OperateType.getByType(ruleCondition.getOperateType());log.info("操作类型:{}", byType);// TODO 当前是常量,之后要考虑变量String expectValue = ruleCondition.getExpectValue();log.info("期望值值:{}", expectValue);return switch (Objects.requireNonNull(byType)) {case NULL:yield StrUtil.isBlank(stringData);case NOT_NULL:yield !StrUtil.isBlank(stringData);case EQ:yield stringData.equals(expectValue);case NOT_EQ:yield !stringData.equals(expectValue);case CONTAINS:yield stringData.contains(expectValue);case NOT_CONTAINS:yield !stringData.contains(expectValue);case GT:yield Integer.parseInt(stringData) > Integer.parseInt(expectValue);case GTE:yield Integer.parseInt(stringData) >= Integer.parseInt(expectValue);case LT, LTE:yield false;case IN:String[] split1 = expectValue.split(",");for (String s : split1) {if (stringData.equals(s)) {yield true;}}case NOT_IN:String[] split2 = expectValue.split(",");for (String s : split2) {if (stringData.equals(s)) {yield false;}}case PREFIX:yield stringData.startsWith(expectValue);case NOT_PREFIX:yield !stringData.startsWith(expectValue);case SUFFIX:yield stringData.endsWith(expectValue);case NOT_SUFFIX:yield !stringData.endsWith(expectValue);};}
}
操作类型枚举
/*** @author wnhyang* @date 2024/4/3**/
@AllArgsConstructor
@Getter
public enum OperateType {NULL(0),NOT_NULL(1),EQ(2),NOT_EQ(3),GT(4),GTE(5),LT(6),LTE(7),IN(8),NOT_IN(9),CONTAINS(10),NOT_CONTAINS(11),PREFIX(12),NOT_PREFIX(13),SUFFIX(14),NOT_SUFFIX(15);private final Integer type;public static OperateType getByType(Integer type) {for (OperateType operateType : OperateType.values()) {if (operateType.getType().equals(type)) {return operateType;}}return null;}
}
测试
@Slf4j
@RestController
@RequestMapping("/field")
@RequiredArgsConstructor
public class FieldController {private final FieldService fieldService;private final FlowExecutor flowExecutor;@GetMapping("/test")public CommonResult<String> test(@RequestParam("appName") String appName, @RequestParam("customerId") String customerId, @RequestParam("money") String money) {FieldContext fieldContext = new FieldContext();fieldContext.setStringData("appName", appName);fieldContext.setStringData("customerId", customerId);fieldContext.setStringData("money", money);LiteflowResponse main1 = flowExecutor.execute2Resp("mainChain", null, fieldContext);log.info(String.valueOf(main1));return success("test");}
}

结果
mainChain的EL表达式为THEN(chain1);,chain1为子流程
IF(OR(AND(ruleConditionIf.tag("1"),ruleConditionIf.tag("2")),ruleConditionIf.tag("3")),orderMode,worstMode);,ruleConditionIf为上面的规则条件组件。
最终应该是这样的:THEN(IF(OR(AND(ruleConditionIf.tag("1"),ruleConditionIf.tag("2")),ruleConditionIf.tag("3")),orderMode,worstMode));

参数为:appName:Phone,customerId:235246,money:35时
此时执行流程为:ruleConditionIf<17>==>ruleConditionIf<2>==>ruleConditionIf<2>==>orderMode<0>
参数为:appName:Phone,customerId:235246,money:3时
此时执行流程为:ruleConditionIf<42>==>ruleConditionIf<2>==>ruleConditionIf<1>==>worstMode<0>

总结
LiteFlow可玩性还是很强的,未来我还会继续完善打造自己设计并实现的风控系统。冲冲冲!!!
写在最后
拙作艰辛,字句心血,望诸君垂青,多予支持,不胜感激。
个人博客:无奈何杨(wnhyang)
个人语雀:wnhyang
共享语雀:在线知识共享
Github:wnhyang - Overview

相关文章:
风控系统之普通规则条件,使用LiteFlow实现
个人博客:无奈何杨(wnhyang) 个人语雀:wnhyang 共享语雀:在线知识共享 Github:wnhyang - Overview 提要 参考:智能风控筑基手册:全面了解风控决策引擎 前面有可配置输入参数的接…...
在一套Dockerfile中完成编译和运行环境部署
大纲 解释型语言编译环境解释环境编译型语言编译环境运行环境 方法编译环境安装系统安装编译依赖下载代码特殊处理(可以忽略)编译准备(可以忽略)编译打包依赖(编译结果) 运行环境安装操作系统安装运行时依赖…...
ubuntu系统里克隆github代码到本地,提示fatal: unable to connect to github.com的解决方案
打开命令行终端生成一个新的SSH密钥对。如果你还没有SSH密钥或者想创建一个新的,可以使用以下命令: ssh-keygen -t rsa -b 4096 -C "your_emailexample.com"当系统提示你“Enter a file in which to save the key”,时,…...
常见docker使用命令
#搭建镜像 “”" sudo docker build -t es_refresh:V1.20230303 . “”" #启动容器 “”" docker run -d --namepara_classify -v /etc/localtime:/etc/localtime -v /data/chenhw/multi_label_classification:/edb2vec -p 8066:8066 --gpus ‘“device0”’…...
Ubuntu系统中设置中文输入法的教程
1、Ubuntu介绍: (https://cn.ubuntu.com/) (Ubuntu | 全球领先的用于个人电脑、平板及手机的操作系统) Ubuntu是一款基于Debian的开源Linux操作系统,由英国Canonical公司赞助支持的全球性社区共同开发。U…...
练习14 Web [极客大挑战 2019]Upload
phtml格式绕过,burp修改content-type绕过,常见的文件上传存放目录名 题目就叫upload,打开靶机 直接上传一个图片格式的一句话木马,返回如下: 提交练习5和9中的两种可以执行图片格式php代码的文件,修改con…...
3.6k star, 免费开源跨平台的数据库管理工具 dbgate
3.6k star, 免费开源跨平台的数据库管理工具 dbgate 分类 开源分享 项目名: dbgate -- 免费开源跨平台的数据库管理工具 Github 开源地址: GitHub - dbgate/dbgate: Database manager for MySQL, PostgreSQL, SQL Server, MongoDB, SQLite and others. Runs under…...
2024.3.2力扣每日一题——受限条件下可到达节点的数目
2024.3.2 题目来源我的题解方法一 深度优先搜索方法二 并查集 题目来源 力扣每日一题;题序:2368 我的题解 方法一 深度优先搜索 使用深度优先搜索实现,在搜索过程中根据restricted进行截停。 时间复杂度:O(n) 空间复杂度&#…...
在云端遇见雨云:一位服务器寻觅者的指南
引言:寻觅一座云端归宿 当我踏入数字世界的边缘,带着对网络的探索与期待,我迫切需要一座安全可靠的数字栖息地。云计算技术正如一场魔法般的变革,而在这片广袤的云端中,雨云就像是一位友善的向导,引领我穿越…...
Pygame基础10-物理模拟
PyMunk PyMunk是一个模拟物理的库。 注意,PyMunk只是进行物理模拟,不包含可视化的功能。如果需要可视化,可使用pygame等库。 可用pip安装pymunk pip install pymunk pymunk中的概念: space: 物理空间。 包含gravity 模…...
蓝桥杯 --- 日期问题模板
目录 1.如何判断闰年 2.如何遍历当前年份的每一天 3.如果想要输出某一年某一天到某一年某一天之间一共有多少天。 4.精确到具体周几到周几的问题分析 5.如何直接通过一层for循环枚举年月日 习题: 蓝桥杯竞赛特别喜欢考日期问题,今天给大家分享一下…...
Java 处理Mysql获取树形的数据
Mysql数据: 代码如下: Entity: Data Accessors(chain true) public class Region {private BigInteger id;//名称private String name;//父idprivate BigInteger parentId;private List<Region> children;private Integer createTim…...
前端三剑客 —— CSS ( 坐标问题 、定位问题和图片居中 )
前期内容回顾: 1.常见样式 text-shadow x轴 y轴 阴影的模糊程度 阴影的颜色 box-shadow border-radio 实现圆角 margin 内边距 padding 外边距 background 2.特殊样式 媒体查询:media 自定义字体:font-face { font-family:自定义名称&#…...
向量数据库 | AI时代的航道灯塔
向量数据库 | AI时代的航道灯塔 什么是向量检索服务拍照搜商品 你使用过向量数据库吗?使用体验?为什么向量数据库能借由大模型引起众多关注向量数据库在当前AI热潮中是昙花一现,还是未来AI时代的航道灯塔? 今天的话题主要是讨论向…...
Linux中的conntrack命令深入解析
在Linux网络管理和监控领域,conntrack命令是一个强大的工具,它提供了对netfilter连接跟踪系统的直接访问🔍。这篇文章将深入探讨conntrack的由来、底层原理、参数意义,以及其常见用法,并对返回结果的每个字段进行详细解…...
反截屏控制技术如何防止信息通过手机拍照泄漏?
反截屏控制技术为企业数据安全提供了重要的防护措施。通过以下几点,有效阻止了信息通过拍照等方式的泄漏: 反截屏控制开启,用户启动截屏操作时,允许非涉密内容截屏操作,但所有涉密内容窗口会自动隐藏,防止涉…...
0.k8s简介
目录 k8s是什么 k8s不是什么 云原生 微服务 整体式架构与微服务架构 微服务的特性 微服务的优势 k8s是什么 Kubernetes 是一个可移植、可扩展的开源平台,用于管理容器化的工作负载和服务,可促进声明式配置和自动化。 Kubernetes 拥有一个庞大且快…...
VScode 集成终端设置默认打开当前文件夹 mac系统
一.快捷键设置 搜索 openInIntegratedTerminal 如图: 二.设置cmd 默认打开位置 点击设置 搜索 ntegrated:cwd 如下图: 三.查看ip 快捷指令: ipconfig getifaddr en0...
HDLbits 刷题 -- Alwaysblock2
学习: For hardware synthesis, there are two types of always blocks that are relevant: Combinational: always (*)Clocked: always (posedge clk) Clocked always blocks create a blob of combinational logic just like combinational always blocks, but…...
一、Docker部署GitLab(详细步骤)
Docker部署GitLab(详细步骤) 一、拉取镜像二、启动容器三、修改配置四、修改密码五、浏览器访问 一、拉取镜像 docker安装教程:https://qingsi.blog.csdn.net/article/details/131270071 docker pull gitlab/gitlab-ce:latest二、启动容器 …...
linux之kylin系统nginx的安装
一、nginx的作用 1.可做高性能的web服务器 直接处理静态资源(HTML/CSS/图片等),响应速度远超传统服务器类似apache支持高并发连接 2.反向代理服务器 隐藏后端服务器IP地址,提高安全性 3.负载均衡服务器 支持多种策略分发流量…...
【SQL学习笔记1】增删改查+多表连接全解析(内附SQL免费在线练习工具)
可以使用Sqliteviz这个网站免费编写sql语句,它能够让用户直接在浏览器内练习SQL的语法,不需要安装任何软件。 链接如下: sqliteviz 注意: 在转写SQL语法时,关键字之间有一个特定的顺序,这个顺序会影响到…...
Nginx server_name 配置说明
Nginx 是一个高性能的反向代理和负载均衡服务器,其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机(Virtual Host)。 1. 简介 Nginx 使用 server_name 指令来确定…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
中医有效性探讨
文章目录 西医是如何发展到以生物化学为药理基础的现代医学?传统医学奠基期(远古 - 17 世纪)近代医学转型期(17 世纪 - 19 世纪末)现代医学成熟期(20世纪至今) 中医的源远流长和一脉相承远古至…...
深入浅出深度学习基础:从感知机到全连接神经网络的核心原理与应用
文章目录 前言一、感知机 (Perceptron)1.1 基础介绍1.1.1 感知机是什么?1.1.2 感知机的工作原理 1.2 感知机的简单应用:基本逻辑门1.2.1 逻辑与 (Logic AND)1.2.2 逻辑或 (Logic OR)1.2.3 逻辑与非 (Logic NAND) 1.3 感知机的实现1.3.1 简单实现 (基于阈…...
GO协程(Goroutine)问题总结
在使用Go语言来编写代码时,遇到的一些问题总结一下 [参考文档]:https://www.topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/goroutine.html 1. main()函数默认的Goroutine 场景再现: 今天在看到这个教程的时候,在自己的电…...
django blank 与 null的区别
1.blank blank控制表单验证时是否允许字段为空 2.null null控制数据库层面是否为空 但是,要注意以下几点: Django的表单验证与null无关:null参数控制的是数据库层面字段是否可以为NULL,而blank参数控制的是Django表单验证时字…...
零知开源——STM32F103RBT6驱动 ICM20948 九轴传感器及 vofa + 上位机可视化教程
STM32F1 本教程使用零知标准板(STM32F103RBT6)通过I2C驱动ICM20948九轴传感器,实现姿态解算,并通过串口将数据实时发送至VOFA上位机进行3D可视化。代码基于开源库修改优化,适合嵌入式及物联网开发者。在基础驱动上新增…...
算法打卡第18天
从中序与后序遍历序列构造二叉树 (力扣106题) 给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。 示例 1: 输入:inorder [9,3,15,20,7…...
