快速搭建一个代码在线编辑预览工具

简介

大家好,我是一个闲着没事热衷于重复造轮子的不知名前端,今天给大家带来的是一个代码在线编辑预览工具的实现介绍,目前这类工具使用很广泛,常见于各种文档网站及代码分享场景,相关工具也比较多,如codepen、jsrun、codesandbox、jsbin、plnkr、jsfiddle等,这些工具大体分两类,一类可以自由添加多个文件,比较像我们平常使用的编辑器,另一类固定只能单独编辑htmljscss,第二类比较常见,对于demo场景来说其实已经够用,当然,说的只是表象,底层实现方式可能还是各有千秋的。

本文主要介绍的是第二类其中的一种实现方式,完全不依赖于后端,所有逻辑都在前端完成,实现起来相当简单,使用的是vue3全家桶来开发,使用其他框架也完全可以。

ps.在本文基础上笔者开发了一个完整的线上工具,带云端保存,地址:http://lxqnsys.com/code-run/,欢迎使用。

页面结构

image-20210427170009062.png

我挑了一个比较典型也比较好看的结构来仿照,默认布局上下分成四部分,工具栏、编辑器、预览区域及控制台,编辑器又分为三部分,分别是HTMLCSSJavaScript,其实就是三个编辑器,用来编辑代码。

各部分都可以拖动进行调节大小,比如按住js编辑器左边的灰色竖条向右拖动,那么js编辑器的宽度会减少,同时css编辑器的宽度会增加,如果向左拖动,那么css编辑器宽度会减少,js编辑器的宽度会增加,当css编辑器宽度已经不能再减少的时候css编辑器也会同时向左移,然后减少html的宽度。

在实现上,水平调节宽度和垂直调节高度原理是一样的,以调节宽度为例,三个编辑器的宽度使用一个数组来维护,用百分比来表示,那么初始就是100/3%,然后每个编辑器都有一个拖动条,位于内部的左侧,那么当按住拖动某个拖动条拖动时的逻辑如下:

1.把本次拖动瞬间的偏移量由像素转换为百分比;

2.如果是向左拖动的话,检测本次拖动编辑器的左侧是否存在还有空间可以压缩的编辑器,没有的话代表不能进行拖动;如果有的话,那么拖动时增加本次拖动编辑器的宽度,同时减少找到的第一个有空间的编辑器的宽度,直到无法再继续拖动;

3.如果是向右拖动的话,检测本次拖动编辑器及其右侧是否存在还有空间可以压缩的编辑器,没有的话也代表不能再拖动,如果有的话,找到第一个并减少该编辑器的宽度,同时增加本次拖动编辑器左侧第一个编辑器的宽度;

核心代码如下:

const onDrag = (index, e) => {
    let client = this._dir === 'v' ? e.clientY : e.clientX
    // 本次移动的距离
    let dx = client - this._last
    // 换算成百分比
    let rx = (dx / this._containerSize) * 100
    // 更新上一次的鼠标位置
    this._last = client
    if (dx < 0) {
        // 向左/上拖动
        if (!this.isCanDrag('leftUp', index)) {
            return
        }
        // 拖动中的编辑器增加宽度
        if (this._dragItemList.value[index][this._prop] - rx < this.getMaxSize(index)) {
            this._dragItemList.value[index][this._prop] -= rx
        } else {
            this._dragItemList.value[index][this._prop] = this.getMaxSize(index)
        }
        // 找到左边第一个还有空间的编辑器索引
        let narrowItemIndex = this.getFirstNarrowItemIndex('leftUp', index)
        let _minSize = this.getMinSize(narrowItemIndex)
        // 左边的编辑器要同比减少宽度
        if (narrowItemIndex >= 0) {
            // 加上本次偏移还大于最小宽度
            if (this._dragItemList.value[narrowItemIndex][this._prop] + rx > _minSize) {
                this._dragItemList.value[narrowItemIndex][this._prop] += rx
            } else {
                // 否则固定为最小宽度
                this._dragItemList.value[narrowItemIndex][this._prop] = _minSize
            }
        }
    } else if (dx > 0) {
        // 向右/下拖动
        if (!this.isCanDrag('rightDown', index)) {
            return
        }
        // 找到拖动中的编辑器及其右边的编辑器中的第一个还有空间的编辑器索引
        let narrowItemIndex = this.getFirstNarrowItemIndex('rightDown', index)
        let _minSize = this.getMinSize(narrowItemIndex)
        if (narrowItemIndex <= this._dragItemList.value.length - 1) {
            let ax = 0
            // 减去本次偏移还大于最小宽度
            if (this._dragItemList.value[narrowItemIndex][this._prop] - rx > _minSize) {
                ax = rx
            } else {
                // 否则本次能移动的距离为到达最小宽度的距离
                ax = this._dragItemList.value[narrowItemIndex][this._prop] - _minSize
            }
            // 更新拖动中的编辑器的宽度
            this._dragItemList.value[narrowItemIndex][this._prop] -= ax
            // 左边第一个编辑器要同比增加宽度
            if (index > 0) {
                if (this._dragItemList.value[index - 1][this._prop] + ax < this.getMaxSize(index - 1)) {
                    this._dragItemList.value[index - 1][this._prop] += ax
                } else {
                    this._dragItemList.value[index - 1][this._prop] = this.getMaxSize(index - 1)
                }
            }
        }
    }
}

实现效果如下:

2021-04-29-19-15-42.gif

为了能提供多种布局的随意切换,我们有必要把上述逻辑封装一下,封装成两个组件,一个容器组件Drag.vue,一个容器的子组件DragItem.vueDragItem通过slot来显示其他内容,DragItem主要提供拖动条及绑定相关的鼠标事件,Drag组件里包含了上述提到的核心逻辑,维护对应的尺寸数组,提供相关处理方法给DragItem绑定的鼠标事件,然后只要根据所需的结构进行组合即可,下面的结构就是上述默认的布局:


    
        
    
    
        
    
    
        
    

这部分代码较多,有兴趣的可以查看源码。

编辑器

目前涉及到代码编辑的场景基本使用的都是codemirror,因为它功能强大,使用简单,支持语法高亮、支持多种语言和主题等,但是为了能更方便的支持语法提示,本文选择的是微软的monaco-editor,功能和VSCode一样强大,VSCode有多强就不用我多说了,缺点是整体比较复杂,代码量大,内置主题较少。

monaco-editor支持多种加载方式,esm模块加载的方式需要使用webpack,但是vite底层打包工具用的是Rollup,所以本文使用直接引入js的方式。

在官网上下载压缩包后解压到项目的public文件夹下,然后参考示例的方式在index.html文件里添加:






monaco-editor内置了10种语言,我们选择中文的,其他不用的可以直接删掉:

image-20210430163748892.png

接下来创建编辑器就可以了:

const editor = monaco.editor.create(
    editorEl.value,// dom容器
    {
        value: props.content,// 要显示的代码
        language: props.language,// 代码语言,css、javascript等
        minimap: {
            enabled: false,// 关闭小地图
        },
        wordWrap: 'on', // 代码超出换行
        theme: 'vs-dark'// 主题
    }
)

就这么简单,一个带高亮、语法提示、错误提示的编辑器就可以使用了,效果如下:

image-20210430154406199.png

其他几个常用的api如下:

// 设置文档内容
editor.setValue(props.content)
// 监听编辑事件
editor.onDidChangeModelContent((e) => {
    console.log(editor.getValue())// 获取文档内容
})
// 监听失焦事件
editor.onDidBlurEditorText((e) => {
    console.log(editor.getValue())
})

预览

代码有了,接下来就可以渲染页面进行预览了,对于预览,显然是使用iframeiframe除了src属性外,HTML5还新增了一个属性srcdoc,用来渲染一段HTML代码到iframe里,这个属性IE目前不支持,不过vue3都要不支持IE了,咱也不管了,如果硬要支持也简单,使用write方法就行了:

iframeRef.value.contentWindow.document.write(htmlStr)

接下来的思路就很清晰了,把htmlcssjs代码组装起来扔给srcdoc不就完了吗:


const assembleHtml = (head, body) => {
    return `
        
        
            
            ${head}
        
        
            ${body}
        
        `
}

const run = () => {
  let head = `
    预览<\/title>
    <style type="text/css">
        ${editData.value.code.css.content}
    <\/style>
  `
  let body = `
    ${editData.value.code.html.content}
    <script>
        ${editData.value.code.javascript.content}
    <\/script>
  `
  let str = assembleHtml(head, body)
  srcdoc.value = str
}
</code></pre> 
 <p>效果如下:</p> 
 <div class="image-package"> 
  <div class="image-container" style="max-width: 700px; max-height: 551px;"> 
   <div class="image-view">  
   </div> 
  </div> 
  <div class="image-caption">
    image-20210507141946844.png 
  </div> 
 </div> 
 <p>为了防止<code>js</code>代码运行出现错误阻塞页面渲染,我们把<code>js</code>代码使用<code>try catch</code>包裹起来:</p> 
 <pre><code class="js">let body = `
    ${editData.value.code.html.content}
    <script>
        try {
          ${editData.value.code.javascript.content}
        } catch (err) {
          console.error('js代码运行出错')
          console.error(err)
        }
    <\/script>
  `
</code></pre> 
 <h1>控制台</h1> 
 <h2>极简方式</h2> 
 <p>先介绍一种非常简单的方式,使用一个叫eruda的库,这个库是用来方便在手机上进行调试的,和<code>vConsole</code>类似,我们直接把它嵌到<code>iframe</code>里就可以支持控制台的功能了,要嵌入<code>iframe</code>里的文件我们都要放到<code>public</code>文件夹下:</p> 
 <pre><code class="js">const run = () => {
  let head = `
    <title>预览<\/title>
    <style type="text/css">
        ${editData.value.code.css.content}
    <\/style>
  `
  let body = `
    ${editData.value.code.html.content}
    <script src="/eruda/eruda.js"><\/script>
    <script>
        eruda.init();
        ${editData.value.code.javascript.content}
    <\/script>
  `
  let str = assembleHtml(head, body)
  srcdoc.value = str
}
</code></pre> 
 <p>效果如下:</p> 
 <div class="image-package"> 
  <div class="image-container" style="max-width: 700px; max-height: 318px;"> 
   <div class="image-view">  
   </div> 
  </div> 
  <div class="image-caption">
    image-20210507154345054.png 
  </div> 
 </div> 
 <p>这种方式的缺点是只能嵌入到<code>iframe</code>里,不能把控制台和页面分开,导致每次代码重新运行,控制台也会重新运行,无法保留之前的日志,当然,样式也不方便控制。</p> 
 <h2>自己实现</h2> 
 <p>如果选择自己实现的话,那么这部分会是本项目里最复杂的,自己实现的话一般只实现一个<code>console</code>的功能,其他的比如<code>html</code>结构、请求资源之类的就不做了,毕竟实现起来费时费力,用处也不是很大。</p> 
 <p><code>console</code>大体上要支持输出两种信息,一是<code>console</code>对象打印出来的信息,二是各种报错信息,先看<code>console</code>信息。</p> 
 <h3>console信息</h3> 
 <p>思路很简单,在<code>iframe</code>里拦截<code>console</code>对象的所有方法,当某个方法被调用时使用<code>postMessage</code>来向父页面传递信息,父页面的控制台打印出对应的信息即可。</p> 
 <pre><code class="js">// /public/console/index.js

