WebRPC开发基础流程

一、WebRTC 使用入门

WebRTC(全称 Web Real-Time Communication),即网页即时通信。 是一个支持网页浏览器进行实时语音对话或视频对话的技术方案。从前端技术开发的视角来看,是一组可调用的API标准。

 WebRTC API

官网:WebRTC samples

WebRTC 标准概括介绍了两种不同的技术:媒体捕获设备点对点连接

媒体捕获设备包括摄像机和麦克风,还包括屏幕捕获设备。对于摄像头和麦克风,我们使用 `navigator.mediaDevices.getUserMedia()` 来捕获 `MediaStreams`。对于屏幕录制,我们改为使用 `navigator.mediaDevices.getDisplayMedia()`。

点对点连接由 `RTCPeerConnection` 接口处理。这是在 WebRTC 中两个对等方之间建立和控制连接的中心点。

WebRPC 需要做以下的几件事:
  • - 获取音频,视频或者其他数据
  • - 获取网络信息比如IP地址,端口,并与其他的WebRTC客户端进行交换,穿过NAT合防火墙进行连接.
  • - 处理信号以便发起请求报告错误或者关闭会话
  • - 交换客户端支持的媒体信息,比如分辨率,解码器
  • - 传输音频视频流或者数据
webrtc工作流程

WebRPC开发基础流程_第1张图片

媒体设备使用入门

针对 Web 开发时,WebRTC 标准提供了用于访问连接到计算机或智能手机的相机和麦克风的 API。这些设备通常称为媒体设备,可以通过实现 `MediaDevices` 接口的 `navigator.mediaDevices` 对象使用 JavaScript 进行访问。通过此对象,我们可以枚举所有已连接的设备,监听设备的变化(设备连接或断开连接时)以及打开设备以检索媒体流(见下文)。

其最常见的方式是通过 `getUserMedia()` 函数,该函数会返回一个解析为匹配媒体设备的 `MediaStream` 的 promise。此函数采用单个 `MediaStreamConstraints` 对象,用于指定我们的要求。例如,要简单地打开默认麦克风和摄像头,请执行以下操作。

// 使用promise
const constraints = {
    'video': true,
    'audio': true
}
navigator.mediaDevices.getUserMedia(constraints)
    .then(stream => {
        console.log('Got MediaStream:', stream);
    })
    .catch(error => {
        console.error('Error accessing media devices.', error);
    });

// 使用await/async
const openMediaDevices = async (constraints) => {
    return await navigator.mediaDevices.getUserMedia(constraints);
}

try {
    const stream = openMediaDevices({'video':true,'audio':true});
    console.log('Got MediaStream:', stream);
} catch(error) {
    console.error('Error accessing media devices.', error);
}

调用 `getUserMedia()` 将触发权限请求。如果用户接受该权限,系统会使用包含一个视频和一个音轨的 `MediaStream` 解析该 promise。如果权限遭拒,系统会抛出 `PermissionDeniedError`。如果没有连接任何匹配的设备,则会抛出 `NotFoundError`。

查询媒体设备

在更复杂的应用中,我们很可能需要检查所有连接的摄像头和麦克风,并向用户提供相应的反馈。这可以通过调用 `enumerateDevices()` 函数来实现。这将返回一个 promise,它可以解析为描述每个已知媒体设备的 `MediaDevicesInfo` 数组。我们可以用它来呈现界面,让用户选择他们喜欢的那个。每个 `MediaDevicesInfo` 都包含一个名为 `kind` 的属性,其值为 `audioinput`、`audiooutput` 或 `videoinput`,指示它是哪种类型的媒体设备。

// promise
function getConnectedDevices(type, callback) {
    navigator.mediaDevices.enumerateDevices()
        .then(devices => {
            const filtered = devices.filter(device => device.kind === type);
            callback(filtered);
        });
}

getConnectedDevices('videoinput', cameras => console.log('Cameras found', cameras));

// async await
async function getConnectedDevices(type) {
    const devices = await navigator.mediaDevices.enumerateDevices();
    return devices.filter(device => device.kind === type)
}

const videoCameras = getConnectedDevices('videoinput');
console.log('Cameras found:', videoCameras);
监听设备更改

大多数计算机都支持在运行时插入各种设备。它可能是通过 USB 连接的摄像头、蓝牙耳机或一组外部扬声器。为了正确支持这一点,Web 应用应监听媒体设备的变化。这可以通过为 `devicechange` 事件的 `navigator.mediaDevices` 添加监听器来实现。

// Updates the select element with the provided set of cameras
function updateCameraList(cameras) {
    const listElement = document.querySelector('select#availableCameras');
    listElement.innerHTML = '';
    cameras.map(camera => {
        const cameraOption = document.createElement('option');
        cameraOption.label = camera.label;
        cameraOption.value = camera.deviceId;
    }).forEach(cameraOption => listElement.add(cameraOption));
}

// Fetch an array of devices of a certain type
async function getConnectedDevices(type) {
    const devices = await navigator.mediaDevices.enumerateDevices();
    return devices.filter(device => device.kind === type)
}

// Get the initial set of cameras connected
const videoCameras = getConnectedDevices('videoinput');
updateCameraList(videoCameras);

// Listen for changes to media devices and update the list accordingly
navigator.mediaDevices.addEventListener('devicechange', event => {
    const newCameraList = getConnectedDevices('video');
    updateCameraList(newCameraList);
});
媒体限制

如果约束对象必须实现 `MediaStreamConstraints` 接口并将其作为参数传递给 `getUserMedia()`,我们就可以打开符合特定要求的媒体设备。此要求可以非常宽泛(音频和/或视频),也可以非常具体(最低相机分辨率或确切设备 ID)。建议使用 `getUserMedia()` API 的应用先检查现有设备,然后使用 `deviceId` 限制条件指定与设备完全匹配的限制条件。如果可能,设备还会根据限制条件进行配置。我们可以对麦克风启用回声消除功能,也可以从摄像头设置视频的特定或最小宽度和高度。

async function getConnectedDevices(type) {
    const devices = await navigator.mediaDevices.enumerateDevices();
    return devices.filter(device => device.kind === type)
}

// Open camera with at least minWidth and minHeight capabilities
async function openCamera(cameraId, minWidth, minHeight) {
    const constraints = {
        'audio': {'echoCancellation': true},
        'video': {
            'deviceId': cameraId,
            'width': {'min': minWidth},
            'height': {'min': minHeight}
            }
        }

    return await navigator.mediaDevices.getUserMedia(constraints);
}

const cameras = getConnectedDevices('videoinput');
if (cameras && cameras.length > 0) {
    // Open first available video camera with a resolution of 1280x720 pixels
    const stream = openCamera(cameras[0].deviceId, 1280, 720);
}
本地播放(拉流)

媒体设备打开后,如果有 MediaStream,我们可以将其分配给视频或音频元素,以在本地播放流。

async function playVideoFromCamera() {
    try {
        const constraints = {'video': true, 'audio': true};
        const stream = await navigator.mediaDevices.getUserMedia(constraints);
        const videoElement = document.querySelector('video#localVideo');
        videoElement.srcObject = stream;
    } catch(error) {
        console.error('Error opening video camera.', error);
    }
}

与 `getUserMedia()` 一起使用的典型视频元素所需的 HTML 通常具有 `autoplay` 和 `playsinline` 属性。`autoplay` 属性将使分配给元素的新数据流自动播放。`playsinline` 属性允许视频在特定移动浏览器中内嵌播放,而不仅仅是全屏播放。此外,我们还建议对直播使用 `controls="false"`,除非用户应能够暂停这些直播。


Local video playback</video></head>
<body>
    <video id="localVideo" autoplay playsinline controls="false"/>
</body>
</html></code></pre> 
  <h5>二、媒体捕获和约束</h5> 
  <p>WebRTC 的媒体部分介绍了如何使用能够捕捉视频和音频的硬件(例如相机和麦克风),以及媒体流的工作原理。此外,还介绍了显示媒体,这是应用可执行屏幕捕获的方式。</p> 
  <h6>媒体设备</h6> 
  <p>您可以通过 `navigator.mediaDevices` 对象访问和管理浏览器支持的所有摄像头和麦克风。应用可以检索已连接设备的最新列表并监听变化,因为许多相机和微型麦克风可通过 USB 连接,并且可以在应用生命周期内连接和断开连接。由于媒体设备的状态可能会随时发生变化,因此建议应用注册设备更改,以便正确处理更改。</p> 
  <h6>采集音视频</h6> 
  <p>访问媒体设备时,建议您提供尽可能详细的限制条件。虽然可以通过简单的约束条件打开默认摄像头和麦克风,但其提供的媒体流可能明显优于应用的最佳流。</p> 
  <p>具体的约束条件在 `MediaTrackConstraint` 对象中定义,一个针对音频,另一个针对视频。此对象中的特性类型为 `ConstraintLong`、`ConstraintBoolean`、`ConstraintDouble` 或 `ConstraintDOMString`。这些对象可以是特定值(例如数字、布尔值或字符串)、范围(具有最小值和最大值的 `LongRange` 或 `DoubleRange`)或具有 `ideal` 或 `exact` 定义的对象。对于特定值,浏览器将尝试选择尽可能接近的值。对于某个范围,将使用该范围内的最佳值。指定 `exact` 后,系统将仅返回与约束条件完全匹配的媒体流。</p> 
  <pre><code class="hljs">// Camera with a resolution as close to 640x480 as possible
{
    "video": {
        "width": 640,
        "height": 480
    }
}</code></pre> 
  <pre><code class="hljs">// Camera with a resolution in the range 640x480 to 1024x768
{
    "video": {
        "width": {
            "min": 640,
            "max": 1024
        },
        "height": {
            "min": 480,
            "max": 768
        }
    }
}</code></pre> 
  <pre><code class="hljs">// Camera with the exact resolution of 1024x768
{
    "video": {
        "width": {
            "exact": 1024
        },
        "height": {
            "exact": 768
        }
    }
}</code></pre> 
  <p>为了确定某个媒体流的特定轨道的实际配置,我们可以调用 `MediaStreamTrack.getSettings()`,它会返回当前应用的 `MediaTrackSettings`。</p> 
  <p>此外,也可以通过对媒体轨道上调用 `applyConstraints()` 来更新已打开的媒体设备上的轨道约束条件。这样,应用无需重新关闭现有音频流,即可重新配置媒体设备。</p> 
  <h6>显示媒体</h6> 
  <p>想要能够截取和录制屏幕的应用必须使用 Display Media API。函数 `getDisplayMedia()`(属于 `navigator.mediaDevices` 的一部分)与 `getUserMedia()` 类似,用于打开显示内容(或部分内容,如窗口)。返回的 `MediaStream` 与使用 `getUserMedia()` 时相同。</p> 
  <p>`getDisplayMedia()` 的约束条件与常规视频或音频输入资源的限制不同。</p> 
  <pre><code class="hljs">{
    video: {
        cursor: 'always' | 'motion' | 'never',
        displaySurface: 'application' | 'browser' | 'monitor' | 'window'
    }
}</code></pre> 
  <p>上述代码片段展示了屏幕录制的特殊限制的工作原理。请注意,并非所有支持显示媒体支持的浏览器都支持这些属性。</p> 
  <h6>帧率降噪功能配置</h6> 
  <ul> 
   <li> <p>frameRate:可以配置视频帧率</p> </li> 
   <li> <p>width:设置视频宽度,ideal代表理想宽度</p> </li> 
   <li> <p>height:设置视频高度,ideal代表理想高度</p> </li> 
   <li> <p>aspectRatio:代表宽高比</p> </li> 
   <li> <p>对于音频则是开启回音消除、降噪、自动增益等操作</p> </li> 
  </ul> 
  <pre><code class="hljs">const mediaStreamContrains = {
 video: {
 		frameRate: {min: 20},
 		width: {min: 640, ideal: 1280},
 		height: {min: 360, ideal: 720},
		aspectRatio: 16/9
 },
 audio: {
 		echoCancellation: true, // 开启回音消除
 		noiseSuppression: true, // 降噪
 		autoGainControl: true // 自动增益
 }
};

