spring boot集成neo4j实现简单的知识图谱

一、neo4j介绍

随着社交、电商、金融、零售、物联网等行业的快速发展,现实社会织起了了一张庞大而复杂的关系网,传统数据库很难处理关系运算。大数据行业需要处理的数据之间的关系随数据量呈几何级数增长,急需一种支持海量复杂数据关系运算的数据库,图数据库应运而生。 世界上很多著名的公司都在使用图数据库,比如:社交领域:Facebook, Twitter,Linkedin用它来管理社交关系,实现好友推荐

二、图数据库neo4j安装

  1. 下载镜像:docker pull neo4j:3.5.0

  2. 运行容器:docker run -d -p 7474:7474 -p 7687:7687 --name neo4j-3.5.0 neo4j:3.5.0

  3. 停止容器:docker stop neo4j-3.5.0

  4. 启动容器:docker start neo4j-3.5.0

  5. 浏览器 http://localhost:7474/ 访问 neo4j 管理后台,初始账号/密码 neo4j/neo4j,会要求修改初始化密码,我们修改为 neo4j/123456

三、简单CQL入门

就像我们平常使用关系型数据库中的SQL语句一样,neo4j中可以使用Cypher查询语言(CQL)进行图形数据库的查询,我们简单来看一下增删改查的用法。

添加节点

在CQL中,可以通过CREATE命令去创建一个节点,创建不含有属性节点的语法如下:

 
 
CREATE (<node-name>:<label-name>)

CREATE语句中,包含两个基础元素,节点名称node-name和标签名称lable-name。标签名称相当于关系型数据库中的表名,而节点名称则代指这一条数据。 以下面的CREATE语句为例,就相当于在Person这张表中创建一条没有属性的空数据。

 
 
CREATE (索尔:Person)

而创建包含属性的节点时,可以在标签名称后面追加一个描绘属性的json字符串:

 
 
CREATE (
   <node-name>:<label-name>
   {    
      <key1>:<value1>,
      …
      <keyN>:<valueN>
   }
)

用下面的语句创建一个包含属性的节点:

 
 
CREATE (洛基:Person {name:"洛基",title:"诡计之神"})

查询节点

在创建完节点后,我们就可以使用MATCH匹配命令查询已存在的节点及属性的数据,命令的格式如下:

 
 
MATCH (<node-name>:<label-name>)

通常,MATCH命令在后面配合RETURNDELETE等命令使用,执行具体的返回或删除等操作。 执行下面的命令:

 
 
MATCH (p:Person) RETURN p

查看可视化的显示结果:3b61cb9f653efdc9b3d7e5ccba14d6b6.png可以看到上面添加的两个节点,分别是不包含属性的空节点和包含属性的节点,并且所有节点会有一个默认生成的id作为唯一标识。

删除节点

接下来,我们删除之前创建的不包含属性的无用节点,上面提到过,需要使用MATCH配合DELETE进行删除。

 
 
MATCH (p:Person) WHERE id(p)=100 
DELETE p

在这条删除语句中,额外使用了WHERE过滤条件,它与SQL中的WHERE非常相似,命令中通过节点的id进行了过滤。 删除完成后,再次执行查询操作,可以看到只保留了洛基这一个节点

添加关联

在neo4j图数据库中,遵循属性图模型来存储和管理数据,也就是说我们可以维护节点之间的关系。 在上面,我们创建过一个节点,所以还需要再创建一个节点作为关系的两端:

 
 
CREATE (p:Person {name:"索尔",title:"雷神"})

创建关系的基本语法如下:

 
 
CREATE (<node-name1>:<label-name1>) 
- [<relation-name>:<relation-label-name>]
-> (<node-name2>:<label-name2>)

当然,也可以利用已经存在的节点创建关系,下面我们借助MATCH先进行查询,再将结果进行关联,创建两个节点之间的关联关系:

 
 
MATCH (m:Person),(n:Person) 
WHERE m.name='索尔' and n.name='洛基' 
CREATE (m)-[r:BROTHER {relation:"无血缘兄弟"}]->(n)
RETURN r

添加完成后,可以通过关系查询符合条件的节点及关系:

 
 
MATCH (m:Person)-[re:BROTHER]->(n:Person) 
RETURN m,re,n

