结合React+TypeScript进行Electron开发

1. electron基本简介

electron是使用JavaScript,HTML和CSS构建跨平台桌面应用程序。我们可以使用一套代码打包成Mac、Windows和Linux的应用,electron比你想象的更简单,如果把你可以建一个网站,你就可以建一个桌面应用程序,我们只需要把精力放在应用的核心上即可。

为什么选择electron?

  • Electron 可以让你使用纯JavaScript调用丰富的原生APIs来创造桌面应用。你可以把它看作是专注于桌面应用。
  • 在PC端桌面应用开发中,nwjs和electron都是可选的方案,它们都是基于Chromium和Node的结合体,而electron相对而言是更好的选择方案,它的社区相对比较活跃,bug比较少,文档相对利索简洁。
  • electron相对来说比nw.js靠谱,有一堆成功的案例:Atom编辑器 Visual Studio Code WordPress等等。
  • Node.js的所有内置模块都在Electron中可用。

2. 快速上手

2.1 安装React(template为ts)

yarn create react-app electron-demo-ts --template typescript

2.2 快速配置React

工程架构

index.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Bleak's electron app base react"
/>
<meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-inline';">
<link rel="stylesheet" href="%PUBLIC_URL%/css/reset.css">
<title>electron App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>

App.tsx

import React from 'react'

export default function App() {
return (
<div>App</div>
)
}

index.tsx

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import reportWebVitals from './reportWebVitals'; const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
// <React.StrictMode>
<App />
// </React.StrictMode>
); // If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

2.3 安装electron

electron包安装到您的应用程序的devDependencies.

// npm
npm install --save-dev electron
// yarn
yarn add --dev electron

2.4 配置main.jspreload.jspackage.json文件

main.js

// 导入app、BrowserWindow模块
// app 控制应用程序的事件生命周期。事件调用app.on('eventName', callback),方法调用app.functionName(arg)
// BrowserWindow 创建和控制浏览器窗口。new BrowserWindow([options]) 事件和方法调用同app
// Electron参考文档 https://www.electronjs.org/docs
const {app, BrowserWindow, nativeImage } = require('electron')
const path = require('path')
// const url = require('url'); function createWindow () {
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 800, // 窗口宽度
height: 600, // 窗口高度
// title: "Electron app", // 窗口标题,如果由loadURL()加载的HTML文件中含有标签<title>,该属性可忽略
icon: nativeImage.createFromPath('public/favicon.ico'), // "string" || nativeImage.createFromPath('public/favicon.ico')从位于 path 的文件创建新的 NativeImage 实例
webPreferences: { // 网页功能设置
webviewTag: true, // 是否使用<webview>标签 在一个独立的 frame 和进程里显示外部 web 内容
webSecurity: false, // 禁用同源策略
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: true // 是否启用node集成 渲染进程的内容有访问node的能力,建议设置为true, 否则在render页面会提示node找不到的错误
}
}) // 加载应用 --打包react应用后,__dirname为当前文件路径
// mainWindow.loadURL(url.format({
// pathname: path.join(__dirname, './build/index.html'),
// protocol: 'file:',
// slashes: true
// })); // 因为我们是加载的react生成的页面,并不是静态页面
// 所以loadFile换成loadURL。
// 加载应用 --开发阶段 需要运行 yarn start
mainWindow.loadURL('http://localhost:3000'); // 解决应用启动白屏问题
mainWindow.on('ready-to-show', () => {
mainWindow.show();
mainWindow.focus();
}); // 当窗口关闭时发出。在你收到这个事件后,你应该删除对窗口的引用,并避免再使用它。
mainWindow.on('closed', () => {
mainWindow = null;
}); // 在启动的时候打开DevTools
mainWindow.webContents.openDevTools()
} app.allowRendererProcessReuse =true; // This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(() =>{
console.log('qpp---whenready');
createWindow();}) // Quit when all windows are closed.
app.on('window-all-closed', function () {
// On macOS it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
console.log('window-all-closed');
if (process.platform !== 'darwin') app.quit()
}) app.on('activate', function () {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) createWindow()
}) // In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.

package.json