// 重写的console对象的构造函数,直接修改console对象的方法进行拦截的方式是不行的,有兴趣可以自行尝试
function ProxyConsole() {};
// 拦截console的所有方法
[
    'debug',
    'clear',
    'error',
    'info',
    'log',
    'warn',
    'dir',
    'props',
    'group',
    'groupEnd',
    'dirxml',
    'table',
    'trace',
    'assert',
    'count',
    'markTimeline',
    'profile',
    'profileEnd',
    'time',
    'timeEnd',
    'timeStamp',
    'groupCollapsed'
].forEach((method) => {
    let originMethod = console[method]
    // 设置原型方法
    ProxyConsole.prototype[method] = function (...args) {
        // 发送信息给父窗口
        window.parent.postMessage({
            type: 'console',
            method,
            data: args
        })
        // 调用原始方法
        originMethod.apply(ProxyConsole, args)
    }
})
// 覆盖原console对象
window.console = new ProxyConsole()
</code></pre> 
 <p>把这个文件也嵌入到<code>iframe</code>里:</p> 
 <pre><code class="js">const run = () => {
  let head = `
    <title>预览<\/title>
    <style type="text/css">
        ${editData.value.code.css.content}
    <\/style>
    <script src="/console/index.js"><\/script>
  `
  // ...
}
</code></pre> 
 <p>父页面监听<code>message</code>事件即可:</p> 
 <pre><code class="js">window.addEventListener('message', (e) => {
  console.log(e)
})
</code></pre> 
 <p>如果如下:</p> 
 <div class="image-package"> 
  <div class="image-container" style="max-width: 700px; max-height: 387px;"> 
   <div class="image-view">  
   </div> 
  </div> 
  <div class="image-caption">
    image-20210507165953197.png 
  </div> 
 </div> 
 <p>监听获取到了信息就可以显示出来,我们一步步来看:</p> 
 <p>首先<code>console</code>的方法都可以同时接收多个参数,打印多个数据,同时打印的在同一行进行显示。</p> 
 <p>1.基本数据类型</p> 
 <p>基本数据类型只要都转成字符串显示出来就可以了,无非是使用颜色区分一下:</p> 
 <pre><code class="js">// /public/console/index.js

// ...

window.parent.postMessage({
    type: 'console',
    method,
    data: args.map((item) => {// 对每个要打印的数据进行处理
        return handleData(item)
    })
})

// ...

// 处理数据
const handleData = (content) => {
    let contentType = type(content)
    switch (contentType) {
        case 'boolean': // 布尔值
            content = content ? 'true' : 'false'
            break;
        case 'null': // null
            content = 'null'
            break;
        case 'undefined': // undefined
            content = 'undefined'
            break;
        case 'symbol': // Symbol,Symbol不能直接通过postMessage进行传递,会报错,需要转成字符串
            content = content.toString()
            break;
        default:
            break;
    }
    return {
        contentType,
        content,
    }
}
</code></pre> 
 <pre><code class="js">// 日志列表
const logList = ref([])

// 监听iframe信息
window.addEventListener('message', ({ data = {} }) => {
  if (data.type === 'console') 
    logList.value.push({
      type: data.method,// console的方法名
      data: data.data// 要显示的信息,一个数组,可能同时打印多条信息
    })
  }
})
</code></pre> 
 <pre><code class="html"><div class="logBox">
    <div class="logRow" v-for="(log, index) in logList" :key="index">
        <template v-for="(logItem, itemIndex) in log.data" :key="itemIndex">
            <!-- 基本数据类型 -->
            <div class="logItem message" :class="[logItem.contentType]" v-html="logItem.content"></div>
        </template>
    </div>
</div>
</code></pre> 
 <div class="image-package"> 
  <div class="image-container" style="max-width: 700px; max-height: 226px;"> 
   <div class="image-view">  
   </div> 
  </div> 
  <div class="image-caption">
    image-20210508091625420.png 
  </div> 
 </div> 
 <p>2.函数</p> 
 <p>函数只要调用<code>toString</code>方法转成字符串即可:</p> 
 <pre><code class="js">const handleData = (content) => {
        let contentType = type(content)
        switch (contentType) {
            // ...
            case 'function':
                content = content.toString()
                break;
            default:
                break;
        }
    }
</code></pre> 
 <p>3.json数据</p> 
 <p><code>json</code>数据需要格式化后进行显示,也就是带高亮、带缩进,以及支持展开收缩。</p> 
 <p>实现也很简单,高亮可以通过<code>css</code>类名控制,缩进换行可以使用<code>div</code>和<code>span</code>来包裹,具体实现就是像深拷贝一样深度优先遍历<code>json</code>树,对象或数组的话就使用一个<code>div</code>来整体包裹,这样可以很方便的实现整体缩进,具体到对象或数组的某项时也使用<code>div</code>来实现换行,需要注意的是如果是作为对象的某个属性的值的话,需要使用<code>span</code>来和属性及冒号显示在同一行,此外,也要考虑到循环引用的情况。</p> 
 <p>展开收缩时针对非空的对象和数组,所以可以在遍历下级属性之前添加一个按钮元素,按钮相对于最外层元素使用绝对定位。</p> 
 <pre><code class="js">const handleData = (content) => {
    let contentType = type(content)
    switch (contentType) {
            // ...
        case 'array': // 数组
        case 'object': // 对象
            content = stringify(content, false, true, [])
            break;
        default:
            break;
    }
}

// 序列化json数据变成html字符串
/* 
    data:数据
    hasKey:是否是作为一个key的属性值
    isLast:是否在所在对象或数组中的最后一项
    visited:已经遍历过的对象/数组,用来检测循环引用
*/
const stringify = (data, hasKey, isLast, visited) => {
    let contentType = type(data)
    let str = ''
    let len = 0
    let lastComma = isLast ? '' : ',' // 当数组或对象在最后一项时,不需要显示逗号
    switch (contentType) {
        case 'object': // 对象
            // 检测到循环引用就直接终止遍历
            if (visited.includes(data)) {
                str += `<span class="string">检测到循环引用</span>`
            } else {
                visited.push(data)
                let keys = Object.keys(data)
                len = keys.length
                // 空对象
                if (len <= 0) {
                    // 如果该对象是作为某个属性的值的话,那么左括号要和key显示在同一行
                    str += hasKey ? `<span class="bracket">{ }${lastComma}</span>` : `<div class="bracket">{ }${lastComma}</div>`
                } else { // 非空对象
                    // expandBtn是展开和收缩按钮
                    str += `<span class="el-icon-arrow-right expandBtn"></span>`
                    str += hasKey ? `<span class="bracket">{</span>` : '<div class="bracket">{</div>'
                    // 这个wrap的div用来实现展开和收缩功能
                    str += '<div class="wrap">'
                    // 遍历对象的所有属性
                    keys.forEach((key, index) => {
                        // 是否是数组或对象
                        let childIsJson = ['object', 'array'].includes(type(data[key]))
                        // 最后一项不显示逗号
                        str += `
                            <div class="objectItem">
                                <span class="key">\"${key}\"</span>
                                <span class="colon">:</span>
                                ${stringify(data[key], true, index >= len - 1, visited)}${index < len - 1 && !childIsJson ? ',' : ''}
                            </div>`
                    })
                    str += '</div>'
                    str += `<div class="bracket">}${lastComma}</div>`
                }
            }
            break;
        case 'array': // 数组
            if (visited.includes(data)) {
                str += `<span class="string">检测到循环引用</span>`
            } else {
                visited.push(data)
                len = data.length
                // 空数组
                if (len <= 0) {
                    // 如果该数组是作为某个属性的值的话,那么左括号要和key显示在同一行
                    str += hasKey ? `<span class="bracket">[ ]${lastComma}</span>` : `<div class="bracket">[ ]${lastComma}</div>`
                } else { // 非空数组
                    str += `<span class="el-icon-arrow-right expandBtn"></span>`
                    str += hasKey ? `<span class="bracket">[</span>` : '<div class="bracket">[</div>'
                    str += '<div class="wrap">'
                    data.forEach((item, index) => {
                        // 最后一项不显示逗号
                        str += `
                            <div class="arrayItem">
                                ${stringify(item, true, index >= len - 1, visited)}${index < len - 1 ? ',' : ''}
                            </div>`
                    })
                    str += '</div>'
                    str += `<div class="bracket">]${lastComma}</div>`
                }
            }
            break;
        default: // 其他类型
            let res = handleData(data)
            let quotationMarks = res.contentType === 'string' ? '\"' : '' // 字符串添加双引号
            str += `<span class="${res.contentType}">${quotationMarks}${res.content}${quotationMarks}</span>`
            break;
    }
    return str
}
</code></pre> 
 <p>模板部分也增加一下对<code>json</code>数据的支持:</p> 
 <pre><code class="html"><template v-for="(logItem, itemIndex) in log.data" :key="itemIndex">
    <!-- json对象 -->
    <div
         class="logItem json"
         v-if="['object', 'array'].includes(logItem.contentType)"
         v-html="logItem.content"
         ></div>
    <!-- 字符串、数字 -->
</template>
</code></pre> 
 <p>最后对不同的类名写一下样式即可,效果如下:</p> 
 <div class="image-package"> 
  <div class="image-container" style="max-width: 576px; max-height: 1064px;"> 
   <div class="image-view">  
   </div> 
  </div> 
  <div class="image-caption">
    image-20210508195753623.png 
  </div> 
 </div> 
 <p>展开收缩按钮的点击事件我们使用事件代理的方式绑定到外层元素上:</p> 
 <pre><code class="html"><div
     class="logItem json"
     v-if="['object', 'array'].includes(logItem.contentType)"
     v-html="logItem.content"
     @click="jsonClick"
     >
</div>
</code></pre> 
 <p>点击展开收缩按钮的时候根据当前的展开状态来决定是展开还是收缩,展开和收缩操作的是<code>wrap</code>元素的高度,收缩时同时插入一个省略号的元素来表示此处存在收缩,同时因为按钮使用绝对定位,脱离了正常文档流,所以也需要手动控制它的显示与隐藏,需要注意的是要能区分哪些按钮是本次可以操作的,否则可能下级是收缩状态,但是上层又把该按钮显示出来了:</p> 
 <pre><code class="js">// 在子元素里找到有指定类名的第一个元素
const getChildByClassName = (el, className) => {
  let children = el.children
  for (let i = 0; i < children.length; i++) {
    if (children[i].classList.contains(className)) {
      return children[i]
    }
  }
  return null
}

