在本教程中,我们将研究如何使用NodeJS构建一个基本的Twitter客户端,以及一个Angular应用程序来显示您的tweets主页时间表。 这是一个快速浏览过程,在设置您自己的Twitter客户端和Angular应用程序时需要考虑的事项。

首先,我们将构建一个NodeJS服务器,该服务器将处理Twitter API和Angular应用程序之间的通信。 然后,我们将构建Angular应用程序以显示您的Twitter时间轴并与之交互。

尽管您可能无需任何NodeJS或Angular的任何经验就可以阅读本教程,但本文将假定您具有一些先验知识。

设置项目

您需要在计算机上设置最新版本的NodeJS 。 然后确保您具有Angular CLI 。 如果您需要其中两个任务的帮助,则提供的链接为您提供了一个良好的起点。

项目源代码可以在GitHub上找到 。 您可以通过使用Git克隆它或从仓库的GitHub档案下载文件来使它们在本地运行。

git clone https://github.com/sitepoint-editors/twitter-angular-client

获得文件后,需要从终端运行npm installnpm install所有依赖项。 这样我们就可以开始工作了!

在NodeJS中创建Twitter客户端

要访问Twitter的API,我们需要注册一个新的“应用程序”,这实际上是Twitter向我们提供一组凭据的一种方式。 这些对于您的应用程序是唯一的,因此请勿在任何地方公开共享。 当然,您必须具有Twitter帐户才能访问数据。

首先,请访问https://apps.twitter.com/并选择创建新应用 。 您可以填写应用程序的名称,描述和网站URL。 (您现在可以使用伪造的URL。如果发布您的应用,则它应该是您的实际网站。)

从那里,您将看到包含您的详细信息的新应用页面。 转到“ 密钥和访问令牌”页面,您可以在底部附近看到一个用于创建我的访问令牌的按钮。 单击按钮,然后您应该看到四个值: 使用者密钥 (API密钥), 使用者密钥 (API密钥), 访问令牌访问令牌密钥 。 我们将在稍后使用它们,因此请务必将这些信息放在方便的位置。

在NodeJS中创建Twitter客户端

现在是时候深入研究我们的NodeJS服务器了,它将弥合Twitter API和Angular应用之间的鸿沟。 在项目中,您应该看到server.js文件,需要打开和调整该文件。

首先,您需要更新包含您之前从Twitter应用程序收到的凭据的块。 您应该将这些值复制到此处的块中。 我们正在使用一个名为Twit的Twitter程序包来帮助我们连接到Twitter,尽管还有其他可用程序具有各种功能级别。

const client = new Twitter({
consumer_key: 'CONSUMER_KEY',
consumer_secret: 'CONSUMER_SECRET',
access_token: 'ACCESS_TOKEN',
access_token_secret: 'ACCESS_TOKEN_SECRET'
});

现在我们应该能够连接到Twitter。 我们还使用流行的ExpressJS创建和管理我们的服务器。 现在,您已经安装了凭据,您可以运行服务器。

node server

我们的下一步是制作一些路由,以处理Angular应用程序加载Twitter数据所需的HTTP请求。 我们的第一个方法是获取当前用户,并验证其凭据。 您提供的访问令牌和密钥已链接到您的Twitter帐户,因此在这种情况下,您将成为授权用户。 调用此路由时,它将调用Twitter account/verify_credentials端点并返回一个包含您的用户数据的对象。

app.get('/api/user', (req, res) => {
client.get('account/verify_credentials').then(user => {
res.send(user)
}).catch(error => {
res.send(error);
});
});

我们将创建的下一条路线是获取您的家庭时间表。 它请求statuses/home_timeline端点,并传递一些参数来提供我们所需的更多数据。

由于Twitter API上的速率限制,我们实现了一个简单的缓存,该缓存每分钟仅请求一次新数据(这是收到错误之前的最大速率)。 它基本上跟踪上次响应以及请求的时间,仅允许对Twitter的新请求在一分钟后运行。 速率限制是构建Twitter应用程序时要考虑的主要设计考虑因素。