可以看到两者之间已经添加了关联:8d55a04deb023a8ca2c13f8a08c1b595.png需要注意的是,如果节点被添加了关联关系后,单纯删除节点的话会报错,:

 
 
Neo.ClientError.Schema.ConstraintValidationFailed
Cannot delete node<85>, because it still has relationships. To delete this node, you must first delete its relationships.

这时,需要在删除节点时同时删除关联关系:

 
 
MATCH (m:Person)-[r:BROTHER]->(n:Person) 
DELETE m,r

执行上面的语句,就会在删除节点的同时,删除它所包含的关联关系了。 那么,简单的cql语句入门到此为止,它已经基本能够满足我们的简单业务场景了,下面我们开始在springboot中整合neo4j。

四、springboot整合neo4j

 pom.xml

 
 
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springboot-demo</artifactId>
        <groupId>com.et</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>


    <artifactId>neo4j</artifactId>


    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <dependencies>


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-neo4j</artifactId>
        </dependency>


        <dependency>
            <groupId>com.hankcs</groupId>
            <artifactId>hanlp</artifactId>
            <version>portable-1.2.4</version>
        </dependency>
        <dependency>
            <groupId>edu.stanford.nlp</groupId>
            <artifactId>stanford-parser</artifactId>
            <version>3.3.1</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>
</project>

属性文件

 
 
server:
  port: 8088
spring:
  data:
    neo4j:
      uri: bolt://127.0.0.1:7687
      username: neo4j
      password: 123456

文本SPO抽取

在项目中构建知识图谱时,很大一部分场景是基于非结构化的数据,而不是由我们手动输入确定图谱中的节点或关系。因此,我们需要基于文本进行知识抽取的能力,简单来说就是要在一段文本中抽取出SPO主谓宾三元组,来构成图谱中的点和边。 这里我们借助Git上一个现成的工具类,来进行文本的语义分析和SPO三元组的抽取工作,

项目地址:https://github.com/hankcs/MainPartExtracto

 
 
package com.et.neo4j.hanlp;


import com.et.neo4j.util.GraphUtil;
import com.hankcs.hanlp.HanLP;
import com.hankcs.hanlp.seg.common.Term;
import edu.stanford.nlp.ling.Word;
import edu.stanford.nlp.parser.lexparser.LexicalizedParser;
import edu.stanford.nlp.trees.*;
import edu.stanford.nlp.trees.international.pennchinese.ChineseTreebankLanguagePack;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


import java.util.Collection;
import java.util.LinkedList;
import java.util.List;


/**
 * 提取主谓宾
 *
 * @author hankcs
 */
public class MainPartExtractor
{


    private static final Logger LOG = LoggerFactory.getLogger(MainPartExtractor.class);
    private static LexicalizedParser lp;
    private static GrammaticalStructureFactory gsf;
    static
    {
        //模型
        String models = "models/chineseFactored.ser";
        LOG.info("载入文法模型:" + models);
        lp = LexicalizedParser.loadModel(models);
        //汉语
        TreebankLanguagePack tlp = new ChineseTreebankLanguagePack();
        gsf = tlp.grammaticalStructureFactory();
    }


    /**
     * 获取句子的主谓宾
     *
     * @param sentence 问题
     * @return 问题结构
     */
    public static MainPart getMainPart(String sentence)
    {
        // 去掉不可见字符
        sentence = sentence.replace("\\s+", "");
        // 分词,用空格隔开
        List<Word> wordList = seg(sentence);
        return getMainPart(wordList);
    }


