>  기사  >  웹 프론트엔드  >  콘텐츠 관리 시스템 만들기: nodePress

콘텐츠 관리 시스템 만들기: nodePress

PHPz
PHPz원래의
2023-09-03 13:53:051395검색

Go를 사용하여 플랫 파일 시스템 콘텐츠 관리 시스템(CMS)을 성공적으로 만들었습니다. 다음 단계는 동일한 아이디어를 가지고 Node.js를 사용하여 웹 서버를 만드는 것입니다. 라이브러리를 로드하고, 서버를 생성하고, 서버를 실행하는 방법을 보여드리겠습니다.

이 CMS는 첫 번째 튜토리얼 "CMS 구축: 구조 및 스타일"에서 소개된 사이트 데이터 구조를 사용합니다. 따라서 이 기본 구조를 다운로드하여 새 디렉토리에 설치하십시오.

노드 및 노드 라이브러리 가져오기

Mac에 Node.js를 설치하는 가장 쉬운 방법은 Homebrew를 사용하는 것입니다. Homebrew가 아직 설치되어 있지 않은 경우 Homebrew Revealed: The Ultimate Package Manager for OS X 튜토리얼에서 설치 방법을 보여줍니다.

Homebrew를 사용하여 Node.js를 설치하려면 터미널에 다음 명령을 입력하세요.

으아악

완료되면 Node 및 npm 명령이 Mac에 완전히 설치됩니다. 다른 모든 플랫폼의 경우 Node.js 웹사이트의 지침을 따르세요.

참고: 현재 많은 패키지 관리자가 Node.js 버전 0.10을 설치하고 있습니다. 이 튜토리얼에서는 버전 5.3 이상이 있다고 가정합니다. 다음을 입력하여 버전을 확인할 수 있습니다.

으아악

node 命令运行 JavaScript 解释器。 npm 명령은 Node.js의 패키지 관리자이며 새 라이브러리를 설치하고, 새 프로젝트를 만들고, 프로젝트 스크립트를 실행하는 데 사용됩니다. Envato Tuts+에는 Node.js 및 NPM에 대한 훌륭한 튜토리얼과 강좌가 많이 있습니다.

웹 서버의 라이브러리를 설치하려면 Terminal.app 또는 iTerm.app 프로그램에서 다음 명령을 실행해야 합니다.

으아악

Express는 웹 애플리케이션 개발 플랫폼입니다. Go의 goWeb 라이브러리와 유사합니다. 핸들바는 페이지 생성을 위한 템플릿 엔진입니다. Moment는 날짜 작업을 위한 라이브러리입니다. Marked는 JavaScript의 HTML 변환기로 훌륭한 Markdown을 제공합니다. Jade는 HTML을 쉽게 생성할 수 있게 해주는 HTML 속기 언어입니다. Morgan은 Apache 표준 로그 파일을 생성하는 Express용 미들웨어 라이브러리입니다.

라이브러리를 설치하는 또 다른 방법은 이 튜토리얼의 소스 파일을 다운로드하는 것입니다. 다운로드하고 압축을 푼 후 기본 디렉터리에 다음을 입력하세요.

으아악

이렇게 하면 이 프로젝트를 만드는 데 필요한 모든 것이 설치됩니다.

nodePress.js

이제 서버 생성을 시작할 수 있습니다. 프로젝트의 최상위 디렉터리에서 nodePress.js라는 파일을 만들고 원하는 편집기에서 열고 다음 코드를 추가하기 시작합니다. 파일에 넣은 코드를 설명하겠습니다.

으아악

서버 코드는 서버를 생성하는 데 사용된 모든 라이브러리를 초기화하는 것부터 시작됩니다. URL이 포함된 주석이 없는 라이브러리는 내부 Node.js 라이브러리입니다.

으아악

다음으로 모든 전역 변수와 라이브러리 구성을 설정했습니다. 전역 변수를 사용하는 것은 최고의 소프트웨어 설계 방식은 아니지만 빠른 개발에 효과가 있고 도움이 됩니다.

parts 변수는 웹페이지의 모든 부분을 포함하는 해시 배열입니다. 각 페이지는 이 변수의 내용을 참조합니다. 이는 서버 디렉터리 상단에 있는 server.json 파일의 내용으로 시작됩니다.

그런 다음 server.json 파일의 정보를 사용하여 이 사이트의 styleslayouts 디렉터리에 대한 전체 경로를 만들었습니다.

