Redis 正在逐步淘汰 RedisGraph. 此博客文章 解释了此决定的动机以及对现有 Redis 客户和社区成员的影响。
支持结束时间定于 2025 年 1 月 31 日。
IMDb(互联网电影数据库)是世界上最受欢迎和最权威的电影、电视剧和名人信息来源。此应用程序是 IMDb 克隆,具有基本帐户身份验证和电影推荐功能。您将学习 RedisGraph 和 NodeJS 的强大功能来构建简单的电影数据库。
docker run -d -p 6379:6379 redis/redis-stack
确保 Docker 容器已启动并正在运行
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
fd5ef30f025a redis/redis-stack "redis-server --load…" 2 hours ago Up 2 hours 0.0.0.0:6379->6379/tcp nervous_buck
docker run -d -v redisinsight:/db -p 8001:8001 redislabs/redisinsight:latest
确保 Docker 容器已启动并正在运行
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
264db1706dcc redislabs/redisinsight:latest "bash ./docker-entry…" About an hour ago Up About an hour 0.0.0.0:8001->8001/tcp angry_shirley
fd5ef30f025a redis/redis-stack "redis-server --load…" 2 hours ago Up 2 hours 0.0.0.0:6379->6379/tcp nervous_buck
git clone https://github.com/redis-developer/basic-redisgraph-movie-demo-app-nodejs
复制 .env.sample
到 .env
并添加以下详细信息:
REDIS_ENDPOINT_URL = "Redis server URI"
REDIS_PASSWORD = "Password to the server"
npm install
node app.js
cd client
yarn install
yarn start
打开 https://IP:3000 访问电影应用程序
输入详细信息以创建新帐户
GRAPH.QUERY "MovieApp" "MATCH (director:Director {tmdbId: \"4945\"})-[:DIRECTED]->(movie:Movie) RETURN DISTINCT movie,director"
在 RedisGraph 中运行以下查询以查找作者参演的电影
GRAPH.QUERY "MovieApp" "MATCH (actor:Actor {tmdbId: \"8537\"})-[:ACTED_IN_MOVIE]->(movie:Movie) RETURN DISTINCT movie,actor"
CREATE (user:User {id: 32,
username: "user", password: "hashed_password", api_key: "525d40da10be8ec75480"})
RETURN user
MATCH (user:User {username: "user"}) RETURN user
该应用程序使用 Express API 提供的数据并通过一些视图将其呈现给最终用户,包括
主页显示流派以及与其相关的电影简要列表。
create (g:Genre{name:"Adventure"})
create (m:Movie {
url: "https://themoviedb.org/movie/862",
id:232,
languages:["English"],
title:"Toy Story",
countries:["USA"],
budget:30000000,
duration:81,
imdbId:"0114709",
imdbRating:8.3,
imdbVotes:591836,
movieId:42,
plot:"...",
poster:"https://image.tmd...",
poster_image:"https://image.tmdb.or...",
released:"1995-11-22",
revenue:373554033,
runtime:$runtime,
tagline:"A cowboy doll is profoundly t...",
tmdbId:"8844",
year:"1995"})
MATCH (g:Genre), (m:Movie)
WHERE g.name = "Adventure" AND m.title = "Toy Story"
CREATE (m)-[:IN_GENRE]->(g)
MATCH (genre:Genre) RETURN genre
MATCH (movie:Movie)-[:IN_GENRE]->(genre)
WHERE toLower(genre.name) = toLower("Film-Noir") OR id(genre) = toInteger("Film-Noir")
RETURN movie
const getByGenre = function (session, genreId) {
const query = [
'MATCH (movie:Movie)-[:IN_GENRE]->(genre)',
'WHERE toLower(genre.name) = toLower($genreId) OR id(genre) = toInteger($genreId)',
'RETURN movie',
].join('\n');
return session
.query(query, {
genreId,
})
.then((result) => manyMovies(result));
};
为了能够对电影进行评分,用户需要登录:为此,实现了基于 JWT 的基本身份验证系统,其中用户详细信息存储在 RedisGraph 中以实现持久性。
CREATE (user:User {id: 32,
username: "user", password: "hashed_password", api_key: "525d40da10be8ec75480"})
RETURN user
MATCH (user:User {username: "user"}) RETURN user
const me = function (session, apiKey) {
return session
.query('MATCH (user:User {api_key: $api_key}) RETURN user', {
api_key: apiKey,
})
.then((foundedUser) => {
if (!foundedUser.hasNext()) {
throw {message: 'invalid authorization key', status: 401};
}
while (foundedUser.hasNext()) {
const record = foundedUser.next();
return new User(record.get('user'));
}
});
};
在此页面上,用户可以对电影进行评分,并查看参与电影制作的演员/导演。
MATCH (m:Movie) WHERE m.title="Jumanji" CREATE (a:Actor :Person{
bio:"Sample...",
bornIn:"Denver, Colorado, USA",
imdbId:"0000245",
name:"Robin Williams",
poster:"https://image.tmdb.org/t/p/w440_and_...",
tmdbId:"2157",
url:"https://themoviedb.org/person/2157"})-[r:ACTED_IN_MOVIE
{role: "Alan Parrish"}]->(m)
MATCH (m:Movie) WHERE m.title="Dead Presidents" CREATE (d:Director :Person{
bio: "From Wikipedia, the free e...",
bornIn: "Detroit, Michigan, USA",
imdbId: "0400436",
name: "Albert Hughes",
tmdbId: "11447",
url: "https://themoviedb.org/person/11447"})-[r:DIRECTED]->(m)
MATCH (movie:Movie {tmdbId: $movieId})
OPTIONAL MATCH (movie)<-[my_rated:RATED]-(me:User {id: "e1e3991f-fe81-439e-a507-aa0647bc0b88"})
OPTIONAL MATCH (movie)<-[r:ACTED_IN_MOVIE]-(a:Actor)
OPTIONAL MATCH (movie)-[:IN_GENRE]->(genre:Genre)
OPTIONAL MATCH (movie)<-[:DIRECTED]-(d:Director)
WITH DISTINCT movie, my_rated, genre, d, a, r
RETURN DISTINCT movie,
collect(DISTINCT d) AS directors,
collect(DISTINCT a) AS actors,
collect(DISTINCT genre) AS genres
const getById = function (session, movieId, userId) {
if (!userId) throw {message: 'invalid authorization key', status: 401};
const query = [
'MATCH (movie:Movie {tmdbId: $movieId})\n' +
' OPTIONAL MATCH (movie)<-[my_rated:RATED]-(me:User {id: $userId})\n' +
' OPTIONAL MATCH (movie)<-[r:ACTED_IN_MOVIE]-(a:Actor)\n' +
' OPTIONAL MATCH (movie)-[:IN_GENRE]->(genre:Genre)\n' +
' OPTIONAL MATCH (movie)<-[:DIRECTED]-(d:Director)\n' +
' WITH DISTINCT movie, my_rated, genre, d, a, r\n' +
' RETURN DISTINCT movie,\n' +
' collect(DISTINCT d) AS directors,\n' +
' collect(DISTINCT a) AS actors,\n' +
' collect(DISTINCT genre) AS genres',
].join(' ');
return session
.query(query, {
movieId: movieId.toString(),
userId: userId.toString(),
})
.then((result) => {
if (result.hasNext()) {
return _singleMovieWithDetails(result.next());
}
throw {message: 'movie not found', status: 404};
});
};
MATCH (actor:Actor {tmdbId: "8537"})-[:ACTED_IN_MOVIE]->(movie:Movie)
RETURN DISTINCT movie,actor
MATCH (director:Director {tmdbId: "4945"})-[:DIRECTED]->(movie:Movie)
RETURN DISTINCT movie,director
const getByDirector = function (session, personId) {
const query = [
'MATCH (director:Director {tmdbId: $personId})-[:DIRECTED]->(movie:Movie)',
'RETURN DISTINCT movie,director',
].join('\n');
return session
.query(query, {
personId,
})
.then((result) => manyMovies(result));
};
MATCH (u:User {id: 42}),(m:Movie {tmdbId: 231})
MERGE (u)-[r:RATED]->(m)
SET r.rating = "7"
RETURN m
MATCH (:User {id: "d6b31131-f203-4d5e-b1ff-d13ebc06934d"})-[rated:RATED]->(movie:Movie)
RETURN DISTINCT movie, rated.rating as my_rating
const getRatedByUser = function (session, userId) {
return session
.query(
'MATCH (:User {id: $userId})-[rated:RATED]->(movie:Movie) \
RETURN DISTINCT movie, rated.rating as my_rating',
{userId},
)
.then((result) =>
result._results.map((r) => new Movie(r.get('movie'), r.get('my_rating'))),
);
};
id, bio, born , bornIn, imdbId, name, poster, tmdbId, url
id, name
id, born, bornIn, imdbId, name, tmdbId, url
id, username, password, api_key
id, url, languages, countries, budget, duration, imdbId, imdbRating, indbVotes, movieId, plot, poster, poster_image, released, revenue, runtime, tagline, tmdbId, year
RATED
->电影DIRECTED
->电影ACTED_IN_MOVIE
->电影IN_GENRE
->类型