这时候我们来修改package.json文件。

  1. 配置启动文件,添加main字段,我们这里也就是main.js文件。如果没有添加,Electron 将尝试加载包含在package.json文件目录中的index.js文件。
  2. 配置运行命令,使用"electron": "electron ." 区别于react的启动命令"start": "react-scripts start",
  3. 安装concurrently: yarn add concurrently
{
...
"main": "main.js", // 配置启动文件
"homepage": ".", // 设置应用打包的根路径
"scripts": {
"start": "react-scripts start", // react 启动命令
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"electron": "electron .", // electron 启动命令
"dev": "concurrently \"npm run start\" \"npm run electron\""
},
}

preload.js

window.addEventListener('DOMContentLoaded', () => {
const replaceText = (selector, text) => {
const element = document.getElementById(selector)
if (element) element.innerText = text
} for (const dependency of ['chrome', 'node', 'electron']) {
replaceText(`${dependency}-version`, process.versions[dependency])
}
})

此时的工程架构

2.5 运行electron项目

  1. yarn start 然后再开一个终端yarn electron
  2. 或者是npm run dev

其实我们就可以看出Electron就是一个应用套了一个谷歌浏览器壳子,然后里面是前端页面。

2.6 打包项目

使用electron-packager依赖:

yarn add --dev electron-packager

package.json配置打包命令:

"package": "electron-packager . bleak-electron-app --platform=win32 --arch=x64 --overwrite --electron-version=18.1.0 --icon=./public/favicon.ico"

配置解释:

electron-packager <应用目录> <应用名称> <打包平台> <架构x86 还是 x64> <架构> <electron版本> <图标>
overwrite 如果输出目录已经存在,替换它

然后运行命令:

yarn package

打包时间慢的话可按照下面两种方式优化:

方法1:
在执行electron-packager前先运行set ELECTRON_MIRROR=http://npm.taobao.org/mirrors/electron/
方法2:
在electron-packager命令行加入参数--download.mirrorOptions.mirror=https://npm.taobao.org/mirrors/electron/
(Windows x64)完整版如下:
electron-packager . bleak-electron-app --platform=win32 --arch=x64 --overwrite --electron-version=18.0.4 --download.mirrorOptions.mirror=https://npm.taobao.org/mirrors/electron/

然后运行bleak-electron-app-win32-x64里面的exe文件就可以了。

3. 自动刷新页面

当你用react开发的时候,网页内容会自动热更新,但是electron窗口的main.js中代码发生变化时不能热加载。

安装插件electron-reloader:

yarn add --dev electron-reloader
npm install --save-develectron-reloader

然后在路口引用插件:

const reloader = require('electron-reloader')
reloader(module)

就可以实现electron插件热更新。

4. 主进程和渲染进程

Electron运行package.json的main脚本的进程称为主进程。在主进程中运行的脚本通过创建web页面来展示用户节面,一个Electron应用总是有且只有一个主进程。

由于Electron使用了Chromium来展示web页面,所以Chromium的多进程架构也被使用到,每个Electron钟大哥web页面运行在它的叫渲染进程的进程中。

在普通的浏览器中,web页面无法访问操作系统的原生资源。然而Electron的用户在Node.js的API支持下可以在页面中和操作系统进行一些底层交互。

ctrl + shift + i 打开渲染进程调试(devtools)

默认打开调试:

// 在启动的时候打开DevTools
mainWindow.webContents.openDevTools()

5.定义原生菜单、顶部菜单

5.1 自定义菜单

可以使用Menu菜单来创建原生应用菜单和上下文菜单。

  1. 首先判断是什么平台,是mac还是其他:
const isMac = process.platform === 'darwin'
  1. 创建菜单模板:

其是由一个个MenuItem组成的,可以在菜单项官网API查看。