let cache = [];
let cacheAge = 0;
app.get('/api/home', (req, res) => {
if (Date.now() - cacheAge > 60000) {
cacheAge = Date.now();
const params = { tweet_mode: 'extended', count: 200 };
if (req.query.since) {
params.since_id = req.query.since;
}
client
.get(`statuses/home_timeline`, params)
.then(timeline => {
cache = timeline;
res.send(timeline);
})
.catch(error => res.send(error));
} else {
res.send(cache);
}
});

最后,我们创建了一组路由来处理推文的喜欢/不喜欢和转发/取消转发操作。 这不仅使我们可以读取数据,而且可以采取行动。 这些将要求您将应用程序访问级别设置为读写 (以防您在Twitter应用程序设置中更改了访问级别)。

app.post('/api/favorite/:id', (req, res) => {
const path = (req.body.state) ? 'create' : 'destroy';
client
.post(`favorites/${path}`, {id: req.params.id})
.then(tweet => res.send(tweet))
.catch(error => res.send(error));
});
app.post('/api/retweet/:id', (req, res) => {
const path = (req.body.state) ? 'retweet' : 'unretweet';
client
.post(`statuses/retweet/${req.params.id}`)
.then(tweet => res.send(tweet))
.catch(error => res.send(error));
});

有许多用于处理Twitter数据的Twitter API ,但是基本规则保持不变。 这里唯一的主要问题是我们已将凭证硬编码到单个用户,您需要该凭证才能设置自己的OAuth服务器(或使用现有的OAuth服务器)来处理身份验证方面,您可以了解更多关于Twitter身份验证文档 。

创建Angular应用

现在该将注意力转向使用我们创建的服务器的Angular应用程序。 我们将研究应用程序的关键方面以及它们如何工作以创建最终结果。 我们已经在UI层上使用Clarity构建了此应用程序(它为我们提供了许多有用的布局组件),但除此之外,所有内容都只是Angular。

要运行Angular应用程序,只需运行以下命令,然后打开http:// localhost:4200:

ng serve

免费学习PHP!

全面介绍PHP和MySQL,从而实现服务器端编程的飞跃。

原价$ 11.95 您的完全免费

免费获得这本书

在应用程序内部,我们在src/app/tweet.ts有一个模型,其中包含TypeScript接口,该接口描述了tweet的大多数属性(有些已被省略)。 我认为,对于大型Angular应用程序和较小的应用程序,正确描述您的类型至关重要,因此此界面使我们形成了鸣叫的形状。

Angular TwitterService

首先,我们需要一个可以向NodeJS服务器发出请求以获取最新推文的服务。 在Angular中,HttpClient是用于发出HTTP请求的实用程序,因此我创建了一个Angular服务来封装这些调用的逻辑。 打开src/app/twitter.service.ts ,您将看到以下代码:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { environment } from '../environments/environment';
import { Tweet } from './tweet';
export interface TwitterResponse {
data: any;
resp: any;
}
@Injectable()
export class TwitterService {
constructor(private http: HttpClient) { }
user() {
return this.http.get<TwitterResponse>(`${environment.api}/user`);
}
home(since?: string) {
return this.http.get<TwitterResponse>(`${environment.api}/home?since=${since}`);
}
action(property: 'favorite'|'retweet', id: string, state: boolean) {
return this.http.post<TwitterResponse>(`${environment.api}/${property}/${id}`, {state});
}
}

这是一项相当基本的服务,它具有为我们将支持的每个API生成请求的方法。 user方法将返回当前用户(永远是您)。 home方法将在您的主时间轴中返回最新的200条tweet(或自指定最后一条tweet以来出现了多少条)。 最后,通过发送布尔state值来切换状态, action属性可以处理收藏夹或转推调用。