그런 다음 세 개의 변수를 빈 값으로 설정합니다. siteCSSsiteScriptsmainPage。这些全局变量将包含所有 CSS、JavaScript 和主索引页内容。这三个项目是任何 Web 服务器上请求最多的项目。因此,将它们保留在内存中可以节省时间。如果 server.json 文件中的 Cache 변수가 false이면 요청이 있을 때마다 항목을 다시 읽습니다.

으아악

이 코드 블록은 Markdown에서 HTML을 생성하도록 Marked 라이브러리를 구성하는 데 사용됩니다. 대부분의 경우 테이블과 smartList 지원을 켭니다.

으아악

parts 变量进一步加载 styleslayout 目录中的部分。 site 目录内的 parts 目录中的每个文件也被加载到 parts 변수는 styleslayout 디렉터리의 섹션을 추가로 로드합니다. site 디렉터리 내

디렉터리의 각 파일도

전역 변수에 로드됩니다. 확장자가 없는 파일 이름은 파일 내용을 저장하는 데 사용되는 이름입니다. 이러한 이름은 Handlebars 매크로에서 확장됩니다. savedatecdate 으아악

다음 코드 조각은 웹 서버에서 사용하기 위해 정의한 Handlebars 도우미를 정의합니다.

. 저장 도우미를 사용하면 페이지 내에서 변수를 생성할 수 있습니다. 이 버전은 매개변수의 이름과 값이 "|"로 구분된 goPress 버전을 지원합니다. 두 개의 매개변수를 사용하여 저장을 지정할 수도 있습니다. 예:

으아악 🎜이렇게 하면 동일한 결과가 나타납니다. 나는 두 번째 접근 방식을 선호하지만 Go의 Handlebars 라이브러리는 여러 매개변수를 허용하지 않습니다. 🎜

datecdate 帮助程序格式化当前日期 (date) 或给定日期 (cdate)根据 moment.js 库格式化规则。 cdate 帮助程序期望渲染的日期是第一个参数并且具有 ISO 8601 格式。

//
// Create and configure the server.
//
var nodePress = express();

//
// Configure middleware.
//
nodePress.use(morgan('combined'))

现在,代码创建一个 Express 实例来配置实际的服务器引擎。 nodePress.use() 函数设置中间件软件。中间件是在每次调用服务器时提供服务的任何代码。在这里,我设置了 Morgan.js 库来创建正确的服务器日志输出。

//
// Define the routes.
//
nodePress.get('/', function(request, response) {
   setBasicHeader(response);
   if((parts["Cache"] == true) && (mainPage != null)) {
       response.send(mainPage);
   } else {
   	mainPage = page("main");
   	response.send(mainPage);
   }
});

nodePress.get('/favicon.ico', function(request, response) {
   var options = {
      root: parts['Sitebase'] + 'images/',
      dotfiles: 'deny',
      headers: {
         'x-timestamp': Date.now(),
         'x-sent': true
      }
   };
   response.set("Content-Type", "image/ico");
   setBasicHeader(response);
   response.sendFile('favicon.ico', options, function(err) {
      if (err) {
         console.log(err);
         response.status(err.status).end();
      } else {
         console.log('Favicon was sent:', 'favicon.ico');
      }
   });
});

nodePress.get('/stylesheets.css', function(request, response) {
   response.set("Content-Type", "text/css");
   setBasicHeader(response);
   response.type("css");
   if((parts["Cache"] == true) && (siteCSS != null)) {
   	response.send(siteCSS);
   } else {
   	siteCSS = fs.readFileSync(parts['Sitebase'] + 'css/final/final.css');
   	response.send(siteCSS);
   }
});

nodePress.get('/scripts.js', function(request, response) {
   response.set("Content-Type", "text/javascript");
   setBasicHeader(response);
   if((parts["Cache"] == true) && (siteScripts != null)) {
   	response.send(siteScripts);
   } else {
   	siteScripts = fs.readFileSync(parts['Sitebase'] + 'js/final/final.js', 'utf8');
   	response.send(siteScripts);
   }
});