    /**
     * 获取句子的主谓宾
     *
     * @param words    HashWord列表
     * @return 问题结构
     */
    public static MainPart getMainPart(List<Word> words)
    {
        MainPart mainPart = new MainPart();
        if (words == null || words.size() == 0) return mainPart;
        Tree tree = lp.apply(words);
        LOG.info("句法树:{}", tree.pennString());
        // 根据整个句子的语法类型来采用不同的策略提取主干
        switch (tree.firstChild().label().toString())
        {
            case "NP":
                // 名词短语,认为只有主语,将所有短NP拼起来作为主语即可
                mainPart = getNPPhraseMainPart(tree);
                break;
            default:
                GrammaticalStructure gs = gsf.newGrammaticalStructure(tree);
                Collection<TypedDependency> tdls = gs.typedDependenciesCCprocessed(true);
                LOG.info("依存关系:{}", tdls);
                TreeGraphNode rootNode = getRootNode(tdls);
                if (rootNode == null)
                {
                    return getNPPhraseMainPart(tree);
                }
                LOG.info("中心词语:", rootNode);
                mainPart = new MainPart(rootNode);
                for (TypedDependency td : tdls)
                {
                    // 依存关系的出发节点,依存关系,以及结束节点
                    TreeGraphNode gov = td.gov();
                    GrammaticalRelation reln = td.reln();
                    String shortName = reln.getShortName();
                    TreeGraphNode dep = td.dep();
                    if (gov == rootNode)
                    {
                        switch (shortName)
                        {
                            case "nsubjpass":
                            case "dobj":
                            case "attr":
                                mainPart.object = dep;
                                break;
                            case "nsubj":
                            case "top":
                                mainPart.subject = dep;
                                break;
                        }
                    }
                    if (mainPart.object != null && mainPart.subject != null)
                    {
                        break;
                    }
                }
                // 尝试合并主语和谓语中的名词性短语
                combineNN(tdls, mainPart.subject);
                combineNN(tdls, mainPart.object);
                if (!mainPart.isDone()) mainPart.done();
        }


        return mainPart;
    }


    private static MainPart getNPPhraseMainPart(Tree tree)
    {
        MainPart mainPart = new MainPart();
        StringBuilder sbResult = new StringBuilder();
        List<String> phraseList = getPhraseList("NP", tree);
        for (String phrase : phraseList)
        {
            sbResult.append(phrase);
        }
        mainPart.result = sbResult.toString();
        return mainPart;
    }


    /**
     * 从句子中提取最小粒度的短语
     * @param type
     * @param sentence
     * @return
     */
    public static List<String> getPhraseList(String type, String sentence)
    {
        return getPhraseList(type, lp.apply(seg(sentence)));
    }


    private static List<String> getPhraseList(String type, Tree tree)
    {
        List<String> phraseList = new LinkedList<String>();
        for (Tree subtree : tree)
        {
            if(subtree.isPrePreTerminal() && subtree.label().value().equals(type))
            {
                StringBuilder sbResult = new StringBuilder();
                for (Tree leaf : subtree.getLeaves())
                {
                    sbResult.append(leaf.value());
                }
                phraseList.add(sbResult.toString());
            }
        }
        return phraseList;
    }


    /**
     * 合并名词性短语为一个节点
     * @param tdls 依存关系集合
     * @param target 目标节点
     */
    private static void combineNN(Collection<TypedDependency> tdls, TreeGraphNode target)
    {
        if (target == null) return;
        for (TypedDependency td : tdls)
        {
            // 依存关系的出发节点,依存关系,以及结束节点
            TreeGraphNode gov = td.gov();
            GrammaticalRelation reln = td.reln();
            String shortName = reln.getShortName();
            TreeGraphNode dep = td.dep();
            if (gov == target)
            {
                switch (shortName)
                {
                    case "nn":
                        target.setValue(dep.toString("value") + target.value());
                        return;
                }
            }
        }
    }


    private static TreeGraphNode getRootNode(Collection<TypedDependency> tdls)
    {
        for (TypedDependency td : tdls)
        {
            if (td.reln() == GrammaticalRelation.ROOT)
            {
                return td.dep();
            }
        }


        return null;
    }


    /**
     * 分词
     *
     * @param sentence 句子
     * @return 分词结果
     */
    private static List<Word> seg(String sentence)
    {
        //分词
        LOG.info("正在对短句进行分词:" + sentence);
        List<Word> wordList = new LinkedList<>();
        List<Term> terms = HanLP.segment(sentence);
        StringBuffer sbLogInfo = new StringBuffer();
        for (Term term : terms)
        {
            Word word = new Word(term.word);
            wordList.add(word);
            sbLogInfo.append(word);
            sbLogInfo.append(' ');
        }
        LOG.info("分词结果为:" + sbLogInfo);
        return wordList;
    }


    public static MainPart getMainPart(String sentence, String delimiter)
    {
        List<Word> wordList = new LinkedList<>();
        for (String word : sentence.split(delimiter))
        {
            wordList.add(new Word(word));
        }
        return getMainPart(wordList);
    }