// json数据展开收缩
let expandIndex = 0
const jsonClick = (e) => {
  // 点击是展开收缩按钮
  if (e.target && e.target.classList.contains('expandBtn')) {
    let target = e.target
    let parent = target.parentNode
    // id,每个展开收缩按钮唯一的标志
    let index = target.getAttribute('data-index')
    if (index === null) {
      index = expandIndex++
      target.setAttribute('data-index', index)
    }
    // 获取当前状态,0表示收缩、1表示展开
    let status = target.getAttribute('expand-status') || '1'
    // 在子节点里找到wrap元素
    let wrapEl = getChildByClassName(parent, 'wrap')
    // 找到下层所有的按钮节点
    let btnEls = wrapEl.querySelectorAll('.expandBtn')
    // 收缩状态 -> 展开状态
    if (status === '0') {
      // 设置状态为展开
      target.setAttribute('expand-status', '1')
      // 展开
      wrapEl.style.height = 'auto'
      // 按钮箭头旋转
      target.classList.remove('shrink')
      // 移除省略号元素
      let ellipsisEl = getChildByClassName(parent, 'ellipsis')
      parent.removeChild(ellipsisEl)
      // 显示下级展开收缩按钮
      for (let i = 0; i < btnEls.length; i++) {
        let _index = btnEls[i].getAttribute('data-for-index')
        // 只有被当前按钮收缩的按钮才显示
        if (_index === index) {
          btnEls[i].removeAttribute('data-for-index')
          btnEls[i].style.display = 'inline-block'
        }
      }
    } else if (status === '1') {
      // 展开状态 -> 收缩状态
      target.setAttribute('expand-status', '0')
      wrapEl.style.height = 0
      target.classList.add('shrink')
      let ellipsisEl = document.createElement('div')
      ellipsisEl.textContent = '...'
      ellipsisEl.className = 'ellipsis'
      parent.insertBefore(ellipsisEl, wrapEl)
      for (let i = 0; i < btnEls.length; i++) {
        let _index = btnEls[i].getAttribute('data-for-index')
        // 只隐藏当前可以被隐藏的按钮
        if (_index === null) {
          btnEls[i].setAttribute('data-for-index', index)
          btnEls[i].style.display = 'none'
        }
      }
    }
  }
}
</code></pre> 
 <p>效果如下:</p> 
 <div class="image-package"> 
  <div class="image-container" style="max-width: 394px; max-height: 1308px;"> 
   <div class="image-view">  
   </div> 
  </div> 
  <div class="image-caption">
    2021-05-08-20-00-57.gif 
  </div> 
 </div> 
 <p>4.console对象的其他方法</p> 
 <p><code>console</code>对象有些方法是有特定逻辑的,比如<code>console.assert(expression, message)</code>,只有当<code>express</code>表达式为<code>false</code>时才会打印<code>message</code>,又比如<code>console</code>的一些方法支持占位符等,这些都得进行相应的支持,先修改一下<code>console</code>拦截的逻辑:</p> 
 <pre><code class="js"> ProxyConsole.prototype[method] = function (...args) {
     // 发送信息给父窗口
     // 针对特定方法进行参数预处理
     let res = handleArgs(method, args)
     // 没有输出时就不发送信息
     if (res.args) {
         window.parent.postMessage({
             type: 'console',
             method: res.method,
             data: res.args.map((item) => {
                 return handleData(item)
             })
         })
     }
     // 调用原始方法
     originMethod.apply(ProxyConsole, args)
 }
</code></pre> 
 <p>增加了<code>handleArgs</code>方法来对特定的方法进行参数处理,比如<code>assert</code>方法:</p> 
 <pre><code class="js">const handleArgs = (method, contents) => {
    switch (method) {
        // 只有当第一个参数为false,才会输出第二个参数,否则不会有任何结果
        case 'assert':
            if (contents[0]) {
                contents = null
            } else {
                method = 'error'
                contents = ['Assertion failed: ' + (contents[1] || 'console.assert')]
            }
            break;
        default:
            break;
    }
    return {
        method,
        args: contents
    }
}
</code></pre> 
 <p>再看一下占位符的处理,占位符描述如下:</p> 
 <div class="image-package"> 
  <div class="image-container" style="max-width: 700px; max-height: 276px;"> 
   <div class="image-view">  
   </div> 
  </div> 
  <div class="image-caption">
    image-20210512135732215.png 
  </div> 
 </div> 
 <p>可以判断第一个参数是否是字符串,以及是否包含占位符,如果包含了,那么就判断是什么占位符,然后取出后面对应位置的参数进行格式化,没有用到的参数也不能丢弃,仍然需要显示:</p> 
 <pre><code class="js">const handleArgs = (method, contents) => {
        // 处理占位符
        if (contents.length > 0) {
            if (type(contents[0]) === 'string') {
                // 只处理%s、%d、%i、%f、%c
                let match = contents[0].match(/(%[sdifc])([^%]*)/gm) // "%d年%d月%d日" -> ["%d年", "%d月", "%d日"]
                if (match) {
                    // 后续参数
                    let sliceArgs = contents.slice(1)
                    let strList = []
                    // 遍历匹配到的结果
                    match.forEach((item, index) => {
                        let placeholder = item.slice(0, 2)
                        let arg = sliceArgs[index]
                        // 对应位置没有数据,那么就原样输出占位符
                        if (arg === undefined) {
                            strList.push(item)
                            return
                        }
                        let newStr = ''
                        switch (placeholder) {
                            // 字符串,此处为简单处理,实际和chrome控制台的输出有差异
                            case '%s':
                                newStr = String(arg) + item.slice(2)
                                break;
                                // 整数
                            case '%d':
                            case '%i':
                                newStr = (type(arg) === 'number' ? parseInt(arg) : 'NaN') + item.slice(2)
                                break;
                                // 浮点数
                            case '%f':
                                newStr = (type(arg) === 'number' ? arg : 'NaN') + item.slice(2)
                                break;
                                // 样式
                            case '%c':
                                newStr = `<span style="${arg}">${item.slice(2)}</span>`
                                break;
                            default:
                                break;
                        }
                        strList.push(newStr)
                    })
                    contents = strList
                    // 超出占位数量的剩余参数也不能丢弃,需要展示
                    if (sliceArgs.length > match.length) {
                        contents = contents.concat(sliceArgs.slice(match.length))   
                    }
                }
            }
        }
        // 处理方法 ...
        switch (method) {}
}
</code></pre> 
 <p>效果如下:</p> 
 <div class="image-package"> 
  <div class="image-container" style="max-width: 700px; max-height: 506px;"> 
   <div class="image-view">  
   </div> 
  </div> 
  <div class="image-caption">
    image-20210512140705004.png 
  </div> 
 </div> 
 <h3>报错信息</h3> 
 <p>报错信息上文已经涉及到了,我们对<code>js</code>代码使用<code>try catch</code>进行了包裹,并使用<code>console.error</code>进行错误输出,但是还有些错误可能是<code>try catch</code>监听不到的,比如定时器代码执行出错,或者是没有被显式捕获的<code>Promise</code>异常,我们也需要加上对应的监听及显示。</p> 
 <pre><code class="js">// /public/console/index.js

// 错误监听
window.onerror = function (message, source, lineno, colno, error) {
    window.parent.postMessage({
        type: 'console',
        method: 'string',
        data: [message, source, lineno, colno, error].map((item) => {
            return handleData(item)
        })
    })
}
window.addEventListener('unhandledrejection', err => {
    window.parent.postMessage({
        type: 'console',
        method: 'string',
        data: [handleData(err.reason.stack)]
    })
})

// ...
</code></pre> 
 <h3>执行输入的js</h3> 
 <p><code>console</code>的最后一个功能是可以输入<code>js</code>代码然后动态执行,这个可以使用<code>eval</code>方法,<code>eval</code>能动态执行<code>js</code>代码并返回最后一个表达式的值,<code>eval</code>会带来一些安全风险,但是笔者没有找到更好的替代方案,知道的朋友请在下方留言一起探讨吧。</p> 
 <p>动态执行的代码里的输出以及最后表达式的值我们也要显示到控制台里,为了不在上层拦截<code>console</code>,我们把动态执行代码的功能交给预览的<code>iframe</code>,执行完后再把最后的表达式的值使用<code>console</code>打印一下,这样所有的输出都能显示到控制台。</p> 
 <pre><code class="html"><textarea v-model="jsInput" @keydown.enter="implementJs"></textarea>
</code></pre> 
 <pre><code class="js">const jsInput = ref('')
const implementJs = (e) => {
    // shift+enter为换行,不需要执行
    if (e.shiftKey) {
        return
    }
    e.preventDefault()
    let code = jsInput.value.trim()
    if (code) {
        // 给iframe发送信息
        iframeRef.value.contentWindow.postMessage({
            type: 'command',
            data: code
        })
        jsInput.value = ''
    }
}
</code></pre> 
 <pre><code class="js">// /public/console/index.js

// 接收代码执行的事件
const onMessage = ({ data = {} }) => {
    if (data.type === 'command') {
        try {
            // 打印一下要执行的代码
            console.log(data.data)
            // 使用eval执行代码
            console.log(eval(data.data))
        } catch (error) {
            console.error('js执行出错')
            console.error(error)
        }
    }
}
window.addEventListener('message', onMessage)
</code></pre> 
 <p>效果如下:</p> 
 <div class="image-package"> 
  <div class="image-container" style="max-width: 394px; max-height: 194px;"> 
   <div class="image-view">  
   </div> 
  </div> 
  <div class="image-caption">
    2021-05-12-18-31-12.gif 
  </div> 
 </div> 
 <h1>支持预处理器</h1> 
 <p>除了基本的<code>html</code>、<code>js</code>和<code>css</code>,作为一个强大的工具,我们有必要支持一下常用的预处理器,比如<code>html</code>的<code>pug</code>,<code>js</code>的<code>TypeScript</code>及<code>css</code>的<code>less</code>等,实现思路相当简单,加载对应预处理器的转换器,然后转换一下即可。</p> 
 <h2>动态切换编辑器语言</h2> 
 <p><code>Monaco Editor</code>想要动态修改语言的话我们需要换一种方式来设置文档,上文我们是创建编辑器的同时直接把语言通过<code>language</code>选项传递进去的,然后使用<code>setValue</code>来设置文档内容,这样后期无法再动态修改语言,我们修改为切换文档模型的方式:</p> 
 <pre><code class="js">// 创建编辑器
editor = monaco.editor.create(editorEl.value, {
    minimap: {
        enabled: false, // 关闭小地图
    },
    wordWrap: 'on', // 代码超出换行
    theme: 'vs-dark', // 主题
    fontSize: 18,
    fontFamily: 'MonoLisa, monospace',
})
// 更新编辑器文档模型 
const updateDoc = (code, language) => {
  if (!editor) {
    return
  }
  // 获取当前的文档模型
  let oldModel = editor.getModel()
  // 创建一个新的文档模型
  let newModel = monaco.editor.createModel(code, language)
  // 设置成新的
  editor.setModel(newModel)
  // 销毁旧的模型
  if (oldModel) {
    oldModel.dispose()
  }
}
</code></pre> 
 <h2>加载转换器</h2> 
 <p>转换器的文件我们都放在<code>/public/parses/</code>文件夹下,然后进行动态加载,即选择了某个预处理器后再去加载对应的转换器资源,这样可以节省不必要的请求。</p> 
 <p>异步加载<code>js</code>我们使用loadjs这个小巧的库,新增一个<code>load.js</code>:</p> 
 <pre><code class="js">// 记录加载状态
const preprocessorLoaded = {
    html: true,
    javascript: true,
    css: true,
    less: false,
    scss: false,
    sass: false,
    stylus: false,
    postcss: false,
    pug: false,
    babel: false,
    typescript: false
}

// 某个转换器需要加载多个文件
const resources = {
    postcss: ['postcss-cssnext', 'postcss']
}