nodePress.get('/images/:image', function(request, response) {
   var options = {
      root: parts['Sitebase'] + 'images/',
      dotfiles: 'deny',
      headers: {
         'x-timestamp': Date.now(),
         'x-sent': true
      }
   };
   response.set("Content-Type", "image/" + path.extname(request.params.image).substr(1));
   setBasicHeader(response);
   response.sendFile(request.params.image, options, function(err) {
      if (err) {
         console.log(err);
         response.status(err.status).end();
      } else {
         console.log('Image was sent:', request.params.image);
      }
   });
});

nodePress.get('/posts/blogs/:blog', function(request, response) {
   setBasicHeader(response);
   response.send(post("blogs", request.params.blog, "index"));
});

nodePress.get('/posts/blogs/:blog/:post', function(request, response) {
   setBasicHeader(response);
   response.send(post("blogs", request.params.blog, request.params.post));
});

nodePress.get('/posts/news/:news', function(request, response) {
   setBasicHeader(response);
   response.send(post("news", request.params.news, "index"));
});

nodePress.get('/posts/news/:news/:post', function(request, response) {
   setBasicHeader(response);
   response.send(post("news", request.params.news, request.params.post));
});

nodePress.get('/:page', function(request, response) {
   setBasicHeader(response);
   response.send(page(request.params.page));
});

这部分代码定义了实现 Web 服务器所需的所有路由。所有路由都运行 setBasicHeader() 函数来设置正确的标头值。所有针对页面类型的请求都会调用 page() 函数,而所有针对 post 类型页面的请求都会调用 posts() 函数。

Content-Type 的默认值为 HTML。因此,对于 CSS、JavaScript 和图像,Content-Type 显式设置为其适当的值。

您还可以使用 putdeletepost REST 动词定义路由。这个简单的服务器仅使用 get 动词。

//
// Start the server.
//
var addressItems = parts['ServerAddress'].split(':');
var server = nodePress.listen(addressItems[2], function() {
   var host = server.address().address;
   var port = server.address().port;

   console.log('nodePress is listening at http://%s:%s', host, port);
});

在定义所使用的不同函数之前要做的最后一件事是启动服务器。 server.json 文件包含 DNS 名称(此处为 localhost)和服务器的端口。解析后,服务器的 listen() 函数使用端口号来启动服务器。服务器端口打开后,脚本会记录服务器的地址和端口。

//
// Function:     	setBasicHeader
//
// Description: 	This function will set the basic header information
// 					needed.
//
// Inputs:
//						response 		The response object
//
function setBasicHeader(response) {
   response.append("Cache-Control", "max-age=2592000, cache");
   response.append("Server", "nodePress - a CMS written in node from Custom Computer Tools: http://customct.com.");
}

定义的第一个函数是 setBasicHeader() 函数。该函数设置响应头,告诉浏览器将页面缓存一个月。它还告诉浏览器该服务器是nodePress服务器。如果您需要任何其他标准标头值,您可以使用 response.append() 函数在此处添加它们。

//
// Function:         page
//
// Description:      This function processes a page request
//
// Inputs:
//                  page 		The requested page
//
function page(page) {
   //
   // Process the given page using the standard layout.
   //
   return (processPage(parts["layout"], parts['Sitebase'] + "pages/" + page));
}

page() 函数将页面的布局模板以及页面在服务器上的位置发送到 processPage() 函数。

//
// Function:         post
//
// Description:      This function processes a post request
//
// Inputs:
//                  type 		The type of post.
//                  cat 		The category of the post.
//                  post 		The requested post
//
function post(type, cat, post) {
   //
   // Process the post given the type and the post name.
   //
   return (processPage(parts["layout"], parts['Sitebase'] + "posts/" + type + "/" + cat + "/" + post));
}

post() 函数就像 page() 函数,不同之处在于帖子有更多项目来定义每个帖子。在这个系列的服务器中,一个post包含一个type、category,以及实际的post。类型为 blogsnews。类别是 flatcms。由于这些代表目录名称,因此您可以将它们设为您想要的任何名称。只需将命名与文件系统中的名称相匹配即可。