    /**
     * 调用演示
     * @param args
     */
    public static void main(String[] args)
    {
       /* String[] testCaseArray = {
                "我一直很喜欢你",
                "你被我喜欢",
                "美丽又善良的你被卑微的我深深的喜欢着……",
                "只有自信的程序员才能把握未来",
                "主干识别可以提高检索系统的智能",
                "这个项目的作者是hankcs",
                "hankcs是一个无门无派的浪人",
                "搜索hankcs可以找到我的博客",
                "静安区体育局2013年部门决算情况说明",
                "这类算法在有限的一段时间内终止",
        };
        for (String testCase : testCaseArray)
        {
            MainPart mp = MainPartExtractor.getMainPart(testCase);
            System.out.printf("%s\t%s\n", testCase, mp);


        }*/
        mpTest();
    }
    public static void mpTest(){
        String[] testCaseArray = {
                "我一直很喜欢你",
                "你被我喜欢",
                "美丽又善良的你被卑微的我深深的喜欢着……",
                "小米公司主要生产智能手机",
                "他送给了我一份礼物",
                "这类算法在有限的一段时间内终止",
                "如果大海能够带走我的哀愁",
                "天青色等烟雨,而我在等你",
                "我昨天看见了一个非常可爱的小孩"
        };
        for (String testCase : testCaseArray) {
            MainPart mp = MainPartExtractor.getMainPart(testCase);
            System.out.printf("%s   %s   %s \n",
                    GraphUtil.getNodeValue(mp.getSubject()),
                    GraphUtil.getNodeValue(mp.getPredicate()),
                    GraphUtil.getNodeValue(mp.getObject()));
        }
    }
}

动态构建知识图谱

在上面的基础上,我们就可以在项目中动态构建知识图谱了,新建一个NodeServiceImpl,其中实现两个关键方法parseAndBind和addNode 首先是根据句子中抽取的主语或宾语在neo4j中创建节点的方法,这里根据节点的name判断是否为已存在的节点,如果存在则直接返回,不存在则添加:

 
 
package com.et.neo4j.service;


import com.et.neo4j.entity.Node;
import com.et.neo4j.entity.Relation;
import com.et.neo4j.hanlp.MainPart;
import com.et.neo4j.hanlp.MainPartExtractor;
import com.et.neo4j.repository.NodeRepository;
import com.et.neo4j.repository.RelationRepository;
import com.et.neo4j.util.GraphUtil;
import edu.stanford.nlp.trees.TreeGraphNode;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import sun.plugin.dom.core.Attr;


import java.util.Arrays;
import java.util.List;
import java.util.Objects;


@Service
@AllArgsConstructor
public class NodeServiceImpl implements NodeService {
    private final NodeRepository nodeRepository;
    private final RelationRepository relationRepository;




    @Override
    public Node save(Node node) {
        Node save = nodeRepository.save(node);
        return save;
    }
    @Override
    public void bind(String name1, String name2, String relationName) {
        Node start = nodeRepository.findByName(name1);
        Node end = nodeRepository.findByName(name2);


        Relation relation =new Relation();
        relation.setStartNode(start);
        relation.setEndNode(end);
        relation.setRelation(relationName);


        relationRepository.save(relation);
    }
    private Node addNode(TreeGraphNode treeGraphNode){


        String nodeName = GraphUtil.getNodeValue(treeGraphNode);


        Node existNode = nodeRepository.findByName(nodeName);
        if (Objects.nonNull(existNode))
            return existNode;


        Node node =new Node();
        node.setName(nodeName);
        return nodeRepository.save(node);
    }
    @Override
    public List<Relation> parseAndBind(String sentence) {
        MainPart mp = MainPartExtractor.getMainPart(sentence);


        TreeGraphNode subject = mp.getSubject();    //主语
        TreeGraphNode predicate = mp.getPredicate();//谓语
        TreeGraphNode object = mp.getObject();      //宾语


        if (Objects.isNull(subject) || Objects.isNull(object))
            return null;


        Node startNode = addNode(subject);
        Node endNode = addNode(object);
        String relationName = GraphUtil.getNodeValue(predicate);//关系词


        List<Relation> oldRelation = relationRepository
                .findRelation(startNode, endNode,relationName);
        if (!oldRelation.isEmpty())
            return oldRelation;


        Relation botRelation=new Relation();
        botRelation.setStartNode(startNode);
        botRelation.setEndNode(endNode);
        botRelation.setRelation(relationName);
        Relation relation = relationRepository.save(botRelation);


        return Arrays.asList(relation);
    }


}

