GraphQL入门有

本文将从GraphQL是什么,为什么要使用GraphQL,使用GraphQL创建简单例子,以及GraphQL实战,四个方面对GraphQL进行阐述。说得不对的地方,希望大家指出斧正。

github项目地址:https://github.com/Charming2015/graphql-todolist

一、GraphQL是什么?


关于GraphQL是什么,网上一搜一大堆。根据官网的解释就是一种用于 API 的查询语言

一看到用于API的查询语言,我也是一脸懵逼的。博主你在开玩笑吧?你的翻译水平不过关?API还能查吗?API不是后端写好,前端调用的吗?

的确可以,这就是GraphQL强大的地方。
引用官方文档的一句话:

ask exactly what you want.

二、为什么要使用GraphQL?


在实际工作中往往会有这种情景出现:比如说我需要展示一个游戏名的列表,可接口却会把游戏的详细玩法,更新时间,创建者等各种各样的 (无用的) 信息都一同返回。

问了后端,原因大概如下:

  • 原来是为了兼容PC端和移动端用同一套接口
  • 或者在整个页面,这里需要显示游戏的标题,可是别的地方需要显示游戏玩法啊,避免多次请求我就全部返回咯
  • 或者是因为有时候项目经理想要显示“标题+更新时间”,有时候想要点击标题展开游戏玩法等等需求,所以把游戏相关的信息都一同返回

简单说就是:

  • 兼容多平台导致字段冗余
  • 一个页面需要多次调用 API 聚合数据
  • 需求经常改动导致接口很难为单一接口精简逻辑

有同学可能会说那也不一定要用GraphQL啊,比方说第一个问题,不同平台不同接口不就好了嘛

http://api.xxx.com/web/getGameInfo/:gameID
http://api.xxx.com/app/getGameInfo/:gameID
http://api.xxx.com/mobile/getGameInfo/:gameID

或者加个参数也行

http://api.xxx.com/getGameInfo/:gameID?platfrom=web

这样处理的确可以解决问题,但是无疑加大了后端的处理逻辑。你真的不怕后端程序员打你?

这个时候我们会想,接口能不能不写死,把静态变成动态?

回答是可以的,这就是GraphQL所做的!

三、GraphQL尝尝鲜——(GraphQL简单例子)


下面是用GraphQL.jsexpress-graphql搭建一个的普通GraphQL查询(query)的例子,包括讲解GraphQL的部分类型和参数,已经掌握了的同学可以跳过。

1. 先跑个hello world

  1. 新建一个graphql文件夹,然后在该目录下打开终端,执行npm init --y初始化一个packjson文件。
  2. 安装依赖包:npm install --save -D express express-graphql graphql
  3. 新建schema.js文件,填上下面的代码

//schema.js
const {GraphQLSchema,GraphQLObjectType,GraphQLString,} = require('graphql');
const queryObj = new GraphQLObjectType({name: 'myFirstQuery',description: 'a hello world demo',fields: {hello: {name: 'a hello world query',description: 'a hello world demo',type: GraphQLString,resolve(parentValue, args, request) {return 'hello world !';}}}
});
module.exports = new GraphQLSchema({query: queryObj
});

这里的意思是新建一个简单的查询,查询名字叫hello,会返回字段hello world !,其他的是定义名字和查询结果类型的意思。

  1. 同级目录下新建server.js文件,填上下面的代码

// server.js
const express = require('express');
const expressGraphql = require('express-graphql');
const app = express();const schema = require('./schema');
app.use('/graphql', expressGraphql({schema,graphiql: true
}));app.get('/', (req, res) => res.end('index'));app.listen(8000, (err) => {if(err) {throw new Error(err);}console.log('*** server started ***');
});

这部分代码是用express跑起来一个服务器,并通过express-graphqlgraphql挂载到服务器上。

  1. 运行一下node server,并打开http://localhost:8000/

这里写图片描述