//
// Function:         processPage
//
// Description:      This function processes a page for the CMS.
//
// Inputs:
//                  layout 		The layout to use for the page.
//                  page 			Path to the page to render.
//
function processPage(layout, page) {
   //
   // Get the pages contents and add to the layout.
   //
   var context = {};
   context = MergeRecursive(context, parts);
   context['content'] = figurePage(page);
   context['PageName'] = path.basename(page, path.extname(page));

   //
   // Load page data.
   //
   if(fileExists(page + ".json")) {
   	//
   	// Load the page's data file and add it to the data structure.
   	//
   	context = MergeRecursive(context, JSON.parse(fs.readFileSync(page + '.json', 'utf8')));
   }

   //
   // Process Handlebars codes.
   //
   var template = Handlebars.compile(layout);
   var html = template(context);

   //
   // Process all shortcodes.
   //
   html = processShortCodes(html);

   //
   // Run through Handlebars again.
   //
   template = Handlebars.compile(html);
   html = template(context);

   //
   // Return results.
   //
   return (html);
}

processPage() 函数获取要呈现的页面内容的布局和路径。该函数首先创建 parts 全局变量的本地副本,并添加“contents”主题标签以及调用 figurePage() 函数的结果。然后,它将 PageName 哈希值设置为页面名称。

然后,该函数使用 Handlebars 将页面内容编译到布局模板。之后, processShortCodes() 函数将展开页面上定义的所有短代码。然后,Handlebars 模板引擎再次检查代码。然后浏览器接收结果。

//
// Function:     	processShortCodes
//
// Description: 	This function takes a string and
// 					processes all of the shortcodes in 
// 					the string.
//
// Inputs:
// 					content 		String to process
//
function processShortCodes(content) {
   //
   // Create the results variable.
   //
   var results = "";

   //
   // Find the first match.
   //
   var scregFind = /\-\[([^\]]*)\]\-/i;
   var match = scregFind.exec(content);
   if (match != null) {
   	results += content.substr(0,match.index);
      var scregNameArg = /(\w+)(.*)*/i;
      var parts = scregNameArg.exec(match[1]);
      if (parts != null) {
         //
         // Find the closing tag.
         //
         var scregClose = new RegExp("\\-\\[\\/" + parts[1] + "\\]\\-");
         var left = content.substr(match.index + 4 + parts[1].length);
         var match2 = scregClose.exec(left);
         if (match2 != null) {
            //
            // Process the enclosed shortcode text.
            //
            var enclosed = processShortCodes(content.substr(match.index + 4 + parts[1].length, match2.index));

            //
            // Figure out if there were any arguments.
            //
            var args = "";
            if (parts.length == 2) {
               args = parts[2];
            }

            //
            // Execute the shortcode.
            //
            results += shortcodes[parts[1]](args, enclosed);

            //
            // Process the rest of the code for shortcodes.
            //
            results += processShortCodes(left.substr(match2.index + 5 + parts[1].length));
         } else {
            //
            // Invalid shortcode. Return full string.
            //
            results = content;
         }
      } else {
         //
         // Invalid shortcode. Return full string.
         //
         results = content;
      }
   } else {
      //
      // No shortcodes found. Return the string.
      //
      results = content;
   }
   return (results);
}

processShortCodes() 函数将网页内容作为字符串并搜索所有短代码。短代码是类似于 HTML 标签的代码块。一个例子是:

-[box]-
    <p>This is inside a box</p>
-[/box]-

此代码在 HTML 段落周围有一个 box 的简码。其中 HTML 使用 >>,短代码使用 -[ 和 >]-。在名称后面,可以包含或不可以包含包含短代码参数的字符串。

processShortCodes() 函数查找短代码,获取其名称和参数,找到末尾以获取内容,处理短代码的内容,使用参数和内容执行短代码,将结果添加到完成中页面,并在页面的其余部分搜索下一个短代码。循环是通过递归调用函数来执行的。