const template = [
// { role: 'appMenu' }
// 如果是mac系统才有
...(isMac ? [{
label: app.name,
submenu: [
{ role: 'about' },
{ type: 'separator' },
{ role: 'services' },
{ type: 'separator' },
{ role: 'hide' },
{ role: 'hideOthers' },
{ role: 'unhide' },
{ type: 'separator' },
{ role: 'quit' }
]
}] : []),
// { role: 'fileMenu' }
{
label: '文件',
submenu: [
isMac ? { role: 'close' } : { role: 'quit', label: '退出' }
]
},
// { role: 'editMenu' }
{
label: '编辑',
submenu: [
{ role: 'undo', label: '撤消' },
{ role: 'redo', label: '恢复' },
{ type: 'separator' },
{ role: 'cut', label: '剪切' },
{ role: 'copy', label: '复制' },
{ role: 'paste', label: '粘贴' },
...(isMac ? [
{ role: 'pasteAndMatchStyle' },
{ role: 'delete' },
{ role: 'selectAll' },
{ type: 'separator' },
{
label: 'Speech',
submenu: [
{ role: 'startSpeaking' },
{ role: 'stopSpeaking' }
]
}
] : [
{ role: 'delete', label: '删除' },
{ type: 'separator' },
{ role: 'selectAll', label: '全选' }
])
]
},
// { role: 'viewMenu' }
{
label: '查看',
submenu: [
{ role: 'reload', label: '重新加载' },
{ role: 'forceReload', label: '强制重新加载' },
{ role: 'toggleDevTools', label: '切换开发工具栏' },
{ type: 'separator' },
{ role: 'resetZoom', label: '原始开发工具栏窗口大小' },
{ role: 'zoomIn', label: '放大开发工具栏窗口'},
{ role: 'zoomOut', label: '缩小开发工具栏窗口' },
{ type: 'separator' },
{ role: 'togglefullscreen', label:'切换开发工具栏全屏' }
]
},
// { role: 'windowMenu' }
{
label: '窗口',
submenu: [
{ role: 'minimize', label:'最小化' },
...(isMac ? [
{ type: 'separator' },
{ role: 'front' },
{ type: 'separator' },
{ role: 'window' }
] : [
{ role: 'close', label: '关闭' }
])
]
},
{
role: 'help',
label: '帮助',
submenu: [
{
label: '从Electron官网学习更多',
click: async () => {
const { shell } = require('electron')
await shell.openExternal('https://electronjs.org')
}
}
]
}
]
  1. 根据模板创建menu:
const menu = Menu.buildFromTemplate(template)
  1. 设置菜单:
Menu.setApplicationMenu(menu)

5.2 给菜单定义点击事件

可以通过click属性来设置点击事件

5.3 抽离菜单定义

创建一个menu.js:

const {app, Menu } = require('electron')

const isMac = process.platform === 'darwin'

const template = [
// { role: 'appMenu' }
// 如果是mac系统才有
...(isMac ? [{
label: app.name,
submenu: [
{ role: 'about' },
{ type: 'separator' },
{ role: 'services' },
{ type: 'separator' },
{ role: 'hide' },
{ role: 'hideOthers' },
{ role: 'unhide' },
{ type: 'separator' },
{ role: 'quit' }
]
}] : []),
// { role: 'fileMenu' }
{
label: '文件',
submenu: [
isMac ? { role: 'close' } : { role: 'quit', label: '退出' }
]
},
// { role: 'editMenu' }
{
label: '编辑',
submenu: [
{ role: 'undo', label: '撤消' },
{ role: 'redo', label: '恢复' },
{ type: 'separator' },
{ role: 'cut', label: '剪切' },
{ role: 'copy', label: '复制' },
{ role: 'paste', label: '粘贴' },
...(isMac ? [
{ role: 'pasteAndMatchStyle' },
{ role: 'delete' },
{ role: 'selectAll' },
{ type: 'separator' },
{
label: 'Speech',
submenu: [
{ role: 'startSpeaking' },
{ role: 'stopSpeaking' }
]
}
] : [
{ role: 'delete', label: '删除' },
{ type: 'separator' },
{ role: 'selectAll', label: '全选' }
])
]
},
// { role: 'viewMenu' }
{
label: '查看',
submenu: [
{ role: 'reload', label: '重新加载' },
{ role: 'forceReload', label: '强制重新加载' },
{ role: 'toggleDevTools', label: '切换开发工具栏' },
{ type: 'separator' },
{ role: 'resetZoom', label: '原始开发工具栏窗口大小' },
{ role: 'zoomIn', label: '放大开发工具栏窗口'},
{ role: 'zoomOut', label: '缩小开发工具栏窗口' },
{ type: 'separator' },
{ role: 'togglefullscreen', label:'切换开发工具栏全屏' }
]
},
// { role: 'windowMenu' }
{
label: '窗口',
submenu: [
{ role: 'minimize', label:'最小化' },
...(isMac ? [
{ type: 'separator' },
{ role: 'front' },
{ type: 'separator' },
{ role: 'window' }
] : [
{ role: 'close', label: '关闭' }
])
]
},
{
role: 'help',
label: '帮助',
submenu: [
{
label: '从Electron官网学习更多',
click: async () => {
const { shell } = require('electron')
await shell.openExternal('https://electronjs.org')
}
}
]
}
] const menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)