// 异步加载转换器的js资源
export const load = (preprocessorList) => {
    // 过滤出没有加载过的资源
    let notLoaded = preprocessorList.filter((item) => {
        return !preprocessorLoaded[item]
    })
    if (notLoaded.length <= 0) {
        return
    }
    return new Promise((resolve, reject) => {
        // 生成加载资源的路径
        let jsList = []
        notLoaded.forEach((item) => {
            let _resources = (resources[item] || [item]).map((r) => {
                return `/parses/${r}.js`
            })
            jsList.push(..._resources)
        })
        loadjs(jsList, {
            returnPromise: true
        }).then(() => {
            notLoaded.forEach((item) => {
                preprocessorLoaded[item] = true
            })
            resolve()
        }).catch((err) => {
            reject(err)
        })
    })
}
</code></pre> 
 <p>然后修改一下上文预览部分的<code>run</code>方法:</p> 
 <pre><code class="js">const run = async () => {
  let h = editData.value.code.HTML.language
  let j = editData.value.code.JS.language
  let c = editData.value.code.CSS.language
  await load([h, j, c])
  // ...
}
</code></pre> 
 <h2>转换</h2> 
 <p>所有代码都使用转换器转换一下,因为有的转换器是同步方式的,有的是异步方式的,所以我们统一使用异步来处理,修改一下<code>run</code>方法:</p> 
 <pre><code class="js">const run = async () => {
  // ...
  await load([h, j, c])
  let htmlTransform = transform.html(h, editData.value.code.HTML.content)
  let jsTransform = transform.js(j, editData.value.code.JS.content)
  let cssTransform = transform.css(c, editData.value.code.CSS.content)
  Promise.all([htmlTransform, jsTransform, cssTransform])
    .then(([htmlStr, jsStr, cssStr]) => {
      // ...
    })
    .catch((error) => {
      // ...
    })
}
</code></pre> 
 <p>接下来就是最后的转换操作,下面只展示部分代码,完整代码有兴趣的可查看源码:</p> 
 <pre><code class="js">// transform.js

const html = (preprocessor, code) => {
    return new Promise((resolve, reject) => {
        switch (preprocessor) {
            case 'html':
                // html的话原封不动的返回
                resolve(code)
                break;
            case 'pug':
                // 调用pug的api来进行转换
                resolve(window.pug.render(code))
            default:
                resolve('')
                break;
        }
    })
}

const js = (preprocessor, code) => {
    return new Promise((resolve, reject) => {
        let _code = ''
        switch (preprocessor) {
            case 'javascript':
                resolve(code)
                break;
            case 'babel':
                // 调用babel的api来编译,你可以根据需要设置presets
                _code = window.Babel.transform(code, {
                    presets: [
                        'es2015',
                        'es2016',
                        'es2017',
                        'react'
                    ]
                }).code
                resolve(_code)
            default:
                resolve('')
                break;
        }
    })
}

const css = (preprocessor, code) => {
    return new Promise((resolve, reject) => {
        switch (preprocessor) {
            case 'css':
                resolve(code)
                break;
            case 'less':
                window.less.render(code)
                    .then(
                        (output) => {
                            resolve(output.css)
                        },
                        (error) => {
                            reject(error)
                        }
                    );
                break;
            default:
                resolve('')
                break;
        }
    })
}
</code></pre> 
 <p>可以看到很简单,就是调一下相关转换器的<code>api</code>来转换一下,不过想要找到这些转换器的浏览器使用版本和<code>api</code>可太难了,笔者基本都没找到,所以这里的大部分代码都是参考codepan的。</p> 
 <h1>其他功能</h1> 
 <p>另外还有一些实现起来简单,但是能很大提升用户体验的功能,比如添加额外的<code>css</code>或<code>js</code>资源,免去手写<code>link</code>或<code>script</code>标签的麻烦:</p> 
 <div class="image-package"> 
  <div class="image-container" style="max-width: 700px; max-height: 312px;"> 
   <div class="image-view">  
   </div> 
  </div> 
  <div class="image-caption">
    image-20210514140452547.png 
  </div> 
 </div> 
 <p>预设一些常用模板,比如<code>vue3</code>、<code>react</code>等,方便快速开始,免去写基本结构的麻烦:</p> 
 <div class="image-package"> 
  <div class="image-container" style="max-width: 700px; max-height: 660px;"> 
   <div class="image-view">  
   </div> 
  </div> 
  <div class="image-caption">
    2021-05-14-14-37-28.gif 
  </div> 
 </div> 
 <h1>有没有更快的方法</h1> 
 <p>如果你看到这里,你一定会说这是哪门子快速搭建,那有没有更快的方法呢,当然有了,就是直接克隆本项目的仓库或者codepan,改改就可以使用啦~</p> 
 <h1>结尾</h1> 
 <p>本文从零开始介绍了如何搭建一个代码在线编辑预览的工具,粗糙实现总有不足之处,欢迎指出。</p> 
 <p>项目仓库code-run,欢迎<code>star</code>。</p> 
</article>
                            </div>
                        </div>
                    </div>
                    <!--PC和WAP自适应版-->
                    <div id="SOHUCS" sid="1643666063432409088"></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">你可能感兴趣的:(快速搭建一个代码在线编辑预览工具)</h4>
        <div id="paradigm-article-related">
            <div class="recommend-post mb30">
                <ul class="widget-links">
                    <li><a href="/article/1904121863366307840.htm"
                           title="Linux_C编程—信号处理函数的返回" target="_blank">Linux_C编程—信号处理函数的返回</a>
                        <span class="text-muted">Aspirant-GQ</span>
<a class="tag" taget="_blank" href="/search/Linux/1.htm">Linux</a><a class="tag" taget="_blank" href="/search/Linux/1.htm">Linux</a><a class="tag" taget="_blank" href="/search/%E4%BF%A1%E5%8F%B7%E5%A4%84%E7%90%86%E5%87%BD%E6%95%B0/1.htm">信号处理函数</a><a class="tag" taget="_blank" href="/search/%E4%BF%A1%E5%8F%B7%E5%A4%84%E7%90%86%E8%BF%94%E5%9B%9E/1.htm">信号处理返回</a>
                        <div>文章目录信号处理函数执行完怎么办setjmp()/longjmp()sigsetjmp()/siglongjmp()信号处理函数执行完怎么办一般来讲,信号处理函数执行完毕都会正常返回,也可以调用其他函数返回到程序的主函数中。这里总结一下信号处理函数执行完毕后跳转到主函数中指定的位置的操作(有点类似goto语句,但goto不支持函数间的跳转,只能在一个函数中跳转),主要是通过俩对函数来实现的:set</div>
                    </li>
                    <li><a href="/article/1904121864049979392.htm"
                           title="WPF 属性值设置优先级详解" target="_blank">WPF 属性值设置优先级详解</a>
                        <span class="text-muted">她说彩礼65万</span>
<a class="tag" taget="_blank" href="/search/WPF/1.htm">WPF</a><a class="tag" taget="_blank" href="/search/wpf/1.htm">wpf</a>
                        <div>在WPF中,依赖属性(DependencyProperty)的值可以通过多种方式设置,每种方式都有其特定的优先级。理解这些优先级对于正确地管理和预期控件的行为至关重要。以下是WPF中依赖属性值的优先级列表,从高到低排列:1.属性系统强制值这包括动画正在运行时的值、强制值等。动画是改变属性值的一种强大方式,当一个属性正在被动画影响时,动画设定的值将具有最高优先级。2.本地值直接在控件上设置的值,例如</div>
                    </li>
                    <li><a href="/article/1904121358862839808.htm"
                           title="HTTP Cookie header 中set-cookie格式" target="_blank">HTTP Cookie header 中set-cookie格式</a>
                        <span class="text-muted">qq_35577990</span>
<a class="tag" taget="_blank" href="/search/fiddler/1.htm">fiddler</a><a class="tag" taget="_blank" href="/search/selenium/1.htm">selenium</a>
                        <div>Cookie相关的Http头有两个Http头部和Cookie有关:Set-Cookie和Cookie。Set-Cookie由服务器发送,它包含在响应请求的头部中。它用于在客户端创建一个CookieCookie头由客户端发送,包含在HTTP请求的头部中。注意,只有cookie的domain和path与请求的URL匹配才会发送这个cookie。Set-CookieHeaderSet-Cookie响应头</div>
                    </li>
                    <li><a href="/article/1904121106671923200.htm"
                           title="JavaScript闭包+函数内部的this指向" target="_blank">JavaScript闭包+函数内部的this指向</a>
                        <span class="text-muted">落日九号</span>
<a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a>
                        <div>关于闭包,  什么是闭包?  闭包就是能够读取其他函数内部变量的函数。如果我们把闭包改称做闭包函数这样理解起来可能更容易一些。闭包就是能够读取其他函数内部变量的函数。例如在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。真正的定义闭包————英文连接闭包————中文连接Aclosure</div>
                    </li>
                    <li><a href="/article/1904120224785952768.htm"
                           title="如何使用API接口对接电商系统?" target="_blank">如何使用API接口对接电商系统?</a>
                        <span class="text-muted">API小爬虫</span>
<a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/%E7%88%AC%E8%99%AB/1.htm">爬虫</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a>
                        <div>在当今的电商时代,API接口成为了不同系统之间数据交互的重要桥梁。无论是大型电商平台还是小型电商创业公司,通过API接口实现数据的无缝对接,可以大大提高运营效率,优化用户体验。本文将详细介绍如何使用API接口对接电商系统,并提供具体的代码示例。一、了解API对接的基本概念1.1什么是API?API(应用程序编程接口)是一套预定义的规则和协议,用于构建和交互软件应用程序。通过API,不同的应用程序可</div>
                    </li>
                    <li><a href="/article/1904119215086956544.htm"
                           title="RocketMQ新消费者加入后的队列一致性保障机制详解" target="_blank">RocketMQ新消费者加入后的队列一致性保障机制详解</a>
                        <span class="text-muted">慢德</span>
<a class="tag" taget="_blank" href="/search/%E5%88%86%E5%B8%83%E5%BC%8F%E8%AE%BE%E8%AE%A1/1.htm">分布式设计</a><a class="tag" taget="_blank" href="/search/rocketmq/1.htm">rocketmq</a>
                        <div>RocketMQ新消费者加入后的队列一致性保障机制详解RocketMQ作为一个高性能的分布式消息中间件,其消费者负载均衡机制是保障系统可扩展性和稳定性的关键。当新消费者加入消费组时,如何保证各个消费者之间的队列分配一致性是一个核心问题。下面将深入解析其详细原理和运作机制。消费模式与队列分配基础首先需要明确的是,在RocketMQ中,队列一致性问题主要出现在集群消费模式下。在这种模式中,一条消息只会</div>
                    </li>
                    <li><a href="/article/1904118458086387712.htm"
                           title="旧衣回收小程序开发,企业的双赢选择" target="_blank">旧衣回收小程序开发,企业的双赢选择</a>
                        <span class="text-muted">冠品网络科技</span>
<a class="tag" taget="_blank" href="/search/%E5%B0%8F%E7%A8%8B%E5%BA%8F%E5%BC%80%E5%8F%91/1.htm">小程序开发</a><a class="tag" taget="_blank" href="/search/%E8%BD%AF%E4%BB%B6%E5%BC%80%E5%8F%91/1.htm">软件开发</a><a class="tag" taget="_blank" href="/search/%E6%97%A7%E8%A1%A3%E5%9B%9E%E6%94%B6/1.htm">旧衣回收</a><a class="tag" taget="_blank" href="/search/%E5%9B%9E%E6%94%B6%E5%B0%8F%E7%A8%8B%E5%BA%8F/1.htm">回收小程序</a>
                        <div>随着全球环保意识的提升和可持续发展理念的普及,越来越多的人开始关注衣物的可持续利用,旧衣回收市场逐渐成为一个备受关注的领域。旧衣回收不仅有助于减少资源浪费和环境污染,还为企业和商家带来了新的商业机会。目前,在数字化的趋势下,旧衣回收小程序作为便捷的回收方式,正在成为企业布局和人们回收的重要工具。1、解决传统回收模式的痛点传统的旧衣回收模式存在众多局限问题,例如回收点分散、回收流程繁琐、用户参与度低</div>
                    </li>
                    <li><a href="/article/1904116440710049792.htm"
                           title="Charles抓包神器全方位指南-从设置到会话捕获" target="_blank">Charles抓包神器全方位指南-从设置到会话捕获</a>
                        <span class="text-muted">2501_91093988</span>