该服务是通用的,并且每个方法都返回一个Observable。 如果您想了解更多关于它们的知识,可以阅读RXJS的Functional Reactive ,但是这里使用它们的方式与promise的工作方式相似。 稍后我们将介绍如何使用它们。

使用Angular TwitterService加载用户

从加载AppComponent开始,我们将在几个地方使用TwitterService。 我们将使用它来加载用户详细信息(显示在右上角),并加载主页的推文列表。 打开src/app/app.component.ts ,您应该看到以下代码:

import { Component , OnInit } from '@angular/core';
import { TwitterService } from './twitter.service';
import { Tweet } from './tweet';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
providers: [TwitterService]
})
export class AppComponent implements OnInit {
user;
constructor(private twitter: TwitterService) {}
ngOnInit() {
this.twitter.user().subscribe(user => this.user = user.data);
}
}

AppComponent使用我们的TwitterService做一件主要的事情。 组件初始化后, ngOnInit方法立即触发,并请求用户数据。 在这里,我们使用的是TwitterService.user方法返回的Observable,当我们使用subscribe ,它将触发实际的HTTP请求触发。 一旦返回,回调函数将存储用户属性,该属性用于在导航栏中显示内容。 您可以在下面的组件模板中看到用户属性绑定,例如user.profile_image_url_https

<clr-main-container>
<clr-header class="header-4">
<div class="branding">
<a class="nav-link">
<div class="title">Twangular</div>
</a>
</div>
<div class="header-actions" *ngIf="user">
<a class="nav-link">
<span class="nav-text">
<img [src]="user.profile_image_url_https" class="avatar" />
@{{user.screen_name}}
</span>
</a>
</div>
</clr-header>
<div class="content-container">
<main class="content-area">
<app-tweets></app-tweets>
</main>
</div>
</clr-main-container>

另外,使用<app-tweets></app-tweets>将插入TweetsComponent,该组件处理tweets的实际加载和显示,因此现在让我们看一下。

显示推文列表

为了帮助分离逻辑,我们实际上有两个组件来显示推文列表。 TweetsComponent管理推文列表,并处理对我们的NodeJS服务的请求,以喜欢或转推。 然后,使用TweetComponent来显示实际的tweet格式和显示。 我总是建议尝试将组件划分为不同的角色,在这种情况下,TweetsComponent负责处理数据交互(例如加载和转发),并且TweetComponent不了解加载数据的知识,只显示内容。 我们先看一下TweetsComponent,所以下面是src/app/tweets/tweets.component.ts

import { Component, OnInit, Input, OnDestroy } from '@angular/core';
import { Tweet } from '../tweet';
import { TwitterService } from '../twitter.service';
@Component({
selector: 'app-tweets',
templateUrl: './tweets.component.html',
styleUrls: ['./tweets.component.scss']
})
export class TweetsComponent implements OnInit, OnDestroy {
inflight = false;
tweets: Tweet[] = [];
ids = [];
timer;
since = '';
constructor(private twitter: TwitterService) {}
ngOnInit() {
this.getTweets();
this.timer = setInterval(() => this.getTweets(), 61000);
}
ngOnDestroy() {
if (this.timer) {
clearInterval(this.timer);
}
}
getTweets() {
this.twitter.home(this.since).subscribe(tweets => {
tweets.data.reverse().forEach(tweet => {
if (this.ids.indexOf(tweet.id_str) < 0) {
this.ids.push(tweet.id_str);
this.tweets.unshift(tweet);
}
});
this.since = this.tweets[0].id_str;
this.cleanUp();
});
}
cleanUp() {
if (this.tweets.length > 1000) {
this.tweets.splice(1000);
this.ids.splice(1000);
}
}
action(action, index) {
if (this.inflight) {
return;
}
const stateKey = (action.property === 'favorite') ? 'favorited' : 'retweeted';
const newState = !action.tweet[stateKey];
this.inflight = true;
this.twitter.action(action.property, action.tweet.id_str, newState).subscribe(tweet => {
this.tweets[index][stateKey] = newState;
this.tweets[index][action.property + '_count'] += (newState) ? 1 : -1;
this.inflight = false;
});
}
}