如图,说明服务器已经跑起来了
打开http://localhost:8000/graphql,是类似下面这种界面说明已经graphql服务已经跑起来了!
这里写图片描述
在左侧输入 (graphql的查询语法这里不做说明)

{hello
}

点击头部的三角形的运行按钮,右侧就会显示你查询的结果了
这里写图片描述

2. 不仅仅是hello world

先简单讲解一下代码:

const queryObj = new GraphQLObjectType({name: 'myFirstQuery',description: 'a hello world demo',fields: {}
});

GraphQLObjectType是GraphQL.js定义的对象类型,包括namedescriptionfields三个属性,其中namedescription 是非必填的。fields是解析函数,在这里可以理解为查询方法

hello: {name: 'a hello world query',description: 'a hello world demo',type: GraphQLString,resolve(parentValue, args, request) {return 'hello world !';}}

对于每个fields,又有name,description,type,resolve参数,这里的type可以理解为hello方法返回的数据类型,resolve就是具体的处理方法。

说到这里有些同学可能还不满足,如果我想每次查询都想带上一个参数该怎么办,如果我想查询结果有多条数据又怎么处理?

下面修改schema.js文件,来一个加强版的查询(当然,你可以整理一下代码,我这样写是为了方便阅读)

const {GraphQLSchema,GraphQLObjectType,GraphQLString,GraphQLInt,GraphQLBoolean} = require('graphql');const queryObj = new GraphQLObjectType({name: 'myFirstQuery',description: 'a hello world demo',fields: {hello: {name: 'a hello world query',description: 'a hello world demo',type: GraphQLString,args: {name: {  // 这里定义参数,包括参数类型和默认值type: GraphQLString,defaultValue: 'Brian'}},resolve(parentValue, args, request) { // 这里演示如何获取参数,以及处理return 'hello world ' + args.name + '!';}},person: {name: 'personQuery',description: 'query a person',type: new GraphQLObjectType({ // 这里定义查询结果包含name,age,sex三个字段,并且都是不同的类型。name: 'person',fields: {name: {type: GraphQLString},age: {type: GraphQLInt},sex: {type: GraphQLBoolean}}}),args: {name: {type: GraphQLString,defaultValue: 'Charming'}},resolve(parentValue, args, request) {return {name: args.name,age: args.name.length,sex: Math.random() > 0.5};}}}
});module.exports = new GraphQLSchema({query: queryObj 
});

重启服务后,继续打开http://localhost:8000/graphql,在左侧输入

{hello(name:"charming"),person(name:"charming"){name,sex,age}
}

右侧就会显示出:
这里写图片描述

你可以在左侧仅输入person方法的sexage两个字段,这样就会只返回sexage的信息。动手试一试吧!

{person(name:"charming"){sex,age}
}

当然,结果的顺序也是按照你输入的顺序排序的。

定制化的数据,完全根据你查什么返回什么结果。这就是GraphQL被称作API查询语言的原因。

四、GraphQL实战


下面我将搭配koa实现一个GraphQL查询的例子,逐步从简单koa服务到mongodb的数据插入查询,再到GraphQL的使用,最终实现用GraphQL对数据库进行增删查改。

项目效果大概如下:
这里写图片描述

有点意思吧?那就开始吧~
先把文件目录建构建好

1. 初始化项目

  • 初始化项目,在根目录下运行npm init --y
  • 然后安装一些包:npm install koa koa-static koa-router koa-bodyparser --save -D
  • 新建configcontrollersgraphqlmongodbpublicrouter这几个文件夹。装逼的操作是在终端输入mkdir config controllers graphql mongodb public router回车,ok~

2. 跑一个koa服务器
新建一个server.js文件,写入以下代码

// server.js
import Koa from 'koa'
import Router from 'koa-router'
import bodyParser from 'koa-bodyparser'const app = new Koa()
const router = new Router();
const port = 4000app.use(bodyParser());router.get('/hello', (ctx, next) => {ctx.body="hello world"
});app.use(router.routes()).use(router.allowedMethods());app.listen(port);console.log('server listen port: ' + port)

执行node server跑起来服务器,发现报错了:
这里写图片描述

这是正常的,这是因为现在的node版本并没有支持es6的模块引入方式。

百度一下就会有解决方案了,比较通用的做法是用babel-polyfill进行转译。

详细的可以看这一个参考操作:How To Enable ES6 Imports in Node.JS

具体操作是:新建一个start.js文件,写入:

// start.js
require('babel-register')({presets: [ 'env' ]
})
require('babel-polyfill')
require('./server.js')

安装相关包:npm install --save -D babel-preset-env babel-polyfill babel-register

修改package.json文件,把"start": "start http://localhost:4000 && node start.js"这句代码加到下面这个位置:
这里写图片描述

运行一下npm run start,打开http://localhost:4000/hello,结果如图:
这里写图片描述
说明koa服务器已经跑起来了。

那么前端页面呢?

(由于本文内容不是讲解前端,所以前端代码自行去github复制)

  • 在public下新建index.html文件和js文件夹,代码直接查看我的项目public目录下的 index.htmlindex-s1.js 文件
  • 修改server.js,引入koa-static模块。koa-static会把路由的根目录指向自己定义的路径(也就是本项目的public路径)

//server.js
import Koa from 'koa'
import Router from 'koa-router'
import KoaStatic from 'koa-static'
import bodyParser from 'koa-bodyparser'const app = new Koa()
const router = new Router();
const port = 4000app.use(bodyParser());router.get('/hello', (ctx, next) => {ctx.body="hello world"
});app.use(KoaStatic(__dirname + '/public'));
app.use(router.routes()).use(router.allowedMethods());app.listen(port);console.log('server listen port: ' + port)

打开http://localhost:4000/,发现是类似下面的页面:
这里写图片描述

这时候页面已经可以进行简单的交互,但是还没有和后端进行数据交互,所以是个静态页面。

3. 搭一个mongodb数据库,实现数据增删改查

注意: 请先自行下载好mongodb并启动mongodb。

a. 写好链接数据库的基本配置和表设定

config文件夹下面建立一个index.js,这个文件主要是放一下链接数据库的配置代码。

//  config/index.js
export default {dbPath: 'mongodb://localhost/todolist'
}

mongodb文件夹新建一个index.jsschema文件夹, 在 schema文件夹文件夹下面新建list.js

mongodb/index.js下写上链接数据库的代码,这里的代码作用是链接上数据库

// mongodb/index.js
import mongoose from 'mongoose'
import config from '../config'require('./schema/list')export const database = () => {mongoose.set('debug', true)mongoose.connect(config.dbPath)mongoose.connection.on('disconnected', () => {mongoose.connect(config.dbPath)})mongoose.connection.on('error', err => {console.error(err)})mongoose.connection.on('open', async () => {console.log('Connected to MongoDB ', config.dbPath)})
}

mongodb/schema/list.js定义表和字段:

//mongodb/schema/list.js
import mongoose from 'mongoose'const Schema = mongoose.Schema
const ObjectId = Schema.Types.ObjectIdconst ListSchema = new Schema({title: String,desc: String,date: String,id: String,checked: Boolean,meta: {createdAt: {type: Date,default: Date.now()},updatedAt: {type: Date,default: Date.now()}}
})ListSchema.pre('save', function (next) {// 每次保存之前都插入更新时间,创建时插入创建时间if (this.isNew) {this.meta.createdAt = this.meta.updatedAt = Date.now()} else {this.meta.updatedAt = Date.now()}next()
})
mongoose.model('List', ListSchema)

b. 实现数据库增删查改的控制器

建好表,也链接好数据库之后,我们就要写一些方法来操作数据库,这些方法都写在控制器(controllers)里面。

controllers里面新建list.js,这个文件对应操作list数据的控制器,单独拿出来写是为了方便后续项目复杂化的模块化管理。

// controllers/list.js
import mongoose from 'mongoose'
const List = mongoose.model('List')
// 获取所有数据
export const getAllList = async (ctx, next) => {const Lists = await List.find({}).sort({date:-1}) // 数据查询if (Lists.length) {ctx.body = {success: true,list: Lists}} else {ctx.body = {success: false}}
}
// 新增
export const addOne = async (ctx, next) => {// 获取请求的数据const opts = ctx.request.bodyconst list = new List(opts)const saveList = await list.save() // 保存数据console.log(saveList)if (saveList) {ctx.body = {success: true,id: opts.id}} else {ctx.body = {success: false,id: opts.id}}
}
// 编辑
export const editOne = async (ctx, next) => {const obj = ctx.request.bodylet hasError = falselet error = nullList.findOne({id: obj.id}, (err, doc) => {if(err) {hasError = trueerror = err} else {doc.title = obj.title;doc.desc = obj.desc;doc.date = obj.date;doc.save();}})if (hasError) {ctx.body = {success: false,id: obj.id}} else {ctx.body = {success: true,id: obj.id}}
}// 更新完成状态
export const tickOne = async (ctx, next) => {const obj = ctx.request.bodylet hasError = falselet error = nullList.findOne({id: obj.id}, (err, doc) => {if(err) {hasError = trueerror = err} else {doc.checked = obj.checked;doc.save();}})if (hasError) {ctx.body = {success: false,id: obj.id}} else {ctx.body = {success: true,id: obj.id}}
}// 删除
export const delOne = async (ctx, next) => {const obj = ctx.request.bodylet hasError = falselet msg = nullList.remove({id: obj.id}, (err, doc) => {if(err) {hasError = truemsg = err} else {msg = doc}})if (hasError) {ctx.body = {success: false,id: obj.id}} else {ctx.body = {success: true,id: obj.id}}
}

c. 实现路由,给前端提供API接口

数据模型和控制器都已经设计好了,下面就利用koa-router路由中间件,来实现请求的接口。

我们回到server.js,在上面添加一些代码。如下:

// server.js
import Koa from 'koa'
import Router from 'koa-router'
import KoaStatic from 'koa-static'
import bodyParser from 'koa-bodyparser'
import {database} from './mongodb' 
import {addOne, getAllList, editOne, tickOne, delOne} from './controllers/list' database() // 链接数据库并且初始化数据模型const app = new Koa()
const router = new Router();
const port = 4000app.use(bodyParser());router.get('/hello', (ctx, next) => {ctx.body = "hello world"
});// 把对请求的处理交给处理器。
router.post('/addOne', addOne).post('/editOne', editOne).post('/tickOne', tickOne).post('/delOne', delOne).get('/getAllList', getAllList)app.use(KoaStatic(__dirname + '/public'));
app.use(router.routes()).use(router.allowedMethods());app.listen(port);console.log('server listen port: ' + port)

上面的代码,就是做了:

1. 引入mongodb设置、list控制器,
2. 链接数据库
3. 设置每一个设置每一个路由对应的我们定义的的控制器。

安装一下mongoose:npm install --save -D mongoose

运行一下npm run start,待我们的服务器启动之后,就可以对数据库进行操作了。我们可以通过postman来模拟请求,先插几条数据:
这里写图片描述
查询全部数据:
这里写图片描述

d. 前端对接接口

前端直接用ajax发起请求就好了,平时工作中都是用axios的,但是我懒得弄,所以直接用最简单的方法就好了。

引入了JQuery之后,改写public/js/index.js文件:略(项目里的public/index-s2.js的代码)

项目跑起来,发现已经基本上实现了前端发起请求对数据库进行操作了。
至此你已经成功打通了前端后台数据库,可以不要脸地称自己是一个小全栈了!

不过我们的目的还没有达到——用grapql实现对数据的操作!

4. 用grapql实现对数据的操作

GraphQL 的大部分讨论集中在数据获取(query),但是任何完整的数据平台也都需要一个改变服务端数据的方法。
REST 中,任何请求都可能最后导致一些服务端副作用,但是约定上建议不要使用 GET 请求来修改数据。GraphQL 也是类似 —— 技术上而言,任何查询都可以被实现为导致数据写入。然而,建一个约定来规范任何导致写入的操作都应该显式通过变更(mutation)来发送。

简单说就是,GraphQL用mutation来实现数据的修改,虽然mutation能做的query也能做,但还是要区分开这连个方法,就如同REST中约定用GET来请求数据,用其他方法来更新数据一样。

a. 实现查询
查询的话比较简单,只需要在接口响应时,获取数据库的数据,然后返回;

const objType = new GraphQLObjectType({name: 'meta',fields: {createdAt: {type: GraphQLString},updatedAt: {type: GraphQLString}}
})
let ListType = new GraphQLObjectType({name: 'List',fields: {_id: {type: GraphQLID},id: {type: GraphQLString},title: {type: GraphQLString},desc: {type: GraphQLString},date: {type: GraphQLString},checked: {type: GraphQLBoolean},meta: {type: objType}}
})
const listFields = {type: new GraphQLList(ListType),args: {},resolve (root, params, options) {return List.find({}).exec() // 数据库查询}
}
let queryType = new GraphQLObjectType({name: 'getAllList',fields: {lists: listFields,}
})export default new GraphQLSchema({query: queryType
})

把增删查改都讲完再更改代码~
b. 实现增删查改

一开始说了,其实mutationquery用法上没什么区别,这只是一种约定。
具体的mutation实现方式如下:

const outputType = new GraphQLObjectType({name: 'output',fields: () => ({id:     { type: GraphQLString},success:   { type: GraphQLBoolean },})
});const inputType = new GraphQLInputObjectType({name: 'input',fields: () => ({id:          { type: GraphQLString },desc:        { type: GraphQLString },title:       { type: GraphQLString },date:        { type: GraphQLString },checked:        { type: GraphQLBoolean }})
});
let MutationType = new GraphQLObjectType({name: 'Mutations',fields: () => ({delOne: {type: outputType,description: 'del',args: {id: { type: GraphQLString }},resolve: (value, args) => {console.log(args)let result = delOne(args)return result}},editOne: {type: outputType,description: 'edit',args: {listObj: { type: inputType }},resolve: (value, args) => {console.log(args)let result = editOne(args.listObj)return result}},addOne: {type: outputType,description: 'add',args: {listObj: { type: inputType }},resolve: (value, args) => {console.log(args.listObj)let result = addOne(args.listObj)return result}},tickOne: {type: outputType,description: 'tick',args: {id: { type: GraphQLString },checked: { type: GraphQLBoolean },},resolve: (value, args) => {console.log(args)let result = tickOne(args)return result}},}),
});export default new GraphQLSchema({query: queryType,mutation: MutationType
})

c. 完善其余代码

在实现前端请求Graphql服务器时,最困扰我的就是参数以什么样的格式进行传递。后来在Graphql界面玩Graphql的query请求时发现了其中的诀窍…

关于前端请求格式进行一下说明:
这里写图片描述
如上图,在玩Graphql的请求时,我们就可以直接在控制台network查看请求的格式了。这里我们只需要模仿这种格式,当做参数发送给Graphql服务器即可。

记得用反引号: `` ,来拼接参数格式。然后用data: {query: params}的格式传递参数,代码如下:

let data = {query: `mutation{addOne(listObj:{id: "${that.getUid()}",desc: "${that.params.desc}",title: "${that.params.title}",date: "${that.getTime(that.params.date)}",checked: false}){id,success}}`}$.post('/graphql', data).done((res) => {console.log(res)// do something})

最后更改server.jsrouter/index.jscontrollers/list.jspublic/index.js改成github项目对应目录的文件代码即可。

完整项目的目录如下:
在这里插入图片描述

五、后记

对于Vue开发者,可以使用vue-apollo使得前端传参更加优雅~

六、参考文献

  • graphql官网教程
  • GraphQL.js
  • 30分钟理解GraphQL核心概念
  • 我的前端故事----我为什么用GraphQL
  • GraphQL 搭配 Koa 最佳入门实践


---------------------
作者:__Charming__
来源:CSDN
原文:https://blog.csdn.net/qq_41882147/article/details/82966783
版权声明:本文为作者原创文章,转载请附上博文链接!
内容解析By:CSDN,CNBLOG博客文章一键转载插件

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

对话庄表伟:开源第一课

庄表伟目前就职于华为的开源管理中心。自2014年开源社成立之初,他便友情参与了开源社的筹办工作。2017年,开源社转型为完全由个人成员组成的组织,庄表伟就以个人身份加入了开源社。作为开源社理事,当被问到“为什么要参选”时&…

【FME实战教程】002:FME完美实现CAD数据转shp案例教程(以三调土地利用现状数据为例)

FME完美实现CAD数据转shp案例教程(以三调土地利用数据为例) 文章目录1. cad数据预览2. 转换过程3. shp数据预览1. cad数据预览 2. 转换过程 (1)打开FME Desktop2020中文软件,点击【新建】。 (2&#xff09…

【GlobalMapper精品教程】017:KML generator快速将坐标转为KML文件

本文介绍KML generator软件,并快速将坐标转为KML文件的使用方法,并用globalmapper中打开kml文件加以验证。本专栏配套完整的案例数据包,请打开data017.rar获取软件及数据。 文章目录 1. KML文件介绍2. kml generator软件介绍2.1 单点KML制作2.2 Excel数据KML制作2.3 文本文件…

模型验证组件 FluentValidation

FluentValidation 是 .NET 下的模型验证组件,和 ASP.NET MVC 基于Attribute 声明式验证的不同处,其利用表达式语法链式编程,使得验证组件与实体分开。正如 FluentValidation 的 介绍: A small validation library for .NET that u…

第二届中国PWA开发者日

点击蓝字关注我们活动介绍为加速推动渐进式 Web 应用 (PWA) 在中国的发展,微软与英特尔携手举办“第二届中国 PWA 开发者日”。本次活动邀请一众业界大咖围绕 PWA 展开分享,探讨最新技术进展,及 PWA 生态的实践与落地。期待与您线上相聚。活动…

【GlobalMapper精品教程】018:提取影像数据的范围生成矢量图层

文章目录 1. 加载影像数据2. 生成边界3. 导出矢量范围4. 背景影响边界解决办法1. 加载影像数据 以DSM为例,加载如下所示: 2. 生成边界 在影像图层上右键→图层→【边界框/覆盖-创建图层覆盖框/多边形区要素】,如下图所示: 选择【否】。 边界创建完成。 3. 导出矢量范围 …

在Leangoo里怎么设置看板周期?

设置看板周期有两种方式: 1)点击看板上的看板周期时间直接修改 2)通过菜单 设置看板周期 浏览器访问官网链接:www.leangoo.com 转载于:https://www.cnblogs.com/shineshine/p/5663104.html

consul部署多节点和consul-template部署

一.consul的介绍 1.1consul是什么? Consul是HashiCorp公司推出的开源工具,用于实现分布式系统的服务发现与配置。 Consul是分布式的、高可用的、可横向扩展的。它具备以下特性 : service discovery:consul通过DNS或者HTTP接口使服务注册和服务发现变的很容易,一些外…

基于ABP实现DDD

什么是DDD呢?领域驱动设计[DDD]是一种针对复杂需求的软件开发方法。将软件实现与不断发展的模型联系起来,专注于核心领域逻辑,而不是基础设施细节。DDD适用于复杂领域和大规模应用,而不是简单的CRUD应用。它有助于建立一个灵活、模…

【GlobalMapper精品教程】019:基于DSM提取离散随机点的高程信息

本文讲解在globalmapper中,基于DSM提取离散随机点的高程信息,配套数据为data019.rar。 文章目录 1. 离散点创建2. 提取离散点高程信息3. 高程标注1. 离散点创建 本文在ArcGIS中,根据给定的范围,随机生成离散点,如下图: 拓展阅读: ArcGIS根据范围创建随机点教程:【ArcG…

shell脚本注意点

2019独角兽企业重金招聘Python工程师标准>>> 直接命令行写脚本的时候,可以用 ; 分割,或 也可以直接回车,然后在继续写脚本在使用 方括号[ ] 的时候,里面空格两边都必须要有空格,比如 [ $a -gt 3 ] 在方括号…

【GlobalMapper精品教程】020:Lidar点云数据分类(自动分类、手动分类)案例详解

航测点云通常跟DSM一致,即包含植被、房屋等信息,必须进行点云分类、过滤,才能生成准确的高程点、等高线和DEM等地形数据。本文以案例的形式详细讲解globalmapper23中点云工具及使用方法。 文章目录 1. 点云分类2. 创建地面高程格网3. 地形绘制4. 格网转点云5. 点云抽稀6. 点…

CSS3 nth 伪类选择器

考察下面的 HTML 代码片段&#xff1a; <div><section>section 1</section><section>section 2</section><ul><li>item 1</li><li><ul><li>sub item 1</li><li>sub item 2</li><li>…

RedisCluster的安装、部署、扩容和 Java客户端调用

Redis下载 官网地址&#xff1a;http://redis.io/ 中文官网地址&#xff1a;http://www.redis.cn/ 下载地址&#xff1a;http://download.redis.io/releases/ 安装 # &#xff08;三台&#xff09;安装 C 语言需要的 GCC 环境 yum install -y gcc-c yum install -y wget # 下…

【CloudCompare教程】001:CloudCompare中文版下载与安装图文教程

CloudCompare是一款功能强大的点云后处理软件,本文讲解CloudCompare中文版下载与安装方法。 文章目录 一、CloudCompare下载地址二、CloudCompare安装教程三、CloudCompare中文设置一、CloudCompare下载地址 官方下载地址:http://www.danielgm.net/cc/release/ 二、CloudComp…

ML.NET相关资源整理

在人工智能领域&#xff0c;无论是机器学习&#xff0c;还是深度学习等&#xff0c;Python编程语言都是绝对的主流&#xff0c;尽管底层都是C实现的&#xff0c;似乎人工智能和C#/F#编程语言没什么关系。在人工智能的工程实现&#xff0c;通常都是将Python训练好的人工智能模型…

JAVA学习日志(7-1-继承)

为什么80%的码农都做不了架构师&#xff1f;>>> 继承 1.提高代码复用性 2.让类与类之间产生关系&#xff0c;有了这个关系才有了多态的特性 **不要为了获取其他类的功能&#xff0c;简化代码而继承&#xff0c; 必须是类与类之间有所属关系才可以继承&#xff0c;所…

BZOJ 1370: [Baltic2003]Gang团伙 [并查集 拆点 | 种类并查集WA]

题意&#xff1a; 朋友的朋友是朋友&#xff0c;敌人的敌人是朋友&#xff1b;朋友形成团伙&#xff0c;求最多有多少团伙 种类并查集WA了一节课&#xff0c;原因是&#xff0c;只有那两种关系才成立&#xff0c;诸如朋友的敌人是朋友之类的都不成立&#xff01; 所以拆点做吧 …

常见Lidar点云数据处理及可视化软件汇总

常见的点云处理及可视化软件有&#xff1a; CloudCompare、Globalmapper、Pix4d、ArcGIS&#xff08;Pro&#xff09;、Lidar 360、PCL等等。 文章目录1. CloudCompare2. Globalmapper3. Pix4d4. ArcGIS&#xff08;Pro&#xff09;5. Lidar 3606. PCL1. CloudCompare CloudCo…