var promise = navigator.mediaDevices.getUserMedia(mediaStreamContrains);</code></pre> 
  <h6>采集视频数据</h6> 
  <ul> 
   <li> <p>采集摄像头的内容并在浏览器上播放</p> </li> 
   <li> <p>需要注意的是,一定要在https协议或者本地localhost域名下才可以调用</p> </li> 
   <li> <p>我们通过调用 <code>getUserMedia</code> 方法,将视频数据加载到 <code>video</code> 标签中进行播放</p> </li> 
   <li> <p>如果video标签想要播放流媒体数据,需要将数据挂在到 <code>srcObject</code>属性上,该属性和普通的 <code>src</code> 属性互斥</p> </li> 
   <li> <p>如果是第一次请求 Camera,浏览器会向用户弹出提示窗口,让用户决定是否可以访问摄像头</p> </li> 
   <li> <p>如果用户允许访问,且设备可用,则调用 <code>gotLocalMediaStream</code> 方法</p> </li> 
  </ul> 
  <h6>获取浏览器设备信息</h6> 
  <ul> 
   <li> <p>以手机为例,它一般会包括前置摄像头和后置摄像头麦克风、相机、耳机等。我们可以根据自己的需要,选择打开不同的设备</p> </li> 
   <li> <p>WebRTC 是否提供了的 <code>enumerateDevices</code> 接口,可以查询自己机子上都有哪些音视频设备</p> </li> 
   <li> <p>deviceInfo中有三个比较重要的属性</p> </li> 
   <li> <p>deviceID:设备的唯一标识</p> </li> 
   <li> <p>label:设备名称,用户已被授予访问媒体设备的权限(要想授予权限需要使用 HTTPS 请求),否则 label 字段始终为空。</p> </li> 
   <li> <p>kind:设备种类,可用于识别出是音频设备还是视频设备,是输入设备还是输出设备</p> </li> 
  </ul> 
  <pre><code class="hljs">// 判断浏览器是否支持这些 API
if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) {
    console.log("enumerateDevices() not supported.");
    return;
}

// 枚举 cameras and microphones.
navigator.mediaDevices.enumerateDevices()
    .then(function (deviceInfos) {
        // 打印出每一个设备的信息
        deviceInfos.forEach(function (deviceInfo) {
            console.log(deviceInfo.kind + ": " + deviceInfo.label +
                " id = " + deviceInfo.deviceId);
        });
    })
    .catch(function (err) {
        console.log(err.name + ": " + err.message);
    });</code></pre> 
  <h6>方法 `getUserMedia` 的配置参数</h6> 
  <ul> 
   <li>facingMode: ‘user’ , ‘environment’ 代表前后置。</li> 
   <li>sampleRate:指定采样率。</li> 
   <li>sampleSize:每个采样点大小的位数</li> 
   <li>volume:从0(静音)到1(最大)取值</li> 
   <li>echoCancellation:是否使用回声消除来尝试去除通过麦克风回传到扬声器的音频</li> 
   <li>autoGainControl:是否要修改麦克风的输入音量</li> 
   <li>noiseSuppression:是否尝试去除音频信号中的背景噪声</li> 
   <li>latency:以秒为单位,控制开始处理声音和下一步可以使用数据之间的时间,不是很确定为什么要设更高的延迟,但是音频编解码器的延时确实有所不同。</li> 
   <li>channelCount:规定了单声道的时候为1,立体声的时候为2。<br>  </li> 
  </ul> 
  <h6>数据流和轨道</h6> 
  <p>`MediaStream` 表示媒体内容流,由音频和视频轨道 (`MediaStreamTrack`) 组成。您可以通过调用 `MediaStream.getTracks()` 从 `MediaStream` 检索所有轨道,该方法会返回一组 `MediaStreamTrack` 对象。</p> 
  <h6>媒体流跟踪</h6> 
  <p>`MediaStreamTrack` 具有的 `kind` 属性为 `audio` 或 `video`,用于表示其表示的媒体类型。您可以通过切换其 `enabled` 属性将各个轨道静音。轨道具有布尔属性 `remote`,它会指示它来自 `RTCPeerConnection` 而来自远程对等设备。</p> 
  <h6>对等连接</h6> 
  <p><strong>点对点连</strong>接是 WebRTC 规范的一部分,该规范旨在对点一台计算机上的两台应用进行连接,以使用点对点协议进行通信。对等设备之间的通信可以是视频、音频或任意二进制数据(适用于支持 `RTCDataChannel` API 的客户端)。为了发现两个对等端如何连接,两个客户端都需要提供 ICE Server 配置。这是 STUN 或 TURN 服务器,其作用是向每个客户端提供 ICE 候选对象,然后这些客户端将被传输到远程对等方。这种转移 ICE 候选对象的方式通常称为信号。</p> 
  <h6>信令</h6> 
  <p>WebRTC 规范包含用于与 ICE(互联网连接建立)服务器通信的 API,但**信令组件**并不属于该组件。需要发出信号才能让两个对等网络共享它们之间的连接方式。这通常可以通过基于 HTTP 的常规 Web API(即 REST 服务或其他 RPC 机制)解决,在此过程中,网络应用可在发起对等连接之前中继必要的信息。</p> 
  <pre><code class="hljs">// Set up an asynchronous communication channel that will be
// used during the peer connection setup
const signalingChannel = new SignalingChannel(remoteClientId);
signalingChannel.addEventListener('message', message => {
    // New message from remote client received
});