该组件负责处理所有加载并与推文列表进行交互。 在ngOnInit方法中,我们调用该方法以获取推文,并设置一个间隔,该间隔每61秒重新加载一次最新的推文。 请记住,对我们可以发出的请求数量有一个速率限制,因此这有助于使我们保持在该限制之下。 删除组件时, ngOnDestroy方法只会取消设置计时器,这是防止内存泄漏的好习惯。

然后,我们有了getTweets方法,该方法使用TwitterService来请求主时间轴。 它还传递了一个字符串,其中包含接收到的最后一条推文ID,因此自创建该ID以来,我们只能请求这些推文。 当我们订阅时,请求就产生了,回调给了我们tweets的列表。 因为我们要首先显示最新的tweet,所以我们反转数组,然后将它们推到现有tweets列表上,更新最新的tweet ID参考,然后进行一些清理。 如果我们有1000多个项目,我们会丢弃其余部分以帮助控制内存消耗。

重要的是要注意,我们正在使用tweets中的id_str属性。 这是因为JavaScript(以及随后的JSON)无法准确处理53位以上的数字(换句话说,JavaScript无法处理非常大的数字,请参见Snowflake ID )。

action方法将用于处理调用TwitterService来收藏或转发推文。 它执行操作(收藏夹或转发),然后切换属性的状态。 (例如,如果您之前转发过,则将取消转发)。 一条推文包含有关您是否已收藏或转推的元数据,以及存在多少收藏或转发的计数。 由于您的偏爱或转发操作更改了该状态,因此此方法也会相应地更新tweet值。

可以在src/app/tweets/tweets.component.html找到该组件的模板,如下所示。 这很简单,因为它遍历了一系列推文,并为每个推文显示了一个TweetComponent实例。 如果该推文为转推,则它也将绑定已转发状态。 Twitter会在原始推文中添加一个retweeted_status属性,如果它是转推文,并且这是我们真正想要显示的属性。 由于我们要显示转发状态,因此它实际上将替换实际的推文(如果存在)。

<div class="tweets">
<div class="card" *ngFor="let tweet of tweets; let i = index">
<app-tweet *ngIf="tweet.retweeted_status" [tweet]="tweet.retweeted_status" [retweet]="tweet" (action)="action($event, i)"></app-tweet>
<app-tweet *ngIf="!tweet.retweeted_status" [tweet]="tweet" (action)="action($event, i)"></app-tweet>
</div>
</div>

该模板显示了TweetComponent上输入和输出绑定的用法。 输入[tweet][retweet]将数据传递到TweetComponent,并且当操作(收藏夹或转推操作)发生时,输出(action)在TweetsComponent上调用action方法。

要查看推文的显示方式,让我们转到TweetComponent,它将大量数据绑定到卡组件中,可以在src/app/tweet/tweet.component.html