<a class="tag" taget="_blank" href="/search/http/1.htm">http</a><a class="tag" taget="_blank" href="/search/udp/1.htm">udp</a><a class="tag" taget="_blank" href="/search/https/1.htm">https</a><a class="tag" taget="_blank" href="/search/websocket/1.htm">websocket</a><a class="tag" taget="_blank" href="/search/%E7%BD%91%E7%BB%9C%E5%AE%89%E5%85%A8/1.htm">网络安全</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/tcp%2Fip/1.htm">tcp/ip</a>
                        <div>如何设置显示Request和Response大家好,我是watchpoints。别想太多,只管提问,所有问题,都会有答案。watchpoints既是我的GitHub用户名,也是我的微信用户名。如果我对某些内容的解释不够清楚,欢迎大家随时提问。现在,让我们来解答一个常见的问题:如何设置显示Request和Response?这个问题的答案其实就在Charles的设置中。无论是从官网下载的Charles</div>
                    </li>
                    <li><a href="/article/1904115810855612416.htm"
                           title="第十三届蓝桥杯大赛软件赛省赛 C/C++ 大学 B 组C题 刷题统计" target="_blank">第十三届蓝桥杯大赛软件赛省赛 C/C++ 大学 B 组C题 刷题统计</a>
                        <span class="text-muted">我是小趴菜一枚</span>
<a class="tag" taget="_blank" href="/search/%E7%AE%97%E6%B3%95/1.htm">算法</a><a class="tag" taget="_blank" href="/search/%E8%93%9D%E6%A1%A5%E6%9D%AF/1.htm">蓝桥杯</a><a class="tag" taget="_blank" href="/search/c%2B%2B/1.htm">c++</a><a class="tag" taget="_blank" href="/search/c%E8%AF%AD%E8%A8%80/1.htm">c语言</a>
                        <div>问题描述小明决定从下周一开始努力刷题准备蓝桥杯竞赛。他计划周一至周五每天做aa道题目,周六和周日每天做bb道题目。请你帮小明计算,按照计划他将在第几天实现做题数大于等于nn题?输入格式输入一行包含三个整数a,ba,b和nn.输出格式输出一个整数代表天数。样例输入102099样例输出8评测用例规模与约定对于50%50%的评测用例,1≤a,b,n≤1061≤a,b,n≤106.对于100%100%的评</div>
                    </li>
                    <li><a href="/article/1904115558949908480.htm"
                           title="普通人怎么利用AI赚钱?AI 变现的 8 种神操作,最后一个你绝对想不到!" target="_blank">普通人怎么利用AI赚钱?AI 变现的 8 种神操作,最后一个你绝对想不到!</a>
                        <span class="text-muted">AI设计酷卡</span>
<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/stable/1.htm">stable</a><a class="tag" taget="_blank" href="/search/diffusion/1.htm">diffusion</a><a class="tag" taget="_blank" href="/search/AI%E4%BD%9C%E7%94%BB/1.htm">AI作画</a><a class="tag" taget="_blank" href="/search/AIGC/1.htm">AIGC</a><a class="tag" taget="_blank" href="/search/midjourney/1.htm">midjourney</a>
                        <div>在国内外,几百款AI工具竞争激烈,衍生出各种需求与市场。下面我们就来盘点AI变现的八大生意,看看你能猜到几个?一、AI文本生成:打造公众号矩阵提到AI,ChatGPT无疑是最为知名的工具之一,其核心功能在于生成高质量文本,写出热门文章。许多人利用AI文本生成的能力,成功构建公众号矩阵,创造出大量10w+的文章,甚至有流量主月入过万。今年上半年,一些知名账号每分钟发布数篇文章,依靠AI技术和自动化手</div>
                    </li>
                    <li><a href="/article/1904115054903619584.htm"
                           title="服务器上部署springboot项目学习笔记" target="_blank">服务器上部署springboot项目学习笔记</a>
                        <span class="text-muted">Warren98</span>
<a class="tag" taget="_blank" href="/search/%E6%9C%8D%E5%8A%A1%E5%99%A8/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><a class="tag" taget="_blank" href="/search/%E5%AD%A6%E4%B9%A0/1.htm">学习</a><a class="tag" taget="_blank" href="/search/%E5%90%8E%E7%AB%AF/1.htm">后端</a><a class="tag" taget="_blank" href="/search/%E9%98%BF%E9%87%8C%E4%BA%91/1.htm">阿里云</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a>
                        <div>Java相关命令运行jar包:在linux中,进入到jar包所在目录后,直接tab补全名称即可java-jarjar包名称查看jar包是否在运行:ps-ef|grepjava终止运行的jar包:kill#是jar包的id根据jar包名称查看运行状态psaux|grepMyBlog-0.0.1-SNAPSHOT.jar设置jar包一直运行每次启动jar包时,都需要打开SSH远程连接工具,比如fina</div>
                    </li>
                    <li><a href="/article/1904114928248221696.htm"
                           title="Charles 抓包工具使用指南:设置、功能详解与最佳实践" target="_blank">Charles 抓包工具使用指南:设置、功能详解与最佳实践</a>
                        <span class="text-muted">技术博主狂热者</span>
<a class="tag" taget="_blank" href="/search/http/1.htm">http</a><a class="tag" taget="_blank" href="/search/udp/1.htm">udp</a><a class="tag" taget="_blank" href="/search/https/1.htm">https</a><a class="tag" taget="_blank" href="/search/websocket/1.htm">websocket</a><a class="tag" taget="_blank" href="/search/%E7%BD%91%E7%BB%9C%E5%AE%89%E5%85%A8/1.htm">网络安全</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/tcp%2Fip/1.htm">tcp/ip</a>
                        <div>引言前段时间入职了一家公司,项目中的代码注释比较少,而且代码量大,比较难以理解每个接口的数据情况。为了分析接口的行为,我们需要安装项目测试环境包,并通过抓包来查看请求参数和header,借此来理解代码逻辑。我选择了使用Charles配合模拟器进行抓包调试。今天我来总结一下Charles的用法以及结合模拟器的简单使用。Charles与SniffmasterCharles是一款强大的抓包调试工具,相信</div>
                    </li>
                    <li><a href="/article/1904114675465908224.htm"
                           title="Python 爬虫实战:汽车电商平台价格波动监控与市场趋势洞察" target="_blank">Python 爬虫实战:汽车电商平台价格波动监控与市场趋势洞察</a>
                        <span class="text-muted">西攻城狮北</span>
<a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/%E7%88%AC%E8%99%AB/1.htm">爬虫</a><a class="tag" taget="_blank" href="/search/%E6%B1%BD%E8%BD%A6/1.htm">汽车</a><a class="tag" taget="_blank" href="/search/%E5%AE%9E%E6%88%98%E6%A1%88%E4%BE%8B/1.htm">实战案例</a>
                        <div>目录一、环境准备与依赖安装二、目标网站分析1.网站页面结构分析2.数据爬取策略三、代码实现1.数据抓取模块(1)爬取车型列表(2)爬取车型详情(3)主爬取函数2.数据存储模块3.数据分析模块四、完整工作流程(1)初始化爬虫(2)执行爬虫(3)数据存储(4)数据分析五、注意事项六、扩展功能在当今数字化时代,汽车电商平台为消费者提供了便捷的购车渠道。通过Python爬虫技术,我们可以监控汽车电商平台的</div>
                    </li>
                    <li><a href="/article/1904114546834993152.htm"
                           title="HTTP长连接与短连接的前世今生" target="_blank">HTTP长连接与短连接的前世今生</a>
                        <span class="text-muted">慢德</span>
<a class="tag" taget="_blank" href="/search/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/1.htm">计算机基础</a><a class="tag" taget="_blank" href="/search/https/1.htm">https</a><a class="tag" taget="_blank" href="/search/%E7%BD%91%E7%BB%9C/1.htm">网络</a>
                        <div>HTTP长连接与短连接的前世今生大家好!作为一名在互联网摸爬滚打多年的开发者,今天想跟大家聊聊HTTP中的长连接和短连接这个话题。记得我刚入行时,对这些概念一头雾水,希望这篇文章能帮助新入行的朋友少走些弯路。什么是HTTP连接?在谈长短连接之前,我们先搞清楚HTTP连接是什么。简单来说,当你的浏览器访问一个网站时,你的电脑和服务器之间需要建立一条通信的"管道",这就是HTTP连接。通过这个管道,你</div>
                    </li>
                    <li><a href="/article/1904112656948719616.htm"
                           title="Python实现微博关键词爬虫" target="_blank">Python实现微博关键词爬虫</a>
                        <span class="text-muted">才华是浅浅的耐心</span>
<a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/%E6%96%B0%E6%B5%AA%E5%BE%AE%E5%8D%9A/1.htm">新浪微博</a><a class="tag" taget="_blank" href="/search/%E7%88%AC%E8%99%AB/1.htm">爬虫</a>
                        <div>1.背景介绍随着社交媒体的广泛应用,微博上的海量数据成为了很多研究和分析的重要信息源。为了方便获取微博的相关内容,本文将介绍如何使用Python编写一个简单的爬虫脚本,从微博中抓取指定关键词的相关数据,并将这些数据保存为Excel文件。本文将以关键词“樊振东”为例,展示从微博抓取该关键词相关数据的全过程。废话不多说,先上结果图。2.项目实现思路该爬虫通过向微博的搜索接口发送HTTP请求,获取与指定</div>
                    </li>
                    <li><a href="/article/1904112530561757184.htm"
                           title="使用 Python 实现批量发送电子邮件" target="_blank">使用 Python 实现批量发送电子邮件</a>
                        <span class="text-muted">才华是浅浅的耐心</span>
<a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/%E7%88%AC%E8%99%AB/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>引言:在日常工作中,我们可能会遇到需要批量发送邮件的场景,例如通知、营销邮件或测试邮件。如果手动发送,不仅效率低下,还容易出错。今天,我将分享一个使用Python实现的自动化邮件发送脚本,通过读取Excel文件中的发件人和收件人信息,轻松完成批量邮件发送任务。功能概述这个脚本的主要功能包括:从Excel文件中读取发件人信息(邮箱和授权码)和收件人信息(邮箱)。根据发件人邮箱的域名,自动匹配SMTP</div>
                    </li>
                    <li><a href="/article/1904112278341480448.htm"
                           title="Trae使用教程,帮助您快速上手这款编程神器。" target="_blank">Trae使用教程,帮助您快速上手这款编程神器。</a>
                        <span class="text-muted">云上的阿七</span>
<a class="tag" taget="_blank" href="/search/%E4%BA%91%E8%AE%A1%E7%AE%97/1.htm">云计算</a>
                        <div>Trae是一款由字节跳动推出的AI驱动集成开发环境(IDE),旨在通过智能代码补全、多模态交互以及对整个代码库的上下文分析等功能,帮助开发者更高效地编写代码。其强大的AI能力能够理解开发者的需求并提供精准的代码生成和修改建议。目前,Trae提供免费版本,集成了Claude-3.5-Sonnet和GPT-4o等主流大模型。rae使用教程,帮助您快速上手这款编程神器。一、安装Trae访问官网:前往Tr</div>
                    </li>
                    <li><a href="/article/1904112151262457856.htm"
                           title="从头开始学C语言第三十二天——函数" target="_blank">从头开始学C语言第三十二天——函数</a>
                        <span class="text-muted">神阶平天牛魔王</span>