然后在main.jscreateWindow的方法使用require调用:

const createWindow = () => {
......
require('./menu')
......
}

5.4 自定义顶部菜单

我们可以自定义顶部菜单,通过以下两个步骤进行:

  • 先通过frame创建无边框窗口。
function createWindow () {
const mainWindow = new BrowserWindow({
......
frame: false
})
}
  • 然后再通过前端页面布局设置顶部菜单

如果想让顶部菜单支持拖拽,可以加如下css:

-webkit-app-region: drag;

5.5 在渲染进程中使用主进程方法remote和electron(点击创建新窗口)

我们想要通过remote来使用主进程方法和功能。

  1. 首先要安装@electron/remote
yarn add @electron/remote
  1. 在主进程main.js中配置remote:
const remote = require("@electron/remote/main")
remote.initialize() const createWindow = () => {
let mainWindow = new BrowserWindow({
......
webPreferences: { // 网页功能设置
......
nodeIntegration: true, // 是否启用node集成 渲染进程的内容有访问node的能力,建议设置为true, 否则在render页面会提示node找不到的错误
contextIsolation : false, //允许渲染进程使用Nodejs
}
})
remote.enable(mainWindow.webContents)
}
  1. 在渲染进程中使用remote的BrowserWindow:App.tsx
import React from 'react'
// 使用electron的功能
// const electron = window.require('electron')
// 使用remote
const { BrowserWindow } = window.require("@electron/remote") export default function App() {
const openNewWindow = () => {
new BrowserWindow({
width:500,
height:500
})
} return (
<div>
App
<div>
<button onClick={openNewWindow}>点我开启新窗口</button>
</div>
</div>
)
}

我们想要通过使用electron提供给渲染进程的API:

const electron = window.require('electron')

然后从electron中提取方法。

5.6 点击打开浏览器

使用electron中的shell可以实现此功能:

import React from 'react'
// 使用electron的功能
// const electron = window.require('electron')
// 使用remote
// const { BrowserWindow } = window.require("@electron/remote")
// 使用shell
const { shell } = window.require('electron') export default function App() {
const openNewWindow = () => {
shell.openExternal('https://www.baidu.com')
} return (
<div>
App
<div>
<button onClick={openNewWindow}>点我开启新窗口打开百度</button>
</div>
</div>
)
}

6. 打开对话框读取文件

6.1 读取文件

主进程中的dialog模块可以显示用于打开和保存文件、警报等的本机系统对话框。

因为dialog模块属于主进程,如果我们在渲染进程中需要使用则需要使用remote模块。

App.tsx

import React,{ useRef } from 'react'
// 使用electron的功能
// const electron = window.require('electron') // 使用remote
// const remote = window.require('@electron/remote')
// const { BrowserWindow } = window.require("@electron/remote")
const { dialog } = window.require("@electron/remote") // 使用shell
const { shell } = window.require('electron') // 使用fs
const fs = window.require('fs') export default function App() {
// ref
const textRef = useRef<HTMLTextAreaElement | null>(null) const openNewWindow = () => {
shell.openExternal('https://www.baidu.com')
} const openFile = () => {
const res = dialog.showOpenDialogSync({
title: '读取文件', // 对话框窗口的标题
buttonLabel: "读取", // 按钮的自定义标签, 当为空时, 将使用默认标签。
filters: [ // 用于规定用户可见或可选的特定类型范围
//{ name: 'Images', extensions: ['jpg', 'png', 'gif', 'jpeg', 'webp'] },
//{ name: 'Movies', extensions: ['mkv', 'avi', 'mp4'] },
{ name: 'Custom File Type', extensions: ['js'] },
{ name: 'All Files', extensions: ['*'] },
]
})
const fileContent:string = fs.readFileSync(res[0]).toString();
(textRef.current as HTMLTextAreaElement).value = fileContent
} return (
<div>
App Test
<div>
<button onClick={openNewWindow}>点我开启新窗口打开百度</button>
</div>
<div>
<button onClick={openFile}>打开文件</button>
<textarea ref={textRef}></textarea>
</div>
</div>
)
}