//
// Define the shortcodes function array.
//
var shortcodes = {
   'box': function(args, inside) {
      return ("<div class='box'>" + inside + "</div>");
   },
   'Column1': function(args, inside) {
      return ("<div class='col1'>" + inside + "</div>");
   },
   'Column2': function(args, inside) {
      return ("<div class='col2'>" + inside + "</div>");
   },
   'Column1of3': function(args, inside) {
      return ("<div class='col1of3'>" + inside + "</div>");
   },
   'Column2of3': function(args, inside) {
      return ("<div class='col2of3'>" + inside + "</div>");
   },
   'Column3of3': function(args, inside) {
      return ("<div class='col3of3'>" + inside + "</div>");
   },
   'php': function(args, inside) {
      return ("<div class='showcode'><pre type='syntaxhighlighter' class='brush: php'>" + inside + "
"); }, 'js': function(args, inside) { return ("
" + inside + "
"); }, 'html': function(args, inside) { return ("
" + inside + "
"); }, 'css': function(args, inside) { return ("
" + inside + "
"); } };

下一节定义 shortcodes json 结构,该结构定义与其函数关联的短代码的名称。所有短代码函数都接受两个参数:argsinsideargs 是名称和空格之后、标签结束之前的所有内容。 inside 是开始和结束短代码标记包含的所有内容。这些功能是基本功能,但您可以创建一个短代码来执行您能在 JavaScript 中想到的任何功能。

//
// Function:        figurePage
//
// Description:     This function figures the page type
//                  and loads the contents appropriately
//                  returning the HTML contents for the page.
//
// Inputs:
//                  page 			The page to load contents.
//
function figurePage(page) {
   var result = "";

   if (fileExists(page + ".html")) {
      //
      // It's an HTML file. Read it in and send it on.
      //
      result = fs.readFileSync(page + ".html");
   } else if (fileExists(page + ".amber")) {
      //
      // It's a jade file. Convert to HTML and send it on. I
      // am still using the amber extension for compatibility
      // to goPress.
      //
      var jadeFun = jade.compileFile(page + ".amber", {});

      // Render the function
      var result = jadeFun({});
   } else if (fileExists(page + ".md")) {
      //
      // It's a markdown file. Convert to HTML and send
      // it on.
      //
      result = marked(fs.readFileSync(page + ".md").toString());

      //
      // This undo marked's URI encoding of quote marks.
      //
      result = result.replace(/\&quot\;/g,"\"");
   }

   return (result);
}

figurePage() 函数接收服务器上页面的完整路径。然后,此函数根据扩展名测试它是否为 HTML、Markdown 或 Jade 页面。我仍然在 Jade 中使用 .amber,因为那是我在 goPress 服务器上使用的库。所有 Markdown 和 Jade 内容都会先转换为 HTML,然后再传递给调用例程。由于 Markdown 处理器将所有引号翻译为 ",因此我在传回之前将它们翻译回来。

//
// Function:     	fileExists
//
// Description: 	This function returns a boolean true if 
// 					the file exists. Otherwise, false.
//
// Inputs:
// 					filePath 	Path to a file in a string.
//
function fileExists(filePath) {
   try {
      return fs.statSync(filePath).isFile();
   } catch (err) {
      return false;
   }
}

fileExists() 函数是 fs.exists() 函数的替代品,该函数曾经是 Node.js 的 fs 库的一部分。它使用 fs.statSync() 函数来尝试获取文件的状态。如果发生错误,则会返回 false。否则,返回 true

//
//  Function:        MergeRecursive
//
//  Description:     Recursively merge properties of two objects
//
//  Inputs:
//                   obj1    The first object to merge
//                   obj2    The second object to merge
//
function MergeRecursive(obj1, obj2) {

   for (var p in obj2) {
      try {
         // Property in destination object set; update its value.
         if (obj2[p].constructor == Object) {
            obj1[p] = MergeRecursive(obj1[p], obj2[p]);

         } else {
            obj1[p] = obj2[p];

         }

      } catch (e) {
         // Property in destination object not set; create it and set its value.
         obj1[p] = obj2[p];

      }
   }

   return obj1;
}

最后一个函数是 MergeRecursive() 函数。它将第二个传递对象复制到第一个传递对象中。在添加特定于页面的部分之前,我利用它将主 parts 全局变量复制到本地副本中。

本地运行

保存文件后,您可以使用以下命令运行服务器:

node nodePress.js

或者,您可以使用 package.json 文件中的 npm 脚本。您可以像这样运行 npm 脚本:

npm start

这将运行 package.json 文件内的 start 脚本。

"콘텐츠

将您的网络浏览器指向 http://localhost:8080,您将看到上面的页面。您可能已经注意到我在主页上添加了更多测试代码。对页面的所有更改都包含在本教程的下载中。它们大多只是一些小的调整,以更全面地测试功能并适应使用不同库的任何差异。最显着的区别是 Jade 库不使用 $ 来命名变量,而 Amber 则使用。

结论

现在,您在 Go 和 Node.js 中拥有完全相同的平面文件系统 CMS。这只是您可以使用此平台构建的内容的表面。尝试并尝试新事物。这是创建您自己的网络服务器的最佳部分。

위 내용은 콘텐츠 관리 시스템 만들기: nodePress의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.