<a class="tag" taget="_blank" href="/search/c%E8%AF%AD%E8%A8%80/1.htm">c语言</a>
                        <div>函数可以定义为完成特定功能的模块,函数程序代码独立,通常要求要有返回值,也就是return,也可以返回空值0主要函数分为三类:主函数也就是main函数库函数,包括用过的scanf,printf,strlen,strcpy等包含在stdio.h,string.h等库中自定义函数,程序员自己定义的函数模块一般形式:(){语句序列;return[()];}数据类型是整个函数返回值的类型return语句表</div>
                    </li>
                    <li><a href="/article/1904112024804192256.htm"
                           title="【面试题】数据结构高频面试题" target="_blank">【面试题】数据结构高频面试题</a>
                        <span class="text-muted">城仕</span>
<a class="tag" taget="_blank" href="/search/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/1.htm">数据结构</a><a class="tag" taget="_blank" href="/search/%E9%9D%A2%E8%AF%95%E9%A2%98/1.htm">面试题</a><a class="tag" taget="_blank" href="/search/%E9%9D%A2%E8%AF%95/1.htm">面试</a>
                        <div>1.简述什么是数据结构?数据结构是计算机存储、组织数据的方式,它使得我们可以有效地访问和修改数据。简单来说,数据结构就像是一个容器,这个容器可以以不同的方式(如线性的、树形的、表格的等)组织数据,以便于数据的查找、添加、删除和其他操作。例如,想象一下你有一本书。如果这本书没有目录、没有章节划分,你想找到某个特定的信息可能会非常困难,因为你必须一页一页地翻阅。这本书就像是一个没有组织的数据结构。现在</div>
                    </li>
                    <li><a href="/article/1904111520212643840.htm"
                           title="shell逐行读取文件 & 远程操作服务器" target="_blank">shell逐行读取文件 & 远程操作服务器</a>
                        <span class="text-muted">二进制杯莫停</span>
<a class="tag" taget="_blank" href="/search/%23/1.htm">#</a><a class="tag" taget="_blank" href="/search/Shell%E7%BC%96%E7%A8%8B/1.htm">Shell编程</a><a class="tag" taget="_blank" href="/search/%E6%9C%8D%E5%8A%A1%E5%99%A8/1.htm">服务器</a><a class="tag" taget="_blank" href="/search/%E8%BF%90%E7%BB%B4/1.htm">运维</a>
                        <div>代码示例whilereadip;doecho"uninstallingtestprogramsin$line"sshroot@$ip'bash-s'<remote_remove_tool.shdone<installed_ips总结✅作用:逐行读取installed_ips文件中的IP地址通过SSH连接到远程服务器,执行remote_remove_tool.sh脚本用于批量卸载多个服务器上的测试程</div>
                    </li>
                    <li><a href="/article/1904109503121518592.htm"
                           title="Python Tkinter库实战(用Entry和button控件做一个小型的浏览器)" target="_blank">Python Tkinter库实战(用Entry和button控件做一个小型的浏览器)</a>
                        <span class="text-muted">IT界小菜鸡</span>
<a class="tag" taget="_blank" href="/search/%E7%AC%94%E8%AE%B0/1.htm">笔记</a><a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a>
                        <div>大家好,上一期我们大概了解了一下PythonTkinter库。这是一个方便快捷的GUI库;可以用短短几行代码生成出一个用户图形化接口的窗口。算是非常方便。既然前一期我们了解了tk库。那么我们今天就来做一个实战。今天这个实战项目源自于我一个奇奇怪怪的想法。当时打开浏览器的时候想着,既然我打开浏览器输入网址,搜索URL。既然别人可以,那我为什么不可以自己做一个呢?抱着这个想法,我就开始了这个实验。废话</div>
                    </li>
                    <li><a href="/article/1904109250850910208.htm"
                           title="群体智能优化算法-模拟退火优化算法(Simulated Annealing, SA,含Matlab源代码)" target="_blank">群体智能优化算法-模拟退火优化算法(Simulated Annealing, SA,含Matlab源代码)</a>
                        <span class="text-muted">HR Zhou</span>
<a class="tag" taget="_blank" href="/search/%E7%AE%97%E6%B3%95/1.htm">算法</a><a class="tag" taget="_blank" href="/search/%E6%A8%A1%E6%8B%9F%E9%80%80%E7%81%AB%E7%AE%97%E6%B3%95/1.htm">模拟退火算法</a><a class="tag" taget="_blank" href="/search/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/1.htm">机器学习</a><a class="tag" taget="_blank" href="/search/matlab/1.htm">matlab</a><a class="tag" taget="_blank" href="/search/%E7%BE%A4%E4%BD%93%E6%99%BA%E8%83%BD%E4%BC%98%E5%8C%96/1.htm">群体智能优化</a><a class="tag" taget="_blank" href="/search/%E4%BC%98%E5%8C%96/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>
                        <div>摘要模拟退火(SA)算法是一种基于物理退火过程的全局优化算法,其核心思想来源于热力学中的退火过程:将材料加热到高温后再缓慢冷却,使其分子结构趋于最低能量状态,从而获得稳定结构。SA算法利用Metropolis准则来决定接受新的解,以一定概率接受劣解,从而避免陷入局部最优。SA具有收敛速度快、计算复杂度低、适用于连续优化问题等特点,被广泛应用于组合优化、函数优化、神经网络训练等领域。算法介绍1.主要</div>
                    </li>
                    <li><a href="/article/1904108872721821696.htm"
                           title="珍藏!Java SpringBoot 精品源码合集约惠来袭,获取路径大公开" target="_blank">珍藏!Java SpringBoot 精品源码合集约惠来袭,获取路径大公开</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/spring/1.htm">spring</a><a class="tag" taget="_blank" href="/search/boot/1.htm">boot</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a>
                        <div>技术范围:SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数据、物联网、机器学习等设计与开发。主要内容:免费功能设计、开题报告、任务书、中期检查PPT、系统功能实现、代码编写、论文编写和辅导、论文降重、长期答辩答疑辅导、腾讯会议一对一专业讲解辅导答辩、模拟答辩演练、和理解代码逻辑思路。文末获取源码联系文末获取源码联</div>
                    </li>
                    <li><a href="/article/1904107990714216448.htm"
                           title="深入解析 Java 递归:构建层级树形结构的优雅实现!!!" target="_blank">深入解析 Java 递归:构建层级树形结构的优雅实现!!!</a>
                        <span class="text-muted">小丁学Java</span>
<a class="tag" taget="_blank" href="/search/%E4%BA%A7%E5%93%81%E8%B5%84%E8%B4%A8%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/1.htm">产品资质管理系统</a><a class="tag" taget="_blank" href="/search/Java%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E7%AE%97%E6%B3%95/1.htm">Java数据结构和算法</a><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><a class="tag" taget="_blank" href="/search/Java/1.htm">Java</a>
                        <div>深入解析Java递归:构建层级树形结构的优雅实现大家好!今天我们来聊聊Java中一个非常常见的操作:通过递归构建层级树形结构。具体来说,我们将深入分析以下代码片段://递归构建子树for(InviteCodechild:children){InviteCodeTreeDTOchildNode=buildTree(child,inviteCodeMap);node.getChildren().add</div>
                    </li>
                    <li><a href="/article/1904107738380693504.htm"
                           title="RAMS(区域大气建模系统)与 OpenFOAM 的耦合:构建跨尺度大气流动模拟平台" target="_blank">RAMS(区域大气建模系统)与 OpenFOAM 的耦合:构建跨尺度大气流动模拟平台</a>
                        <span class="text-muted">Hardess-god</span>
<a class="tag" taget="_blank" href="/search/RAMS/1.htm">RAMS</a><a class="tag" taget="_blank" href="/search/%E7%AE%97%E6%B3%95/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/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/1.htm">机器学习</a>
                        <div>随着城市气象、风能开发和空气质量模拟需求的提升,单一尺度的模拟工具已难以满足复杂地形和城市结构下的精细气流场重建需求。RegionalAtmosphericModelingSystem(RAMS)作为区域尺度大气模式,在捕捉天气系统和地形强迫方面表现优异;而OpenFOAM则是功能强大的开源计算流体力学(CFD)平台,能够实现亚米级的湍流建模和局地流场分辨。将两者耦合,实现区域与城市尺度的联动模拟</div>
                    </li>
                    <li><a href="/article/1904105972205416448.htm"
                           title="用`ode23`和`ode45`函数求解一个常微分方程并展示结果" target="_blank">用`ode23`和`ode45`函数求解一个常微分方程并展示结果</a>
                        <span class="text-muted">神经网络15044</span>
<a class="tag" taget="_blank" href="/search/matlab/1.htm">matlab</a><a class="tag" taget="_blank" href="/search/%E7%AE%97%E6%B3%95/1.htm">算法</a><a class="tag" taget="_blank" href="/search/%E7%AE%97%E6%B3%95/1.htm">算法</a><a class="tag" taget="_blank" href="/search/%E5%AD%A6%E4%B9%A0/1.htm">学习</a>
                        <div>使用Matlab中的ode23,ode45函数求解方程,并展示结果。我将使用ode23和ode45函数求解一个常微分方程并展示结果。这里以一个简单的一阶常微分方程为例:dydt=−2y\frac{dy}{dt}=-2ydtdy=−2y,初始条件为y(0)=1y(0)=1y(0)=1。以下是求解该方程的Matlab代码:%定义微分方程dydt=@(t,y)-2*y;%初始条件y0=1;%时间范围ts</div>
                    </li>
                    <li><a href="/article/1904105468406591488.htm"
                           title="python调用DeepSeek的API" target="_blank">python调用DeepSeek的API</a>
                        <span class="text-muted">garfield_sun06</span>
<a class="tag" taget="_blank" href="/search/%E5%A4%A7%E6%A8%A1%E5%9E%8B/1.htm">大模型</a><a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B/1.htm">语言模型</a>
                        <div>1获取API获得deepseek开放平台的APIhttps://platform.deepseek.com/api_keys点击创建APIkey2调用方法方法一:采用openai的调用方法pipinstallopenai需要openai的包调用的代码框架fromopenaiimportOpenAIimportosclient=OpenAI(api_key='自己的APIkey',base_url=</div>
                    </li>
                    <li><a href="/article/1904105216219869184.htm"
                           title="Python GUI 开发:全面指南" target="_blank">Python GUI 开发:全面指南</a>
                        <span class="text-muted">一休哥助手</span>
<a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a>
                        <div>1.PythonGUI开发简介GUI是指图形用户界面,它使用户可以通过图形元素(如按钮、文本框、下拉菜单等)与应用程序进行交互。与命令行界面相比,GUI更加直观易用。Python提供了多种库和框架,使开发者能够轻松创建功能丰富的桌面应用程序。1.1为什么选择Python进行GUI开发?简洁易读:Python的语法简洁,代码易于理解,开发者可以专注于应用程序的逻辑而不是语法。跨平台:Python是跨</div>
                    </li>
                    <li><a href="/article/1904104710571356160.htm"
                           title="【前端构建】使用Docker打包多个前端项目到一个Nginx镜像,并给conf文件动态传递参数" target="_blank">【前端构建】使用Docker打包多个前端项目到一个Nginx镜像,并给conf文件动态传递参数</a>
                        <span class="text-muted">Zacks_xdc</span>