<div class="card-header">
<img [src]="tweet.user.profile_image_url_https" class="avatar" /> {{tweet.user.name}} (@{{tweet.user.screen_name}})
<span *ngIf="retweet" class="retweeted"><clr-icon shape="sync"></clr-icon> Retweeted by {{retweet.user.name}} (@{{retweet.user.screen_name}})</span>
<div class="card-header-actions">
<button type="button" class="btn btn-icon" [ngClass]="{'btn-success': tweet.favorited}" (click)="toggleAction('favorite')"><clr-icon shape="heart"></clr-icon> {{tweet.favorite_count}}</button>
<button type="button" class="btn btn-icon" [ngClass]="{'btn-success': tweet.retweeted}" (click)="toggleAction('retweet')"><clr-icon shape="share"></clr-icon> {{tweet.retweet_count}}</button>
</div>
</div>
<div class="card-block">
<div class="card-img" *ngIf="hasPhoto(tweet)">
<img [src]="tweet.entities?.media[0].media_url_https" (click)="media = true" />
</div>
<p class="card-text" [innerHTML]="tweet | tweet"></p>
</div>
<div class="card-footer" *ngIf="!retweet">
{{tweet.created_at | amTimeAgo}}
<clr-icon shape="minus"></clr-icon>
{{tweet.created_at | date:'medium'}}
</div>
<div class="card-footer" *ngIf="retweet">
{{retweet.created_at | amTimeAgo}}
<clr-icon shape="minus"></clr-icon>
{{retweet.created_at | date:'medium'}}
</div>
<clr-modal [(clrModalOpen)]="media" *ngIf="tweet.entities.media" clrModalSize="lg">
<h3 class="modal-title"><img [src]="tweet.user.profile_image_url_https" class="avatar" /> {{tweet.user.name}} (@{{tweet.user.screen_name}})
<span *ngIf="retweet" class="retweeted"><clr-icon shape="sync"></clr-icon> Retweeted by {{retweet.user.name}}</span></h3>
<div class="modal-body">
<img [src]="tweet.entities?.media[0].media_url_https" />
</div>
<div class="modal-footer" [innerHTML]="tweet | tweet"></div>
</clr-modal>

我将指出该模板的一些关键方面。 首先, .card-header-actions元素中的两个按钮显示收藏夹和转发的数量。 它们还具有事件绑定(click)="toggleAction('favorite')" ,该事件调用click上的方法来处理操作。 此方法将发出一个直到TweetsComponent的事件,该事件正在使用(action)事件绑定进行捕获。

另外,您会看到很多插值绑定,它们是{{tweet.favorite_count}} 。 有很多内容要显示,因此这是将文本或内容打印到页面中的最简单方法。

接下来,该推文的主要文本直接绑定到.card-text元素的innerHTML属性,如您在此处看到的。 这是因为我们希望显示HTML内容而不是文本,因为它允许我们注入带有链接的内容。

<p class="card-text" [innerHTML]="tweet | tweet"></p>

之所以完成对innerHTML的绑定,是因为我们有一个自定义管道(稍后我们将对其进行回顾),该管道可以分析推文并用链接替换某些内容。 因此,例如,如果一条推文中包含一个URL,它将用实际的锚链接替换纯文本值。 同样,如果该推文提及其他用户,则它会执行相同的操作。 我们还包括了amTimeAgo管道,这是一组用于时间管理的Angular管道 。

最后,在底部有一个clr-modal元素,它是Clarity modal 。 如果该推文包含图像,并且用户单击该图像(位于.card-img元素的上方),它将打开具有较大版本的模式。

要包装此组件,查看src/app/tweet/tweet.component.ts的组件控制器很有用,它定义了一些重要属性:

import { Component, EventEmitter, Output, Input } from '@angular/core';
import { Tweet } from '../tweet';
@Component({
selector: 'app-tweet',
templateUrl: './tweet.component.html',
styleUrls: ['./tweet.component.scss']
})
export class TweetComponent {
@Input() tweet: Tweet;
@Input() retweet: Tweet;
@Output() action = new EventEmitter<{property: string, tweet: Tweet}>();
hasPhoto(tweet: Tweet) {
if (tweet.entities.media
&& tweet.entities.media.length
&& tweet.entities.media[0].type === 'photo') {
return true;
}
return false;
}
toggleAction(property: 'favorite'|'retweet') {
this.action.emit({property, tweet: this.tweet});
}
}

该组件声明两个输入@Input() tweet@Input() retweet ,以及一个输出@Output() action 。 这两个输入允许我们绑定到要显示的tweet中,如果是转推,我们也要绑定到该tweet信息中。 您看到这些值是从TweetsComponent模板传递的。