本文只是拿出关键代码作讲解,具体的代码参考代码仓库地址里面neo4j模块

代码仓库

  • https://github.com/Harries/springboot-demo

五、测试

启动java应用,输入以下地址

http://127.0.0.1:8088/parse?sentence=海拉又被称为死亡女神
http://127.0.0.1:8088/parse?sentence= 死亡女神捏碎了雷神之锤
http://127.0.0.1:8088/parse?sentence=雷神之锤属于索尔

在图数据库neo4j里面查询

MATCH (p:Person) RETURN p

71a67517f23271460bedd565ccf41d2f.png

六、引用

  • https://www.cnblogs.com/trunks2008/p/16706962.html

  • http://www.liuhaihua.cn/archives/710286.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/447398.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

每日OJ题_链表⑤_力扣25. K 个一组翻转链表

目录 力扣25. K 个一组翻转链表 解析代码 力扣25. K 个一组翻转链表 25. K 个一组翻转链表 难度 困难 给你链表的头节点 head &#xff0c;每 k 个节点一组进行翻转&#xff0c;请你返回修改后的链表。 k 是一个正整数&#xff0c;它的值小于或等于链表的长度。如果节点总…

java集合类常用的方法介绍

在 Java 中&#xff0c;集合&#xff08;Collections&#xff09;是用于存储多个元素的容器。Java Collections Framework 提供了丰富的集合类&#xff0c;用于满足不同的数据存储需求。以下是一些常用的 Java 集合类及其常用方法&#xff0c;以及简单的例子来说明它们的用法。…

AI 对齐是未来十年最重要的科学和社会技术工程 | 新程序员

【导读】人工智能与机器学习技术犹如疾风骤雨般席卷全球&#xff0c;在颠覆传统的同时为人类带来了新一轮的伦理挑战。AI 模型虽能凭借强大的数据处理能力和优化效率在各个行业大放异彩&#xff0c;然而在追求极致准确性的模型行为背后&#xff0c;却存在与其设计初衷产生偏差的…

2024-03-10 c++

&#x1f338; MFC下拉框控件 | Combo Box eg 计算器 1。新建MFC项目&#xff08;基于对话框、静态库&#xff09; 2。添加控件&#xff0c;删除初始的3个多余控件 加3个edit control 加1个combo box&#xff0c;属性sort改为false&#xff0c;data为 ;-;;;% 加1个static text…

【数据结构】红黑树(C++实现)

&#x1f440;樊梓慕&#xff1a;个人主页 &#x1f3a5;个人专栏&#xff1a;《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》《C》《Linux》《算法》 &#x1f31d;每一个不曾起舞的日子&#xff0c;都是对生命的辜负 目录 前言 1.概念 2.性质 3.…

企业微信HOOK协议,新设备二次验证处理

提示设备强制二次验证问题已处理 HOOK&#xff1a;https://www.showdoc.com.cn/1663062930779972/7859611259700402密码&#xff1a;999999999

蓝桥杯练习系统(算法训练)ALGO-979 移动

资源限制 内存限制&#xff1a;256.0MB C/C时间限制&#xff1a;1.0s Java时间限制&#xff1a;3.0s Python时间限制&#xff1a;5.0s 问题描述 给定一个n长的数列&#xff0c;有m次操作&#xff0c;第i次操作表示将整个数列循环移动mi位&#xff0c;询问每次操作结束后…

前端解决跨域问题( 6种方法 )

本专栏是汇集了一些HTML常常被遗忘的知识&#xff0c;这里算是温故而知新&#xff0c;往往这些零碎的知识点&#xff0c;在你开发中能起到炸惊效果。我们每个人都没有过目不忘&#xff0c;过久不忘的本事&#xff0c;就让这一点点知识慢慢渗透你的脑海。 本专栏的风格是力求简洁…

【MATLAB源码-第160期】基于matlab的胡桃夹子优化算法(NOA)无人机三维路径规划,输出做短路径图和适应度曲线