<a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/docker/1.htm">docker</a><a class="tag" taget="_blank" href="/search/nginx/1.htm">nginx</a>
                        <div>提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档文章目录背景正文DockerFileNginx配置模板接收变量并替换Shell脚本将Nginx配置模板替换成配置文件使用构建镜像运行容器总结背景公司给一些客户要部署三个前端项目。最初,每个前端项目都以独立的镜像形式交付并部署。然而,随着客户数量的增加,每个客户都提出了一些自定义需求,后端也进行了对应改造。这导致了部署过程变得复杂且繁琐</div>
                    </li>
                    <li><a href="/article/1904104584226336768.htm"
                           title="编写有内存漏洞的 C++ 代码,并实现内存注入的示例(一个程序注入另一个程序)" target="_blank">编写有内存漏洞的 C++ 代码,并实现内存注入的示例(一个程序注入另一个程序)</a>
                        <span class="text-muted">SmartGridequation</span>
<a class="tag" taget="_blank" href="/search/C%2FC%2B%2B/1.htm">C/C++</a><a class="tag" taget="_blank" href="/search/c%2B%2B/1.htm">c++</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/%E5%86%85%E5%AD%98%E6%BC%8F%E6%B4%9E/1.htm">内存漏洞</a><a class="tag" taget="_blank" href="/search/%E5%86%85%E5%AD%98%E6%B3%A8%E5%85%A5/1.htm">内存注入</a>
                        <div>实现思路在Windows平台下,可以使用WindowsAPI编写一个程序来对另一个目标程序进行内存注入。基本步骤如下:查找目标进程:通过进程名找到目标进程的ID。打开目标进程:使用OpenProcess函数打开目标进程,获取进程句柄。在目标进程中分配内存:使用VirtualAllocEx函数在目标进程的地址空间中分配一块内存。将数据写入目标进程的内存:使用WriteProcessMemory函数将</div>
                    </li>
                                <li><a href="/article/13.htm"
                                       title="github中多个平台共存" target="_blank">github中多个平台共存</a>
                                    <span class="text-muted">jackyrong</span>
<a class="tag" taget="_blank" href="/search/github/1.htm">github</a>
                                    <div>在个人电脑上,如何分别链接比如oschina,github等库呢,一般教程之列的,默认 
ssh链接一个托管的而已,下面讲解如何放两个文件 
 
 
1) 设置用户名和邮件地址 
 
$ git config --global user.name "xx" 
 
$ git config --global user.email "test@gmail.com"</div>
                                </li>
                                <li><a href="/article/140.htm"
                                       title="ip地址与整数的相互转换(javascript)" target="_blank">ip地址与整数的相互转换(javascript)</a>
                                    <span class="text-muted">alxw4616</span>
<a class="tag" taget="_blank" href="/search/JavaScript/1.htm">JavaScript</a>
                                    <div>//IP转成整型