6.2 保存文件

保存文件需要使用dialog函数里的showSaveDialogSync,与之前的读取文件所用到的showOpenDialogSync类似:

const saveFile = () => {
const res = dialog.showSaveDialogSync({
title:'保存文件',
buttonLable: "保存",
filters: [
{ name: 'index', extensions: ['js']}
]
})
fs.writeFileSync(res, textRef.current?.value)
}

7. 定义快捷键

7.1 主线程定义

引入globalShortcut

const {app, BrowserWindow, nativeImage, globalShortcut } = require('electron')

注册快捷键打印字符串、窗口最大化、窗口最小化、关闭窗口。

const createWindow = () => {
......
// 注册快捷键
globalShortcut.register('CommandOrControl+X', () => {
console.log('CommandOrControl + X is pressed')
}) globalShortcut.register('CommandOrControl+M', () => {
mainWindow.maximize()
}) globalShortcut.register('CommandOrControl+T', () => {
mainWindow.unmaximize()
}) globalShortcut.register('CommandOrControl+H', () => {
mainWindow.close()
}) // 检查快捷键是否注册成功
// console.log(globalShortcut.isRegistered('CommandOrControl+X'))
} // 将要退出时的生命周期,注销快捷键
app.on('will-quit', () => {
// 注销快捷键
globalShortcut.unregister('CommandOrControl+X')
// 注销所有快捷键
globalShortcut.unregisterAll()
})

7.2在渲染进程中定义

通过retmote来定义

const { globalShortcut } = window.require("@electron/remote")

globalShortcut.register("Ctrl+O", () => {
console.log('ctrl+O is pressed.')
})

8. 主进程和渲染进程通讯

在渲染进程使用ipcRenderer,主进程使用ipcMain,可以实现主进程和渲染进程的通讯:

App.tsx

......
import React,{ useState, useRef } from 'react'
const { shell, ipcRenderer } = window.require('electron')
export default function App() {
// state
const [windowSize, setWindowSize] = useState('max-window')
......
// 传参
const maxWindow = () => {
ipcRenderer.send('max-window', windowSize);
if(windowSize === 'max-window') {
setWindowSize('unmax-window')
} else {
setWindowSize('max-window')
}
}
......
return (
<div>
<div>
<button onClick={maxWindow}>与主进程进行通讯,窗口最大化或取消窗口最大化</button>
</div>
</div>
</div>
)
}

main.js

const {app, BrowserWindow, nativeImage, globalShortcut, ipcMain } = require('electron')
const createWindow = () => {
let mainWindow = ......
......
// 定义通讯事件
ipcMain.on('max-window', (event, arg) => {
if(arg === 'max-window') {
mainWindow.maximize()
} else if (arg === 'unmax-window') {
mainWindow.unmaximize()
}
})
......
}
......