// Send an asynchronous message to the remote client
signalingChannel.send('Hello!');</code></pre> 
  <p>信令可以通过许多不同的方式实现,WebRTC 规范不偏好任何特定的解决方案。(前端程序员,可以使用nodejs,websocket技术实现)</p> 
  <h6>启动对等连接</h6> 
  <p>每个对等连接都由一个 `RTCPeerConnection` 对象处理。此类的构造函数接受单个 `RTCConfiguration` 对象作为其参数。此对象定义对等连接的设置方式,**应包含关于要使用的 ICE 服务器的信息**。</p> 
  <p>每个对等连接都由一个RTCPeerconnection对象处理。此类的构造函数将单个RTCConfiguration对象作为其参数。此对象定义了对等连接的设置方式,并应包含有关要使用的ICE服务器的信息。</p> 
  <p>一旦创建了RTCPeerConnection连接,我们需要创建**SDP提供**或**应答**,这取决于我们是主叫对等体还是接收对等体。一旦创建了SDP提供或应答,就必须通过不同的信道将其发送到远程对等端。将SDP对象传递给远程对等方称为**信令**,不在Web RTC规范的范围内。</p> 
  <p>为了从调用端启动对等连接设置,我们创建了一个RTCPeerconnection对象,然后调用createOffer()来创建一个RTCSessionDescription对象。使用setLocalDescription()将此会话描述设置为本地描述,然后通过我们的信令信道发送到接收方。我们还为我们的信号通道设置了一个监听器,以便在从接收端接收到对我们提供的会话描述的回答时使用。</p> 
  <pre><code class="hljs">async function makeCall() {
    const configuration = {'iceServers': [{'urls': 'stun:stun.l.google.com:19302'}]}
    const peerConnection = new RTCPeerConnection(configuration);
    signalingChannel.addEventListener('message', async message => {
        if (message.answer) {
            const remoteDesc = new RTCSessionDescription(message.answer);
            await peerConnection.setRemoteDescription(remoteDesc);
        }
    });
    const offer = await peerConnection.createOffer();
    await peerConnection.setLocalDescription(offer);
    signalingChannel.send({'offer': offer});
}</code></pre> 
  <p>RTCPeerConnection.createOffer():RTCPeerConnection接口的 createOffer() 方法启动创建一个[SDP](https://developer.mozilla.org/zh-CN/docs/Glossary/SDP) offer,目的是启动一个新的 WebRTC 去连接远程端点。SDP offer 包含有关已附加到 WebRTC 会话,浏览器支持的编解码器和选项的所有[`MediaStreamTrack`](https://developer.mozilla.org/zh-CN/docs/Web/API/MediaStreamTrack)s 信息,以及[ICE](https://developer.mozilla.org/zh-CN/docs/Glossary/ICE) 代理,目的是通过信令信道发送给潜在远程端点,以请求连接或更新现有连接的配置。返回值是一个[`Promise` (en-US)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise),创建 offer 后,将使用包含新创建的要约的[`RTCSessionDescription`](https://developer.mozilla.org/zh-CN/docs/Web/API/RTCSessionDescription)对象来解析该返回值。</p> 
  <p>在接收端,我们会等待传入的回应,然后再创建 `RTCPeerConnection` 实例。完成后,我们使用 `setRemoteDescription()` 设置收到的回应。接下来,我们调用 `createAnswer()` 为收到的优惠创建答案。系统会使用 `setLocalDescription()` 将此答案设置为本地说明,然后通过我们的信令服务器将其发送至发起调用的一方。</p> 
  <pre><code class="hljs">const peerConnection = new RTCPeerConnection(configuration);
signalingChannel.addEventListener('message', async message => {
    if (message.offer) {
        peerConnection.setRemoteDescription(new RTCSessionDescription(message.offer));
        const answer = await peerConnection.createAnswer();
        await peerConnection.setLocalDescription(answer);
        signalingChannel.send({'answer': answer});
    }
});</code></pre> 
  <p>两个对等方同时设置了本地和远程会话说明之后,他们就会了解远程对等方的功能。这并不意味着对等设备之间的连接已准备就绪。为此,我们需要在每个对等端收集 ICE 候选项,并通过信令通道传输给另一个对等方。</p> 
  <h6>ICE</h6> 
  <p>创建 `RTCPeerConnection` 对象后,底层框架会使用提供的 ICE 服务器收集连接建立的候选对象(ICE 候选对象)。`RTCPeerConnection` 上的事件 `icegatheringstatechange` 会指示 ICE 收集的状态为(`new`、`gathering` 或 `complete`)。</p> 
  <p>虽然对等设备可以等待 ICE 收集完成,但通常要高效地使用“滚动冰”技术,并在发现每个 ICE 候选设备后将其传输到远程对等设备。这将大大缩短对等连接的设置时间,并允许视频通话以更低的延迟开始。</p> 
  <p>要收集 ICE 候选对象,只需为 `icecandidate` 事件添加监听器即可。针对该监听器发出的 `RTCPeerConnectionIceEvent` 将包含 `candidate` 属性,该属性表示应发送到远程对等端的新候选音频(请参阅信号)。</p> 
  <pre><code class="hljs">// Listen for local ICE candidates on the local RTCPeerConnection
peerConnection.addEventListener('icecandidate', event => {
    if (event.candidate) {
        signalingChannel.send({'new-ice-candidate': event.candidate});
    }
});

// Listen for remote ICE candidates and add them to the local RTCPeerConnection
signalingChannel.addEventListener('message', async message => {
    if (message.iceCandidate) {
        try {
            await peerConnection.addIceCandidate(message.iceCandidate);
        } catch (e) {
            console.error('Error adding received ice candidate', e);
        }
    }
});</code></pre> 
  <h6>已建立连接</h6> 
  <p>收到 ICE 候选对象后,我们的对等连接状态最终会变为已连接状态。为了检测这一点,我们在 `RTCPeerConnection` 中添加一个监听器,用于监听 `connectionstatechange` 事件。</p> 
  <pre><code class="hljs">// Listen for connectionstatechange on the local RTCPeerConnection
peerConnection.addEventListener('connectionstatechange', event => {
    if (peerConnection.connectionState === 'connected') {
        // Peers connected!
    }
});</code></pre> 
  <h6>远程数据流使用入门</h6> 
  <p>`RTCPeerConnection` 连接到远程对等设备后,就可以在它们之间流式传输音频和视频。此时,我们会将从 `getUserMedia()` 收到的数据流连接到 `RTCPeerConnection`。媒体流包含至少一个媒体轨道,当我们想将媒体传输到远程对等设备时,它们会分别添加到 `RTCPeerConnection` 中。</p> 
  <pre><code class="hljs">const localStream = await getUserMedia({vide: true, audio: true});
const peerConnection = new RTCPeerConnection(iceConfig);
localStream.getTracks().forEach(track => {
    peerConnection.addTrack(track, localStream);
});</code></pre> 
  <p>轨道可以在连接到远程对等方之前添加到 `RTCPeerConnection`,因此最好尽早执行此设置,而不是等待连接完成。</p> 
  <h6> 添加远程轨道</h6> 
  <p>为了接收由另一个对等方添加的远程轨道,我们会在本地 `RTCPeerConnection` 上注册一个监听器,用于监听 `track` 事件。`RTCTrackEvent` 包含一个 `MediaStream` 对象数组,这些对象与对等项的相应本地数据流具有相同的 `MediaStream.id` 值。在我们的示例中,每个轨道仅与单个数据流相关联。</p> 
  <pre><code class="hljs">const remoteVideo = document.querySelector('#remoteVideo');

peerConnection.addEventListener('track', async (event) => {
    const [remoteStream] = event.streams;
    remoteVideo.srcObject = remoteStream;
});</code></pre> 
  <h6>数据通道</h6> 
  <p>WebRTC 标准还涵盖用于通过 `RTCPeerConnection` 发送任意数据的 API。可通过对 `RTCPeerConnection` 对象调用 `createDataChannel()` 来完成此操作,该方法会返回 `RTCDataChannel` 对象。</p> 
  <pre><code class="hljs">const peerConnection = new RTCPeerConnection(configuration);
const dataChannel = peerConnection.createDataChannel();</code></pre> 
  <p>远程对等端可以通过监听 `RTCPeerConnection` 对象的 `datachannel` 事件来接收数据通道。收到的事件是 `RTCDataChannelEvent` 类型,包含一个 `channel` 属性,该属性表示在对等方之间连接的 `RTCDataChannel`。</p> 
  <pre><code class="hljs">const peerConnection = new RTCPeerConnection(configuration);
peerConnection.addEventListener('datachannel', event => {
    const dataChannel = event.channel;
});</code></pre> 
  <h6>打开和关闭事件</h6> 
  <p>在使用数据通道发送数据之前,客户端需要等到数据通道打开后才能使用它。具体方法是监听 `open` 事件。同样,当任意一侧关闭频道时,也会发生 `close` 事件。</p> 
  <pre><code class="hljs">const messageBox = document.querySelector('#messageBox');
const sendButton = document.querySelector('#sendButton');
const peerConnection = new RTCPeerConnection(configuration);
const dataChannel = peerConnection.createDataChannel();

// Enable textarea and button when opened
dataChannel.addEventListener('open', event => {
    messageBox.disabled = false;
    messageBox.focus();
    sendButton.disabled = false;
});

// Disable input when closed
dataChannel.addEventListener('close', event => {
    messageBox.disabled = false;
    sendButton.disabled = false;
});</code></pre> 
  <h6>信息</h6> 
  <p>如需在 `RTCDataChannel` 上发送消息,请使用要发送的数据调用 `send()` 函数。此函数的 `data` 参数可以是字符串、`Blob`、`ArrayBuffer` 或 `ArrayBufferView`。</p> 
  <pre><code class="hljs">const messageBox = document.querySelector('#messageBox');
const sendButton = document.querySelector('#sendButton');

// Send a simple text message when we click the button
sendButton.addEventListener('click', event => {
    const message = messageBox.textContent;
    dataChannel.send(message);
})</code></pre> 
  <p>远程对等端将通过监听 `message` 事件来接收 `RTCDataChannel` 上发送的消息。</p> 
  <pre><code class="hljs">const incomingMessages = document.querySelector('#incomingMessages');

const peerConnection = new RTCPeerConnection(configuration);
const dataChannel = peerConnection.createDataChannel();

// Append new messages to the box of incoming messages
dataChannel.addEventListener('message', event => {
    const message = event.data;
    incomingMessages.textContent += message + '\n';
});</code></pre> 
  <h5>代码流程实例</h5> 
  <p>webrtc.js</p> 
  <pre><code class="hljs">export default {
  data() {
    return {
      stream: null,
    }
  },
  methods: {
    // 推流
    async pullPlayer() {
      try {
        // 1.获取本地音视频流
        // 调用 getUserMedia API 获取音视频流
        this.stream = await navigator.mediaDevices.getUserMedia({
          audio: true,
          video: true,
        })
        // 拉流
        this.localVideo = document.getElementById('localVideo')
        console.log('Received local stream', this.stream, this.localVideo.srcObject)
        // this.desc = '11111'
        this.localVideo.srcObject = this.stream
        this.localStream = this.stream
      } catch (e) {
        console.log(`getUserMedia() error: ${e}`)
      }
    },
    // 创建RTCPeerConnection连接:发送方
    createRTCPeer() {
      this.configuration = {
        iceServers: [
          {
            urls: 'stun:stun.l.google.com:19302',
          },
        ],
      }
      // 源连接
      this.pc1 = new RTCPeerConnection(this.configuration)

      // 监听返回的 Candidate
      // 当ice准备好后,加到目标源中
      this.pc1.addEventListener('icecandidate', e => this.onIceCandidate(this.pc1, e))
      this.pc1.addEventListener('iceconnectionstatechange', e => this.onIceStateChange(this.pc1, e))
      //把localStream的音视频,放到源中
      this.getTracksStreams()
    },
    accceptRTCPeer() {
      //目标
      this.pc2 = new RTCPeerConnection(this.configuration)
      // 当ice准备好后,加到目标源中
      this.pc2.addEventListener('icecandidate', e => this.onIceCandidate(this.pc2, e))
      this.pc2.addEventListener('iceconnectionstatechange', e => this.onIceStateChange(this.pc2, e))
      //等待源发来的流
      this.pc2.addEventListener('track', this.gotRemoteStream)
    },

    //把localStream的音视频,放到源中
    getTracksStreams() {
      // 遍历本地流的所有轨道
      this.localStream.getTracks().forEach(track => this.pc1.addTrack(track, this.localStream))
    },
    // 添加 iceCandidate 时调用的方法
    async onIceCandidate(pc, event) {
      try {
        // 源发来的ice,加入到目标中
        await this.getOtherPc(pc).addIceCandidate(event.candidate)
        // 添加成功
        this.onAddIceCandidateSuccess(pc)
      } catch (e) {
        // 添加失败
        this.onAddIceCandidateError(pc, e)
      }
      console.log(
        `${this.getName(pc)} ICE candidate:\n${event.candidate ? event.candidate.candidate : '(null)'}`,
      )
    },
    // 拉流:将发送来的轨道数据赋值
    gotRemoteStream(e) {
      if (this.remoteVideo.srcObject !== e.streams[0]) {
        // getUserMedia 获得流后,将音视频流展示并保存到 localStream
        this.remoteVideo.srcObject = e.streams[0]
        console.log('pc2 received remote stream', e.streams[0])
      }
    },
    // 判断是发送方还是接收方
    getOtherPc(pc) {
      return pc === this.pc1 ? this.pc2 : this.pc1
    },
    getName(pc) {
      return pc === this.pc1 ? 'pc1' : 'pc2'
    },
    onIceStateChange(pc, event) {
      if (pc) {
        console.log(`${this.getName(pc)} ICE state: ${pc.iceConnectionState}`)
        console.log('ICE state change event: ', event)
      }
    },

    // 创建和设置连接描述
    async createOffers() {
      try {
        console.log('pc1 createOffer start')
        this.offerOptions = {
          offerToReceiveAudio: 1,
          offerToReceiveVideo: 1,
        }
        // 交换媒体描述信息
        const offer = await this.pc1.createOffer(this.offerOptions)
        await this.onCreateOfferSuccess(offer)
      } catch (e) {
        this.onCreateSessionDescriptionError(e)
      }
    },
    async onCreateOfferSuccess(desc) {
      // 发送端创建连接描述
      try {
        // 本地设置描述并将它发送给远端
        // 将 offer 保存到本地
        await this.pc1.setLocalDescription(desc)
        this.onSetLocalSuccess(this.pc1)
      } catch (e) {
        this.onSetSessionDescriptionError()
      }
      // 接收端创建连接描述
      try {
        // 远端将本地给它的描述设置为远端描述
        // 远端将 offer 保存
        await this.pc2.setRemoteDescription(desc)
        this.onSetRemoteSuccess(this.pc2)
      } catch (e) {
        this.onSetSessionDescriptionError()
      }
      // 目标 拿到源的连接描述后,给自己,并生成自己的连接描述
      try {
        // 远端创建应答 answer
        const answer = await this.pc2.createAnswer()
        await this.onCreateAnswerSuccess(answer)
      } catch (e) {
        this.onCreateSessionDescriptionError(e)
      }
    },
    // 本地描述创建成功
    onSetLocalSuccess(pc) {
      console.log(`${this.getName(pc)} setLocalDescription complete`)
    },
    // 本地描述创建失败
    onSetSessionDescriptionError(error) {
      console.log(`Failed to set session description: ${error.toString()}`)
    },
    // 接收描述创建成功
    onSetRemoteSuccess(pc) {
      console.log(`${this.getName(pc)} setRemoteDescription complete`)
    },
    // 接收描述创建失败
    onCreateSessionDescriptionError(error) {
      console.log(`Failed to create session description: ${error.toString()}`)
    },
    // 接收端:生成自己的连接描述
    async onCreateAnswerSuccess(desc) {
      try {
        // 远端设置本地描述并将它发给本地
        // 远端保存 answer
        await this.pc2.setLocalDescription(desc)
        this.onSetLocalSuccess(this.pc2)
      } catch (e) {
        this.onSetSessionDescriptionError(e)
      }
      console.log('pc1 setRemoteDescription start')
      try {
        // 本地将远端的应答描述设置为远端描述
        // 本地保存 answer
        await this.pc1.setRemoteDescription(desc)
        this.onSetRemoteSuccess(this.pc1)
      } catch (e) {
        this.onSetSessionDescriptionError(e)
      }
    },
    // 接收端创建本地描述成功
    onSetLocalSuccess(pc) {
      console.log(`${this.getName(pc)} setLocalDescription complete`)
    },
    // 3.端与端建立连接
    handleConnection(event) {
      // 获取到触发 icecandidate 事件的 RTCPeerConnection 对象
      // 获取到具体的Candidate
      const peerConnection = event.target
      const iceCandidate = event.candidate

      if (iceCandidate) {
        // 创建 RTCIceCandidate 对象
        const newIceCandidate = new RTCIceCandidate(iceCandidate)
        // 得到对端的 RTCPeerConnection
        const otherPeer = getOtherPeer(peerConnection)

        // 将本地获得的 Candidate 添加到远端的 RTCPeerConnection 对象中
        // 为了简单,这里并没有通过信令服务器来发送 Candidate,直接通过 addIceCandidate 来达到互换 Candidate 信息的目的
        otherPeer
          .addIceCandidate(newIceCandidate)
          .then(() => {
            handleConnectionSuccess(peerConnection)
          })
          .catch(error => {
            handleConnectionFailure(peerConnection, error)
          })
      }
    }, // 4.显示远端媒体流
    gotRemoteMediaStream(event) {
      if (remoteVideo.srcObject !== event.streams[0]) {
        remoteVideo.srcObject = event.streams[0]
        remoteStream = event.streams[0]
        console.log('remote 开始接受远端流')
      }
    },
  },
}
</code></pre> 
  <p>player.vue</p> 
  <pre><code class="hljs"><template>
  <div class="video-window">
    <video id="localVideo" playsinline autoplay muted></video>
    <video id="remoteVideo" playsinline autoplay></video>

    <div class="box">
      <button id="startButton" @click="start">Start</button>
      <button id="callButton" @click="call">Call</button>
      <button id="hangupButton" @click="hangup">Hang Up</button>
    </div>
    <div>{{ desc }}</div>
    <button class="btn" @click="ToIndex1">001</button>
  </div>
</template>
<script>
import wx from 'weixin-js-sdk'
import webrtc from '../mixins/webrtc'
export default {
  data() {
    return {
      startButton: null,
      callButton: null,
      hangupButton: null,
      localVideo: null,
      remoteVideo: null,
      startTime: null,

      pc1: null,
      pc2: null,
      desc: '',
    }
  },
  mixins: [webrtc],
  created() {
    this.roomId = this.$route.query.roomId ? this.$route.query.roomId : '001'
  },
  mounted() {
    this.startButton = document.getElementById('startButton')
    this.callButton = document.getElementById('callButton')
    this.hangupButton = document.getElementById('hangupButton')
    this.callButton.disabled = true
    this.hangupButton.disabled = true

    this.remoteVideo = document.getElementById('remoteVideo')
  },
  methods: {
    async start() {
      try {
        await this.pullPlayer()
        this.startButton.disabled = true
        this.callButton.disabled = false
      } catch (error) {
        this.desc = e
        this.startButton.disabled = false
      }
    },
    // 拉流
    async call() {
      this.callButton.disabled = true
      this.hangupButton.disabled = false
      console.log('Starting call')
      this.startTime = window.performance.now()
      // 视频轨道
      const videoTracks = this.localStream.getVideoTracks()
      // 音频轨道
      const audioTracks = this.localStream.getAudioTracks()

      // 判断视频轨道是否有值
      if (videoTracks.length > 0) {
        console.log(`Using video device: ${videoTracks[0].label}`)
      }
      // 判断音频轨道是否有值
      if (audioTracks.length > 0) {
        console.log(`Using audio device: ${audioTracks[0].label}`)
      }

      await this.createRTCPeer()
      await this.accceptRTCPeer()
      await this.createOffers()
    },

    // 断链
    hangup() {
      console.log('Ending call')
      this.pc1.close()
      this.pc2.close()
      this.pc1 = null
      this.pc2 = null
      this.hangupButton.disabled = true
      this.callButton.disabled = false
    },

    ToIndex() {
      wx.miniProgram.navigateTo({
        url: '/pages/index/index', //小程序地址
      })
    },

    ToIndex1() {
      this.$router.push({
        path: 'index',
      })
    },
  },
}
</script>
<style scoped>
.video-window {
  width: 100%;
  height: 100%;
}
/* .video-window video {
  width: 100%;
  height: 100%;
  margin: 12px 12px 0;
} */
video {
  width: calc(100% - 48px);
  height: 200px;
  background: #000;
  margin: 24px 24px 0;
}
.btn {
  padding: 12px;
  position: absolute;
  top: 12px;
  right: 12px;
}
.box {
  width: 100%;
  height: 42px;
  text-align: center;
}
.box button {
  padding: 8px 12px;
  margin: 12px 4px;
}
</style>
</code></pre> 
  <h5 style="background-color:transparent;">WEBRTC能力测试</h5> 
  <p>TRTC 能力检测</p> 
  <h6>页面准备</h6> 
  <p>视频播放的媒介是 H5 提供的 Video(音视频)和 Audio(纯音频)。<br>  </p> 
  <pre><code class="hljs"><body >
<!-- 音视频 -->
<!--
本地视频流
muted:
本地视频流的video必须置为静音(muted),否则会出现啸叫/回声等问题
Mac / iPhone / iPad 需要用js设置muted属性
autoplay:必须为激活状态
playsinline:保证在ios safari中不全屏播放
-->
<video id="localVideo" muted autoplay playsinline></video>
<!-- 远端视频流 -->
<video id="remoteVideo" autoplay playsinline></video>
<!-- 纯音频 -->
<!-- 本地音频流 / 这种场景下,localaudio 其实没有播放的必要了,可以用来调试 -->
<!-- <audio id="localAudioMedia" muted autoplay></audio> -->
<!-- 远端音频流 -->
<!-- <audio id="remoteAudioMedia" autoplay ></audio> -->
<script src="https://sqimg.qq.com/expert_qq/webrtc/3.0/WebRTCAPI.min.js"></script>
</body></code></pre> 
  <h6>H5 支持的平台</h6> 
  <table> 
   <thead> 
    <tr> 
     <th>操作系统平台</th> 
     <th>浏览器/webview</th> 
     <th>版本要求</th> 
     <th>备注</th> 
    </tr> 
   </thead> 
   <tbody> 
    <tr> 
     <td>iOS</td> 
     <td>Safari ( 只支持Safari )</td> 
     <td>11.1.2</td> 
     <td>由于苹果 Safari 仍有偶现的 bug,产品化方案建议先规避,待苹果解决后再使用对于iOS可以考虑使用我们的小程序解决方案</td> 
    </tr> 
    <tr> 
     <td>Android</td> 
     <td>TBS (微信和手机QQ的默认Webview)</td> 
     <td>43600</td> 
     <td>微信和手机QQ默认内置的浏览器内核为TBS。TBS 介绍</td> 
    </tr> 
    <tr> 
     <td>Android</td> 
     <td>Chrome</td> 
     <td>60+</td> 
     <td>需要支持 H264</td> 
    </tr> 
    <tr> 
     <td>Mac</td> 
     <td>Chrome</td> 
     <td>47+</td> 
     <td></td> 
    </tr> 
    <tr> 
     <td>Mac</td> 
     <td>Safari</td> 
     <td>11+</td> 
     <td></td> 
    </tr> 
    <tr> 
     <td>Windows(PC)</td> 
     <td>Chrome</td> 
     <td>52+</td> 
     <td></td> 
    </tr> 
    <tr> 
     <td>Windows(PC)</td> 
     <td>QQ浏览器</td> 
     <td>10.2</td> 
     <td></td> 
    </tr> 
   </tbody> 
  </table> 
  <pre><code class="hljs">function checkTBSVersion(ua) {
//ua = "Mozilla/5.0 (Linux; Android 7.1.1; vivo X9 Build/NMF26F; wv) 
//AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/53.0.2785.49 
//Mobile MQQBrowser/6.2 TBS/043501 Safari/537.36 
//MicroMessenger/6.5.13.1100 NetType/WIFI Language/zh_CN";
    var list = ua.split(" ");
    for (var i = 0; i < list.length; i++) {
        var item = list[i];
        if (item.indexOf("TBS") !== -1 || item.indexOf("tbs") !== -1) {
            var versionStr = item.split("/")[1];
            var version = parseInt(versionStr) || 0;
            if (version <= 43600) {
                alert("您的TBS版本号(" + versionStr + ")过低,不支持WebRTC,请升级!");
            }
        }
    }
}</code></pre> 
  <p></p> 
  <p></p> 
 </div> 
</div>
                            </div>
                        </div>
                    </div>
                    <!--PC和WAP自适应版-->
                    <div id="SOHUCS" sid="1738149596758073344"></div>
                    <script type="text/javascript" src="/views/front/js/chanyan.js"></script>
                    <!-- 文章页-底部 动态广告位 -->
                    <div class="youdao-fixed-ad" id="detail_ad_bottom"></div>
                </div>
                <div class="col-md-3">
                    <div class="row" id="ad">
                        <!-- 文章页-右侧1 动态广告位 -->
                        <div id="right-1" class="col-lg-12 col-md-12 col-sm-4 col-xs-4 ad">
                            <div class="youdao-fixed-ad" id="detail_ad_1"> </div>
                        </div>
                        <!-- 文章页-右侧2 动态广告位 -->
                        <div id="right-2" class="col-lg-12 col-md-12 col-sm-4 col-xs-4 ad">
                            <div class="youdao-fixed-ad" id="detail_ad_2"></div>
                        </div>
                        <!-- 文章页-右侧3 动态广告位 -->
                        <div id="right-3" class="col-lg-12 col-md-12 col-sm-4 col-xs-4 ad">
                            <div class="youdao-fixed-ad" id="detail_ad_3"></div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
    <div class="container">
        <h4 class="pt20 mb15 mt0 border-top">你可能感兴趣的:(前端,webrtc)</h4>
        <div id="paradigm-article-related">
            <div class="recommend-post mb30">
                <ul class="widget-links">
                    <li><a href="/article/1885588395481165824.htm"
                           title="前端网页开发学习(HTML+CSS+JS)有这一篇就够!" target="_blank">前端网页开发学习(HTML+CSS+JS)有这一篇就够!</a>
                        <span class="text-muted">软件技术NINI</span>
<a class="tag" taget="_blank" href="/search/html%2Fcss%E7%AC%94%E8%AE%B0/1.htm">html/css笔记</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/%E5%AD%A6%E4%B9%A0/1.htm">学习</a><a class="tag" taget="_blank" href="/search/html/1.htm">html</a>
                        <div>前端网页开发是一个不断发展的领域,涉及到构建和设计网站以及Web应用程序的用户界面。以下是一份全面的学习指南,涵盖HTML、CSS和JavaScript的基础知识,帮助你入门前端开发。HTML(超文本标记语言)1.基础结构文档类型和标签:定义了文档类型和根元素。htmlDocument标签:包含元数据,如字符集、标题和链接到CSS文件。标签:包含网页的可见内容。2.常用标签文本内容:<h</div>
                    </li>
                    <li><a href="/article/1885583220016214016.htm"
                           title="JavaScript中的隐式类型转换" target="_blank">JavaScript中的隐式类型转换</a>
                        <span class="text-muted">阿珊和她的猫</span>
<a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a><a class="tag" taget="_blank" href="/search/ecmascript/1.htm">ecmascript</a>
                        <div>前端开发工程师、技术日更博主、已过CET6阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1牛客高级专题作者、打造专栏《前端面试必备》、《2024面试高频手撕题》蓝桥云课签约作者、上架课程《Vue.js和Egg.js开发企业级健康管理项目》、《带你从入门到实战全面掌握uni-app》前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。文章</div>
                    </li>
                    <li><a href="/article/1885580951602393088.htm"
                           title="分布式服务接口的幂等性如何设计(比如不能重复扣款)?" target="_blank">分布式服务接口的幂等性如何设计(比如不能重复扣款)?</a>
                        <span class="text-muted">码农小旋风</span>
<a class="tag" taget="_blank" href="/search/%E5%90%8E%E7%AB%AF/1.htm">后端</a>
                        <div>面试题分布式服务接口的幂等性如何设计(比如不能重复扣款)?面试官心理分析从这个问题开始,面试官就已经进入了实际的生产问题的面试了。一个分布式系统中的某个接口,该如何保证幂等性?这个事儿其实是你做分布式系统的时候必须要考虑的一个生产环境的技术问题。啥意思呢?你看,假如你有个服务提供一些接口供外部调用,这个服务部署在了5台机器上,接着有个接口就是付款接口。然后人家用户在前端上操作的时候,不知道为啥,总</div>
                    </li>
                    <li><a href="/article/1885575527390048256.htm"
                           title="SpringBoot(2) —— 什么是微服务架构" target="_blank">SpringBoot(2) —— 什么是微服务架构</a>
                        <span class="text-muted">原来是小别扇</span>
<a class="tag" taget="_blank" href="/search/SpringBoot/1.htm">SpringBoot</a><a class="tag" taget="_blank" href="/search/springboot/1.htm">springboot</a><a class="tag" taget="_blank" href="/search/%E5%88%86%E5%B8%83%E5%BC%8F/1.htm">分布式</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a>
                        <div>微服务1.什么是微服务?微服务是一种架构风格(前面学习的RestFul风格也就是一种风格,但是它是接口风格,而微服务是一种架构风格,我们学习过的架构风格有后端开发的MVC3层架构和MVVM前端架构),它要求我们在开发一个应用的时候,这个应用必须构建成一系列小服务的组合;可以通过http的方式进行互通。要说微服务架构,先得说说过去我们的单体应用架构。2.单体应用架构所谓单体应用架构(allinone</div>
                    </li>
                    <li><a href="/article/1885550438472806400.htm"
                           title="web api 与html 部署,详解.net core webapi 前后端开发分离后的配置和部署" target="_blank">web api 与html 部署,详解.net core webapi 前后端开发分离后的配置和部署</a>
                        <span class="text-muted">万小柯要努力学习</span>
<a class="tag" taget="_blank" href="/search/web/1.htm">web</a><a class="tag" taget="_blank" href="/search/api/1.htm">api</a><a class="tag" taget="_blank" href="/search/%E4%B8%8Ehtml/1.htm">与html</a><a class="tag" taget="_blank" href="/search/%E9%83%A8%E7%BD%B2/1.htm">部署</a>
                        <div>背景:现在越来越多的企业都采用了在开发上前后端分离,前后端开发上的分离有很多种,那么今天,我来分享一下项目中得的前后端分离。B/SSaas项目:(这个项目可以理解成个人中心,当然不止这么点功能)前端:node.js+vue后端:.netcorewebapi前端安装node.js跟创建vue项目这些不是这篇文章的重点,重点在于项目完成后的部署。.netcorewebapi创建后,默认就创建了一个ww</div>
                    </li>
                    <li><a href="/article/1885540220976295936.htm"
                           title="Vue3配置vite.config.js代理解决跨域问题" target="_blank">Vue3配置vite.config.js代理解决跨域问题</a>
                        <span class="text-muted">码喽的自我修养</span>
<a class="tag" taget="_blank" href="/search/vue2%2F3/1.htm">vue2/3</a><a class="tag" taget="_blank" href="/search/%E4%BB%8E%E5%9F%BA%E7%A1%80%E5%88%B0%E8%B5%B7%E9%A3%9E/1.htm">从基础到起飞</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B%E6%90%AD%E5%BB%BA/1.htm">前端工程搭建</a><a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a><a class="tag" taget="_blank" href="/search/ecmascript/1.htm">ecmascript</a><a class="tag" taget="_blank" href="/search/vue.js/1.htm">vue.js</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/nginx/1.htm">nginx</a>
                        <div>前言:当浏览器发出一个请求时,只要请求URL的协议、域名、端口三者之间任意一个与当前页面URL不同,就称为跨域。跨域一般出现在开发阶段,由于线上环境前端代码被打包成了静态资源,因而不会出现跨域问题,这篇文章主要给大家介绍了关于Vue3配置vite.config.js解决跨域问题的相关资料,创作不易,如果能帮助到带大家,欢迎收藏+关注哦问题再现后台报错:AccesstoXMLHttpRequesta</div>
                    </li>
                    <li><a href="/article/1885539716607045632.htm"
                           title="前端使用 Element Plus架构vue3.0实现图片拖拉拽,后等比压缩,上传到Spring Boot后端" target="_blank">前端使用 Element Plus架构vue3.0实现图片拖拉拽,后等比压缩,上传到Spring Boot后端</a>
                        <span class="text-muted">慧香一格</span>
<a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/%E5%BE%AE%E6%9C%8D%E5%8A%A1/1.htm">微服务</a><a class="tag" taget="_blank" href="/search/web/1.htm">web</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/%E6%9E%B6%E6%9E%84/1.htm">架构</a><a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a><a class="tag" taget="_blank" href="/search/boot/1.htm">boot</a>
                        <div>图片拖拉拽等比压缩上传为了在前端对图片文件进行等比压缩后再上传到后端,可以使用canvas元素来实现图片的压缩。以下是一个详细的实现步骤:前端实现图片等比压缩:使用canvas元素对图片进行压缩。前端上传压缩后的图片:使用el-upload组件上传压缩后的图片。后端接收并保存图片:在SpringBoot中接收上传的图片并保存。下面是一个详细的实现示例。1.前端实现图片等比压缩首先,确保你已经安装了</div>
                    </li>
                    <li><a href="/article/1885528247937069056.htm"
                           title="Web前端最全Koa 基础篇(二)—— 路由与中间件(1),前端组件化架构实践" target="_blank">Web前端最全Koa 基础篇(二)—— 路由与中间件(1),前端组件化架构实践</a>
                        <span class="text-muted">2401_84447112</span>
<a class="tag" taget="_blank" href="/search/%E7%A8%8B%E5%BA%8F%E5%91%98/1.htm">程序员</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/%E4%B8%AD%E9%97%B4%E4%BB%B6/1.htm">中间件</a><a class="tag" taget="_blank" href="/search/%E6%9E%B6%E6%9E%84/1.htm">架构</a>
                        <div>最后如果你已经下定决心要转行做编程行业,在最开始的时候就要对自己的学习有一个基本的规划,还要对这个行业的技术需求有一个基本的了解。有一个已就业为目的的学习目标,然后为之努力,坚持到底。如果你有幸看到这篇文章,希望对你有所帮助,祝你转行成功。开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】基本使用router.get(“/”,asyncctx=>{ctx.body=“h</div>
                    </li>
                    <li><a href="/article/1885522576063655936.htm"
                           title="软件架构原理与实战:深入理解BFF模式及其在微服务中的应用" target="_blank">软件架构原理与实战:深入理解BFF模式及其在微服务中的应用</a>
                        <span class="text-muted">AI天才研究院</span>
<a class="tag" taget="_blank" href="/search/AI%E5%AE%9E%E6%88%98/1.htm">AI实战</a><a class="tag" taget="_blank" href="/search/AI%E5%A4%A7%E6%A8%A1%E5%9E%8B%E4%BC%81%E4%B8%9A%E7%BA%A7%E5%BA%94%E7%94%A8%E5%BC%80%E5%8F%91%E5%AE%9E%E6%88%98/1.htm">AI大模型企业级应用开发实战</a><a class="tag" taget="_blank" href="/search/%E5%A4%A7%E6%95%B0%E6%8D%AE/1.htm">大数据</a><a class="tag" taget="_blank" href="/search/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/1.htm">人工智能</a><a class="tag" taget="_blank" href="/search/%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B/1.htm">语言模型</a><a class="tag" taget="_blank" href="/search/AI/1.htm">AI</a><a class="tag" taget="_blank" href="/search/LLM/1.htm">LLM</a><a class="tag" taget="_blank" href="/search/Java/1.htm">Java</a><a class="tag" taget="_blank" href="/search/Python/1.htm">Python</a><a class="tag" taget="_blank" href="/search/%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1/1.htm">架构设计</a><a class="tag" taget="_blank" href="/search/Agent/1.htm">Agent</a><a class="tag" taget="_blank" href="/search/RPA/1.htm">RPA</a>
                        <div>1.背景介绍随着互联网的不断发展,软件架构也不断演进,微服务架构成为了当前最流行的软件架构之一。微服务架构将软件应用程序划分为一系列小的服务,每个服务都独立部署和扩展。这种架构的优点在于它的灵活性、可扩展性和容错性。在微服务架构中,服务之间通过网络进行通信,因此需要一种适合网络通信的架构模式。这就是BFF模式的诞生。BFF(BackingFrontend)模式是一种软件架构模式,它将前端应用程序与</div>
                    </li>
                    <li><a href="/article/1885521440732672000.htm"
                           title="JavaScript网页设计案例:响应式动态购物车" target="_blank">JavaScript网页设计案例:响应式动态购物车</a>
                        <span class="text-muted">学不完了是吧</span>
<a class="tag" taget="_blank" href="/search/js/1.htm">js</a><a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a>
                        <div>在现代网页开发中,购物车是电子商务网站的重要功能之一。通过JavaScript,我们可以实现一个响应式动态购物车,提供用户友好的体验,并展示前端开发的核心能力。案例需求我们的购物车需要实现以下功能:动态添加商品:用户可以从商品列表中选择并添加商品到购物车。实时更新:购物车的商品数量、价格和总金额自动更新。修改商品数量:用户可以调整购物车中商品的数量。移除商品:用户可以从购物车中移除商品。结算功能:</div>
                    </li>
                    <li><a href="/article/1885514123643514880.htm"
                           title="2025年React前端路线图:从初级到高级" target="_blank">2025年React前端路线图:从初级到高级</a>
                        <span class="text-muted">倔强青铜3</span>
<a class="tag" taget="_blank" href="/search/React%E6%88%90%E7%A5%9E%E4%B9%8B%E8%B7%AF/1.htm">React成神之路</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/react.js/1.htm">react.js</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF%E6%A1%86%E6%9E%B6/1.htm">前端框架</a><a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a>
                        <div>2025年React前端路线图:从初级到高级原文链接:2025ReactFrontendRoadmap:BeginnertoSeniorLevel作者:tak089译者:倔强青铜三前言大家好,我是倔强青铜三。是一名热情的软件工程师,我热衷于分享和传播IT技术,致力于通过我的知识和技能推动技术交流与创新,欢迎关注我,微信公众号:倔强青铜三。欢迎点赞、收藏、关注,一键三连!!!1.初级(入门级)目标:</div>
                    </li>
                    <li><a href="/article/1885497093410320384.htm"
                           title="workman服务端开发模式-应用开发-总架构逻辑说明" target="_blank">workman服务端开发模式-应用开发-总架构逻辑说明</a>
                        <span class="text-muted">龙哥·三年风水</span>
<a class="tag" taget="_blank" href="/search/PHP/1.htm">PHP</a><a class="tag" taget="_blank" href="/search/%E9%95%BF%E9%93%BE%E6%8E%A5/1.htm">长链接</a><a class="tag" taget="_blank" href="/search/%E5%88%86%E5%B8%83%E5%BC%8F/1.htm">分布式</a><a class="tag" taget="_blank" href="/search/%E5%88%86%E5%B8%83%E5%BC%8F/1.htm">分布式</a><a class="tag" taget="_blank" href="/search/php/1.htm">php</a><a class="tag" taget="_blank" href="/search/gateway/1.htm">gateway</a>
                        <div>一、后台管理端(操作页面端)管理员用浏览器打开页面管理端后,页面管理端会自动检测,如果本地cookie不存在的情况下,跳转到登录页面,如果本地cookie存在的情况下,跳转到首页。登录的情况下,就不说,后面在业务架构里面会说明的。在登录页面输入邮箱账号、密码、验证码,点击提交。提交之前会在前端进行类型及相应的格式验证,如果验证结果都是OK的情况下,将参数提交到api接口中,等待返回结果。如果api</div>
                    </li>
                    <li><a href="/article/1885496967056912384.htm"
                           title="AIGC时代的Vue或React前端开发" target="_blank">AIGC时代的Vue或React前端开发</a>
                        <span class="text-muted">GISer_Jinger</span>
<a class="tag" taget="_blank" href="/search/Javascript/1.htm">Javascript</a><a class="tag" taget="_blank" href="/search/React/1.htm">React</a><a class="tag" taget="_blank" href="/search/Vue/1.htm">Vue</a><a class="tag" taget="_blank" href="/search/AIGC/1.htm">AIGC</a><a class="tag" taget="_blank" href="/search/vue.js/1.htm">vue.js</a><a class="tag" taget="_blank" href="/search/react.js/1.htm">react.js</a>
                        <div>在AIGC(人工智能生成内容)时代,Vue开发正经历着深刻的变革。以下是对AIGC时代Vue开发的详细分析:一、AIGC技术对Vue开发的影响代码生成与自动化AIGC技术使得开发者能够借助智能工具快速生成和优化Vue代码。例如,通过自然语言处理模型(如ChatGPT),开发者可以描述组件的功能和样式需求,然后自动生成包含模板、脚本和样式的完整组件代码。这不仅大大提高了开发效率,还减少了人为错误的可</div>
                    </li>
                    <li><a href="/article/1885493310118752256.htm"
                           title="WSL开发环境配置(linux + python + nodejs + docker)" target="_blank">WSL开发环境配置(linux + python + nodejs + docker)</a>
                        <span class="text-muted">Lilixxs</span>
<a class="tag" taget="_blank" href="/search/%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/1.htm">环境搭建</a><a class="tag" taget="_blank" href="/search/%E5%9F%BA%E7%A1%80%E8%AE%BE%E6%96%BD/1.htm">基础设施</a><a class="tag" taget="_blank" href="/search/linux/1.htm">linux</a><a class="tag" taget="_blank" href="/search/%E8%BF%90%E7%BB%B4/1.htm">运维</a><a class="tag" taget="_blank" href="/search/%E6%9C%8D%E5%8A%A1%E5%99%A8/1.htm">服务器</a>
                        <div>配置要求及目标总体目标:完整的Linux开发环境可开发基于node.js的前端程序可开发基于python的后端程序(仅日常程序,不包含机器学习程序)可运行docker容器,用于快速搭建测试环境Linux环境要求支持centos发行版类似的操作方式和指令(如使用rpm、dnf进行软件包管理)登录用户具有root权限(执行高权限命令,输入sudo即可执行)可从国内源更新软件基本优化:内核指令优化、禁用</div>
                    </li>
                    <li><a href="/article/1885490916249104384.htm"
                           title="「前端工具」postman接口测试工具详解" target="_blank">「前端工具」postman接口测试工具详解</a>
                        <span class="text-muted">吴维炜</span>
<a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF%E5%B0%B1%E9%82%A3%E4%B9%88%E5%9B%9E%E4%BA%8B/1.htm">前端就那么回事</a><a class="tag" taget="_blank" href="/search/postman/1.htm">postman</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF%E5%B7%A5%E5%85%B7/1.htm">前端工具</a><a class="tag" taget="_blank" href="/search/API%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/1.htm">API开发工具</a><a class="tag" taget="_blank" href="/search/RESTful/1.htm">RESTful</a><a class="tag" taget="_blank" href="/search/API/1.htm">API</a><a class="tag" taget="_blank" href="/search/postman%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95/1.htm">postman自动化测试</a>
                        <div>Postman是一款流行的API开发工具,用于构建和测试RESTfulAPI。以下是Postman的一些关键特性和使用方法的详解:1.界面和基本操作工作区:Postman的主界面,用于显示集合、环境和全局变量。请求构建器:用于输入请求的URL、HTTP方法、请求头、请求体等。响应区:显示服务器的响应,包括状态码、响应头和响应体。2.创建请求GET请求:获取资源。POST请求:提交新资源。PUT请求</div>
                    </li>
                    <li><a href="/article/1885451704573423616.htm"
                           title="【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】1.25 视觉风暴:NumPy驱动数据可视化" target="_blank">【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】1.25 视觉风暴:NumPy驱动数据可视化</a>
                        <span class="text-muted">精通代码大仙</span>
<a class="tag" taget="_blank" href="/search/numpy/1.htm">numpy</a><a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/numpy/1.htm">numpy</a><a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/%E4%BF%A1%E6%81%AF%E5%8F%AF%E8%A7%86%E5%8C%96/1.htm">信息可视化</a>
                        <div>1.25视觉风暴:NumPy驱动数据可视化目录视觉风暴:NumPy驱动数据可视化百万级点云实时渲染优化CT医学影像三维重建实战交互式数据分析看板开发地理空间数据可视化进阶WebAssembly前端渲染融合1.25.1百万级点云实时渲染优化1.25.2CT医学影像三维重建实战1.25.3交互式数据分析看板开发1.25.4地理空间数据可视化进阶1.25.5WebAssembly前端渲染融合视觉风暴:N</div>
                    </li>
                    <li><a href="/article/1885450693288980480.htm"
                           title="【自学笔记】JavaWeb的重点知识点-持续更新" target="_blank">【自学笔记】JavaWeb的重点知识点-持续更新</a>
                        <span class="text-muted">Long_poem</span>
<a class="tag" taget="_blank" href="/search/%E7%AC%94%E8%AE%B0/1.htm">笔记</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/web/1.htm">web</a>
                        <div>提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档文章目录JavaWeb知识点一、基础概念二、项目结构三、Tomcat服务器四、数据库连接(JDBC)五、前端技术六、高级技术总结以下是JavaWeb知识点的MD格式罗列:JavaWeb知识点一、基础概念静态Web与动态Web静态Web:页面内容固定不变,每次访问都返回相同的内容。动态Web:页面内容可以根据请求或用户输入而变化。网站访</div>
                    </li>
                    <li><a href="/article/1885408809002790912.htm"
                           title="基于HarmonyOS 5.0 Next的应用开发设计模式与前端框架的架构整合与实践【附代码实例】" target="_blank">基于HarmonyOS 5.0 Next的应用开发设计模式与前端框架的架构整合与实践【附代码实例】</a>
                        <span class="text-muted">一键难忘</span>
<a class="tag" taget="_blank" href="/search/%E7%B2%BE%E9%80%9AAI%E5%AE%9E%E6%88%98%E5%8D%83%E4%BE%8B%E4%B8%93%E6%A0%8F%E5%90%88%E9%9B%86/1.htm">精通AI实战千例专栏合集</a><a class="tag" taget="_blank" href="/search/harmonyos/1.htm">harmonyos</a><a class="tag" taget="_blank" href="/search/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/1.htm">设计模式</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF%E6%A1%86%E6%9E%B6/1.htm">前端框架</a>
                        <div>文章目录HarmonyOS5.0Next应用开发:架构设计中的设计模式与前端框架设计HarmonyOS5.0Next概览设计模式在HarmonyOS应用开发中的应用单例模式工厂模式观察者模式设计模式的使用分层架构设计1.公共能力层(CommonLayer)2.基础特性层(FeatureLayer)3.产品定制层(ProductLayer)模块化设计1.模块化结构2.模块化代码示例前端框架的设计Ar</div>
                    </li>
                    <li><a href="/article/1885390277267484672.htm"
                           title="《亿级流量下的架构实战:HTTP全链路解析与智能监控系统搭建》" target="_blank">《亿级流量下的架构实战:HTTP全链路解析与智能监控系统搭建》</a>
                        <span class="text-muted">我的青春不太冷</span>
<a class="tag" taget="_blank" href="/search/%E6%9E%B6%E6%9E%84/1.htm">架构</a><a class="tag" taget="_blank" href="/search/http/1.htm">http</a><a class="tag" taget="_blank" href="/search/%E7%BD%91%E7%BB%9C%E5%8D%8F%E8%AE%AE/1.htm">网络协议</a><a class="tag" taget="_blank" href="/search/%E7%A7%91%E6%8A%80/1.htm">科技</a><a class="tag" taget="_blank" href="/search/%E7%BB%8F%E9%AA%8C%E5%88%86%E4%BA%AB/1.htm">经验分享</a><a class="tag" taget="_blank" href="/search/%E5%AD%A6%E4%B9%A0/1.htm">学习</a><a class="tag" taget="_blank" href="/search/%E7%BD%91%E7%BB%9C/1.htm">网络</a>
                        <div>文章目录全链路解析:HTTP请求响应与数据可视化监控一、HTTP请求响应全流程解析1.全链路交互流程图2.关键技术实现2.1前端请求构造(ES6+语法示例)2.2服务端处理架构(Node.js/Express)二、数据可视化监控方案1.数据存储架构设计2.数据库操作层实现3.管理界面实现方案3.1可视化看板路由//routes/admin.js3.2数据可视化模板(EJS示例)4.最佳实践建议4.</div>
                    </li>
                    <li><a href="/article/1885374510740336640.htm"
                           title="Vue - route路由(router-link、useRoute、useRouter)" target="_blank">Vue - route路由(router-link、useRoute、useRouter)</a>
                        <span class="text-muted">来一碗刘肉面</span>
<a class="tag" taget="_blank" href="/search/Vue/1.htm">Vue</a><a class="tag" taget="_blank" href="/search/vue.js/1.htm">vue.js</a><a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a>
                        <div>为了避免反复在app.vue中去修改引入的路径,当用了新的页面,想切换回老页面的时候,都需要去手动改变路径,那么有没有一种可能,可以在一个地方,把这些组件配置好,然后通过不同的路径,就去访问不同的组件呢?vuerouter就提供了这个功能,翻译一下,router:路由,vuerouter,vue的路由。单页面应用:SPASPA的核心思想是将应用划分为多个组件,通过前端路由来控制不同组件的显示,实现</div>
                    </li>
                    <li><a href="/article/1885363289643347968.htm"
                           title="优化冗余代码:提升前端项目开发效率的实用方法" target="_blank">优化冗余代码:提升前端项目开发效率的实用方法</a>
                        <span class="text-muted">三掌柜666</span>
<a class="tag" taget="_blank" href="/search/web%E5%89%8D%E7%AB%AF%E7%9F%A5%E8%AF%86%E6%B1%87%E6%80%BB/1.htm">web前端知识汇总</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a>
                        <div>目录前言代码复用与组件化模块化开发与代码分割工具辅助与自动化结束语前言在前端开发中,我们常常会遇到代码冗余的问题,这不仅增加了代码量,还影响了项目的可维护性和开发效率。还有就是有时候会接到紧急业务需求,要求立马完成上线,这时候多人协作开发,代码质量不会很高,很多都是复制粘贴;亦或是接手的代码比较老旧,公共组件里面写了大量冗余代码,这种情况下时间越久,开发起来就越难受。那么本文将结合实际项目案例,分</div>
                    </li>
                    <li><a href="/article/1885356857632026624.htm"
                           title="前端学习-事件解绑,mouseover和mouseenter的区别(二十九)" target="_blank">前端学习-事件解绑,mouseover和mouseenter的区别(二十九)</a>
                        <span class="text-muted">marshalVS</span>
<a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/%E5%AD%A6%E4%B9%A0/1.htm">学习</a>
                        <div>目录前言解绑事件语法鼠标经过事件的区别鼠标经过事件示例代码两种注册事件的区别总结前言人道洛阳花似锦,偏我来时不逢春解绑事件on事件方式,直接使用null覆盖就可以实现事件的解绑语法btn.onclick=function(){alert('点击了')}btn.onclick=null;constben=document.querySelector('button');ben.addEventLis</div>
                    </li>
                    <li><a href="/article/1885354459828121600.htm"
                           title="毕设开源 python大数据旅游数据分析可视化系统(源码分享)" target="_blank">毕设开源 python大数据旅游数据分析可视化系统(源码分享)</a>
                        <span class="text-muted">bee_dc</span>
<a class="tag" taget="_blank" href="/search/%E6%AF%95%E4%B8%9A%E8%AE%BE%E8%AE%A1/1.htm">毕业设计</a><a class="tag" taget="_blank" href="/search/%E6%AF%95%E8%AE%BE/1.htm">毕设</a><a class="tag" taget="_blank" href="/search/%E5%A4%A7%E6%95%B0%E6%8D%AE/1.htm">大数据</a>
                        <div>文章目录0前言1课题背景2数据处理3数据可视化工具3.1django框架介绍3.2ECharts4Django使用echarts进行可视化展示(mysql数据库)4.1修改setting.py连接mysql数据库4.2导入数据4.3使用echarts可视化展示5实现效果5.1前端展示5.2后端展示6最后0前言这两年开始毕业设计和毕业答辩的要求和难度不断提升,传统的毕设题目缺少创新和亮点,往往达不到</div>
                    </li>
                    <li><a href="/article/1885325708717256704.htm"
                           title="java 字符串日期字段格式化前端显示" target="_blank">java 字符串日期字段格式化前端显示</a>
                        <span class="text-muted">qq_36608622</span>
<a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a>
                        <div>在Java应用程序中,如果你有一个字符串类型的日期字段,并希望将其格式化后显示在前端,可以通过多种方式实现。这通常涉及到在后端将字符串转换为Date或LocalDateTime等对象,然后使用适当的注解或配置来确保它们以正确的格式序列化为JSON发送到前端。以下是几种常见方法:方法一:使用@JsonFormat注解(Jackson)如果你使用的是Jackson来处理JSON序列化和反序列化,可以在</div>
                    </li>
                    <li><a href="/article/1885323687482159104.htm"
                           title="前端知识速记—JS篇:箭头函数" target="_blank">前端知识速记—JS篇:箭头函数</a>
                        <span class="text-muted">无限大.</span>
<a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF%E7%9F%A5%E8%AF%86%E9%80%9F%E8%AE%B0/1.htm">前端知识速记</a><a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a>
                        <div>前端知识速记—JS篇:箭头函数什么是箭头函数?箭头函数是ES6引入的一种新的函数书写方式,其语法更为简洁,常用于替代传统的函数表达式。箭头函数的基本语法如下:constfunctionName=(parameters)=>{//函数体};通过这种方式,开发者可以以更简练的形式定义函数,提高代码的可读性。箭头函数的基本特性1.简化语法箭头函数最直接的优势就是语法简单,特别是在定义短小的函数时,能显著</div>
                    </li>
                    <li><a href="/article/1885305774884843520.htm"
                           title="ambari-server页面错位问题解决" target="_blank">ambari-server页面错位问题解决</a>
                        <span class="text-muted">王木头</span>
<a class="tag" taget="_blank" href="/search/ambari/1.htm">ambari</a><a class="tag" taget="_blank" href="/search/hadoop/1.htm">hadoop</a><a class="tag" taget="_blank" href="/search/%E5%A4%A7%E6%95%B0%E6%8D%AE/1.htm">大数据</a>
                        <div>背景:项目新安装的ambari集群页面错位如下解决办法(临时):修改ambari-server的前端文件:/usr/lib/ambari-server/web/javascripts/app.js原代码:initNavigationBar:function(){if(App.get('router.mainController.isClusterDataLoaded')){$('body').on</div>
                    </li>
                    <li><a href="/article/1885302496662974464.htm"
                           title="如何优化代码性能?" target="_blank">如何优化代码性能?</a>
                        <span class="text-muted">杨胜增</span>
<a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/1.htm">性能优化</a>
                        <div>优化代码性能是编程中的一个重要课题,无论是在处理大量数据的后台服务,还是在资源受限的前端应用中,都需要高效的代码。优化代码性能不仅仅是让代码跑得更快,还要保持代码的可读性、可维护性和可扩展性。下面我将从多个角度来探讨如何优化代码性能:1.算法优化算法是影响性能的核心。如果用最简单的方式解决问题,可能会导致性能瓶颈。因此,首先需要选择合适的算法。时间复杂度:使用更高效的算法来替代低效的算法。例如,排</div>
                    </li>
                    <li><a href="/article/1885301990288846848.htm"
                           title="前端的核心技术" target="_blank">前端的核心技术</a>
                        <span class="text-muted">善良的小乔</span>
<a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a>
                        <div>前端开发的核心技术主要围绕HTML、CSS、JavaScript三大基础语言展开,同时结合现代前端开发的需求,还包括前端框架、构建工具、前端安全和性能优化等内容。下面,我们详细解析前端开发中的核心技术。一.HTML(超文本标记语言)详解HTML(HyperTextMarkupLanguage)是前端开发的基础,用于构建网页的结构。HTML由各种标签(Tag)组成,每个标签都有不同的作用,主要用于定</div>
                    </li>
                    <li><a href="/article/1885277514507546624.htm"
                           title="深入了解 React:从入门到高级应用" target="_blank">深入了解 React:从入门到高级应用</a>
                        <span class="text-muted">╰つ゛木槿</span>
<a class="tag" taget="_blank" href="/search/web%E5%89%8D%E7%AB%AF/1.htm">web前端</a><a class="tag" taget="_blank" href="/search/react.js/1.htm">react.js</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF%E6%A1%86%E6%9E%B6/1.htm">前端框架</a>
                        <div>深入了解React:从入门到高级应用React是由Facebook开发并维护的一个开源JavaScript库,用于构建用户界面。自2013年发布以来,React在前端开发领域迅速崛起,成为最受欢迎的UI构建工具之一。无论是小型的单页应用(SPA)还是复杂的大型企业级应用,React都能提供高效、灵活的解决方案。本文将全面、详细地介绍React,包括其核心概念、工作原理、最佳实践以及生态系统。目录:</div>
                    </li>
                    <li><a href="/article/1885264147717877760.htm"
                           title="构建企业级React应用的进阶实践" target="_blank">构建企业级React应用的进阶实践</a>
                        <span class="text-muted">python算法(魔法师版)</span>
<a class="tag" taget="_blank" href="/search/react.js/1.htm">react.js</a>
                        <div>构建企业级React应用的进阶实践在当今前端开发领域,React凭借其组件化架构和声明式编程范式,已成为构建复杂用户界面的首选方案。本文将深入探讨React的高级应用场景,通过一系列精心设计的代码示例,展示如何打造高性能、可维护的现代化前端应用。一、状态管理的艺术1.1原子化状态管理typescript复制//lib/recoil/atoms.tsimport{atom,selector}from</div>
                    </li>
                                <li><a href="/article/16.htm"
                                       title="深入浅出Java Annotation(元注解和自定义注解)" target="_blank">深入浅出Java Annotation(元注解和自定义注解)</a>
                                    <span class="text-muted">Josh_Persistence</span>
<a class="tag" taget="_blank" href="/search/Java+Annotation/1.htm">Java Annotation</a><a class="tag" taget="_blank" href="/search/%E5%85%83%E6%B3%A8%E8%A7%A3/1.htm">元注解</a><a class="tag" taget="_blank" href="/search/%E8%87%AA%E5%AE%9A%E4%B9%89%E6%B3%A8%E8%A7%A3/1.htm">自定义注解</a>
                                    <div>一、基本概述 
  
  
   Annontation是Java5开始引入的新特征。中文名称一般叫注解。它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。 
  
  更通俗的意思是为程序的元素(类、方法、成员变量)加上更直观更明了的说明,这些说明信息是与程序的业务逻辑无关,并且是供指定的工具或</div>
                                </li>
                                <li><a href="/article/143.htm"
                                       title="mysql优化特定类型的查询" target="_blank">mysql优化特定类型的查询</a>
                                    <span class="text-muted">annan211</span>
<a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/%E5%B7%A5%E4%BD%9C/1.htm">工作</a><a class="tag" taget="_blank" href="/search/mysql/1.htm">mysql</a>
                                    <div>

本节所介绍的查询优化的技巧都是和特定版本相关的,所以对于未来mysql的版本未必适用。

1 优化count查询
  对于count这个函数的网上的大部分资料都是错误的或者是理解的都是一知半解的。在做优化之前我们先来看看
  真正的count()函数的作用到底是什么。
  count()是一个特殊的函数,有两种非常不同的作用,他可以统计某个列值的数量,也可以统计行数。
  在统</div>
                                </li>
                                <li><a href="/article/270.htm"
                                       title="MAC下安装多版本JDK和切换几种方式" target="_blank">MAC下安装多版本JDK和切换几种方式</a>
                                    <span class="text-muted">棋子chessman</span>
<a class="tag" taget="_blank" href="/search/jdk/1.htm">jdk</a>
                                    <div>环境: 
MAC AIR,OS X 10.10,64位 
  
历史: 
过去 Mac 上的 Java 都是由 Apple 自己提供,只支持到 Java 6,并且OS X 10.7 开始系统并不自带(而是可选安装)(原自带的是1.6)。 
后来 Apple 加入 OpenJDK 继续支持 Java 6,而 Java 7 将由 Oracle 负责提供。 
  
在终端中输入jav</div>
                                </li>
                                <li><a href="/article/397.htm"
                                       title="javaScript (1)" target="_blank">javaScript (1)</a>
                                    <span class="text-muted">Array_06</span>
<a class="tag" taget="_blank" href="/search/JavaScript/1.htm">JavaScript</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/%E6%B5%8F%E8%A7%88%E5%99%A8/1.htm">浏览器</a>
                                    <div>JavaScript 
 
1、运算符 
  运算符就是完成操作的一系列符号,它有七类:   赋值运算符(=,+=,-=,*=,/=,%=,<<=,>>=,|=,&=)、算术运算符(+,-,*,/,++,--,%)、比较运算符(>,<,<=,>=,==,===,!=,!==)、逻辑运算符(||,&&,!)、条件运算(?:)、位</div>
                                </li>
                                <li><a href="/article/524.htm"
                                       title="国内顶级代码分享网站" target="_blank">国内顶级代码分享网站</a>
                                    <span class="text-muted">袁潇含</span>
<a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/jdk/1.htm">jdk</a><a class="tag" taget="_blank" href="/search/oracle/1.htm">oracle</a><a class="tag" taget="_blank" href="/search/.net/1.htm">.net</a><a class="tag" taget="_blank" href="/search/PHP/1.htm">PHP</a>
                                    <div>       现在国内很多开源网站感觉都是为了利益而做的 
  
        
       当然利益是肯定的,否则谁也不会免费的去做网站 
  
   &</div>
                                </li>
                                <li><a href="/article/651.htm"
                                       title="Elasticsearch、MongoDB和Hadoop比较" target="_blank">Elasticsearch、MongoDB和Hadoop比较</a>
                                    <span class="text-muted">随意而生</span>
<a class="tag" taget="_blank" href="/search/mongodb/1.htm">mongodb</a><a class="tag" taget="_blank" href="/search/hadoop/1.htm">hadoop</a><a class="tag" taget="_blank" href="/search/%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E/1.htm">搜索引擎</a>
                                    <div>    
IT界在过去几年中出现了一个有趣的现象。很多新的技术出现并立即拥抱了“大数据”。稍微老一点的技术也会将大数据添进自己的特性,避免落大部队太远,我们看到了不同技术之间的边际的模糊化。假如你有诸如Elasticsearch或者Solr这样的搜索引擎,它们存储着JSON文档,MongoDB存着JSON文档,或者一堆JSON文档存放在一个Hadoop集群的HDFS中。你可以使用这三种配</div>
                                </li>
                                <li><a href="/article/778.htm"
                                       title="mac os 系统科研软件总结" target="_blank">mac os 系统科研软件总结</a>
                                    <span class="text-muted">张亚雄</span>
<a class="tag" taget="_blank" href="/search/mac+os/1.htm">mac os</a>
                                    <div>1.1 Microsoft Office for Mac 2011 
     大客户版,自行搜索。 
     1.2 Latex (MacTex): 
     系统环境:https://tug.org/mactex/ 
    &nb</div>
                                </li>
                                <li><a href="/article/905.htm"
                                       title="Maven实战(四)生命周期" target="_blank">Maven实战(四)生命周期</a>
                                    <span class="text-muted">AdyZhang</span>
<a class="tag" taget="_blank" href="/search/maven/1.htm">maven</a>
                                    <div>1. 三套生命周期     Maven拥有三套相互独立的生命周期,它们分别为clean,default和site。 每个生命周期包含一些阶段,这些阶段是有顺序的,并且后面的阶段依赖于前面的阶段,用户和Maven最直接的交互方式就是调用这些生命周期阶段。 以clean生命周期为例,它包含的阶段有pre-clean, clean 和 post</div>
                                </li>
                                <li><a href="/article/1032.htm"
                                       title="Linux下Jenkins迁移" target="_blank">Linux下Jenkins迁移</a>
                                    <span class="text-muted">aijuans</span>
<a class="tag" taget="_blank" href="/search/Jenkins/1.htm">Jenkins</a>
                                    <div>1. 将Jenkins程序目录copy过去       源程序在/export/data/tomcatRoot/ofctest-jenkins.jd.com下面                tar -cvzf jenkins.tar.gz ofctest-jenkins.jd.com &</div>
                                </li>
                                <li><a href="/article/1159.htm"
                                       title="request.getInputStream()只能获取一次的问题" target="_blank">request.getInputStream()只能获取一次的问题</a>
                                    <span class="text-muted">ayaoxinchao</span>
<a class="tag" taget="_blank" href="/search/request/1.htm">request</a><a class="tag" taget="_blank" href="/search/Inputstream/1.htm">Inputstream</a>
                                    <div>问题:在使用HTTP协议实现应用间接口通信时,服务端读取客户端请求过来的数据,会用到request.getInputStream(),第一次读取的时候可以读取到数据,但是接下来的读取操作都读取不到数据        
原因:   1. 一个InputStream对象在被读取完成后,将无法被再次读取,始终返回-1;   2. InputStream并没有实现reset方法(可以重</div>
                                </li>
                                <li><a href="/article/1286.htm"
                                       title="数据库SQL优化大总结之 百万级数据库优化方案" target="_blank">数据库SQL优化大总结之 百万级数据库优化方案</a>
                                    <span class="text-muted">BigBird2012</span>
<a class="tag" taget="_blank" href="/search/SQL%E4%BC%98%E5%8C%96/1.htm">SQL优化</a>
                                    <div>网上关于SQL优化的教程很多,但是比较杂乱。近日有空整理了一下,写出来跟大家分享一下,其中有错误和不足的地方,还请大家纠正补充。 
这篇文章我花费了大量的时间查找资料、修改、排版,希望大家阅读之后,感觉好的话推荐给更多的人,让更多的人看到、纠正以及补充。 
1.对查询进行优化,要尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。 
2.应尽量避免在 where </div>
                                </li>
                                <li><a href="/article/1413.htm"
                                       title="jsonObject的使用" target="_blank">jsonObject的使用</a>
                                    <span class="text-muted">bijian1013</span>
<a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/json/1.htm">json</a>
                                    <div>        在项目中难免会用java处理json格式的数据,因此封装了一个JSONUtil工具类。 
JSONUtil.java 
package com.bijian.json.study;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;</div>
                                </li>
                                <li><a href="/article/1540.htm"
                                       title="[Zookeeper学习笔记之六]Zookeeper源代码分析之Zookeeper.WatchRegistration" target="_blank">[Zookeeper学习笔记之六]Zookeeper源代码分析之Zookeeper.WatchRegistration</a>
                                    <span class="text-muted">bit1129</span>
<a class="tag" taget="_blank" href="/search/zookeeper/1.htm">zookeeper</a>
                                    <div>Zookeeper类是Zookeeper提供给用户访问Zookeeper service的主要API,它包含了如下几个内部类 
  
  
首先分析它的内部类,从WatchRegistration开始,为指定的znode path注册一个Watcher, 
  
    /**
     * Register a watcher for a particular p</div>
                                </li>
                                <li><a href="/article/1667.htm"
                                       title="【Scala十三】Scala核心七:部分应用函数" target="_blank">【Scala十三】Scala核心七:部分应用函数</a>
                                    <span class="text-muted">bit1129</span>
<a class="tag" taget="_blank" href="/search/scala/1.htm">scala</a>
                                    <div>何为部分应用函数? 
Partially applied function: A function that’s used in an expression and that misses some of its arguments.For instance, if function f has type Int => Int => Int, then f and f(1) are p</div>
                                </li>
                                <li><a href="/article/1794.htm"
                                       title="Tomcat Error listenerStart 终极大法" target="_blank">Tomcat Error listenerStart 终极大法</a>
                                    <span class="text-muted">ronin47</span>
<a class="tag" taget="_blank" href="/search/tomcat/1.htm">tomcat</a>
                                    <div>Tomcat报的错太含糊了,什么错都没报出来,只提示了Error listenerStart。为了调试,我们要获得更详细的日志。可以在WEB-INF/classes目录下新建一个文件叫logging.properties,内容如下 
 
Java代码  
handlers = org.apache.juli.FileHandler, java.util.logging.ConsoleHa</div>
                                </li>
                                <li><a href="/article/1921.htm"
                                       title="不用加减符号实现加减法" target="_blank">不用加减符号实现加减法</a>
                                    <span class="text-muted">BrokenDreams</span>
<a class="tag" taget="_blank" href="/search/%E5%AE%9E%E7%8E%B0/1.htm">实现</a>
                                    <div>        今天有群友发了一个问题,要求不用加减符号(包括负号)来实现加减法。 
        分析一下,先看最简单的情况,假设1+1,按二进制算的话结果是10,可以看到从右往左的第一位变为0,第二位由于进位变为1。 
   </div>
                                </li>
                                <li><a href="/article/2048.htm"
                                       title="读《研磨设计模式》-代码笔记-状态模式-State" target="_blank">读《研磨设计模式》-代码笔记-状态模式-State</a>
                                    <span class="text-muted">bylijinnan</span>
<a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/1.htm">设计模式</a>
                                    <div>声明: 本文只为方便我个人查阅和理解,详细的分析以及源代码请移步 原作者的博客http://chjavach.iteye.com/ 
 
 




/*

当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类
状态模式主要解决的是当控制一个对象状态的条件表达式过于复杂时的情况
把状态的判断逻辑转移到表示不同状态的一系列类中,可以把复杂的判断逻辑简化

如果在</div>
                                </li>
                                <li><a href="/article/2175.htm"
                                       title="CUDA程序block和thread超出硬件允许值时的异常" target="_blank">CUDA程序block和thread超出硬件允许值时的异常</a>
                                    <span class="text-muted">cherishLC</span>
<a class="tag" taget="_blank" href="/search/CUDA/1.htm">CUDA</a>
                                    <div>调用CUDA的核函数时指定block 和 thread大小,该大小可以是dim3类型的(三维数组),只用一维时可以是usigned int型的。 
以下程序验证了当block或thread大小超出硬件允许值时会产生异常!!!GPU根本不会执行运算!!! 
所以验证结果的正确性很重要!!! 
在VS中创建CUDA项目会有一个模板,里面有更详细的状态验证。 
 
 
以下程序在K5000GPU上跑的。</div>
                                </li>
                                <li><a href="/article/2302.htm"
                                       title="诡异的超长时间GC问题定位" target="_blank">诡异的超长时间GC问题定位</a>
                                    <span class="text-muted">chenchao051</span>
<a class="tag" taget="_blank" href="/search/jvm/1.htm">jvm</a><a class="tag" taget="_blank" href="/search/cms/1.htm">cms</a><a class="tag" taget="_blank" href="/search/GC/1.htm">GC</a><a class="tag" taget="_blank" href="/search/hbase/1.htm">hbase</a><a class="tag" taget="_blank" href="/search/swap/1.htm">swap</a>
                                    <div>HBase的GC策略采用PawNew+CMS, 这是大众化的配置,ParNew经常会出现停顿时间特别长的情况,有时候甚至长到令人发指的地步,例如请看如下日志: 
2012-10-17T05:54:54.293+0800: 739594.224: [GC 739606.508: [ParNew: 996800K->110720K(996800K), 178.8826900 secs] 3700</div>
                                </li>
                                <li><a href="/article/2429.htm"
                                       title="maven环境快速搭建" target="_blank">maven环境快速搭建</a>
                                    <span class="text-muted">daizj</span>
<a class="tag" taget="_blank" href="/search/%E5%AE%89%E8%A3%85/1.htm">安装</a><a class="tag" taget="_blank" href="/search/mavne/1.htm">mavne</a><a class="tag" taget="_blank" href="/search/%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE/1.htm">环境配置</a>
                                    <div>一 下载maven 
 
安装maven之前,要先安装jdk及配置JAVA_HOME环境变量。这个安装和配置java环境不用多说。 
 
maven下载地址:http://maven.apache.org/download.html,目前最新的是这个apache-maven-3.2.5-bin.zip,然后解压在任意位置,最好地址中不要带中文字符,这个做java 的都知道,地址中出现中文会出现很多</div>
                                </li>
                                <li><a href="/article/2556.htm"
                                       title="PHP网站安全,避免PHP网站受到攻击的方法" target="_blank">PHP网站安全,避免PHP网站受到攻击的方法</a>
                                    <span class="text-muted">dcj3sjt126com</span>
<a class="tag" taget="_blank" href="/search/PHP/1.htm">PHP</a>
                                    <div>  
对于PHP网站安全主要存在这样几种攻击方式:1、命令注入(Command Injection)2、eval注入(Eval Injection)3、客户端脚本攻击(Script Insertion)4、跨网站脚本攻击(Cross Site Scripting, XSS)5、SQL注入攻击(SQL injection)6、跨网站请求伪造攻击(Cross Site Request Forgerie</div>
                                </li>
                                <li><a href="/article/2683.htm"
                                       title="yii中给CGridView设置默认的排序根据时间倒序的方法" target="_blank">yii中给CGridView设置默认的排序根据时间倒序的方法</a>
                                    <span class="text-muted">dcj3sjt126com</span>
<a class="tag" taget="_blank" href="/search/GridView/1.htm">GridView</a>
                                    <div>public function searchWithRelated() { 
        $criteria = new CDbCriteria; 
 
        $criteria->together = true; //without th</div>
                                </li>
                                <li><a href="/article/2810.htm"
                                       title="Java集合对象和数组对象的转换" target="_blank">Java集合对象和数组对象的转换</a>
                                    <span class="text-muted">dyy_gusi</span>
<a class="tag" taget="_blank" href="/search/java%E9%9B%86%E5%90%88/1.htm">java集合</a>
                                    <div>    在开发中,我们经常需要将集合对象(List,Set)转换为数组对象,或者将数组对象转换为集合对象。Java提供了相互转换的工具,但是我们使用的时候需要注意,不能乱用滥用。 
1、数组对象转换为集合对象 
    最暴力的方式是new一个集合对象,然后遍历数组,依次将数组中的元素放入到新的集合中,但是这样做显然过</div>
                                </li>
                                <li><a href="/article/2937.htm"
                                       title="nginx同一主机部署多个应用" target="_blank">nginx同一主机部署多个应用</a>
                                    <span class="text-muted">geeksun</span>
<a class="tag" taget="_blank" href="/search/nginx/1.htm">nginx</a>
                                    <div>近日有一需求,需要在一台主机上用nginx部署2个php应用,分别是wordpress和wiki,探索了半天,终于部署好了,下面把过程记录下来。 
1.   在nginx下创建vhosts目录,用以放置vhost文件。 
mkdir vhosts 
  
2.   修改nginx.conf的配置, 在http节点增加下面内容设置,用来包含vhosts里的配置文件 
#</div>
                                </li>
                                <li><a href="/article/3064.htm"
                                       title="ubuntu添加admin权限的用户账号" target="_blank">ubuntu添加admin权限的用户账号</a>
                                    <span class="text-muted">hongtoushizi</span>
<a class="tag" taget="_blank" href="/search/ubuntu/1.htm">ubuntu</a><a class="tag" taget="_blank" href="/search/useradd/1.htm">useradd</a>
                                    <div>ubuntu创建账号的方式通常用到两种:useradd 和adduser .   本人尝试了useradd方法,步骤如下:  
1:useradd 
   使用useradd时,如果后面不加任何参数的话,如:sudo useradd sysadm 创建出来的用户将是默认的三无用户:无home directory ,无密码,无系统shell。 
顾应该如下操作: 
  </div>
                                </li>
                                <li><a href="/article/3191.htm"
                                       title="第五章 常用Lua开发库2-JSON库、编码转换、字符串处理" target="_blank">第五章 常用Lua开发库2-JSON库、编码转换、字符串处理</a>
                                    <span class="text-muted">jinnianshilongnian</span>
<a class="tag" taget="_blank" href="/search/nginx/1.htm">nginx</a><a class="tag" taget="_blank" href="/search/lua/1.htm">lua</a>
                                    <div>   JSON库 
  
在进行数据传输时JSON格式目前应用广泛,因此从Lua对象与JSON字符串之间相互转换是一个非常常见的功能;目前Lua也有几个JSON库,本人用过cjson、dkjson。其中cjson的语法严格(比如unicode \u0020\u7eaf),要求符合规范否则会解析失败(如\u002),而dkjson相对宽松,当然也可以通过修改cjson的源码来完成</div>
                                </li>
                                <li><a href="/article/3318.htm"
                                       title="Spring定时器配置的两种实现方式OpenSymphony Quartz和java Timer详解" target="_blank">Spring定时器配置的两种实现方式OpenSymphony Quartz和java Timer详解</a>
                                    <span class="text-muted">yaerfeng1989</span>
<a class="tag" taget="_blank" href="/search/timer/1.htm">timer</a><a class="tag" taget="_blank" href="/search/quartz/1.htm">quartz</a><a class="tag" taget="_blank" href="/search/%E5%AE%9A%E6%97%B6%E5%99%A8/1.htm">定时器</a>
                                    <div>原创整理不易,转载请注明出处:Spring定时器配置的两种实现方式OpenSymphony Quartz和java Timer详解 
代码下载地址:http://www.zuidaima.com/share/1772648445103104.htm 
有两种流行Spring定时器配置:Java的Timer类和OpenSymphony的Quartz。 
1.Java Timer定时 
首先继承jav</div>
                                </li>
                                <li><a href="/article/3445.htm"
                                       title="Linux下df与du两个命令的差别?" target="_blank">Linux下df与du两个命令的差别?</a>
                                    <span class="text-muted">pda158</span>
<a class="tag" taget="_blank" href="/search/linux/1.htm">linux</a>
                                    <div> 一、df显示文件系统的使用情况,与du比較,就是更全盘化。     最经常使用的就是 df -T,显示文件系统的使用情况并显示文件系统的类型。     举比例如以下:     [root@localhost ~]# df -T     Filesystem                   Type &n</div>
                                </li>
                                <li><a href="/article/3572.htm"
                                       title="[转]SQLite的工具类 ---- 通过反射把Cursor封装到VO对象" target="_blank">[转]SQLite的工具类 ---- 通过反射把Cursor封装到VO对象</a>
                                    <span class="text-muted">ctfzh</span>
<a class="tag" taget="_blank" href="/search/VO/1.htm">VO</a><a class="tag" taget="_blank" href="/search/android/1.htm">android</a><a class="tag" taget="_blank" href="/search/sqlite/1.htm">sqlite</a><a class="tag" taget="_blank" href="/search/%E5%8F%8D%E5%B0%84/1.htm">反射</a><a class="tag" taget="_blank" href="/search/Cursor/1.htm">Cursor</a>
                                    <div>在写DAO层时,觉得从Cursor里一个一个的取出字段值再装到VO(值对象)里太麻烦了,就写了一个工具类,用到了反射,可以把查询记录的值装到对应的VO里,也可以生成该VO的List。 
  
使用时需要注意:   
考虑到Android的性能问题,VO没有使用Setter和Getter,而是直接用public的属性。  
表中的字段名需要和VO的属性名一样,要是不一样就得在查询的SQL中</div>
                                </li>
                                <li><a href="/article/3699.htm"
                                       title="该学习笔记用到的Employee表" target="_blank">该学习笔记用到的Employee表</a>
                                    <span class="text-muted">vipbooks</span>
<a class="tag" taget="_blank" href="/search/oracle/1.htm">oracle</a><a class="tag" taget="_blank" href="/search/sql/1.htm">sql</a><a class="tag" taget="_blank" href="/search/%E5%B7%A5%E4%BD%9C/1.htm">工作</a>
                                    <div>    这是我在学习Oracle是用到的Employee表,在该笔记中用到的就是这张表,大家可以用它来学习和练习。 
 
 

drop table Employee;
-- 员工信息表
create table Employee(
       -- 员工编号
       EmpNo number(3) primary key,
       -- 姓</div>
                                </li>
                </ul>
            </div>
        </div>
    </div>

<div>
    <div class="container">
        <div class="indexes">
            <strong>按字母分类:</strong>
            <a href="/tags/A/1.htm" target="_blank">A</a><a href="/tags/B/1.htm" target="_blank">B</a><a href="/tags/C/1.htm" target="_blank">C</a><a
                href="/tags/D/1.htm" target="_blank">D</a><a href="/tags/E/1.htm" target="_blank">E</a><a href="/tags/F/1.htm" target="_blank">F</a><a
                href="/tags/G/1.htm" target="_blank">G</a><a href="/tags/H/1.htm" target="_blank">H</a><a href="/tags/I/1.htm" target="_blank">I</a><a
                href="/tags/J/1.htm" target="_blank">J</a><a href="/tags/K/1.htm" target="_blank">K</a><a href="/tags/L/1.htm" target="_blank">L</a><a
                href="/tags/M/1.htm" target="_blank">M</a><a href="/tags/N/1.htm" target="_blank">N</a><a href="/tags/O/1.htm" target="_blank">O</a><a
                href="/tags/P/1.htm" target="_blank">P</a><a href="/tags/Q/1.htm" target="_blank">Q</a><a href="/tags/R/1.htm" target="_blank">R</a><a
                href="/tags/S/1.htm" target="_blank">S</a><a href="/tags/T/1.htm" target="_blank">T</a><a href="/tags/U/1.htm" target="_blank">U</a><a
                href="/tags/V/1.htm" target="_blank">V</a><a href="/tags/W/1.htm" target="_blank">W</a><a href="/tags/X/1.htm" target="_blank">X</a><a
                href="/tags/Y/1.htm" target="_blank">Y</a><a href="/tags/Z/1.htm" target="_blank">Z</a><a href="/tags/0/1.htm" target="_blank">其他</a>
        </div>
    </div>
</div>
<footer id="footer" class="mb30 mt30">
    <div class="container">
        <div class="footBglm">
            <a target="_blank" href="/">首页</a> -
            <a target="_blank" href="/custom/about.htm">关于我们</a> -
            <a target="_blank" href="/search/Java/1.htm">站内搜索</a> -
            <a target="_blank" href="/sitemap.txt">Sitemap</a> -
            <a target="_blank" href="/custom/delete.htm">侵权投诉</a>
        </div>
        <div class="copyright">版权所有 IT知识库 CopyRight © 2000-2050 E-COM-NET.COM , All Rights Reserved.
<!--            <a href="https://beian.miit.gov.cn/" rel="nofollow" target="_blank">京ICP备09083238号</a><br>-->
        </div>
    </div>
</footer>
<!-- 代码高亮 -->
<script type="text/javascript" src="/static/syntaxhighlighter/scripts/shCore.js"></script>
<script type="text/javascript" src="/static/syntaxhighlighter/scripts/shLegacy.js"></script>
<script type="text/javascript" src="/static/syntaxhighlighter/scripts/shAutoloader.js"></script>
<link type="text/css" rel="stylesheet" href="/static/syntaxhighlighter/styles/shCoreDefault.css"/>
<script type="text/javascript" src="/static/syntaxhighlighter/src/my_start_1.js"></script>





</body>

</html>