function ip2int(ip){
    var num = 0;
    ip = ip.split(".");
    num = Number(ip[0]) * 256 * 256 * 256 + Number(ip[1]) * 256 * 256 + Number(ip[2]) * 256 + Number(ip[3]);
    n</div>
                                </li>
                                <li><a href="/article/267.htm"
                                       title="读书笔记-jquey+数据库+css" target="_blank">读书笔记-jquey+数据库+css</a>
                                    <span class="text-muted">chengxuyuancsdn</span>
<a class="tag" taget="_blank" href="/search/html/1.htm">html</a><a class="tag" taget="_blank" href="/search/jquery/1.htm">jquery</a><a class="tag" taget="_blank" href="/search/oracle/1.htm">oracle</a>
                                    <div>1、grouping ,group by rollup, GROUP BY GROUPING SETS区别 
2、$("#totalTable tbody>tr td:nth-child(" + i + ")").css({"width":tdWidth, "margin":"0px", &q</div>
                                </li>
                                <li><a href="/article/394.htm"
                                       title="javaSE javaEE javaME == API下载" target="_blank">javaSE javaEE javaME == API下载</a>
                                    <span class="text-muted">Array_06</span>
<a class="tag" taget="_blank" href="/search/java/1.htm">java</a>
                                    <div>oracle下载各种API文档: 
http://www.oracle.com/technetwork/java/embedded/javame/embed-me/documentation/javame-embedded-apis-2181154.html 
 
JavaSE文档: 
http://docs.oracle.com/javase/8/docs/api/ 
JavaEE文档: 
ht</div>
                                </li>
                                <li><a href="/article/521.htm"
                                       title="shiro入门学习" target="_blank">shiro入门学习</a>
                                    <span class="text-muted">cugfy</span>
<a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/Web/1.htm">Web</a><a class="tag" taget="_blank" href="/search/%E6%A1%86%E6%9E%B6/1.htm">框架</a>
                                    <div>声明本文只适合初学者,本人也是刚接触而已,经过一段时间的研究小有收获,特来分享下希望和大家互相交流学习。 
首先配置我们的web.xml代码如下,固定格式,记死就成 
 <filter> 
        <filter-name>shiroFilter</filter-name> 
&nbs</div>
                                </li>
                                <li><a href="/article/648.htm"
                                       title="Array添加删除方法" target="_blank">Array添加删除方法</a>
                                    <span class="text-muted">357029540</span>
<a class="tag" taget="_blank" href="/search/js/1.htm">js</a>
                                    <div>     刚才做项目前台删除数组的固定下标值时,删除得不是很完整,所以在网上查了下,发现一个不错的方法,也提供给需要的同学。 
 
//给数组添加删除 
            Array.prototype.del = function(n){ 
</div>
                                </li>
                                <li><a href="/article/775.htm"
                                       title="navigation bar 更改颜色" target="_blank">navigation bar 更改颜色</a>
                                    <span class="text-muted">张亚雄</span>
<a class="tag" taget="_blank" href="/search/IO/1.htm">IO</a>
                                    <div>今天郁闷了一下午,就因为objective-c默认语言是英文,我写的中文全是一些乱七八糟的样子,到不是乱码,但是,前两个自字是粗体,后两个字正常体,这可郁闷死我了,问了问大牛,人家告诉我说更改一下字体就好啦,比如改成黑体,哇塞,茅塞顿开。 
      翻书看,发现,书上有介绍怎么更改表格中文字字体的,代码如下 
   </div>
                                </li>
                                <li><a href="/article/902.htm"
                                       title="unicode转换成中文" target="_blank">unicode转换成中文</a>
                                    <span class="text-muted">adminjun</span>
<a class="tag" taget="_blank" href="/search/unicode/1.htm">unicode</a><a class="tag" taget="_blank" href="/search/%E7%BC%96%E7%A0%81%E8%BD%AC%E6%8D%A2/1.htm">编码转换</a>
                                    <div>  
在Java程序中总会出现\u6b22\u8fce\u63d0\u4ea4\u5fae\u535a\u641c\u7d22\u4f7f\u7528\u53cd\u9988\uff0c\u8bf7\u76f4\u63a5这个的字符,这是unicode编码,使用时有时候不会自动转换成中文就需要自己转换了使用下面的方法转换一下即可。 
	/**
	 * unicode 转换成 中文
	</div>
                                </li>
                                <li><a href="/article/1029.htm"
                                       title="一站式 Java Web 框架 firefly" target="_blank">一站式 Java Web 框架 firefly</a>
                                    <span class="text-muted">aijuans</span>
<a class="tag" taget="_blank" href="/search/Java+Web/1.htm">Java Web</a>
                                    <div>Firefly是一个高性能一站式Web框架。 涵盖了web开发的主要技术栈。 包含Template engine、IOC、MVC framework、HTTP Server、Common tools、Log、Json parser等模块。 
firefly-2.0_07修复了模版压缩对javascript单行注释的影响,并新增了自定义错误页面功能。 
更新日志: 
 
 增加自定义系统错误页面功能</div>
                                </li>
                                <li><a href="/article/1156.htm"
                                       title="设计模式——单例模式" target="_blank">设计模式——单例模式</a>
                                    <span class="text-muted">ayaoxinchao</span>
<a class="tag" taget="_blank" href="/search/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/1.htm">设计模式</a>
                                    <div>定义 
  
       Java中单例模式定义:“一个类有且仅有一个实例,并且自行实例化向整个系统提供。” 
  
分析 
  
       从定义中可以看出单例的要点有三个:一是某个类只能有一个实例;二是必须自行创建这个实例;三是必须自行向系统提供这个实例。 
  &nb</div>
                                </li>
                                <li><a href="/article/1283.htm"
                                       title="Javascript 多浏览器兼容性问题及解决方案" target="_blank">Javascript 多浏览器兼容性问题及解决方案</a>
                                    <span class="text-muted">BigBird2012</span>
<a class="tag" taget="_blank" href="/search/JavaScript/1.htm">JavaScript</a>
                                    <div>不论是网站应用还是学习js,大家很注重ie与firefox等浏览器的兼容性问题,毕竟这两中浏览器是占了绝大多数。 
一、document.formName.item(”itemName”) 问题  
问题说明:IE下,可以使用 document.formName.item(”itemName”) 或 document.formName.elements ["elementName&quo</div>
                                </li>
                                <li><a href="/article/1410.htm"
                                       title="JUnit-4.11使用报java.lang.NoClassDefFoundError: org/hamcrest/SelfDescribing错误" target="_blank">JUnit-4.11使用报java.lang.NoClassDefFoundError: org/hamcrest/SelfDescribing错误</a>
                                    <span class="text-muted">bijian1013</span>
<a class="tag" taget="_blank" href="/search/junit4.11/1.htm">junit4.11</a><a class="tag" taget="_blank" href="/search/%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95/1.htm">单元测试</a>
                                    <div>        下载了最新的JUnit版本,是4.11,结果尝试使用发现总是报java.lang.NoClassDefFoundError: org/hamcrest/SelfDescribing这样的错误,上网查了一下,一般的解决方案是,换一个低一点的版本就好了。还有人说,是缺少hamcrest的包。去官网看了一下,如下发现: 
   </div>
                                </li>
                                <li><a href="/article/1537.htm"
                                       title="[Zookeeper学习笔记之二]Zookeeper部署脚本" target="_blank">[Zookeeper学习笔记之二]Zookeeper部署脚本</a>
                                    <span class="text-muted">bit1129</span>
<a class="tag" taget="_blank" href="/search/zookeeper/1.htm">zookeeper</a>
                                    <div>Zookeeper伪分布式安装脚本(此脚本在一台机器上创建Zookeeper三个进程,即创建具有三个节点的Zookeeper集群。这个脚本和zookeeper的tar包放在同一个目录下,脚本中指定的名字是zookeeper的3.4.6版本,需要根据实际情况修改): 
  
#!/bin/bash  
  
#!!!Change the name!!!  
#The zookeepe</div>
                                </li>
                                <li><a href="/article/1664.htm"
                                       title="【Spark八十】Spark RDD API二" target="_blank">【Spark八十】Spark RDD API二</a>
                                    <span class="text-muted">bit1129</span>
<a class="tag" taget="_blank" href="/search/spark/1.htm">spark</a>
                                    <div>coGroup 
package spark.examples.rddapi

import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.SparkContext._

object CoGroupTest_05 {
  def main(args: Array[String]) {
    v</div>
                                </li>
                                <li><a href="/article/1791.htm"
                                       title="Linux中编译apache服务器modules文件夹缺少模块(.so)的问题" target="_blank">Linux中编译apache服务器modules文件夹缺少模块(.so)的问题</a>
                                    <span class="text-muted">ronin47</span>
<a class="tag" taget="_blank" href="/search/modules/1.htm">modules</a>
                                    <div>在modules目录中只有httpd.exp,那些so文件呢? 
 
我尝试在fedora core 3中安装apache 2. 当我解压了apache 2.0.54后使用configure工具并且加入了 --enable-so 或者 --enable-modules=so (两个我都试过了) 
去make并且make install了。我希望在/apache2/modules/目录里有各种模块,</div>
                                </li>
                                <li><a href="/article/1918.htm"
                                       title="Java基础-克隆" target="_blank">Java基础-克隆</a>
                                    <span class="text-muted">BrokenDreams</span>
<a class="tag" taget="_blank" href="/search/java%E5%9F%BA%E7%A1%80/1.htm">java基础</a>
                                    <div>        Java中怎么拷贝一个对象呢?可以通过调用这个对象类型的构造器构造一个新对象,然后将要拷贝对象的属性设置到新对象里面。Java中也有另一种不通过构造器来拷贝对象的方式,这种方式称为 
克隆。 
        Java提供了java.lang.</div>
                                </li>
                                <li><a href="/article/2045.htm"
                                       title="读《研磨设计模式》-代码笔记-适配器模式-Adapter" target="_blank">读《研磨设计模式》-代码笔记-适配器模式-Adapter</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/ 
 
 
package design.pattern;

/*
 * 适配器模式解决的主要问题是,现有的方法接口与客户要求的方法接口不一致
 * 可以这样想,我们要写这样一个类(Adapter):
 * 1.这个类要符合客户的要求 ---> 那显然要</div>
                                </li>
                                <li><a href="/article/2172.htm"
                                       title="HDR图像PS教程集锦&心得" target="_blank">HDR图像PS教程集锦&心得</a>
                                    <span class="text-muted">cherishLC</span>
<a class="tag" taget="_blank" href="/search/PS/1.htm">PS</a>
                                    <div>HDR是指高动态范围的图像,主要原理为提高图像的局部对比度。 
软件有photomatix和nik hdr efex。 
 一、教程 
叶明在知乎上的回答: 
http://www.zhihu.com/question/27418267/answer/37317792 
大意是修完后直方图最好是等值直方图,方法是HDR软件调一遍,再结合不透明度和蒙版细调。 
 
 二、心得 
 1、去除阴影部分的</div>
                                </li>
                                <li><a href="/article/2299.htm"
                                       title="maven-3.3.3 mvn archetype 列表" target="_blank">maven-3.3.3 mvn archetype 列表</a>
                                    <span class="text-muted">crabdave</span>
<a class="tag" taget="_blank" href="/search/ArcheType/1.htm">ArcheType</a>
                                    <div>maven-3.3.3 mvn archetype 列表 
可以参考最新的:http://repo1.maven.org/maven2/archetype-catalog.xml 
  
[INFO] Scanning for projects... 
[INFO]                 </div>
                                </li>
                                <li><a href="/article/2426.htm"
                                       title="linux shell 中文件编码查看及转换方法" target="_blank">linux shell 中文件编码查看及转换方法</a>
                                    <span class="text-muted">daizj</span>
<a class="tag" taget="_blank" href="/search/shell/1.htm">shell</a><a class="tag" taget="_blank" href="/search/%E4%B8%AD%E6%96%87%E4%B9%B1%E7%A0%81/1.htm">中文乱码</a><a class="tag" taget="_blank" href="/search/vim/1.htm">vim</a><a class="tag" taget="_blank" href="/search/%E6%96%87%E4%BB%B6%E7%BC%96%E7%A0%81/1.htm">文件编码</a>
                                    <div>一、查看文件编码。 
    在打开文件的时候输入:set fileencoding 
    即可显示文件编码格式。 
 
 
 
二、文件编码转换 
    1、在Vim中直接进行转换文件编码,比如将一个文件转换成utf-8格式 
      &</div>
                                </li>
                                <li><a href="/article/2553.htm"
                                       title="MySQL--binlog日志恢复数据" target="_blank">MySQL--binlog日志恢复数据</a>
                                    <span class="text-muted">dcj3sjt126com</span>
<a class="tag" taget="_blank" href="/search/binlog/1.htm">binlog</a>
                                    <div>   恢复数据的重要命令如下   mysql> flush logs;  默认的日志是mysql-bin.000001,现在刷新了重新开启一个就多了一个mysql-bin.000002                    </div>
                                </li>
                                <li><a href="/article/2680.htm"
                                       title="数据库中数据表数据迁移方法" target="_blank">数据库中数据表数据迁移方法</a>
                                    <span class="text-muted">dcj3sjt126com</span>
<a class="tag" taget="_blank" href="/search/sql/1.htm">sql</a>
                                    <div>刚开始想想好像挺麻烦的,后来找到一种方法了,就SQL中的 INSERT 语句,不过内容是现从另外的表中查出来的,其实就是 MySQL中INSERT INTO SELECT的使用 
  
下面看看如何使用 
  
语法:MySQL中INSERT INTO SELECT的使用 
1. 语法介绍 
      有三张表a、b、c,现在需要从表b</div>
                                </li>
                                <li><a href="/article/2807.htm"
                                       title="Java反转字符串" target="_blank">Java反转字符串</a>
                                    <span class="text-muted">dyy_gusi</span>
<a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/%E5%8F%8D%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2/1.htm">反转字符串</a>
                                    <div>       前几天看见一篇文章,说使用Java能用几种方式反转一个字符串。首先要明白什么叫反转字符串,就是将一个字符串到过来啦,比如"倒过来念的是小狗"反转过来就是”狗小是的念来过倒“。接下来就把自己能想到的所有方式记录下来了。 
1、第一个念头就是直接使用String类的反转方法,对不起,这样是不行的,因为Stri</div>
                                </li>
                                <li><a href="/article/2934.htm"
                                       title="UI设计中我们为什么需要设计动效" target="_blank">UI设计中我们为什么需要设计动效</a>
                                    <span class="text-muted">gcq511120594</span>
<a class="tag" taget="_blank" href="/search/UI/1.htm">UI</a><a class="tag" taget="_blank" href="/search/linux/1.htm">linux</a>
                                    <div>随着国际大品牌苹果和谷歌的引领,最近越来越多的国内公司开始关注动效设计了,越来越多的团队已经意识到动效在产品用户体验中的重要性了,更多的UI设计师们也开始投身动效设计领域。 
 
但是说到底,我们到底为什么需要动效设计?或者说我们到底需要什么样的动效?做动效设计也有段时间了,于是尝试用一些案例,从产品本身出发来说说我所思考的动效设计。 
 
一、加强体验舒适度 
 
嗯,就是让用户更加爽更加爽的用</div>
                                </li>
                                <li><a href="/article/3061.htm"
                                       title="JBOSS服务部署端口冲突问题" target="_blank">JBOSS服务部署端口冲突问题</a>
                                    <span class="text-muted">HogwartsRow</span>
<a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/%E5%BA%94%E7%94%A8%E6%9C%8D%E5%8A%A1%E5%99%A8/1.htm">应用服务器</a><a class="tag" taget="_blank" href="/search/jboss/1.htm">jboss</a><a class="tag" taget="_blank" href="/search/server/1.htm">server</a><a class="tag" taget="_blank" href="/search/EJB3/1.htm">EJB3</a>
                                    <div>服务端口冲突问题的解决方法,一般修改如下三个文件中的部分端口就可以了。 
  
1、jboss5/server/default/conf/bindingservice.beans/META-INF/bindings-jboss-beans.xml 
  
2、./server/default/deploy/jbossweb.sar/server.xml 
  
3、.</div>
                                </li>
                                <li><a href="/article/3188.htm"
                                       title="第三章 Redis/SSDB+Twemproxy安装与使用" target="_blank">第三章 Redis/SSDB+Twemproxy安装与使用</a>
                                    <span class="text-muted">jinnianshilongnian</span>
<a class="tag" taget="_blank" href="/search/ssdb/1.htm">ssdb</a><a class="tag" taget="_blank" href="/search/reids/1.htm">reids</a><a class="tag" taget="_blank" href="/search/twemproxy/1.htm">twemproxy</a>
                                    <div>目前对于互联网公司不使用Redis的很少,Redis不仅仅可以作为key-value缓存,而且提供了丰富的数据结果如set、list、map等,可以实现很多复杂的功能;但是Redis本身主要用作内存缓存,不适合做持久化存储,因此目前有如SSDB、ARDB等,还有如京东的JIMDB,它们都支持Redis协议,可以支持Redis客户端直接访问;而这些持久化存储大多数使用了如LevelDB、RocksD</div>
                                </li>
                                <li><a href="/article/3315.htm"
                                       title="ZooKeeper原理及使用 " target="_blank">ZooKeeper原理及使用 </a>
                                    <span class="text-muted">liyonghui160com</span>

                                    <div>  
  
       ZooKeeper是Hadoop Ecosystem中非常重要的组件,它的主要功能是为分布式系统提供一致性协调(Coordination)服务,与之对应的Google的类似服务叫Chubby。今天这篇文章分为三个部分来介绍ZooKeeper,第一部分介绍ZooKeeper的基本原理,第二部分介绍ZooKeeper</div>
                                </li>
                                <li><a href="/article/3442.htm"
                                       title="程序员解决问题的60个策略" target="_blank">程序员解决问题的60个策略</a>
                                    <span class="text-muted">pda158</span>
<a class="tag" taget="_blank" href="/search/%E6%A1%86%E6%9E%B6/1.htm">框架</a><a class="tag" taget="_blank" href="/search/%E5%B7%A5%E4%BD%9C/1.htm">工作</a><a class="tag" taget="_blank" href="/search/%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95/1.htm">单元测试</a>
                                    <div>根本的指导方针 
1. 首先写代码的时候最好不要有缺陷。最好的修复方法就是让 bug 胎死腹中。 
 
  良好的单元测试  
  强制数据库约束  
  使用输入验证框架  
  避免未实现的“else”条件  
  在应用到主程序之前知道如何在孤立的情况下使用  
 
  
日志 
2. print 语句。往往额外输出个一两行将有助于隔离问题。 
3. 切换至详细的日志记录。详细的日</div>
                                </li>
                                <li><a href="/article/3569.htm"
                                       title="Create the Google Play Account" target="_blank">Create the Google Play Account</a>
                                    <span class="text-muted">sillycat</span>
<a class="tag" taget="_blank" href="/search/Google/1.htm">Google</a>
                                    <div>Create the Google Play Account 
 
Having a Google account, pay 25$, then you get your google developer account. 
 
References: 
http://developer.android.com/distribute/googleplay/start.html 
https://p</div>
                                </li>
                                <li><a href="/article/3696.htm"
                                       title="JSP三大指令" target="_blank">JSP三大指令</a>
                                    <span class="text-muted">vikingwei</span>
<a class="tag" taget="_blank" href="/search/jsp/1.htm">jsp</a>
                                    <div>JSP三大指令 
  一个jsp页面中,可以有0~N个指令的定义! 
1. page --> 最复杂:<%@page language="java" info="xxx"...%> 
  * pageEncoding和contentType: 
    > pageEncoding:它</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>