操作环境&#xff1a; MATLAB 2022a 1、算法描述 胡桃夹子优化算法&#xff08;Nutcracker Optimization Algorithm, NOA&#xff09;是一个灵感来源于胡桃夹子的故事的元启发式优化算法。这个故事中&#xff0c;胡桃夹子是一个能够将坚果壳轻易地破开以获取内部果仁的工具。…

linux系统adb调试工具

adb的全称为Android Debug Bridge&#xff0c;就是起到调试桥的作用。通过adb可以在Eclipse中通过DDMS来调试Android程序&#xff0c;说白了就是调试工具。 adb的工作方式比较特殊&#xff0c;采用监听Socket TCP 5554等端口的方式让IDE和Qemu通讯&#xff0c;默认情况下adb会…

Rust接收命令行参数和新建文件读写和追加操作与IO

接收命令行参数 命令行程序是计算机程序最基础的存在形式&#xff0c;几乎所有的操作系统都支持命令行程序并将可视化程序的运行基于命令行机制。 命令行程序必须能够接收来自命令行环境的参数&#xff0c;这些参数往往在一条命令行的命令之后以空格符分隔。 在很多语言中&a…

145.乐理基础-增三和弦、减三和弦

内容参考于&#xff1a;三分钟音乐社 上一个内容&#xff1a;144.根三五音、大三和弦、小三和弦 上一个内容里练习的答案&#xff1a; 增三和弦与减三和弦的结构 增三和弦例子&#xff1a; 下图红框里的乐谱是c、e、升g&#xff0c;这个和弦&#xff0c;c-e是大三度&#xff…

_note_06

1.说一说函数的按地址传递和按值传递&#xff0c;他们的区别是什么&#xff1f; 函数的参数传递方式可以分为按地址传递&#xff08;也称为按引用传递&#xff09;和按值传递两种方式。按值传递是指将实际参数的值复制给形式参数&#xff0c;即在函数调用时&#xff0c;实际参数…

Ps:画笔工具

画笔工具 Brush Tool是 Photoshop 中最常用的工具&#xff0c;可广泛地用于绘画与修饰工作之中。 快捷键&#xff1a;B ◆ ◆ ◆ 常用操作方法与技巧 1、熟练掌握画笔工具的操作对于使用其他工具也非常有益&#xff0c;因为 Photoshop 中许多与笔刷相关的工具有类似的选项和操…

Nestjs与Vue实现多人聊天[简易版]

本项目是一个小demo,帮助各位理清一点开发思路&#xff0c;作为一个小参考&#xff0c;虽然技术栈是nodejs。但是其他语言也是相通的。 准备环境&#xff1a; Nodejs version >18.13.0Vue3Nestjssoket.io 一、初始化 打开一个路径启动cmd窗口&#xff0c;初始化前后端项…

智慧城市的前景:数字孪生技术在智慧城市中的应用前景

目录 一、引言 二、数字孪生技术及其在智慧城市中的应用概述 三、数字孪生技术在智慧城市中的应用前景 1、城市规划与仿真模拟 2、智能交通与出行服务 3、智慧环保与可持续发展 4、智慧公共服务与社会治理 5、智慧能源与绿色建筑 四、数字孪生技术在智慧城市中的挑战与…

CSS 入门指南(二)CSS 常用样式及注册页面案例

CSS 常用样式 颜色属性 常见样式的颜色属性&#xff1a; color&#xff1a;定义文本的颜色border-color&#xff1a;定义边框的颜色background-color&#xff1a;设置背景色 颜色属性值设置方式&#xff1a; 十六进制值 - 如&#xff1a;&#xff03;FF0000一个RGB值 - 如…

冬去春来天气阴晴不定 美食拿捏味蕾安稳换季

俗话说“春打六九头”&#xff0c;3月虽然已经入春&#xff0c;但是天气依然是凉飕飕的 &#xff0c;冬天春天的换季期&#xff0c;因为天气的变化&#xff0c;尤为痛苦。但是来到了换季期&#xff0c;天气也不总是那么稳定&#xff0c;随着气温的起伏&#xff0c;我们的食欲也…

Orange3数据预处理(预处理器组件)

1.组件介绍 Orange3 提供了一系列的数据预处理工具&#xff0c;这些工具可以帮助用户在数据分析之前准备好数据。以下是您请求的预处理组件的详细解释&#xff1a; Discretize Continuous Variables&#xff08;离散化连续变量&#xff09;&#xff1a; 这个组件将连续变量转…