-
Notifications
You must be signed in to change notification settings - Fork 0
/
content.json
1 lines (1 loc) · 468 KB
/
content.json
1
{"meta":{"title":"MagicYou","subtitle":null,"description":null,"author":"magicyou","url":"https://blog.magicyou.cn","root":"/"},"pages":[{"title":"友情链接","date":"2022-09-03T05:08:18.952Z","updated":"2022-09-03T05:08:18.952Z","comments":true,"path":"links/index.html","permalink":"https://blog.magicyou.cn/links/index.html","excerpt":"","text":""},{"title":"about","date":"2016-12-21T05:49:15.000Z","updated":"2022-09-03T05:08:18.949Z","comments":true,"path":"about/index.html","permalink":"https://blog.magicyou.cn/about/index.html","excerpt":"","text":"123desc: 一个前端开发工程师,略懂PHP,学了点Python,走在学习的路上hobby: 想要健身"},{"title":"Repositories","date":"2022-09-03T05:08:18.952Z","updated":"2022-09-03T05:08:18.952Z","comments":false,"path":"repository/index.html","permalink":"https://blog.magicyou.cn/repository/index.html","excerpt":"","text":""},{"title":"分类","date":"2022-09-03T05:08:18.951Z","updated":"2022-09-03T05:08:18.951Z","comments":false,"path":"categories/index.html","permalink":"https://blog.magicyou.cn/categories/index.html","excerpt":"","text":""},{"title":"书单","date":"2022-09-03T05:08:18.950Z","updated":"2022-09-03T05:08:18.950Z","comments":false,"path":"books/index.html","permalink":"https://blog.magicyou.cn/books/index.html","excerpt":"","text":""},{"title":"标签","date":"2022-09-03T05:08:18.977Z","updated":"2022-09-03T05:08:18.977Z","comments":false,"path":"tags/index.html","permalink":"https://blog.magicyou.cn/tags/index.html","excerpt":"","text":""}],"posts":[{"title":"Next.js 一些东西","slug":"react-Next.js 一些东西","date":"2022-08-31T13:33:00.000Z","updated":"2022-08-31T13:33:00.000Z","comments":true,"path":"2022/08/31/react-Next.js 一些东西/","link":"","permalink":"https://blog.magicyou.cn/2022/08/31/react-Next.js%20%E4%B8%80%E4%BA%9B%E4%B8%9C%E8%A5%BF/","excerpt":"","text":"Next.js 一些东西 Next.js 一些东西一. 疑问:为什么要用 Next.js需要ssrssr是什么,发展历程 服务端渲染:后端先调用数据库,获取数据之后,将数据和页面元素进行拼装,组合成完整的html页面,在直接返回给浏览器,以便用户浏览 客户端渲染:数据有浏览器通过ajax动态获取,在通过js将数据填充到dom元素最终展示到网页中,这样的过程叫做客户端渲染。 服务端渲染 VS 客户端渲染 服务端渲染需要消耗更多的服务器资源(CPU,内存等) 客户端渲染可以将静态资源部署到cdn上,实现实现高并发 服务端渲染对SEO更友好 如今的react和Vue 同样都是前后端分离 客户端页面渲染 二. Next.js 初始化1yarn create next-app 三. 路由在 Next.js 中,一个 page(页面) 就是一个从 .js、jsx、.ts 或 .tsx 文件导出(export)的 React 组件 ,这些文件存放在 pages 目录下。每个 page(页面)都使用其文件名作为路由(route)。 1234|- pages |- index.js |- help.js |- about.js 路由基本使用link跳转(next/link) 属性 href 添加跳转路径(默认是history的push模式,向历史纪录添加一条) 字符串:“ /news?newsId=123 ” 对象: “ {pathname: ‘/news’, query: {newsId: ‘123’} } ” 属性 replace 替换当前页面 属性 scroll 跳转到页面顶部,默认为true 属性 passHref,强制 link 将href发送给子元素,默认false 属性 prefetch,在后台预取目标页面。默认值为 true。(预取功能只在生产环境中开启) js编程式跳转 useRouter React Hook router.push(‘/testRouter1’) router.push({pathname: ‘/testRouter1’, query: {id: ‘123’} }) withRouter class 组件 router对象的定义参数: 属性 用法 默认值 pathname - - - - query - - - - 其他的用法 123456789import { useRouter } from 'next/router' const router = useRouter()router.query // 当前地址参数router.replace()router.prefetch() // 同 link 标签的prefetch属性,仅对 link标签有效,只能用于生产环境router.beforePopState() // ***router.reload() // 等同 window.location.reload() router.events 路由事件123456789101112import { useRouter } from 'next/router' const router = useRouter()routeChangeStart(url, { shallow }) // 路由改变时routeChangeComplete(url, { shallow }) // 路由改变之后routeChangeError(url, { shallow }) // 路由跳转错误routeChangeError(err, url, { shallow }) // 路由跳转错误beforeHistoryChange(err, url, { shallow }) // 在更改浏览器历史记录之前触发hashChangeStart(err, url, { shallow }) // 当哈希值已更改但页面未更改时触发hashChangeComplete(err, url, { shallow }) // 当哈希值已更改但页面未更改时触发 动态路由pages 目录下的[typeId].js 就是动态路由。比如当前目录: 12345678|- pages |- index.js |- help.js |- about.js |- news |- index.js |- [typeId].js ‘pages/news/1’、’pages/news/2’、’pages/news/2?name=lxl‘ 都会匹配到 ’pages/news/[typeId].js‘ 123{typeId: 1} // pages/news/1.js{typeId: 2} // pages/news/2.js{typeId: 2, name: lxl} // pages/news/2.js?name=lxl 多个参数呢?动态路由在pages目录下动态路由文件名加三个点来扩展捕获所有参数‘pages/goods/生活用品’、’pages/goods/科技数码?isHot=1’、’pages/goods/文学作品/3’ 就可以匹配到 ’pages/goods/[…slugs].js‘。(’slugs‘是自定义的,根据需要自行定义即可) 123{slugs: ['生活用品']} // pages/goods/生活用品{slugs: ['科技数码'], isHot: '1'} // pages/goods/科技数码?isHot=1{slugs: ['文学作品', '3'], isHot: '1'} // pages/goods/文学作品/3 四. 预加载什么是预加载 预加载是性能优化技术 所需资源提前加载到本地,这样后面在需要用的时候直接从缓存取资源Link 标签的属性 prefetch 123<Link href="/" prefetch> <a>pageIndex</a></Link> 五. 404 和 错误页定制pages目录下: 定制 404: 404.js 定制 错误页: _error.js 六. css支持添加全局样式表全局样式应在 ‘pages/_app.js’中引入 12345678import '../styles/globals.css'import 'antd/dist/antd.css'function MyApp({ Component, pageProps }) { return <Component {...pageProps} />}export default MyApp 这些样式 (globals.css) 将应用于你的应用程序中的所有页面和组件。 并且为了避免冲突,应该 只在 pages/_app.js 文件中导入(import)这些全局样式表 从 node_modules 目录导入(import)样式12import Texty from 'rc-texty';import 'rc-texty/assets/index.css'; 添加组件级 CSSNext.js 通过 [name].module.css 文件命名约定来支持 CSS 模块 。 CSS 模块会自动创建唯一的类名,在不同文件使用同一类名,样式不会冲突 nextjs中,个人推荐使用此方法写样式表 12345678910111213import Head from 'next/head'import styles from '../styles/Home.module.css'export default function About() { return ( <div className={styles.container}> <main className={styles.main}> about </main> </div> )} 原生对sass支持很好Next.js 内置 Sass,要确保安装 ‘sass’ 1npm install sass CSS-in-JS 支持内联样式 支持 styled-jsx123456789101112131415161718192021 <main className={styles.main}> <div> <p>1</p> <p>2</p> <style jsx> {` p { width: 100px; } p:nth-child(1) { color: blue; background: red; } p:nth-child(2) { color: green; background: pink; } `} </style> </div></main> 七. middleware 中间件八. Dynamic Import 动态导入nextjs 也支持 react 的 ‘lazy’相似的组件导入方式 123import dynamic from 'next/dynamic'const DynamicComponent = dynamic(() => import('../components/userInfo')) 也可以让他不在服务端渲染,在浏览器端异步加载 1const DynamicComponent = dynamic(() => import('../components/userInfo'), { loading: () => <p>加载中...</p>, ssr: false }) 支持Suspense 12345678const DynamicLazyComponent = dynamic( () => import('../components/userInfo'), { suspense: true })<Suspense fallback={`<p>加载中...</p>`}> <DynamicLazyComponent /></Suspense> 九. 图片优化 模糊效果占位图,支持懒加载 默认加上 srcset 属性来适配不同 devicePixelRatio 的屏幕 默认会返回压缩后的图片 支持给图片设置优先级 十. 如何获取数据nextjs中有两种形式的预渲染 1. 服务端渲染访问xxx路由之前,向服务服务器要数据,吧要回来的数据和html加工直接返回前台展示 2. 静态化访问xxx路由之前,向服务器请求数据,将请求来的数据和html加工成真正的XXX.html文件作用:下次访问同一个路由地址的时候,直接返回静态页面,减小服务器压力,以达到性能优化目的 方法 静态化 只能在pages文件夹下 作用 getStaticProps 静态生成 是 在构建时获取数据 getServerSideProps 服务器端渲染 是 每次进入页面重新获取数据 十一. MDXMDX 是 Markdown 的超集,允许您直接在 markdown 文件中写入 JSX。 安装依赖包 1npm install @next/mdx @mdx-js/loader next.config.js 完善扩展1234567891011121314// next.config.jsconst withMDX = require('@next/mdx')({ extension: /\\.mdx?$/, options: { remarkPlugins: [], rehypePlugins: [], // If you use `MDXProvider`, uncomment the following line. // providerImportSource: "@mdx-js/react", },})module.exports = withMDX({ // Append the default value with md extensions pageExtensions: ['ts', 'tsx', 'js', 'jsx', 'md', 'mdx'],}) 现在 可以直接在 pages 目录创建后缀是 ’.mdx‘ 的文件,当然它也和’index.js’一样也是一个路由 十二. 如何部署目前尝试部署方案 源码放生产环境 yarn install yarn build yarn start,此时项目运行在服务端8883端口(可按实际情况更改端口) 不挂断运行服务 nohup yarn start >> ./log/nohupdate +%Y-%m-%d-%H.out 2>&1 & 有域名,用nginx代理到8883端口 运行 nohup 方式nohup yarn start >> ./log/nohupdate +%Y-%m-%d-%H.out 2>&1 & pm2 方式pm2 start yarn –name testxj03 – start","categories":[{"name":"前端","slug":"前端","permalink":"https://blog.magicyou.cn/categories/%E5%89%8D%E7%AB%AF/"}],"tags":[{"name":"React","slug":"React","permalink":"https://blog.magicyou.cn/tags/React/"},{"name":"Next.js","slug":"Next-js","permalink":"https://blog.magicyou.cn/tags/Next-js/"}]},{"title":"vue-","slug":"vue-特性","date":"2022-03-11T14:13:02.000Z","updated":"2022-03-11T14:13:04.000Z","comments":true,"path":"2022/03/11/vue-特性/","link":"","permalink":"https://blog.magicyou.cn/2022/03/11/vue-%E7%89%B9%E6%80%A7/","excerpt":"复习vue特性: 自定义v-model、$nextTick、slot、动态异步组件、keep-alive、mixin","text":"复习vue特性: 自定义v-model、$nextTick、slot、动态异步组件、keep-alive、mixin 主要参考来源: https://cn.vuejs.org/v2/api v-model 你可以用 v-model 指令在表单 <input>、<textarea> 及 <select> 元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。尽管有些神奇,但 v-model 本质上不过是语法糖。它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。 — vue官方文档 基本用法: 123456789101112<template> <input type="text" id="name" v-model="name"></template><script>export default { data () { return { name: 'zjl', } }}</script> v-model 做了什么?v-model 是个语法糖,一个指令实现数据同步、绑定数据,类似如下代码: 1<input type="text" v-bind:value="tangVal" v-on:input="tangVal=$event.target.value"> 修饰符 .lazy v-model 默认 input 事件触发值与数据同步,添加 lazy 修饰符之后,会转为 change 事件之后进行同步; .number 自动将输入的内容转为数字类型(type=”number”返回的值也是字符串); .trim 自动过滤用户输入的首尾空白字符 自定义组件使用 v-model 一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件,但是像单选框、复选框等类型的输入控件可能会将 value attribute 用于不同的目的。model 选项可以用来避免这样的冲突 — vue官方文档 手写一个input 和 select 自定义 input 组件 12345678910<template> <input type="text" :value="value" @input="$emit('input', $event.target.value)"></template><script>export default { props: { value: String },}</script> 12345678910111213141516171819<template> <select :value="value" @input="$emit('change', $event.target.value)"> <option value="学生">学生</option> <option value="老师">老师</option> <option value="工人">工人</option> <option value="其他">其他</option> </select></template><script>export default { model: { prop: 'value', event: 'change' }, props: { value: String }}</script> $nextTick 将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。它跟全局方法 Vue.nextTick 一样,不同的是回调的 this 自动绑定到调用它的实例上 — vue官方文档$nextTick 是vue 实例的方法 参数: {Function} [callback] 通俗的讲就是,当vue实例的数据更新之后,dom会发生变化,dom更新数据之后采购执行 $nextTick 的回调。 不使用 $nextTick,data更改之后直接使用dom元素获取 dom 的 innerText 12345678910111213141516171819202122<template> <div class="main"> <p id="test_data">测试dom:{{ name }}</p> <button @click="onClickSetData">点击更改为->456</button> </div></template><script>export default { name: 'TestNextTick', data () { return { name: '123', } }, methods: { onClickSetData() { this.name = '456'; console.log(document.getElementById('test_data').innerText); } }}</script> 控制台打印结果: 1测试dom:123 结果表明,修改了vue 实例的data后,直接获取 dom 的 innerText,获取结果依旧是更改data之前的内容;使用 $nextTick,更改 methods 1234 onClickSetData() { this.name = '456'; console.log(document.getElementById('test_data').innerText);} 控制台打印结果: 1测试dom:456 $nextTick 的使用场景 生命周期 created 内需要操作dom时,需要使用 $nextTick; 生命周期 mounted 内需要操作 dom 时,推荐使用 $nextTick;官方文档描述,mounted 不会保证所有的子组件也都被挂载完成,所以在此生命周期需要操作dom时候,推荐使用 $nextTick; 数据更新之后需要操作 dom,需要使用 $nextTick,保证 此时操作的 dom 是最终渲染完毕的; 注意$nextTick 在官方文档最后,有参考异步更新队列。当 vue 实例的 data 被修改,组件并不会立即重新渲染 DOM。这个过程是异步的,vue会开启一个队列,这个事件会被推入队列(中间也做了去除重复数据渲染这样不必要的操作)。 当你设置 vm.someData = ‘new value’,该组件不会立即重新渲染。当刷新队列时,组件会在下一个事件循环“tick”中更新。多数情况我们不需要关心这个过程,但是如果你想基于更新后的 DOM 状态来做点什么,这就可能会有些棘手。虽然 Vue.js 通常鼓励开发人员使用“数据驱动”的方式思考,避免直接接触 DOM,但是有时我们必须要这么做。为了在数据变化之后等待 Vue 完成更新 DOM,可以在数据变化之后立即使用 Vue.nextTick(callback)。这样回调函数将在 DOM 更新完成后被调用 — vue官方文档 slot插槽 动态异步组件keep-alivemixin当有多个组件有部分公用的 vue 实例方法时,可以使用mixin 将通用的部分提取出来。","categories":[{"name":"Vue","slug":"Vue","permalink":"https://blog.magicyou.cn/categories/Vue/"}],"tags":[{"name":"Vue","slug":"Vue","permalink":"https://blog.magicyou.cn/tags/Vue/"}]},{"title":"计算机基础-进制转换","slug":"计算机基础-进制转换","date":"2021-12-10T05:34:12.000Z","updated":"2021-12-10T05:34:23.000Z","comments":true,"path":"2021/12/10/计算机基础-进制转换/","link":"","permalink":"https://blog.magicyou.cn/2021/12/10/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80-%E8%BF%9B%E5%88%B6%E8%BD%AC%E6%8D%A2/","excerpt":"计算机只识别二进制,何为二进制?我们为什么生活中只用十进制?还有十六进制?如何计算,相互转换?","text":"计算机只识别二进制,何为二进制?我们为什么生活中只用十进制?还有十六进制?如何计算,相互转换? 什么是二进制二进制是计算技术中广泛采用的一种数制。二进制数据是用0和1两个数码来表示的数。它的基数为2,进位规则是“逢二进一”,借位规则是“借一当二”。 附言:同样的,8进制就是“逢8进1”;十六进制就是“逢16进1” 如何计算呢十进制转换为二进制 由上图计算方式,得出的余数倒过来,就是该数字对应的二进制: 十进制 二进制 14 1110 18 10010 8 1000 12 1100 十进制转换为八进制 由上图计算方式,得出的余数倒过来,就是该数字对应的八进制: 十进制 八进制 16 20 127 177 188 274 32 40 二进制和八进制如何转换回十进制呢?二进制转换成十进制按照二进制,索引从后往前,每一位数字与乘以2的当前索引次方(当前数字 * 2^索引),然后相加得出的和就是该二进制数字的十进制1110(二进制)-> ?(十进制)(1 * 2 ** 3) + (1 * 2 ** 2) + (1 * 2 ** 1) + (0 * 2 ** 0) = 8 + 4 + 2 + 0 = 14 10010(二进制)-> ?(十进制)(1 * 2 ** 4) + (0 * 2 ** 3) + (0 * 2 ** 2) + (1 * 2 ** 1) + (0 * 2 ** 0)= 16 + 0 + 0 + 2 + 0 = 18 1000(二进制)-> ?(十进制)(1 * 2 ** 3) + (0 * 2 ** 2) + (0 * 2 ** 1) + (0 * 2 ** 0) = 8 + 0 + 0 + 0 = 8 1100(二进制)-> ?(十进制)(1 * 2 ** 3) + (1 * 2 ** 2) + (0 * 2 ** 1) + (0 * 2 ** 0) = 8 + 4 + 0 + 0 = 12 八进制转换成十进制参照二进制算法,八进制,索引从后往前,每一位数字与乘以8的当前索引次方(当前数字 * 8^索引),然后相加得出的和就是该八进制数字的十进制20(八进制)-> ?(十进制) (2 * 8 ** 1) + (0 * 8 ** 0) = 16 + 0 = 16 177(八进制)-> ?(十进制)(1 * 8 ** 2) + (7 * 8 ** 1) + (7 * 2 ** 0)= 64 + 56 + 7 = 127 274(八进制)-> ?(十进制)(2 * 8 ** 2) + (7 * 8 ** 1) + (4 * 2 ** 0)= 128 + 56 + 4 = 188 40(八进制)-> ?(十进制) (4 * 8 ** 1) + (0 * 8 ** 0) = 32 + 0 = 32 负数的二进制?以上通过两组数字知道八进制和二进制,二进制和十进制 如何相互转换,十六进制十四进制应该知道如何计算了。但是发现上面计算的都是正数,那负数的二进制该如何计算?","categories":[{"name":"计算机基础","slug":"计算机基础","permalink":"https://blog.magicyou.cn/categories/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/"}],"tags":[{"name":"计算机基础","slug":"计算机基础","permalink":"https://blog.magicyou.cn/tags/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/"}]},{"title":"vue-通信方式","slug":"vue-通信方式","date":"2021-11-15T14:13:02.000Z","updated":"2021-11-15T14:13:04.000Z","comments":true,"path":"2021/11/15/vue-通信方式/","link":"","permalink":"https://blog.magicyou.cn/2021/11/15/vue-%E9%80%9A%E4%BF%A1%E6%96%B9%E5%BC%8F/","excerpt":"复习vue通信方式","text":"复习vue通信方式 主要参考来源: https://cn.vuejs.org/v2/api","categories":[{"name":"Vue","slug":"Vue","permalink":"https://blog.magicyou.cn/categories/Vue/"}],"tags":[{"name":"Vue","slug":"Vue","permalink":"https://blog.magicyou.cn/tags/Vue/"}]},{"title":"vue-重新研究生命周期","slug":"vue-重新研究生命周期","date":"2021-10-11T14:13:02.000Z","updated":"2021-10-11T14:13:04.000Z","comments":true,"path":"2021/10/11/vue-重新研究生命周期/","link":"","permalink":"https://blog.magicyou.cn/2021/10/11/vue-%E9%87%8D%E6%96%B0%E7%A0%94%E7%A9%B6%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F/","excerpt":"Vue 的生命周期:beforeCreate,created,beforeMount,beforeUpdate, mounted,beforeUpdate,updated,updated, beforeDestroy,destroy","text":"Vue 的生命周期:beforeCreate,created,beforeMount,beforeUpdate, mounted,beforeUpdate,updated,updated, beforeDestroy,destroy 主要参考来源: https://vuex.vuejs.org/zh/ Vue 的生命周期是个啥?每个 Vue 组件都是一个个的 Vue 实例,当一个 Vue 实例被创建时,都会有一系列的初始化过程,每个过程都会执行一些特定的方法。学过 JAVA 或者 PHP 的都应该知道,对象在被实例化和被销毁的时候也有一个过程,期间会调用构造函数,析构函数;这个和 Vue 生命周期类似。 官网的示意图 复习每个生命周期钩子 beforeCreate(创建前)在实例初始化之后,进行数据侦听和事件/侦听器的配置之前同步调用。此时组件上的对象还未被创建,el 和 data 都没有初始化,也不能调用 methods,data,computed,等方法和数据 create(创建后)在实例创建完成后被立即同步调用。在这一步中,实例已完成对选项的处理,意味着以下内容已被配置完毕:数据侦听、计算属性、方法、事件/侦听器的回调函数。然而,挂载阶段还没开始,且 $el property 目前尚不可用。总之就是,除了页面还没有开始渲染,其他的工作已经准备就绪,包括数据观测,树形和方法调用,watch/event事件回调,data初始化。可以进行 ajax 请求数据了,但是最好不要,毕竟此时的页面还是空白 beforeMount在挂载开始之前被调用:相关的 render 函数首次被调用。实例已完成以下的配置: 编译模板,把data里面的数据和模板生成html,完成了el和data 初始化,但是此时还没有挂在html到页面上,还是没有 dom。 mounted挂载完成了,html已经渲染到了document。可以进行ajax请求了。官方温馨提示: mounted 不会保证所有的子组件也都被挂载完成。如果你希望等到整个视图都渲染完毕再执行某些操作,可以在 mounted 内部使用 vm.$nextTick beforeUpdate数据更新之后,dom 被更新之前调用。注意官方的一句话:这里适合在现有 DOM 将要被更新之前访问它,比如移除手动添加的事件监听器 updated在数据更改导致的虚拟 DOM 重新渲染和更新完毕之后被调用。这个时候dom已经更新完毕,可以进行依赖于dom的操作,但是,你应该避免在此期间更改状态。如果要相应状态改变,因为这可能会导致更新无限循环 beforeDestroy实例销毁之前调用。在这一步,实例仍然完全可用。 这一步还可以用this来获取实例 一般在这一步做一些重置的操作,比如清除掉组件中的定时器 和 监听的dom事件 destroyed实例销毁后调用。该钩子被调用后,对应 Vue 实例的所有指令都被解绑,所有的事件监听器被移除,所有的子实例也都被销毁 以上复习了生命周期钩子函数在各个阶段都干了什么。然后用实例来验证加强记忆一下,顺便探讨父子组件的生命周期表现是什么样的。 单组件的生命周期简单两个组件123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657<template> <div class="main"> <span id="tip_text">我是一个测试生命周期的组件</span> </div></template><script>export default { name: 'LifeCycle', data () { return { name: '我是谁' } }, beforeCreate() { console.log('**** parent beforeCreate'); console.log('**** parent beforeCreate -this.name:', this.name); console.log('**** parent beforeCreate -test dom:', document.getElementById('tip_text')); }, created() { console.log('**** parent created'); console.log('**** parent created -this.name:', this.name); console.log('**** parent created -test dom:', document.getElementById('tip_text')); }, beforeMount() { console.log('**** parent beforeMount'); console.log('**** parent beforeMount -this.name:', this.name); console.log('**** parent beforeMount -test dom:', document.getElementById('tip_text')); }, mounted() { console.log('**** parent mounted'); console.log('**** parent mounted -this.name:', this.name); console.log('**** parent mounted -test dom:', document.getElementById('tip_text')); }, beforeUpdate() { console.log('**** parent beforeUpdate'); console.log('**** parent beforeUpdate -this.name:', this.name); console.log('**** parent beforeUpdate -test dom:', document.getElementById('tip_text')); }, updated() { console.log('**** parent updated'); console.log('**** parent updated -this.name:', this.name); console.log('**** parent updated -test dom:', document.getElementById('tip_text')); }, beforeDestroy() { console.log('**** parent beforeDestroy'); console.log('**** parent beforeDestroy -this.name:', this.name); console.log('**** parent beforeDestroy -test dom:', document.getElementById('tip_text')); }, destroyed() { console.log('**** parent destroyed'); console.log('**** parent destroyed -this.name:', this.name); console.log('**** parent destroyed -test dom:', document.getElementById('tip_text')); }, methods: {}}</script> 模拟页面进入,控制台输出如下:12345678910111213**** parent beforeCreate**** parent beforeCreate -this.name: undefined**** parent beforeCreate -test dom: null**** parent created**** parent created -this.name: 我是谁**** parent created -test dom: null**** parent beforeMount**** parent beforeMount -this.name: 我是谁**** parent beforeMount -test dom: null**** parent mounted**** parent mounted -this.name: 我是谁**** parent mounted -test dom: <span id="tip_text">我是一个测试生命周期的组件</span> 从上面可以看到,组件在创建的时候 依次执行:beforeCreate > created > beforeMount > mounted; beforeCreate 期间,不能访问data,而 created之后就可以访问 data 了; mounted 时期,不仅能访问data,dom元素也可以正确获取; 切换路由,模拟页面离开,控制台输出如下:123456**** parent beforeDestroy**** parent beforeDestroy -this.name: 我是谁**** parent beforeDestroy -test dom: null**** parent destroyed**** parent destroyed -this.name: 我是谁**** parent destroyed -test dom: null 组件离开被销毁时候,验证出以下观点 依次执行:beforeDestroy > destroyed; beforeDestroy 期间,还能访问 data,但是 dom 此时已经被销毁了; destroyed 时期,竟然还能访问data?有点疑惑; 发现updated和beforeUpdate并没有参与组件的创建和销毁,尝试加入动作; 调整组件 12345678910111213141516171819202122232425<template> <div class="main"> <span>我是一个测试生命周期的组件</span> <div> <span id="tip_text">点击测试 {{ num }} </span> <button @click="onClickCount">点击</button> </div> </div></template><script>{ data () { return { name: '我是谁', num: 1, } }, methods: { onClickCount() { this.num++; } }}</script> 123456**** parent beforeUpdate**** parent beforeUpdate -this.name: 我是谁**** parent beforeUpdate -test dom: <span id="tip_text">点击测试 3 </span>**** parent updated**** parent updated -this.name: 我是谁**** parent updated -test dom: <span id="tip_text">点击测试 3 </span> 组件嵌套的生命周期会如何表现呢?先来两个组件 父组件 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455<template> <div class="main"> <span>我是一个测试生命周期的组件</span> <div> <span>输入的内容:{{ title || '-' }}</span> </div> <SlInput @slInput="onSlInput" :title="title"/> </div></template><script>import SlInput from './SlInput.vue'export default { name: 'LifeCycle', components: { SlInput }, data () { return { name: '我是谁', title: '', num: 1, } }, beforeCreate() { console.log('**** parent beforeCreate'); }, created() { console.log('**** parent created'); }, beforeMount() { console.log('**** parent beforeMount'); }, mounted() { console.log('**** parent mounted'); }, beforeUpdate() { console.log('**** parent beforeUpdate'); }, updated() { console.log('**** parent updated'); }, beforeDestroy() { console.log('**** parent beforeDestroy'); }, destroyed() { console.log('**** parent destroyed'); }, methods: { onSlInput(value) { this.title = value } }}</script> 子组件 123456789101112131415161718192021222324252627282930313233343536373839404142<template> <input type="text" :value="title" @input="onInput"></template><script>export default { name: 'SlInput', data() { return {} }, props: ['title'], beforeCreate() { console.log('**** children beforeCreate'); }, created() { console.log('**** children created'); }, beforeMount() { console.log('**** children beforeMount'); }, mounted() { console.log('**** children mounted'); }, beforeUpdate() { console.log('**** children beforeUpdate'); }, updated() { console.log('**** children updated'); }, beforeDestroy() { console.log('**** children beforeDestroy'); }, destroyed() { console.log('**** children destroyed'); }, methods: { onInput(event) { this.$emit('slInput', event.target.value) } }}</script> 同样再次模拟页面进入,控制台输出如下:12345678**** parent beforeCreate**** parent created**** parent beforeMount**** children beforeCreate**** children created**** children beforeMount**** children mounted**** parent mounted 可以发现,父子组件嵌套情况下,组件实例化时: 父组件先进行实例化,在模板挂载之前,进行子组件实例化,父组件会等待子组件实例化完毕,再进行父组件自己的模板挂载beforeCreate >created >beforeMount >children-beforeCreate >children-created >children-beforeMount >children-mounted >mounted 模拟页面离开,控制台输出如下:1234**** parent beforeDestroy**** children beforeDestroy**** children destroyed**** parent destroyed 父子组件嵌套,父组件在销毁时: 父组件先准备进行销毁,进入 调用钩子 beforeDestroy,等待子组件销毁完毕,父组件继续执行销毁,调用钩子 destroyed,执行顺序如下:parent beforeDestroy >children-beforeDestroy >children-destroyed >destroyed 何时调用 update 呢?尝试子组件和父组件通信,修改父组件的 data1234**** parent beforeUpdate**** children beforeUpdate**** children updated**** parent updated 尝试父组件修改子组件data父组件组件添加以下内容: 1234567891011121314 <div> <button @click="onClickSetChildrenCount">点击修改子组件data</button> </div><script> { methods: { onClickSetChildrenCount() { this.$refs.SlInput.childrenCount++ } }}</script> 子组件: 12345678910111213 <div> 子组件data修改测试:{{ childrenCount }}</div><script> { data() { return { childrenCount: 1 } }}</script> 点击按钮触发,修改子组件的data,控制台输出如下 12**** children beforeUpdate**** children updated 此时没有父组件的参与 单修改父组件data,会触发子组件的 update 吗?还是那个点击测试 123456789101112131415161718<div> <span id="tip_text">点击测试 {{ num }} </span> <button @click="onClickCount">点击</button> </div><script> { data() { return { childrenCount: 1 } } methods: { onClickCount() { this.num++; }, }}</script> 点击触发事件,控制台输出 12**** parent beforeUpdate**** parent updated 此时没有子组件的参与 子组件创建和销毁,父组件生命周期是个什么表现?123456789101112131415161718192021222324252627<template> <div class="main"> <h1>我是一个测试生命周期的组件</h1> <hr /> 子组件在这: <SlInput id="tip_text" :title="title" v-if="isShow" /> <hr /> <div> <button @click="onClickToggle">点击{{ isShow ? '隐藏' : '显示' }}</button> </div> </div></template><script> { data() { return { isShow: true } } methods: { onClickToggle() { this.isShow = !this.isShow }, }}</script> 点击隐藏子组件 123456**** parent beforeUpdate**** parent beforeUpdate -test dom: <div id="tip_text">…</div>**** children beforeDestroy**** children destroyed**** parent updated**** parent updated -test dom: null 模拟子组件销毁,可以看出: 子组件销毁之前,父组件进入 beforeUpdate; 等子组件销毁完毕,继续进行父组件的 updated,父组件更新完毕; 点击显示子组件 12345678**** parent beforeUpdate**** parent beforeUpdate -test dom: null**** children beforeCreate**** children created**** children beforeMount**** children mounted**** parent updated**** parent updated -test dom: <div id="tip_text">…</div> 父组件内,模拟初始化子组件,可以看出: 子组件实例化之前,父组件进入 beforeUpdate; 等子组件挂载完毕,继续进行父组件的 updated,父组件更新完毕; 组件嵌套时,生命周期总结 子组件的创建销毁会触发父组件的 update; 子组件的 update,不一定会触发父组件的 update; 父组件的 update,不一定会触发子组件的 update;","categories":[{"name":"Vue","slug":"Vue","permalink":"https://blog.magicyou.cn/categories/Vue/"}],"tags":[{"name":"Vue","slug":"Vue","permalink":"https://blog.magicyou.cn/tags/Vue/"}]},{"title":"算法-四数之和","slug":"算法-四数之和","date":"2021-09-28T13:00:02.000Z","updated":"2021-09-28T13:00:02.000Z","comments":true,"path":"2021/09/28/算法-四数之和/","link":"","permalink":"https://blog.magicyou.cn/2021/09/28/%E7%AE%97%E6%B3%95-%E5%9B%9B%E6%95%B0%E4%B9%8B%E5%92%8C/","excerpt":"leetcode-四数之和https://leetcode-cn.com/problems/4sum/坚持每天进步","text":"leetcode-四数之和https://leetcode-cn.com/problems/4sum/坚持每天进步 四数之和给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] : 0 <= a, b, c, d < na、b、c 和 d 互不相同nums[a] + nums[b] + nums[c] + nums[d] == target你可以按 任意顺序 返回答案 。 示例 1: 12输入:nums = [1,0,-1,0,-2,2], target = 0输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]] 示例 2: 12输入:nums = [2,2,2,2,2], target = 8输出:[[2,2,2,2]] 提示: 1 <= nums.length <= 200 -109 <= nums[i] <= 109 -109 <= target <= 109 解题思路 定义双指针,避免暴力循环解题; 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455/** * @param {number[]} nums * @param {number} target * @return {number[][]} */var fourSum = function (nums, target) { nums = nums.sort((a, b) => (a - b)); let res = []; let len = nums.length; for (let i = 0; i < len - 2; i++) { if (i > 0 && nums[i] == nums[i - 1]) { continue; } for (let j = i + 1; j < len - 2; j++) { if (j > i + 1 && nums[j] == nums[j - 1]) { continue; } let firstNum = nums[i]; let secondNum = nums[j]; const restVal = target - firstNum - secondNum let leftIndex = j + 1; let rightIndex = len - 1; while(leftIndex < rightIndex) { let sum = nums[leftIndex] + nums[rightIndex]; if (restVal === sum) { res.push([firstNum, secondNum, nums[leftIndex], nums[rightIndex]]); while (nums[leftIndex] === nums[leftIndex + 1]) { leftIndex++; } while (nums[rightIndex] === nums[rightIndex - 1]) { rightIndex--; } leftIndex++; rightIndex--; } else if (sum < restVal) { leftIndex++; } else if (sum > restVal) { rightIndex--; } } } } return res;};","categories":[{"name":"算法","slug":"算法","permalink":"https://blog.magicyou.cn/categories/%E7%AE%97%E6%B3%95/"}],"tags":[{"name":"算法","slug":"算法","permalink":"https://blog.magicyou.cn/tags/%E7%AE%97%E6%B3%95/"}]},{"title":"算法-最接近的三数之和","slug":"算法-最接近的三数之和","date":"2021-09-27T13:00:02.000Z","updated":"2021-09-27T13:00:02.000Z","comments":true,"path":"2021/09/27/算法-最接近的三数之和/","link":"","permalink":"https://blog.magicyou.cn/2021/09/27/%E7%AE%97%E6%B3%95-%E6%9C%80%E6%8E%A5%E8%BF%91%E7%9A%84%E4%B8%89%E6%95%B0%E4%B9%8B%E5%92%8C/","excerpt":"leetcode-最接近的三数之和https://leetcode-cn.com/problems/3sum-closest/坚持每天进步","text":"leetcode-最接近的三数之和https://leetcode-cn.com/problems/3sum-closest/坚持每天进步 最接近的三数之和给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。 示例: 12输入:nums = [-1,2,1,-4], target = 1输出:2 解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。 提示: 3 <= nums.length <= 10^3 -10^3 <= nums[i] <= 10^3 -10^4 <= target <= 10^4 解题思路 定义双指针,避免暴力循环解题; 1234567891011121314151617181920212223242526272829303132333435363738394041/** * @param {number[]} nums * @param {number} target * @return {number} */var threeSumClosest = function (nums, target) { nums = nums.sort((a, b) => (a - b)); let min = target; let res = 0; for (let len = nums.length, i = 0; i < len - 2; i++) { const currNum = nums[i]; let leftIndex = i + 1; let rightIndex = len - 1; if (i === 0) { min = Math.abs(target - (currNum + nums[1] + nums[2])); } while (leftIndex < rightIndex) { const sum = (currNum + nums[leftIndex] + nums[rightIndex]); const cha = target - sum; const chaAbs = Math.abs(cha); if (chaAbs == 0) { res = sum; min = 0; break; } else if (chaAbs <= min) { min = chaAbs; res = sum; } if (cha > 0) { leftIndex++; } else if (cha < 0) { rightIndex--; } } } return res;};","categories":[{"name":"算法","slug":"算法","permalink":"https://blog.magicyou.cn/categories/%E7%AE%97%E6%B3%95/"}],"tags":[{"name":"算法","slug":"算法","permalink":"https://blog.magicyou.cn/tags/%E7%AE%97%E6%B3%95/"}]},{"title":"算法-20210926-三数之和","slug":"算法-三数之和","date":"2021-09-26T13:00:02.000Z","updated":"2021-09-26T13:00:02.000Z","comments":true,"path":"2021/09/26/算法-三数之和/","link":"","permalink":"https://blog.magicyou.cn/2021/09/26/%E7%AE%97%E6%B3%95-%E4%B8%89%E6%95%B0%E4%B9%8B%E5%92%8C/","excerpt":"leetcode-三数之和https://leetcode-cn.com/problems/3sum/坚持每天进步","text":"leetcode-三数之和https://leetcode-cn.com/problems/3sum/坚持每天进步 三数之和给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。 注意:答案中不可以包含重复的三元组。 示例 1: 12输入:nums = [-1,0,1,2,-1,-4]输出:[[-1,-1,2],[-1,0,1]] 示例 2: 12输入:nums = []输出:[] 示例 3: 12输入:nums = [0]输出:[] 提示: 0 <= nums.length <= 3000 -105 <= nums[i] <= 105 解题思路 定义双指针,避免暴力循环解题; 使用逻辑判断,有效的避免重复数据,同事提高循环效率; 12345678910111213141516171819202122232425262728293031323334353637383940414243/** * @param {number[]} nums * @return {number[][]} */var threeSum = function (nums) { let res = []; const len = nums.length; nums = nums.sort((a, b) => (a - b)); for (let i = 0; i < len - 2; i++) { if (i > 0 && nums[i] === nums[i - 1]) { continue; } // 定义双指针 let leftIndex = i + 1; let rightIndex = len - 1; while (leftIndex < rightIndex) { const sum = nums[i] + nums[leftIndex] + nums[rightIndex]; if (sum === 0) { res.push([ nums[i], nums[leftIndex], nums[rightIndex] ]); while (nums[leftIndex] == nums[leftIndex + 1]) { leftIndex++; } while (nums[rightIndex] == nums[rightIndex - 1]) { rightIndex--; } leftIndex++; rightIndex--; } else if (sum > 0) { rightIndex--; } else if (sum < 0) { leftIndex++; } } } return res;};","categories":[{"name":"算法","slug":"算法","permalink":"https://blog.magicyou.cn/categories/%E7%AE%97%E6%B3%95/"}],"tags":[{"name":"算法","slug":"算法","permalink":"https://blog.magicyou.cn/tags/%E7%AE%97%E6%B3%95/"}]},{"title":"算法-最长公共前缀","slug":"算法-最长公共前缀","date":"2021-09-25T13:00:02.000Z","updated":"2021-09-25T13:00:02.000Z","comments":true,"path":"2021/09/25/算法-最长公共前缀/","link":"","permalink":"https://blog.magicyou.cn/2021/09/25/%E7%AE%97%E6%B3%95-%E6%9C%80%E9%95%BF%E5%85%AC%E5%85%B1%E5%89%8D%E7%BC%80/","excerpt":"leetcode-最长公共前缀https://leetcode-cn.com/problems/longest-common-prefix/坚持每天进步","text":"leetcode-最长公共前缀https://leetcode-cn.com/problems/longest-common-prefix/坚持每天进步 最长公共前缀编写一个函数来查找字符串数组中的最长公共前缀。 如果不存在公共前缀,返回空字符串 “”。 示例 1: 12输入:strs = ["flower","flow","flight"]输出:"fl" 示例 2: 123输入:strs = ["dog","racecar","car"]输出:""解释:输入不存在公共前缀。 提示: 1 <= strs.length <= 200 0 <= strs[i].length <= 200 strs[i] 仅由小写英文字母组成 1234567891011121314151617181920212223242526272829/** * @param {string[]} strs * @return {string} */var longestCommonPrefix = function (strs) { if (strs.length === 1) { return strs[0] } let prefix = ''; let firstStr = strs[0]; for (let len = firstStr.length + 1, i = 1; i < len; i++) { const temPrefix = firstStr.slice(0, i); // console.log('temPrefix:', temPrefix); let hasFlag = true; for (let len2 = strs.length, j = 1; j < len2; j++) { if (strs[j].indexOf(temPrefix) !== 0) { hasFlag = false; break; } } if (!hasFlag) { break; } else { prefix = temPrefix; } } return prefix;}; 暂时没有更好的办法,暴力解题","categories":[{"name":"算法","slug":"算法","permalink":"https://blog.magicyou.cn/categories/%E7%AE%97%E6%B3%95/"}],"tags":[{"name":"算法","slug":"算法","permalink":"https://blog.magicyou.cn/tags/%E7%AE%97%E6%B3%95/"}]},{"title":"算法-20210924-盛最多水的容器","slug":"算法-盛最多水的容器","date":"2021-09-24T13:00:02.000Z","updated":"2021-09-24T13:00:02.000Z","comments":true,"path":"2021/09/24/算法-盛最多水的容器/","link":"","permalink":"https://blog.magicyou.cn/2021/09/24/%E7%AE%97%E6%B3%95-%E7%9B%9B%E6%9C%80%E5%A4%9A%E6%B0%B4%E7%9A%84%E5%AE%B9%E5%99%A8/","excerpt":"leetcode-盛最多水的容器https://leetcode-cn.com/problems/container-with-most-water/坚持每天进步","text":"leetcode-盛最多水的容器https://leetcode-cn.com/problems/container-with-most-water/坚持每天进步 盛最多水的容器给你 n 个非负整数 a1,a2,…,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。 说明:你不能倾斜容器。 示例 1: 123输入:[1,8,6,2,5,4,8,3,7]输出:49 解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。 示例 2: 12输入:height = [1,1]输出:1 示例 3: 12输入:height = [4,3,2,1,4]输出:16 示例 4: 12输入:height = [1,2,1]输出:2 提示: n == height.length 2 <= n <= 105 0 <= height[i] <= 104 解题123456789101112131415161718192021222324252627282930/** * @param {number[]} height * @return {number} */var maxArea = function (height) { let max = 0; const length = height.length; let startIndex = 0; let endIndex = length - 1; while (startIndex < endIndex) { let len = endIndex - startIndex; let h = height[startIndex]; if (height[startIndex] > height[endIndex]) { h = height[endIndex]; endIndex--; } else { h = height[startIndex]; startIndex++; } const sum = len * h; if (sum > max) { max = sum } } return max;}; 前后两个游标向中间移动寻求最优解","categories":[{"name":"算法","slug":"算法","permalink":"https://blog.magicyou.cn/categories/%E7%AE%97%E6%B3%95/"}],"tags":[{"name":"算法","slug":"算法","permalink":"https://blog.magicyou.cn/tags/%E7%AE%97%E6%B3%95/"}]},{"title":"算法-整数转罗马数字","slug":"算法-整数转罗马数字","date":"2021-09-23T13:00:02.000Z","updated":"2021-09-23T13:00:02.000Z","comments":true,"path":"2021/09/23/算法-整数转罗马数字/","link":"","permalink":"https://blog.magicyou.cn/2021/09/23/%E7%AE%97%E6%B3%95-%E6%95%B4%E6%95%B0%E8%BD%AC%E7%BD%97%E9%A9%AC%E6%95%B0%E5%AD%97/","excerpt":"leetcode-整数转罗马数字https://leetcode-cn.com/problems/integer-to-roman/坚持每天进步","text":"leetcode-整数转罗马数字https://leetcode-cn.com/problems/integer-to-roman/坚持每天进步 整数转罗马数字罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。 12345678字符 数值I 1V 5X 10L 50C 100D 500M 1000 例如, 罗马数字 2 写做 II ,即为两个并列的 1。12 写做 XII ,即为 X + II 。 27 写做 XXVII, 即为 XX + V + II 。 通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况: I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。给你一个整数,将其转为罗马数字。 示例 1: 12输入: num = 3输出: "III" 示例 2: 12输入: num = 4输出: "IV" 示例 3: 12输入: num = 9输出: "IX" 示例 4: 123输入: num = 58输出: "LVIII"解释: L = 50, V = 5, III = 3. 示例 5: 123输入: num = 1994输出: "MCMXCIV"解释: M = 1000, CM = 900, XC = 90, IV = 4. 提示: 1 <= num <= 3999 解题1234567891011121314151617181920/** * @param {number} num * @return {string} */var intToRoman = function (num) { let list1 = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1] let list2 = ['M', 'CM', 'D', 'CD', 'C', 'XC', 'L', 'XL', 'X', 'IX', 'V', 'IV', 'I'] let len = list1.length, index = 0; let res = ''; while (index < len) { while (num >= list1[index]) { res += list2[index]; num -= list1[index]; } index++; } return res;}; 贪心算法?","categories":[{"name":"算法","slug":"算法","permalink":"https://blog.magicyou.cn/categories/%E7%AE%97%E6%B3%95/"}],"tags":[{"name":"算法","slug":"算法","permalink":"https://blog.magicyou.cn/tags/%E7%AE%97%E6%B3%95/"}]},{"title":"React 简单入门","slug":"react-简单入门","date":"2021-04-13T13:33:00.000Z","updated":"2021-04-13T13:33:00.000Z","comments":true,"path":"2021/04/13/react-简单入门/","link":"","permalink":"https://blog.magicyou.cn/2021/04/13/react-%E7%AE%80%E5%8D%95%E5%85%A5%E9%97%A8/","excerpt":"React 简单入门","text":"React 简单入门 目标: 类比vue入门 react 简单入门,着重react使用方法 教程暂时不议论 redux 安装123npx create-react-app my-appcd my-appnpm start 注意:第一行的 npx 不是拼写错误 —— 它是 npm 5.2+ 附带的 package 运行工具。 安装结束之后,的 app.js , 一起见识一下 JSX 1234567891011121314151617181920212223242526import logo from './logo.svg';import './App.css';function App() { return ( <div className="App"> <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <p> Edit <code>src/App.js</code> and save to reload. </p> <a className="App-link" href="https://reactjs.org" target="_blank" rel="noopener noreferrer" > Learn React </a> </header> </div> );}export default App; 认识 JSX1const element = <h1>Hello, world!</h1>; 123456const element = ( <div> <h1>Hello!</h1> <h2>Good to see you here.</h2> </div>); 按照管官方的话说:既不是字符串也不是 HTML React 认为渲染逻辑本质上与其他 UI 逻辑内在耦合 在 JavaScript 代码中将 JSX 和 UI 放在一起时,会在视觉上有辅助作用 eact DOM 在渲染所有输入内容之前,默认会进行转义, 可以有效地防止 XSS(cross-site-scripting, 跨站脚本)攻击,防止注入攻击 1. 在 JSX 中, 可以在大括号内放置任何有效的 JavaScript 表达式123456const name = 'lxl';const element = ( <h1> Hello, {name}! </h1>); 2. JSX 特定属性 使用引号,来将属性值指定为字符串字面量 1const element = <div tabIndex="0"></div>; 使用大括号,来在属性值中插入一个 JavaScript 表达式 1const element = <img src={user.avatarUrl}></img>; 注意: 在属性中嵌入 JavaScript 表达式时,不要在大括号外面加上引号。你应该仅使用引号(对于字符串值)或大括号(对于表达式)中的一个,对于同一属性不能同时使用这两种符号 因为 JSX 语法上更接近 JavaScript 而不是 HTML,所以 React DOM 使用 camelCase(小驼峰命名)来定义属性的名称,而不使用 HTML 属性名称的命名约定,例如:data-id 写为 dataId JSX 里的 class 是个关键字,所以使用 className 代替 class。 JSX 原理简单来说就是对象,和vue的虚拟DOM一样,你写的想模板的jsx代码会转换成类似下边的结构 12345678// 注意:这是简化过的结构const element = { type: 'h1', props: { className: 'greeting', children: 'Hello, world!' }}; Babel 会把 JSX 转译成一个名为 React.createElement() 函数调用 等效的写法 12345const element = ( <h1 className="greeting"> Hello, world! </h1>); 12345const element = React.createElement( 'h1', {className: 'greeting'}, 'Hello, world!'); 函数组件与 class 组件函数组件最先安装好 react 后的 app.js,就是一个简单的函数组件,本质上就是一个函数,同样也能接收参数 123function Welcome(props) { return <h1>Hello, {props.name}</h1>;} class 组件上文说的 jsx 特性里 ‘JSX 里的 class 是个关键字,所以使用 className 代替 class’,是有点误解,事实上 class 是 ES6 的关键词,声明一个 类: 12345class Welcome extends React.Component { render() { return <h1>Hello, {this.props.name}</h1>; }} State和vue一样,react也需要存储很多状态,通常是一些页面需要渲染的数据、响应页面元素的变量等,react的状态需要 __state__管理(高阶可以结合redux): 12345678910111213141516class UserInfo extends React.Component { constructor(props) { super(props); this.state = {name: 'lxl', age: 18}; } render() { return ( <div> <h1>User Info</h1> <h2>name: {this.state.name}.</h2> <h2>age: {this.state.age}.</h2> </div> ); }} 与vue不同,state并没有被组件或者对象双向绑定,无法响应式的直接‘this.state.name = ‘www’’,设置name,这样是错误的,需要调用 setState: 123this.setState({ date: new Date()}); this.setState({ date: new Date() }); 数据自顶向下流动引用一下菜鸟的文字: 父组件或子组件都不能知道某个组件是有状态还是无状态,并且它们不应该关心某组件是被定义为一个函数还是一个类。这就是为什么状态通常被称为局部或封装。 除了拥有并设置它的组件外,其它组件不可访问。 任何状态始终由某些特定组件所有,并且从该状态导出的任何数据或 UI 只能影响树中下方的组件 生命周期React v16.3之前组件的生命周期可分成三个状态: Mounting:已插入真实 DOM Updating:正在被重新渲染 Unmounting:已移出真实 DOM | 生命周期 | 描述 | | ------------------------- | ------------------------------------------------------------ | | componentWillMount | 只会在装载之前调用一次,在 `render` 之前调用,你可以在这个方法里面调用 `setState` 改变状态,并且不会导致额外调用一次 `render` | | componentDidMount | 只会在装载完成之后调用一次,在 `render` 之后调用,从这里开始可以通过 `ReactDOM.findDOMNode(this)` 获取到组件的 DOM 节点。以在这个方法中调用setTimeout, setInterval或者发送AJAX请求等操作(防止异步操作阻塞UI)。 | | componentWillReceiveProps | 更新组件触发,在组件接收到一个新的 prop (更新后)时被调用。这个方法在初始化render时不会被调用。 | | shouldComponentUpdate | 更新组件触发,返回一个布尔值。在组件接收到新的props或者state时被调用。在初始化时或者使用forceUpdate时不被调用。<br/>可以在你确认不需要更新组件时使用。 | | componentWillUpdate | 更新组件触发,在组件接收到新的props或者state但还没有render时被调用。在初始化时不会被调用。 | | componentDidUpdate | 更新组件触发在组件完成更新后立即调用。在初始化时不会被调用。 | | componentWillUnmount | 卸载组件触发,在组件从 DOM 中移除之前立刻被调用。 | React v16.3 之后,引入了两个新的生命周期函数: getDerivedStateFromProps getSnapshotBeforeUpdate 同时deprecate了一组生命周期API,包括: componentWillReceiveProps componentWillMount componentWillUpdate 看例子,userInfo。 事件处理事件处理上和 DOM 元素的很相似,不同的是: React 事件的命名采用小驼峰式(camelCase),而不是纯小写。123<button onClick={activateLasers}> Activate Lasers</button> onBlur、onInput … 使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。 123456789101112function ActionLink() { function handleClick(e) { e.preventDefault(); console.log('The link was clicked.'); } return ( <a href="#" onClick={handleClick}> Click me </a> );} 来自官方忠告: 你必须谨慎对待 JSX 回调函数中的 this,在 JavaScript 中,class 的方法默认不会绑定 this。如果你忘记绑定 this.handleClick 并把它传入了 onClick,当你调用这个函数的时候 this 的值为 undefined。这并不是 React 特有的行为;这其实与 JavaScript 函数工作原理有关。通常情况下,如果你没有在方法后面添加 (),例如 onClick={this.handleClick},你应该为这个方法绑定 this。 如果觉得使用 bind 很麻烦,这里有两种方式可以解决。 如果你正在使用实验性的 public class fields 语法,你可以使用 class fields 正确的绑定回调函数 123456789101112131415class LoggingButton extends React.Component {// 此语法确保 `handleClick` 内的 `this` 已被绑定。// 注意: 这是 *实验性* 语法。handleClick = () => { console.log('this is:', this);}render() { return ( <button onClick={this.handleClick}> Click me </button> );}} 如果你没有使用 class fields 语法,你可以在回调中使用箭头函数 1234567891011121314class LoggingButton extends React.Component {handleClick() { console.log('this is:', this);}render() { // 此语法确保 `handleClick` 内的 `this` 已被绑定。 return ( <button onClick={() => this.handleClick()}> Click me </button> );}} 注意:此语法问题在于每次渲染 LoggingButton 时都会创建不同的回调函数。在大多数情况下,这没什么问题,但如果该回调函数作为 prop 传入子组件时,这些组件可能会进行额外的重新渲染。我们通常建议在构造器中绑定或使用 class fields 语法来避免这类性能问题 条件渲染 可以直接三目运算符: 12345678render() { const showUser = false; return ( <div> {showUser ? <UserInfo userData={userData}/> : <span>啥也没有</span>} </div> ) } 可以if else: 12345678910111213141516toggleUserInfo () { const showUser = false; if (showUser) { return <UserInfo />; } else { return null }}render() { return ( <div> {toggleUserInfo} </div> )} 运算符也是可以的12345678render() { const showUser = false; return ( <div> {showUser && <UserInfo userData={userData}/>} </div> ) } 没有做不到,只有想不到 列表渲染12345678910111213141516render () { const links = [ {'name': 'Blog', icon:'iconbiji', linkUrl: 'https://blog.magicyou.cn/'}, {'name': 'Cloud', icon:'iconwenjianjia', linkUrl: ''}, {'name': 'Frp', icon:'icondiannao', linkUrl: ''}, {'name': 'Pi', icon:'iconcaomeigan', linkUrl: 'http://pi.magicyou.cn/'}, ]; <Row> {links.map((item, index) => ( <Col className="gutter-row" span={6} key={index}> <p className="height-100">{ item.name }</p> </Col> ))} </Row>} 状态提升在 React 中,将多个组件中需要共享的 state 向上移动到它们的最近共同父组件中,便可实现共享 state,这就是所谓的“状态提升”。 查看温度换算例子 包含关系有些组件无法提前知晓它们子组件的具体内容。在 Sidebar(侧边栏)和 Dialog(对话框)等展现通用容器(box)的组件中特别容易遇到这种情况 查看组合组件弹窗示例 路由http://react-guide.github.io/react-router-cn/docs/guides/basics/RouteConfiguration.html","categories":[],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/tags/JavaScript/"},{"name":"react","slug":"react","permalink":"https://blog.magicyou.cn/tags/react/"}]},{"title":"Linux-安装git及秘钥生成","slug":"Linux-安装git及秘钥生成","date":"2021-02-03T10:23:00.000Z","updated":"2021-02-03T10:23:00.000Z","comments":true,"path":"2021/02/03/Linux-安装git及秘钥生成/","link":"","permalink":"https://blog.magicyou.cn/2021/02/03/Linux-%E5%AE%89%E8%A3%85git%E5%8F%8A%E7%A7%98%E9%92%A5%E7%94%9F%E6%88%90/","excerpt":"在Linux系统上安装Git","text":"在Linux系统上安装Git 前言Git是目前流行的非常好用的版本控制工具,这里介绍安装和生成秘钥 1. yum安装git在Linux上是有yum安装Git,非常简单,只需要一行命令 1yum -y install git 123456789101112131415161718192021222324252627282930............................(4/5): git-core-doc-2.27.0-1.el8.noarch.rpm 15 MB/s | 2.5 MB 00:00 (5/5): git-core-2.27.0-1.el8.x86_64.rpm 22 MB/s | 5.7 MB 00:00 --------------------------------------------------------------------------------------------------------------------------------------------------------------Total 33 MB/s | 8.5 MB 00:00 Running transaction checkTransaction check succeeded.Running transaction testTransaction test succeeded.Running transaction Preparing : 1/1 Installing : git-core-2.27.0-1.el8.x86_64 1/5 Installing : git-core-doc-2.27.0-1.el8.noarch 2/5 Installing : perl-Error-1:0.17025-2.el8.noarch 3/5 Installing : perl-Git-2.27.0-1.el8.noarch 4/5 Installing : git-2.27.0-1.el8.x86_64 5/5 Running scriptlet: git-2.27.0-1.el8.x86_64 5/5 Verifying : git-2.27.0-1.el8.x86_64 1/5 Verifying : git-core-2.27.0-1.el8.x86_64 2/5 Verifying : git-core-doc-2.27.0-1.el8.noarch 3/5 Verifying : perl-Error-1:0.17025-2.el8.noarch 4/5 Verifying : perl-Git-2.27.0-1.el8.noarch 5/5 Installed: git-2.27.0-1.el8.x86_64 git-core-2.27.0-1.el8.x86_64 git-core-doc-2.27.0-1.el8.noarch perl-Error-1:0.17025-2.el8.noarch perl-Git-2.27.0-1.el8.noarch Complete! 2. 生成公钥12345ssh-keygen -t rsa -C "[email protected]" #-C注释为了区分秘钥,也可以不写# Generating public/private rsa key pair...# 三次回车即可生成 ssh key 查看生成的公钥 12cat ~/.ssh/id_rsa.pub","categories":[{"name":"Linux","slug":"Linux","permalink":"https://blog.magicyou.cn/categories/Linux/"}],"tags":[{"name":"Linux","slug":"Linux","permalink":"https://blog.magicyou.cn/tags/Linux/"},{"name":"Centos","slug":"Centos","permalink":"https://blog.magicyou.cn/tags/Centos/"}]},{"title":"java-java基础-标识符","slug":"java-java基础-标识符","date":"2020-12-10T05:34:12.000Z","updated":"2020-12-10T05:34:23.000Z","comments":true,"path":"2020/12/10/java-java基础-标识符/","link":"","permalink":"https://blog.magicyou.cn/2020/12/10/java-java%E5%9F%BA%E7%A1%80-%E6%A0%87%E8%AF%86%E7%AC%A6/","excerpt":"什么是标识符:包、类、变量、方法…等等,只要是起名字的地方吗,那个名字就是标识符","text":"什么是标识符:包、类、变量、方法…等等,只要是起名字的地方吗,那个名字就是标识符 什么是标识符包、类、变量、方法…等等,只要是起名字的地方吗,那个名字就是标识符 标识符定义的规则 四个可以(组成部分) 数字 字母 下划线_ 美元符号$ 注意: 字母概念比较宽泛,指的是英文字母,汉子,日语,俄语…,但是我们一般起名字尽量使用英文字母 两个不可以 不可以以数字开头 不可以使用java关键字 见名知意:增加可读性 大小写敏感:int a; int A; 遵照驼峰命名: 类名: 首字母大写,其余遵循驼峰命名 方法名、变量名:小驼峰 包名:全部小写,不遵循驼峰命名 长度不限制,但不建议太长","categories":[{"name":"JAVA","slug":"JAVA","permalink":"https://blog.magicyou.cn/categories/JAVA/"}],"tags":[{"name":"JAVA","slug":"JAVA","permalink":"https://blog.magicyou.cn/tags/JAVA/"}]},{"title":"树莓派-内网穿透","slug":" 树莓派-内网穿透","date":"2020-10-03T13:33:00.000Z","updated":"2020-10-03T13:33:00.000Z","comments":true,"path":"2020/10/03/ 树莓派-内网穿透/","link":"","permalink":"https://blog.magicyou.cn/2020/10/03/%20%E6%A0%91%E8%8E%93%E6%B4%BE-%E5%86%85%E7%BD%91%E7%A9%BF%E9%80%8F/","excerpt":"树莓派只能在家里玩?当然不是,如何在外网访问家里的树莓派呢?","text":"树莓派只能在家里玩?当然不是,如何在外网访问家里的树莓派呢? 折腾树莓派,安装了宝塔,有了基本的 web 环境;安装了 homeassistant,没有小米mesh蓝牙网关设备也能在线看到小米温湿度数值;安装了 jenkins,但是不能用各种错误;我想如果不在家也能连上树莓派,有什么方案呢?需要完成这样一个工作:内网穿透。 参考地址: https://blog.csdn.net/weixin_44845947/article/details/108160292内网穿透有很多种方法,这里主要说以下三种:如果家里有公网IP的话,可以用路由器做映射,进而实现内网穿透;natapp、ngrok等商业化或非商业化内网穿透工具,用的其实是人家的公网IP;买云主机服务器以获得公网IP这三种方法有一个共性就是,要想在公网访问到树莓派,必须要有一个公网的IP,而在这三种方法里,最省钱的无疑是第一种,操作也很简单,最多需要做个动态解析。唯一的问题在于,你很可能没有公网IP,那就无法使用第一种方法了。 最终选择 frp 内网穿透 1. 下载并部署Frp下载https://github.com/fatedier/frp/releases,找到对应的版本,arm64版本和普通版本,如下图: 部署分别解压 将frpc和frpc.ini两个文件传到树莓派上(我放在了/home/pi/目录下) frps和frps.ini两个文件放云主机(我放在了/root/目录下) 2. 配置frpc123456789101112131415161718192021222324[common]server_addr = XXX.XXX.XXX.XXXserver_port = 7000privilege_token = 12345678log_file = /home/pi/log/frpc.loglog_level = infolog_max_days = 3pool_count = 5tcp_mux = true[ssh]type = tcplocal_ip = 127.0.0.1local_port = 22remote_port = 6000[vnc]type = tcplocal_ip = 127.0.0.1local_port = 5900remote_port = 5901 添加日志目录 1234cd /home/pimkdir logcd logtouch frpc.log 启动frpc 1./frpc -c ./frpc.ini 3. 配置frps1234567891011121314151617[common]subdomain_host = magicyou.cnbind_port = 7000vhost_http_port = 8080vhost_https_port = 444dashboard_port = 7500dashboard_user = userdashboard_pwd = pwdprivilege_token = 12345678log_file = /root/frps/log/frps.loglog_level = infolog_max_days = 3max_pool_count = 5authentication_timeout = 900tcp_mux = true 添加日志目录 1234cd /rootmkdir logcd logtouch frps.log 启动frps 1./frps -c ./frps.ini 注意: privilege_token frps 和 frpc 一致; 4. 端口加入防火墙白名单以上配置frpc和frps的所有端口都添加入云主机配置的安全组,当然记得在宝塔系统加入端口开放,否则云主机无法和树莓派通信 5. 尝试访问 XXX.XXX.XXX.XXX:7500输入配置的 dashboard_user,dashboard_pwd; 可以看到 到此,内网已经成功穿透,已经可以远程访问家里的树莓派。 6. 添加开机启动项以云主机 frps 为例。 创建启动文件12sudo vim /lib/systemd/system/frps.service 之后添加下面的内容12345678910111213[Unit]Description=frps serviceAfter=network.target network-online.target syslog.targetWants=network.target network-online.target[Service]Type=simpleRestart=on-failureRestartSec=5sExecStart=/root/frps/frps -c /root/frps/frps.ini #对应自己安装的frps路径[Install]WantedBy=multi-user.target wq保存退出,之后就可以使用如下指令:12345sudo systemctl start frps.service #开启frps服务sudo systemctl enable frps.service #设置开机自启动sudo systemctl restart frps.service #重启frpssudo systemctl status frps.service #查看frps状态 7. 树莓派就顺利的连入了互联网","categories":[{"name":"树莓派","slug":"树莓派","permalink":"https://blog.magicyou.cn/categories/%E6%A0%91%E8%8E%93%E6%B4%BE/"}],"tags":[{"name":"树莓派","slug":"树莓派","permalink":"https://blog.magicyou.cn/tags/%E6%A0%91%E8%8E%93%E6%B4%BE/"},{"name":"Linux","slug":"Linux","permalink":"https://blog.magicyou.cn/tags/Linux/"}]},{"title":"javascript-单向链表实现","slug":"javascript-单向链表实现","date":"2020-08-25T08:23:56.000Z","updated":"2020-08-26T12:23:56.000Z","comments":true,"path":"2020/08/25/javascript-单向链表实现/","link":"","permalink":"https://blog.magicyou.cn/2020/08/25/javascript-%E5%8D%95%E5%90%91%E9%93%BE%E8%A1%A8%E5%AE%9E%E7%8E%B0/","excerpt":"js 实现一个单向链表","text":"js 实现一个单向链表 先来复习几个小知识123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165class Node { constructor(data) { this.data = data; this.prev = null; this.next = null; }}// 单链表class SingleList { constructor() { this.size = 0; // 单链表的长度 this.head = new Node('head'); // 表头节点 this.currNode = ''; // 当前节点的指向 } // 判断单链表是否为空 isEmpty() { return this.size === 0; } // 获取单链表的最后一个节点 findLast() { let currNode = this.head; while (currNode.next) { currNode = currNode.next; } return currNode; } // 单链表的遍历显示 display() { let result = ''; let currNode = this.head; while (currNode) { result += currNode.data; currNode = currNode.next; if (currNode) { result += '->'; } } console.log(result); } // 从当前位置向前移动 n 个节点。 advance(n, currNode = this.head) { this.currNode = currNode; while ((n--) && this.currNode.next) { this.currNode = this.currNode.next; } this.remove(currNode.data); this.insert(this.currNode.data, currNode.data); return this.currNode; } // 在单链表中寻找item元素 find(item) { let currNode = this.head; while (currNode && (currNode.data !== item)) { currNode = currNode.next; } return currNode; } // 显示当前节点 show() { console.log(this.currNode.data); } // 获取单链表的长度 getLength() { return this.size; } // 向单链表中插入元素 insert(item, element) { let itemNode = this.find(item); if (!itemNode) { // 如果item元素不存在 return; } let newNode = new Node(element); newNode.next = itemNode.next; // 若currNode为最后一个节点,则currNode.next为空 itemNode.next = newNode; this.size++; } // 在单链表中删除一个节点 remove(item) { if (!this.find(item)) { // item元素在单链表中不存在时 return; } // 企图删除头结点 if (item === 'head') { if (!(this.isEmpty())) { return; } else { this.head.next = null; return; } } let currNode = this.head; while (currNode.next.data !== item) { // 企图删除不存在的节点 if (!currNode.next) { return; } currNode = currNode.next; } currNode.next = currNode.next.next; this.size--; } // 在单链表的尾部添加元素 append(element) { let currNode = this.findLast(); let newNode = new Node(element); currNode.next = newNode; this.size++; } // 清空单链表 clear() { this.head.next = null; this.size = 0; }}slet myList = new SingleList();let arr = [3, 4, 5, 6, 7, 8, 9];for (let i = 0; i < arr.length; i++) { myList.append(arr[i]);}myList.display(); // head->3->4->5->6->7->8->9// const curr = myList.find(3);// myList.insert(3, 10);// myList.display();// myList.remove(10);;// console.log(myList.find(3));// console.log('===');console.log(myList.advance(2, myList.find(5)));;myList.show();myList.display();","categories":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/categories/JavaScript/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/tags/JavaScript/"}]},{"title":"树莓派-配置免密登录云主机","slug":" 树莓派-配置免密登录云主机","date":"2020-08-03T13:33:00.000Z","updated":"2020-08-03T13:33:00.000Z","comments":true,"path":"2020/08/03/ 树莓派-配置免密登录云主机/","link":"","permalink":"https://blog.magicyou.cn/2020/08/03/%20%E6%A0%91%E8%8E%93%E6%B4%BE-%E9%85%8D%E7%BD%AE%E5%85%8D%E5%AF%86%E7%99%BB%E5%BD%95%E4%BA%91%E4%B8%BB%E6%9C%BA/","excerpt":"树莓派频繁登陆腾讯云主机,配置免密登录云主机,省的来回输入密码","text":"树莓派频繁登陆腾讯云主机,配置免密登录云主机,省的来回输入密码 知识补充1. 生成ssh密匙1ssh-keygen -t rsa -P '' -f ~/.ssh/id_rsa 参数说明: -t 加密算法类型,这里是使用rsa算法 -P 指定私钥的密码,不需要可以不指定 -f 指定生成秘钥对保持的位置 2. 复制SSH密钥到目标主机,开启无密码SSH登录1ssh-copy-id [-i [identity_file]] [user@]machine 参数说明: -i:指定公钥文件 生成ssh密匙1ssh-keygen -t rsa 一路回车 123456789101112131415161718192021pi@raspberrypi:~/.ssh $ ssh-keygen -t rsaGenerating public/private rsa key pair.Enter file in which to save the key (/home/pi/.ssh/id_rsa): Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /home/pi/.ssh/id_rsa.Your public key has been saved in /home/pi/.ssh/id_rsa.pub.The key fingerprint is:SHA256:fCy8svfejc6O3P7KNvx5xz4b/GT5EizIZbE8IGq0eIU pi@raspberrypiThe key's randomart image is:+---[RSA 2048]----+| . || E o . . || o + . o o || . =o . * || o S.o+ o || +o . + .|| . . . . *o|| o.. *+o.=B|| .. o==@===B|+----[SHA256]-----+ copy 公钥到云主机1ssh-copy-id -i ~/.ssh/id_rsa.pub hostname@host 12345678910pi@raspberrypi:~/.ssh $ ssh-copy-id -i ~/.ssh/id_rsa.pub [email protected]/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/pi/.ssh/id_rsa.pub"/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new [email protected]'s password: Number of key(s) added: 1Now try logging into the machine, with: "ssh '[email protected]'"and check to make sure that only the key(s) you wanted were added. 验证 12345pi@raspberrypi:~/.ssh $ ssh [email protected] failed login: Thu Mar 18 18:05:56 CST 2021 from 107.175.50.219 on ssh:nottyThere were 14 failed login attempts since the last successful login.Last login: Thu Mar 18 17:54:17 2021 from 120.244.218.59[root@VM-0-12-centos ~]#","categories":[{"name":"树莓派","slug":"树莓派","permalink":"https://blog.magicyou.cn/categories/%E6%A0%91%E8%8E%93%E6%B4%BE/"}],"tags":[{"name":"树莓派","slug":"树莓派","permalink":"https://blog.magicyou.cn/tags/%E6%A0%91%E8%8E%93%E6%B4%BE/"},{"name":"Linux","slug":"Linux","permalink":"https://blog.magicyou.cn/tags/Linux/"}]},{"title":"javascript复习:变量提升","slug":"javascript复习:变量提升","date":"2020-06-30T12:24:02.000Z","updated":"2020-06-30T12:24:02.000Z","comments":true,"path":"2020/06/30/javascript复习:变量提升/","link":"","permalink":"https://blog.magicyou.cn/2020/06/30/javascript%E5%A4%8D%E4%B9%A0%EF%BC%9A%E5%8F%98%E9%87%8F%E6%8F%90%E5%8D%87/","excerpt":"什么是变量提升,为什么会发生变量提升?这个问题还是挺经典的,让我们深度的研究一下。","text":"什么是变量提升,为什么会发生变量提升?这个问题还是挺经典的,让我们深度的研究一下。 什么情况下发生?123var str = 'hello world';console.log(str);// 输出: 以上代码输出 “hello world”,和预期的一样。 实例 2: 123console.log(str2);var str2 = 'hello world';// 输出: undefined 代码没报错,输出了 “undefined”,按照正常的代码执行操作,不应该是没有定义 “str2”,会报错的么。问题是出在“var str2 = ‘hello world’;”。这就是javascript的变量提升。 深入了解一下 “变量提升”意味着变量和函数的声明会在物理层面移动到代码的最前面,但这么说并不准确。实际上变量和函数声明在代码里的位置是不会动的,而是在编译阶段被放入内存中。 ”在编译阶段被放入内存“,这句话道出了变量提升的发生原因。实例 2 在打印 ”str2“的时候内存中已经有”str2“,只是只提升了声明,赋值并没有被提前。就像如下例子: 123console.log(num); // Returns undefinedvar num;num = 6; 表达式与函数声明提升时的差异1. 函数声明提升时,函数本身也会提升到当前作用域的最前面1234foo() // 1function foo() { console.log('1')} 2. 函数表达式执行时,只会将变量提升函数表达式执行时则不然,只会将变量提升,暂赋值undefined,赋值或其他逻辑运行会留在原地 1234foo() // Uncaught TypeError: foo is not a function, 此时的foo为undefinedvar foo = function() { console.log('1')} javascript 语法为何如此松散凌乱?怎么解决这样的变量提升导致的莫名报错?变量提升这个概念只有 js 里才有,可能是语言设计的导致。es6之后,有了新的变量声明关键词”let,const“; 123console.log(num); // Uncaught ReferenceError: num is not definedlet num;num = 6; 变量提升”消失“了,”let“ 关键词发挥了作用 附上一份面试题及答案选自 https://zhuanlan.zhihu.com/p/139644982 1234567891011121314151617function Foo() { getName = function () { alert (1); }; return this;}Foo.getName = function () { alert (2);};Foo.prototype.getName = function () { alert (3);};var getName = function () { alert (4);};function getName() { alert (5);}//请写出以下输出结果:Foo.getName(); // 2 => Foo的静态属性getNamegetName(); // 4 => 执行全局环境下的getNameFoo().getName(); // 1 <=>window.getName()getName(); // 1 => 执行全局环境下的getNamenew Foo.getName(); // 2 => 执行Foo的静态属性getName的构造函数new Foo().getName(); // 3 相当于Foo实例原型上的getNamenew new Foo().getName(); // 3 相当于Foo实例原型上的getName的构造函数","categories":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/categories/JavaScript/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/tags/JavaScript/"}]},{"title":"javascript复习:web安全","slug":"javascript复习:web安全","date":"2020-04-07T13:00:02.000Z","updated":"2020-04-07T13:00:02.000Z","comments":true,"path":"2020/04/07/javascript复习:web安全/","link":"","permalink":"https://blog.magicyou.cn/2020/04/07/javascript%E5%A4%8D%E4%B9%A0%EF%BC%9Aweb%E5%AE%89%E5%85%A8/","excerpt":"web安全: XSS 跨站攻击,XSRF 跨站请求伪造","text":"web安全: XSS 跨站攻击,XSRF 跨站请求伪造 常见的web 端攻击方式 XSS 跨站攻击即Cross Site Script Execution(通常简写为XSS)是指攻击者利用网站程序对用户输入过滤不足,输入可以显示在页面上对其他用户造成影响的HTML代码,从而盗取用户资料、利用用户身份进行某种动作或者对访问者进行病毒侵害的一种攻击方式。 XSRF 跨站请求伪造(英语:Cross-site request forgery),也被称为 one-click attack 或者 session riding,通常缩写为 CSRF 或者 XSRF, 是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。跟跨网站脚本(XSS)相比,XSS 利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。 XSS 跨站攻击场景 一个博客网站,我发表一篇文章,其中嵌入 ‘&ltscript&gt’ 脚本 脚本内容:获取cookie,发送到我的服务器(服务器配合跨域) 发布这篇博客,有人查看他,我就轻松收割访问者的cookieXSS 预防 替换特殊字符,如 ‘<’ 变为 ‘<’,‘>’ 变为 ‘>’; ‘&ltscript&gt’ 变为 ‘ &ltscript>’,直接显示,而不会作为脚本执行 前端要替换,后端也要替换,都做总不会错 XSRF攻击场景 你正在购物看中了某个商品,商品id是100 付费接口是 xxx.com/pay?id=100,但没有任何验证 我是攻击者。我看中了一个商品,id是200 我想你的发送一封电子邮件,邮件标题很吸引人 但邮件正文隐藏着 你一查看邮件,就帮我购买了id是 200 的商品 XSRF 预防 使用post接口 增加验证,例如密码,短信验证码,指纹等","categories":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/categories/JavaScript/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/tags/JavaScript/"}]},{"title":"javascript复习:性能优化","slug":"javascript复习:性能优化","date":"2020-04-07T12:00:02.000Z","updated":"2020-04-07T12:00:02.000Z","comments":true,"path":"2020/04/07/javascript复习:性能优化/","link":"","permalink":"https://blog.magicyou.cn/2020/04/07/javascript%E5%A4%8D%E4%B9%A0%EF%BC%9A%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/","excerpt":"性能优化原则 多食用内存,缓存或其他方法 减少CPU 计算量,减少网络加载耗时 (适用于所有编程的性能优化 —- 空间换时间)","text":"性能优化原则 多食用内存,缓存或其他方法 减少CPU 计算量,减少网络加载耗时 (适用于所有编程的性能优化 —- 空间换时间) 从何入手 让加载更快 减少资源体积,压缩代码 减少访问次数:合并代码,SSR 服务器端渲染,缓存 使用更快的网络: CDN 让渲染更快 CSS 放在head,JS 放在body 最下面 尽早开始执行JS,用DOMContentLoaded 触发 懒加载(图片懒加载,上滑加载更多) 对 DOM 查询进行缓存 频繁DOM 操作,合并到一起插入DOM 结构 节流 throttle,防抖 debounce 缓存 静态资源加hash后缀,根据文件内容计算hash 文件内容不变,则hash不变,则url不变 url 和文件不变,则会自动触发http缓存机制,返回304 SSR 服务器端渲染:将网页和数据一起加载,一起渲染 非SSR(前后端分离):先加载网页,再加载数据,再渲染数据 早先的 jsp,asp,php,现在的vue React SSR,都是SSR 防抖 debounce 监听一个输入框,文字变化后触发change事件 直接用keyup事件,则会频繁触发change事件 防抖:用户输入完毕触发change事件1234567891011121314151617181920const input = document.getElementById('input');function debounce(fn, delay = 500) { // timer 是闭包中的 let timer = null; return function () { if (timer) { clearTimeout(timer); } timer = setTimeout(() => { fn.apply(this, arguments); timer = null; }, delay); }}input.addEventListener('keyup', debounce(() => { console.log(input.value);}, 600)); 节流 throttle 拖拽一个元素,要随时拿到该元素被拖拽的位置 直接用 drag 事件,则会频繁触发,很容易导致卡顿 节流:无论拖拽速度多快,都会每隔100ms触发一次 1234567891011121314151617181920const div1 = document.getElementById('div1');function throttle(fn, delay = 100) { // timer 是闭包中的 let timer = null; return function () { if (timer) { return; } timer = setTimeout(() => { fn.apply(this, arguments); timer = null; }, delay); }}div1.addEventListener('drag', throttle((e) => { console.log(e);}, 100));","categories":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/categories/JavaScript/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/tags/JavaScript/"}]},{"title":"javascript复习:页面加载","slug":"javascript复习:页面加载","date":"2020-04-07T12:00:02.000Z","updated":"2020-04-07T12:00:02.000Z","comments":true,"path":"2020/04/07/javascript复习:页面加载/","link":"","permalink":"https://blog.magicyou.cn/2020/04/07/javascript%E5%A4%8D%E4%B9%A0%EF%BC%9A%E9%A1%B5%E9%9D%A2%E5%8A%A0%E8%BD%BD/","excerpt":"题目 从输入url到渲染出页面的整个过程 window.onload 和 DOMContentLoaded 的区别","text":"题目 从输入url到渲染出页面的整个过程 window.onload 和 DOMContentLoaded 的区别 知识点 加载资源的形式 加载资源的过程 渲染页面的过程 加载资源的形式 html 代码 媒体文件,如图片,视频等 javascript css 加载资源的过程 DNS 解析: 域名 -> IP地址 浏览器根据IP地址向服务器发起http请求 服务器处理http请求,并返回给浏览器 渲染页面的过程 根据HTML代码生成DOM Tree 根据CSS 代码生成CSSOM 将DOM Tree 和 CSSOM整合形成 Render Tree 根据Render Tree 渲染页面 遇到 则暂停渲染,有限加载并执行js代码,完成再继续 直至Render Tree 渲染完成 问题解答: 从输入url到渲染出页面的整个过程从输入URL到页面加载的全过程 - 小火柴的蓝色理想 - 博客园 (cnblogs.com) 问题解答: window.onload 和 DOMContentLoaded 的区别1234567window.addEventListener('load', function () { // 页面全部资源加载完毕才会执行,包括图片,视频等})document.addEventListener('DOMContentLoaded', function() { // DOM 渲染完即可进行,此时图片,视频还可能没有加载完}) 思考 为何建议把 css 放入 head 中 放在body底部,在DOM Tree构建完成之后开始构建render Tree,计算布局然后绘制网页,等css文件加载后,开始构建CSSOM Tree,并和DOM Tree一起构建render Tree,再次计算布局重新绘制; 放在head中,先加载css,构建CSSOM,同时构建DOM Tree,CSSOM和DOM Tree构建完成后,构建render Tree,进行计算布局绘制网页。 总体来看,放在body底部要比放在head中多一次构建render Tree,多一次计算布局,多一次绘制,从性能方面来看,不如放在head中。再次,放在body底部网页会闪现默认样式的DOM结构,用户体验不好。 为何建议把 js 放在 body 之后","categories":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/categories/JavaScript/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/tags/JavaScript/"}]},{"title":"javascript复习:webpack 和 babel","slug":"javascript复习:webpack 和 babel","date":"2020-04-06T12:00:02.000Z","updated":"2020-04-06T12:00:02.000Z","comments":true,"path":"2020/04/06/javascript复习:webpack 和 babel/","link":"","permalink":"https://blog.magicyou.cn/2020/04/06/javascript%E5%A4%8D%E4%B9%A0%EF%BC%9Awebpack%20%E5%92%8C%20babel/","excerpt":"为什么用 webpack 和 babel ES6 模块化,浏览器暂不支持 ES6 语法,浏览器并不完全支持 压缩代码,整合代码,让网页加载更快","text":"为什么用 webpack 和 babel ES6 模块化,浏览器暂不支持 ES6 语法,浏览器并不完全支持 压缩代码,整合代码,让网页加载更快 初始化1234567891011121314151617npm init -y ✔ npm init -yWrote to /Users/magicyou/www/test_webpack/package.json:{ "name": "test_webpack", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \\"Error: no test specified\\" && exit 1" }, "keywords": [], "author": "", "license": "ISC"} 安装webpack12345678910111213141516171819npm install webpack webpack-cli -D✔ npm install webpack webpack-cli -Dnpm notice Beginning October 4, 2021, all connections to the npm registry - including for package installation - must use TLS 1.2 or higher. You are currently using plaintext http to connect. Please visit the GitHub blog for more information: https://github.blog/2021-08-23-npm-registry-deprecating-tls-1-0-tls-1-1/npm notice Beginning October 4, 2021, all connections to the npm registry - including for package installation - must use TLS 1.2 or higher. You are currently using plaintext http to connect. Please visit the GitHub blog for more information: https://github.blog/2021-08-23-npm-registry-deprecating-tls-1-0-tls-1-1/npm notice Beginning October 4, 2021, all connections to the npm registry - including for package installation - must use TLS 1.2 or higher. You are currently using plaintext http to connect. Please visit the GitHub blog for more information: https://github.blog/2021-08-23-npm-registry-deprecating-tls-1-0-tls-1-1/ ............npm notice created a lockfile as package-lock.json. You should commit this file.npm WARN [email protected] No descriptionnpm WARN [email protected] No repository field.+ [email protected]+ [email protected] 121 packages from 158 contributors and audited 121 packages in 60.95s17 packages are looking for funding run `npm fund` for detailsfound 0 vulnerabilities 安装完的package.json12345678910111213141516{ "name": "test_webpack", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \\"Error: no test specified\\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "webpack": "^5.52.1", "webpack-cli": "^4.8.0" }} 安装插件 html-webpack-plugin,解析html1234567891011121314npm install html-webpack-plugin -D ✔ npm install html-webpack-plugin -Dnpm notice Beginning October 4, 2021, all connections to the npm registry - including for package installation - must use TLS 1.2 or higher. You are currently using plaintext http to connect. Please visit the GitHub blog for more information: https://github.blog/2021-08-23-npm-registry-deprecating-tls-1-0-tls-1-1/npm notice Beginning October 4, 2021, all connections to the npm registry - including for package installation - must use TLS 1.2 or higher. You are currently using plaintext http to connect. Please visit the GitHub blog for more information: https://github.blog/2021-08-23-npm-registry-deprecating-tls-1-0-tls-1-1/npm notice Beginning October 4, 2021, all connections to the npm registry - including for package installation - must use TLS 1.2 or higher. You are currently using plaintext http to connect. Please visit the GitHub blog for more information: https://github.blog/2021-08-23-npm-registry-deprecating-tls-1-0-tls-1-1/......+ [email protected] 33 packages from 19 contributors and audited 154 packages in 9.446s27 packages are looking for funding run `npm fund` for detailsfound 0 vulnerabilities 安装插件 webpack-dev-server,解析html12345678910111213141516171819npm install webpack-dev-server -D✔ npm install webpack-dev-server -Dnpm notice Beginning October 4, 2021, all connections to the npm registry - including for package installation - must use TLS 1.2 or higher. You are currently using plaintext http to connect. Please visit the GitHub blog for more information: https://github.blog/2021-08-23-npm-registry-deprecating-tls-1-0-tls-1-1/npm notice Beginning October 4, 2021, all connections to the npm registry - including for package installation - must use TLS 1.2 or higher. You are currently using plaintext http to connect. Please visit the GitHub blog for more information: https://github.blog/2021-08-23-npm-registry-deprecating-tls-1-0-tls-1-1/npm notice Beginning October 4, 2021, all connections to the npm registry - including for package installation - must use TLS 1.2 or higher. You are currently using plaintext http to connect. Please visit the GitHub blog for more information: https://github.blog/2021-08-23-npm-registry-deprecating-tls-1-0-tls-1-1/npm notice Beginning October 4, 2021, all connections to the npm registry - including for package installation - must use TLS 1.2 or higher. You are currently using plaintext http to connect. Please visit the GitHub blog for more information: https://github.blog/2021-08-23-npm-registry-deprecating-tls-1-0-tls-1-1/......npm WARN [email protected] No descriptionnpm WARN [email protected] No repository field.+ [email protected] 205 packages from 139 contributors and audited 359 packages in 37.916s54 packages are looking for funding run `npm fund` for detailsfound 0 vulnerabilities webpack.config.js 添加 html-webpack-plugin123456789101112131415161718192021const path = require('path');const HtmlWebpackPlugin = require('html-webpack-plugin');module.exports = { mode: 'development', // 开发模式 development / production entry: path.join(__dirname, 'src', 'index.js'), output: { filename: 'bundle.js', path: path.join(__dirname, 'dist') }, pliugins: [ new HtmlWebpackPlugin({ template: path.join(__dirname, 'src', 'index.html'), // 模板文件 filename: 'index.html' // 产出文件 }) ], devServer: { port: 3000, contentBase: path.join(__dirname, 'dist') }}; ES6 转化成 ES5,安装 babel12345678910npm install @babel/core @babel/preset-env babel-loader -D ✔ npm install @babel/core @babel/preset-env babel-loader -D 14s 21:01:56added 152 packages, and audited 512 packages in 53s6 packages are looking for funding run `npm fund` for detailsfound 0 vulnerabilities 此时的package.json 123456789"devDependencies": { "@babel/core": "^7.6.2", "@babel/preset-env": "^7.6.2", "babel-loader": "^8.0.6", "html-webpack-plugin": "^3.2.0", "webpack": "^4.41.0", "webpack-cli": "^3.3.9", "webpack-dev-server": "^3.8.2"} 配置babel创建 .babelrc 文件123{ "presets": ["@babel/preset-env"]} 对生产环境单独做配置 创建文件 webpack.prod.js; 修改 ’mode‘ 为 ‘production’ ,打包出来的文件就会压缩成一行,体积减小,加载速度快 output 优化,添加 ’[contenthash]‘,文件改动就重新生成文件名,防止读取缓存123456789101112131415161718192021222324252627const path = require('path');const HtmlWebpackPlugin = require('html-webpack-plugin');module.exports = { mode: 'production', // 开发模式 development / production entry: path.join(__dirname, 'src', 'index.js'), output: { filename: 'bundle.[contenthash].js', path: path.join(__dirname, 'dist') }, module: { rules: [ { test: /\\.js$/, loader: ['babel-loader'], include: path.join(__dirname, 'src'), exclude: /node_modules/ } ] }, plugins: [ new HtmlWebpackPlugin({ template: path.join(__dirname, 'src', 'index.html'), // 模板文件,指定已有的html文件 filename: 'index.html' // 产出文件 }) ]};","categories":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/categories/JavaScript/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/tags/JavaScript/"}]},{"title":"javascript复习:存储","slug":"javascript复习:存储","date":"2020-04-05T09:00:02.000Z","updated":"2020-04-05T09:00:02.000Z","comments":true,"path":"2020/04/05/javascript复习:存储/","link":"","permalink":"https://blog.magicyou.cn/2020/04/05/javascript%E5%A4%8D%E4%B9%A0%EF%BC%9A%E5%AD%98%E5%82%A8/","excerpt":"题目 描述cookie localStorage,sessionStorage 区别","text":"题目 描述cookie localStorage,sessionStorage 区别 知识点 cookie localStorage和 sessionStorage cookie 本身用于浏览器和server通讯 被“ 借用 ”到本地存储来 可用 document.cookie = ‘…’ 来修改 有时效性缺点 存储大小,最大4KB http 请求时,需要发送到服务端,增加请求数据量 只能document.cookie = ‘…’ 来修改,js的相关api太过简陋 localStorage和 sessionStorage HTML5 专门为存储而设计,最大可存储5M API 简单一用,setItem getItem 不会随着http 请求被发送出去 localStorage 数据会被永久存储,除非代码或手动删除 sessionStorage 数据值存在于当前会话,浏览器关闭则清空 一般用 localStorage 会更多些 问题解答: 描述cookie localStorage,sessionStorage 区别 容量 API 易用性 是否跟随http请求发送出去","categories":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/categories/JavaScript/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/tags/JavaScript/"}]},{"title":"javascript复习:ajax","slug":"javascript复习:ajax","date":"2020-04-05T04:00:02.000Z","updated":"2020-04-05T04:00:02.000Z","comments":true,"path":"2020/04/05/javascript复习:ajax/","link":"","permalink":"https://blog.magicyou.cn/2020/04/05/javascript%E5%A4%8D%E4%B9%A0%EF%BC%9Aajax/","excerpt":"题目 手写一个简易的ajax 跨域常用的实现方式","text":"题目 手写一个简易的ajax 跨域常用的实现方式 知识点 XMLHttpRequest 状态码 跨域: 同源策略,跨域解决方案 1. XMLHttpRequest get 请求 123456789101112let xhr = new XMLHttpRequest();xhr.open('GET', '/data.json', true)xhr.onreadystatechange = function () { if (xhr.readyState === 4) { if (xhr.status === 200) { console.log(xhr); console.log(xhr.responseText); } }}xhr.send(null) post 请求 123456789101112131415let xhr = new XMLHttpRequest();xhr.open('POST', '/login', true)xhr.onreadystatechange = function () { if (xhr.readyState === 4) { if (xhr.status === 200) { console.log(xhr); console.log(xhr.responseText); } }}const postData = {user: 'lxl',pwd: '111'};xhr.send(JSON.stringify(postData)) 1. xhr.readyState 0 -(未初始化) 还没有调用send() 方法 1 -(载入) 已调用send() 方法,正在发送请求 2 -(载入完成)send() 方法执行完成,已经接收到全部响应内容 3 - (交互)正在解析相应内容 2. xhr.status 2XX - 表示成功请求,如 200 3XX - 需要重定向,浏览器直接跳转,如 301,302, 304 4XX - 客户端请求错误,如 404,403 5XX - 服务端错误 2. 跨域什么是跨域 同源策略 ajax 请求时,浏览器要求当前网页和server必须同源(安全) 同源: 协议、域名、端口,三者必须一致 前端: http:// a.com:8080/; server: https://b.com/api/xxx 加载图片,css,js 可无视同源策略 <img / > 可用于统计打点,可使用第三方统计服务 可使用CDN,CDN 一般都是外域 可实现JSONP 跨域 所有的跨域,都必须经过server 端 和配合 未经server端就实现跨域,说明浏览器有;漏洞,危险信号 jsonp 访问 https://imooc.com/ , 服务端一定返回一个html文件吗? 服务端可以任意动态拼接数据返回,只要符合html格式要求 同理于 可绕过跨域限制 服务端可以任意动态拼接数据返回 所以,就可以获得跨域的数据,只要服务端愿意返回 CORS-服务器设置http header12345678$response->headers->add(['Access-Control-Allow-Origin' => 'http://localhost:8011']);$response->headers->add(['Access-Control-Allow-Headers' => 'Origin, Content-Type, Cookie,X-CSRF-TOKEN, Accept,Authorization']);$response->headers->add(['Access-Control-Expose-Headers' => 'Authorization,authenticated']);$response->headers->add(['Access-Control-Allow-Methods' => 'GET, POST, PATCH, PUT, OPTIONS']);// 接受跨域的cookie$response->headers->add(['Access-Control-Allow-Credentials' => 'true']); 问题解答:手写一个简易的ajax12345678910111213141516171819202122232425function ajax(url) { const p = new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open('GET', url, true) xhr.onreadystatechange = function () { if (xhr.readyState === 4) { if (xhr.status === 200) { console.log(xhr); console.log(xhr.responseText); resolve(JSON.parse(xhr.responseText)) } else if (xhr.status === 404) { reject(new Error('404 not found')) } else { reject(new Error('Error')) } } } xhr.send(null) }); return p;} 问题解答:跨域常用的实现方式 jsonp CORS 几个常用的ajax工具 jquery 的 ajax方法 fetch,返回Promise 不会被标记为reject,即便是404和500,网络故障才会reject 不会从服务端发送或者接收cookies(要接收cookie,必须设置Credentials) axios 可以在浏览器端和服务端使用 支持Promise","categories":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/categories/JavaScript/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/tags/JavaScript/"}]},{"title":"javascript复习:事件绑定和事件冒泡","slug":"javascript复习:事件绑定和事件冒泡","date":"2020-04-04T04:00:02.000Z","updated":"2020-04-04T04:00:02.000Z","comments":true,"path":"2020/04/04/javascript复习:事件绑定和事件冒泡/","link":"","permalink":"https://blog.magicyou.cn/2020/04/04/javascript%E5%A4%8D%E4%B9%A0%EF%BC%9A%E4%BA%8B%E4%BB%B6%E7%BB%91%E5%AE%9A%E5%92%8C%E4%BA%8B%E4%BB%B6%E5%86%92%E6%B3%A1/","excerpt":"题目 编写一个通用的事件监听函数 描述事件冒泡的流程 无线下拉的图片列表,如何监听每一个图片的点击?","text":"题目 编写一个通用的事件监听函数 描述事件冒泡的流程 无线下拉的图片列表,如何监听每一个图片的点击? 知识点 事件绑定 事件冒泡 事件代理 问题解答: 编写一个通用的事件监听函数1234567891011121314151617181920function bindEvent(elem, type, selector, fn) { if (fn == null) { fn = selector; } elem.addEventListener(type, function (event) { const target = event.target; if (selector) { // 代理绑定 if (target.matches(selector)) { fn.call(target, event); } } else { // 普通绑定 fn.call(target, event); } })} 问题解答: 描述事件冒泡的流程 基于DOM树形结构 事件会顺着出发元素往上冒泡 应用场景:事件代理问题解答: 无线下拉的图片列表,如何监听每一个图片的点击? 事件代理 用e.target 获取触发元素 用 matches 来判断是否触发元素 补充: 如何阻止事件冒泡和默认(行为)事件 链接:https://blog.csdn.net/binlety/article/details/81390433 1.event.stopPropagation()方法这是阻止事件的冒泡方法,不让事件向documen上蔓延,但是默认事件任然会执行,当你掉用这个方法的时候,如果点击一个连接,这个连接仍然会被打开, 2.event.preventDefault()方法这是阻止默认事件的方法,调用此方法是,连接不会被打开,但是会发生冒泡,冒泡会传递到上一层的父元素; 3.return false ;这个方法比较暴力,他会同事阻止事件冒泡也会阻止默认事件 但是需要注意的是: IE:window.event.cancelBubble = true;//停止冒泡window.event.returnValue = false;//阻止事件的默认行为 Firefox:event.preventDefault();// 取消事件的默认行为event.stopPropagation(); // 阻止事件的传播","categories":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/categories/JavaScript/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/tags/JavaScript/"}]},{"title":"git-新仓库准备","slug":"git-新仓库准备","date":"2020-04-03T13:33:00.000Z","updated":"2020-04-03T13:33:00.000Z","comments":true,"path":"2020/04/03/git-新仓库准备/","link":"","permalink":"https://blog.magicyou.cn/2020/04/03/git-%E6%96%B0%E4%BB%93%E5%BA%93%E5%87%86%E5%A4%87/","excerpt":"新建了一个git仓库,如何将已有代码项目加入。","text":"新建了一个git仓库,如何将已有代码项目加入。 Git 全局设置12git config --global user.name "xiaolong"git config --global user.email "[email protected]" 创建一个新仓库123456git clone [email protected]:xiaolong/demo.gitcd demotouch README.mdgit add README.mdgit commit -m "add README"git push -u origin master 推送现有文件夹123456cd existing_foldergit initgit remote add origin [email protected]:xiaolong/demo.gitgit add .git commit -m "Initial commit"git push -u origin master 推送现有的 Git 仓库12345cd existing_repogit remote rename origin old-origingit remote add origin [email protected]:xiaolong/demo.gitgit push -u origin --allgit push -u origin --tags","categories":[],"tags":[{"name":"Linux","slug":"Linux","permalink":"https://blog.magicyou.cn/tags/Linux/"},{"name":"git","slug":"git","permalink":"https://blog.magicyou.cn/tags/git/"}]},{"title":"javascript复习:JS-Web-API-BOM","slug":"javascript复习:JS-Web-API-BOM","date":"2020-04-03T04:24:02.000Z","updated":"2020-04-03T04:24:02.000Z","comments":true,"path":"2020/04/03/javascript复习:JS-Web-API-BOM/","link":"","permalink":"https://blog.magicyou.cn/2020/04/03/javascript%E5%A4%8D%E4%B9%A0%EF%BC%9AJS-Web-API-BOM/","excerpt":"BOM (browser object model的缩写,简称浏览器对象模型)","text":"BOM (browser object model的缩写,简称浏览器对象模型) 题目: 如何识别浏览器的类型 分析拆解url各个部分 知识点 navigator123const ua = navigator.userAgentconst isChrome = ua.indexOf('Chrome')console.log(isChrome) screen12console.log(screen.width) // 2560console.log(screen.height) // 1440 location123456console.log(location.href); // https://www.baidu.com/?tn=98050039_dg&ch=1#/name/lxlconsole.log(location.protocol); // https:console.log(location.pathname); // /writerconsole.log(location.search); // ?tn=98050039_dg&ch=1console.log(location.hash); // #/name/lxlconsole.log(location.host); // "www.baidu.com"","categories":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/categories/JavaScript/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/tags/JavaScript/"}]},{"title":"javascript复习:JS-Web-API-DOM","slug":"javascript复习:JS-Web-API-DOM","date":"2020-04-02T04:24:02.000Z","updated":"2020-04-02T04:24:02.000Z","comments":true,"path":"2020/04/02/javascript复习:JS-Web-API-DOM/","link":"","permalink":"https://blog.magicyou.cn/2020/04/02/javascript%E5%A4%8D%E4%B9%A0%EF%BC%9AJS-Web-API-DOM/","excerpt":"补充知识 JS 基础知识,规定语法(ECMA 262标准) JS Web API, 网页操作的API (W3C标准) 前者是后者的基础,两者结合才能真正实现应用","text":"补充知识 JS 基础知识,规定语法(ECMA 262标准) JS Web API, 网页操作的API (W3C标准) 前者是后者的基础,两者结合才能真正实现应用 JS Web API DOM (domcument object model,即文档对象模型) BOM (browser object model的缩写,简称浏览器对象模型) 事件绑定 ajax 存储 题目: DOM 是那种数据结构 DOM 操作的常用API attr 和 property 的区别 一次性插入多个DOM节点,考虑性能知识点 DOM 本质树 DOM 节点操作 获取节点12345678910111213141516document.getElementById('div1');document.getElementsByClassName('div1') // 集合document.getElementsByTagName('dev') // 集合document.querySelectorAll('p') // 集合const pList = document.querySelectorAll('p');const p1 = pList[0];// property 形式p1.style.width = '100px';p1.className = 'red';console.log(p1.className);console.log(p1.nodeName);console.log(p1.nodeType); attribute1234// attribute:p1.setAttribute('data-name', 'lxl')console.log(p1.getAttribute('data-name'));p1.setAttribute('style', 'font-size: 18px;') DOM 结构操作12345678910111213141516171819202122232425262728// 新增/插入节点const div1 = document.getElementById('div1');// 添加新节点const p1 = document.createElement('p')p1.innerHTML = 'this is p1';div1.appendChild(p1);// 移动已有节点,是移动const p2 = document.getElementById('p2');div1.appendChild(p2);// 获取子元素列表const div1 = document.getElementById('div1');const child = div1.childNodes;console.log(child)// 获取父元素const div1 = document.getElementById('div1');const parent = div1.parentNode;console.log(parent)// 删除节点const div1 = document.getElementById('div1');const child = div1.childNodes;div1.removeChild(child[0]) DOM 性能DOM 操作非常昂贵,避免频繁的DOM操作对DOM 查询做缓存将频繁操作改为一次操作12345678910const div1 = document.getElementById('div1');const frag = document.createDocumentFragment();for (let i = 0; i < 10; i++) { const p = document.createElement('p'); p.innerHTML = `${i} 后期插入`; frag.appendChild(p);}div1.appendChild(frag) 问题回答: DOM 是那种数据结构树(DOM树) 问题回答: DOM 操作的常用API123456789101112131415161718192021222324252627282930313233343536373839404142// 获取domdocument.getElementById('div1');document.getElementsByClassName('div1') // 集合document.getElementsByTagName('dev') // 集合document.querySelectorAll('p') // 集合// property 形式p1.style.width = '100px';p1.className = 'red';// attribute:p1.setAttribute('data-name', 'lxl')p1.setAttribute('style', 'font-size: 18px;')// 新增/插入节点const div1 = document.getElementById('div1');// 添加新节点const p1 = document.createElement('p')p1.innerHTML = 'this is p1';div1.appendChild(p1);// 移动已有节点,是移动const p2 = document.getElementById('p2');div1.appendChild(p2);// 获取子元素列表const div1 = document.getElementById('div1');const child = div1.childNodes;console.log(child)// 获取父元素const div1 = document.getElementById('div1');const parent = div1.parentNode;console.log(parent)// 删除节点const div1 = document.getElementById('div1');const child = div1.childNodes;div1.removeChild(child[0]) 问题回答: attr 和 property 的区别 property: 修改对象属性,不会提现到html结构中 attribute 修改html属性,会改变html结构 两者都可能引起 DOM 重新渲染 推荐文章 JS中attribute和property的区别 - L_mj - 博客园 (cnblogs.com) 问题回答: 一次性插入多个DOM节点,考虑性能12345678910const div1 = document.getElementById('div1');const frag = document.createDocumentFragment();for (let i = 0; i < 10; i++) { const p = document.createElement('p'); p.innerHTML = `${i} 后期插入`; frag.appendChild(p);}div1.appendChild(frag)","categories":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/categories/JavaScript/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/tags/JavaScript/"}]},{"title":"javascript复习:异步","slug":"javascript复习:异步","date":"2020-04-01T04:24:02.000Z","updated":"2020-04-01T04:24:02.000Z","comments":true,"path":"2020/04/01/javascript复习:异步/","link":"","permalink":"https://blog.magicyou.cn/2020/04/01/javascript%E5%A4%8D%E4%B9%A0%EF%BC%9A%E5%BC%82%E6%AD%A5/","excerpt":"问题: 同步和异步的区别是什么? 手写用Promise加载一张图片 前端使用异步的场景有哪些?","text":"问题: 同步和异步的区别是什么? 手写用Promise加载一张图片 前端使用异步的场景有哪些? 知识点 单线程和异步 应用场景 callback hell 和 Promise callback hell (回调地狱)回调函数多层嵌套使用 单线程和异步 js 是单线程语言,只能同时做同一件事 浏览器和nodejs 已经支持js启动进程,如 Web Worker js 和 DOM 渲染共用同行一个线程,因为 js 可修改DOM结构 遇到等待(网络请求,定时任务)不能卡住 需要异步 回调callback 函数形式 问题解答:同步和异步的区别是什么 基于js是单线程语言 异步不会阻塞代码执行 同步会阻塞代码执行问题解答:手写用Promise加载一张图片1234567891011121314function loadImg(src) { return new Promise(function (resolve, reject) { const imgObj = document.createElement('img'); imgObj.onload = function () { console.log('loaded img success'); resolve(imgObj); }; imgObj.onerror = function (error) { console.log('loaded img error'); reject(new Error(`图片加载失败${src}`)); }; imgObj.src = src; });} 问题解答:前端使用异步的场景有哪些 网络请求,如ajax ,图片加载 定时任务,如 setTimeout 补充: 关于promise中reject和catch的问题一、reject后的东西,一定会进入then中的第二个回调,如果then中没有写第二个回调,则进入catch1234567891011121314var p1=new Promise((resolve,rej) => { console.log('没有resolve') //throw new Error('手动返回错误') rej('失败了') }) p1.then(data =>{ console.log('data::',data); },err=> { console.log('err::',err) }).catch( res => { console.log('catch data::', res) }) 结果: 12没有resolveerr:: 失败了 then中没有第二个回调的情况 123456789101112var p1=new Promise((resolve,rej) => { console.log('没有resolve') //throw new Error('手动返回错误') rej('失败了') }) p1.then(data =>{ console.log('data::',data); }).catch( res => { console.log('catch data::', res) }) 结果: 12没有resolvecatch data:: 失败了 二、resolve的东西,一定会进入then的第一个回调,肯定不会进入catch12345678910111213var p1=new Promise((resolve,rej) => { console.log('resolve') //throw new Error('手动返回错误') resolve('成功了') }) p1.then(data =>{ console.log('data::',data); }).catch( res => { console.log('catch data::', res) }) 结果: 12resolvedata:: 成功了 不会进入catch的情况,只要resolve了,就算抛出err,也不会进入catch 1234567891011var p1=new Promise((resolve,rej) => { console.log('resolve') //throw new Error('手动返回错误') resolve('成功了')})p1.catch( res => { console.log('catch data::', res)}) 结果: 1resolve throw new Error 的情况和rej一样,但是他俩只会有一个发生另外,网络异常(比如断网),会直接进入catch而不会进入then的第二个回调","categories":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/categories/JavaScript/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/tags/JavaScript/"}]},{"title":"javascript复习:作用域和自由变量","slug":"javascript复习:作用域和自由变量","date":"2020-03-31T04:24:02.000Z","updated":"2020-03-31T04:24:02.000Z","comments":true,"path":"2020/03/31/javascript复习:作用域和自由变量/","link":"","permalink":"https://blog.magicyou.cn/2020/03/31/javascript%E5%A4%8D%E4%B9%A0%EF%BC%9A%E4%BD%9C%E7%94%A8%E5%9F%9F%E5%92%8C%E8%87%AA%E7%94%B1%E5%8F%98%E9%87%8F/","excerpt":"问题 this 的不同应用场景,如何取值 手写 bind 函数 实际开发中闭包的应用场景,举例说明","text":"问题 this 的不同应用场景,如何取值 手写 bind 函数 实际开发中闭包的应用场景,举例说明 知识点 作用域和自有变量 闭包 this 知识点:作用域 全局作用域代码中写一个变量,不受函数的约束,在全局都可随意使用(window,document) 函数作用域受函数的约束,函数内使用,函数外无法使用 块级作用域(ES6新增)if,for,while花括号内,let 知识点:自由变量如我在全局中定义了一个变量a,然后我在函数中使用了这个a,这个a就可以称之为自由变量,可以这样理解,凡sss是跨了自己的作用域的变量都叫自由变量。 12345var a = "追梦子";function b(){ console.log(a); //追梦子}b(); 如下最内圈,a, a1, a2 就是自有变量 知识点:闭 包函数作为返回值12345678910function create(fn) { const a = 100; return function () { console.log(a); }}let fn = create();let a = 200;fn(); // 100 函数作为参数1234567891011function print (fn) { const a = 200; fn();}const a = 100;function fn () { console.log(a);}print(fn); // 100 错误示例:12345678910function print (fn) { const a = 100; fn();}function fn () { console.log(a);}print(fn); // 报错:Uncaught ReferenceError: a is not defined 所有自由变量的查找,实在函数定义的地方向上级组用于查找,不是在执行的地方!!! 知识点:this使用场景 作为普通函数 12function fn1() { console.log(this) }fn1() // Window 使用call,apply,bind 1234function fn1() { console.log(this) }fn1.call({ x: 10 }) // { x: 10 }const fn2 = fn1.bind({ x: 200 })fn2() // { x: 200 } 作为对象方法被调用 在class方法调用 箭头函数箭头函数中的 this 永远取上级作用域的this; this取什么值是在函数执行的时候确定的,不是定义的确定的; 原题: 创建十个’‘标签,点击的时候弹出对应的序号123456789for (let i = 0; i < 10; i++) { a = document.createElement('a'); a.innerHTML = i + '<br/>'; a.addEventListener('click' , (e) => { e.preventDefault(); console.log(i); }) document.body.append(a)} 问题解答:this 的不同应用场景,如何取值 当做普通函数被调用,取window call,apply,bind,this指向传入的对象 作为对象方法调用指向对象本身 class 方法中调用,this 指向 实例本身 箭头函数中的 this 永远取上级作用域的this 问题解答:手写 bind 函数12345678910111213141516171819202122232425262728function fn (a, b, c) { console.log('this:', this); console.log('a, b, c:', a, b, c);} fn() // this: window a, b, c: undefined undefined undefinedconst fn1 = fn.bind({x: 200}, 1, 2, 3);fn1() // this: {x: 200} a, b, c: 1 2 3Function.prototype.myBind = function () { // 将参数拆解为数组 let args = Array.prototype.slice.call(arguments); // 获取 this (数组的第一项) const t = args.shift(); // 获取 fn1.bind() 的 fn1 const self = this; // 返回一个函数 return function () { return self.apply(t, args) }}const fn2 = fn.myBind({ x: 300 }, 1, 2, 3);fn2() // this: {x: 300} a, b, c: 1 2 3 问题解答:实际开发中闭包的应用场景,举例说明闭包隐藏数据,只提供 API123456789101112131415function createCatch () { const data = {}; return { set(key, value) { data[key] = value; }, get(key) { return data[key] } }}const c = createCatch();c.set('a', 100);console.log(c.get('a')); // 100","categories":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/categories/JavaScript/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/tags/JavaScript/"}]},{"title":"javascript复习:原型和原型链","slug":"javascript复习:原型和原型链","date":"2020-03-30T04:24:02.000Z","updated":"2020-03-30T04:24:02.000Z","comments":true,"path":"2020/03/30/javascript复习:原型和原型链/","link":"","permalink":"https://blog.magicyou.cn/2020/03/30/javascript%E5%A4%8D%E4%B9%A0%EF%BC%9A%E5%8E%9F%E5%9E%8B%E5%92%8C%E5%8E%9F%E5%9E%8B%E9%93%BE/","excerpt":"js是基于原型继承的语言","text":"js是基于原型继承的语言 问题 如何准确判断一个变量是不是数组? 手写一个简易的jquery,考虑插件和扩展性? class的原型本质,怎么理解? 知识点 class和继承 类型判断instanceof 原型和原型链 继承12345678910111213141516171819202122232425262728293031323334353637class People { constructor(name) { this.name = name; } eat () { console.log(`${this.name} 在吃饭呢`); }}class Student extends People { constructor (name, number) { super(name); this.number = number; } sayHi() { console.log(`${this.name} 说了 hi`); }}class Teacher extends People { constructor(name, major) { super(name); this.major = major; } teach() { console.log(`${this.name} 教授 ${this.major} 课程`); }}let xialuo = new Student('夏洛', 89);xialuo.sayHi(); 注意: class是ES6的语法规范,由ECMA委员会发布; ECMA 只规定语法规则,即为我们代码的书写规范,不规定如何实现; 以上实现方式都是V8引擎的实现方式,也是主流的; 原型链 问题解答:如何准确判断一个变量是不是数组 arr instaceof Array问题解答:class的原型本质,怎么理解 原型和原型链的图示 属性和方法的执行规则 问题解答:手写一个简易的jquery,考虑插件和扩展性123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354class jQuery{ constructor (selector) { let result = document.querySelectorAll(selector); let length = result.length; for (let i = 0; i < length; i++) { this[i] = result[i]; } this.length = length; this.selector = selector; } each(fn) { for (let i = 0; i < this.length; i++) { fn(this[i], i) } } get(index) { return this[index]; } on (type, fn) { return this.each((elem) => { window.addEventListener(type, fn, false); }) }}const $p = new jQuery('p');$p.each(elem => (console.log(elem)))$p.on('click', elem => (console.log('click:', elem)))// 插件jQuery.prototype.dialog = function() { alert('jquery dialog');}// 扩展class myJQuery extends jQuery{ constructor(selector) { super(selector); } addClass() { } style() { }}","categories":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/categories/JavaScript/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/tags/JavaScript/"}]},{"title":"javascript复习:变量类型和计算","slug":"javascript复习:变量类型和计算","date":"2020-03-29T04:24:02.000Z","updated":"2020-03-29T04:24:02.000Z","comments":true,"path":"2020/03/29/javascript复习:变量类型和计算/","link":"","permalink":"https://blog.magicyou.cn/2020/03/29/javascript%E5%A4%8D%E4%B9%A0%EF%BC%9A%E5%8F%98%E9%87%8F%E7%B1%BB%E5%9E%8B%E5%92%8C%E8%AE%A1%E7%AE%97/","excerpt":"问题 typeof 能判断哪些类型 何时使用 === 和 == 值类型和引用类型区别","text":"问题 typeof 能判断哪些类型 何时使用 === 和 == 值类型和引用类型区别 补充知识: 变量类型1. 变量类型(值类型,引用类型) 值类型undefinedstringnumberbooleansymbol 引用类型null 特殊引用类型objectarrayfunction 2. 深拷贝1234567891011121314151617function deepClone(obj = {}) { if (typeof obj !== 'object' || obj == null) { return obj; } let result; if (obj instanceof Array) { result = []; } else { result = {}; } for (const key in obj) { if (obj.hasOwnProperty(key)) { result[key] = deepClone(obj[key]) } } return result;} 问题解答:值类型和引用类型区别值类型 栈 引用类型 堆 问题解答: typeof 能判断哪些类型 识别所有值类型 识别 函数类型 function 识别引用类型,但对于 null ,object,array, 都识别为Object; 补充知识:变量计算1. 类型转换 字符串拼接 == if 语句和逻辑运算 运算符 “ + ”1234const a = 100 + 10; // 110const b = 100 + '10'; //10010const c = true + '10' // true10const d = false + true; // 1 运算符 “ == ”12345100 == '100' // true0 == '' // true0 == false // truefalse == '' // truenull == undefined // true 2. falsely 变量,truly变量以下是falsely变量,除此之外全是truly变量: 123456!!0 === false!!NaN === false!!'' === false!!null === false!!undefined === false!!false === false 问题解答: 何时使用 === 和 ==除了 ‘== null’,之外,其他都一律用 ‘===’,例如: 12const obj = { x: 100 };if (obj.a == null) {}","categories":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/categories/JavaScript/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/tags/JavaScript/"}]},{"title":"uni-app开发微信公众号","slug":"uni-app-开发微信公众号","date":"2020-01-04T13:33:00.000Z","updated":"2020-01-04T13:33:00.000Z","comments":true,"path":"2020/01/04/uni-app-开发微信公众号/","link":"","permalink":"https://blog.magicyou.cn/2020/01/04/uni-app-%E5%BC%80%E5%8F%91%E5%BE%AE%E4%BF%A1%E5%85%AC%E4%BC%97%E5%8F%B7/","excerpt":"记录一下uni-app开发微信公众号遇到的问题。","text":"记录一下uni-app开发微信公众号遇到的问题。 之前用 uni-app 开发App,小程序,这次用来开发微信公众号,防止客户后期 ”变卦“,要做app或者小程序。当然也遇到一些问题; 问题1 如何调起微信支付 判断window原型链是否有 ” WeixinJSBridge “ 12345678910111213141516171819202122if (window.hasOwnProperty('WeixinJSBridge')) { WeixinJSBridge.invoke('getBrandWCPayRequest', { "appId":"wx2421b1c4370ec43b", //公众号ID,由商户传入 "timeStamp":"1395712654", //时间戳,自1970年以来的秒数 "nonceStr":"e61463f8efa94090b1f366cccfbbb444", //随机串 "package":"prepay_id=u802345jgfjsdfgsdg888", "signType":"MD5", //微信签名方式: "paySign":"70EA570631E4BB79628FBCA90534C63FF7FADD89" //微信签名 }, (res) => { if (res.err_msg === "get_brand_wcpay_request:ok") { this.$toast.success('支付成功'); // .... 支付成功后操作 } else { this.$toast.warning('支付失败'); // .... 支付失败后操作 } }); } else { this.$toast.warning('支付失败'); } 问题2 海报下载 尝试拼canvas后,转为base64下载,但是不成功,微信公众号不允许此行为; 微信公众号虽然不支持下载,但是可以长按图片图片保存 canvas容器 12<canvas canvas-id="myCanvas" id="myCanvas" ref="myCanvas" style=" position:fixed; top:0;left:10000px; width: 985px; height: 1585px"></canvas> Canvas 海报生成 1234567891011121314151617181920212223242526272829303132333435363738// 绘制canvas图片var bgPath = '/static/images/bg_invite.jpg' // 本地静态资源图片var name = this.details.name;var img_base64 = this.details.img_base64; // 远程拿下来的二维码base64const ctx = uni.createCanvasContext('myCanvas') ctx.setFillStyle('#ffffff');ctx.fillRect(0,0,985,1585);uni.downloadFile({ url: bgPath, //仅为示例,并非真实的资源 success: (res) => { console.log('bgPath_res:', res) ctx.drawImage(res.tempFilePath, 0, 0, 985, 1280) uni.downloadFile({ url: img_base64, //仅为示例,并非真实的资源 success: (res) => { console.log('img_base64_res:', res) ctx.drawImage(res.tempFilePath, 40, 1330, 200, 200) ctx.setFillStyle('#fff'); ctx.setFontSize(46) ctx.fillText('邀请得奖励了!', 360, 1100) ctx.setFillStyle('#3C3C3C'); ctx.setFontSize(46) ctx.fillText(name, 280, 1390) ctx.setFillStyle('#3C3C3C'); ctx.setFontSize(36) ctx.fillText('关注车汇管,让养车轻松起来!', 280, 1490) ctx.draw() } }); }}) 从 生成的海报canvas,获取base64,放进img 标签 123456789101112131415161718192021222324252627282930313233343536 <view class="wrapper-poster" v-if="is_show_poster"> <view class="wrapper-poster-inner"> <image class="img-poster" :src="poster_base64" mode="" ></image> </view> <text class="txt-tip">长按图片保存</text> <button class="btn-close" @click="is_show_poster = false" ><text class="iconfont icondelete2"></text></button></view> <script> downloadQr() { console.log('----'); let canvas = document.getElementById('myCanvas').getElementsByTagName('canvas')[0]; let href = canvas.toDataURL() // 获取canvas对应的base64编码 uni.showLoading({ title: '正在保存...' }); setTimeout(() => { this.poster_base64 = href; // let a = document.createElement('a') // 创建a标签 // a.download = this.details.name+'的邀请海报.png' // 设置图片名字 // a.href = href // a.dispatchEvent(new MouseEvent('click')) uni.hideLoading(); this.is_show_poster = true; }, 1000); return; } </script> 问题3 调用微信公众号的导航 引入 jssdk 1<script src="//res.wx.qq.com/open/js/jweixin-1.6.0.js"></script> 微信公众号官方是这么用 12345678910111213141516wx.getLocation({// type:'gcj02', // 默认为 wgs84 返回 gps 坐标,gcj02 返回可用于 wx.openLocation 的坐标 success: function (res) { console.log(res.latitude); console.log(res.longitude); wx.openLocation({ latitude: 31, // 纬度,范围为-90~90,负数表示南纬 longitude: 121, // 经度,范围为-180~180,负数表示西经 scale: 8, // 缩放比例 name:"测试", address:"测试详细地址", success:function(r){ console.log(r) } }) } }) 然而当放进uni-app后会报错 后来查阅得知 uni-app 内 ’wx‘ 关键字被重写,所以需要另辟蹊径,使用关键字 ‘ jWeixin ’,如下: 1234567891011121314151617181920212223242526let config = { appId: '', // 必填,公众号的唯一标识 timestamp: , // 必填,生成签名的时间戳 nonceStr: '', // 必填,生成签名的随机串 signature: '',// 必填,签名};jWeixin.config({ debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。 ...config, jsApiList: ['openLocation'] // 必填,需要使用的JS接口列表});jWeixin.error((res) => { this.$toast.warning('开启导航失败');});jWeixin.ready(() => { jWeixin.openLocation({ latitude: this.latitude, // 纬度,浮点数,范围为90 ~ -90 longitude: this.longitude, // 经度,浮点数,范围为180 ~ -180。 name: this.store_details.store_name, // 位置名 address: this.store_details.store_name, // 地址详情说明 scale: 1, // 地图缩放级别,整形值,范围从1~28。默认为最大 infoUrl: '' // 在查看位置界面底部显示的超链接,可点击跳转 });});","categories":[{"name":"uni-app","slug":"uni-app","permalink":"https://blog.magicyou.cn/categories/uni-app/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/tags/JavaScript/"},{"name":"微信","slug":"微信","permalink":"https://blog.magicyou.cn/tags/%E5%BE%AE%E4%BF%A1/"},{"name":"微信公众号","slug":"微信公众号","permalink":"https://blog.magicyou.cn/tags/%E5%BE%AE%E4%BF%A1%E5%85%AC%E4%BC%97%E5%8F%B7/"},{"name":"uni-app","slug":"uni-app","permalink":"https://blog.magicyou.cn/tags/uni-app/"}]},{"title":"position:sticky","slug":"css-不常用","date":"2019-12-01T12:23:45.000Z","updated":"2019-12-01T08:22:21.000Z","comments":true,"path":"2019/12/01/css-不常用/","link":"","permalink":"https://blog.magicyou.cn/2019/12/01/css-%E4%B8%8D%E5%B8%B8%E7%94%A8/","excerpt":"sticky 英文字面意思是粘,粘贴,所以可以把它称之为粘性定位。","text":"sticky 英文字面意思是粘,粘贴,所以可以把它称之为粘性定位。 前言position: sticky; 基于用户的滚动位置来定位。粘性定位的元素是依赖于用户的滚动,在 position:relative 与 position:fixed 定位之间切换。 它的行为就像 position:relative; 而当页面滚动超出目标区域时,它的表现就像 position:fixed;,它会固定在目标位置。 元素定位表现为在跨越特定阈值前为相对定位,之后为固定定位。 这个特定阈值指的是 top, right, bottom 或 left 之一,换言之,指定 top, right, bottom 或 left 四个阈值其中之一,才可使粘性定位生效。否则其行为与相对定位相同。","categories":[{"name":"前端","slug":"前端","permalink":"https://blog.magicyou.cn/categories/%E5%89%8D%E7%AB%AF/"}],"tags":[{"name":"CSS3","slug":"CSS3","permalink":"https://blog.magicyou.cn/tags/CSS3/"}]},{"title":"环形温度调节实现","slug":"javascript-环形温度调节实现","date":"2019-09-03T13:33:00.000Z","updated":"2019-09-03T13:33:00.000Z","comments":true,"path":"2019/09/03/javascript-环形温度调节实现/","link":"","permalink":"https://blog.magicyou.cn/2019/09/03/javascript-%E7%8E%AF%E5%BD%A2%E6%B8%A9%E5%BA%A6%E8%B0%83%E8%8A%82%E5%AE%9E%E7%8E%B0/","excerpt":"同事要做个物联网小程序,可以通过小程序控制家里空调的温度设置(具体怎么实现以后研究)。温度控制界面上一个大大的环形,需求是拨动这个环形上的拖柄,可控制环形色条。","text":"同事要做个物联网小程序,可以通过小程序控制家里空调的温度设置(具体怎么实现以后研究)。温度控制界面上一个大大的环形,需求是拨动这个环形上的拖柄,可控制环形色条。 先来预设一些数值 暂定画布宽高为绝对值(500px * 500px)(实际情况可能需要做成自适应的尺寸) 轨道圆形的圆心为(250, 250),半径分别为100px、120px 上面两个条件之后轨道上的小圆型(滑柄)半径为20, 圆心坐标为(360,250) 这几个数值下面要用。 模板如下: 1234<view style=""> <canvas @touchstart='touchStart' @touchmove='touchMove' style="width: 500px; height: 500px;background: #333333;" canvas-id="secondCanvas"></canvas> </view> data: 1234567objCanvas: null, // canvas画布对象handleArc: { // 轨道上小圆行初始值 r: 20, x: 360, y: 250, angle: 0}, 1. 设计思路 这样的需求只能选用canvas画布,div做出的环形无法控制颜色任意填充面积; 画布上的环形需要一个‘‘运动轨道’’ 和 一个可以用来拖拽的小圆(下文称为滑柄),小圆要在“轨道”上运动; 拖动的同时要限制小圆,不能脱离轨道,轨道填充颜色要和小圆一致; 2.我们先来完成初始状态绘制1234567891011121314151617181920212223242526272829303132333435363738394041this.objCanvas = wx.createCanvasContext('secondCanvas');var context = this.objCanvas;context.setStrokeStyle("#00ff00")// 先画出轨道 内轨圆形轮廓context.beginPath();context.arc(250, 250, 100, 0, 2 * Math.PI, true)context.stroke()// 画出轨道 外轨圆形轮廓context.beginPath();context.arc(250, 250, 120, 0, 2 * Math.PI, true)context.stroke()// 绘制轨道填充圆context.beginPath();context.moveTo(250,250);context.arc(250, 250, 120, this.handleArc.angle * Math.PI /180, 0 * Math.PI /180, true)context.closePath();context.fillStyle='#f15a4a';context.fill();// 填补轨道内圆形颜色,使其展示为空心圆context.beginPath();context.moveTo(250,250);context.arc(250, 250, 100, 0, 2 * Math.PI, true)context.closePath();context.fillStyle='#333333';context.fill();// 绘制滑块轨道上圆形滑块context.beginPath();context.moveTo(200,200);context.arc(this.handleArc.x, this.handleArc.y, this.handleArc.r, 0,Math.PI*2,false); context.fillStyle='#f15a4a';context.fill();// 绘制context.draw() 大概样子如下: 3. 怎样让他能拖动呢?(1)首先来复习一下三角函数 复习几个js的Math对象方法: 方法 描述 abs(x) 返回数的绝对值。 acos(x) 返回数的反余弦值。 asin(x) 返回数的反正弦值。 atan(x) 以介于 -PI/2 与 PI/2 弧度之间的数值来返回 x 的反正切值。 atan2(y,x) 返回从 x 轴到点 (x,y) 的角度(介于 -PI/2 与 PI/2 弧度之间)。 ceil(x) 对数进行上舍入。 cos(x) 返回数的余弦。 exp(x) 返回 e 的指数。 floor(x) 对数进行下舍入。 log(x) 返回数的自然对数(底为e)。 max(x,y) 返回 x 和 y 中的最高值。 min(x,y) 返回 x 和 y 中的最低值。 pow(x,y) 返回 x 的 y 次幂。 random() 返回 0 ~ 1 之间的随机数。 round(x) 把数四舍五入为最接近的整数。 sin(x) 返回数的正弦。 sqrt(x) 返回数的平方根。 tan(x) 返回角的正切。 toSource() 返回该对象的源代码。 valueOf() 返回 Math 对象的原始值。 当然不会都用上,顺便复习其他的 (2)再来理一下思路 canvas画布上的原始坐标轴以左上角为(0, 0)原点,原点为基础,右方位x轴正方向,下方为y轴正方向; 手指(或鼠标)在进入canvas画布就开始检测是否可以触发滑柄滑动; 根据手指的位置需要转换为滑柄在轨道上的准确位置; 手指在画布移动事件 12345678910111213141516touchMove(e) { // 手指移入进画布后,获取坐标 var cli = {}; cli.x = e.touches[0].x; cli.y = e.touches[0].y; // 矫正为滑柄在轨道坐标 var b = this.checkDirc(cli); this.handleArc.x = b.x this.handleArc.y = b.y this.handleArc.angle = b.angle // 重新绘制画布 this.reDrawCanvas(); }, 主要是如何获取滑柄在轨道上的准确位置 利用相似三角形定律和几个三角函数如下: 你看这个图他又丑又草,凑合着看版本 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950// 判断是左还是右 checkDirc: function(point) { // 获取小圆心在轨道圆上的坐标 // 以手指和轨道圆心为斜边的直角三角形和滑柄圆心和轨道圆心为斜边的直角三角形是相似三角形 // 手指斜边 var pc = Math.sqrt(Math.pow((point.x - 250), 2) + Math.pow((point.y - 250), 2)); var ph = point.y - 250; var pa = point.x - 250; // 滑柄在运动轨迹圆半径 var c = 110; var h = Math.abs((ph * c) / pc); var a = Math.abs(Math.sqrt(Math.pow(c, 2) - Math.pow(h, 2))) // 计算出夹角度数 var angle = Math.asin(h / c) * 180 / Math.PI; var handleArcPoint = {}; // 手指可能在(250, 250)的四个方位,根据不同的方向,做相应计算 // 判断方向,获取小圆坐标 if (point.x > 250 && point.y > 250) { handleArcPoint.x = 250 + a; handleArcPoint.y = 250 + h; angle = angle; } else if (point.x > 250 && point.y < 250) { handleArcPoint.x = 250 + a; handleArcPoint.y = 250 - h; angle = 90 - angle + 270; } else if (point.x < 250 && point.y > 250) { handleArcPoint.x = 250 - a; handleArcPoint.y = 250 + h; angle = 90 - angle + 90; } else if (point.x < 250 && point.y < 250) { handleArcPoint.x = 250 - a; handleArcPoint.y = 250 - h; angle = angle + 180; } if (this.handleArc.angle > 270) { handleArcPoint.x = 250; handleArcPoint.y = 250 - 110; angle = 270; } handleArcPoint.angle = angle; return handleArcPoint; } 基本上可以了,然后就是重新绘制画布: 1234567891011121314151617181920212223242526272829303132333435363738reDrawCanvas: function() { var context = this.objCanvas; // 清除画布颜色(实际就是覆盖颜色) context.clearRect(0, 0, 500, 500) context.setStrokeStyle("#00ff00") context.beginPath(); context.arc(250, 250, 100, 0, 2 * Math.PI, true) context.stroke() context.beginPath(); context.arc(250, 250, 120, 0, 2 * Math.PI, true) context.stroke() context.beginPath(); context.moveTo(250, 250); context.arc(250, 250, 120, this.handleArc.angle * Math.PI / 180, 0 * Math.PI / 180, true) context.closePath(); context.fillStyle = '#f15a4a'; context.fill(); context.beginPath(); context.moveTo(250, 250); context.arc(250, 250, 100, 0, 2 * Math.PI, true) context.closePath(); context.fillStyle = '#333333'; context.fill(); context.beginPath(); context.moveTo(200, 200); context.arc(this.handleArc.x, this.handleArc.y, this.handleArc.r, 0, Math.PI * 2, false); // 绘制滑块 context.fillStyle = '#f15a4a'; context.fill(); context.draw() } 最终效果如下: 4. 注意小程序有自己的长度单位rpx,但是canvas是个例外,canvas只认px,所以,实际使用在小程序时,需要再处理一下单位,最好方案是响应式的单位(原理和rem类似)","categories":[{"name":"前端","slug":"前端","permalink":"https://blog.magicyou.cn/categories/%E5%89%8D%E7%AB%AF/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/tags/JavaScript/"},{"name":"微信小程序","slug":"微信小程序","permalink":"https://blog.magicyou.cn/tags/%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F/"},{"name":"canvas","slug":"canvas","permalink":"https://blog.magicyou.cn/tags/canvas/"}]},{"title":"vue-组件封装中之slot插槽使用","slug":" vue-组件封装中之slot插槽使用","date":"2019-08-03T13:33:00.000Z","updated":"2019-08-03T13:33:00.000Z","comments":true,"path":"2019/08/03/ vue-组件封装中之slot插槽使用/","link":"","permalink":"https://blog.magicyou.cn/2019/08/03/%20vue-%E7%BB%84%E4%BB%B6%E5%B0%81%E8%A3%85%E4%B8%AD%E4%B9%8Bslot%E6%8F%92%E6%A7%BD%E4%BD%BF%E7%94%A8/","excerpt":"","text":"引用一下Vue官方的说法 https://cn.vuejs.org/v2/guide/components-slots.html 1在 2.6.0 中,我们为具名插槽和作用域插槽引入了一个新的统一的语法 (即 v-slot 指令)。它取代了 slot 和 slot-scope 这两个目前已被废弃但未被移除且仍在文档中的 attribute。新语法的由来可查阅这份 RFC。 通俗的说就是插槽可以用来在组件封装的时候放自定义的任何内容 基本使用 子组件 aimm-panel 12345678910<template> <div class="wrapper-panel"> <p class="txt-header"> <span>{{ title }}</span> <span class="header-right"> <slot></slot> </span> </p> </div></template> 父组件里面用法 1234567891011121314<aimm-panel title="Position"> <template v-slot> <div class="box-item-info"> <div class="item-position-info"> <p class="txt-title">LATITUDE</p> <p class="txt-value">8° 0.499' N</p> </div> <div class="item-position-info"> <p class="txt-title">LONGITUDE</p> <p class="txt-value">8° 0.499' N</p> </div> </div> </template> </aimm-panel> 有name的插槽有时候我们需要在组件内多个指定地方放不同内容,可以加上name属性 子组件 aimm-panel 12345678910111213<template> <div class="wrapper-panel"> <p class="txt-header"> <span>{{ title }}</span> <span class="header-right"> <slot name="headerRight"></slot> </span> </p> <div> <slot name="body"></slot> </div> </div></template> 父组件用法 1234567891011121314<aimm-panel title="Position"> <template v-slot:body> <div class="box-item-info"> <div class="item-position-info"> <p class="txt-title">LATITUDE</p> <p class="txt-value">8° 0.499' N</p> </div> <div class="item-position-info"> <p class="txt-title">LONGITUDE</p> <p class="txt-value">8° 0.499' N</p> </div> </div> </template> </aimm-panel> 或许官方组件更清晰 1234567891011<div class="container"> <header> <slot name="header"></slot> </header> <main> <slot></slot> </main> <footer> <slot name="footer"></slot> </footer></div>","categories":[{"name":"前端","slug":"前端","permalink":"https://blog.magicyou.cn/categories/%E5%89%8D%E7%AB%AF/"}],"tags":[{"name":"Vue","slug":"Vue","permalink":"https://blog.magicyou.cn/tags/Vue/"},{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/tags/JavaScript/"}]},{"title":"冒泡排序和快速排序","slug":"javascript-冒泡排序和快速排序","date":"2019-08-03T13:33:00.000Z","updated":"2019-08-03T13:33:00.000Z","comments":true,"path":"2019/08/03/javascript-冒泡排序和快速排序/","link":"","permalink":"https://blog.magicyou.cn/2019/08/03/javascript-%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F%E5%92%8C%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F/","excerpt":"冒泡排序和快速排序","text":"冒泡排序和快速排序 冒泡排序12345678910111213141516171819202122function paixu(arr) { var temp; for (let i = 0; i < arr.length; i++) { for (let j = 0; j < i+1; j++) { if (arr[j] > arr[j+1]) { temp = arr[j] arr[j] = arr[j+1] arr[j+1] = temp } } } return arr}var arr = [1, 4, 2, 2, 3, 5, 6, 9, 34, 23, 12]console.log(arr);console.log(paixu(arr)); 快速排序1234567891011121314151617181920212223function fastxu(arr) { if (arr.length <= 1) { return arr } var middelIndex = Math.floor(arr.length/2) var middle = arr[middelIndex] arr.splice(middelIndex, 1) var left = [] var right = [] for (let i = 0; i < arr.length; i++) { if (arr[i] > middle) { left.push(arr[i]) } else { right.push(arr[i]) } } return fastxu(left).concat([middle], fastxu(right))}var arr = [1, 4, 2, 2, 3, 5, 6, 9, 34, 23, 12]console.log(arr);console.log(fastxu(arr));","categories":[],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/tags/JavaScript/"},{"name":"PHP","slug":"PHP","permalink":"https://blog.magicyou.cn/tags/PHP/"},{"name":"微信","slug":"微信","permalink":"https://blog.magicyou.cn/tags/%E5%BE%AE%E4%BF%A1/"},{"name":"小程序","slug":"小程序","permalink":"https://blog.magicyou.cn/tags/%E5%B0%8F%E7%A8%8B%E5%BA%8F/"}]},{"title":"微信小程序---PHP获取微信小程序码","slug":"php-PHP获取微信小程序码","date":"2019-07-03T13:33:00.000Z","updated":"2019-07-03T13:33:00.000Z","comments":true,"path":"2019/07/03/php-PHP获取微信小程序码/","link":"","permalink":"https://blog.magicyou.cn/2019/07/03/php-PHP%E8%8E%B7%E5%8F%96%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F%E7%A0%81/","excerpt":"记录一下折腾小程序遇到的问题,PHP获取微信小程序码。省的以后再次踩坑","text":"记录一下折腾小程序遇到的问题,PHP获取微信小程序码。省的以后再次踩坑 小程序端要合成海报,上面带小程序码,所以有了这个博文过程。 先提供封装的工具类部分源码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990/** * * 获取小程序码 * @param string $access_token 接口凭证 * @param string $urlPath 小程序路径 * @return array 应答 */ public function createQRCode($access_token, $urlPath) { $url = "https://api.weixin.qq.com/wxa/getwxacode?access_token={$access_token}"; $post_data = [ "path" => $urlPath, "auto_color" => false, "width" => 240, ]; $re_string = $this->curlPost($url, $post_data); $re_data = json_decode($re_string, true); if ($re_data['errcode']) { return $re_data; } $path = 'Uploads/recode/' . date('Ymd') . '/'; if (!is_dir($path)) { mkdir($path, 0755, true); } $filepath = $path . 'recode_' . time() . ".png"; $file = fopen($filepath, "w"); fwrite($file, $re_string); fclose($file); return ['file_path'=>$filepath]; } /** * * 登录凭证校验。通过 wx.login 接口获得临时登录凭证 code 后传到开发者服务器调用此接口完成登录流程 * @return array 应答 */ public function getAccessToken() { $appid = $this->appid; // 小程序APPID $secret = $this->secret; // 小程序secret $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={$appid}&secret={$secret}"; return $this->curlGet($url); } /** * 发送请求 * @param string $url 请求地址 * @return string 应答json字符串 */ public function curlPost($url, $dataObj) { $curl = curl_init(); curl_setopt($curl, CURLOPT_URL, $url); curl_setopt($curl, CURLOPT_HEADER, 0); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); curl_setopt($curl, CURLOPT_POST, 1); curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($dataObj)); curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0); curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); curl_setopt($curl, CURLOPT_HTTPHEADER, array("Content-type: application/json") ); $ret = curl_exec($curl); curl_close($curl); return $ret; } /** * GET方式请求接口 * @param [String] $url [curl地址] * @return [返回结果数据] */ private function curlGet($url) { $curl = curl_init(); curl_setopt($curl, CURLOPT_URL, $url); curl_setopt($curl, CURLOPT_HEADER, 0); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); $data = curl_exec($curl); curl_close($curl); return json_decode($data, true); } 不想啰嗦的直接拿走用,写的简单,直接用就可以。 一步一步阐述遇到的问题* 官方文档先来看看官方文档,传送门[https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/qr-code/wxacode.createQRCode.html] 官方提供了三种获取小程序码Api,对应不同的业务场景。这里我选用第二个, * 报错 47001完整错误信息如下: 1string(69) "{"errcode":47001,"errmsg":"data format error hint: [21WqGa05154711]"}" 死活找不到问题,post修了好几次,后来找到一篇博文,说要用post请求body要用raw,格式为json 1"Content-type: application/json" 改完后依旧报同样的错,无奈,再后来找到博文说,获取获取小程序码第二三个接口调用时,post参数不需要放access_token,试了试,额,,,,果然好了。 先来看看官方的文档证据: 难道是我不够心细,没看到官方说明。仔细从上到下,没发现说post参数不需要access_token,官方文档有点坑X。 * 其他报错其他也遇到了错误,不过报错翻译就明白怎么解决,比如: access_token失效;access_token有效期7200秒,做一下存储,处理一下就行 path参数缺失;第三个Api的url路径参数名是page,额,,,我搞岔劈了 进一步优化存储access_tokenaccess_token有效期为两小时,且每天获取次数有限制,最好做一下缓存,方法很多,文件存储,radis存储等。我这里用Mysql存一下,还能记录获取历史记录。 创建数据表: 12345678CREATE TABLE `tp_wx_access_token` ( `id` int(11) NOT NULL AUTO_INCREMENT, `access_token` varchar(255) DEFAULT NULL COMMENT 'access_token', `appid` varchar(100) DEFAULT NULL COMMENT '微信小程序appid', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `create_timestamp` bigint(20) DEFAULT NULL COMMENT '创建时间戳', PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8mb4; 存储小程序码省的每次都要重新获取,既然每次都要获取后都会存下来,索性获取记录也做个存储。获取前参数作为条件查询是否存在,有了直接返回,没的话获取再存储再返回 创建数据表: 12345678CREATE TABLE `tp_wx_acode` ( `id` int(11) NOT NULL AUTO_INCREMENT, `appid` varchar(50) DEFAULT NULL COMMENT '小程序appid', `file_path` varchar(255) DEFAULT NULL COMMENT '小程序图片保存', `url_path` varchar(255) DEFAULT NULL COMMENT '关联的url', `create_time` datetime DEFAULT NULL COMMENT '创建时间', PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=22 DEFAULT CHARSET=utf8mb4 COMMENT='小程序码生成记录'; 这样子下来,保证了查询速度,都做了存储,自我感觉良好 使用123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990/** * 获取小程序码 * @param $param * @return array */ public function getAcode($param) { $data = $this ->where(['appid'=>C('WX_APPID'), 'url_path' =>$param['url'] ]) ->find(); if ($data) { $data['file_path'] = 'https://' . $_SERVER['HTTP_HOST'] . '/' . $data['file_path']; $array = [ 'code' => 1, 'msg' => '获取成功', 'data' => $data, ]; return $array; } // 检查token是否过期 $access_token_info = M('wx_access_token') ->where(['appid' => C('WX_APPID')]) ->order('create_time desc') ->find(); $applet = new Applet(); if ($access_token_info && $access_token_info['create_timestamp'] + 7200 > time()) { $access_token = $access_token_info['access_token']; } else { $re_token = $applet->getAccessToken(); if (!$re_token['access_token']) { $array = [ 'code' => 0, 'msg' => 'access_token获取失败', ]; return $array; } $access_token = $re_token['access_token']; $data = [ 'access_token' => $access_token, 'appid' => C('WX_APPID'), 'create_time' => date('Y-m-d H:i:s'), 'create_timestamp' => time(), ]; M('wx_access_token')->add($data); } $re_create = $applet->createQRCode($access_token, $param['url']); if ($re_create['errcode']) { $array = [ 'code' => 0, 'msg' => '二维码获取失败', 'data' => $re_create ]; return $array; } $data = [ 'appid' => C('WX_APPID'), 'file_path' => $re_create['file_path'], 'url_path' => $param['url'], 'create_time' => date('Y-m-d H:i:s'), ]; $add_result = $this->add($data); if (!$add_result) { $array = [ 'code' => 0, 'msg' => '获取失败' ]; return $array; } $array = [ 'code' => 1, 'msg' => '获取成功', 'data' =>[ 'file_path' => 'https://'. $_SERVER['HTTP_HOST'] .'/'. $re_create['file_path'], ], ]; return $array; } 收工","categories":[{"name":"前端","slug":"前端","permalink":"https://blog.magicyou.cn/categories/%E5%89%8D%E7%AB%AF/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/tags/JavaScript/"},{"name":"PHP","slug":"PHP","permalink":"https://blog.magicyou.cn/tags/PHP/"},{"name":"微信小程序","slug":"微信小程序","permalink":"https://blog.magicyou.cn/tags/%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F/"}]},{"title":"微信小程序---合成海报分享到朋友圈","slug":"wx-微信小程序合成海报","date":"2019-07-03T13:33:00.000Z","updated":"2019-07-03T13:33:00.000Z","comments":true,"path":"2019/07/03/wx-微信小程序合成海报/","link":"","permalink":"https://blog.magicyou.cn/2019/07/03/wx-%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F%E5%90%88%E6%88%90%E6%B5%B7%E6%8A%A5/","excerpt":"记录一下折腾小程序遇到的问题。小程序目前只提供了分享给朋友或者群聊,想要分享到朋友圈只能合成带小程序码的海报分享到朋友圈","text":"记录一下折腾小程序遇到的问题。小程序目前只提供了分享给朋友或者群聊,想要分享到朋友圈只能合成带小程序码的海报分享到朋友圈 本篇的小程序使用的是uni-app开发小程序,代码都是适用于uni-app的,方法和微信小程序官方差别不大,”uni.”改成”wx.”基本就可以使用。 合成带小程序码的海报分享到朋友圈,小程序码怎么获取看前文《PHP获取微信小程序码》,这篇文章要做的是所有图片素材已经有网络路径,当然路径是https协议的,并且在小程序后台已配置。 思路 合理布局,画布设置好尺寸,放在屏幕以外备用 wx.downloadFile()方法下载所有需要使用的图片,得到的路径为本地临时路径 canvas绘制海报;将图片合理绘制在海报上预设的位置 将canvas提取出来,获得海报本地临时路径,在弹窗中展示使用 点击按钮,wx.saveImageToPhotosAlbum(),使用海报本地临时路径下载到本地相册 准备工作data中定义几个变量,方便下文使用: 123456789product_cover: '', // 商品图urlscreenWidth: 375 , // 海报宽度windowHeight: 665, // 海报高度acode_path: null, // 小程序码路径qrcode_temp: '', // 小程序码本地临时路径goods_temp: '', // 商品图本地临时路径popupShow: false, // 是否显示海报弹窗maskShow: false, // 遮罩曾是否显示tempFilePath: null // 海报本地临时路径 模板和css 123456789101112131415<!-- canvas绘制海报 --> <canvas canvas-id="myCanvas" class="box-canvas" style="width:375px;" :style="{height: windowHeight + 'px'}"></canvas> <!-- 海报弹窗模板 --> <view class="box-mask" v-if="popupShow"></view> <!-- 海报弹窗--> <view class="box-poster" v-if="popupShow"> <view class="btn-close iconfont iconguanbi2" @click="cancelPoster"></view> <view class="canvas-box"> <image :src="tempFilePath"></image> </view> <button class="btn-save" @click="canvasToImage">下载图片</button> </view> 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849/* 把canvas直接展示,移出到屏幕可视区域以外,方便绘制,调试的时候放在屏幕上就行 */.box-canvas{ position: fixed; top:100000px; } /* 海报弹窗 */ .box-poster{ position: fixed; top:50%; left: 50%; transform: translate(-50%, -50%); z-index: 101; .btn-close{ position: absolute; top: -25rpx; right: -25rpx; width: 50rpx; height: 50rpx; line-height: 50rpx; text-align: center; font-size: 40rpx; background: #fff; border-radius: 50%; color: $uni-color-primary; box-shadow: 2rpx 2rpx 10rpx $uni-color-primary; } .canvas-box{ width: 460rpx; height: 820rpx; display: inline-block; image{ width: 460rpx; height: 820rpx; } } .btn-save{ margin-top: 5px; width: 460rpx; height: 75rpx; line-height: 75rpx; background: #fa4547; color: #fff; font-size: 26rpx; } } 主要是把画布设置好尺寸,放在屏幕以外的地方 下载所有需要使用的图片https://developers.weixin.qq.com/miniprogram/dev/api/network/download/wx.downloadFile.html 官方说明 wx.downloadFile() 12345678910111213DownloadTask wx.downloadFile(Object object)下载文件资源到本地。客户端直接发起一个 HTTPS GET 请求,返回文件的本地临时路径,单次下载允许的最大文件为 50MB。使用前请注意阅读相关说明。注意:请在服务端响应的 header 中指定合理的 Content-Type 字段,以保证客户端正确处理文件类型。示例代码wx.downloadFile({ url: 'https://example.com/audio/123', //仅为示例,并非真实的资源 success (res) { // 只要服务器有响应数据,就会把响应内容写入文件并进入 success 回调,业务需要自行判断是否下载到了想要的内容 }}) 关键源码 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556/** * 缓存图片 * @param {Object} target 目标 * @param {Object} img_url 图片网络链接 */ cacheFile(target, img_url, func=null) { let _this = this; wx.downloadFile({ url: img_url, success: function(res2) { if (target == 'qrcode') { console.log('二维码:' + res2.tempFilePath) //缓存二维码 _this.qrcode_temp = res2.tempFilePath; } else if (target == 'portrait') { console.log('头像:' + res2.tempFilePath) //缓存二维码 _this.portrait_temp = res2.tempFilePath; } else if (target == 'goods') { console.log('商品封面:' + res2.tempFilePath) //缓存二维码 _this.goods_temp = res2.tempFilePath; } if (func) { func() } } }) }, /** * 缓存图片 * @param {Object} target 目标 * @param {Object} img_url 图片网络链接 * @param {Object} func 回调函数 */ cacheFile(target, img_url, func=null) { let _this = this; wx.downloadFile({ url: img_url, success: function(res2) { if (target == 'qrcode') { console.log('二维码:' + res2.tempFilePath) _this.qrcode_temp = res2.tempFilePath; } else if (target == 'portrait') { console.log('头像:' + res2.tempFilePath) _this.portrait_temp = res2.tempFilePath; } else if (target == 'goods') { console.log('商品封面:' + res2.tempFilePath) _this.goods_temp = res2.tempFilePath; } if (func) { func() } } }) } canvas绘制海https://developers.weixin.qq.com/miniprogram/dev/api/canvas/wx.createOffscreenCanvas.html canvasToTempFilePath() 123456789101112131415wx.canvasToTempFilePath(Object object, Object this)把当前画布指定区域的内容导出生成指定大小的图片。在 draw() 回调里调用该方法才能保证图片导出成功。示例代码wx.canvasToTempFilePath({ x: 100, y: 200, width: 50, height: 50, destWidth: 100, destHeight: 100, canvasId: 'myCanvas', success(res) { console.log(res.tempFilePath) } 关键源码 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273/** * 绘制海报 */ drawImage() { //绘制canvas图片 var _this = this var bgPath = '/static/image/banner_index.png' var portraitPath = _this.portrait_temp var goodsPath = _this.goods_temp var qrPath = _this.qrcode_temp var hostNickname = 'test' var scale = _this.scale var windowWidth = _this.screenWidth var windowHeight = _this.windowHeight var product_price = _this.detail.product_price; var product_name = _this.detail.product_name; // // 绘制头像 // ctx.save() // ctx.beginPath() // ctx.arc(25, 25, 25, 0, 2 * Math.PI) // ctx.clip() // ctx.drawImage(portraitPath, 0, 0, 50, 50) // ctx.restore() const ctx = wx.createCanvasContext('myCanvas') ctx.setFillStyle('#ffffff'); ctx.fillRect(0,0,windowWidth,windowHeight); // 绘制商品图 ctx.drawImage(goodsPath, 0, 300 * scale, 375 * scale, 375 * scale, 15 * scale, 45 * scale, 345 * scale, 345 * scale); // 绘制 ctx.setFillStyle('#333333'); ctx.setFontSize(18 * scale) ctx.fillText(product_name, 15 * scale, 445 * scale) ctx.setFillStyle('red'); ctx.setFontSize(22 * scale) ctx.fillText('¥'+product_price, 15 * scale, 565 * scale) ctx.setFillStyle('#9a9a9a'); ctx.setFontSize(12 * scale) ctx.fillText('扫描或长按小程序码', 250 * scale, 620 * scale) // 绘制二维码 ctx.drawImage(qrPath, 260 * scale, 520 * scale, 80 * scale, 80 * scale) ctx.draw(true, function(){ wx.canvasToTempFilePath({ x: 0, y: 0, width: _this.windowWidth, height: _this.windowWidth , destWidth: _this.windowWidth * 4, destHeight: _this.windowWidth * 4, canvasId: 'myCanvas', success: function(res) { _this.tempFilePath = res.tempFilePath; }, fail: function(err) { uni.hideLoading(); console.log('失败') console.log(err) } }) }); return; }, wx.canvasToTempFilePath()必须在ctx.draw()执行完之后才能调用。 获得海报本地临时路径后,可以直接在image标签中使用,弹窗展示海报预览图。 点击保存海报到本地wx.saveImageToPhotosAlbum() 123456789wx.saveImageToPhotosAlbum(Object object)基础库 1.2.0 开始支持,低版本需做兼容处理。调用前需要 用户授权 scope.writePhotosAlbum保存图片到系统相册。示例代码wx.saveImageToPhotosAlbum({ success(res) { }}) 12345678910111213141516/** * 保存为文件 */ canvasToImage(){ var _this = this uni.showLoading({ title: '正在保存...' }); setTimeout(function(){ wx.saveImageToPhotosAlbum({ filePath: _this.tempFilePath, }) uni.hideLoading(); _this.popupShow = false; }, 1000); }, 成品如下:","categories":[{"name":"前端","slug":"前端","permalink":"https://blog.magicyou.cn/categories/%E5%89%8D%E7%AB%AF/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/tags/JavaScript/"},{"name":"微信小程序","slug":"微信小程序","permalink":"https://blog.magicyou.cn/tags/%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F/"},{"name":"uni-app","slug":"uni-app","permalink":"https://blog.magicyou.cn/tags/uni-app/"}]},{"title":"配置webgate拦截跳转到ldap登录页实现单点登录","slug":"Linux-单点登录配置 记录","date":"2019-02-24T10:23:00.000Z","updated":"2019-02-24T10:23:00.000Z","comments":true,"path":"2019/02/24/Linux-单点登录配置 记录/","link":"","permalink":"https://blog.magicyou.cn/2019/02/24/Linux-%E5%8D%95%E7%82%B9%E7%99%BB%E5%BD%95%E9%85%8D%E7%BD%AE%20%E8%AE%B0%E5%BD%95/","excerpt":"前提是企业已经做好LDAP系统,子系统已经实现自己的登录逻辑,且子系统只需要配置好webgate以及相关配置,就能对接LDAP单点登录。(对于LDAP也就了解,这里不做LDAP的详细搭建)","text":"前提是企业已经做好LDAP系统,子系统已经实现自己的登录逻辑,且子系统只需要配置好webgate以及相关配置,就能对接LDAP单点登录。(对于LDAP也就了解,这里不做LDAP的详细搭建) 了解一下通过LDAP登录的原理 ![image-20190413163116319](/Users/magicyou/Library/Application Support/typora-user-images/image-20190413163116319.png) • 单点集成,需要在应用系统服务器配置Apache服务器,对应用系统做反向代理,并在其上安装用于拦截Http请求的插件Webgate。 • OAM和插件通信,实现对应用系统的单点集成,此处需要应用系统改造现有登陆逻辑,接收OAM认证后传递的用户信息。 • 集成LDAP单点的同时,需实现逃生机制,即可实现在应用系统本地登录认证。 前面说过,我们的系统是在lnmp运行,所以Apache在这里作为中介,转到nginx的端口上的应用。 准备1. 增加用户和组User appuser Group appgrp 其他 apache安装路径 /app/soft/apache/ webgate目标安装路径 /app/soft/webgate/ 本机名 xxxx 本机ip 10.0.XX.X 按照集团给的配置信息(重要): Name Value WebGate ID XXX_WebGate Password for WebGate 123456 Access Server ID acc_id_oam Host name where an Access Server is installed node2 Port number the Access Server listens to 6000 安装apache 安装apache 尽可能不要用centos带的那个Apache,自己再装一个版本最好是2.2,因为老系统用的就是这个版本(毕竟webgate版本没有升级),防止莫名错误,低调行事。 修改httpd.conf 如下几项 12345678User appuserGroup appgrpListen 9008ServerName 10.0.XX.X:9008 /etc/hosts添加 10.0.XX.X hostname 安装webgate添加hosts文件 在j2ee系统安装apache的服务器用root用户登录服务器,添加hosts文件 1vi /etc/hosts 添加如下 1210.0.59.21 node110.0.59.22 node2 安装依赖包等 123456789101112131415161718192021222324cd /lib64/ln -s /usr/lib64/libstdc++.so.6 .ln -s /usr/lib64/libstdc++.so.5 .执行安装安装组件yum install libXau-1.0.6-4.el6.x86_64yum install libXau-1.0.6-4.el6.i686yum install libXdmcp-1.1.1-3.el6.x86_64yum install libXdmcp-1.1.1-3.el6.i686yum install libxcb-1.9.1-2.el6.x86_64yum install libxcb-1.9.1-2.el6.i686yum install libX11-1.6.0-2.2.el6.x86_64yum install libX11-1.6.0-2.2.el6.i686yum install libXext-1.3.2-2.1.el6.x86_64yum install libXext-1.3.2-2.1.el6.i686yum install libXp-1.0.2-2.1.el6.x86_64yum install libXp-1.0.2-2.1.el6.i686yum install libXi-1.7.2-2.2.el6.x86_64yum install libXi-1.7.2-2.2.el6.i686yum install libXtst-1.2.2-2.1.el6.x86_64yum install libXtst-1.2.2-2.1.el6.i686 命令行安装(使用appuser用户安装) 12cd /home/appuser/software./Oracle_Access_Manager10_1_4_3_0_linux64_APACHE22_WebGate 开始了漫长的选择题以及填空题 1234567891011121314151617181920212223[appuser@xxxx software]$ ./Oracle_Access_Manager10_1_4_3_0_linux64_APACHE22_WebGateInstallShield Wizard���ڳ�ʼ�� InstallShield Wizard...������ Java(tm) �����.............................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................��������[appuser@xxxx software] 莫名其妙,直接退出了,可能是如下问题,尝试解决。 运行到此直接退出,无日志输出,使用如下命令可以输入日志 1./Oracle_Access_Manager10_1_4_3_0_linux64_APACHE22_WebGate -is:log tmp.log 日志内容如下,最后报错信息: 1ERROR: Invalid bundled JVM. Missing 'jvm' file错误:无效的捆绑JVM。失踪的jvm文件 需要安装glibc.i686,glibc.x86_64已安装,但webgate依赖32位glibc 安装命令如下,使用root身份,两个包需要同时安装。 1rpm -ivh glibc-2.12-1.80.el6.i686.rpm nss-softokn-freebl-3.12.9-11.el6.i686.rpm 12345678910111213141516171819202122232425262728293031323334353637[appuser@xxxx software]$ ./Oracle_Access_Manager10_1_4_3_0_linux64_APACHE22_WebGateInstallShield Wizard���ڳ�ʼ�� InstallShield Wizard...������ Java(tm) �����.............................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................�������� InstallShield Wizard...-------------------------------------------------------------------------------欢迎使用 Oracle Access Manager 10.1.4.3.0 WebGate 的 InstallShield 向导InstallShield 向导将在计算机上安装 Oracle Access Manager 10.1.4.3.0 WebGate。要继续,选择“下一步”。 Oracle Access Manager 10.1.4.3.0 WebGateOracle按 1 到下一步, 3 取消 或者 4 重新显示 [1] 1 填1,下一步 12345678910您即将安装的产品的所有者必须是运行 Web 服务器时使用的同一个用户身份。大多数时候, 运行 Web 服务器所使用的身份是 `root' 或`nobody'。查找所有者身份的快捷方式是对服务器进程执行 `ps'操作。 输入运行 Web 服务器时所使用身份的用户名 [nobody] appuser 为上述用户名输入组 [nobody] appgrp按 1 到下一步, 2 到上一步, 3 取消 或者 4 重新显示 [1] 1 如下:用户名 appuser用户组 appgrp填1,下一步 1234567请指定 Oracle Access Manager 10.1.4.3.0 WebGate 的安装目录。 请指定目录名称或按 Enter 键 [/opt/netpoint/webgate] /app/soft/webgate按 1 到下一步, 2 到上一步, 3 取消 或者 4 重新显示 [1] 1 填写webgate安装目录,/app/soft/webgate填1,下一步 12345678910Oracle Access Manager 10.1.4.3.0 WebGate 将安装在以下位置:/app/soft/webgate4/access总大小: 133.4 MB请记下 Oracle Access Manager 10.1.4.3.0 WebGate 安装目录: /app/soft/webgate4/access,因为您将来需要引用它。按 1 到下一步, 2 到上一步, 3 取消 或者 4 重新显示 [1] 1 填1,下一步 1234567891011121314151617181920212223242526272829303132333435363738 GCC 运行时库的位置 []: /lib64按 1 到下一步, 2 到上一步, 3 取消 或者 4 重新显示 [1] 1正在安装 Oracle Access Manager 10.1.4.3.0 WebGate... 请等待。|-----------|-----------|-----------|------------|0% 25% 50% 75% 100%||||||||||||||||||||||||||||||||||||||||||||||||||正在创建卸载程序...正在解压缩语言包。请稍候...WebGate 配置WebGate 配置正在更改文件所有权和权限...-------------------------------------------------------------------------------指定传输安全模式 [X] 1 - 公开模式: 不加密 [ ] 2 - 简单模式: 通过 SSL 和 Oracle 提供的公共密钥证书进行加密 [ ] 3 - 证书模式: 通过 SSL 和外部 CA 提供的公钥证书进行加密 要选择一个选项,请输入它的编号或在完成时输入 0: [0]按 1 到下一步, 3 取消 或者 4 重新显示 [1] 1 填1,下一步 1234567891011121314请提供 WebGate ID, 主机名和 WebGate 连接的端口号。 必须对安装的每个 WebGate 使用唯一的 ID。 WebGate ID [XXX_WebGate]WebGate 的口令6 [*******] Access Server ID [acc_id_oam] 在其中安装 Access Server 的主机名 [node2] Access Server 监听的端口号 [6000]按 1 到下一步, 2 到上一步, 3 取消 或者 4 重新显示 [1] 1 按照集团给的配置填写,例: Name Value WebGate ID XXX_WebGate Password for WebGate 123456 Access Server ID acc_id_oam Host name where an Access Server is installed node2 Port number the Access Server listens to 6000 填1,下一步 123456789101112131415161718正在配置 WebGate...-------------------------------------------------------------------------------Oracle Access Manager 10.1.4.3.0 WebGate 安装在 Oracle Access Manager 10.1.4.3.0WebGate 安装目录下。要使用 Oracle Access Manager 10.1.4.3.0 WebGate 模块, 可通过修改 Web服务器目录中的配置来配置 Web 服务器。Oracle 可以自动为您更新配置。您也可以手动更新。是否继续自动更新 "httpd.conf"? [X] 1 - 是 [ ] 2 - 否 要选择一个选项,请输入它的编号或在完成时输入 0: [0]按 1 到下一步, 3 取消 或者 4 重新显示 [1] 1 填1,下一步 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293------------------------------------------------------------------------------- 请输入 Web 服务器配置目录的 httpd.conf 的绝对路径。 (如 "/export/apache/conf/httpd.conf") [] /app/soft/apache/conf/httpd.conf按 1 到下一步, 2 到上一步, 3 取消 或者 4 重新显示 [1] 1正在更新 Web 服务器配置...出现错误。请重试。WebGate already exists in your httpd.conf.EditHttpConf Error. Unable to automatically update/app/soft/apache/conf/httpd.conf.No changes have been made to /app/soft/apache/conf/httpd.conf.-------------------------------------------------------------------------------Oracle Access Manager 10.1.4.3.0 WebGate 安装在 Oracle Access Manager 10.1.4.3.0WebGate 安装目录下。要使用 Oracle Access Manager 10.1.4.3.0 WebGate 模块, 可通过修改 Web服务器目录中的配置来配置 Web 服务器。Oracle 可以自动为您更新配置。您也可以手动更新。是否继续自动更新 "httpd.conf"? [X] 1 - 是 [ ] 2 - 否 要选择一个选项,请输入它的编号或在完成时输入 0: [0]按 1 到下一步, 3 取消 或者 4 重新显示 [1] 4-------------------------------------------------------------------------------Oracle Access Manager 10.1.4.3.0 WebGate 安装在 Oracle Access Manager 10.1.4.3.0WebGate 安装目录下。要使用 Oracle Access Manager 10.1.4.3.0 WebGate 模块, 可通过修改 Web服务器目录中的配置来配置 Web 服务器。Oracle 可以自动为您更新配置。您也可以手动更新。是否继续自动更新 "httpd.conf"? [X] 1 - 是 [ ] 2 - 否 要选择一个选项,请输入它的编号或在完成时输入 0: [0]按 1 到下一步, 3 取消 或者 4 重新显示 [1] 1------------------------------------------------------------------------------- 请输入 Web 服务器配置目录的 httpd.conf 的绝对路径。 (如 "/export/apache/conf/httpd.conf") [/app/soft/apache/conf/httpd.conf]按 1 到下一步, 2 到上一步, 3 取消 或者 4 重新显示 [1] 1正在更新 Web 服务器配置...出现错误。请重试。Error: You are not authorized to configure this web serverEditHttpConf Error. Unable to automatically update/app/soft/apache/conf/httpd.conf.No changes have been made to /app/soft/apache/conf/httpd.conf.-------------------------------------------------------------------------------Oracle Access Manager 10.1.4.3.0 WebGate 安装在 Oracle Access Manager 10.1.4.3.0WebGate 安装目录下。要使用 Oracle Access Manager 10.1.4.3.0 WebGate 模块, 可通过修改 Web服务器目录中的配置来配置 Web 服务器。Oracle 可以自动为您更新配置。您也可以手动更新。是否继续自动更新 "httpd.conf"? [X] 1 - 是 [ ] 2 - 否 要选择一个选项,请输入它的编号或在完成时输入 0: [0]按 1 到下一步, 3 取消 或者 4 重新显示 [1] 4-------------------------------------------------------------------------------Oracle Access Manager 10.1.4.3.0 WebGate 安装在 Oracle Access Manager 10.1.4.3.0WebGate 安装目录下。要使用 Oracle Access Manager 10.1.4.3.0 WebGate 模块, 可通过修改 Web服务器目录中的配置来配置 Web 服务器。Oracle 可以自动为您更新配置。您也可以手动更新。是否继续自动更新 "httpd.conf"? [X] 1 - 是 [ ] 2 - 否 要选择一个选项,请输入它的编号或在完成时输入 0: [0] 1是否继续自动更新 "httpd.conf"? [X] 1 - 是 [ ] 2 - 否 要选择一个选项,请输入它的编号或在完成时输入 0: [0]按 1 到下一步, 3 取消 或者 4 重新显示 [1] 1 输入Apache配置文件的绝对路径,配置失败查找原因:1.怀疑是第一次配置的webgate在捣鬼,第一次配置成功,但是WebGate 主机名和 WebGate 连接的端口号是失败的,直接跳过了。删掉Apache,重装一次;果然,奏效,接着往下走 123456789101112131415161718192021------------------------------------------------------------------------------- 请输入 Web 服务器配置目录的 httpd.conf 的绝对路径。 (如 "/export/apache/conf/httpd.conf") [/app/soft/apache/conf/httpd.conf]按 1 到下一步, 2 到上一步, 3 取消 或者 4 重新显示 [1] 1正在更新 Web 服务器配置...-------------------------------------------------------------------------------配置 Web 服务器已经为 WebGate 修改 Web 服务器配置 请重新启动 Web 服务器以完成 WebGate 的安装。按 1 到下一步, 3 取消 或者 4 重新显示 [1] 填1,下一步 1234567891011121314151617181920212223242526272829303132333435363738394041-------------------------------------------------------------------------------配置 Web 服务器如果 Web 服务器是在 SSL 模式下设置的, 则需要使用与 SSL 相关的参数配置 httpd.conf 文件。要手动调整 SSL 配置,请按显示的说明进行操作。按 1 到下一步, 2 到上一步, 3 取消 或者 4 重新显示 [1]-------------------------------------------------------------------------------有关其余产品设置和 Web 服务器配置的信息, 请参阅文档: /app/soft/webgate4/access/oblix/lang/en-us/docs/config.htm 是否希望安装程序启动浏览器来查看此文档?也可以记下文档路径, 以后自己启动浏览器。 [X] 1 - 是 [ ] 2 - 否 要选择一个选项,请输入它的编号或在完成时输入 0: [0] 1有关其余产品设置和 Web 服务器配置的信息, 请参阅文档: /app/soft/webgate4/access/oblix/lang/en-us/docs/config.htm 是否希望安装程序启动浏览器来查看此文档?也可以记下文档路径, 以后自己启动浏览器。 [X] 1 - 是 [ ] 2 - 否 要选择一个选项,请输入它的编号或在完成时输入 0: [0]按 1 到下一步, 3 取消 或者 4 重新显示 [1] 1错误: 无法在当前路径中找到 Netscape 浏览器。-------------------------------------------------------------------------------配置 Web 服务器请启动浏览器并打开 /app/soft/webgate4/access /oblix/lang/en-us/docs/config.htm 文档,以获得有关配置 Web 服务器的详细信息。按 1 到下一步, 2 到上一步, 3 取消 或者 4 重新显示 [1] 1 启动浏览器失败,无所谓,往下走,填1,下一步 1234567891011121314151617181920212223242526272829303132333435正在更改文件所有权和权限...-------------------------------------------------------------------------------▽请阅读以下信息。Oracle COREid 10.1.4.3.0 自述文件▽<VirtualHost *:9008>--------------------------------------- 目录 ------------------------------------------------------------------------------ 与 Oracle 联系 ---------------------------------------如果您在安装或配置 NetPoint 方面有疑问,请与 Oracle 客户关怀联系,电话为 (800) 833-3536,或者可以访问链接http://www.oracle.com/corporate/contact/index.html。Oracle http://www.oracle.com 公司总部 500 Oracle Parkway Redwood Shores,CA 94065按 Enter 键 阅读文本(Y)(Q) [输入 q 以退出(N)] q 完成安装填q,退出 1234567891011121314按 1 到下一步, 3 取消 或者 4 重新显示 [1] 1-------------------------------------------------------------------------------Oracle Access Manager 10.1.4.3.0 WebGate 已成功安装。Oracle Access Manager 10.1.4.3.0 WebGate 设置信息 传输安全: open WebGate ID: CRLD_ERPZT_WebGate Access Server 主机名: drjtldapvdra05 Access Server 端口号: 5575按 3 完成 或者 4 重新显示 [3] 3您在 /var/spool/mail/appuser 中有邮件 webgate安装结束, Apache重启 Apache配置1.1 修改/apps/apache/conf/httpd.conf监听的端口: 1Listen 9008 修改: 1Include conf/httpd-vhosts.conf 在 123<Location "/oberr.cgi">SetHandler obwebgateerr</Location> 下面增加: 1234<Location "/obrar.cgi">AuthType Oblixrequire valid-user</Location> 修改: 1234<LocationMatch "*">AuthType Oblixrequire valid-user</LocationMatch> 为 1234<LocationMatch "^/((?!(api|logfiles|osp)).)*$">AuthType Oblixrequire valid-user</LocationMatch> 1.2. 修改/app/soft/apache/conf/httpd-vhost.conf初始该配置文件的内容为空,参考以下配置: 123456789101112131415<VirtualHost *:9008>ServerAdmin jitian-web-ospDocumentRoot "/www/jitian-web-osp"ServerName jitian-web-ospServerAlias jitian-web-osp# ErrorLog "/www/jitian-web/logs/error_log"# CustomLog "/www/jitian-web/logs/dummy-host.8888.com-access_log" common# 开启反向代理# Allow Override All# Order allow,deny# Allow from all# PHPProxyPass / http://10.0.XX.X:9009/ProxyPassReverse / http://10.0.XX.X:9009/</VirtualHost> ProxyPass 与 ProxyPassReverse 后面的『http://10.0.XX.X:9009/』是需要我们的请求的真正的目的服务器,其前边的 空格+/+空格 也是属于我们配置的一部分 检查Apache是否安装成功重启: 12/app/soft/apache/bin/httpd -k startlsof -i :9008","categories":[{"name":"Linux","slug":"Linux","permalink":"https://blog.magicyou.cn/categories/Linux/"}],"tags":[{"name":"Linux","slug":"Linux","permalink":"https://blog.magicyou.cn/tags/Linux/"},{"name":"Centos","slug":"Centos","permalink":"https://blog.magicyou.cn/tags/Centos/"}]},{"title":"一次lnmp环境升级记录","slug":"Linux-一次环境升级记录","date":"2019-02-23T09:23:00.000Z","updated":"2019-02-23T09:23:00.000Z","comments":true,"path":"2019/02/23/Linux-一次环境升级记录/","link":"","permalink":"https://blog.magicyou.cn/2019/02/23/Linux-%E4%B8%80%E6%AC%A1%E7%8E%AF%E5%A2%83%E5%8D%87%E7%BA%A7%E8%AE%B0%E5%BD%95/","excerpt":"集团内部要对所有服务器进行版本升级,因此代码要迁移,环境要搭建","text":"集团内部要对所有服务器进行版本升级,因此代码要迁移,环境要搭建 环境变化现役环境: 服务器版本 PHP Nginx Apache centos 6.8 php5.6 nginx/1.12.0 Apache/2.2.15 (Unix) 升级后环境: 服务器版本 PHP Nginx Apache centos 7.4 php5.6 nginx/1.15.10 Apache/2.4.6 (CentOS) 相关路径记录123456789101112nginx路径/usr/local/nginxphp路径/usr/local/php56/php.ini和php-fpm.conf路径/usr/local/php56/etcPhp扩展路径/usr/local/php56/include/php/ext/usr/local/php56/lib/php/extensions 安装记录1. 安装nginx1.检查并安装Nginx基础依赖包pcre-devel openssl-devel名称中带有”devel”字符串的软件包是必须要安装的 1234[appuser@xxxxx ~]# rpm -qa openssl-devel opensslopenssl-1.0.2k-8.el7.x86_64[appuser@xxxxx ~]# rpm -qa pcre-devel pcrepcre-8.32-17.el7.x86_64 2.安装pcre-devel以及openssl-devel 1# yum install -y openssl-devel pcre-devel 安装后查看下 123456[appuser@xxxxx~]# rpm -qa openssl-devel opensslopenssl-1.0.2k-8.el7.x86_64openssl-devel-1.0.2k-8.el7.x86_64[appuser@xxxxx~]# rpm -qa pcre-devel pcrepcre-devel-8.32-17.el7.x86_64pcre-8.32-17.el7.x86_64 3.开始安装Nginx: 1# mkdir /usr/local/nginx 进入目录 1# cd /usr/local/nginx 下载软件包,进入http://nginx.org/download/复制对应版本的下载链接地址。 1# wget http://nginx.org/download/nginx-1.15.10.tar.gz 解压安装 12345[appuser@xxxxx nginx]# ls -l nginx-1.15.10.tar.gz-rw-r--r-- 1 root root 981687 Oct 17 21:20 nginx-1.15.10.tar.gz[appuser@xxxxx nginx]# useradd -M -s /sbin/nologin nginx [appuser@xxxxx nginx]# tar zxf nginx-1.15.10.tar.gz [appuser@xxxxx nginx]# cd nginx-1.15.10/ 安装 12# ./configure --prefix=/usr/local/nginx --with-http_dav_module --with-http_stub_status_module --with-http_addition_module --with-http_sub_module --with-http_flv_module --with-http_mp4_module --with-pcre --with-http_ssl_module --with-http_gzip_static_module --user=nginx --group=nginx# make && make install 安装完成后的优化: 1# ln -s /usr/local/nginx/sbin/nginx /usr/local/sbin/ 启动Nginx 1# nginx 查看 12# netstat -anput | grep nginx# ps -ef | grep nginx 停止 12345nginx -s stop # 关闭nginx -s quit # 退出(也是关闭)nginx -s reload # 重新加载配置文件nginx -s reopen # 重新打开日志文件nginx -t # 测试配置文件 2. 安装php,php-fpmyum安装php5.61# yum install --enablerepo=remi --enablerepo=remi-php56 php php-opcache php-devel php-mbstring php-mcrypt php-mysqlnd php-phpunit-PHPUnit php-pecl-xdebug php-pecl-xhprof -y 查看php版本 123456# php --version[appuser@xxxx /]$ php -vPHP 5.6.0 (cli) (built: Apr 9 2019 16:00:50)Copyright (c) 1997-2014 The PHP GroupZend Engine v2.6.0, Copyright (c) 1998-2014 Zend Technologies 测试php-fpm 1# netstat -lnt | grep 9000 安装php扩展pgsql 扩展 找到相应的版本 php 5.6.0 https://www.php.net/releases/ 1、从php官网下载一个合适版本的php;2、从PHP解压包里ext目录找到pdo_pgsql 和 pgsql目录,并且复制放到liunx的 /root 目录(随便一个)3、分别进入pdo_pgsql 和 pgsql目录 :phpize./configure (./configure –with-php-config=/usr/local/php56/bin/php-config)make make install Ps:这里不要直接make && make install,可能会报错,很抓狂 4、修改php配置文件php.ini,添加pgsql.so、pdo_pgsql.so模块。extension=pgsql.so 5、重启php-fpm pdo_pgsql 扩展按照相同的办法,安装 pdo_pgsql还有php配置文件php.ini添加 1extension=pdo_pgsql.so 安装pgsql扩展时候报错如下: (不知道啥情况,先放着) 123Installing shared extensions: /usr/local/php56/lib/php/extensions/no-debug-non-zts-20131226/cp: cannot create regular file '/usr/local/php56/lib/php/extensions/no-debug-non-zts-20131226/#INST@558502#': Permission deniedmake: *** [install-modules] 错误 1 ![image-20190410131947892](/Users/magicyou/Library/Application Support/typora-user-images/image-20190410131947892.png) 基本完成,配置nginx并上传代码1.创建项目文件夹 12345cd /mkdir wwwcd wwwmkdir web-ospchmod -R 777 web-osp 代码用命令行或者工具同步进服务器对应文件夹 2.修改配置文件nginx.conf 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657#user nobody;worker_processes 4;#error_log logs/error.log;#error_log logs/error.log notice;#error_log logs/error.log info;#pid logs/nginx.pid;events { worker_connections 1024;}http { include mime.types; default_type application/octet-stream; underscores_in_headers on; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; log_format addHeaderlog '$remote_addr - $remote_user [$time_local] ' '"$request" $status $body_bytes_sent ' '"$http_referer" "$http_user_agent" "$http_x_forwarded_for" ["$http_oam_uid"]'; access_log logs/access.log addHeaderlog; sendfile on; #tcp_nopush on; #keepalive_timeout 0; keepalive_timeout 65; fastcgi_connect_timeout 300s; fastcgi_send_timeout 300s; fastcgi_read_timeout 300s; fastcgi_buffer_size 128k; fastcgi_buffers 8 128k;#8 128 fastcgi_busy_buffers_size 256k; fastcgi_temp_file_write_size 256k; fastcgi_intercept_errors on; #gzip on; server { listen 80; server_name test.data.com.cn; location / { proxy_pass http://10.0.xx.x:9010/; } } server { include conf.d/*.conf;} 编辑conf.d文件夹下的配置文件 1234567891011121314151617181920212223242526272829303132333435363738394041server { listen 9010; server_name 127.0.0.1; root "/www/web-ceshi"; index index.html index.htm index.php info.php; location / { try_files $uri @rewrite; proxy_set_header OAM_UID $http_OAM_UID; proxy_pass_request_headers on; } location @rewrite { set $static 0; if ($uri ~ \\.(css|js|jpg|jpeg|png|gif|ico|woff|eot|svg|css\\.map|min\\.map)$) { set $static 1; } if ($static = 0) { rewrite ^/(.*)$ /index.php?s=/$1; } } location ~ /Uploads/.*\\.php$ { deny all; } location ~ \\.php/ { if ($request_uri ~ ^(.+\\.php)(/.+?)($|\\?)) { } fastcgi_pass 127.0.0.1:9000; include fastcgi_params; fastcgi_param SCRIPT_NAME $1; fastcgi_param PATH_INFO $2; fastcgi_param SCRIPT_FILENAME $document_root$1; } location ~ \\.php$ { fastcgi_pass 127.0.0.1:9000; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } location ~ /\\.ht { deny all; }} 重启php-fpm、nginx 部署基本完毕,尝试进入项目,出现两次502,记录如下第一次 502 Bad Gatewaynginx配置和php-fpm端口不一致,改好,能登录 1fastcgi_pass 127.0.0.1:9000; 第二次 502 Bad Gateway登录后进入主页 502 查找问题 https://host.zzidc.com/wljc/928.html 查看是否是php-fpm的问题,找到正在使用的配置文件 123456[appuser@XXXXX lib]$ ps -ef | grep php-fpmroot 13824 1 0 2018 ? 00:17:16 php-fpm: master process (/usr/local/etc/php-fpm.conf)www 18922 13824 0 12:48 ? 00:00:01 php-fpm: pool wwwwww 24891 13824 0 14:08 ? 00:00:01 php-fpm: pool wwwwww 25449 13824 0 14:15 ? 00:00:00 php-fpm: pool wwwappuser 28587 25750 0 14:46 pts/1 00:00:00 grep php-fpm 编辑php-fpm.conf 1# /usr/local/php/etc/php-fpm.conf 没有 php-fpm.conf 的话 1# cp php-fpm.conf.default php-fpm.conf 设置 123request_terminate_timeoutrequest_terminate_timeout = 600 重启php-fpm,还是不行 去看php-fpm日志记录,发现日志没开,先打开日志记录 1.修改php-fpm.conf中的配置**,如果没有请增加: 复制代码 代码如下: 1234[global]error_log = log/php_error_log[www]catch_workers_output = yes 2.修改php.ini中配置 ,没有则增加: 复制代码 代码如下: 123log_errors = Onerror_log = "/usr/local/php/var/log/error_log"error_reporting=E_ALL&~E_NOTICE 3.重启php-fpm 当PHP执行错误时就能看到错误日志在”/usr/local/php56/var/log/php_error_log”中了 4.浏览器进项目,然后查看看日志 12# /usr/local/php56/var/log/php_error_log[10-Apr-2019 15:30:58] WARNING: [pool www] child 272566 said into stderr: "php-fpm: pool www: symbol lookup error: /usr/local/php56/lib/php/extensions/no-debug-non-zts-20131226/pgsql.so: undefined symbol: _safe_emalloc_string" 重新装pgsql扩展安装的时候不要make && make install先 make, 然后make install 刷新网页Call to undefined function bcdiv()继续填坑 查找问题,没有安装扩展bcmath 同安装pgsql一样,安装后基本没发现其他问题 后续配置Apache,ldap集团单点登录 Apache的单点登录配置简单预热,详细看下一篇cd /etc/httpd/conf/ sudo vim httpd-vhosts.conf 123456<VirtualHost *:9008>ServerAdmin jituan-web-ospDocumentRoot "/www/jituan-web"ServerName jituan-web-ospServerAlias jituan-web-osp</VirtualHost> 查看httpd进程 ps -ef | grep httpd 1234567第一、启动、终止、重启systemctl start httpd.service #启动systemctl stop httpd.service #停止systemctl restart httpd.service #重启 启动报错 1234567891011121314151617181920appuser@xxxx conf]$ sudo systemctl status httpd.service● httpd.service - The Apache HTTP Server Loaded: loaded (/usr/lib/systemd/system/httpd.service; disabled; vendor preset: disabled) Active: failed (Result: exit-code) since 三 2019-04-10 17:18:23 CST; 10min ago Docs: man:httpd(8) man:apachectl(8) Process: 604898 ExecStop=/bin/kill -WINCH ${MAINPID} (code=exited, status=1/FAILURE) Process: 604895 ExecStart=/usr/sbin/httpd $OPTIONS -DFOREGROUND (code=exited, status=1/FAILURE) Main PID: 604895 (code=exited, status=1/FAILURE)4月 10 17:18:23 xxxx systemd[1]: Starting The Apache HTTP Server...4月 10 17:18:23 xxxx httpd[604895]: (98)Address already in use: AH00072: make_sock: could not bind to address 0.0.0.0:804月 10 17:18:23 xxxx httpd[604895]: no listening sockets available, shutting down4月 10 17:18:23 xxxx httpd[604895]: AH00015: Unable to open logs4月 10 17:18:23 xxxx systemd[1]: httpd.service: main process exited, code=exited, status=1/FAILURE4月 10 17:18:23 xxxx kill[604898]: kill: cannot find process ""4月 10 17:18:23 xxxx systemd[1]: httpd.service: control process exited, code=exited status=14月 10 17:18:23 xxxx systemd[1]: Failed to start The Apache HTTP Server.4月 10 17:18:23 xxxx systemd[1]: Unit httpd.service entered failed state.4月 10 17:18:23 xxxx systemd[1]: httpd.service failed. 更根据上面的信息 修改端口80为9008 123456789101112131415161718192021[appuser@xxxx /]$ sudo systemctl restart httpd.service您在 /var/spool/mail/appuser 中有新邮件[appuser@xxxx /]$ sudo systemctl status httpd.service● httpd.service - The Apache HTTP Server Loaded: loaded (/usr/lib/systemd/system/httpd.service; disabled; vendor preset: disabled) Active: active (running) since 三 2019-04-10 17:35:06 CST; 8s ago Docs: man:httpd(8) man:apachectl(8) Process: 604898 ExecStop=/bin/kill -WINCH ${MAINPID} (code=exited, status=1/FAILURE) Main PID: 654829 (httpd) Status: "Processing requests..." CGroup: /system.slice/httpd.service ├─654829 /usr/sbin/httpd -DFOREGROUND ├─654834 /usr/sbin/httpd -DFOREGROUND ├─654835 /usr/sbin/httpd -DFOREGROUND ├─654836 /usr/sbin/httpd -DFOREGROUND ├─654837 /usr/sbin/httpd -DFOREGROUND └─654838 /usr/sbin/httpd -DFOREGROUND4月 10 17:35:06 xxxx systemd[1]: Starting The Apache HTTP Server...4月 10 17:35:06 xxxx systemd[1]: Started The Apache HTTP Server.","categories":[{"name":"Linux","slug":"Linux","permalink":"https://blog.magicyou.cn/categories/Linux/"}],"tags":[{"name":"Linux","slug":"Linux","permalink":"https://blog.magicyou.cn/tags/Linux/"},{"name":"Centos","slug":"Centos","permalink":"https://blog.magicyou.cn/tags/Centos/"},{"name":"PHP","slug":"PHP","permalink":"https://blog.magicyou.cn/tags/PHP/"},{"name":"Nginx","slug":"Nginx","permalink":"https://blog.magicyou.cn/tags/Nginx/"},{"name":"Apache","slug":"Apache","permalink":"https://blog.magicyou.cn/tags/Apache/"}]},{"title":"yum源的代理设置及修改","slug":"Linux-yum源的代理设置及修改","date":"2019-02-03T10:23:00.000Z","updated":"2019-02-03T10:23:00.000Z","comments":true,"path":"2019/02/03/Linux-yum源的代理设置及修改/","link":"","permalink":"https://blog.magicyou.cn/2019/02/03/Linux-yum%E6%BA%90%E7%9A%84%E4%BB%A3%E7%90%86%E8%AE%BE%E7%BD%AE%E5%8F%8A%E4%BF%AE%E6%94%B9/","excerpt":"最近公司服务器要进行升级,所以要在升级之前系统整体迁移到新的服务器。","text":"最近公司服务器要进行升级,所以要在升级之前系统整体迁移到新的服务器。 前言系统都是内网使用,所以服务器也是内网,所幸是运维给了一个代理ip,能访问几个特定的网站,比如qq.com(本来开的腾讯云短信,后来发现qq.com域名下二级域名都能访问)。 升级的道路很坎坷,各种包要重装,依赖很多,但是没有外网,虽然原本带的yum包足够多,但还是不够。 后来后来,作死的换了yum源,阿里的,无奈不能使用。我找到所有的yum源地址,一个一个试,惊奇的发现中科大yum源居然能用代理访问。 1curl -m 30 --retry 3 -x 10.0.248.64:3128 http://centos.ustc.edu.cn 1. Centos系统使用代理上网时 yum的代理设置1.1 编辑yum配置文件:1vim /etc/yum.conf 1.2 打开yum的配置文件之后,在文件最后加上代理服务器的协议、地址、端口,如果代理服务器需要用户认证话,同时加上认证用户的用户名和密码。代理服务器不需要认证:加上 proxy=协议://代理服务器地址:端口 ,例: 1proxy=http://192.168.1.1:80 代理服务器需要认证用户:加上 proxy=协议://代理服务器地址:端口 ,例: 123proxy=http://192.168.1.1:80proxy_username=代理服务器用户名proxy_password=代理服务器密码 2. yum源的更换2.1 首先备份CentOS-Base.repo1mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup 2.2 下载对应版本的CentOS-Base.repo, 放入/etc/yum.repos.d/12345678Centos5https://lug.ustc.edu.cn/wiki/_export/code/mirrors/help/centos?codeblock=1Centos6https://lug.ustc.edu.cn/wiki/_export/code/mirrors/help/centos?codeblock=2Centos7https://lug.ustc.edu.cn/wiki/_export/code/mirrors/help/centos?codeblock=3 放进 /etc/yum.repos.d/ 2.3 运行以下命令生成缓存12yum clean allyum makecache 3. yum简单使用12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667# 安装rpm包:yum install RPM包# 删除rpm包,包括与该包有依赖性的包:yum remove 包名# 检查可更新的rpm包:yum check-update# 更新所有的rpm包:yum update# 更新指定的rpm包:yum update 包名# 大规模的升级版本:yum upgrade# 列出资源库中所有可以安装或更新的rpm包的信息:yum info# 列出资源库中特定的可以安装或更新以及已经安装的rpm包的信息:yum info 包名# 列出资源库中所有可以更新的rpm包的信息:yum info updates# 列出已经安装的所有的rpm包的信息:yum info installed# 列出已经安装的但是不包含在资源库中的rpm包信息:yum info 包名# 列出资源库中所有可以更新的rpm包:yum list updates# 列出已经安装的所有rpm包:yum list installed# 列出已经安装的但不包含在资源库中的rpm包:yum list extras# 列出资源库中所有可以安装或更新的rpm包:yum list# 列出资源库中特定的可以安装或更新以及已经安装的rpm包:yum list 包名# 搜索匹配特定字符的rpm包的详细信息:yum search 包名# 搜索包含特定文件名的rpm包:yum provides 包名# 清除暂存的rpm包文件:yum clean packages# 清除暂存的rpm头文件:yum clean headers# 清除暂存中旧的rpm旧文件:yum clean oldheaders# 清除暂存中旧的rpm头文件和包文件:yum clean或yum clean all","categories":[{"name":"Linux","slug":"Linux","permalink":"https://blog.magicyou.cn/categories/Linux/"}],"tags":[{"name":"Linux","slug":"Linux","permalink":"https://blog.magicyou.cn/tags/Linux/"},{"name":"Centos","slug":"Centos","permalink":"https://blog.magicyou.cn/tags/Centos/"}]},{"title":"脚本微博登录自动发微博并返回cookie","slug":"Python-脚本微博登录自动发微博并返回cookie","date":"2018-10-23T12:12:23.000Z","updated":"2018-10-23T12:12:23.000Z","comments":true,"path":"2018/10/23/Python-脚本微博登录自动发微博并返回cookie/","link":"","permalink":"https://blog.magicyou.cn/2018/10/23/Python-%E8%84%9A%E6%9C%AC%E5%BE%AE%E5%8D%9A%E7%99%BB%E5%BD%95%E8%87%AA%E5%8A%A8%E5%8F%91%E5%BE%AE%E5%8D%9A%E5%B9%B6%E8%BF%94%E5%9B%9Ecookie/","excerpt":"研究新浪微博前端的层层加密,脚本登录,发送微博,返回cookie","text":"研究新浪微博前端的层层加密,脚本登录,发送微博,返回cookie 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151#!/usr/bin/python#coding=utf-8import rsaimport jsonimport requestsimport refrom urllib import parseimport base64import timefrom binascii import b2a_heximport urllib3urllib3.disable_warnings() # 取消警告def get_timestamp(): ''' 获取13位时间戳 :return: ''' return int(time.time()*1000)def get_su(username): ''' 加密用户名 :param username: 用户名 :return: 加密后的用户名 ''' su = base64.b64encode(parse.quote(username).encode('utf-8')) return suclass WeiboLogin(object): # 请求头 header = { 'Content-Type': 'application/x-www-form-urlencoded', 'Origin': 'http://my.sina.com.cn', 'Referer': 'http://my.sina.com.cn/profile/unlogin', 'User-Agent ': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36' } def __init__(self, username, password): self.username = username self.password = password self.session = requests.session() self.session.verify = False # 取消证书验证 # 第一次请求获取参数,注备登录 self.get_origin_param() def get_origin_param(self): ''' 第一次请求获取参数,注备登录 ''' url = "https://login.sina.com.cn/sso/prelogin.php?entry=weibo&callback=sinaSSOController.preloginCallBack&su=%s&rsakt=mod&checkpin=1&client=ssologin.js(v1.4.19)&_=%s" % ( self.get_su(), get_timestamp()) data = None response = self.session.post(url, data=data, headers=self.header).text val_keys = re.findall(r"[(](.*?)[)]", response) params = json.loads(val_keys[0]) self.pubkey = params['pubkey'] self.retcode = params['retcode'] self.servertime = params['servertime'] self.pcid = params['pcid'] self.nonce = params['nonce'] self.rsakv = params['rsakv'] self.is_openlock = params['is_openlock'] self.showpin = params['showpin'] self.exectime = params['exectime'] def get_su(self): ''' 获取加密用户名 ''' su = base64.b64encode(parse.quote(self.username).encode('utf-8')) return su.decode('utf-8') def get_sp(self): ''' 加密密码 ''' publickey = rsa.PublicKey(int(self.pubkey, 16), int('10001', 16)) message = str(self.servertime) + '\\t' + str(self.nonce) + '\\n' + str(self.password) sp = rsa.encrypt(message.encode(), publickey) return b2a_hex(sp).decode('utf-8') def login(self): ''' 微博登录 ''' url = "https://login.sina.com.cn/sso/login.php?client=ssologin.js(v1.4.15)&_=%s" % (get_timestamp()) data = { "entry": "weibo", "gateway": 1, "from": None, "savestate": 30, "qrcode_flag": 'false', "useticket": 1, "pagerefer": "http://my.sina.com.cn/", "vsnf": 1, "su": self.get_su(), "service": "miniblog", "servertime": self.servertime, "nonce": self.nonce, "pwencode": "rsa2", "rsakv": self.rsakv, "sp": self.get_sp(), "sr": "1600 * 900", "encoding": "UTF-8", "cdult": 3, "domain": "sina.com.cn", "prelt": 35, "returntype": "META", "url" : "https://weibo.com/ajaxlogin.php?framelogin=1&callback=parent.sinaSSOController.feedBackUrlCallBack", } response = self.session.post(url, data=data, allow_redirects=False, headers=self.header) # 在一些post请求中,还需要用到headers部分,此处未加,在下文中会说到 redirect_url = re.findall(r'location.replace\\("(.*?)"\\);', response.text)[0] result = self.session.get(redirect_url, allow_redirects=False, headers=self.header).text ticket,ssosavestate = re.findall(r'ticket=(.*?)&ssosavestate=(.*?)"',result)[0] #获取ticket和ssosavestate参数 uid_url = 'https://passport.weibo.com/wbsso/login?ticket={}&ssosavestate={}&callback=sinaSSOController.doCrossDomainCallBack&scriptId=ssoscript0&client=ssologin.js(v1.4.19)&_={}'.format( ticket, ssosavestate, get_timestamp()) data = self.session.get(uid_url, headers=self.header).text # 请求获取uid uid = re.findall(r'"uniqueid":"(.*?)"', data)[0] print('uid:',uid) self.uid = uid def main(self): cookies = [] self.login() cookie = self.session.cookies.get_dict() print('cookie:',cookie) cookies.append(cookie) return cookiesif __name__ == "__main__": username = "账号XXX" password = '密码XXX' wb_hander = WeiboLogin(username, password) wb_hander.main()","categories":[{"name":"Python","slug":"Python","permalink":"https://blog.magicyou.cn/categories/Python/"}],"tags":[{"name":"Python","slug":"Python","permalink":"https://blog.magicyou.cn/tags/Python/"}]},{"title":"scrapy爬虫之初次尝试","slug":"Python-scrapy爬虫之初次尝试","date":"2018-10-10T13:34:23.000Z","updated":"2018-10-10T13:34:23.000Z","comments":true,"path":"2018/10/10/Python-scrapy爬虫之初次尝试/","link":"","permalink":"https://blog.magicyou.cn/2018/10/10/Python-scrapy%E7%88%AC%E8%99%AB%E4%B9%8B%E5%88%9D%E6%AC%A1%E5%B0%9D%E8%AF%95/","excerpt":"本文主要记录scrapy安装、配置,到第一个爬虫实例实现的过程。","text":"本文主要记录scrapy安装、配置,到第一个爬虫实例实现的过程。 准备工作环境 环境:macOS python版本:python 3.7 Scrapy版本:1.5.1 安装scrapy1pip3 install scrapy 创建Scrapy项目创建python目录,切换到python目录,创建爬虫项目 12345678910magicyou@magicYoudeMacBook-Pro ~$ mkdir ~/pythonmagicyou@magicYoudeMacBook-Pro ~$ cd ~/pythonmagicyou@magicYoudeMacBook-Pro python$ scrapy startproject articleSpiderNew Scrapy project 'articleSpider', using template directory '/usr/local/lib/python3.7/site-packages/scrapy/templates/project', created in: /Users/magicyou/Desktop/python/articleSpiderYou can start your first spider with: cd articleSpider scrapy genspider example example.com 创建一个Spider爬虫程序。本文进行抓取的模板网站 ‘blog.jobbole.com’, 1234magicyou@magicYoudeMacBook-Pro python$ cd articleSpidermagicyou@magicYoudeMacBook-Pro articleSpider$ scrapy genspider jobbole blog.jobbole.comCreated spider 'jobbole' using template 'basic' in module: articleSpider.spiders.jobbole 创建一个mysql表,用来存储文章信息直接上sql 12345678910111213141516DROP TABLE IF EXISTS `article`;CREATE TABLE `article` ( `url_object_id` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT 'url的md5', `url` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT 'url', `title` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '文章名字', `create_date` date DEFAULT NULL COMMENT '创建日期', `front_image_url` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '封面路原始url', `front_image_path` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '封面图本地路径', `comment_nums` int(11) DEFAULT NULL COMMENT '评论数', `fav_nums` int(11) DEFAULT NULL COMMENT '点赞数', `praise_nums` int(11) DEFAULT NULL, `content` longtext COLLATE utf8_unicode_ci COMMENT '文章内容', PRIMARY KEY (`url_object_id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;SET FOREIGN_KEY_CHECKS = 1; 至此准备工作基本完成此时的目录结构应该是如下展示: 1234567891011121314151617|-- articleSpider |--- articleSpider |-- spiders # Spiders类 |-- jobbole.py |-- __init__.py |-- __pycache__ |-- __init__.cpython-37.pyc |-- __pycache__ |-- settings.cpython-37.pyc |-- __init__.cpython-37.pyc |-- middlewares. |-- pipelines.py |-- items.py |-- settings.py |-- __init__.py |-- scrapy.cfg 简单的了解SpidersSpider类定义了如何爬取某个(或某些)网站。包括了爬取的动作(例如:是否跟进链接)以及如何从网页的内容中提取结构化数据(爬取item)。 换句话说,Spider就是您定义爬取的动作及分析某个网页(或者是有些网页)的地方。 对spider来说,爬取的循环类似下文: 以初始的URL初始化Request,并设置回调函数。 当该request下载完毕并返回时,将生成response,并作为参数传给该回调函数。 spider中初始的request是通过调用 start_requests() 来获取的。 start_requests() 读取 start_urls 中的URL, 并以 parse 为回调函数生成 Request 。 在回调函数内分析返回的(网页)内容,返回 Item 对象或者 Request 或者一个包括二者的可迭代容器。 返回的Request对象之后会经过Scrapy处理,下载相应的内容,并调用设置的callback函数(函数可相同)。 在回调函数内,您可以使用 选择器(Selectors) (您也可以使用BeautifulSoup, lxml 或者您想用的任何解析器) 来分析网页内容,并根据分析的数据生成item。 最后,由spider返回的item将被存到数据库(由某些 Item Pipeline 处理)或使用 Feed exports Items爬取的主要目标就是从非结构性的数据源提取结构性数据,例如网页。 Scrapy提供 Item 类来满足这样的需求。Item 对象是种简单的容器,保存了爬取到得数据。 其提供了 类似于词典(dictionary-like) 的API以及用于声明可用字段的简单语法。 Item Pipeline当Item在Spider中被收集之后,它将会被传递到Item Pipeline,一些组件会按照一定的顺序执行对Item的处理。 每个item pipeline组件(有时称之为“Item Pipeline”)是实现了简单方法的Python类。他们接收到Item并通过它执行一些行为,同时也决定此Item是否继续通过pipeline,或是被丢弃而不再进行处理。 以下是item pipeline的一些典型应用: 清理HTML数据 验证爬取的数据(检查item包含某些字段) 查重(并丢弃) 将爬取结果保存到数据库中 爬虫逻辑编写入口文件 main.py编辑器是PyCharm,为了调试方便,添加main.py作为程序的入口,切换在articleSpider操作(当前路径 ~/python/articleSpider/articleSpider) 1234567891011#!/usr/bin/python3# -*- coding: utf-8 -*-__author__ = 'magicYou'from scrapy.cmdline import executeimport sysimport osprint(os.path.dirname(os.path.abspath(__file__)))sys.path.append(os.path.dirname(os.path.abspath(__file__)))execute(["scrapy", "crawl", "jobbole"]) Spider爬虫使用parse(self,response)方法来解析所下载的页面。此方法返回一个包含新的URL资源网址的迭代对象,这些新的URL网址将被添加到下载队列中以供将来进行爬取数据和解析。spider主要代码(python/articleSpider/articleSpider/jobbole.py): 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758# -*- coding: utf-8 -*-from urllib import parsefrom datetime import datetimefrom scrapy.http import Requestfrom articleSpider.items import JobboleArticlespiderItemfrom .utils.common import get_md5class JobboleSpider(scrapy.Spider): name = 'jobbole' allowed_domains = ['blog.jobbole.com'] start_urls = ['http://blog.jobbole.com/all-posts/'] def parse(self, response): ''' 1.获取文章列表中的文章url并交给scrapy下载并进行解析 2.获取下一页的url并交给scary进行下载,下载完成后交给parse ''' post_nodes = response.css("#archive .floated-thumb .post-thumb a") for post_node in post_nodes: image_url = post_node.css("img::attr(src)").extract_first("") post_url = post_node.css("::attr(href)").extract_first() yield Request(url=parse.urljoin(response.url, post_url), meta={"front_image_url":image_url}, callback=self.parse_detail) # 提取下一页并交给scrapy进行下载 next_urls = response.css(".next.page-numbers::attr(href)").extract_first("") if next_urls: yield Request(url=parse.urljoin(response.url, next_urls), callback=self.parse) def parse_detail(self, response): ''' 详情页信息提取 ''' article = JobboleArticlespiderItem() url = response.url title = response.xpath("//div[@class='entry-header']/h1/text()").extract()[0] create_date = response.xpath("//div[@class='entry-meta']/p[@class='entry-meta-hide-on-mobile']/text()").extract()[0].strip() create_date = datetime.strptime(create_date.split()[0], "%Y/%m/%d") praise_nums = response.xpath("//div[@class='post-adds']/span[contains(@class, 'vote-post-up')]/h10/text()").extract()[0] fav_nums = response.xpath("//div[@class='post-adds']/span[contains(@class, 'bookmark-btn')]/text()").extract()[0] fav_nums = fav_nums.replace('收藏', '') if fav_nums.replace('收藏', '').strip() else 0 comment_nums = response.xpath("//div[@class='post-adds']/a[@href='#article-comment']/span/text()").extract()[0] comment_nums = comment_nums.replace('评论', '') if comment_nums.replace('评论', '').strip() else 0 content = response.xpath("//div[@class='entry']").extract()[0] # 获取图片 front_image_url = response.meta.get("front_image_url", "") article_item = {} article_item["url"] = url article_item["url_object_id"] = get_md5(url) article_item["title"] = title article_item["create_date"] = create_date article_item["praise_nums"] = int(praise_nums) article_item["fav_nums"] = int(fav_nums) article_item["comment_nums"] = int(comment_nums) article_item["content"] = content article_item["front_image_url"] = [front_image_url] yield article_item 过程简单描述 parse函数从start_urls为起始页,获取列表里的文章url,和文章的封面图url; 讲步骤1中的获取的url交给parse_detail,进行详情处理,处理结果交付给item; 获取下一页的url,作为参数再次交给parse函数,直到没有下一页为止。 Item使用简单的class定义语法以及 Field 对象来声明items主要代码(python/articleSpider/items.py): 123456789101112import scrapyclass ArticleItem(scrapy.Item): title = scrapy.Field() url = scrapy.Field() url_object_id = scrapy.Field() crate_date = scrapy.Field() praise_nums = scrapy.Field() fav_nums = scrapy.Field() comment_nums = scrapy.Field() content = scrapy.Field() front_image_url = scrapy.Field() pipeline中接收item传过来的数据,并且存入数据库 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889from scrapy.pipelines.images import ImagesPipelineimport pymysqlfrom twisted.enterprise import adbapiclass ArticleImagePipeline(ImagesPipeline): ''' 存储图片,并返回存储的路径 ''' def item_completed(self, results, item, info): for ok, value in results: image_file_path = value['path'] # 给item添加图片本地存储路径 item["front_image_path"] = image_file_path return itemclass MysqlPipeline(): ''' 同步存储mysql,比较慢 ''' def __init__(self, params): self.conn = pymysql.connect( host = params['host'], user = params['user'], password = params['password'], db = params['db'], charset = params['charset'], cursorclass = pymysql.cursors.DictCursor) self.cursor = self.conn.cursor() @classmethod def from_crawler(cls, crawler): ''' 获取settings文件中的配置 ''' params = crawler.settings.get('MYSQL') return cls(params) def process_item(self, item, spider): insert_sql = ''' insert into article (title, create_date, url, url_object_id, front_image_url, front_image_path, comment_nums, fav_nums, praise_nums, content) values ('%s', '%s','%s', '%s', '%s', '%s', %d, %d, %d,'%s') ''' % (item['title'], item['create_date'], item['url'], item['url_object_id'], ','.join(item['front_image_url']), item["front_image_path"], item['comment_nums'], item['fav_nums'], item['praise_nums'], item['content']) self.cursor.execute(insert_sql) self.conn.commit() return itemclass MysqlTwistedPipline(object): ''' 异步存储mysql,相对较快 ''' def __init__(self, params): # 使用Twisted中的adbapi获取数据库连接池对象 self.dbpool = adbapi.ConnectionPool("pymysql", **params) @classmethod def from_crawler(cls,crawler): ''' 获取settings文件中的配置 ''' param = crawler.settings.get('MYSQL') params = dict( host = param['host'], user = param['user'], password = param['password'], db = param['db'], charset = param['charset'], cursorclass = pymysql.cursors.DictCursor, ) return cls(params) def process_item(self, item, spider): # 使用数据库连接池对象进行数据库操作,自动传递cursor对象到第一个参数 query = self.dbpool.runInteraction(self.do_insert, item) # 设置出错时的回调方法,自动传递出错消息对象failure到第一个参数 query.addErrback(self.handle_error) def handle_error(self,failure): print(failure) def do_insert(self, cursor, item): insert_sql = ''' insert into article (title, create_date, url, url_object_id, front_image_url, front_image_path, comment_nums, fav_nums, praise_nums, content) values ('%s', '%s','%s', '%s', '%s', '%s', %d, %d, %d,'%s') ''' % (item['title'], item['create_date'], item['url'], item['url_object_id'], ','.join(item['front_image_url']), item["front_image_path"], item['comment_nums'], item['fav_nums'], item['praise_nums'], item['content']) cursor.execute(insert_sql) 最重要的一步就是settings需要配置关键的settings配置: 123456789101112131415161718192021import os# Pipeline自定义一个,就要在这里添加一个,后面的数字表示执行的先后顺序,数字越大越靠后ITEM_PIPELINES = { # 'articleSpider.pipelines.ArticlespiderPipeline': 300, 'scrapy.pipelines.images.ImagesPipeline': 2, 'articleSpider.pipelines.ArticleImagePipeline': 1, 'articleSpider.pipelines.MysqlPipeline': 3, # 'articleSpider.pipelines.MysqlTwistedPipline': 3,}# 图片下载# item中要下载的图片路径,数值类型必须为listIMAGES_URLS_FIELD = "front_image_url"# 获取当前绝对路径project_dir = os.path.abspath(os.path.dirname(__file__))# 图片存储到本地路径IMAGES_STORE = os.path.join(project_dir, 'images')# mysql配置参数MYSQL = {'host':'localhost','user':'root','password':'root','db':'jobbole','charset':'utf8'} 注意 spiders目录下添加utils目录,存放常用的自定义公共方法common.py123456789101112#!/usr/bin/python3# -*- coding: utf-8 -*-__author__ = 'magicYou'import hashlibdef get_md5(url): if isinstance(url, str): url = url.encode("utf-8") m = hashlib.md5() m.update(url) return m.hexdigest() articleSpider目录下创建images,用来存储下载的图片 知识补充xpath基本语法 表达式 说明 /body 选出当前选择器的根元素body /body/div 选取当前选择器文档的根元素body的所有div子元素 /body/div[1] 选取body根元素下面第一个div子元素 /body/div[last()] 选取body根元素下面最后一个div子元素 /body/div[last()-1] 选取body根元素下面倒数第二个div子元素 //div 选取所有div子元素(不论出现在文档任何地方) body//div 选取所有属于body元素的后代的div元素(不论出现在body下的任何地方) /body/@id 选取当前选择器文档的根元素body的id属性 //@class 选取所有元素的class属性 //div[@class] 选取所有拥有class属性的div元素 //div[@class=’bold’] 选取所有class属性等于bold的div元素 //div[contains(@class,’bold’)] 选取所有class属性包含bold的div元素 /div/* 选取当前文档根元素div的所有子元素 //* 选取文档所有节点 //div[@*] 获取所有带属性的div元素 //div/a | //div/p 选取所有div元素下面的子元素a和子元素p(并集) //p[@id=’content’]/text() 选取id为content的p标签的内容(子元素的标签和内容都不会获取到) 一个用法实例: 1txt_header = response.xpath("//div[@id='wrapper_header']/h3/text()").extract()[0] # 选取wrapper_header下h3标签的内容 CSS选择器 表达式 说明 * 选择所有节点 #container 选择Id为container的节点 .container 选取所有包含container类的节点 li a 选取所有li下的所有后代a元素(子和孙等所有的都会选中) ul + p 选取ul后面的第一个相邻兄弟p元素 div#container > ul 选取id为container的div的所有ul子元素 ul ~ p 选取与ul元素后面的所有兄弟p元素 a[title] 选取所有有title属性的a元素 a[href=’http://taobao.com'] 选取所有href属性等于http://taobao.com的a元素 a[href*=’taobao’] 选取所有href属性包含taobao的a元素 a[href^=’http’] 选取所有href属性开头为http的a元素 a[href$=’.com’] 选取所有href属性结尾为.com的a元素 input[type=radio]:checked 选取选中的radio的input元素 div:not(#container) 选取所有id非container的div元素 li:nth-child(3) 选取第三个li元素 tr:nth-child(2n) 选取偶数位的tr元素 a::attr(href) 获取所有a元素的href属性值 一个用法实例: 1txt_header = response.css("#wrapper_header h3::text").extract_first() # 选取wrapper_header下h3标签的内容 参考资料、视频 scrapy中文文档 Python爬虫框架Scrapy学习笔记原创(来自CSDN) Python分布式爬虫打造搜索引擎(慕课视频)","categories":[{"name":"Python","slug":"Python","permalink":"https://blog.magicyou.cn/categories/Python/"}],"tags":[{"name":"Python","slug":"Python","permalink":"https://blog.magicyou.cn/tags/Python/"},{"name":"scrapy","slug":"scrapy","permalink":"https://blog.magicyou.cn/tags/scrapy/"}]},{"title":"安装python的crypto模块","slug":"Python-安装python的crypto模块","date":"2018-09-26T15:12:45.000Z","updated":"2018-09-26T15:12:45.000Z","comments":true,"path":"2018/09/26/Python-安装python的crypto模块/","link":"","permalink":"https://blog.magicyou.cn/2018/09/26/Python-%E5%AE%89%E8%A3%85python%E7%9A%84crypto%E6%A8%A1%E5%9D%97/","excerpt":"crypto模块安装老是出问题,这次咋就这么顺利,先记一下","text":"crypto模块安装老是出问题,这次咋就这么顺利,先记一下 测试环境1: 系统:centos6.8 python版本:Python 3.6.1 测试环境2: 系统:macOs python版本:Python 3.7.2 安装办法 点击下载 下载 手动安装 123>>> tar zxvf pycrypto-2.6.1.tar.gz>>> cd pycrypto-2.6.1>>> python3 setup.py install 测试一下 123456789[root@localhost testPython]# python3Python 3.6.1 (default, Dec 29 2018, 16:52:58)[GCC 4.4.7 20120313 (Red Hat 4.4.7-23)] on linuxType "help", "copyright", "credits" or "license" for more information.>>> from Crypto import Random>>> import binascii>>> from Crypto.PublicKey import RSA>>> from Crypto.Cipher import PKCS1_v1_5>>> SUCCESS ! *","categories":[{"name":"Python","slug":"Python","permalink":"https://blog.magicyou.cn/categories/Python/"}],"tags":[{"name":"Python","slug":"Python","permalink":"https://blog.magicyou.cn/tags/Python/"}]},{"title":"python3多进程处理队列","slug":"Python-多进程处理队列","date":"2018-09-23T12:12:45.000Z","updated":"2018-09-23T12:12:45.000Z","comments":true,"path":"2018/09/23/Python-多进程处理队列/","link":"","permalink":"https://blog.magicyou.cn/2018/09/23/Python-%E5%A4%9A%E8%BF%9B%E7%A8%8B%E5%A4%84%E7%90%86%E9%98%9F%E5%88%97/","excerpt":"使用过python多进程和多线程模块的都知道,Python下多线程是鸡肋,推荐使用多进程。至于原因,这里不做深究,本篇主要解决Python多进程处理队列的问题。","text":"使用过python多进程和多线程模块的都知道,Python下多线程是鸡肋,推荐使用多进程。至于原因,这里不做深究,本篇主要解决Python多进程处理队列的问题。 简单学习一下 多进程 Multiprocessing模块Process 类Process 类用来描述一个进程对象。创建子进程的时候,只需要传入一个执行函数和函数的参数即可完成 Process 示例的创建。 star() 方法启动进程, join() 方法实现进程间的同步,等待所有进程退出。 close() 用来阻止多余的进程涌入进程池 Pool 造成进程阻塞。使用方法multiprocessing.Process(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None) target 是函数名字,需要调用的函数 args 函数需要的参数,以 tuple 的形式传入 例子1234567891011121314import multiprocessingdef run(name): print('我是子进程%d ' % name)if __name__ == '__main__': print('我是主进程') ser = 0 for i in range(3): ser += 1 p = multiprocessing.Process(target=run, args=(ser,)) print('子进程%d启动' % ser) p.start() p.join() print('进程关闭') PoolPool 可以提供指定数量的进程供用户使用,默认是 CPU 核数。当有新的请求提交到 Poll 的时候,如果池子没有满,会创建一个进程来执行,否则就会让该请求等待。 Pool 对象调用 join 方法会等待所有的子进程执行完毕 调用 join 方法之前,必须调用 close 调用 close 之后就不能继续添加新的 Process 了 pool.apply_asyncapply_async 方法用来同步执行进程,允许多个进程同时进入池子。 例子1234567891011121314from multiprocessing import Pooldef run(name): print('我是子进程%d ' % name)if __name__ == '__main__': print('我是主进程') po = Pool(processes=3) ser = 0 for i in range(3): ser += 1 po.apply_async(run, args=(ser,)) print('子进程%d启动' % ser) po.close() po.join() print('进程关闭') multiprocessing.Manager().Queue()多进程中的队列,可以实现多进程之间的通信,用法和Python自带queue类似 发现问题 需求:主进程有一堆数据,需要一个一个进行处理。现在要做到主进程提供数据,让多个子进程进行协同处理这些数据,并且不能重复处理。问题代码如下:12345678910111213141516171819202122232425262728293031323334353637383940#!/usr/bin/python3# -*- coding: utf-8 -*-from multiprocessing import Manager,Pool,freeze_supportimport os# 进程数NUM_PROCESS = 3def read(q): pid = os.getpid() print('子进程(%s) 启动' % pid) while True: data = q.get() if q.empty(): print("列表为空!") break else: print('read从write中获取:', data) returndef main(): print('主进程(%s) start'%os.getpid()) queue=Manager().Queue() #Manager中的Queue才能配合Pool po = Pool(processes=NUM_PROCESS) # 模拟数据 print('write启动') for i in range(100): queue.put(i) results = [] for i in range(NUM_PROCESS): print("进程%d" % (i)) result = po.apply_async(read,args=(queue,)) results.append(result) po.close() #不允许进程池再加新的请求了 po.join()if __name__ == '__main__': main() 运行结果: 处理快要结束的的时候,进程卡住了。第二次运行还是卡到,子进程不结束,主进程也无法完成。 猜测是子进程的死循环无法跳出导致,修改read()方法进行测试1234567def read(q): pid = os.getpid() print('子进程(%s) 启动' % pid) while not q.empty(): data = q.get() print('read从write中获取:', data) return 运行结果:问题依旧没有改善。 四处搜索网上有没有前辈遇到这个类似的问题,有个说队列末尾加上一个标识,用这个标识判断队列中的数据是否处理完毕修改源代码,进行测试:123456789101112131415161718192021222324252627282930def read(q): pid = os.getpid() print('子进程(%s) 启动' % pid) while True: data = q.get() if q.empty() or data == 'end': # 使用end标识判断队列中的数据是否处理完毕 print("列表为空!") break else: print('read从write中获取:', data) returndef main(): print('主进程(%s) start'%os.getpid()) queue=Manager().Queue() #Manager中的Queue才能配合Pool po = Pool(processes=NUM_PROCESS) # 模拟数据 print('write启动') for i in range(100): queue.put(i) queue.put('end') # 队列末尾添加 "end" 结束标识 results = [] for i in range(NUM_PROCESS): print("进程%d" % (i)) result = po.apply_async(read,args=(queue,)) results.append(result) po.close() #不允许进程池再加新的请求了 po.join() #不允许进程池再加新的请求了 问题依旧没有改善。多次运行脚本,发现这个问题像是具有随机性,有时候能顺利退出脚本,有时候会卡死。不如让每个进程都多进行一次循环,判断是否有结束标志,再退出子进程呢。1234567891011121314151617181920212223242526272829303132def read(q): pid = os.getpid() print('子进程(%s) 启动' % pid) while True: data = q.get() if q.empty() or data == 'end': print("列表为空!") break else: print('read从write中获取:', data) returndef main(): print('主进程(%s) start'%os.getpid()) queue=Manager().Queue() #Manager中的Queue才能配合Pool po = Pool(processes=NUM_PROCESS) # 模拟数据 print('write启动') for i in range(101): queue.put(i) for i in range(NUM_PROCESS): queue.put('end') results = [] for i in range(NUM_PROCESS): print("进程%d" % (i)) result = po.apply_async(read,args=(queue,)) results.append(result) po.close() #不允许进程池再加新的请求了 po.join() 测试运行多次,进程每次都能正常退出。算是解决了问题。 完整代码如下:12345678910111213141516171819202122232425262728293031323334353637383940414243444546#!/usr/bin/python3# -*- coding: utf-8 -*-__author__ = 'magicYou'from multiprocessing import Manager,Poolimport os# 进程数NUM_PROCESS = 3def read(q): pid = os.getpid() print('子进程(%s) 启动' % pid) while True: data = q.get() if q.empty() or data == 'end': print("列表为空!") break else: print('read从write中获取:', data) returndef main(): print('主进程(%s) start'%os.getpid()) queue=Manager().Queue() #Manager中的Queue才能配合Pool po = Pool(processes=NUM_PROCESS) # 模拟数据 print('write启动') for i in range(101): queue.put(i) for i in range(NUM_PROCESS): queue.put('end') results = [] for i in range(NUM_PROCESS): print("进程%d" % (i)) result = po.apply_async(read,args=(queue,)) results.append(result) po.close() #不允许进程池再加新的请求了 po.join()if __name__ == '__main__': main()","categories":[{"name":"Python","slug":"Python","permalink":"https://blog.magicyou.cn/categories/Python/"}],"tags":[{"name":"Python","slug":"Python","permalink":"https://blog.magicyou.cn/tags/Python/"}]},{"title":"centos防火墙设置","slug":"Linux-centos防火墙设置","date":"2018-07-06T07:23:00.000Z","updated":"2018-07-06T07:23:00.000Z","comments":true,"path":"2018/07/06/Linux-centos防火墙设置/","link":"","permalink":"https://blog.magicyou.cn/2018/07/06/Linux-centos%E9%98%B2%E7%81%AB%E5%A2%99%E8%AE%BE%E7%BD%AE/","excerpt":"centos防火墙设置","text":"centos防火墙设置 环境概况 12系统:CentOS Linux release 6.8 安装pyinstaller``` 查看防火墙状态service iptables status 停止防火墙service iptables stop 启动防火墙service iptables start 重启防火墙service iptables restart 永久关闭防火墙chkconfig iptables off 永久关闭后重启chkconfig iptables on ``` 3、开启80端口 vim /etc/sysconfig/iptables 12# 加入如下代码,比着两葫芦画瓢 :)-A INPUT -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT 保存退出后重启防火墙 1service iptables restart","categories":[{"name":"Linux","slug":"Linux","permalink":"https://blog.magicyou.cn/categories/Linux/"}],"tags":[{"name":"Centos","slug":"Centos","permalink":"https://blog.magicyou.cn/tags/Centos/"}]},{"title":"pyinstaller编译python脚本为单文件可执行文件遇到的问题","slug":"Python-pyinstaller编译python脚本为单文件可执行文件遇到的问题","date":"2018-07-06T07:23:00.000Z","updated":"2018-07-06T07:23:00.000Z","comments":true,"path":"2018/07/06/Python-pyinstaller编译python脚本为单文件可执行文件遇到的问题/","link":"","permalink":"https://blog.magicyou.cn/2018/07/06/Python-pyinstaller%E7%BC%96%E8%AF%91python%E8%84%9A%E6%9C%AC%E4%B8%BA%E5%8D%95%E6%96%87%E4%BB%B6%E5%8F%AF%E6%89%A7%E8%A1%8C%E6%96%87%E4%BB%B6%E9%81%87%E5%88%B0%E7%9A%84%E9%97%AE%E9%A2%98/","excerpt":"编译python脚本为单文件可执行文件(python3)","text":"编译python脚本为单文件可执行文件(python3) 环境概况 123系统:CentOS Linux release 7.5python版本:3.6.1注:已安装pip 安装pyinstaller pip install pyinstaller pyinstaller --onefile testCsv.py #编译好的文件在dist目录下 激动的开始做成可执行单文件,这是什么鬼… 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253[root@magicYou signal]# pyinstaller --onefile testCsv.py204 INFO: PyInstaller: 3.3.1205 INFO: Python: 3.6.1206 INFO: Platform: Linux-3.10.0-693.17.1.el7.x86_64-x86_64-with-centos-7.5.1804-Core206 INFO: wrote /python/signal/testCsv.spec209 INFO: UPX is not available.210 INFO: Extending PYTHONPATH with paths['/python/signal', '/python/signal']210 INFO: checking Analysis210 INFO: Building Analysis because out00-Analysis.toc is non existent210 INFO: Initializing module dependency graph...211 INFO: Initializing module graph hooks...212 INFO: Analyzing base_library.zip ...3827 INFO: running Analysis out00-Analysis.toc3840 INFO: Caching module hooks...3844 INFO: Analyzing /python/signal/testCsv.py3867 INFO: Loading module hooks...3868 INFO: Loading module hook "hook-encodings.py"...3937 INFO: Loading module hook "hook-pydoc.py"...3938 INFO: Loading module hook "hook-xml.py"...4206 INFO: Looking for ctypes DLLs4206 INFO: Analyzing run-time hooks ...4213 INFO: Looking for dynamic libraries4753 INFO: Looking for eggs4753 INFO: Python library not in binary dependencies. Doing additional searching...Traceback (most recent call last): File "/usr/local/python3/bin/pyinstaller", line 11, in <module> sys.exit(run()) File "/usr/local/python3/lib/python3.6/site-packages/PyInstaller/__main__.py", line 94, in run run_build(pyi_config, spec_file, **vars(args)) File "/usr/local/python3/lib/python3.6/site-packages/PyInstaller/__main__.py", line 46, in run_build PyInstaller.building.build_main.main(pyi_config, spec_file, **kwargs) File "/usr/local/python3/lib/python3.6/site-packages/PyInstaller/building/build_main.py", line 791, in main build(specfile, kw.get('distpath'), kw.get('workpath'), kw.get('clean_build')) File "/usr/local/python3/lib/python3.6/site-packages/PyInstaller/building/build_main.py", line 737, in build exec(text, spec_namespace) File "<string>", line 16, in <module> File "/usr/local/python3/lib/python3.6/site-packages/PyInstaller/building/build_main.py", line 213, in __init__ self.__postinit__() File "/usr/local/python3/lib/python3.6/site-packages/PyInstaller/building/datastruct.py", line 161, in __postinit__ self.assemble() File "/usr/local/python3/lib/python3.6/site-packages/PyInstaller/building/build_main.py", line 545, in assemble self._check_python_library(self.binaries) File "/usr/local/python3/lib/python3.6/site-packages/PyInstaller/building/build_main.py", line 629, in _check_python_library raise IOError(msg)OSError: Python library not found: libpython3.6.so.1.0, libpython3.6mu.so.1.0, libpython3.6m.so.1.0This would mean your Python installation doesn't come with proper library files.This usually happens by missing development package, or unsuitable build parameters of Python installation.* On Debian/Ubuntu, you would need to install Python development packages * apt-get install python3-dev * apt-get install python-dev* If you're building Python by yourself, please rebuild your Python with `--enable-shared` (or, `--enable-framework` on Darwin) 按照一贯做法,百度谷歌一波 尝试解决方案一: 搜来的原话是这样说的 12大概意思就是pyinstaller报错找不到python动态库,也提示说升级python-dev。因为我是centos,各种yum install python-devel和yum install python-dev都不行,什么升级/重装python、sudo、su甚至重装系统都不行!!!我又必须在centos6.2环境下编译,如果我在其他高版本的环境下打包后拿到centos6.2就会报错,言归正传!其实处理起来很简单。只需要复制python安装目录下的动态库到系统地动态库目录即可。cp -R /usr/local/python3.4/lib/* /usr/lib64/ 这样就搞定了! 我的是python3.6,要不就试试呗。 看了一下,python安装位置,没毛病, 1cp -R /usr/local/python3.6/lib/* /usr/lib64/ 再次生成可执行单文件,依然报错,问题一样,接着找解决办法 尝试解决方案二:仔细看报错的信息,有这么一段很醒目: 1234567SError: Python library not found: libpython3.6.so.1.0, libpython3.6mu.so.1.0, libpython3.6m.so.1.0This would mean your Python installation doesn't come with proper library files.This usually happens by missing development package, or unsuitable build parameters of Python installation.* On Debian/Ubuntu, you would need to install Python development packages * apt-get install python3-dev * apt-get install python-dev 是在说我缺少了某个东西,让我安装python3-dev云云的。深入了解一下 12345678linux发行版通常会把类库的头文件和相关的pkg-config分拆成一个单独的xxx-dev(el)包. //pkg=package,包裹以python为例, 以下情况你是需要python-dev的你需要自己安装一个源外的python类库, 而这个类库内含需要编译的调用python api的c/c++文件 //如:安装使用WiringpisPi库需要python-dev你自己写的一个程序编译需要链接libpythonXX.(a|so)(注:以上不含使用ctypes/ffi或者裸dlsym方式直接调用libpython.so)其他正常使用python或者通过安装源内的python类库的不需要python-dev. 后再来找一一个靠谱的回答 原话是这样说的: 12345You must install python-dev for your python's versionfor example:Debian/Ubuntu - Python 2.7 ---> python2.7-devCentOs/RedHat - Python 3.4 ---> python34-develCentOs/RedHat - Python 2.7 ---> python27-devel 各个python版本对应的python-dev版本,推测我的是python36-devel,动手试试 123456789101112131415161718192021222324252627[root@magicYou signal]# yum install python36-devel已加载插件:fastestmirror, langpacksRepository base is listed more than once in the configurationRepository updates is listed more than once in the configurationRepository extras is listed more than once in the configurationRepository centosplus is listed more than once in the configurationLoading mirror speeds from cached hostfile正在解决依赖关系--> 正在检查事务---> 软件包 python36-devel.x86_64.0.3.6.5-3.el7 将被 安装--> 正在处理依赖关系 python36-libs(x86-64) = 3.6.5-3.el7,它被软件包 python36-devel-3.6.5-3.el7.x86_64 需要--> 正在处理依赖关系 python36 = 3.6.5-3.el7,它被软件包 python36-devel-3.6.5-3.el7.x86_64 需要--> 正在处理依赖关系 python(abi) = 3.6,它被软件包 python36-devel-3.6.5-3.el7.x86_64 需要............ python36-devel.x86_64 0:3.6.5-3.el7作为依赖被安装: dwz.x86_64 0:0.11-3.el7 perl-srpm-macros.noarch 0:1-8.el7 python-rpm-macros.noarch 0:3-22.el7 python-srpm-macros.noarch 0:3-22.el7 python36.x86_64 0:3.6.5-3.el7 python36-libs.x86_64 0:3.6.5-3.el7 redhat-rpm-config.noarch 0:9.1.0-80.el7.centos完毕! 再次尝试pyinstaller生成可执行单文件 123456789101112131415161718192021222324252627282930313233343536373839404142root@magicYou signal]# pyinstaller --onefile testCsv.py234 INFO: PyInstaller: 3.3.1234 INFO: Python: 3.6.1235 INFO: Platform: Linux-3.10.0-693.17.1.el7.x86_64-x86_64-with-centos-7.5.1804-Core236 INFO: wrote /python/signal/testCsv.spec239 INFO: UPX is not available.240 INFO: Extending PYTHONPATH with paths['/python/signal', '/python/signal']240 INFO: checking Analysis240 INFO: Building Analysis because out00-Analysis.toc is non existent240 INFO: Initializing module dependency graph...241 INFO: Initializing module graph hooks...243 INFO: Analyzing base_library.zip ...3891 INFO: running Analysis out00-Analysis.toc3904 INFO: Caching module hooks...3908 INFO: Analyzing /python/signal/testCsv.py3933 INFO: Loading module hooks...3934 INFO: Loading module hook "hook-encodings.py"...4006 INFO: Loading module hook "hook-pydoc.py"...4007 INFO: Loading module hook "hook-xml.py"...4292 INFO: Looking for ctypes DLLs4292 INFO: Analyzing run-time hooks ...4299 INFO: Looking for dynamic libraries4864 INFO: Looking for eggs4864 INFO: Python library not in binary dependencies. Doing additional searching...4901 INFO: Using Python library /usr/lib64/libpython3.6m.so.1.04905 INFO: Warnings written to /python/signal/build/testCsv/warntestCsv.txt4936 INFO: Graph cross-reference written to /python/signal/build/testCsv/xref-testCsv.html4949 INFO: checking PYZ4949 INFO: Building PYZ because out00-PYZ.toc is non existent4949 INFO: Building PYZ (ZlibArchive) /python/signal/build/testCsv/out00-PYZ.pyz5320 INFO: Building PYZ (ZlibArchive) /python/signal/build/testCsv/out00-PYZ.pyz completed successfully.5323 INFO: checking PKG5323 INFO: Building PKG because out00-PKG.toc is non existent5323 INFO: Building PKG (CArchive) out00-PKG.pkg10523 INFO: Building PKG (CArchive) out00-PKG.pkg completed successfully.10526 INFO: Bootloader /usr/local/python3/lib/python3.6/site-packages/PyInstaller/bootloader/Linux-64bit/run10526 INFO: checking EXE10526 INFO: Building EXE because out00-EXE.toc is non existent10526 INFO: Building EXE from out00-EXE.toc10526 INFO: Appending archive to ELF section in EXE /python/signal/dist/testCsv10555 INFO: Building EXE from out00-EXE.toc completed successfully. 问题解决,很棒很棒! 续集后来又在centos6.8,发现上述办法不能解决这个问题 Centos6.8上如下 123456789[root@magicYou /]# yum install python36-devel已加载插件:fastestmirror设置安装进程Loading mirror speeds from cached hostfilebase | 3.7 kB 00:00extras | 3.4 kB 00:00updates | 3.4 kB 00:00No package python36-devel available.错误:无须任何处理 后来又在centos7.2 上看了一下: 123456789[root@VM_0_12_centos ~]# yum install python36-devel已加载插件:fastestmirror, langpacksepel | 3.2 kB 00:00:00extras | 3.4 kB 00:00:00os | 3.6 kB 00:00:00updates | 3.4 kB 00:00:00Loading mirror speeds from cached hostfile软件包 python36-devel-3.6.6-1.el7.x86_64 已安装并且是最新版本无须任何处理 为啥不一样呢,依然解决不了问题。后来在一个角落找到一个靠谱的,如下: 12345678910I have solved this in the way below:1st. check your system if it has libpython3.4m.so.1.0. If yes, go to step 2nd. If no, download it(I'm using anaconda python, so I have it in anaconda folder.)2nd. sudo cp /folder/to/your/libpython3.4m.so.1.0 /usr/lib# 翻译一下我用下面的方法解决了这个问题:1. 检查系统是否具有libpython3.4.so.1.0。如果有,请转到步骤2。如果没有,下载它(我使用的是anaconda python,所以我把它放在anaconda文件夹中)。2. sudo cp /folder/to/your/libpython3.4.so.1.0 /usr/lib 比葫芦画瓢,find一下,还真找到了 1find / -name libpython3.6mu.so.1.0 1cp /folder/to/your/libpython3.6mu.so.1.0 /usr/lib 再次解决问题,十分坎坷 总结: pyinstaller的问题在不同的系统上都不一样; 还有一个比较严重的问题就是在高版本的centos上生成的可执行单文件在低版本上可能无法运行,我自己的第一个虚拟机是centos7.2费了老劲搞好环境生成单文件放在生产环境centos6.8报错,查找问题是说一个系统的库版本太高导致;","categories":[{"name":"Python","slug":"Python","permalink":"https://blog.magicyou.cn/categories/Python/"}],"tags":[{"name":"Python","slug":"Python","permalink":"https://blog.magicyou.cn/tags/Python/"}]},{"title":"我是怎么维护更新两个环境的四份代码的","slug":"Linux-我是怎么维护更新两个环境的四份代码的","date":"2018-07-03T12:23:00.000Z","updated":"2018-07-03T12:23:00.000Z","comments":true,"path":"2018/07/03/Linux-我是怎么维护更新两个环境的四份代码的/","link":"","permalink":"https://blog.magicyou.cn/2018/07/03/Linux-%E6%88%91%E6%98%AF%E6%80%8E%E4%B9%88%E7%BB%B4%E6%8A%A4%E6%9B%B4%E6%96%B0%E4%B8%A4%E4%B8%AA%E7%8E%AF%E5%A2%83%E7%9A%84%E5%9B%9B%E4%BB%BD%E4%BB%A3%E7%A0%81%E7%9A%84/","excerpt":"公司的系统都是内网访问,服务器也不能访问外网,所以git更新维护代码是不现实的。顺便学习几个新命令","text":"公司的系统都是内网访问,服务器也不能访问外网,所以git更新维护代码是不现实的。顺便学习几个新命令 学习两个命令1. scp Linux scp命令用于Linux之间复制文件和目录。 scp是 secure copy的缩写, scp是linux系统下基于ssh登陆进行安全的远程文件拷贝命令。 命令格式scp [可选参数] [原路径] [目标路径] 参数说明 -1: 强制scp命令使用协议ssh1 -2: 强制scp命令使用协议ssh2 -4: 强制scp命令只使用IPv4寻址 -6: 强制scp命令只使用IPv6寻址 -B: 使用批处理模式(传输过程中不询问传输口令或短语) -C: 允许压缩。(将-C标志传递给ssh,从而打开压缩功能) -p:保留原文件的修改时间,访问时间和访问权限。 -q: 不显示传输进度条。 -r: 递归复制整个目录。 -v:详细方式显示输出。scp和ssh(1)会显示出整个过程的调试信息。这些信息用于调试连接,验证和配置问题。 -c cipher: 以cipher将数据传输进行加密,这个选项将直接传递给ssh。 -F ssh_config: 指定一个替代的ssh配置文件,此参数直接传递给ssh。 -i identity_file: 从指定文件中读取传输时使用的密钥文件,此参数直接传递给ssh。 -l limit: 限定用户所能使用的带宽,以Kbit/s为单位。 -o ssh_option: 如果习惯于使用ssh_config(5)中的参数传递方式, -P port:注意是大写的P, port是指定数据传输用到的端口号 -S program: 指定加密传输时所使用的程序。此程序必须能够理解ssh(1)的选项 使用方法 从本地复制到远程命令格式: 1234567scp local_file remote_username@remote_ip:remote_folder 或者 scp local_file remote_username@remote_ip:remote_file 或者 scp local_file remote_ip:remote_folder 或者 scp local_file remote_ip:remote_file 复制目录命令格式: 123scp -r local_folder remote_username@remote_ip:remote_folder 或者 scp -r local_folder remote_ip:remote_folder 从远程复制到本地从远程复制到本地,只要将从本地复制到远程的命令的后2个参数调换顺序即可,如下实例 应用实例: 12scp [email protected]:/home/root/others/music /home/space/music/1.mp3 scp -r www.magicyou.cn:/home/root/others/ /home/space/music/ 指定端口1.如果远程服务器防火墙有为scp命令设置了指定的端口,我们需要使用 -P 参数来设置命令的端口号,命令格式如下: 12#scp 命令使用端口号 4588scp -P 4588 [email protected]:/usr/local/sin.sh /home/administrator 2. rsyncrsync的目的是实现本地主机和远程主机上的文件同步(包括本地推到远程,远程拉到本地两种同步方式),也可以实现本地不同路径下文件的同步,但不能实现远程路径1到远程路径2之间的同步(scp可以实现)。 命令格式rsync [可选参数] [原路径] [目标路径] 参数说明 -v:显示rsync过程中详细信息。可以使用”-vvvv”获取更详细信息。 -P:显示文件传输的进度信息。(实际上”-P”=”–partial –progress”,其中的”–progress”才是显示进度信息的)。 -n –dry-run :仅测试传输,而不实际传输。常和”-vvvv”配合使用来查看rsync是如何工作的。 -a –archive :归档模式,表示递归传输并保持文件属性。等同于”-rtopgDl”。 -r –recursive:递归到目录中去。 -t –times:保持mtime属性。强烈建议任何时候都加上”-t”,否则目标文件mtime会设置为系统时间,导致下次更新 :检查出mtime不同从而导致增量传输无效。 -o –owner:保持owner属性(属主)。 -g –group:保持group属性(属组)。 -p –perms:保持perms属性(权限,不包括特殊权限)。 -D :是”–device –specials”选项的组合,即也拷贝设备文件和特殊文件。 -l –links:如果文件是软链接文件,则拷贝软链接本身而非软链接所指向的对象。 -z :传输时进行压缩提高效率。 -R –relative:使用相对路径。意味着将命令行中指定的全路径而非路径最尾部的文件名发送给服务端,包括它们的属性。用法见下文示例。 –size-only :默认算法是检查文件大小和mtime不同的文件,使用此选项将只检查文件大小。 -u –update :仅在源mtime比目标已存在文件的mtime新时才拷贝。注意,该选项是接收端判断的,不会影响删除行为。 -d –dirs :以不递归的方式拷贝目录本身。默认递归时,如果源为”dir1/file1”,则不会拷贝dir1目录,使用该选项将拷贝dir1但不拷贝file1。 –max-size :限制rsync传输的最大文件大小。可以使用单位后缀,还可以是一个小数值(例如:”–max-size=1.5m”) –min-size :限制rsync传输的最小文件大小。这可以用于禁止传输小文件或那些垃圾文件。 –exclude :指定排除规则来排除不需要传输的文件。 –delete :以SRC为主,对DEST进行同步。多则删之,少则补之。注意”–delete”是在接收端执行的,所以它是在 :exclude/include规则生效之后才执行的。 -b –backup :对目标上已存在的文件做一个备份,备份的文件名后默认使用”~”做后缀。 –backup-dir:指定备份文件的保存路径。不指定时默认和待备份文件保存在同一目录下。 -e :指定所要使用的远程shell程序,默认为ssh。 –port :连接daemon时使用的端口号,默认为873端口。 –password-file:daemon模式时的密码文件,可以从中读取密码实现非交互式。注意,这不是远程shell认证的密码,而是rsync模块认证的密码。 -W –whole-file:rsync将不再使用增量传输,而是全量传输。在网络带宽高于磁盘带宽时,该选项比增量传输更高效。 –existing :要求只更新目标端已存在的文件,目标端还不存在的文件不传输。注意,使用相对路径时如果上层目录不存在也不会传输。 –ignore-existing:要求只更新目标端不存在的文件。和”–existing”结合使用有特殊功能,见下文示例。 –remove-source-files:要求删除源端已经成功传输的文件。 使用方法 从本地复制到远程命令格式: 1234567rsync local_file remote_username@remote_ip:remote_folder 或者 rsync local_file remote_username@remote_ip:remote_file 或者 rsync local_file remote_ip:remote_folder 或者 rsync local_file remote_ip:remote_file 复制目录命令格式: 123rsync -r local_folder remote_username@remote_ip:remote_folder 或者 rsync -r local_folder remote_ip:remote_folder 从远程复制到本地从远程复制到本地,只要将从本地复制到远程的命令的后2个参数调换顺序即可,如下实例 应用实例: 12rsync [email protected]:/home/root/others/music /home/space/music/1.mp3 rsync -r www.magicyou.cn:/home/root/others/ /home/space/music/ 指定端口1rsync -r --port 8888 www.magicyou.cn:/home/root/others/ /home/space/music/ 3. scp 和 rsync 的区别scp是相当于复制,黏贴,如果有的话是覆盖,比较耗时间,不智能。 rsync是复制,如果有重复的文件,会直接跳过,而且他自己的算法优化。 scp是把文件全部复制过去,当文件修改后还是把所有文件复制过去,rsync 第一次是把所有文件同步过去,当文件修改后,只把修改的文件同步过去。 进入正题 列出目前已经有的环境以及路径 名称 本地路径 所在服务器 服务器路径 nginx端口 演示环境 /Applications/MAMP/htdocs/modou_obj/xxxx-web-osp/ 10.0.111.217 /www/xxxx-web-ceshi/ 9009 测试环境 /Applications/MAMP/htdocs/modou_obj/xxxx-web-ceshi/ 10.0.111.217 /www/xxxx-web-osp/ 9010 校验数据的生产环境 /Applications/MAMP/htdocs/modou_obj/xxxx-web_test/ 10.0.112.222 /opt/www/xxxx-web_new/ 9006 生产环境 /Applications/MAMP/htdocs/modou_obj/xxxx-web_new/ 10.0.112.222 /opt/www/xxxx-web_new/ 8080 本地环境 /Applications/MAMP/htdocs/modou_obj/xxxx-web/ ————— ————— 80 情况介绍 由于环境很多,而且是在内网访问,服务器也不能连接外网,git就无能为力。按照之前,每次更新代码或者深夜上线新功能,都要远程联系运维,运维再连上vpn,更新代码。 麻烦不,后来技术总监推给我,让我维护这几个环境。 内网怎么去把代码传到服务器呢? 工具不太好使,而且每次都要打开,然后文件往里面拖。很容易传输过程缺文件,系统崩溃,用户又要反馈; 工具更新也慢,每次都要全部拖进去,文件多了耗时间; scp这个命令也不是不可以,就是每次都要全量同步 最后当然是用rsync命令了,妥妥的。 首先,复制几套代码,放一块,文件夹名字尽可能和服务器项目所在文件夹保持一致,防止混乱; ———— —————— xxxx-web 本地开发代码 xxxx-web-ceshi 本地开发代码复制,对应测试环境 xxxx-web-osp 本地开发代码复制,演示环境 xxxx-web_test 本地开发代码复制,校验数据的内测环境 xxxx-web_new 本地开发代码复制,生产环境 然后,代码当然是放在git上,git忽略代码需要针对各个项目分别做维护 文件名 文件相对项目目录所在路径 git忽略原因 dev.php 数据库配置,redis配置等 测试对应测试数据库,生产对应生产数据库 orginfo.php 各个万象城基本配置信息 项目需要在不同项目下先是不同的名称,比如在演示环境不允许出现真实地名,大区,其他环境正常,就在演示环境下改名称为演示大区 基本完美,每次都要打命令,我干脆简单点,吧命令记录在文本里,每次更新代码复制粘贴 正式环境更新 2.0.0-prod 123456rsync -r /Applications/MAMP/htdocs/modou_obj/xxxx-web_new/Application/ [email protected]:/data02/dmp/www/xxxx-web_new/Application/rsync -r /Applications/MAMP/htdocs/modou_obj/xxxx-web_new/Public/ [email protected]:/data02/dmp/www/xxxx-web_new/Public/rsync -r /Applications/MAMP/htdocs/modou_obj/xxxx-web_new/ThinkPHP/ [email protected]:/data02/dmp/www/xxxx-web_new/ThinkPHP/ 正式环境(内测) 9006 2.0.0-dev 1234567rsync -r /Applications/MAMP/htdocs/modou_obj/xxxx-web_test/Application/ [email protected]:/data02/dmp/www/xxxx-web_test/Application/rsync -r /Applications/MAMP/htdocs/modou_obj/xxxx-web_test/Public/ [email protected]:/data02/dmp/www/xxxx-web_test/Public/rsync -r /Applications/MAMP/htdocs/modou_obj/xxxx-web_test/ThinkPHP/ [email protected]:/data02/dmp/www/xxxx-web_test/ThinkPHP/ 测试环境 9010 123456rsync -r /Applications/MAMP/htdocs/modou_obj/xxxx-web-ceshi/Application/ [email protected]:/www/xxxx-web-ceshi/Application/rsync -r /Applications/MAMP/htdocs/modou_obj/xxxx-web-ceshi/Public/ [email protected]:/www/xxxx-web-ceshi/Public/rsync -r /Applications/MAMP/htdocs/modou_obj/xxxx-web-ceshi/ThinkPHP/ [email protected]:/www/xxxx-web-ceshi/ThinkPHP/ 演示环境 90091234567rsync -r /Applications/MAMP/htdocs/modou_obj/xxxx-web-osp/Application/ [email protected]:/www/xxxx-web-osp/Application/rsync -r /Applications/MAMP/htdocs/modou_obj/xxxx-web-osp/Public/ [email protected]:/www/xxxx-web-osp/Public/rsync -r /Applications/MAMP/htdocs/modou_obj/xxxx-web-osp/ThinkPHP/ [email protected]:/www/xxxx-web-osp/Public/ThinkPHP/ 懒人模式开启,不好的是不容易记忆这几个命令,多打几遍应该没坏处,emmmmm….","categories":[{"name":"Linux","slug":"Linux","permalink":"https://blog.magicyou.cn/categories/Linux/"}],"tags":[{"name":"Linux","slug":"Linux","permalink":"https://blog.magicyou.cn/tags/Linux/"},{"name":"MacOS","slug":"MacOS","permalink":"https://blog.magicyou.cn/tags/MacOS/"}]},{"title":"PHP执行系统命令的有几个常用的函数","slug":"php-PHP执行系统命令的有几个常用的函数","date":"2018-07-03T07:12:00.000Z","updated":"2018-07-03T07:12:12.000Z","comments":true,"path":"2018/07/03/php-PHP执行系统命令的有几个常用的函数/","link":"","permalink":"https://blog.magicyou.cn/2018/07/03/php-PHP%E6%89%A7%E8%A1%8C%E7%B3%BB%E7%BB%9F%E5%91%BD%E4%BB%A4%E7%9A%84%E6%9C%89%E5%87%A0%E4%B8%AA%E5%B8%B8%E7%94%A8%E7%9A%84%E5%87%BD%E6%95%B0/","excerpt":"PHP执行系统命令的有几个常用的函数,如有:system函数、exec函数、popen函数,passthru,shell_exec函数","text":"PHP执行系统命令的有几个常用的函数,如有:system函数、exec函数、popen函数,passthru,shell_exec函数 先附上测试用的python脚本1234567891011121314151617181920212223242526272829303132testCsv.py#!/usr/bin/python3# -*- coding: utf-8 -*-import timeimport csvimport randomimport sysfilepath = '/Users/magicyou/Desktop/python/temp/'# 生成csv文件def exportCsv(title,data): print('test') print('test2') time.sleep(10) fileName = time.strftime('%Y%m%d%H%M',time.localtime(time.time())) + '_'+ str(int(round((time.time()) * 1000))) + ".csv" with open(filepath + fileName,"w",newline='',encoding='gb18030') as csvfile: writer = csv.writer(csvfile) # 先写入columns_name try: writer.writerow(title) writer.writerows(data) except: info = sys.exc_info() return {'status':False, 'msg':'文件写入失败!'} return {'status':True, 'fileName':hostName + fileName}if __name__ == "__main__": import time title = ['序号','姓名','年龄','价值度','喜好'] data = [['序号','姓名','年龄','价值度','喜好'],['序号','姓名','年龄','价值度','喜好'],['序号','姓名','年龄','价值度','喜好']] exportCsv(title,data) 1 system函数说明1string system ( string $command [, int &$return_var ] ) 同 C 版本的 system() 函数一样, 本函数执行 command 参数所指定的命令, 并且输出执行结果。如果 PHP 运行在服务器模块中, system() 函数还会尝试在每行输出完毕之后, 自动刷新 web 服务器的输出缓存。 参数| 参数 | 描述 || - | - | -: || command| 要执行的命令。|| return_var| 如果提供 return_var 参数, 则外部命令执行后的返回状态将会被设置到此变量中。| 返回值成功则返回命令输出的最后一行, 失败则返回 FALSE 12345678# 案例$last_line = system('/usr/local/bin/python3 /Desktop/python/python/testCsv.py', $output);print_r($last_line);echo $output;// 页面返回// test// test0 2 exec函数说明1string exec ( string $command [, array &$output [, int &$return_var ]] ) exec() 执行 command 参数所指定的命令 参数| 参数 | 描述 || - | - | -: || command| 要执行的命令。|| output| 如果提供了 output 参数, 那么会用命令执行的输出填充此数组, 每行输出填充数组中的一个元素。 数组中的数据不包含行尾的空白字符,例如 \\n 字符。 请注意,如果数组中已经包含了部分元素,exec() 函数会在数组末尾追加内容。如果你不想在数组末尾进行追加, 请在传入 exec() 函数之前 对数组使用 unset() 函数进行重置|| return_var| 如果提供 return_var 参数, 则外部命令执行后的返回状态将会被设置到此变量中。| 返回值命令执行结果的最后一行内容。 如果你需要获取未经处理的全部输出数据, 请使用 passthru() 函数。如果想要获取命令的输出内容, 请确保使用 output 参数。 12345678910111213# 案例$last_line = exec('/usr/local/bin/python3 /Desktop/python/python/testCsv.py', $output,$status);print_r($last_line);var_dump($output);echo $status;# 页面返回// test// array(1) {// [0]=>// string(4) "test"// }// 0 3 popen函数说明1resource popen ( string $command , string $mode ) 打开一个指向进程的管道,该进程由派生给定的 command 命令执行而产生。 参数| 参数 | 描述 || - | - | -: || command| 要执行的命令。|| mode| 必需。规定连接模式。可能的值:r: 只读。w: 只写(打开并清空已有文件或创建一个新文件)| 返回值返回一个和 fopen() 所返回的相同的文件指针,只不过它是单向的(只能用于读或写)并且必须用 pclose() 来关闭。此指针可以用于 fgets(),fgetss() 和 fwrite()。 当模式为 ‘r’,返回的文件指针等于命令的 STDOUT,当模式为 ‘w’,返回的文件指针等于命令的 STDIN。如果出错返回 FALSE。 123456789101112# 案例$handle = popen('/usr/local/bin/python3 /Desktop/python/python/testCsv.py', 'r');print_r($handle);echo "'$handle'; " . gettype($handle) . "\\n";$read = fread($handle, 2096);echo $read;pclose($handle);# 页面返回// Resource id #2// 'Resource id #2'; resource// test 4 passthru函数说明1void passthru ( string $command [, int &$return_var ] ) 同 exec() 函数类似, passthru() 函数 也是用来执行外部命令(command)的。 当所执行的 Unix 命令输出二进制数据, 并且需要直接传送到浏览器的时候, 需要用此函数来替代 exec() 或 system() 函数。 常用来执行诸如 pbmplus 之类的可以直接输出图像流的命令。 通过设置 Content-type 为 image/gif, 然后调用 pbmplus 程序输出 gif 文件, 就可以从 PHP 脚本中直接输出图像到浏览器。 参数| 参数 | 描述 || - | - | -: || command| 要执行的命令。|| return_var| 如果提供 return_var 参数, Unix 命令的返回状态会被记录到此参数。| 返回值没有返回值。 123456# 案例$returnVal = passthru('/usr/local/bin/python3 /Desktop/python/python/testCsv.py', $status);print_r($status);# 页面返回// test// 0 5 shell_exec函数说明1string shell_exec ( string $cmd ) 通过 shell 环境执行命令,并且将完整的输出以字符串的方式返回。 参数| 参数 | 描述 || - | - | -: || command| 要执行的命令。|返回值命令执行的输出。 如果执行过程中发生错误或者进程不产生输出,则返回 NULL。Note:当进程执行过程中发生错误,或者进程不产生输出的情况下,都会返回 NULL, 所以,使用本函数无法通过返回值检测进程是否成功执行。 如果需要检查进程执行的退出码,请使用 exec() 函数。 12345# 案例$returnVal = shell_exec('/usr/local/bin/python3 /Desktop/python/python/testCsv.py');echo $returnVal;# 页面返回// test Tip上述案例都是php在等脚本执行完毕返回状态或者结果,怎样才能只调用脚本不等结果呢?完整的命令如下:这个命令好处是当程序报错,错误记录在log_testPython.txt 1python3 /Desktop/python/python/testCsv.py > /temp/test/log_testPython.txt 2>&1 & 原理:该程序的输出被重定向到一个文件或者其它输出流去你也可以这样: 当然,这样是不会获取到脚本报错信息的 123python3 /Desktop/python/python/testCsv.py > /temp/test/null 2>&1 &或者python3 /Desktop/python/python/testCsv.py > /temp/test/null &","categories":[{"name":"PHP","slug":"PHP","permalink":"https://blog.magicyou.cn/categories/PHP/"}],"tags":[{"name":"PHP","slug":"PHP","permalink":"https://blog.magicyou.cn/tags/PHP/"}]},{"title":"vuex使用方式","slug":"vue-vuex的五个属性","date":"2018-05-11T14:13:02.000Z","updated":"2018-05-11T14:13:04.000Z","comments":true,"path":"2018/05/11/vue-vuex的五个属性/","link":"","permalink":"https://blog.magicyou.cn/2018/05/11/vue-vuex%E7%9A%84%E4%BA%94%E4%B8%AA%E5%B1%9E%E6%80%A7/","excerpt":"vuex的五个属性 state, getters, mutations, actions, modules","text":"vuex的五个属性 state, getters, mutations, actions, modules 主要参考来源: https://vuex.vuejs.org/zh/ https://blog.csdn.net/wh710107079/article/details/88181015 vuex是什么?vuex是一个专门为Vue.js应用开发的状态管理扩展(官方文档叫状态管理模式) 官方建议:vuex适合大型单页应用,如果你的项目足够小,不建议使用,小项目使用vuex可能是繁琐冗余的。 使用方法vuex怎么在vue脚手架里面使用呢? src目录下创建store,store下创建文件store.js store.js内容如下(demo): 123import Vue from 'vue';import Vuex from 'vuex';Vue.use(Vuex) 然后就是五个属性的使用方法 state, getters, mutations, actions, modules State 定义了应用状态的数据结构,可以在这里设置默认的初始状态。 12345678/** state即Vuex中的基本数据! */var state = { count: 0, token: '21212', userId: '还没有',} 组件内使用 1234567export default { computed: { tokenUser: function(){ return this.$store.state.token } } } Getterstate的派生状态,(可以认为是 store 的计算属性)。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。。 1234567891011121314/*即从store的state中派生出的状态。getters接收state作为其第一个参数,接受其他 getters 作为第二个参数,如不需要,第二个参数可以省略如下例子:*/var getters = { // 单个参数 countTokenUser: function(state){ return state.token + state.userId }, // 两个参数 countDoubleAndDouble: function(state, getters) { return getters.countDouble * 2 }} 组件内使用 1234567export default { computed: { tokenUser: function(){ return this.$store.getters.countTokenUser } } } Mutation是唯一更改 store 中状态的方法,且必须是同步函数。 1234567891011121314/*提交mutation是更改Vuex中的store中的状态的唯一方法。mutation必须是同步的,如果要异步需要使用action。每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数,提交载荷作为第二个参数。(提交荷载在大多数情况下应该是一个对象),提交荷载也可以省略的。*/var mutations = { login(state, userInfo) { state.token = userInfo.token state.userId = userInfo.userId }, jian(a) { a.count-- }} 12345methods: { login(){ this.$store.commit('login', {token: '333333', userId: 'lxlxlxl'}) }, } Action用于提交 mutation,而不是直接变更状态,可以包含任意异步操作。 12345678910111213/** actionsAction 类似于 mutation,不同在于:Action 提交的是 mutation,而不是直接变更状态。Action 可以包含任意异步操作 */var actions = { increment (context) { setInterval(function(){ context.commit('increment') }, 1000) }} 感觉官方例子好理解: +++++++++++来自官方让我们来注册一个简单的 action: 123456789101112131415const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment (state) { state.count++ } }, actions: { increment (context) { context.commit('increment') } }}) Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters。当我们在之后介绍到 Modules 时,你就知道 context 对象为什么不是 store 实例本身了。 实践中,我们会经常用到 ES2015 的 参数解构 来简化代码(特别是我们需要调用 commit 很多次的时候): 12345actions: { increment ({ commit }) { commit('increment') }} 分发 ActionAction 通过 store.dispatch 方法触发: store.dispatch(‘increment’)乍一眼看上去感觉多此一举,我们直接分发 mutation 岂不更方便?实际上并非如此,还记得 mutation 必须同步执行这个限制么?Action 就不受约束!我们可以在 action 内部执行异步操作: 123456789101112131415161718192021actions: { incrementAsync ({ commit }) { setTimeout(() => { commit('increment') }, 1000) }}Actions 支持同样的载荷方式和对象方式进行分发:// 以载荷形式分发store.dispatch('incrementAsync', { amount: 10})// 以对象形式分发store.dispatch({ type: 'incrementAsync', amount: 10}) +++++++++++ Module允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中。 1234567891011121314151617181920212223242526272829/** Modules使用单一状态树,导致应用的所有状态集中到一个很大的对象。但是,当应用变得很大时,store 对象会变得臃肿不堪。为了解决以上问题,Vuex 允许我们将 store 分割到模块(module)。每个模块拥有自己的 state、mutation、action、getters、甚至是嵌套子模块——从上至下进行类似的分割: */const moduleA = { state: { name: 'lxl', age:23 }, mutation: { setUserInfo(state, userInfo) { state.name = userInfo.name state.age = userInfo.age } }}const moduleB = { state: { name: 'ljz', age: 22 }, mutation: { setUserInfo2(state, userInfo) { state.name = userInfo.name state.age = userInfo.age } }} 当然还需要使用这五个属性12345678910export default new Vuex.Store({ state, mutations, getters, actions, modules: { a: moduleA, b: moduleB, }}) 最后别忘了要在main.js添加:1import store from './store/store.js'","categories":[{"name":"Vue","slug":"Vue","permalink":"https://blog.magicyou.cn/categories/Vue/"}],"tags":[{"name":"Vue","slug":"Vue","permalink":"https://blog.magicyou.cn/tags/Vue/"}]},{"title":"记一次python为PHP打辅助","slug":"php-记一次为PHP打辅助的python脚本","date":"2018-05-05T05:12:45.000Z","updated":"2018-05-05T05:12:45.000Z","comments":true,"path":"2018/05/05/php-记一次为PHP打辅助的python脚本/","link":"","permalink":"https://blog.magicyou.cn/2018/05/05/php-%E8%AE%B0%E4%B8%80%E6%AC%A1%E4%B8%BAPHP%E6%89%93%E8%BE%85%E5%8A%A9%E7%9A%84python%E8%84%9A%E6%9C%AC/","excerpt":"项目结构:B/S结构,web端,自然是PHP主控; 问题场景:根据相应的条件,实时生成csv文件并下载。文件很大的时候,客户点击导出就一直在等,看着加载一直在打圈圈,大一点的文件三分钟也是常见,时间长了干脆请求超时,下载失败,体验极差; 解决办法:做成离线任务,用户点击下载之后,不再理会,在另一个面板查看文件是否已经生成,生成就可以下载。","text":"项目结构:B/S结构,web端,自然是PHP主控; 问题场景:根据相应的条件,实时生成csv文件并下载。文件很大的时候,客户点击导出就一直在等,看着加载一直在打圈圈,大一点的文件三分钟也是常见,时间长了干脆请求超时,下载失败,体验极差; 解决办法:做成离线任务,用户点击下载之后,不再理会,在另一个面板查看文件是否已经生成,生成就可以下载。 研究一下php怎样做离线任务找到两个相对有代表性的博文: PHP工作笔记:离线执行 离线下载文件(或者是离线执行任务) 简单研究了一下,感觉不是很好用,理由如下: 不可控,文件大了还是容易断 占用php资源,其他的页面请求开始慢下来 这样的话不如想想用如何把这个处理过程交给后台,让它几自个儿玩。 学Python有一段时间,刚好试试”手艺”如何。 规划流程 用户点击导出,php拿到用户请求,整理用户要导出的相关数据(还好这些数据都是一条sql语句查出来的),存入数据库一条记录,状态status为0,表示未处理。顺便展示一下这个表结构,python和php的互帮互助全靠这张表; 列名 类型 长度 是否可为空 备注 id int 11 否 主键,非空且唯一 name varchar 100 否 任务名称 title varchar 255 否 导出字段标题 query varchar 255 否 数据库查询sql creater_name varchar 100 否 创建人 status int 否 任务状态(0:未处理,1:处理中,2,成功,3:失败),默认为0 msg text 是 错误信息记录 filepath varchar 255 是 文件地址 Python脚本一直在服务器运行,实时检测这张表是否有需要处理的任务(就是检测这张表是否有状态值status==1的记录); 2.1 有,就将这条记录的状态status改为1(这时前端页面看到的任务状态是”处理中”) 2.2 查询数据库生成一个csv文件,成功后将文件路径放入条记录的 字段”filepath” 2.3 没有报错,就将status改为2; 有报错,就将status改为3,并将失败原因记录在msg 用户看到最新状态是”成功”,就可以点击文件下载了;状态是 “失败”,则需要进一步查看失败原因进行重新生成相关csv文件。 上python脚本123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160#!/usr/bin/python3# -*- coding: utf-8 -*-import pymysqlimport psycopg2import psycopg2.extrasimport timeimport csvimport randomimport sysfrom Crypto import Randomimport binasciifrom Crypto.PublicKey import RSAfrom Crypto.Cipher import PKCS1_v1_5import threading# 线程数,限定数thread = 6# 本地测试环境hostName = 'http://test-web'mysql = {'host':'localhost','user':'root','pwd':'','db':'test'}pgsql = {'host':'127.0.0.1','database':'sys','user':'root','password':'root','port':'5432'}def conMysql(type='dict'): ''' mysql连接 :param type: 获取数据类型 默认为字典类型 :return:游标 ''' if type=='dict': conn = pymysql.connect(mysql['host'],mysql['user'],mysql['pwd'],mysql['db'],charset='utf8',cursorclass=pymysql.cursors.DictCursor) else: conn = pymysql.connect(mysql['host'],mysql['user'],mysql['pwd'],mysql['db'],charset='utf8') return conndef conPgsql(): ''' pgsql数据库连接 :return: 游标 ''' conn = psycopg2.connect(database=pgsql['database'],user=pgsql['user'],password=pgsql['password'],host=pgsql['host'],port=pgsql['port']) return conndef exportCsv(title,data): ''' 生成csv文件 :param title: 表头 :param data: 数据 :return: {'status':任务执行状况, 'fileName':文件名} ''' # 数据处理 for i in range(len(data)): dataList = list(data[i].values()) dataList.insert(0, i+1) data[i] = dataList fileName = '/Public/Temp/' + time.strftime('%Y%m%d%H%M',time.localtime(time.time())) + '_'+ str(int(round((time.time()) * 1000))) + ".csv" title = title.split(',') with open(fileName,"w",newline='',encoding='gb18030') as csvfile: writer = csv.writer(csvfile) # 先写入columns_name try: writer.writerow(title) writer.writerows(data) except: return {'status':False, 'msg':'文件写入失败!'} return {'status':True, 'fileName':hostName + fileName}def getCsvDate(sql): ''' 获取csv数据 :param sql: 获取csv数据的sql语句 :return:{'status':sql执行状况, 'data':数据} ''' conn = conPgsql() cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) try: cur.execute(sql) rows = cur.fetchall() except: cur.close() conn.close() return {'status':False, 'msg':'sql执行失败!'} conn.commit() cur.close() conn.close() return {'status':True, 'data':rows}def dealWork(): ''' 任务处理 :return: None ''' conn = conMysql() # 看有没有正在进行任务是否超过限定数,超过限定数就跳跳出 cursor = conn.cursor() sql = "select count(*) count from box_download_task where status='1';" cursor.execute(sql) re = cursor.fetchone() rowsNum = re['count'] if rowsNum > thread: return # 看有没有未开始的任务,有的话继续进行,没有则跳出 sql = "select count(*) count from box_download_task where status='0' and type<>'4' order by id asc;" cursor.execute(sql) re = cursor.fetchone() if re['count']==0: return else: # 根据降序选择最早加入的并且未处理的任务进行处理 sql = "select * from box_download_task where status='0' order by id asc;" cursor.execute(sql) re = cursor.fetchone() workId = re['id'] # 修改记录status为1,处理中,防止其他线程重复处理 sql = 'update box_download_task set status="%s" where id=%d;' % ('1',workId) cursor.execute(sql) conn.commit() sql = re['query'] title = re['title'] # 获取csv的数据 res = getCsvDate(sql) if res['status']: # 导出csv file = exportCsv(title, res['data']) # 写入文件是否成功,成功则修改status为2,失败则status为3 if file['status']: sql = 'update box_download_task set status="%s", filepath="%s" where id=%d;' % ( '2', file['fileName'], workId) cursor.execute(sql) conn.commit() else: sql = 'update box_download_task set status="%s", msg="%s" where id=%d;' % ('3', file['msg'], workId) cursor.execute(sql) conn.commit() else: sql = 'update box_download_task set status="%s", msg="%s" where id=%d;' % ('3', res['msg'], workId) cursor.execute(sql) conn.commit() return False conn.close() return Falseif __name__ == "__main__": # 引入多线程并行快速处理 while True: for i in range(thread): t = threading.Thread(target=dealWork, args=()) t.start() time.sleep(1) t.join() print('执行了') time.sleep(5) 小结: python引入多线程,并行处理多个任务,效率不言而喻; 引用队列思想,先进先出,一个一个处理,一个不落; python独立于php进程,不占用php运行资源; 比起以往点击下载后进行查询生成文件再下载,虽然步骤多了,但是用户体验提高了不止一点,属于优化(想想不该掉原先php的导出文件,数据大迟早要出问题) 完结!","categories":[{"name":"Python","slug":"Python","permalink":"https://blog.magicyou.cn/categories/Python/"}],"tags":[{"name":"Python","slug":"Python","permalink":"https://blog.magicyou.cn/tags/Python/"}]},{"title":"怎样一个服务器存放多个站点,分别绑定不同的域名","slug":"Linux-怎样一个服务器存放多个站点,分别绑定不同的域名","date":"2018-05-03T05:14:00.000Z","updated":"2018-05-03T05:14:12.000Z","comments":true,"path":"2018/05/03/Linux-怎样一个服务器存放多个站点,分别绑定不同的域名/","link":"","permalink":"https://blog.magicyou.cn/2018/05/03/Linux-%E6%80%8E%E6%A0%B7%E4%B8%80%E4%B8%AA%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%AD%98%E6%94%BE%E5%A4%9A%E4%B8%AA%E7%AB%99%E7%82%B9%EF%BC%8C%E5%88%86%E5%88%AB%E7%BB%91%E5%AE%9A%E4%B8%8D%E5%90%8C%E7%9A%84%E5%9F%9F%E5%90%8D/","excerpt":"作为富(qiong)有(bi)开发工程师,有一个平常拿来练手服务器再正常不过。你不要说你的本地集成环境wamp,mamp,phpstudy什么就能练手,像我们这样的会的很多很牛(cai)逼(ji)的web开发,开发微信,开发小程序,服务器是必须的。这就有个问题,怎样才能在一个服务器上放置多个站点并且互不影响呢。别急,听我娓娓道来。","text":"作为富(qiong)有(bi)开发工程师,有一个平常拿来练手服务器再正常不过。你不要说你的本地集成环境wamp,mamp,phpstudy什么就能练手,像我们这样的会的很多很牛(cai)逼(ji)的web开发,开发微信,开发小程序,服务器是必须的。这就有个问题,怎样才能在一个服务器上放置多个站点并且互不影响呢。别急,听我娓娓道来。 准备工作 1234云服务器:腾讯云1核1GB1M系统:centos7.2环境:已安装lnmp 1.5备案域名:xxx.cn 2.现在有两个站点,节省开支,当然要用二级域名,对应服务器上两个不同的站点目录 12movie.xxx.cn /home/wwwroot/movieweixin.xxx.cn /home/wwwroot/weixin 3.添加两个站点配置文件,在添加配置文件前先打开ngin.conf看看是否有这句话: 1include vhost/*.conf; 没有的话加上路径要准确 进入vhost,创建两个配置文件 12movie.xxx.cn vhost/movie.xxx.cn.confweixin.xxx.cn vhost/weixin.xxx.cn.conf 写入配置,如下 12345678910111213141516171819202122232425262728293031323334353637383940# movie.magicyou.cn.confserver{ listen 80; server_name movie.xxx.cn; # 域名 index index.html index.htm index.php; root /home/wwwroot/movie; # 网站目录完整路径 #error_page 404 /404.html; # Deny access to PHP files in specific directory #location ~ /(wp-content|uploads|wp-includes|images)/.*\\.php$ { deny all; } include enable-php.conf; location /nginx_status { stub_status on; access_log off; } location ~ .*\\.(gif|jpg|jpeg|png|bmp|swf)$ { expires 30d; } location ~ .*\\.(js|css)?$ { expires 12h; } location ~ /.well-known { allow all; } location ~ /\\. { deny all; } } 不会写不要紧,直接从默认配置文件里复制出来就行,改改就好 另一个配置文件和上面类似,改一下路径和域名就行。 4.最后一步,在hosts添加映射 1vim /etc/hosts 添加以下内容: 12你的公网IP movie.xxx.cn你的公网IP weixin.xxx.cn 最后重启一下服务器,记得启动lnmp。 5.最重要的一步,域名解析要加上,我的是阿里云买的域名,解析如下 tip如果最后还是不行,页面还是进不去,那么你就要检查一下你的网站目录的读写权限了 好了,到此结束!哪里不对,勿打脸","categories":[{"name":"Linux","slug":"Linux","permalink":"https://blog.magicyou.cn/categories/Linux/"}],"tags":[{"name":"Linux","slug":"Linux","permalink":"https://blog.magicyou.cn/tags/Linux/"}]},{"title":"mac版本Navicat连接远程mysql","slug":"other-mac版本Navicat连接远程mysql","date":"2018-05-02T05:12:45.000Z","updated":"2018-05-02T06:34:12.000Z","comments":true,"path":"2018/05/02/other-mac版本Navicat连接远程mysql/","link":"","permalink":"https://blog.magicyou.cn/2018/05/02/other-mac%E7%89%88%E6%9C%ACNavicat%E8%BF%9E%E6%8E%A5%E8%BF%9C%E7%A8%8Bmysql/","excerpt":"一步一步解决mac版本的Navicat连接远程mysql","text":"一步一步解决mac版本的Navicat连接远程mysql 云服务器:腾讯云1核1GB1Mbps 系统:centos7.2 已安装lnmp 1.5 这时mac上使用Navicat连接远程mysql是拒绝访问,没关系,来一步步搞一下 1.设置mysql允许远程连接数据库 使用“use mysql”命令,选择要使用的数据库,修改远程连接的基本信息,保存在mysql数据库中,因此使用mysql数据库。 使用“GRANT ALL PRIVILEGES ON . TO ‘root’@’%’ IDENTIFIED BY ‘root’ WITH GRANT OPTION;”命令可以更改远程连接的设置。 1mysql> use mysql;mysql> GRANT ALL PRIVILEGES ON . TO '数据库用户名(一般是root)'@'%' IDENTIFIED BY '数据库密码' WITH GRANT OPTION; 使用“flush privileges;”命令刷新刚才修改的权限,使其生效 1mysql> flush privileges; 12345678910mysql> use mysqlDatabase changedmysql> GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '*********' WITH GRANT OPTION;Query OK, 0 rows affected (0.00 sec)mysql> flush privileges;Query OK, 0 rows affected (0.00 sec)mysql> exitBye 2.开放3306端口 Centos 7使用firewalld代替了原来的iptables。下面记录如何使用firewalld开放Linux端口 12345678910111213141.systemctl start firewalld 开启防火墙2.firewall-cmd --zone=public --add-port=3306/tcp --permanent命令含义:--zone #作用域--add-port=80/tcp #添加端口,格式为:端口/通讯协议--permanent #永久生效,没有此参数重启后失效3.firewall-cmd --reload 3. 测试一下,还不行的,添加出入站规则 4.可能会出现下图状况 解决方案: 进入mysql 更改max_connection_errors的值,即提高允许的max_connection_errors的数量。 查看该属性设置为多大:命令:show global variables like ‘%max_connect_errors%’; 1234567mysql> show global variables like '%max_connect_errors%';+--------------------+-------+| Variable_name | Value |+--------------------+-------+| max_connect_errors | 100 |+--------------------+-------+1 row in set (0.00 sec) 然后修改该属性 123456789101112mysql> set global max_connect_errors=1000;Query OK, 0 rows affected (0.00 sec)mysql> show global variables like '%max_connect_errors%';+--------------------+-------+| Variable_name | Value |+--------------------+-------+| max_connect_errors | 1000 |+--------------------+-------+1 row in set (0.00 sec)mysql> exit 这样子设置后会发现还会出现一样的问题,永久解决问题的话就接着操作 进入mysql配置文件,设置max_connect_errors = 1000,重启mysql,完毕 1max_connect_errors = 1000","categories":[{"name":"MacOS","slug":"MacOS","permalink":"https://blog.magicyou.cn/categories/MacOS/"}],"tags":[{"name":"other","slug":"other","permalink":"https://blog.magicyou.cn/tags/other/"}]},{"title":"javascript常用方法记录","slug":"javascript-常用方法记录","date":"2018-04-10T05:34:12.000Z","updated":"2018-04-10T05:34:23.000Z","comments":true,"path":"2018/04/10/javascript-常用方法记录/","link":"","permalink":"https://blog.magicyou.cn/2018/04/10/javascript-%E5%B8%B8%E7%94%A8%E6%96%B9%E6%B3%95%E8%AE%B0%E5%BD%95/","excerpt":"记录一些javascript常用方法","text":"记录一些javascript常用方法 截取一定长度的字符串(前端展示为了美观,通常需要截取相同长度的字符串作为填充,要保证空间上长度基本一致,这段js用派上用场) 123456789101112131415161718192021222324252627/* * js截取字符串,中英文都能用 * @param len: 需要截取的长度 */String.prototype.gbslice = function(len) { var str_length = 0; var str_len = 0; str_cut = new String(); str_len = this.length; for (var i = 0; i < str_len; i++) { a = this.charAt(i); str_length++; if (escape(a).length > 4) { //中文字符的长度经编码之后大于4 str_length++; } str_cut = str_cut.concat(a); if (str_length >= len) { str_cut = str_cut.concat(".."); return str_cut; } } //如果给定字符串小于指定长度,则返回源字符串; if (str_length < len) { return this; }} echars图片下载(echars生成的图片上有自己带的下载按钮,如果需要在图片外做一个按钮下载相应的echars,这个方法就是你需要的) 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677// echars图片下载function myBrowser() { var userAgent = navigator.userAgent; //取得浏览器的userAgent字符串 var isOpera = userAgent.indexOf("OPR") > -1; if (isOpera) { return "Opera" }; //判断是否Opera浏览器 OPR/43.0.2442.991 if (userAgent.indexOf("Firefox") > -1) { return "FF"; } //判断是否Firefox浏览器 Firefox/51.0 if (userAgent.indexOf("Trident") > -1) { return "IE"; } //判断是否IE浏览器 Trident/7.0; rv:11.0 if (userAgent.indexOf("Edge") > -1) { return "Edge"; } //判断是否Edge浏览器 Edge/14.14393 if (userAgent.indexOf("Chrome") > -1) { return "Chrome"; }// Chrome/56.0.2924.87 if (userAgent.indexOf("Safari") > -1) { return "Safari"; } //判断是否Safari浏览}function base64Img2Blob(code){ var parts = code.split(';base64,'); var contentType = parts[0].split(':')[1]; var raw; if(window.atob){ raw = window.atob(parts[1]); var rawLength = raw.length; var uInt8Array = new Uint8Array(rawLength); for (var i = 0; i < rawLength; ++i) { uInt8Array[i] = raw.charCodeAt(i); } return new Blob([uInt8Array], {type: contentType}); } else { raw = BaseCode(parts[1]); }}function downloadFile(fileName, content) { var blob = base64Img2Blob(content); // 支持IE11 base64Img2Blob window.navigator.msSaveBlob(blob, fileName);}var Global_interDown = true;/* * echars保存为图片 * @param tag: 按钮id,例:"#btn_click" * @param mychart: echars对象 * @param image_name: 保存的图片名字 */function initSaveImg(tag, mychart, image_name) { $(tag).unbind(); $(tag).click(function() { var hasData = $(this).parents('.wrapper-echars').find('canvas').length == 0 ? false : true; if (!hasData) { return false; } setTimeout(function() { Global_interDown = true; }, 1000); if (!Global_interDown) { return false; } Global_interDown = false; var aTag = document.createElement("a"); var dataurl = mychart.getDataURL({ type: 'png' }); if (myBrowser() == "IE") { aTag.href = "#"; downloadFile(image_name + '.png', dataurl); } else { var MIME_TYPE = 'image/png'; aTag.href = dataurl; aTag.target = "_self"; aTag.download = image_name + '.png'; } document.body.appendChild(aTag); aTag.click(); document.body.removeChild(aTag); });} 日期格式化 12345678910111213141516// 日期格式化 var yesterDay = new Date(yesterdayTime).Format("yyyy-MM-dd");Date.prototype.Format = function (fmt) { //author: meizz var o = { "M+": this.getMonth() + 1, //月份 "d+": this.getDate(), //日 "h+": this.getHours(), //小时 "m+": this.getMinutes(), //分 "s+": this.getSeconds(), //秒 "q+": Math.floor((this.getMonth() + 3) / 3), //季度 "S": this.getMilliseconds() //毫秒 }; if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length)); for (var k in o) if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length))); return fmt;}; 日期排序 123456789101112//日期排序function sortDownDate(a, b) { return Date.parse(a) - Date.parse(b);}function sortUpDate(a, b) { return Date.parse(b) - Date.parse(a);}var arr = ['2018-02-02','2018-03-03','2017-03-03','2019-01-01'];arr.sort(sortDownDate)arr.sort(sortUpDate) 防抖节流 12345678910111213141516171819202122232425262728293031323334353637383940// 节流function throttle (fn, delay=500) { let prev = 0; return function () { let now = new Date(); let args = arguments; if (now - prev > delay) { fn.call(this, args) prev = now; } }}const input_search = document.getElementById('input_search');input_search.oninput = throttle(function() { console.log('*** 输入了内容'); console.log(input_search.value);}, 500)// 防抖function debounce (fn, delay=500) { let timer = null return function () { let context = this; let args = arguments; if (timer) { clearTimeout(timer); timer = null } timer = setTimeout(function(){ fn.call(context, args) }, delay) }}const btn_search = document.getElementById('btn_search');btn_search.onclick = debounce(function () { console.log('*** 点击了'); console.log(this);}, 500)","categories":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/categories/JavaScript/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/tags/JavaScript/"}]},{"title":"百度地图聚合折腾","slug":"javascript-百度地图聚合折腾","date":"2018-03-02T10:12:45.000Z","updated":"2018-03-02T10:12:45.000Z","comments":true,"path":"2018/03/02/javascript-百度地图聚合折腾/","link":"","permalink":"https://blog.magicyou.cn/2018/03/02/javascript-%E7%99%BE%E5%BA%A6%E5%9C%B0%E5%9B%BE%E8%81%9A%E5%90%88%E6%8A%98%E8%85%BE/","excerpt":"被客户爸爸折腾了好久,最后终于做出基本满意的“热力图”","text":"被客户爸爸折腾了好久,最后终于做出基本满意的“热力图” 起初做的热力图,但是来回反复测试百度地图热力图和echars热力图+百度地图,终究不满足客户爸爸,原因很简单某些地区放大后,为什么啥也没有了,为什么?为什么?mmmp不知当讲不当讲,热力图放大数据稀疏的地方当然不明显,遂开始改热力图各种配置,渐变色,透明度,无奈怎么也不满足。最后技术总监说干脆用某某项目的聚合算了,接着就进入了正题百度地图–聚合折腾之路。 百度地图的聚合在文档里不太好找,在百度地图开源库里才能找到 #####贴一下官方的简单说明|类 | 描述 || - | - ||BMapLib.MarkerClusterer(map, options)|MarkerClusterer| 方法 方法 返回值 描述 addMarker(marker) None 添加一个聚合的标记。 addMarkers(markers) None 添加要聚合的标记数组。 clearMarkers() None 从地图上彻底清除所有的标记 getClustersCount() Number 获取聚合的总数量。 getGridSize() Number 获取网格大小 getMap() Map 获取聚合的Map实例。 getMarkers() Array 获取所有的标记数组。 getMaxZoom() Number 获取聚合的最大缩放级别。 getMinClusterSize() Number 获取单个聚合的最小数量。 getStyles() Array 获取聚合的样式风格集合 isAverageCenter() Boolean 获取单个聚合的落脚点是否是聚合内所有标记的平均中心。 removeMarker(marker) Boolean 删除单个标记 removeMarkers(markers) Boolean 删除一组标记 setGridSize(size) None 设置网格大小 setMaxZoom(maxZoom) None 设置聚合的最大缩放级别 setMinClusterSize(size) None 设置单个聚合的最小数量。 setStyles(styles) None 设置聚合的样式风格集合 事实上要不是逼急一般人不会来这里看这些文档,百度地图的demo基本上就够用。由于数据量巨大,本项目的就有十三万数据,一股脑显示在地图上之后来回缩放,发现….卡,卡到没朋友,还有就是这十三万数据从服务器传送到前端耗时十多秒赶上网速不好就得挂在半路500错误码。无奈之下的继续优化,终于在苦苦搜索之后找到了网上大神的优化代码 (传送门),不得不说效率确实大增,但是,优化后依然扛不住13万的数据。将来的数据可不止13万。进一步优化。结合网上的仿照链家等地图找房例子(传送门),规划这样的方案:1.进行区域划分,省、市、区对应不同的地图等级显示不同等级的数据,这样起码就解决的全国数据时有十三万的尴尬,这样折合下来全国数据也就30多条,效率不言而喻的高!2.省、市、区级别之后进行自动聚合,就是百度地图真正的聚合。3.为了避免数据多余营销效率,每次请求只请求可视区域内的数据。 然后是甩锅到本渣渣这里,开始自我猜想:1.地图要做到拖动请求,缩放请求2.每次请求都要知道现在的地图缩放级别是多少,以便判断当前是省、市、区3.每次请求还要有地图可视的区域经纬度4.省市区要用地图的自定义覆盖物,Label是个不错的选择5.请求数据地图级别省市区的时候,数据要包含行政区域名称,人数,经纬度基本上是这样,然后开始搞 123456789获取地图缩放级别var zoom = map.getZoom(); // 直接返回数字1-20吧好像获取可视区域的经纬度getBounds()用法var bs = map.getBounds(); // 获取可视区域var bssw = bs.getSouthWest(); // bssw.lng, bssw.latvar bsne = bs.getNorthEast(); // bsne.lng, bsne.lat 添加文本标注labelLabel(content: String, opts: LabelOptions)创建一个文本标注实例。point参数指定了文本标注所在的地理位置|方法|返回值| 描述||setStyle(styles: Object)|none|设置文本标注样式,该样式将作用于文本标注的容器元素上。其中styles为JavaScript对象常量,比如: setStyle({ color : “red”, fontSize : “12px” }) 注意:如果css的属性名中包含连字符,需要将连字符去掉并将其后的字母进行大写处理,例如:背景色属性要写成:backgroundColor||setContent(content: String)|none|设置文本标注的内容。支持HTML||setPosition(position: Point)|none|设置文本标注坐标。仅当通过Map.addOverlay()方法添加的文本标注有效| 然后就是代码说事儿 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160var map = new BMap.Map("echars_heatmap", {minZoom:5});var BMapLib = window.BMapLib = BMapLib || {}; var Page = { init: function() { this.initMap(); this.echarsHeatmap(this.data.heatmapDatas); }, data:{ timerMapGetdata:null, zoom: 5, condition:{ zoom: 5, level: 1, }, markerClusterer: null, heatmapDatas: { centerPoint: null, points: null, lengthMax: 0 }, }, // 初始化地图触发事件 initMap:function(){ var _this = this; var point = new BMap.Point(116.403981, 39.91571); // 设置中心点坐标和地图级别 5全国 10省 map.centerAndZoom(point, 5); // 允许滚轮缩放 map.enableScrollWheelZoom(); // 获取地图的当前缩放级别 _this.data.zoom = map.getZoom(); // 地图缩放事件,每次缩放都要重新触发获取视野内新数据 map.addEventListener('zoomend', function(){ console.log('缩放事件:'); // 事件触发优化,防止短时间内多次触发事件频繁请求数据,造成服务器压力,同时也为了用户体验 clearTimeout(_this.data.timerMapGetdata); _this.data.timerMapGetdata = null; _this.data.timerMapGetdata = setTimeout(function() { _this.getHeatData(); }, 300); }); // 地图拖动事件,每次拖动地图,地图的可视区域都会变化,都要重新触发获取视野内新数据 map.addEventListener("moveend", function(){ console.log('拖动事件:'); // 事件触发优化,防止短时间内多次触发事件频繁请求数据,造成服务器压力,同时也为了用户体验 clearTimeout(_this.data.timerMapGetdata); _this.data.timerMapGetdata = null; _this.data.timerMapGetdata = setTimeout(function() { _this.getHeatData(); }, 300); }); }, getHeatData: function() { var _this = this; // layer弹窗插件,加载弹窗 layer.load(1, { shade: [0.1, '#fff'], }); //获取可视区域 var bs = map.getBounds(); var bssw = bs.getSouthWest(); var bsne = bs.getNorthEast(); // 获取地图缩放级别,并且划分请求级别 var zoom = map.getZoom(); var level = (zoom < 8) ? 1 : ((zoom >= 8 && zoom <= 11) ? 2 : ((zoom > 11 && zoom < 15) ? 3 : 4)); _this.data.level = level; _this.data.condition['level'] = level; _this.data.condition['left'] = bssw.lng; _this.data.condition['top'] = bssw.lat; _this.data.condition['right'] = bsne.lng; _this.data.condition['bottom'] = bsne.lat; $.ajax({ url: '', data: { condition: _this.data.condition }, type: 'POST', success: function(res) { layer.closeAll(); // 重新渲染地图 _this.data.heatmapDatas.points = res.data; _this.data.heatmapDatas.lengthMax = res.max; _this.echarsHeatmap(_this.data.heatmapDatas); } }); }, // 热力图 echarsHeatmap: function(obj) { var _this = this; var points = obj.points ? obj.points : []; _this.data.zoom = map.getZoom(); var zoom = Number(_this.data.zoom); var level = _this.data.level; // 清除覆盖物 map.clearOverlays(); if(_this.markerClusterer) { _this.markerClusterer.clearMarkers(); } // 1.当请求级别是4的时候,百度地图自动集合; // 2.当请求级别小于4,使用后台做聚合查询,前端label展示。 if (level != 4) { for (var i = 0; i < points.length; i++) { _this.addLabel(points[i]); } } else { var markers = []; var pt = null; for (var i = 0; i < points.length; i++) { pt = new BMap.Point(points[i].lng, points[i].lat); var maker = new BMap.Marker(pt); maker.setTitle(points[i].macs); markers.push(maker); } //最简单的用法,生成一个marker数组,然后调用markerClusterer类即可。 var tt1 = new Date().getTime(); _this.markerClusterer = new BMapLib.MarkerClusterer(map, {markers:markers}); var tt2 = new Date().getTime(); } }, // 添加覆盖物 addLabel: function(pointObj) { var zoom = Number(this.data.zoom); var point = new BMap.Point(pointObj.longitude, pointObj.latitude); var label = new BMap.Label(); label.setStyle({ maxWidth: 'none', color:"#fff", //颜色 fontSize:"12px", //字号 border:"0", //边 borderRadius: '50%', height:"60px", //高度 width:"60px", //宽 textAlign:"center", //文字水平居中显示 background: "#F99D32", cursor:"pointer", position: "absolute", left: "-60px", top: "-60px" }); var content = '<p style="text-align:center;margin-top:16px;">'+ pointObj.name +'</p>'; content += '<p style="text-align:center;">'+ pointObj.macs +'</p>'; label.setContent(content); label.setPosition(point); label.addEventListener("click", function() { //拖动事件 map.centerAndZoom(point, zoom+2) }); map.addOverlay(label); },}; 后台用的php,这里贴上sql说明一下问题就行 当请求级别是小于4的时候,sql如下: 1234567891011121314151617181920212223242526272829303132333435/*接受参数:conditioin:{ level: 1, left: 110.329288, right: 119.895882, top:31.356779, bottom: 36.253414}简要说明:当请求级别小于4的时候,后台数据要做聚合处理,从user表查询出所有人数,通过‘order by city’做分组聚合处理(根据请求级别 省1:state,市2:city,区3:country),然后关联address表(tf_address_region表存储中国所有的省市区对应的名字、经纬度,具体表结构和sql文件在最下边展示与下载)查询出每个地址对应的经纬度*/SELECT A.NAME, A.LEVEL, A.longitude, A.latitude, b.member_skey FROM tf_address_region A, ( SELECT city, /*省1:state,市2:city,区3:country*/ COUNT ( 1 ) member_skey FROM td_user WHERE ( AND ( latitude <> '' AND longitude IS NOT NULL ) AND ( latitude BETWEEN 31.356779 AND 36.253414 AND longitude BETWEEN 110.329288 AND 119.895882 ) ) GROUP BY city /*省1:state,市2:city,区3:country*/ ) b WHERE ( b.city = A.NAME ) /*省1:state,市2:city,区3:country*/ 当请求级别是4的时候,sql如下: 1234567891011121314151617181920212223242526/*接受参数:conditioin:{ level: 4, left: 113.622692 right: 113.697431 top:34.739694, bottom: 34.775282}简要说明:当请求级别等于4的时候,后台数据不要做聚合处理,直接从user表查询出所有人数,聚合工作直接给百度地图处理*/SELECT longitude lng, latitude lat, COUNT ( 1 ) member_skey FROM td_userWHERE ( AND ( latitude <> '' AND longitude IS NOT NULL ) AND ( latitude BETWEEN 34.739694 AND 34.775282 AND longitude BETWEEN 113.622692 AND 113.697431 ) ) GROUP BY latitude, longitude 效果图 附加address表的说明:表结构: 名称 类型 备注 id bigint(11) 表id name varchar(32) 地区名称 level tinyint(4) 地区等级 分省市县区 pid bigint(10) 父id longitude float 百度坐标,经度 latitude float 百度坐标,纬度 下载地址","categories":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/categories/JavaScript/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/tags/JavaScript/"}]},{"title":"PHP常用的方法集锦(持续更新)","slug":"php-常用方法集锦(持续更新)","date":"2018-02-03T09:23:00.000Z","updated":"2018-02-03T07:23:00.000Z","comments":true,"path":"2018/02/03/php-常用方法集锦(持续更新)/","link":"","permalink":"https://blog.magicyou.cn/2018/02/03/php-%E5%B8%B8%E7%94%A8%E6%96%B9%E6%B3%95%E9%9B%86%E9%94%A6%EF%BC%88%E6%8C%81%E7%BB%AD%E6%9B%B4%E6%96%B0%EF%BC%89/","excerpt":"开发中一个项目又一个项目,积累一下常用方法,方便查阅","text":"开发中一个项目又一个项目,积累一下常用方法,方便查阅 获取头部信息 123456789101112131415161718192021222324252627282930/** * 获取头部信息 * @param string $name [参数名字] * @return string [返回对应参数的值] */function getHeader($name=''){ $header = []; if (function_exists('apache_request_headers') && $result = apache_request_headers()) { $header = $result; } else { if($name = 'Authorization'){ $name = 'authorization'; } $server = $_SERVER; foreach ($server as $key => $val) { if (0 === strpos($key, 'HTTP_')) { $key = str_replace('_', '-', strtolower(substr($key, 5))); $header[$key] = $val; } } if (isset($server['CONTENT_TYPE'])) { $header['content-type'] = $server['CONTENT_TYPE']; } if (isset($server['CONTENT_LENGTH'])) { $header['content-length'] = $server['CONTENT_LENGTH']; } } return $header[$name]?$header[$name]:null;} 二维数组排序 12345678910111213141516171819202122/** * 二维数组排序 * @param string $arr 二维数组 * @param string $keys 排序键值 * @param string $type 排序方式 asc正序 desc倒*/function array_sort($arr, $keys, $type = 'asc'){ $keysvalue = $new_array = array(); foreach ($arr as $k => $v) { $keysvalue[$k] = $v[$keys]; } if ($type == 'asc') { asort($keysvalue); } else { arsort($keysvalue); } reset($keysvalue); foreach ($keysvalue as $k => $v) { $new_array[$k] = $arr[$k]; } return $new_array;} 获取用户token 1234567891011/*** 获取用户token*/function get_user_token($user_id) { $token = new \\Org\\Gamegos\\jwt\\src\\Token(); $token->setClaim('user_id', $user_id); // alternatively you can use $token->setSubject('[email protected]') method $token->setClaim('exp', time() + C('jwt_time')); $encoder = new \\Org\\Gamegos\\jwt\\src\\Encoder(); $encoder->encode($token, C('jwt_key'), C('jwt_alg')); return $token->getJWT();} 数组随机指定个数抽取元素 1234567891011121314/** * 数组随机指定个数抽取元素 * @param string $arr 数组 * @param string $keys 指定个数 */function array_rand_to($arr, $num){ $ran_arr = array_rand($arr, $num); $arr_new = []; for ($i = 0; $i < count($ran_arr); $i++) { array_push($arr_new, $arr[$ran_arr[$i]]); } return $arr_new;} php验证身份证号码是否正确函数 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263/* * php验证身份证号码是否正确函数 * @param string $id 身份证号 */function is_idcard($id){ $id = strtoupper($id); $regx = "/(^\\d{15}$)|(^\\d{17}([0-9]|X)$)/"; $arr_split = array(); if(!preg_match($regx, $id)) { return FALSE; } if(15==strlen($id)) //检查15位 { $regx = "/^(\\d{6})+(\\d{2})+(\\d{2})+(\\d{2})+(\\d{3})$/"; @preg_match($regx, $id, $arr_split); //检查生日日期是否正确 $dtm_birth = "19".$arr_split[2] . '/' . $arr_split[3]. '/' .$arr_split[4]; if(!strtotime($dtm_birth)) { return FALSE; } else { return TRUE; } } else //检查18位 { $regx = "/^(\\d{6})+(\\d{4})+(\\d{2})+(\\d{2})+(\\d{3})([0-9]|X)$/"; @preg_match($regx, $id, $arr_split); $dtm_birth = $arr_split[2] . '/' . $arr_split[3]. '/' .$arr_split[4]; if(!strtotime($dtm_birth)) //检查生日日期是否正确 { return FALSE; } else { //检验18位身份证的校验码是否正确。 //校验位按照ISO 7064:1983.MOD 11-2的规定生成,X可以认为是数字10。 $arr_int = array(7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2); $arr_ch = array('1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'); $sign = 0; for ( $i = 0; $i < 17; $i++ ) { $b = (int) $id{$i}; $w = $arr_int[$i]; $sign += $b * $w; } $n = $sign % 11; $val_num = $arr_ch[$n]; if ($val_num != substr($id,17, 1)) { return FALSE; } //phpfensi.com else { return TRUE; } } }} 获取当前页面完整URL地址 1234567891011/** * 获取当前页面完整URL地址 */function get_url(){ //$sys_protocal = isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == '443' ? 'https://' : 'http://'; $php_self = $_SERVER['PHP_SELF'] ? $_SERVER['PHP_SELF'] : $_SERVER['SCRIPT_NAME']; $path_info = isset($_SERVER['PATH_INFO']) ? $_SERVER['PATH_INFO'] : ''; $relate_url = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : $php_self . (isset($_SERVER['QUERY_STRING']) ? '?' . $_SERVER['QUERY_STRING'] : $path_info); return $relate_url; //return $sys_protocal . (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : '') . $relate_url;} 请求接口两种方式 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950/** * GET方式请求接口 * @param [String] $url [curl地址] * @return [返回结果数据] */function curl_get($url){ // 初始化 $curl = curl_init(); // 设置抓取的url curl_setopt($curl, CURLOPT_URL, $url); // 设置头文件的信息作为数据流输出 curl_setopt($curl, CURLOPT_HEADER, 0); // 设置获取的信息以文件流的形式返回,而不是直接输出。 curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); // 执行命令 $data = curl_exec($curl); // 关闭URL请求 curl_close($curl); // 返回获得的数据 return $data;}/** * POST方式请求接口 * @param [String] $url [curl地址] * @param [array] $param [curl参数数组] * @return [返回结果数据] */function curl_post($url, $param){ // 初始化 $curl = curl_init(); // 设置抓取的url curl_setopt($curl, CURLOPT_URL, $url); // 设置头文件的信息作为数据流输出 curl_setopt($curl, CURLOPT_HEADER, 1); // 设置获取的信息以文件流的形式返回,而不是直接输出。 curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); // 设置post方式提交 curl_setopt($curl, CURLOPT_POST, 1); // 设置post数据 curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($param)); // 执行命令 $data = curl_exec($curl); // 关闭URL请求 curl_close($curl); // 返回获得的数据 return $data;} 获取图片的Base64编码(不支持url) 123456789101112131415161718192021222324252627282930313233343536/** * 获取图片的Base64编码(不支持url) * @param $img_file [传入本地图片地址] * @return string */function img_to_base64($file) { $code = ''; if (file_exists($file)) { $info = getimagesize($file); // 取得图片的大小,类型等 //echo '<pre>' . print_r($info, true) . '</pre><br>'; $fp = fopen($file, "r"); // 图片是否可读权限 if ($fp) { $type = ''; $size = filesize($file); $content = fread($fp, $size); $encode = chunk_split(base64_encode($content)); // base64编码 switch ($info[2]) { //判读图片类型 case 1: $type = "gif"; break; case 2: $type = "jpg"; break; case 3: $type = "png"; break; } $code = 'data:image/' . $type . ';base64,' . $encode; //合成图片的base64编码 } fclose($fp); } return $code; //返回图片的base64}","categories":[{"name":"PHP","slug":"PHP","permalink":"https://blog.magicyou.cn/categories/PHP/"}],"tags":[{"name":"PHP","slug":"PHP","permalink":"https://blog.magicyou.cn/tags/PHP/"}]},{"title":"简单粗暴的使用rem做单位开发移动端","slug":"javascript-使用rem做单位开发移动端","date":"2018-01-23T12:12:23.000Z","updated":"2018-01-23T12:12:23.000Z","comments":true,"path":"2018/01/23/javascript-使用rem做单位开发移动端/","link":"","permalink":"https://blog.magicyou.cn/2018/01/23/javascript-%E4%BD%BF%E7%94%A8rem%E5%81%9A%E5%8D%95%E4%BD%8D%E5%BC%80%E5%8F%91%E7%A7%BB%E5%8A%A8%E7%AB%AF/","excerpt":"rem开发移动端再适合不过,省时省心省力","text":"rem开发移动端再适合不过,省时省心省力 123456789101112(function(doc,win){ var docEl=doc.documentElement,//html resizeEvt='orientationchange' in window?'orientationchange':'resize', recalc=function(){ var clientWidth=docEl.clientWidth; if(!clientWidth)return; docEl.style.fontSize=20/375*clientWidth+"px"; }; if(!doc.addEventListener)return; win.addEventListener(resizeEvt,recalc,false); doc.addEventListener('DOMContentLoaded',recalc,false); })(document,window); 123456@charset "UTF-8";$rem:20;$color:#fa0;@function r($px){ @return $px/$rem+rem;}","categories":[{"name":"前端","slug":"前端","permalink":"https://blog.magicyou.cn/categories/%E5%89%8D%E7%AB%AF/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/tags/JavaScript/"},{"name":"HTML5","slug":"HTML5","permalink":"https://blog.magicyou.cn/tags/HTML5/"}]},{"title":"centos7.2安装redis","slug":"Linux-centos7.2安装redis","date":"2018-01-03T10:23:00.000Z","updated":"2018-01-03T10:23:00.000Z","comments":true,"path":"2018/01/03/Linux-centos7.2安装redis/","link":"","permalink":"https://blog.magicyou.cn/2018/01/03/Linux-centos7.2%E5%AE%89%E8%A3%85redis/","excerpt":"简单记录redis安装,以及相关端口的开放","text":"简单记录redis安装,以及相关端口的开放 一、安装redis1234567891011121314151617181920212223242526271、检查是否有redis yum 源yum install redis2、下载fedora的epel仓库yum install epel-release3、安装redis数据库yum install redis4、安装完毕后,使用下面的命令启动redis服务# 启动redisservice redis start# 停止redisservice redis stop# 查看redis运行状态service redis status# 查看redis进程ps -ef | grep redis5、设置redis为开机自动启动chkconfig redis on6、进入redis服务# 进入本机redisredis-cli 二、修改redis默认端口和密码1、打开配置文件 1vi /etc/redis.conf 2、修改默认端口,查找 port 6379 修改为相应端口即可 3、修改默认密码,查找 requirepass foobared 将 foobared 修改为你的密码 4、使用配置文件启动 redis 1redis-server /etc/redis.conf & 5、使用端口登录 1redis-cli -h 127.0.0.1 -p 6666 6、此时再输入命令则会报错 7、输入刚才输入的密码 1auth 123456 8、停止redis 命令方式关闭redis12redis-cli -h 127.0.0.1 -p 6179shutdown 进程号杀掉redis12ps -ef | grep rediskill -9 XXX 三、开放端口CentOS7安装iptables防火墙CentOS7默认的防火墙不是iptables,而是firewalle. 1、安装iptable iptable-service 123456789#先检查是否安装了iptablesservice iptables status#安装iptablesyum install -y iptables#升级iptablesyum update iptables #安装iptables-servicesyum install iptables-services 2、禁用/停止自带的firewalld服务 1234#停止firewalld服务systemctl stop firewalld#禁用firewalld服务systemctl mask firewalld 3、设置现有规则 123456789101112131415161718192021222324252627282930313233#查看iptables现有规则iptables -L -n#先允许所有,不然有可能会杯具iptables -P INPUT ACCEPT#清空所有默认规则iptables -F#清空所有自定义规则iptables -X#所有计数器归0iptables -Z#允许来自于lo接口的数据包(本地访问)iptables -A INPUT -i lo -j ACCEPT#开放22端口iptables -A INPUT -p tcp --dport 22 -j ACCEPT#开放21端口(FTP)iptables -A INPUT -p tcp --dport 21 -j ACCEPT#开放80端口(HTTP)iptables -A INPUT -p tcp --dport 80 -j ACCEPT#开放443端口(HTTPS)iptables -A INPUT -p tcp --dport 443 -j ACCEPT#开放6666端口iptables -A INPUT -p tcp --dport 6666 -j ACCEPT#允许pingiptables -A INPUT -p icmp --icmp-type 8 -j ACCEPT#允许接受本机请求之后的返回数据 RELATED,是为FTP设置的iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT#其他入站一律丢弃iptables -P INPUT DROP#所有出站一律绿灯iptables -P OUTPUT ACCEPT#所有转发一律丢弃iptables -P FORWARD DROP 4、保存规则设定 12#保存上述规则service iptables save 5、开启iptables服务 1234567#注册iptables服务#相当于以前的chkconfig iptables onsystemctl enable iptables.service#开启服务systemctl start iptables.service#查看状态systemctl status iptables.service","categories":[{"name":"Linux","slug":"Linux","permalink":"https://blog.magicyou.cn/categories/Linux/"}],"tags":[{"name":"Linux","slug":"Linux","permalink":"https://blog.magicyou.cn/tags/Linux/"}]},{"title":"碰到的VUE的几个小知识补充","slug":"vue-碰到的VUE的几个小知识补充","date":"2017-11-08T09:12:45.000Z","updated":"2017-06-20T09:12:45.000Z","comments":true,"path":"2017/11/08/vue-碰到的VUE的几个小知识补充/","link":"","permalink":"https://blog.magicyou.cn/2017/11/08/vue-%E7%A2%B0%E5%88%B0%E7%9A%84VUE%E7%9A%84%E5%87%A0%E4%B8%AA%E5%B0%8F%E7%9F%A5%E8%AF%86%E8%A1%A5%E5%85%85/","excerpt":"学过angular的皮毛,后来公司项目需要,搞了VUE,上手还是挺快的。开发中遇到几个小知识,补充一下","text":"学过angular的皮毛,后来公司项目需要,搞了VUE,上手还是挺快的。开发中遇到几个小知识,补充一下 解决vue路由本页面参数变化页面不刷新官方叫“响应路由参数的变化” 1234567watch: { // 路由监听 '$route' (to, from) { // 对路由变化作出响应... console.log('url变化了'); }} vue的两种url传参方式方法一1<router-link :to="{path:'/name',query:{key:value,key2:value2}}"> </router-link> 跳转到目标页面后,观察页面地址栏的url,你会发现神奇的有了‘get’参数“#/name?key=value&key2=value2”,获取参数很简单 12var key = this.$route.query.key;var key2 = this.$route.query.key2; 优点:使用灵活、方便 方法二这个方法需要在路由里面直接定义,如下: 12345678export default new Router({ routes: [ { path: '/demo/:key/:key2', component: demo, }, ]}) 带冒号的就是要传的参数名,目标页面获取参数的方法如下: 12var key = this.$route.params.key;var key2 = this.$route.params.key2; 两个相比较两个比较下来,我更新喜欢使用第一种方法,使用起来更灵活,参数的多少并没有限制,但是要考虑浏览器对URL长度的限制;第二个方法过于严谨,不仅路由需要定义,而且,使用的时候少一个参数就会找不到页面,判定路由不存在,项目中间需求改了多加个参数还得改路由。 vue路由使用的几个小知识vue的路由很好玩,可以像原生js一样传参,跳转,后退,刷新等等。(事实上,vue里面用原生js也是ok的) 1234567891011121.跳转到name页面this.$router.push('/name');2.刷新本页面this.$router.go();3.后退一个页面this.$router.go(-1);4.后退两个页面this.$router.go(-2);5.据说能退n步(回头试试,捂脸)this.$router.go(n);6.替换当前页面,是替换,替换!不会给浏览器留下‘history’this.$router.replace('/demo'); vue的路由导航守卫1234const router = new VueRouter({ ... })router.beforeEach((to, from, next) => { // ...}) 有什么用呢?举个栗子场景一:做H5的app时,需要限制页面的后退,不能登录后还能后退到登录界面;不能退出登录了,点击后退还能进入登录后的界面,(这个app是有账号才能进的);场景二:部分页面需要登陆才能进的,比如商城项目,购物券、优惠券、购物卡、我的订单、我的积分等等,都是登录才有的信息; 123456789101112131415# main.js #router.beforeEach((to, from, next) => { // console.log('路由跳转'); let allowVisit = true; // 给个默认值true if (to.meta.allowVisit !== undefined) { allowVisit = to.meta.allowVisit; } // console.log('allowVisit:', allowVisit); var token = window.localStorage.getItem('token'); if (!allowVisit && !token) { next({ path: '/login' }) } else { next(); }}) 上面的代码就实现了场景二的需求,每次路由跳转都会判断是否已经登录,如果是特定的页面并且没有登陆,就回到登录界面。当然,我的路由在定义的时候页做了些手脚,如下: 123456789export default new Router({ routes: [ { path: '/placeOrder', component: placeOrder, meta: {allowVisit: false} }, ]}) 多了个‘meta’参数,用来判断是否是必须登录才能访问的界面","categories":[{"name":"Vue","slug":"Vue","permalink":"https://blog.magicyou.cn/categories/Vue/"}],"tags":[{"name":"Vue","slug":"Vue","permalink":"https://blog.magicyou.cn/tags/Vue/"},{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/tags/JavaScript/"}]},{"title":"python之字符串篇","slug":"Python-之字符串篇","date":"2017-10-08T12:12:45.000Z","updated":"2017-10-09T12:40:56.000Z","comments":true,"path":"2017/10/08/Python-之字符串篇/","link":"","permalink":"https://blog.magicyou.cn/2017/10/08/Python-%E4%B9%8B%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%AF%87/","excerpt":"python自学之路","text":"python自学之路 capitaliz() 首字母大写 123>>> str = 'magicyou'>>> print(str.capitalize())Magicyou count() 统计指定元素在字符串中出现次数 123>>> str = 'my blog \\'name is magicyou'>>> print(str.count('m'))3 center() 以指定的字符占位,返回制定长度的字符串,原字符串居中 123>>> str = 'my blog \\'name is magicyou'>>> print(str.center(50,'-'))------------my blog 'name is magicyou------------- endswith() 判断是否以某个字符串结尾,返回布尔值 12345>>> str = 'my blog \\'name is magicyou'>>> print(str.endswith('you'))True>>> print(str.endswith('me'))False expandtabs() 将字符串中的tab替换成指定长度的空格 1234>>> str = 'magic\\tyou'>>> print(str.expandtabs(tabsize=30))magic you>>> find() 查找某字符在已知字符串中的位置 123>>> str = 'my blog \\'name is magicyou'>>> print(str.find('name'))9 format() 字符串格式化 12345678910111213141516171819202122232425262728293031# 1、使用位置参数>>> str = 'my name is {},age {}'>>> print(str.format('magicYou',23))my name is magicYou,age 23# 2、使用关键字参数>>> str = 'my name is {name},age {age}'>>> print(str.format(name='magicYou',age=23))my name is magicYou,age 23# 3、填充与格式化:[填充字符][对齐方式 <^>][宽度]>>> '{0:*>10}'.format('abc')'*******abc'>>> '{0:*<10}'.format('abc')'abc*******'>>> '{0:*^10}'.format('abc')'***abc****'# 4、精度与进制>>> '{0:.2f}'.format(0.66666)'0.67'>>> '{0:b}'.format(10)'1010'>>> '{0:o}'.format(10)'12'>>> '{0:x}'.format(10)'a'>>> '{:,}'.format(123456789)'123,456,789'# 5、使用索引>>> info = ['magicYou',23]>>> 'name is {0[0]} age is {0[1]}'.format(info)'name is magicYou age is 23' format_map() 格式化字符串 1234>>> hash = {'name':'magicYou','age':23}>>> str = 'i am {name}, i am {age}'>>> print(str.format_map(hash))i am magicYou, i am 23 isalnum() 检测字符串是否由字母或数字组成 123456789>>> str = 'magicYou'>>> print(str.isalnum())True>>> str = '56ppp'>>> print(str.isalnum())True>>> str = '*&^'>>> print(str.isalnum())False isalpha() 检测字符串是否只有字母 123456>>> str = 'magicYou'>>> print(str.isalpha())True>>> str = 'magicYou4'>>> print(str.isalpha())False isdecimal() 检查字符串是否只包含十进制字符。这种方法只存在于unicode对象注意:定义一个十进制字符串,只需要在字符串前添加 ‘u’ 前缀即可 123456>>> str = u'123456'>>> print(str.isdecimal())True>>> str = u'123456qq'>>> print(str.isdecimal())False isdigit() 检测字符串是否只由数字组成。 123456>>> str = '123456'>>> print(str.isdigit())True>>> str = '123456qq'>>> print(str.isdigit())False isidentifier() 检测是否是一个合法的标识符注意:合法的标识符就是符合变量名的就是合法标识符(只能以字母或下划线开头) 123456789>>> str = '123456'>>> print(str.isidentifier())False>>> str = 'name123'>>> print(str.isidentifier())True>>> str = '_123456'>>> print(str.isidentifier())True isnumeric() 检测是否为全为数字(小数也不算) 123456>>> str = '123456'>>> print(str.isnumeric())True>>> str = '1234.56'>>> print(str.isnumeric())False isspace() 检测是否只有空格组成 123456>>> str = '123 456'>>> print(str.isspace())False>>> str = ' '>>> print(str.isspace())True istitle() 检测字符串中所有的单词拼写首字母是否为大写,且其他字母为小写 123456789>>> str = 'My Name Is MagicYou'>>> print(str.istitle())False>>> str = 'My Name Is Magicyou'>>> print(str.istitle())True>>> str = 'My name is magicYou'>>> print(str.istitle())False isupper() 检测是否全市大写字母 123456>>> str = 'MAGICYOU'>>> print(str.isupper())True>>> str = 'MagicYou'>>> print(str.isupper())False join() 将字符串、元组、列表中的元素以指定的字符(分隔符)连接生成一个新的字符串 123456>>> print('-'.join(['m','a','g','i','c']))m-a-g-i-c>>> print('-'.join(('m','a','g','i','c')))m-a-g-i-c>>> print('-'.join('magic'))m-a-g-i-c ljust() 返回一个原字符串左对齐,并使用空格填充至指定长度的新字符串。如果指定的长度小于原字符串的长度则返回原字符串rjust() 返回一个原字符串右对齐,并使用空格填充至指定长度的新字符串。如果指定的长度小于原字符串的长度则返回原字符串 12345>>> str = 'magic'>>> print(str.ljust(50,'*'))magic*********************************************>>> print(str.rjust(50,'*'))*********************************************magic lower() 转换字符串中所有大写字符为小写 123>>> str = 'MAGICYOU'>>> print(str.lower())magicyou upper() 转换字符串中所有小写字符为大写 123>>> str = 'magic'>>> print(str.upper())MAGIC lstrip() 截掉字符串左边的空格或指定字符。 123456>>> str = ' magic'>>> print(str.lstrip())magic>>> str = 'PPmagic'>>> print(str.lstrip('PP'))magic strip() 移除字符串头尾指定的字符(默认为空格) 123456>>> str = ' magic '>>> print(str.strip())magic>>> str = 'PPmagicPP'>>> print(str.strip('PP'))magic maketrans(str1,str2) 创建字符映射的转换表,对于接受两个参数的最简单的调用方式,第一个参数是字符串,表示需要转换的字符,第二个参数也是字符串表示转换的目标 123>>> p = str.maketrans('ace','135')>>> print('abcdefghi'.translate(p))1b3d5fghi replace(str1, str2, [max]) 替换字符串中的str1为str2,最多不超过max次 12345>>> str = 'i am magicYou'>>> print(str.replace('m', 'M'))i aM MagicYou>>> print(str.replace('m', 'M', 1))i aM magicYou find(str, start, end) 检测字符串中是否包含子字符串 str ,如果指定 beg(开始) 和 end(结束) 范围,则检查是否包含在指定范围内,如果包含子字符串返回开始的索引值,否则返回-1 1234567>>> str = 'i am magicYou'>>> str.find('m')3>>> str.find('m', 3)3>>> str.find('m', 8, 10)-1 rfind() 返回字符串最后一次出现的位置(从右向左查询),如果没有匹配项则返回-1 123456789>>> str = 'i am magicYou'>>> str.rfind('m')5>>> str.rfind('m', 6, 8)-1>>> str.rfind('m', 5)5>>> str.rfind('m', 4)5 split(str, [num]) 通过指定分隔符对字符串进行切片,返回列表,如果参数num 有指定值,则仅分隔 num 个子字符串 1234567>>> str = 'i am magicYou'>>> str.split(' ')['i', 'am', 'magicYou']>>> str.split(' ', 2)['i', 'am', 'magicYou']>>> str.split(' ', 1)['i', 'am magicYou'] splitlines() 根据换行符(‘\\r’, ‘\\r\\n’, \\n’)分割成列表 12>>> print('1+2\\n3+5'.splitlines())['1+2', '3+5'] swapcase() 将字符串中的大写换小写,小写换大写 123>>> str = 'i am magicYou'>>> str.swapcase()'I AM MAGICyOU' title() 将字符串中的每个单词的首字母转换为大写 123>>> str = 'i am magicYou'>>> str.title()'I Am Magicyou' zfill() 方法返回指定长度的字符串,原字符串右对齐,前面填充0 12>>> str.zfill(50)'000000000000000000000000000000000000000000magicYou' 此外,字符串也可以切片(和列表类似) 1234567>>> str = 'magicYou'>>> print(str[2:])gicYou>>> print(str[2:3])g>>> print(str[:6])magicY","categories":[{"name":"Python","slug":"Python","permalink":"https://blog.magicyou.cn/categories/Python/"}],"tags":[{"name":"Python","slug":"Python","permalink":"https://blog.magicyou.cn/tags/Python/"}]},{"title":"jQ的链式操作是怎么实现的","slug":"javascript-jQ的链式操作是怎么实现的","date":"2017-08-14T04:13:02.000Z","updated":"2017-08-15T04:13:04.000Z","comments":true,"path":"2017/08/14/javascript-jQ的链式操作是怎么实现的/","link":"","permalink":"https://blog.magicyou.cn/2017/08/14/javascript-jQ%E7%9A%84%E9%93%BE%E5%BC%8F%E6%93%8D%E4%BD%9C%E6%98%AF%E6%80%8E%E4%B9%88%E5%AE%9E%E7%8E%B0%E7%9A%84/","excerpt":"天天用jQuery,“$(‘id’).find(‘span’).parents(‘.box-wrapper’).html(‘test’)” 链式操作如此方便是怎么实现的?","text":"天天用jQuery,“$(‘id’).find(‘span’).parents(‘.box-wrapper’).html(‘test’)” 链式操作如此方便是怎么实现的? 1. 实现原理:这里使用原生js来模拟这样的效果: 123456789101112131415161718let fun = { fun1: function() { console.log("fun1"); return this; }, fun2: function() { console.log("fun2"); return this; }, fun3: function() { console.log("fun3"); return this; }}fun.fun1().fun2().fun3(); 这样就可以连续的输出字符串的fun1、fun2、fun3 了,原因是在每个方法后面加了一个return this。 如果没有加上return this语句的话,那么执行完一个函数之后,会默认返回undefined,这个是js内部自己隐式添加的。返回undefined的时候,再调用另一个方法肯定就会报错,因为undefined是没有方法的。 2. 为什么要返回this?因为在一个对象里面,this指向的是对象本身,而我们连续调用方法的时候,这些方法都是在对象内部定义的,所以this是可以访问到这些方法 例子: 1234567891011121314151617181920212223function jQ(selector){ this.elem = document.querySelectorAll(selector)[0];}jQ.prototype = { html: function(strHtml) { let elem = this.elem; elem.innerHTML = strHtml; return this; }, on: function(type, func) { let elem = this.elem; elem.addEventListener(type, func) return this }};window.$ = function(selector) { return new jQ(selector);}console.log($('#test_demo'));$('#test_demo').html('<a>111</a>').on('click', function() { console.log('click');})","categories":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/categories/JavaScript/"}],"tags":[{"name":"jQuery","slug":"jQuery","permalink":"https://blog.magicyou.cn/tags/jQuery/"}]},{"title":"密码强度检测","slug":"javascript-密码强度检测","date":"2017-06-12T12:12:45.000Z","updated":"2017-06-20T14:22:21.000Z","comments":true,"path":"2017/06/12/javascript-密码强度检测/","link":"","permalink":"https://blog.magicyou.cn/2017/06/12/javascript-%E5%AF%86%E7%A0%81%E5%BC%BA%E5%BA%A6%E6%A3%80%E6%B5%8B/","excerpt":"自己动手写一个密码强度测试,算是自用","text":"自己动手写一个密码强度测试,算是自用 贴代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104<style> *{ padding:0; margin:0; } .wrapper{ width:200px; } .wrapper input{ width:100%; } .color-hint{ display: flex; list-style: none; margin-top:10px; } .color-hint li{ display: inline-block; width:33%; height:10px; border-radius: 5px; }</style><div class="wrapper"> <input type="text" oninput="pwCheck.init(this.value)"> <ul class="color-hint" id="color_hint"> <li style="background-color: #ccc"></li> <li style="background-color: #ccc"></li> <li style="background-color: #ccc"></li> </ul></div><script>var pwCheck = { init: function(str) { this.str = str; this.checkStrong(); }, str: '', // 检测字符是什么类型 CharMode: function(iN) { if (iN >= 48 && iN <= 57) //数字 return 1; if (iN >= 65 && iN <= 90) //大写字母 return 2; if (iN >= 97 && iN <= 122) //小写 return 4; else return 8; //特殊字符 }, // 检测有多少种类型字符 countType: function(str) { var arrType = []; if (str.length == 0) { return 0; } if (str.length < 6) { return 1; } for (var i = 0; i < str.length; i++) { if ( arrType.indexOf( this.CharMode(str.charCodeAt(i)) ) == -1 ) { arrType.push( this.CharMode(str.charCodeAt(i)) ); } } return arrType.length; }, // 显示不同的颜色 checkStrong: function() { var _this = this; var str = _this.str; var S_level = _this.countType(str); H_color = "#cccccc"; R_color = "#ff4500"; O_color = "#ff7c00"; G_color = "#92ec00"; switch (S_level) { case 0: color_1 = H_color; color_2 = H_color; color_3 = H_color; break; case 1: color_1 = R_color; color_2 = H_color; color_3 = H_color; break; case 2: color_1 = R_color; color_2 = O_color; color_3 = H_color; break; default: color_1 = R_color; color_2 = O_color; color_3 = G_color; break; } var node = document.getElementById('color_hint').getElementsByTagName('li'); node[0].style.backgroundColor = color_1; node[1].style.backgroundColor = color_2; node[2].style.backgroundColor = color_3; }};</script> 补充知识 charCodeAt()用法12345定义和用法charCodeAt() 方法可返回指定位置的字符的 Unicode 编码。这个返回值是 0 - 65535 之间的整数。语法stringObject.charCodeAt(index)// 参数index: 必需。表示字符串中某个位置的数字,即字符在字符串中的下标。 indexOf()用法1234567定义和用法indexOf() 方法可返回某个指定的字符串值在字符串中首次出现的位置。语法stringObject.indexOf(searchvalue,fromindex)// 参数 searchvalue: 必需。规定需检索的字符串值。// 参数 fromindex: 可选的整数参数。规定在字符串中开始检索的位置。它的合法取值是 0 到 stringObject.length - 1。如省略该参数,则将从字符串的首字符开始检索。","categories":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/categories/JavaScript/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/tags/JavaScript/"}]},{"title":"常见的 web 攻击方式有哪些","slug":"前端-常见的 web 攻击方式","date":"2017-04-14T04:13:02.000Z","updated":"2017-06-14T04:13:04.000Z","comments":true,"path":"2017/04/14/前端-常见的 web 攻击方式/","link":"","permalink":"https://blog.magicyou.cn/2017/04/14/%E5%89%8D%E7%AB%AF-%E5%B8%B8%E8%A7%81%E7%9A%84%20web%20%E6%94%BB%E5%87%BB%E6%96%B9%E5%BC%8F/","excerpt":"常见的 web 攻击方式有哪些,简述原理?如何预防?(百度后收录,侵删)","text":"常见的 web 攻击方式有哪些,简述原理?如何预防?(百度后收录,侵删) SQL注入上学的时候就知道有一个“SQL注入”的攻击方式。例如做一个系统的登录界面,输入用户名和密码,提交之后,后端直接拿到数据就拼接 SQL 语句去查询数据库。如果在输入时进行了恶意的 SQL 拼装,那么最后生成的 SQL 就会有问题。但是现在稍微大型的一点系统,都不会这么做,从提交登录信息到最后拿到授权,都经过层层的验证。因此,SQL 注入都只出现在比较低端小型的系统上。 XSS攻击前端最常见的攻击就是 XSS(Cross Site Scripting,跨站脚本攻击),很多大型网站(例如 FaceBook 都被 XSS 攻击过)。举一个例子,我在一个博客网站正常发表一篇文章,输入汉字、英文和图片,完全没有问题。但是如果我写的是恶意的 js 脚本,例如获取到document.cookie然后传输到自己的服务器上,那我这篇博客的每一次浏览,都会执行这个脚本,都会把自己的 cookie 中的信息偷偷传递到我的服务器上来。 预防 XSS 攻击就得对输入的内容进行过滤,过滤掉一切可以执行的脚本和脚本链接。大家可以参考xss.js这个开源工具。 简单总结一下,XSS 其实就是攻击者事先在一个页面埋下攻击代码,让登录用户去访问这个页面,然后偷偷执行代码,拿到当前用户的信息。 CSRF/XSRF 还有一个比较常见的攻击就是 CSRF/XSRF(Cross-site request forgery,跨站请求伪造)。它是借用了当前操作者的权限来偷偷的完成某个操作,而不是拿到用户的信息。例如,一个购物网站,购物付费的接口是http://buy.com/pay?id=100,而这个接口在使用时没有任何密码或者 token 的验证,只要打开访问就付费购买了。一个用户已经登录了http://buy.com在选择商品时,突然收到一封邮件,而这封邮件正文有这么一行代码\\<img src=”http://buy.com/pay?id=100"\\/>,他访问了邮件之后,其实就已经完成了购买。 预防 CSRF 就是加入各个层级的权限验证,例如现在的购物网站,只要涉及到现金交易,肯定输入密码或者指纹才行。","categories":[{"name":"前端","slug":"前端","permalink":"https://blog.magicyou.cn/categories/%E5%89%8D%E7%AB%AF/"}],"tags":[{"name":"jQuery","slug":"jQuery","permalink":"https://blog.magicyou.cn/tags/jQuery/"}]},{"title":"自己的一套多图上传","slug":"javascript-自己的一套多图上传","date":"2017-03-25T08:23:56.000Z","updated":"2017-03-26T12:23:56.000Z","comments":true,"path":"2017/03/25/javascript-自己的一套多图上传/","link":"","permalink":"https://blog.magicyou.cn/2017/03/25/javascript-%E8%87%AA%E5%B7%B1%E7%9A%84%E4%B8%80%E5%A5%97%E5%A4%9A%E5%9B%BE%E4%B8%8A%E4%BC%A0/","excerpt":"多图上传在各个网站都是个很常见的功能,对于刚入门前端不久的新人来说,网上插件一大堆,随便拿来就能用是个正解。菜鸟的我喜欢折腾,闲来无事,自己写个玩玩。","text":"多图上传在各个网站都是个很常见的功能,对于刚入门前端不久的新人来说,网上插件一大堆,随便拿来就能用是个正解。菜鸟的我喜欢折腾,闲来无事,自己写个玩玩。 先来复习几个小知识1234567891011121314151617181920FileReader:FileReader主要用于将文件内容读入内存,通过一系列异步接口,可以在主线程中访问本地文件。使用FileReader对象,web应用程序可以异步的读取存储在用户计算机上的文件(或者原始数据缓冲)内容,可以使用File对象或者Blob对象来指定所要处理的文件或数据。创建实例:var reader = new FileReader();方法:abort():void 终止文件读取操作 readAsArrayBuffer(file):void 异步按字节读取文件内容,结果用ArrayBuffer对象表示 readAsBinaryString(file):void 异步按字节读取文件内容,结果为文件的二进制串 readAsDataURL(file):void 异步读取文件内容,结果用data:url的字符串形式表示 readAsText(file,encoding):void 异步按字符读取文件内容,结果用字符串形式表示 事件:onabort 当读取操作被中止时调用 onerror 当读取操作发生错误时调用 onload 当读取操作成功完成时调用 onloadend 当读取操作完成时调用,不管是成功还是失败 onloadstart 当读取操作将要开始之前调用 onprogress 在读取数据过程中周期性调用 有点神奇了,然后了解一下base64 123百度百科上的解释Base64是网络上最常见的用于传输8Bit字节码的编码方式之一,Base64就是一种基于64个可打印字符来表示二进制数据的方法。可查看RFC2045~RFC2049,上面有MIME的详细规范。Base64编码是从二进制到字符的过程,可用于在HTTP环境下传递较长的标识信息。例如,在Java Persistence系统Hibernate中,就采用了Base64来将一个较长的唯一标识符(一般为128-bit的UUID)编码为一个字符串,用作HTTP表单和HTTP GET URL中的参数。在其他应用程序中,也常常需要把二进制数据编码为适合放在URL(包括隐藏表单域)中的形式。此时,采用Base64编码具有不可读性,需要解码后才能阅读。 有点不知所云,简单来说,base64就是一大串不可读字符串,将图片文件直接转为base64,就可以将图片以文本的方式直接传给后台。 说说实现原理有了上面的复习,多图片上传似乎有了点眉目。1.用户每上传一次图片,就将这张图片转化为base64,放进数组2.提交的时候把数组当做普通数据直接传到后台 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132<style> *{ padding:0; margin:0; } body{ background:#eee; } .wrapper-upload{ width:500px; height:400px; margin: 100 auto; box-shadow: 0 0 5px #ccc; border-radius: 10px; background-color: #fff; position: relative; overflow: hidden; } .wrapper-upload input[type=file]{ display: none; } .file-list ul{ list-style: none; text-align: center; margin-top:10px; } .file-list li{ width:130px; height:100px; display: inline-block; overflow: hidden; border-radius: 5px; margin:5px; box-shadow: 0 0 5px #000; position:relative; transition: all 1s ease; } .file-list li img.imgs{ height:100px; } .file-list li img.btn-del{ position:absolute; left:50%; top:0; transform: translate(-50%,-50%); width:30px; opacity: 0; transition: all .5s ease; z-index: 3; cursor: pointer; } .file-list li:hover img.imgs{ opacity: .6; } .file-list li:hover img.btn-del{ opacity: 1; top:50%; display: block; } .btn-upload{ position: absolute; bottom:10px; left:50%; display: block; padding: 0; width: 90px; height: 30px; background-color: orange; color: #fff; font-size: 16px; font-weight: 600; text-align: center; line-height: 30px; border-radius: 3px; transform: translate(-50%,0); cursor: pointer; }</style><script>$(function(){ Upload.init();});var Upload = { init: function() { this.eventChange(); this.deleteImg(); }, arrImg: [], // 已上传图片的base64组成数组 eventChange: function() { var that = this; $(document).on('change', '#upload', function(e) { if (that.arrImg.length >= 9) { alert('最多上传9张图片'); return false; } var reader = new FileReader(); var file = event.target.files[0]; // 存在图片就读取为base64 if (!file) { alert('未选择任何图片'); }else{ reader.readAsDataURL(file); } // 当读取操作成功完成时将图片放进数组,然后遍历出图片 reader.onload = function (e) { var imgBase64 = reader.result; that.arrImg.push(imgBase64); that.readerImg(); } }); }, // 图片遍历到面板上 readerImg: function() { var that = this; var strHtml = ''; for (var i = 0; i < that.arrImg.length; i++) { strHtml += '<li> <img src="./img/del.svg" data-index="' + i + '" class="btn-del" /> <img class="imgs" src="' + that.arrImg[i] + '"></li>'; } $('.file-list ul').html(strHtml); }, // 删除图片操作 deleteImg: function() { var that = this; $(document).on('click', '.btn-del', function(){ var index = $(this).attr('data-index'); that.arrImg.splice(index, 1); that.readerImg(); }); }};</script> 预览一下持续更新…","categories":[{"name":"前端","slug":"前端","permalink":"https://blog.magicyou.cn/categories/%E5%89%8D%E7%AB%AF/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/tags/JavaScript/"},{"name":"CSS3","slug":"CSS3","permalink":"https://blog.magicyou.cn/tags/CSS3/"}]},{"title":"js美化html上传按钮控件的方法","slug":"javascript- js美化html上传按钮控件的方法","date":"2017-02-15T12:23:45.000Z","updated":"2017-02-15T12:23:45.000Z","comments":true,"path":"2017/02/15/javascript- js美化html上传按钮控件的方法/","link":"","permalink":"https://blog.magicyou.cn/2017/02/15/javascript-%20js%E7%BE%8E%E5%8C%96html%E4%B8%8A%E4%BC%A0%E6%8C%89%E9%92%AE%E6%8E%A7%E4%BB%B6%E7%9A%84%E6%96%B9%E6%B3%95/","excerpt":"html中有几个比较难美化的控件,比如checkbox、radio、select,还有这篇博文的重点——上传控件。下面就说说怎么美化。","text":"html中有几个比较难美化的控件,比如checkbox、radio、select,还有这篇博文的重点——上传控件。下面就说说怎么美化。 简短的说明事实上,在html上传控件原本的基础上美化目前是不可能的,主流的浏览器都无法使用css样式表对丑陋的上传控件进行美化,只能另辟蹊径,隐藏掉原本丑陋的控件,用其他元素代替原本的样子,”改头换面”后,控件就能成为自己或者说符合UI的审美。这样的话,css和html打头阵尤为重要,如下代码片段: 1234567891011121314151617181920212223242526272829303132<style> *{ padding:0; margin:0; } .input-file>label>input[type=file] { display: none; } .input-file>label>label { display: inline-block; padding: 0; width: 90px; height: 30px; background-color: orange; color: #fff; font-size: 16px; font-weight: 600; text-align: center; line-height: 30px; border-radius: 3px; } .input-file>label>span { display: inline-block; }</style><div class="input-file"> <label> <input type="file" name='customer_id_data' id="fileUp" /> <label for="fileUp">选择</label> <span class="file-name-hook">未选择文件</span> </label></div> 可能有人问label标签套个label是什么鬼,我只能说,这样做是为了有个假按钮代替原有丑陋的按钮,同时这个假按钮又和原来丑按钮一样的点击弹出文件选择的效果,label和input毕竟是老搭档,如下手册原文的解释: 123456定义和用法<label> 标签为 input 元素定义标注(标记)。label 元素不会向用户呈现任何特殊效果。不过,它为鼠标用户改进了可用性。如果您在 label 元素内点击文本,就会触发此控件。就是说,当用户选择该标签时,浏览器就会自动将焦点转到和标签相关的表单控件上。<label> 标签的 for 属性应当与相关元素的 id 属性相同。提示和注释:注释:"for" 属性可把 label 绑定到另外一个元素。请把 "for" 属性的值设置为相关元素的 id 属性的值。 重头戏JavaScript(顺便带一个简单的文件格式验证)为了方便,当然是要引用jQ。详细的说明和代码片段如下: 12345678910111213141516171819202122232425$(document).on('change', '#fileUp', function() { var str = $(this).val(); console.log(str); // 上面在控制台输出的是‘C:\\fakepath\\test.xlsx’, var strArr = str.split('\\\\'); // 获取最后一个'\\'后面的字符串,获取后缀名 var fileName = strArr[strArr.length - 1]; var strSuf = fileName.split('.'); strSuf = strSuf[strSuf.length - 1]; // 定义一个希望上传文件正确格式数组,这里用excel文件格式为例 var format = ['xlsx', 'xls']; // 判断当前上传文件格式是否正确 // 正确:将文件名同步到'.file-name-hook' // 错误:复制出上传控件追加到原控件后面,并且删除原控件 if (format.indexOf(strSuf) > -1) { $('.file-name-hook').text(fileName); return false; } else { alert('文件格式不正确'); $('#fileUp').after($('#fileUp').clone().val("")); $('#fileUp').remove(); $('.file-name-hook').text('未选择文件'); return false; }}); 这里事件引用方法为何是‘on()’?而不是‘change()’,或者‘bind(‘change’,function(){}),是有学问的。最后格式错误是要复制出上传控件追加到原控件后面,并且删除原控件的,这样一次错误格式操作之后,方法‘change()’和‘bind(‘change’,function(){})’是失效的。 1234567891011定义和用法on() 方法在被选元素及子元素上添加一个或多个事件处理程序。语法$(selector).on(event,childSelector,data,function)|参数 |描述 || ------------- |:-------------:||event |必需。规定要从被选元素移除的一个或多个事件或命名空间。由空格分隔多个事件值,也可以是数组。必须是有效的事件。||childSelector |可选。规定只能添加到指定的子元素上的事件处理程序(且不是选择器本身,比如已废弃的 delegate() 方法)。||data |可选。规定传递到函数的额外数据。||function |可选。规定当事件发生时运行的函数。| 好了,这就完结了,路过的大神不吝赐教,互相学习。","categories":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/categories/JavaScript/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/tags/JavaScript/"}]},{"title":"css常用片段","slug":"css-css常用片段","date":"2017-02-01T12:23:45.000Z","updated":"2017-02-02T08:22:21.000Z","comments":true,"path":"2017/02/01/css-css常用片段/","link":"","permalink":"https://blog.magicyou.cn/2017/02/01/css-css%E5%B8%B8%E7%94%A8%E7%89%87%E6%AE%B5/","excerpt":"收集几个自己常用但记不住的css片段。","text":"收集几个自己常用但记不住的css片段。 css中文英文换行、禁止换行、显示省略号12345word-break:break-all;只对英文起作用,以字母作为换行依据word-wrap:break-word; 只对英文起作用,以单词作为换行依据white-space:pre-wrap; 只对中文起作用,强制换行white-space:nowrap; 强制不换行,都起作用white-space:nowrap; overflow:hidden; text-overflow:ellipsis;不换行,超出部分隐藏且以省略号形式出现 多行省略(MacOS上,火狐,谷歌,Edge,safari测试通过)123456.content{ display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;} css超过三行就隐藏成省略号1234567891011121314.esptext{ display: inline-block; overflow: hidden; text-overflow: ellipsis; cursor:pointer; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;}.esptext:hover{ color:#00A3A6; transition: 0.8s;} 持续更新…","categories":[{"name":"前端","slug":"前端","permalink":"https://blog.magicyou.cn/categories/%E5%89%8D%E7%AB%AF/"}],"tags":[{"name":"CSS3","slug":"CSS3","permalink":"https://blog.magicyou.cn/tags/CSS3/"}]},{"title":"手动实现移动端确认框(阿里开源的SUI Mobile框架的操作表)","slug":"javascript-手动实现移动端确认框(阿里开源的SUI Mobile框架的操作表)","date":"2017-01-20T14:12:23.000Z","updated":"2017-01-20T14:22:21.000Z","comments":true,"path":"2017/01/20/javascript-手动实现移动端确认框(阿里开源的SUI Mobile框架的操作表)/","link":"","permalink":"https://blog.magicyou.cn/2017/01/20/javascript-%E6%89%8B%E5%8A%A8%E5%AE%9E%E7%8E%B0%E7%A7%BB%E5%8A%A8%E7%AB%AF%E7%A1%AE%E8%AE%A4%E6%A1%86%EF%BC%88%E9%98%BF%E9%87%8C%E5%BC%80%E6%BA%90%E7%9A%84SUI%20Mobile%E6%A1%86%E6%9E%B6%E7%9A%84%E6%93%8D%E4%BD%9C%E8%A1%A8%EF%BC%89/","excerpt":"翻看几个前端的移动框架,发现各有千秋,不过现在好像不用框架都不好意思说自己是做移动端的。其实不然,个人在移动端由于刚刚起步,暂时没用上框架。不过有时候我会仿照框架写几个需要的组件,比如说这个:","text":"翻看几个前端的移动框架,发现各有千秋,不过现在好像不用框架都不好意思说自己是做移动端的。其实不然,个人在移动端由于刚刚起步,暂时没用上框架。不过有时候我会仿照框架写几个需要的组件,比如说这个: 由于只是需要框架的几个功能,为了节省流量,优化网页加载,所以不需要把整个框架引入,简单把功能实现。本文就介绍一下怎样实现阿里开源的SUI Mobile框架的操作表,点击查看原版原文 1.基本思路1.1 概述用户点击某些操作按钮,比如,删除、添加、修改时,为了防止用户操作错误,往往需要用户再次进行确认,防止用户重要信息被误操作。移动端比较好的体验效果就是这样的操作单,从在屏幕下方滑出显示,取消时”沿路返回“。在屏幕下方是应为操作方便,毕竟是移动端,大拇指很容易操作到。 1.3 js设计原理点击出现,点击取消再次隐藏,PC端很容易实现,移动端一样;需要说明的是这里的遮罩层和操作单(操作单就是出现的那块操作区)都是单独,单独放在html标签之间,没有嵌套关系。操作表的出现方式是淡入向上滑动,隐藏方式是淡出向下滑动,这里最好的办法就是CSS3动画实现,没毛病。 贴上代码,一一介绍 2.代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Document</title><!-- 使用rem作布局方式时,视口的设置很有必要--> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" /> <meta content="yes" name="apple-mobile-web-app-capable" /> <meta name="apple-mobile-web-app-status-bar-style" content="blank"> <meta content="telephone=no" name="format-detection" /> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="keywords" content="xx,xx,xx" /> <meta name="description" content="yyyyyyy" /><!-- <script src="./js/faskclick.js"></script>--><!-- 引入自己封装的rem.js文件--> <script src="./js/rem.js"></script> <script src="./js/jquery-1.11.1.min.js"></script> <style> * { margin: 0; padding: 0; } .mask { width: 100%; height: 100%; position: fixed; top: 0; left: 0; right: 0; z-index: 9; background: rgba(0, 0, 0, 0.6); display: none; } .wrap { width: 99%; height: 7rem; position: fixed; bottom: 2px; left: 0.5%; right: 0; z-index: 10; background: #FFF; border-radius: 5px; display: none; } .wrap div { box-sizing: border-box; -webkit-box-sizing: border-box; border-top: 1px solid #333; } .wrap .div-title { width: 100%; height: 3rem; text-align: center; line-height: 3rem; } .wrap .div-btn { width: 100%; height: 2rem; text-align: center; line-height: 2rem; } .pass { background: green; color: aliceblue; } .close { background: red; color: aliceblue; } /* 预设动画*/ .animated { -webkit-animation-duration: .5s; animation-duration: .5s; -webkit-animation-fill-mode: both; animation-fill-mode: both; } @-webkit-keyframes fadeInUp { from { opacity: 0; -webkit-transform: translate3d(0, 100%, 0); transform: translate3d(0, 100%, 0); } to { opacity: 1; -webkit-transform: none; transform: none; } } @keyframes fadeInUp { from { opacity: 0; -webkit-transform: translate3d(0, 100%, 0); transform: translate3d(0, 100%, 0); } to { opacity: 1; -webkit-transform: none; transform: none; } } .fadeInUp { -webkit-animation-name: fadeInUp; animation-name: fadeInUp; } @-webkit-keyframes fadeOutDown { from { opacity: 1; } to { opacity: 0; -webkit-transform: translate3d(0, 100%, 0); transform: translate3d(0, 100%, 0); } } @keyframes fadeOutDown { from { opacity: 1; } to { opacity: 0; -webkit-transform: translate3d(0, 100%, 0); transform: translate3d(0, 100%, 0); } } .fadeOutDown { -webkit-animation-name: fadeOutDown; animation-name: fadeOutDown; } </style></head><body> <button class="haha">点击</button> <div class="mask" id="mask"></div> <div class="wrap animated"> <div class="div-title">您确定要删除我么?</div> <div class="div-btn pass">确认</div> <div class="div-btn close">取消</div> </div> <script> //点击按钮,遮罩层先出现,淡出淡入在视觉上好一些,操作表向上滑动出现 $('.haha').bind('click', function() { $('#mask').fadeIn(); //操作表向上滑动出现,先显示操作表(display:block),移除预设动画类'fadeOutDown',再添加预设动画类’fadeInUp‘ $('.wrap').show().removeClass('fadeOutDown').addClass('fadeInUp'); }); //点击取消按钮,操作表向下滑动隐藏,‘delay(500)’可以防止操作表在视觉上还没向下滑动就隐藏掉的问题, $('.close').bind('click', function() { //操作表向下滑动隐藏,先移除预设动画类'fadeInUp',再添加预设动画类’fadeOutDown‘,最后隐藏操作表(display:none), $('.wrap').removeClass('fadeInUp').addClass('fadeOutDown').delay(500).hide(0); //然后遮罩层晚操作表隐藏 $('#mask').fadeOut(); })//(’display:block‘和’display:none‘只做说明) </script></body></html> 3、补充说明对于动画的实现,自己做也是没问图,博主比较懒,拿现成的省时省力。大家应该都知道animate.css.你可以选择全部拿下来整个引用。怕冗余代码多的话,我教你拿出你需要的代码。1.确定你需要的效果,博主需要淡入向上滑动,和淡出向下滑动,如下2.然后下载下来整个animate.css类库文件,打开找到相应的类名fadeInUp、fadeOutDown3.复制粘贴出来,放在自己的css文件中,还没完,必须把’animated‘类也拿出来,放在动画代码的前面(必须)4.需要动画的div,要预先添加’animaied‘,再添加相应的动画类,比如’fadeInUp‘这样以后就可以随意使用动画了,像这样的:animate.css实际项目使用 4、在线演示在线演示","categories":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/categories/JavaScript/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/tags/JavaScript/"},{"name":"CSS3","slug":"CSS3","permalink":"https://blog.magicyou.cn/tags/CSS3/"}]},{"title":"可拖拽的弹出框","slug":"javascript-可拖拽的弹出框","date":"2016-12-09T14:12:23.000Z","updated":"2016-12-09T15:45:02.000Z","comments":true,"path":"2016/12/09/javascript-可拖拽的弹出框/","link":"","permalink":"https://blog.magicyou.cn/2016/12/09/javascript-%E5%8F%AF%E6%8B%96%E6%8B%BD%E7%9A%84%E5%BC%B9%E5%87%BA%E6%A1%86/","excerpt":"闲来无聊,拿来许久以前的可拖拽弹出框特效分享给过路的。以百度首页的登录框为样版,来一一说明怎么实现相同的效果。喜欢的也可以来看看原文原文","text":"闲来无聊,拿来许久以前的可拖拽弹出框特效分享给过路的。以百度首页的登录框为样版,来一一说明怎么实现相同的效果。喜欢的也可以来看看原文原文 1.基本思路1.1 概述可拖拽弹出框,顾名思义:弹出,可以拖拽。简单明了。看到百度首页的登录弹出框,还多了几个,总结了一下,如下几条:1.点击登录弹出登录框2.点击小叉叉,关闭登录框3.登录框弹出的同时有个遮罩层遮罩覆盖了除登录框以外的内容4.鼠标在登录框上部分时,可以拖拽,任意摆放位置,并且不会跑出可视区域5.每次改变浏览器大小时,弹出框都会再次居中 1.2 复习几个js知识123456789101112131415161718192021222324网页可见区域宽: document.body.clientWidth 网页可见区域高: document.body.clientHeight 网页可见区域宽: document.body.offsetWidth (包括边线的宽) 网页可见区域高: document.body.offsetHeight (包括边线的高) 网页正文全文宽: document.body.scrollWidth 网页正文全文高: document.body.scrollHeight 网页被卷去的高: document.body.scrollTop 网页被卷去的左: document.body.scrollLeft 网页正文部分上: window.screenTop 网页正文部分左: window.screenLeft 屏幕分辨率的高: window.screen.height 屏幕分辨率的宽: window.screen.width 屏幕可用工作区高度: window.screen.availHeight 屏幕可用工作区宽度: window.screen.availWidth 获取元素宽度:oDiv.offsetWidth获取元素高度:oDiv.offsetHeight获取坐标(处理兼容:var e = event || window.event;)相对于屏幕:e.screenX,e.screenY相对浏览器窗口:e.clientX,e.clientY相对文档:e.pageX || e.clientX + scrollX, e.pageY || e.clientY + scrollY; 1.3 js设计原理1.3.1 点击登录弹出登录框页面加载初,css设置弹出框默认隐藏,点击登录按钮,js设置样式弹出框显示 1mask.style.display = 'block'; 1.3.2 点击小叉叉,关闭登录框点击小叉叉,js设置样式弹出框再次隐藏 1mask.style.display = 'none'; 1.3.3 登录框弹出的同时有个遮罩层…这里做一下说明,我这里的遮罩层直接包裹在了登录框外,上面说的弹出框隐藏出现的,实际上是遮罩层隐藏出现,为了说明问题,我还是当做弹出框对待 1234567<div class="mask" id="mask"> <div class="login" id="login"> <div class="title-dragable" id="dragable"> <sapn class="close" id="close">×</span> </div> </div> </div> 1.3.4 拖拽不会跑出可视区域鼠标对弹出框拖拽时,对left和top值进行判断处理,使得弹出框极限时只能在可是区域内沿边移动 1234left = left <= 0 ? 0 : left;left = left >= (mask.offsetWidth - node.offsetWidth) ? (mask.offsetWidth - node.offsetWidth) : left;top = top <= 0 ? 0 : top;top = top >= (mask.offsetHeight - node.offsetHeight) ? (mask.offsetHeight - node.offsetHeight) : top; 更为详细的说明下面代码中都有注释详细说明,如下 2.代码2.1 html代码12345678910111213141516171819202122232425262728<div class="container" id="container"> <div class="header"> <ul> <li><a href="##">糯米</a></li> <li><a href="##">新闻</a></li> <li><a href="##">hao123</a></li> <li><a href="##">地图</a></li> <li><a href="##">视频</a></li> <li><a href="##">视频贴吧</a></li> <li><a href="javascript:;" id="dl">登录</a></li> <li><a href="##">设置</a></li> <li class="more"><a href="##">更多商品</a></li> </ul> </div> <div class="content"> </div> <div class="footer"> </div> <!-- 登录弹出框--> <div class="mask" id="mask"> <div class="login" id="login"> <div class="title-dragable" id="dragable"> <sapn class="close" id="close">×</span> </div> </div> </div> </div> 2.2 css代码12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788* { padding: 0; margin: 0; } .container { min-width: 1000px; width: 100%; margin: 0 auto; } .header { height: 24px; margin: 19px 0 5px; } .header ul { height: 24px; float: right; margin-right: 20px; } .header ul li { float: left; list-style: none; line-height: 24px; margin-left: 20px; } .header ul li a { color: #333; font-weight: 700; font-size: 13px; text-decoration: underline; } .header ul .more { width: 60px; height: 23px; background: #38f; line-height: 24px; text-align: center; overflow: hidden; border-bottom: 1px solid #38f; margin-left: 19px; margin-right: 2px; } .header ul .more a { font-size: 13px; color: #fff; text-decoration: none; } .mask { width: 100%; height: 100%; background: rgba(0, 0, 0, .3); position: absolute; top: 0; left: 0; z-index: 99; display: none; } .login { width: 393px; height: 505px; background: #FFF; position: absolute; top: 50%; left: 50%; } .title-dragable { width: 391px; height: 46px; background-color: #f7f7f7; border: 1px solid #ddd; cursor: move; } .close { display: block; float: right; width: 16px; height: 16px; font-size: 18px; cursor: pointer; text-align: center; line-height: 16px; margin: 15px; color: #ccc; } .close:hover { color: #333; } 2.3 js代码1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859window.onload = function() { //点击登录弹出登录框 var dl = document.getElementById('dl'); var mask = document.getElementById('mask'); dl.onclick = function() { mask.style.display = 'block'; center(); } //点击小叉叉关闭登录框 var close = document.getElementById('close'); close.onclick = function() { mask.style.display = 'none'; } //开始为登录框添加鼠标拖动效果 var dragBar = document.getElementById('dragable'); dragBar.onmousedown = function(e) { var e = e || window.e; var node = this.parentNode; // 获取当前鼠标z相对dragBar的坐标,记录下来,供拖拽时使用 var offsetX = e.offsetX; var offsetY = e.offsetY; //添加鼠标在dragBar按下且未松开时的window事件 window.onmousemove = function(e) { var e = e || window.e; // 再次获取当前鼠标的位置坐标 var moveX = e.clientX; var moveY = e.clientY; // 计算拖动时应有的偏移量 var left = moveX - offsetX; var top = moveY - offsetY; //对偏移量进行处理,使得被拖拽的元素不会跑出可视范围,算是获得好的客户体验吧 left = left <= 0 ? 0 : left; left = left >= (mask.offsetWidth - node.offsetWidth) ? (mask.offsetWidth - node.offsetWidth) : left; top = top <= 0 ? 0 : top; top = top >= (mask.offsetHeight - node.offsetHeight) ? (mask.offsetHeight - node.offsetHeight) : top; //将处理过的偏移量添加到拖拽元素样式 node.style.top = top + 'px'; node.style.left = left + 'px'; } //鼠标按键松开,拖拽结束 window.onmouseup = function() { window.onmousemove = null; } } //处理login弹出框,使得每次浏览器窗口大小改变,login弹出框居中 var login = document.getElementById('login'); var container = document.getElementById('container'); window.onresize = function() { center(); } //封装成单独函数,便于调用 function center() { mask.style.width = document.body.clientWidth>container.offsetWidth?document.body.clientWidth+'px':container.offsetWidth+'px'; var left = (mask.offsetWidth - login.offsetWidth) / 2; var top = (mask.offsetHeight - login.offsetHeight) / 2; login.style.top = top + 'px'; login.style.left = left + 'px'; } } 3、补充说明移动端时不支持的!移动端时不支持的!移动端时不支持的!重要的事情说三遍。对于移动端,拖拽效果貌似没什么必要,毕竟只是个登录框,移动端屏幕也就那么大(iPad之类的当我没说)。不过移动端的拖拽会有另一篇做介绍,以及功能实现。 4、在线演示在线演示","categories":[{"name":"前端","slug":"前端","permalink":"https://blog.magicyou.cn/categories/%E5%89%8D%E7%AB%AF/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/tags/JavaScript/"}]},{"title":"无限分页设计与实现","slug":"javascript-无限分页设计与实现","date":"2016-12-01T15:12:23.000Z","updated":"2016-12-01T15:14:02.000Z","comments":true,"path":"2016/12/01/javascript-无限分页设计与实现/","link":"","permalink":"https://blog.magicyou.cn/2016/12/01/javascript-%E6%97%A0%E9%99%90%E5%88%86%E9%A1%B5%E8%AE%BE%E8%AE%A1%E4%B8%8E%E5%AE%9E%E7%8E%B0/","excerpt":"最近接手一个微信端的的项目,客户要求在文章列表页面不要用分页。然后项目经理就交代下来,用瀑布流吧(他对前端并不了解),开发组的顿姐姐就说是不是无限分页呀… 然后我就开始自行脑补瀑布流,无限分页,原理不还是一样嘛。自行研究了一下,就开始了造轮子之路…当然,欢迎来原文看看,原文","text":"最近接手一个微信端的的项目,客户要求在文章列表页面不要用分页。然后项目经理就交代下来,用瀑布流吧(他对前端并不了解),开发组的顿姐姐就说是不是无限分页呀… 然后我就开始自行脑补瀑布流,无限分页,原理不还是一样嘛。自行研究了一下,就开始了造轮子之路…当然,欢迎来原文看看,原文 1.基本思路1.1 概述无限分页,就是说页面在不显示页码,不需要用户去点击页码,使用技术手段实现页面滚动到某一时刻进行加载下一页内容,并且陈列到页面上。这不就是赤裸裸的瀑布流一样的原理么。 1.2 脑补一下先来补充一个sql语句知识,当然这里只做MySQL的实例 1select * from imgs limit 0,8; 查询语句的意思:查询imgs表的所有内容,显示第一页的8条内容从第一条数据开始算起那么第二页怎么写呢:(直接写出前四页的sql语句,一起来找规律) 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556第一页 select * from imgs limit 0,8;结果: +----+------------+| id | src |+----+------------+| 1 | img/01.jpg || 2 | img/02.jpg || 3 | img/03.jpg || 4 | img/01.jpg || 5 | img/01.jpg || 6 | img/01.jpg || 7 | img/01.jpg || 8 | img/01.jpg |+----+------------+第二页 select * from imgs limit 8,8;结果:+----+------------+| id | src |+----+------------+| 9 | img/01.jpg || 10 | img/01.jpg || 11 | img/01.jpg || 12 | img/01.jpg || 13 | img/01.jpg || 14 | img/02.jpg || 15 | img/02.jpg || 16 | img/02.jpg |+----+------------+第三页 select * from imgs limit 16,8;结果:+----+------------+| id | src |+----+------------+| 17 | img/02.jpg || 18 | img/02.jpg || 19 | img/02.jpg || 20 | img/02.jpg || 21 | img/02.jpg || 22 | img/02.jpg || 23 | img/02.jpg || 24 | img/03.jpg |+----+------------+第四页 select * from imgs limit 24,8;结果:+----+------------+| id | src |+----+------------+| 25 | img/03.jpg || 26 | img/03.jpg || 27 | img/03.jpg || 28 | img/03.jpg || 29 | img/03.jpg || 30 | img/03.jpg || 31 | img/02.jpg || 32 | img/02.jpg |+----+------------+ 好了,规律想必已经找到了。不过还是要大致说一下这个sql语句意思。‘limit’后面有两个数字,这就输关键所在,第一个数字是开始的索引值,0代表第一个开始,8代表显示条数,根据第二个数字有规律的改变第一个数字,分页效果就出来了。 1.3 js设计原理页面总是有个宽度高度的,页面加载完毕,上下滚动页面,有个触发事件,每次到一个合适的条件,就触发事件进行ajax的请求,并将返回的数据加进页面内,就完成一次加载(或者说完成一次加载下一页数据的请求),在代码行内的注释进行更详细的说明 2.代码2.1 html代码12345678<div class="container"> <div class="wrap"><!-- 这里放加载的数据--> </div> <div class="loader"><!-- 这里放加载时候的加载动画,还有加载完毕后提示信息 --> </div></div> 2.2 css代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899 * { margin: 0; padding: 0; } .container { width: 640px; margin: 0 auto; background: #FFF; } .container a { display: inline-block; margin: 10px; } .container a img { width: 100%; } .loader { width: 100%; text-align: center; height: 50px; line-height: 50px; } /* 预设加载动画,css3做的动画效果 */ .dot { width: 18px; height: 18px; background: #3ac; border-radius: 100%; display: inline-block; animation: slide 1s infinite; -webkit-animation: slide 1s infinite; margin-left:5px; margin-right:5px; } .dot:nth-child(1) { animation-delay: 0.1s; -webkit-animation-delay: 0.1s; background: #ccc; } .dot:nth-child(2) { animation-delay: 0.2s; -webkit-animation-delay: 0.2s; background: #ccc; } .dot:nth-child(3) { animation-delay: 0.3s; -webkit-animation-delay: 0.3s; background: #ccc; } .dot:nth-child(4) { animation-delay: 0.4s; -webkit-animation-delay: 0.4s; background: #ccc; } .dot:nth-child(5) { animation-delay: 0.5s; -webkit-animation-delay: 0.5s; background: #ccc; } @-webkit-keyframes slide { 0% { transform: scale(1); -webkit-transform: scale(1); } 50% { opacity: 0.3; transform: scale(1.2); -webkit-transform: scale(1.2); } 100% { transform: scale(1); -webkit-transform: scale(1); } } @keyframes slide { 0% { transform: scale(1); -webkit-transform: scale(1); } 50% { opacity: 0.3; transform: scale(1.2); -webkit-transform: scale(1.2); } 100% { transform: scale(1); -webkit-transform: scale(1); } } 这里放了一个css3做的加载动画,也是在其他博客上看到的,挺好玩,拿来用用 2.3 js代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687$(function () { //预设全局变量page,就是用来当做'limit'后面的第一个数字 var page = 0; //给预设一个全局变量,判断一次请求是否完毕,完毕后才能进行下一次请求,防止多次重复进行请求 var status = true; //给预设一个全局变量,判断数据是否已经完全加载完毕,并且已经没有可以再次加载的数据,防止已经没有可加载后还会进行请求, var dataStatu = true; $(window).scroll(function () { //滚动的高度 var scrollTop = $(window).scrollTop(); //浏览器可视区域的高度 var screenHei = $(window).height(); //文档的总高度 var bodyHei = $(document).height(); //计算一个差值,通过这个差值,判断是否进行ajax请求 var c = bodyHei - screenHei - scrollTop; if (c < 100) { //判断是否可以加载数据 if (dataStatu == true && status == true) { //可以加载数据,把status状态修改为false status = false; //重置容器‘.loader’为空白 $('.loader').html(''); //往容器‘.loader’加进加载动画 var str = '<div class="dot"></div><div class="dot"></div><div class="dot"></div><div class="dot"></div><div class="dot"></div>'; $('.loader').html(str); //调用加载数据的函数 getData(); } } }); //页面加载时先进行一次数据请求 getData(); function getData() { //引用jq的ajax方法,不解释,好用就行 $.ajax({ //看清楚url带的get参数 url: './control/getData.php?page=' + page + '&pageNum=8', type: 'get', dataType: 'text', success: function (data) { //处理返回的json数据 var data = JSON.parse(data); //请求已经发送,并且请求的数据已经返回,重置状态码status为false,准备下一次的请求 status = true; //sql语句原理说过‘limit’后第一个数字有规律变化,就在这里 page += 8; //让数据有个缓冲的过程去过渡一下,给两秒时间,两秒后把请求的数据放进网页里 setTimeout(function(){ //重置容器‘.loader’为空白 $('.loader').html(' '); //判断返回的数据是否为空,由于是处理json后的数据,当然是数组,判断 数组长度是否为0即可 if (data.length == 0) { //当所有数据已经加载完毕,没有可以在加载的数据,将状态码dataStatu设置为false,不再进行数据请求 dataStatu = false; console.log('没有数据加载了'); //容器‘.loader’填写数据已经加载完毕的提示 var str = '没有可加载数据了'; $('.loader').html(str); } else { console.log('还有数据加载呢.......'); } //将请求的数据遍历出来,拼接进字符串 var str = ''; for (var i = 0; i < data.length; i++) { str += '<a href="">'; str += '<img src="' + data[i].src + '" alt="">'; str += '</a>'; } //将凭借出来的字符串追加进容器内 $('.wrap').append(str); },2000) }, error: function (statuCode) { console.log(statuCode); } }); } }) 3、补充说明对于page,我这里是get传递0,8,16,24…实际环境下,需要问一下后台人员有木有对page在后台处理,他们可能需要1,2,3,4,5…..就好了;还有ajax的url参数‘pageNum=8’,后台直接设置的话,前端就不需要传送;总而言之,实际操作时,还得和后台人员沟通,原理就是这么个原理,有不恰当的地方,希望过路的大神们不吝赐教 4、在线演示在线演示","categories":[{"name":"前端","slug":"前端","permalink":"https://blog.magicyou.cn/categories/%E5%89%8D%E7%AB%AF/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/tags/JavaScript/"},{"name":"CSS3","slug":"CSS3","permalink":"https://blog.magicyou.cn/tags/CSS3/"}]},{"title":"lamp/lnmp搭建+typecho搭建","slug":"Linux-lnmp搭建+typecho搭建","date":"2016-11-20T15:12:23.000Z","updated":"2016-11-20T15:12:02.000Z","comments":true,"path":"2016/11/20/Linux-lnmp搭建+typecho搭建/","link":"","permalink":"https://blog.magicyou.cn/2016/11/20/Linux-lnmp%E6%90%AD%E5%BB%BA+typecho%E6%90%AD%E5%BB%BA/","excerpt":"linux下简单的lamp服务器搭建对于新手来说,可能有些困难,博主抱着系天下苍生的心来帮助需要帮助的人,大神绕道右拐。phpStudy for linux版本的出现,真是极大地便利了安装时间成本。话不多说,详细步骤如下:","text":"linux下简单的lamp服务器搭建对于新手来说,可能有些困难,博主抱着系天下苍生的心来帮助需要帮助的人,大神绕道右拐。phpStudy for linux版本的出现,真是极大地便利了安装时间成本。话不多说,详细步骤如下: 1.准备工作环境:centos6.5 64位软件:phpstudy for linux(云主机搭建最好忽略此条) 2.安装lamp/lump1.从网络获取安装包 1wget -c http://lamp.phpstudy.net/phpstudy.bin 此步骤需要点时间下载,稍等一会儿就好,如果不是云主机,建议从官网提前下载安装包直达车http://www.phpstudy.net/a.php/203.html 2.权限设置 1chmod +x phpstudy.bin 3.运行安装 1./phpstudy.bin 此过程需要时间较长,可能十几到二十几分钟,和主机的配置有关系,我的用了25分钟多安装完之后会有提示,简单的选择而已。喜欢lamp的就”a”,喜欢lnmp的就”n”,依次类推。还有php版本选择的,博主选的5.5,可能大家都喜欢5.5的吧。(我也没发现5.6,7.0有什么不好的,注意:typecho环境需要php5.2以上) 4.安装过程完毕,easy的跟1似的。方便快捷,记得刚学php时环境配置搞了一整天,繁琐的头疼,修改各类配置文件,各种排错。几个命令 123 服务进程管理:phpstudy (start|stop|restart|uninstall)站点主机管理:phpstudy (add|del|list)ftpd用户管理:phpstudy ftp (add|del|list) 3.修改mysql1.修改mysql数据库密码 默认mysql密码是root,首先,登陆mysql 1mysql -uroot -p 然后操作mysql库的user表,进行update 123mysql> use mysql;mysql> update user set password=password('654321') where user='root';mysql> flush privileges; 2.允许远程连接数据库使用“use mysql”命令,选择要使用的数据库,修改远程连接的基本信息,保存在mysql数据库中,因此使用mysql数据库。 使用“GRANT ALL PRIVILEGES ON . TO ‘root’@’%’ IDENTIFIED BY ‘root’ WITH GRANT OPTION;”命令可以更改远程连接的设置。 12mysql> use mysql;mysql> GRANT ALL PRIVILEGES ON *.* TO '数据库用户名(一般是root)'@'%' IDENTIFIED BY '数据库密码' WITH GRANT OPTION; 使用“flush privileges;”命令刷新刚才修改的权限,使其生效 1mysql> flush privileges; 3.安装typecho这个貌似过时的轻量级开源博客程序好像不更新了都,没办法,个人觉得挺好用1.从官网下载压缩包http://typecho.org/2.解压后,将“build”文件夹放进安装好的lamp环境的www目录下3.修改Apache或者Nginx默认网站目录Apache:找到DocumentRoot “/phpstudy/www”<Directory “/phpstudy/www”>修改为:DocumentRoot “/phpstudy/www/build”<Directory “/phpstudy/www/build”>4.重启phpStudy配置成功,剩下的就是根据typecho提示进行安装操作注意:1.数据库需要自行手动创建 2.安装typecho时,如果数据库连接配置失败,修改‘localhost’为’127.0.0.1‘,还不行的话,直接改为云主机的ip即可。 个人经验,供大家哦交流学习,有错的地方望大家不吝赐教。","categories":[{"name":"Linux","slug":"Linux","permalink":"https://blog.magicyou.cn/categories/Linux/"}],"tags":[{"name":"Linux","slug":"Linux","permalink":"https://blog.magicyou.cn/tags/Linux/"}]},{"title":"常见的浏览器兼容问题","slug":"前端-常见的浏览器兼容问题","date":"2016-09-14T14:23:02.000Z","updated":"2016-09-15T14:23:04.000Z","comments":true,"path":"2016/09/14/前端-常见的浏览器兼容问题/","link":"","permalink":"https://blog.magicyou.cn/2016/09/14/%E5%89%8D%E7%AB%AF-%E5%B8%B8%E8%A7%81%E7%9A%84%E6%B5%8F%E8%A7%88%E5%99%A8%E5%85%BC%E5%AE%B9%E9%97%AE%E9%A2%98/","excerpt":"总有IE浏览器和其他浏览器的分类,所以总有各种兼容问题(持续更新)","text":"总有IE浏览器和其他浏览器的分类,所以总有各种兼容问题(持续更新) 1. event.srcElement问题问题说明:IE下,event对象有srcElement属性,但是没有target属性;Firefox下,even对象有target属性,但是没有srcElement属性。解决方法:使用srcObj =event.srcElement ?event.srcElement : event.target; 2. ajax略有不同IE:ActiveXObject其他:xmlHttpReuest 3. event.x 与 event.y 问题在IE中,event 对象有x,y属性,FF中没有在FF中,与 event.x 等效的是 event.pageX ,但event.pageX IE中没有故采用 event.clientX 代替 event.x ,在IE中也有这个变量event.clientX 与 event.pageX 有微妙的差别,就是滚动条要完全一样,可以这样:mX = event.x ? event.x : event.pageX;然后用 mX 代替 event.x 4. css中的width和padding在IE7和FF中width宽度不包括padding,在Ie6中包括padding. 5. 边框:border-radius: 最低兼容至 IE9,其它浏览器兼容情况优良。box-shadow: 最低兼容至IE9, 其它浏览器兼容情况优良。 6. 背景:background-size: 最低兼容至IE9, 其它浏览器兼容情况优良。 7.字体:@font-face: IE9及以上版本的IE浏览器,支持引入任何格式的字体文件,而在IE9之前的浏览器,只支持引入EOT格式的字体文件。 其它浏览器兼容情况优良。 8. 2D转换:transform: 最低兼容至IE9(需要添加-ms-前缀),其它浏览器兼容情况优良。在transform属性前加入浏览器内核前缀是很好的实践。不建议在svg元素上使用transform属性,最新版本的IE并不支持这一使用方式。 9. 3D转换:IE10 和 Firefox 支持 3D 转换。Chrome 和 Safari 需要前缀 -webkit-。Opera 仍然不支持 3D 转换,它只支持2D 转换。 10. 过渡:transition:最低兼容至IE10,其它浏览器兼容情况优良。Safari浏览器需要前缀-webkit-,其它大部分浏览器对此并未有前缀要求,因此除了特殊情况,可以不添加其它浏览器的前缀。 11. 动画:animation:兼容情况与transition属性大致相同。","categories":[{"name":"前端","slug":"前端","permalink":"https://blog.magicyou.cn/categories/%E5%89%8D%E7%AB%AF/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/tags/JavaScript/"},{"name":"CSS","slug":"CSS","permalink":"https://blog.magicyou.cn/tags/CSS/"},{"name":"HTML","slug":"HTML","permalink":"https://blog.magicyou.cn/tags/HTML/"}]},{"title":"放大镜的设计与实现","slug":"javascript-放大镜的设计与实现","date":"2016-09-09T15:12:23.000Z","updated":"2016-09-09T15:12:02.000Z","comments":true,"path":"2016/09/09/javascript-放大镜的设计与实现/","link":"","permalink":"https://blog.magicyou.cn/2016/09/09/javascript-%E6%94%BE%E5%A4%A7%E9%95%9C%E7%9A%84%E8%AE%BE%E8%AE%A1%E4%B8%8E%E5%AE%9E%E7%8E%B0/","excerpt":"喜欢购物的童鞋会发现,PC端的电商详情页总喜欢用一个放大镜的效果来实现顾客查看图片的细节,貌似很好玩,业余时间来给大家科普实现的方法,当然是怎么简单怎么来,适合初出茅庐的童鞋,大神们也欢迎来点评下。","text":"喜欢购物的童鞋会发现,PC端的电商详情页总喜欢用一个放大镜的效果来实现顾客查看图片的细节,貌似很好玩,业余时间来给大家科普实现的方法,当然是怎么简单怎么来,适合初出茅庐的童鞋,大神们也欢迎来点评下。 1.实现原理1.原理图图里面的节点关系看下边页面结构层(html文件)js实现原理:1.首先鼠标移入容器”.wrap”或者”.show”时,显示”.show span”和”.show-ks”,当然,进入页面的时候他们要默认隐藏(display:none);当鼠标移出的时候还要再次隐藏他们2.然后就是鼠标在容器”.wrap”内移动的时候,要让小容器”.show span”跟随鼠标移动,使鼠标一直在小容器”.show span”正中间3.相对位移问题,鼠标在容器”.wrap”中移动的时候,右边容器”.show-ks”的图片要显示”.show span”范围的图片内容,这样就有了放大的效果。单拿上边距(top,offsetTop)来说明一下比例问题: 12345var wrap = document.querySelector('.wrap');var show = wrap.querySelector('.show');var showSpan = show.querySelector('span');var showKs = wrap.querySelector('.show-ks');var showBig = showKs.querySelector('span'); showspan的上边距就是 1var Top = Y-showSpan.offsetHeight/2; 假设showBig的上边距应该是Top2,那么比例式子就是: 12Top/(wrap.offsetHeight-showSpan.offsetHeight)=Top2/(showBig.offsetHeight-showKs.offsetHeight) 左边距的entity就和上边距的问题一样,迎刃而解 2.代码1.结构层(html) 12345678910111213<div class="wrap"> <div class="show"> <img src="./img/img1.jpg" alt=""> <span> </span> </div> <div class="show-ks"> <span> </span> </div></div> 2.表现层(css) 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849*{ padding:0; margin:0; } .wrap{ width:418px; height:418px; background: red; position:relative; margin:50px; cursor: move; } .show{ width:418px; height:418px; background: orange; position: relative; } .show img{ width:100%; } .show span{ display:none; width:218px; height:218px; background: rgba(108,158,248,0.5); position: absolute; top:0; left:0; } .show-ks{ display:none; width:418px; height:418px; background: blue; position: absolute; top:0; left:428px; overflow: hidden; } .show-ks span{ display:block; width:800px; height:800px; background: url(./img/img1.jpg); position: absolute; top:0; left:0; } 3.行为层(js) 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253window.onload=function(){ //获取所需要的事件源 var wrap = document.querySelector('.wrap'); var show = wrap.querySelector('.show'); var showSpan = show.querySelector('span'); var showKs = wrap.querySelector('.show-ks'); var showBig = showKs.querySelector('span'); // 添加/监控鼠标移入事件 show.addEventListener('mouseover',function(){ showSpan.style.display = 'block'; showKs.style.display = 'block'; //监听鼠标移动事件 wrap.addEventListener('mousemove',function(e){ //处理兼容性 var e = window.e||e; //实时的获取鼠标相对于wrap容器的横纵坐标(X,Y) var X = e.clientX-this.offsetLeft; var Y = e.clientY-this.offsetTop; //通过鼠标横纵坐标(X,Y),减去showspan宽度/高度的一半,使得鼠标一直在showspan的正中间 var Left = X-showSpan.offsetWidth/2; var Top = Y-showSpan.offsetHeight/2; //防止showspan1跑出show容器外,判断Left和Top值, if(Left < 0){ Left = 0; }else if(Left > this.offsetWidth-showSpan.offsetWidth){ Left = this.offsetWidth-showSpan.offsetWidth; } if(Top < 0){ Top = 0; }else if(Top > this.offsetHeight-showSpan.offsetHeight){ Top = this.offsetHeight-showSpan.offsetHeight; } showSpan.style.left = Left+'px'; showSpan.style.top = Top+'px'; //对应大图的偏移量 //根据原理中的比例等式,获取并设置大图应有的相对偏移量 var bigTop = Top/(this.offsetHeight-showSpan.offsetHeight)*(showBig.offsetHeight-showKs.offsetHeight); var bigLeft = Left/(this.offsetWidth-showSpan.offsetWidth)*(showBig.offsetWidth-showKs.offsetWidth); showBig.style.top = -bigTop+'px'; showBig.style.left = -bigLeft+'px'; }) }); //添加鼠标移出事件 show.addEventListener('mouseout',function(){ showSpan.style.display = 'none'; showKs.style.display = 'none'; }); } 3.注意可能有童鞋疑惑为什么不直接用pageX和PageY,恩,感兴趣可以自己试一试,博主试了试有点问题,所以委曲求全。哪里不对的,希望过路的大神不吝赐教。","categories":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/categories/JavaScript/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/tags/JavaScript/"}]},{"title":"电梯导航","slug":"javascript-电梯导航","date":"2016-09-01T15:12:23.000Z","updated":"2016-09-01T15:12:02.000Z","comments":true,"path":"2016/09/01/javascript-电梯导航/","link":"","permalink":"https://blog.magicyou.cn/2016/09/01/javascript-%E7%94%B5%E6%A2%AF%E5%AF%BC%E8%88%AA/","excerpt":"在一些大型电商页面上,由于页面过于长,所以设计人员喜欢在侧边加上一个导航,然后点击可以快速导航到各个栏目,比如京东商城,这样不仅方便快捷,而且美观。","text":"在一些大型电商页面上,由于页面过于长,所以设计人员喜欢在侧边加上一个导航,然后点击可以快速导航到各个栏目,比如京东商城,这样不仅方便快捷,而且美观。 1.基本思路相比轮播图,电梯导航貌似简单得多。获取所有楼层的offsetTop值,放在一个数组里面。利用滚动事件监听window.offsetTop,并且判断是在数组中那两个数值区间之间,做出相应的效果。安利几个小内容 1234567scrollHeight: 获取对象的滚动高度。 scrollLeft:设置或获取位于对象左边界和窗口中目前可见内容的最左端之间的距离 scrollTop:设置或获取位于对象最顶端和窗口中可见内容的最顶端之间的距离 scrollWidth:获取对象的滚动宽度 offsetHeight:获取对象相对于版面或由父坐标 offsetParent 属性指定的父坐标的高度 offsetLeft:获取对象相对于版面或由 offsetParent 属性指定的父坐标的计算左侧位置 offsetTop:获取对象相对于版面或由 offsetTop 属性指定的父坐标的计算顶端位置 这几个是原生js的,自己对应到jQ。 2.代码2.1 html代码123456789101112131415161718 <div class="side-nav"> <span class="active">1</span> <span>2</span> <span>3</span> <span>4</span> <span>5</span> <span>6</span> <span>7</span></div><div class="floor floor-01"></div><div class="floor floor-02"></div><div class="floor floor-03"></div><div class="floor floor-04"></div><div class="floor floor-05"></div><div class="floor floor-06"></div><div class="floor floor-07"></div><div class="footer"></div> 2.2 css代码12345678910111213141516171819202122232425262728293031*{margin:0;padding:0;} .side-nav{ width:50px; position:fixed; left:0; top:20%; background:chocolate; } .side-nav span{ display:block; width:50px; height:50px; text-align: center; line-height: 50px; color:#FFF; cursor: pointer; } .side-nav .active{ border:2px solid #FFF; } .floor{ width:100%; height:400px; margin-bottom: 20px; background: pink; } .footer{ width:100%; height:300px; background: pink; } 2.3 js代码12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152 $(function(){ //给一个颜色数组 var colorArr = ['red','orange','yellow','green','blue','cyan','purple']; //便利给各个导航“span”元素,和各个对应的楼层栏目元素 for(i=0;i<$('.side-nav span').length;i++){ $('.side-nav span').eq(i).css({background:colorArr[i]}); $('.floor').eq(i).css({background:colorArr[i]}); } //安利一下知识点 //arr.push() //push() 方法可把它的参数顺序添加到 arrayObject 的尾部。它直接修改 arrayObject,而不是创建一个新的数组。 //offset() //JQ中获取匹配元素在当前视口的相对偏移。返回的对象包含两个整型属性:top 和 left。此方法只对可见元素有效。 //获取各个楼层的距离浏览器上部偏移量,并放入数组 var divTopArr = []; for(var i=0;i<$('.floor').length;i++){ divTopArr.push($('.floor').eq(i).offset().top); } //给导航每个栏目按钮添加点击事件,点击导航上的每个栏目按钮,html(body)元素滑动到对应的栏目 $('.side-nav span').click(function(){ $('body,html').animate({scrollTop:divTopArr[$(this).index()]+'px'}); $('.side-nav span').removeClass('active'); $(this).addClass('active'); }) //添加页面滚轮滚动事件, $(window).scroll(function(){ //获取获取页面当前已经滚动的scrollTop值 var scrollTop = $(window).scrollTop(); //divTopArr 遍历每一个楼层或者每个楼层对应的按钮 for(i=0;i<$('.side-nav span').length;i++){ //判断当前页面已经滚动的top值是否大于最后一个楼层top偏移量(最后一个要拿出来单算) if(scrollTop < divTopArr[divTopArr.length-1]){// 给一个循环动态判断的条件,若当前scrollTop值大于数组的arr[i],且小于arr[i+1],就对应的栏目按钮添加样式 if(scrollTop >= divTopArr[i] && scrollTop < divTopArr[i+1]){ $('.side-nav span').removeClass('active'); $('.side-nav span').eq(i).addClass('active'); } }else{ //若当前scrollTop值大于数组的arr[length-1](即数组的最后一个值,最后一个栏目的offsetTop), $('.side-nav span').removeClass('active'); $('.side-nav span').eq(divTopArr.length-1).addClass('active'); } } }) }) 4、在线演示在线演示在一些大型电商页面上,由于页面过于长,所以设计人员喜欢在侧边加上一个导航,然后点击可以快速导航到各个栏目,比如京东商城,这样不仅方便快捷,而且美观。 1.基本思路相比轮播图,电梯导航貌似简单得多。获取所有楼层的offsetTop值,放在一个数组里面。利用滚动事件监听window.offsetTop,并且判断是在数组中那两个数值区间之间,做出相应的效果。安利几个小内容 1234567scrollHeight: 获取对象的滚动高度。 scrollLeft:设置或获取位于对象左边界和窗口中目前可见内容的最左端之间的距离 scrollTop:设置或获取位于对象最顶端和窗口中可见内容的最顶端之间的距离 scrollWidth:获取对象的滚动宽度 offsetHeight:获取对象相对于版面或由父坐标 offsetParent 属性指定的父坐标的高度 offsetLeft:获取对象相对于版面或由 offsetParent 属性指定的父坐标的计算左侧位置 offsetTop:获取对象相对于版面或由 offsetTop 属性指定的父坐标的计算顶端位置 这几个是原生js的,自己对应到jQ。 2.代码2.1 html代码123456789101112131415161718 <div class="side-nav"> <span class="active">1</span> <span>2</span> <span>3</span> <span>4</span> <span>5</span> <span>6</span> <span>7</span></div><div class="floor floor-01"></div><div class="floor floor-02"></div><div class="floor floor-03"></div><div class="floor floor-04"></div><div class="floor floor-05"></div><div class="floor floor-06"></div><div class="floor floor-07"></div><div class="footer"></div> 2.2 css代码12345678910111213141516171819202122232425262728293031*{margin:0;padding:0;} .side-nav{ width:50px; position:fixed; left:0; top:20%; background:chocolate; } .side-nav span{ display:block; width:50px; height:50px; text-align: center; line-height: 50px; color:#FFF; cursor: pointer; } .side-nav .active{ border:2px solid #FFF; } .floor{ width:100%; height:400px; margin-bottom: 20px; background: pink; } .footer{ width:100%; height:300px; background: pink; } 2.3 js代码12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152 $(function(){ //给一个颜色数组 var colorArr = ['red','orange','yellow','green','blue','cyan','purple']; //便利给各个导航“span”元素,和各个对应的楼层栏目元素 for(i=0;i<$('.side-nav span').length;i++){ $('.side-nav span').eq(i).css({background:colorArr[i]}); $('.floor').eq(i).css({background:colorArr[i]}); } //安利一下知识点 //arr.push() //push() 方法可把它的参数顺序添加到 arrayObject 的尾部。它直接修改 arrayObject,而不是创建一个新的数组。 //offset() //JQ中获取匹配元素在当前视口的相对偏移。返回的对象包含两个整型属性:top 和 left。此方法只对可见元素有效。 //获取各个楼层的距离浏览器上部偏移量,并放入数组 var divTopArr = []; for(var i=0;i<$('.floor').length;i++){ divTopArr.push($('.floor').eq(i).offset().top); } //给导航每个栏目按钮添加点击事件,点击导航上的每个栏目按钮,html(body)元素滑动到对应的栏目 $('.side-nav span').click(function(){ $('body,html').animate({scrollTop:divTopArr[$(this).index()]+'px'}); $('.side-nav span').removeClass('active'); $(this).addClass('active'); }) //添加页面滚轮滚动事件, $(window).scroll(function(){ //获取获取页面当前已经滚动的scrollTop值 var scrollTop = $(window).scrollTop(); //divTopArr 遍历每一个楼层或者每个楼层对应的按钮 for(i=0;i<$('.side-nav span').length;i++){ //判断当前页面已经滚动的top值是否大于最后一个楼层top偏移量(最后一个要拿出来单算) if(scrollTop < divTopArr[divTopArr.length-1]){// 给一个循环动态判断的条件,若当前scrollTop值大于数组的arr[i],且小于arr[i+1],就对应的栏目按钮添加样式 if(scrollTop >= divTopArr[i] && scrollTop < divTopArr[i+1]){ $('.side-nav span').removeClass('active'); $('.side-nav span').eq(i).addClass('active'); } }else{ //若当前scrollTop值大于数组的arr[length-1](即数组的最后一个值,最后一个栏目的offsetTop), $('.side-nav span').removeClass('active'); $('.side-nav span').eq(divTopArr.length-1).addClass('active'); } } }) }) 4、在线演示在线演示","categories":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/categories/JavaScript/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/tags/JavaScript/"}]},{"title":"轮播图(4)--基于JQ的3D轮播设计与实现","slug":"javascript-轮播图(4)--基于JQ的3D轮播设计与实现","date":"2016-08-24T14:24:45.000Z","updated":"2016-08-24T14:51:02.000Z","comments":true,"path":"2016/08/24/javascript-轮播图(4)--基于JQ的3D轮播设计与实现/","link":"","permalink":"https://blog.magicyou.cn/2016/08/24/javascript-%E8%BD%AE%E6%92%AD%E5%9B%BE%EF%BC%884%EF%BC%89--%E5%9F%BA%E4%BA%8EJQ%E7%9A%843D%E8%BD%AE%E6%92%AD%E8%AE%BE%E8%AE%A1%E4%B8%8E%E5%AE%9E%E7%8E%B0/","excerpt":"前面已经有三个类型的轮播,已经足够使用,现在介绍一个相对较为炫酷的3D旋转轮播图。3D轮播就原理来说,特别简单,天色不早了,开始造轮子","text":"前面已经有三个类型的轮播,已经足够使用,现在介绍一个相对较为炫酷的3D旋转轮播图。3D轮播就原理来说,特别简单,天色不早了,开始造轮子 1、思路1.1 css样式布局介绍一下上图的布局思路上图所示,容器里面有五个小容器,五个小容器一定要是绝对定位,用top和left值来逐个定位,顺序就是上图的标号从一到五(按照自己的需要修改容器个数,这里用五个图举例),当然一定要记得父级元素相对定位,“子绝父相”哪里都需要。 1.2 js的大致思路自动循环轮播的图片,当前图片向左滑动,下一张图片就紧接着向左滑动进入可视区域banner,以此类推,用全局索引实现循环来回播放,当然需要借助animate()方法和css()方法。大致流程如下:这里要用到一个相对高端的东西,JSON。其实也没那么玄乎,无非就是一个多个对象组成的数组,把上面每个盛放图片的容器的每个css属性逐个都放在对象里面,再把对象放在一个数组里面,json数组好了。然后就是利用原生js内数组对象内置函数的使用,什么 12345 arr.push(),arr.shift(),arr.unshift(),arr.pop():pop() 删除并返回数组的最后一个元素push() 向数组的末尾添加一个或更多元素,并返回新的长度。shift() 删除并返回数组的第一个元素unshift() 向数组的开头添加一个或更多元素,并返回新的长度。 点击下一张按钮一次,就将第一个对象追加到数组之后点击上一张按钮一次,就将最后一个对象添加到数组之前这样每次点击完就会将数组重新生成,数组的每个对象数组就得重新往每个盛放图片的容器上遍历一次,利用animate()方法,就呈现出动态效果。 2、开搞2.1 html代码12345678910111213<div class="wrap" id="wrap"> <ul> <li><img src="./image/1.jpg"></li> <li><img src="./image/2.jpg"></li> <li><img src="./image/3.jpg"></li> <li><img src="./image/4.jpg"></li> <li><img src="./image/5.jpg"></li> </ul> <span class="ctrl"> <button class="prev"><</button> <button class="next">></button> </span></div> 2.2 css代码1234567891011121314151617181920212223242526272829303132333435363738*{padding:0;margin:0;list-style:none;} .wrap{ width:1100px; height:500px; margin:0 auto; background-color:#ccc; position:relative; } .wrap ul{ position:relative; } .wrap ul li{ position: absolute; top:0; left:0; } .wrap ul li img{ width:100%; } .ctrl{ display:block; width:100%; position:absolute; z-index:99; top:300px; } .ctrl button{ width:30px; height:50px; } .prev{ float:left; margin-left:30px; } .next{ float:right; margin-right:30px; } 2.3 js代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384$(function(){ var json=[ {//图1 top:'0px', left:'200px', width:'300px', height:'187px', zIndex:6, opacity:0.2 }, {//图2 top:'88px', left:'75px', width:'400px', height:'250px', zIndex:8, opacity:0.8 }, {//图3 top:'188px', left:'300px', width:'500px', height:'312px', zIndex:10, opacity:1 }, {//图4 top:'88px', left:'625px', width:'400px', height:'250px', zIndex:8, opacity:0.8 }, {//图5 top:'0px', left:'600px', width:'300px', height:'187px', zIndex:6, opacity:0.2 } ]; var jieliu=true;// 封装一个函数,将json每个数据通过遍历加在每个'li'容器上 function addCss(){ for(var i in json){ $('#wrap ul li').eq(i).css({zIndex:json[i].zIndex}); $('#wrap ul li').eq(i).animate({ top:json[i].top, left:json[i].left, width:json[i].width, height:json[i].height, opacity:json[i].opacity },'slow',function(){ jieliu=true; }); } } //页面打开先调用一次,页面不会乱 addCss(); $('.prev').click(function(){ if(jieliu==true){ jieliu=false; //json.shift(),删除数组的第一个值,并返回第一个值 //json.push(json.shift())将返回的第一个值追加到数组末尾 json.push(json.shift()); //重新遍历 addCss(); } }); $('.next').click(function(){ if(jieliu==true){ jieliu=false; //json.pop(),删除数组的最后个值,并返回最后一个值 //json.unshift(json.pop())将返回的最后一个值添加到数组最前面 json.unshift(json.pop()); //重新遍历 addCss(); } });}); 3、注意记得要引入jq,否则无法运行;防止按钮连续点击的优化前面的轮播都有提到,不做阐述。 4、在线演示在线演示","categories":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/categories/JavaScript/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/tags/JavaScript/"}]},{"title":"轮播图(3)--基于jQ的左右缓动焦点究极版","slug":"javascript-轮播图(3)--基于jQ的左右缓动焦点究极版","date":"2016-08-17T15:24:02.000Z","updated":"2016-08-17T14:39:02.000Z","comments":true,"path":"2016/08/17/javascript-轮播图(3)--基于jQ的左右缓动焦点究极版/","link":"","permalink":"https://blog.magicyou.cn/2016/08/17/javascript-%E8%BD%AE%E6%92%AD%E5%9B%BE%EF%BC%883%EF%BC%89--%E5%9F%BA%E4%BA%8EjQ%E7%9A%84%E5%B7%A6%E5%8F%B3%E7%BC%93%E5%8A%A8%E7%84%A6%E7%82%B9%E7%A9%B6%E6%9E%81%E7%89%88/","excerpt":"上回埋下的伏笔今天来补上,前两个轮播图显然不太好用(但是在一些场景下还是可以用的,比如不需要焦点的左右轮播,可以用轮播图(2),嗨淘的轮播图,是淡入淡出的轮播图(1),网上还是有很多同款轮播图的),今天这个轮播还是左右滑动的轮播图,但是在体验上相对最为优秀,尤其是对焦点点击效果的处理,绝对好用。","text":"上回埋下的伏笔今天来补上,前两个轮播图显然不太好用(但是在一些场景下还是可以用的,比如不需要焦点的左右轮播,可以用轮播图(2),嗨淘的轮播图,是淡入淡出的轮播图(1),网上还是有很多同款轮播图的),今天这个轮播还是左右滑动的轮播图,但是在体验上相对最为优秀,尤其是对焦点点击效果的处理,绝对好用。 1、思路我知道没有图你不会来的,嘿嘿 1.1 css样式布局介绍一下上图的布局思路容器banner当然有个相对固定的宽高(说相对固定是针对响应式布局),ul的大小和最大的容器一致,不过有个相对定位属性,里面的每个盛放img的li宽高当然都和ul相同(按照自己需要设定),所有的li都绝对定位,第一个li放在ul中,其余的li都用相对定位定于容器外部的正右边, 1.2 js的大致思路自动循环轮播的图片,当前图片向左滑动,下一张图片就紧接着向左滑动进入可视区域banner,以此类推,用全局索引实现循环来回播放,当然需要借助animate()方法和css()方法。大致流程如下:①自动轮播和点击下一张流程当前图片(index)滑向左边,移出可视区域->下一张图片(index+1)用css()方法回到最右边,做滑动准备->下一张图片(index+1)滑向容器,移入可视区域②点击上一张流程当前图片(index)滑向右边,移出可视区域->下一张图片(index-1)用css()方法回到最左边,做滑动准备->下一张图片(index-1)滑向容器,移入可视区域当然,自动轮播和点击下一张流程,点击上一张流程都需要判断索引号是否过大或者过小,做出相应的重新赋值准备③点击焦点时当点击的焦点索引大于当前在可视区域图片(li)的索引当前图片(index)滑向左边,移出可视区域->下一张图片(焦点索引对应的图片)用css()方法回到最右边,做滑动准备->下一张图片(焦点索引对应的图片)滑向容器,移入可视区域当点击的焦点索引小于当前在可视区域图片(li)的索引当前图片(index)滑向右边,移出可视区域->下一张图片(焦点索引对应的图片)用css()方法回到最左边,做滑动准备->下一张图片(焦点索引对应的图片)滑向容器,移入可视区域注意:注意animate()方法和css()方法的使用,animate()方法有过渡的效果,用来做滑动效果最合适,css()没有任何过渡效果,用来做不动声色的li转移 2、开搞2.1 html代码<div class="lunbo" id="banner"> <ul> <li><a href="#"><img src="images/1.jpg"></a></li> <li><a href="#"><img src="images/2.jpg"></a></li> <li><a href="#"><img src="images/3.jpg"></a></li> <li><a href="#"><img src="images/4.jpg"></a></li> <li><a href="#"><img src="images/5.jpg"></a></li> <li><a href="#"><img src="images/6.jpg"></a></li> <li><a href="#"><img src="images/7.jpg"></a></li> </ul> <div> <span class="contr"> <!-- <i class="on"></i><i></i><i></i><i></i><i></i><i></i><i></i> --> </span> </div> <div> <button title="上一张" class="prev">&lt;</button> <button title="下一张" class="next">&gt;</button> </div> </div> 2.2 css代码*{padding:0;margin:0;list-style: none;} .lunbo{ width:500px; height:312px; overflow: hidden; margin:0 auto; position:relative; } .lunbo>ul{ height:312px; position: relative; } .lunbo>ul li{ float:left; position: absolute; left:500px; top:0; } .lunbo>ul li:nth-child(1){ left:0px; } .lunbo div:nth-child(2){ width:100%; height:20px; position: absolute; margin:0 auto; top:280px; } .lunbo div span{ display: block; height:30px; width:210px; padding: 0px 10px; margin: 0 auto; } .lunbo>div>span i{ display:block; width:16px; height:16px; border-radius: 50%; background:chocolate; border:2px solid chocolate; margin-right:10px; float:left; cursor:pointer; } .lunbo div span i:last-child{ margin-right: 0; } .lunbo>div>span .on{ background:white; } .lunbo div:nth-child(3){ width:100%; position: absolute; top:110px; left:0; } .lunbo div:nth-child(3) button{ width:40px; height:60px; background: #ccc; color:#FFF; border:0; outline:none; opacity: 0.5; font-size: 30px; cursor:pointer; } .lunbo div:nth-child(3) button:first-child{ float:left; margin-left:10px; } .lunbo div:nth-child(3) button:last-child{ float:right; margin-right:10px; } 2.3 js代码$(function(){ //创建控制小圆点 var is=$('#banner>ul li').length; for(i=0;i<is;i++){ $('<i order='+i+'></i>').appendTo('.contr'); } //给予第一个小圆点class属性 $('#banner .contr i').first().addClass('on'); //定义一个全局索引 var index = 0; //封装一个函数,使当前索引对应的焦点(小圆点)显示不同于其他焦点的状态,方便下边的一次次调用 function focus(){ $('#banner .contr i').removeClass('on'); $('#banner .contr i').eq(index).addClass('on'); } //给每个焦点添加点击事件 $('#banner .contr i').click(function(){ //获取当前点击的焦点的索引值 var thisIndex = $(this).index(); //判断当前点击的焦点索引与现在显示图片的索引关系 //当点击的焦点索引 大于 现在显示图片的索引时 if(thisIndex > index){ $('#banner>ul li').eq(index).animate({left:'-500px'}); $('#banner>ul li').eq(thisIndex).css({left:'500px'}); $('#banner>ul li').eq(thisIndex).animate({left:'0px'}); index = thisIndex; focus(); //当点击的焦点索引 小于 现在显示图片的索引时 }else if(thisIndex < index){ $('#banner>ul li').eq(index).animate({left:'500px'}); $('#banner>ul li').eq(thisIndex).css({left:'-500px'}); $('#banner>ul li').eq(thisIndex).animate({left:'0px'}); index = thisIndex; focus(); } }); //播放函数,主要是为了自动播放的调用 function play(){ $('#banner>ul li').eq(index).animate({left:'-500px'}); index++; if(index == $('#banner>ul li').length){ index = 0; $('#banner>ul li').eq(index).css({left:'500px'}); } $('#banner>ul li').eq(index).css({left:'500px'}); $('#banner>ul li').eq(index).animate({left:'0px'}); focus(); } //定义一个值为null的全局变量 var timer = null; //自动播放函数 function autoPlay(){ timer = setInterval(play,1000); } //添加鼠标移入轮播图容器时,自动播放停止 $('#banner').bind('mouseover',function(){ clearInterval(timer); timer = null; }); //添加鼠标移出轮播图容器时,自动播放继续 $('#banner').bind('mouseout',function(){ autoPlay(); }); //添加一个状态当为true时,可以点击,false时点击无效,这样可以防止多次点击按钮获得更好的客户体验, var statu = true; $('#banner .prev').click(function(){ if(statu == true){ // 给状态值改为false statu = false; //当前图片滑向右边,全局索引index减1 $('#banner>ul li').eq(index).animate({left:'500px'}); index--; //判断此时全局索引是否为或者小于最小索引值,是,则使全局索引值改为最大索引值, if(index < 0){ index = $('#banner>ul li').length-1; } //将改变后的全局索引所对应的图片用css()方法将其“拉”回,最左边。然后用animate()方法滑向当前可视区域(容器区域内) $('#banner>ul li').eq(index).css({left:'-500px'}); //动画完成后将状态值改为true,以便下一次的点击 $('#banner>ul li').eq(index).animate({left:'0px'},function(){statu = true;}); focus(); } }); $('#banner .next').click(function(){ if(statu == true){ // 给状态值改为false statu = false; //当前图片滑向左边,全局索引index加1 $('#banner>ul li').eq(index).animate({left:'-500px'}); index++; //判断此时全局索引是否大于最大索引值(最大索引值是$('#banner>ul li').length-1 ),是,则使全局索引值改为最大索引值, if(index == $('#banner>ul li').length){ index = 0; } //将改变后的全局索引所对应的图片用css()方法将其“拉”回最右边。然后用animate()方法滑向当前可视区域(容器区域内) $('#banner>ul li').eq(index).css({left:'500px'}); //动画完成后将状态值改为true,以便下一次的点击 $('#banner>ul li').eq(index).animate({left:'0px'},function(){statu = true;}); focus(); } }) //页面打开时,默认调用自动轮播函数 autoPlay(); }); 3、注意记得要引入jq,否则无法运行 4、在线演示在线演示","categories":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/categories/JavaScript/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/tags/JavaScript/"}]},{"title":"轮播图(2)--基于JQ的左右缓动","slug":"javascript-轮播图(2)--基于JQ的左右缓动","date":"2016-08-10T15:24:02.000Z","updated":"2016-08-10T13:29:02.000Z","comments":true,"path":"2016/08/10/javascript-轮播图(2)--基于JQ的左右缓动/","link":"","permalink":"https://blog.magicyou.cn/2016/08/10/javascript-%E8%BD%AE%E6%92%AD%E5%9B%BE%EF%BC%882%EF%BC%89--%E5%9F%BA%E4%BA%8EJQ%E7%9A%84%E5%B7%A6%E5%8F%B3%E7%BC%93%E5%8A%A8/","excerpt":"#轮播图(2)上回书我们说到原生js淡入淡出效果的轮播图,这回我们说说左右滑动轮播图,由于需要缓动动画效果,原生js需要封装缓动函数,个人觉得不如JQ来的实在,所以本渣渣直接引入JQ,用JQ相对简单,重要的是思想。","text":"#轮播图(2)上回书我们说到原生js淡入淡出效果的轮播图,这回我们说说左右滑动轮播图,由于需要缓动动画效果,原生js需要封装缓动函数,个人觉得不如JQ来的实在,所以本渣渣直接引入JQ,用JQ相对简单,重要的是思想。 1、思路老规矩先来个大致思路,有个好的战略计划才能获取战斗的成功 1.1 css样式布局有好的布局才能进行js的操控,css大致布局如下图由上图简单的介绍一下布局红色的是最外层容器,固定宽高,老规矩相对定位,还有就是一定要有溢出隐藏(overflow:hidden);ul根据图片的多少宽度相对倍数改变,,同样ul是绝对定位,js就是改变ul的绝对定位的left值控制显示第几张的图片;焦点图的布局和轮播图(1)一样的方法,不知道的去瞅瞅;左右按钮就的容器也是绝对定位,用margin-top或者top值控制距离上部的距离,宽度也是100%;然后各自向左右浮动,margin值控制按钮相距最外层左右的距离; 1.2 js的大致思路js同样要实现动态创建和图片相同个数的焦点图,计时器控制ul的left值,从而动态显示最外层里所显示的图片,加上溢出隐藏,就成了动态轮播图 2、开搞2.1 html代码<div class="banner" id="banner"> <ul> <li><a href="#"><img src="images/1.jpg"></a></li> <li><a href="#"><img src="images/2.jpg"></a></li> <li><a href="#"><img src="images/3.jpg"></a></li> <li><a href="#"><img src="images/4.jpg"></a></li> <li><a href="#"><img src="images/5.jpg"></a></li> <li><a href="#"><img src="images/6.jpg"></a></li> <li><a href="#"><img src="images/7.jpg"></a></li> </ul> <span class="order"> <!-- <i class="on"></i><i></i><i></i><i></i><i></i><i></i><i></i> --> </span> <div class="ctrl"> <button title="上一张" class="prev">&lt;</button> <button title="下一张" class="next">&gt;</button> </div> </div> 2.2 css代码*{padding:0;margin:0;list-style: none;} .banner{ width:500px; height:312px; overflow: hidden; margin:0 auto; position:relative; } .banner>ul{ height:312px; width:3500px; position: absolute; left:0; top:0; } .banner>ul li{ float:left; width:500px; } .order{ display: block; height:30px; width:100%; position: absolute; top:90%; left:0; text-align: center; } .order i{ display:inline-block; width:16px; height:16px; border-radius: 50%; background:chocolate; border:2px solid chocolate; margin-right:10px; cursor:pointer; } .order i:last-child{ margin-right: 0; } .order .on{ background:white; } .ctrl{ width:100%; height:60px; position: absolute; top:120px; left:0; } .ctrl button{ width:40px; height:60px; background: #ccc; color:#FFF; border:0; outline:none; opacity: 0.5; font-size: 30px; cursor:pointer; } .ctrl .prev{ float:left; margin-left:10px; } .ctrl .next{ float:right; margin-right:10px; } 2.3 js代码<script src="./js/jquery-3.1.0.min.js" type="text/javascript"></script> <script> $(function(){ //创建控制小圆点 var imgLis = $('#banner>ul li'); console.log(imgLis.length); for(i=0;i<imgLis.length;i++){ $('<i order='+i+'></i>').appendTo('.order'); } //在第一个小圆点添加类名“on” $('.order i').eq(0).addClass('on'); //将放置图片的容器ul里第一份”li“复制一份,追加到ul后面 imgLis.first().clone().appendTo('#banner ul'); console.log(imgLis.length); $('#banner>ul').width($('#banner ul li').width()*(imgLis.length+1)); var num = 0; var timer = null; //状态预设,是为防止用户连续点击左右按钮,导致短时间内图片切换频率过高而犯神经,程序反应不来 var state = true; function play(){ //从左向右时,判断num,是否大于最大值 if(num < 0){ num = imgLis.length-1; $('#banner>ul').css({left:-imgLis.length*500+'px'}); } //从右向左时,判断num,是否大于最大值 if(num > imgLis.length){ num = 1; $('#banner>ul').css({left:'0'}); } var left = num*500; $('#banner>ul').animate({left:-left+'px'},function(){ state = true; }); //焦点事件 $('.order i').removeClass('on'); $('.order i').eq(num).addClass('on'); if(num >= imgLis.length){ $('.order i').first().addClass('on'); } } //自动播放 function autoPlay(){ timer = setInterval(function(){ num++; play(); },1000); } //添加鼠标移入暂停,移出继续事件 $('#banner').mouseover( function(){ clearInterval(timer); timer = null; } ).mouseout( function(){ autoPlay(); } ); //给左右按钮添加点击事件 $('.prev').click(function(){ if(state == true){ state = false; num--; console.log(num); play(); } }); $('.next').click(function(){ if(state == true){ state = false; num++; play(); } }); //给焦点添加点击事件 $('.order i').click(function(){ num = $(this).index() play(); }); autoPlay(); }); </script> 3、注意记得要引入jq,否则无法运行 4、在线演示在线演示","categories":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/categories/JavaScript/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/tags/JavaScript/"}]},{"title":"轮播图(1)--原生js的淡入淡出","slug":"javascript-轮播图(1)--原生js的淡入淡出","date":"2016-08-03T13:24:02.000Z","updated":"2016-08-03T14:23:02.000Z","comments":true,"path":"2016/08/03/javascript-轮播图(1)--原生js的淡入淡出/","link":"","permalink":"https://blog.magicyou.cn/2016/08/03/javascript-%E8%BD%AE%E6%92%AD%E5%9B%BE%EF%BC%881%EF%BC%89--%E5%8E%9F%E7%94%9Fjs%E7%9A%84%E6%B7%A1%E5%85%A5%E6%B7%A1%E5%87%BA/","excerpt":"轮播图在网页中随处可见,几乎所有的大大小小的网站都有各式各样的轮播图,今天本渣渣在这里为小白们做一个最简单的轮播图效果。当然比较实用。","text":"轮播图在网页中随处可见,几乎所有的大大小小的网站都有各式各样的轮播图,今天本渣渣在这里为小白们做一个最简单的轮播图效果。当然比较实用。 1.思路对于js特效,有一个正确的思路,事情就成功的了一半,剩下的一半就是代码加调试。 1.1、明确思路你要做一个什么样的轮播图,是一直从右向左的无缝滚动,还是一张又一张淡入淡出,还是,一张又一张的从右向左逐张滑动出现等等等(这几种轮播图都会在下几篇轮播图系列一个个介绍),明确下来之后就开始造!轮!子! 1.2、思路:(1)html的布局:首先来个最外层容器,往里面放两个子元素,一个是用来放图片,另一个自然是放焦点;(2)css样式设置最外层的容器添加上相对定位,里面的放图片的容器用相对定位,里面的每个图片元素或者放图片的元素都用绝对定位,并且top和left属性值都为‘0’。放焦点的容器用绝对定位,防止出现叠放元素引起的覆盖,也是为了让焦点能更好控制布局,(注意:焦点一定要用”display:inline-block”,这样是为了让焦点都能自动居中,他的父级元素当然要有”text-align:center”,这样可以让不管有多少个焦点都能自动让焦点居中,为了好看嘛)(3)js部分实现得准备工作,用js创建相应个数的焦点(这个我认为很有必要),然后就是用排他思想(先设置所有同类元素,再设置其中某一个使其不同于其他同类元素),和选项卡的思想一样,利用全局变量,定时器循环播放,so easy!代码中穿插相对详细备注说明 2.代码2.1 先来摆上要用的html代码,下面的css和js都要围绕html展开的<div class="banner" id="banner1"> <ul id="img" class="img"> <li class="on"><img src="./img/1.jpg"></li> <li><img src="./img/2.jpg"></li> <li><img src="./img/3.jpg"></li> <li><img src="./img/4.jpg"></li> </ul> <ul id="order" class="order"> <!-- <li class="on">1</li> <li>2</li> <li>3</li> <li>4</li> --> </ul> </div> 2.2样式工欲善其事必先利其器。首先布局得合理,必须得合理,绝对定位一定要有,当然,被赋予绝对定位元素的父级元素得有相对定位。(和二级菜单的相对套绝对是一样的道理),要不然你的页面就会乱掉。 *{ padding:0; margin:0; list-style: none; font-family: "微软雅黑"; outline: none; } .banner{ width:720px; height:480px; margin:50px auto; position:relative; } .img>li{ position:absolute; transition: all 1s ease; opacity:0; } .order{ width:100%; position:absolute; top:430px; text-align: center; } .order li{ display:inline-block; width:20px; height:20px; background: white; border-radius: 50%; margin: 0 15px; cursor: pointer; } #img .on{ display:block; opacity:1; } #order .on{ background: chocolate; } 2.3高端大气的js部分(请允许装个X)var banner = document.getElementById('banner'); var img = document.getElementById('img'); var lis = img.getElementsByTagName('li'); var order = document.getElementById('order'); //根据上面图片的数量创建相应个数的焦点 for(i=0;i<lis.length;i++){ var creLi = document.createElement('li'); order.appendChild(creLi); } //获取焦点事件源,并将第一个添加类(class="on") var orderLis = order.getElementsByTagName('li'); orderLis[0].className = 'on'; //前期准备工作结束,开始正式的主要部分,让它动起来 var num = 0; var xh = null; function play(num){ for(i=0;i<lis.length;i++){ lis[i].className = ''; orderLis[i].className = ''; } lis[num].className = 'on'; orderLis[num].className = 'on'; } //自动播放 function autoPlay(){ xh = setInterval(function(){ num++; if(num >= lis.length){ num = 0; } play(num); console.log(num); },1000); } autoPlay(); //添加鼠标移入暂停,移出继续轮播事件 banner.onmouseover = function(){ clearInterval(xh); xh = null; } banner.onmouseout = function(){ autoPlay(); } //给焦点添加实时监控事件,鼠标移到哪个焦点,就显示那个对应的图片 for(i=0;i<orderLis.length;i++){ //万物皆对象,遍历出的所有orderLis都是一个个单独的对象,给每个orderLis添加index属性,利用它记忆每个orderLis自己的索引号 orderLis[i].index = i; orderLis[i].onmouseover = function(){ //让全局变量num等于此时显示的图片的索引号,防止鼠标离开后继续播放不正常的事情发生 num = this.index; play(this.index); } } 3.完工,是不是so easy 觉得不够好的自行优化一下,这里不再做过多的赘述,以后几篇会介绍如何优化,使其体验更好4、在线演示在线演示","categories":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/categories/JavaScript/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/tags/JavaScript/"}]},{"title":"javascript-购物车的加减","slug":"javascript-购物车的加减","date":"2016-07-14T14:23:02.000Z","updated":"2016-07-15T14:23:04.000Z","comments":true,"path":"2016/07/14/javascript-购物车的加减/","link":"","permalink":"https://blog.magicyou.cn/2016/07/14/javascript-%E8%B4%AD%E7%89%A9%E8%BD%A6%E7%9A%84%E5%8A%A0%E5%87%8F/","excerpt":"对于专注于网站或者前端研究很久的同学来说,电商网站应该是再熟悉不过。毕竟经常拿来练手,电商网站不管是后台还是前端设计制作,绝对是练手的不二选择第一篇博客来个简单的电商网站常见的购物车商品数量加减,不要小看这个功能,对于刚入手js的同学,还是小有难度的","text":"对于专注于网站或者前端研究很久的同学来说,电商网站应该是再熟悉不过。毕竟经常拿来练手,电商网站不管是后台还是前端设计制作,绝对是练手的不二选择第一篇博客来个简单的电商网站常见的购物车商品数量加减,不要小看这个功能,对于刚入手js的同学,还是小有难度的 首先简单的html按钮及表单<div> <input type="button" class="jian" value="-"/> <input type="text" class="num" value="1"/> <input type="button" class="jia" value="+"/> </div> <div> <input type="button" class="jian" value="-"/> <input type="text" class="num" value="1"/> <input type="button" class="jia" value="+"/> </div> <div> <input type="button" class="jian" value="-"/> <input type="text" class="num" value="1"/> <input type="button" class="jia" value="+"/> </div> <div> <input type="button" class="jian" value="-"/> <input type="text" class="num" value="1"/> <input type="button" class="jia" value="+"/> </div> <div> <input type="button" class="jian" value="-"/> <input type="text" class="num" value="1"/> <input type="button" class="jia" value="+"/> </div> <div> <input type="button" class="jian" value="-"/> <input type="text" class="num" value="1"/> <input type="button" class="jia" value="+"/> </div> 给补充个小东西原生js获取同级节点元素的方法: 先获取父节点对象 然后再获取父节点对象的子元素 这样就获取了包括原节点的所有同级节点元素 代码如下: var outer=this.parentNode; var inner=outer.children; (不要诧异为啥是this,获取所点击节点本节点对象) 然后就是jq获取方法: jq就简单多了,毕竟jq的诞生就是为了方便快捷,原理一样,只不过可以直接获取需要的某个通同级节点元素 代码如下: var num = $(this).parent().children('.num'); 然后的步骤就好说了然后就是直接对相应的数值显示(商品数量)做加减 原生代码: 加:inner[1].value++; 减:--inner[1].value; 加加减减最方便,直接转化为number类型做运算 jq代码: 加:num.val(num.val()*1+1); 减:num.val(num.val()*1-1); “num.val()*1”是为了将string类型直接转化为number类型 ###结尾 别忘了给加个判断,不要让数值减到比1还小。这个简单,不做过多描述。 ####源代码:原生js: <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>购物车商品数量加减</title> </head> <body> <div> <input type="button" class="jian" value="-"/> <input type="text" class="num" value="1"/> <input type="button" class="jia" value="+"/> </div> <div> <input type="button" class="jian" value="-"/> <input type="text" class="num" value="1"/> <input type="button" class="jia" value="+"/> </div> <div> <input type="button" class="jian" value="-"/> <input type="text" class="num" value="1"/> <input type="button" class="jia" value="+"/> </div> <div> <input type="button" class="jian" value="-"/> <input type="text" class="num" value="1"/> <input type="button" class="jia" value="+"/> </div> <div> <input type="button" class="jian" value="-"/> <input type="text" class="num" value="1"/> <input type="button" class="jia" value="+"/> </div> <script> var jia=document.getElementsByClassName('jia'); var jian=document.getElementsByClassName('jian'); for(i=0;i<jia.length;i++){ jia[i].onclick=function(){ var outer=this.parentNode; var inner=outer.children; inner[1].value++; } jian[i].onclick=function(){ var outer=this.parentNode; var inner=outer.children; if(inner[1].value>1){ --inner[1].value; } } } </script> </body> </html> jq部分: <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>购物车商品数量加减</title> <script src="./jquery-3.1.0.min.js" type="text/javascript"></script> </head> <body> <div> <input type="button" class="jian" value="-"/> <input type="text" class="num" value="1"/> <input type="button" class="jia" value="+"/> </div> <div> <input type="button" class="jian" value="-"/> <input type="text" class="num" value="1"/> <input type="button" class="jia" value="+"/> </div> <div> <input type="button" class="jian" value="-"/> <input type="text" class="num" value="1"/> <input type="button" class="jia" value="+"/> </div> <div> <input type="button" class="jian" value="-"/> <input type="text" class="num" value="1"/> <input type="button" class="jia" value="+"/> </div> <div> <input type="button" class="jian" value="-"/> <input type="text" class="num" value="1"/> <input type="button" class="jia" value="+"/> </div> <div> <input type="button" class="jian" value="-"/> <input type="text" class="num" value="1"/> <input type="button" class="jia" value="+"/> </div> <script> $(document).ready(function(){ $('.jian').click(function(){ var num = $(this).parent().children('.num'); if(num.val()>1) num.val(num.val()*1-1); }); $('.jia').click(function(){ var num = $(this).parent().children('.num'); num.val(num.val()*1+1); }); }) </script> </body> 第一次发博客,格式什么的还不讲究,将就着看吧","categories":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/categories/JavaScript/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/tags/JavaScript/"}]}],"categories":[{"name":"前端","slug":"前端","permalink":"https://blog.magicyou.cn/categories/%E5%89%8D%E7%AB%AF/"},{"name":"Vue","slug":"Vue","permalink":"https://blog.magicyou.cn/categories/Vue/"},{"name":"计算机基础","slug":"计算机基础","permalink":"https://blog.magicyou.cn/categories/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/"},{"name":"算法","slug":"算法","permalink":"https://blog.magicyou.cn/categories/%E7%AE%97%E6%B3%95/"},{"name":"Linux","slug":"Linux","permalink":"https://blog.magicyou.cn/categories/Linux/"},{"name":"JAVA","slug":"JAVA","permalink":"https://blog.magicyou.cn/categories/JAVA/"},{"name":"树莓派","slug":"树莓派","permalink":"https://blog.magicyou.cn/categories/%E6%A0%91%E8%8E%93%E6%B4%BE/"},{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/categories/JavaScript/"},{"name":"uni-app","slug":"uni-app","permalink":"https://blog.magicyou.cn/categories/uni-app/"},{"name":"Python","slug":"Python","permalink":"https://blog.magicyou.cn/categories/Python/"},{"name":"PHP","slug":"PHP","permalink":"https://blog.magicyou.cn/categories/PHP/"},{"name":"MacOS","slug":"MacOS","permalink":"https://blog.magicyou.cn/categories/MacOS/"}],"tags":[{"name":"React","slug":"React","permalink":"https://blog.magicyou.cn/tags/React/"},{"name":"Next.js","slug":"Next-js","permalink":"https://blog.magicyou.cn/tags/Next-js/"},{"name":"Vue","slug":"Vue","permalink":"https://blog.magicyou.cn/tags/Vue/"},{"name":"计算机基础","slug":"计算机基础","permalink":"https://blog.magicyou.cn/tags/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/"},{"name":"算法","slug":"算法","permalink":"https://blog.magicyou.cn/tags/%E7%AE%97%E6%B3%95/"},{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.magicyou.cn/tags/JavaScript/"},{"name":"react","slug":"react","permalink":"https://blog.magicyou.cn/tags/react/"},{"name":"Linux","slug":"Linux","permalink":"https://blog.magicyou.cn/tags/Linux/"},{"name":"Centos","slug":"Centos","permalink":"https://blog.magicyou.cn/tags/Centos/"},{"name":"JAVA","slug":"JAVA","permalink":"https://blog.magicyou.cn/tags/JAVA/"},{"name":"树莓派","slug":"树莓派","permalink":"https://blog.magicyou.cn/tags/%E6%A0%91%E8%8E%93%E6%B4%BE/"},{"name":"git","slug":"git","permalink":"https://blog.magicyou.cn/tags/git/"},{"name":"微信","slug":"微信","permalink":"https://blog.magicyou.cn/tags/%E5%BE%AE%E4%BF%A1/"},{"name":"微信公众号","slug":"微信公众号","permalink":"https://blog.magicyou.cn/tags/%E5%BE%AE%E4%BF%A1%E5%85%AC%E4%BC%97%E5%8F%B7/"},{"name":"uni-app","slug":"uni-app","permalink":"https://blog.magicyou.cn/tags/uni-app/"},{"name":"CSS3","slug":"CSS3","permalink":"https://blog.magicyou.cn/tags/CSS3/"},{"name":"微信小程序","slug":"微信小程序","permalink":"https://blog.magicyou.cn/tags/%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F/"},{"name":"canvas","slug":"canvas","permalink":"https://blog.magicyou.cn/tags/canvas/"},{"name":"PHP","slug":"PHP","permalink":"https://blog.magicyou.cn/tags/PHP/"},{"name":"小程序","slug":"小程序","permalink":"https://blog.magicyou.cn/tags/%E5%B0%8F%E7%A8%8B%E5%BA%8F/"},{"name":"Nginx","slug":"Nginx","permalink":"https://blog.magicyou.cn/tags/Nginx/"},{"name":"Apache","slug":"Apache","permalink":"https://blog.magicyou.cn/tags/Apache/"},{"name":"Python","slug":"Python","permalink":"https://blog.magicyou.cn/tags/Python/"},{"name":"scrapy","slug":"scrapy","permalink":"https://blog.magicyou.cn/tags/scrapy/"},{"name":"MacOS","slug":"MacOS","permalink":"https://blog.magicyou.cn/tags/MacOS/"},{"name":"other","slug":"other","permalink":"https://blog.magicyou.cn/tags/other/"},{"name":"HTML5","slug":"HTML5","permalink":"https://blog.magicyou.cn/tags/HTML5/"},{"name":"jQuery","slug":"jQuery","permalink":"https://blog.magicyou.cn/tags/jQuery/"},{"name":"CSS","slug":"CSS","permalink":"https://blog.magicyou.cn/tags/CSS/"},{"name":"HTML","slug":"HTML","permalink":"https://blog.magicyou.cn/tags/HTML/"}]}