当发生某些事情时,输出会警告父组件,在这种情况下,我们想提醒有关单击这些按钮时喜欢收藏或转发该推文的操作。 就像普通JavaScript事件一样,这些信息也可以简单地传递,并且TweetsComponent组件将通过action方法来处理该信息。

在总结显示推文的方式之前,让我们快速浏览一下我们用来格式化和解析推文的TweetPipe。

使用TweetPipe格式化数据

最后一个要审查的主要功能是TweetPipe,位于src/app/tweet.pipe.ts ,显示在下面。 这处理推文文本和元数据的解析以提供格式:

import { Pipe, PipeTransform } from '@angular/core';
import { Tweet } from './tweet';
import { DomSanitizer } from '@angular/platform-browser';
@Pipe({
name: 'tweet'
})
export class TweetPipe implements PipeTransform {
constructor(private sanitizer: DomSanitizer) {}
transform(tweet: Tweet, args?: any): any {
let text = this.sanitizer.sanitize(tweet.full_text);
if (tweet.entities.user_mentions) {
tweet.entities.user_mentions.forEach(mention => {
text = text.replace(new RegExp(`@${mention.screen_name}`, 'gi'), `<a href="https://twitter.com/${mention.screen_name}" target="_blank">@${mention.screen_name}</a>`);
});
}
if (tweet.entities.urls) {
tweet.entities.urls.forEach(url => {
text = text.replace(url.url, `<a href="${url.url}" target="_blank">${url.display_url}</a>`);
});
}
if (tweet.entities.media) {
tweet.entities.media.forEach(url => {
text = text.replace(url.url, '');
});
}
text = text.replace(/\n/gm, '<br />');
return this.sanitizer.bypassSecurityTrustHtml(text);
}
}

创建自定义管道时,必须实现transform方法并返回要显示的值。 在这种情况下,我们将接收整个tweet对象(因为需要元数据,而不仅仅是文本),并以多种方式对其进行处理。 Twitter以一致的结构返回数据,因此我们仅检查每个属性即可确定是否存在任何URL,媒体或提及。 如果是的话,我们用链接替换这些值,或者在媒体的情况下,因为已经显示了图像,所以将其删除。

但是,出于安全原因,Angular通常会阻止您传递HTML并将其绑定到模板中。 Angular允许您绕过此方法并直接处理清理输入。 我们在这里解决问题的方法是先清理tweet文本,这将删除任何潜在的危险内容(例如带有javascript:或script标签的链接)。 然后,我们修改文本字符串以使用链接标记替换提及和网址。 最后,我们使用DomSanitizer.bypassSecurityTrustHtml方法绕过要显示的文本的安全限制。 但是,由于我们在一开始就对文本进行了清理,因此可以信任内容。

当您拥有这样的管道时,请特别注意安全性,我建议您阅读Angular安全性指南 。

摘要

总结了我们快速浏览Angular Twitter客户端的过程,并且看到了许多Angular的关键功能,并了解了如何构建连接到Twitter API的基本NodeJS服务器。 这旨在作为一个基本示例,但是可以相当容易地添加许多其他功能,例如撰写推文,查看用户个人资料和其他交互。 我鼓励您查看Twitter API文档,以了解您拥有哪些选项,以及可以构建哪些其他功能!

翻译自: https://www.sitepoint.com/building-twitter-app-using-angular/