Electron结合React和TypeScript进行开发的更多相关文章

  1. 从零配置webpack(react+less+typescript+mobx)

    本文目标 从零搭建出一套支持react+less+typescript+mobx的webpack配置 最简化webpack配置 首页要初始化yarn和安装webpack的依赖 yarn init -y ...

  2. React Hooks Typescript 开发的一款 H5 移动端 组件库

    CP design 使用 React hooks Typescript 开发的一个 H5 移动端 组件库 English | 简体中文 badge button icon CP Design Mobi ...

  3. electron教程(番外篇一): 开发环境及插件, VSCode调试, ESLint + Google JavaScript Style Guide代码规范

    我的electron教程系列 electron教程(一): electron的安装和项目的创建 electron教程(番外篇一): 开发环境及插件, VSCode调试, ESLint + Google ...

  4. React Native – 使用 JavaScript 开发原生应用

    前不久,Facebook 在F8开发者大会上正式开源了 React Native 项目.不过目前只有 iOS 版,Android 版还需要再等一段时间,这是最新的用 JavaScript 语言开发原生 ...

  5. 实例讲解基于 React+Redux 的前端开发流程

    原文地址:https://segmentfault.com/a/1190000005356568 前言:在当下的前端界,react 和 redux 发展得如火如荼,react 在 github 的 s ...

  6. React Router 4.x 开发,这些雷区我们都帮你踩过了

    前言 在前端框架层出不穷的今天,React 以其虚拟 DOM .组件化开发思想等特性迅速占据了主流位置,成为前端开发工程师热衷的 Javascript 库.作为 React 体系中的重要组成部分:Re ...

  7. react + react-router + less +antd 开发环境

    react + react-router + less +antd 开发环境搭建 1.基于create-reacte-app,需要先安装这个脚手架,然后初始化项目. 2.进入项目目录,首先 npm r ...

  8. TypeScript进阶开发——ThreeJs基础实例,从入坑到入门

    前言 我们前面使用的是自己编写的ts,以及自己手动引入的jquery,由于第三方库采用的是直接引入js,没有d.ts声明文件,开发起来很累,所以一般情况下我们使用npm引入第三方的库,本文记录使用np ...

  9. React与Typescript整合

    0. Typescript Typescript对于前端来说可以说是越来越重要了,前端的很多项目都用Typescript进行了重构.这主要得益于Typescript有比较好的类型支持,在编码的过程中可 ...

随机推荐

  1. 到底为什么不建议使用SELECT *?

    "不要使用SELECT *"几乎已经成为了MySQL使用的一条金科玉律,就连<阿里Java开发手册>也明确表示不得使用*作为查询的字段列表,更是让这条规则拥有了权威的加 ...

  2. 还在担心CC攻击? 让我们来了解它, 并尽可能将其拒之服务之外.

    还在担心CC攻击? 让我们来了解它, 并尽可能将其拒之服务之外. CC攻击是什么? 基本原理 CC原名为ChallengeCollapsar, 这种攻击通常是攻击者通过大量的代理机或者肉鸡给目标服务器 ...

  3. TypeSciprt webpack配置

    初始化 初始化项目 npm init -y 安装依赖 npm install ... --save-dev 依赖包列表 名称 作用 webpack 构建工具webpack webpack-cli we ...

  4. 使用 JWT 来保护你的 SpringBoot 应用

    关键词 写在前面 Spring Boot 创建Spring Boot应用 创建一个Web 应用 使用JWT保护你的Spring Boot应用 添加Spring Security 本文代码 关键词 Sp ...

  5. Spring两种注入方式

    1.XML注入 2.标签注入

  6. Java并发机制(9)--Callable、Future、FutureTask的使用

    Java并发编程:Callable.Future.FutureTask的使用 整理自:博客园-海子-http://www.cnblogs.com/dolphin0520/p/3949310.html ...

  7. kafka-linux-install

    linux按照kafka 必须先按照java jdk包!!!!!!!!!!!! 先安装zookeeper 下载:http://mirrors.hust.edu.cn/apache/zookeeper/ ...

  8. @Controller 注解?

    该注解表明该类扮演控制器的角色,Spring 不需要你继承任何其他控制器基类或 引用 Servlet API.

  9. 信号量,semaphore源代码之我见

    信号量,Semaphore,一个限定访问线程数量的工具类,属于并发包java.util.concurrent 里面的类. Semaphore,内部提供了构造方法(包含默认的非公平信号量构造方法,已经可 ...

  10. WEB架构深度优化之PHP

    一.PHP引擎缓存加速优化(4种) 1.eAccelerator 2.XCache 3.APC 4.Zend 二.使用tmpfs作为缓存加速缓存的目录(可用rc.local或fstab来自动挂载) m ...