使用NodeJS和Angular构建Twitter客户端相关推荐

  1. twitter客户端_使用Twitter进行客户推荐

    twitter客户端 When reading customer testimonials on websites I sometimes wonder how genuine the words a ...

  2. oauth0 oauth2_通过OAuth(第1部分)访问社交网站,构建启用OAuth的桌面Twitter客户端

    oauth0 oauth2 存档日期:2019年5月15日 | 首次发布:2010年1月5日 OAuth是一种开放协议,使用户可以在不同的网站之间共享其受保护的资源,而不会面临暴露的风险. OAuth ...

  3. angular 渐进_如何使用Angular构建渐进式Web应用

    angular 渐进 介绍 (Introduction) Progressive web apps are web applications built with technologies that ...

  4. angular 构建可以动态挂载的配置服务

    angular 构建可以动态挂载的配置服务 Intro 在 angular 中可以指定 environment 来区分不同环境下的配置,然而 environment 中的配置会在打包时是固定的,想要像 ...

  5. 使用JacpFX和JavaFX2构建富客户端

    创建快速且可扩展的桌面客户端始终是一个挑战,特别是在处理大量数据和长时间运行的任务时. 尽管Eclipse RCP和Netbeans RCP是已建立的平台,但其想法是建立一个轻量级的框架来异步处理组件 ...

  6. java基础 day12-FileInputStream类,文件的复制,缓冲流,Propertes文件,xml文件读写,网络socket编程(构建TCP客户端),内部类

    FileInputStream类的其他常用方法() /**在project下新建temp文件,内容为abcdef*FileInputStream类的其他常用方法:* int available():返 ...

  7. 实用的twitter客户端:Twitterrific for Mac

    Twitterrific for Mac是Mac平台上非常优秀的twitter客户端,twitterrific 5 for twitter让你轻松阅读并撰写推文,还支持通知中心.Retina显示屏.内 ...

  8. twitter客户端_3个用于Linux命令行的Twitter客户端

    twitter客户端 Twitter的命令行? 为什么不! 尽管这似乎是解决问题的解决方案,但对于某些人来说,在终端窗口中与Twitter进行交互是有意义的. 与台式机Twitter客户端甚至Twit ...

  9. 您可能不需要Twitter客户端,只需在New Twitter中学习热键

    It's probably not unfair to call me a Twitter Power User. I use it a lot, it's my favorite Social Ne ...

最新文章

  1. 【直播】黎佳佳:音频数据分析以及特征提取
  2. 使用sn.exe为程序集签名
  3. 利用Docker/Ansible实现轻量集群服务部署(视频演示+彩蛋)
  4. Ubuntu 16.04下面安装grub-customizer来切换ubuntu+win7双系统开机启动顺序
  5. SAP CRM和C4C的订单Number range
  6. 聚类算法:K-Means
  7. ZZUOJ 1199 大小关系(拓扑排序,两种方法_判断入度和dfs回路判断)
  8. C++VS2019中新建自定义模板与删除自定义模板
  9. ajax方法参数详解
  10. 1052. Linked List Sorting (25)再
  11. GreenSock (TweenMax) 极简入门指南
  12. DX C++实现超炫酷粒子特效之烟花特效
  13. 跟张小龙学习做优秀产品经理的设计思路
  14. QQ群关键字提醒设置
  15. 产业安全专家谈丨数字经济高速发展,数据要素安全该如何保障?
  16. MySQL数据库练习3
  17. 关于iPad程序如何强制横屏
  18. buildroot GNU strip分析
  19. 次短路问题(陕西师范大学第九届ACM程序设计竞赛-F 新冠病毒要回家)
  20. linux开源软件推荐,10个Linux平台开源ERP软件推荐

热门文章

  1. 商用计算机idc市场排名,IDC 2019 年中国 PC 市场十大预测:出货量约 5060 万台
  2. Linux——Linux驱动之GPIO中断的应用实战(上)(中断概述及相关函数、设备树中指定中断、驱动中获取中断)
  3. 「とても」「あまり」「大変」的用法区别
  4. 常见分布式ID生成方案
  5. Red Hat Linux 基础命令大全
  6. 程序员,到底要不要去外包公司?
  7. 非常可乐(九度 OJ 1457)
  8. RSD 教程 —— §3.5 观察光谱曲线
  9. 学习Javascript 的书籍有哪些?
  10. c语言预上机报告,《C语言程序设计》上机报告.doc