cool_油

cool_油 查看完整档案

武汉编辑武汉理工大学  |  信息管理与信息系统 编辑小米  |  软件开发工程师 编辑 www.zhouht.site 编辑
编辑

热爱生活&&享受技术&&持续学习

个人动态

cool_油 收藏了文章 · 2018-05-26

我是如何一步步“改造”redux的

从Vue换到React+Redux进行开发已经有半年多的时间,总的来说体验是很好的,对于各种逻辑和业务组件的抽象实在是方便的不行,高阶组件,洋葱模型等等给我带来了很多编程思想上的提升。但是在使用Redux开发的过程中还是感觉不太顺手,本文将阐述我是如何对Redux进行一步步“改造”以适应个人和团队开发需求的。
过程中的示例和结果放在了easy-redux,欢迎star。
原文链接

问题

在使用Redux开发的过程中逐渐发现,虽然我们已经将UI组件和业务组件尽可能的进行抽离,尽可能的保证reduceractions的复用性,
但是我们还是会花费大量的时间来书写近乎相同的代码。尤其是我们组内希望秉承一个原则:尽量将所有的操作及状态修改都交由action来执行,方便我们对问题进行定位。当我在某大型前端交流群里还看到“不用Redux,不想加班”的说法时,不得不感叹,需要做些努力来解决我目前的问题了。

是的,Redux对我来说,太复杂了

针对一个简单的操作,我们需要进行以下步骤来完成:

1.定义action

export const CHANGE_CONDITION = 'CHANGE_CONDITION'

2.定义一个对应的action创建函数

export const changeCondition = condition => ({
  type: CHANGE_CONDITION,
  condition
})

3.引入action, 定义reducer, 在复杂的switch语句中,对对象进行更改

import { CHANGE_CONDITION } from '@actions'

const condition = (state = initCondition, action) => {
  switch(action.type) {
    case CHANGE_CONDITION:
      return ...
    default:
      return state
  }
}

4.在需要时,引入action创建函数, 并将对应的state进行连接

import { changeCondition } from 'actions'
@connect(...)

我只是想做一个简单的状态修改呀!

可能我们会说,这样拆分能够保证我们整个项目的规范化,增强业务的可预测性与错误定位能力。
但是随着项目的不断扩大,每个页面都有一堆action需要我加的时候,实在是让人头痛啊。

而且,针对请求的修改,我们往往要把action拆分成START,SUCCESS,FAILED三种状态,reducer里需要进行三次修改。而且往往
针对这些修改,我们进行的处理都是大致相同的:更新loading状态,更新数据,更新错误等等。

所以说,我们如何在保证redux的设计原则以及项目规范性上,对其进行“简化改造”,是我这里需要解决的问题。

使用middleware简化请求

针对请求的处理,我之前也写过一篇文章优雅地减少redux请求样板代码, 通过封装了一个redux中间件react-fetch-middleware
来对请求代码进行优化。

大致思路如下:

1.action创建函数返回的内容为一个包含请求信息的对象,并包含需要分发的三个action,这三个action可以通过actionCreator进行创建

import { actionCreator } from 'redux-data-fetch-middleware'

// create action types
export const actionTypes = actionCreator('GET_USER_LIST')

export const getUserList = params => ({
  url: '/api/userList',
  params: params,
  types: actionTypes,
  // handle result
  handleResult: res => res.data.list,
  // handle error
  handleError: ...
})

2.在redux中间件中,针对以上格式的action进行处理,首先进行请求,并分发请求开始的action,
在请求成功和失败时,分别分发对应的action

const applyFetchMiddleware = (
  fetchMethod = fetch,
  handleResponse = val => val,
  handleErrorTotal = error => error
) =>
  store => next => action => {
    // 判断action的格式
    if (!action.url || !Array.isArray(action.types)) {
      return next(action)
    }
    // 获取传入的三个action
    const [ START, SUCCESS, FAILED ] = action.types

    // 在不同状态分发action, 并传入loading,error状态
    next({
      type: START,
      loading: true,
      ...action
    })
    return fetchMethod(url, params)
      .then(ret => {
        next({
          type: SUCCESS,
          loading: false,
          payload: handleResult(ret)
        })
      })
      .catch(error => {
        next({
          type: FAILED,
          loading: false,
          error: handleError(error)
        })
      })
  }

3.将reducer进行对应的默认处理,使用reducerCreator创建的函数中自动进行对应处理,并且提供二次处理的机制

const [ GET, GET_SUCCESS, GET_FAILED ] = actionTypes

// 会在这里自动处理分发的三个action
const fetchedUserList = reducerCreator(actionTypes)

const userList = (state = {
  list: []
}, action => {
  // 二次处理
  switch(action.type) {
    case GET_SUCCESS:
      return {
        ...state,
        action.payload
      }
  }
})
export default combineReducers({
  userList: fetchedUserList(userList)
})

再进一步,简化Redux Api

经过前一步对请求的简化,我们已经可以在保证不改变redux原则和书写习惯的基础上,极大的简化请求样板代码。
针对普通的数据处理,我们是不是可以更进一步?

很高兴看到这个库: Rematch
, 对Redux Api进行了极大的简化。

但是有些功能和改进并不是我们想要的,因此我仅对我需要的功能和改进点进行说明,并用自己的方式进行实现。我们来一步步看看
我们需要解决的问题以及如何解决的。

1.冗长的switch语句

针对reducer,我们不希望重复的引用定义的各个action, 并且去掉冗长的switch判断。其实我们可以将其进行反转拆分,将每一个action定义为标准化的reducer, 在其中对state进行处理.

const counter = {
  state: 1,
  reducers: {
    add: (state, payload) => state + payload,
    sub: (state, payload) => state - payload
  }
}

2.复杂的action创建函数

去掉之前的action和action创建函数,直接在actions中进行数据处理,并与对应的reducer进行match

export const addNum = num => dispatch => dispatch('/counter/add', num)

我们会看到,与reducer进行match时,我们使用了'/counter/add'这种命名空间的方式,
目的是在保证其直观性的同时,保证action与其reducer是一一对应的。

我们可以通过增强的combinceReducer进行命名空间的设定:

const counter1 = {
  ...
}
const counter2 = {
  ...
}

const counters = combinceReducer({
  counter1,
  counter2
})

const list = {
  ...
}
// 设置大reducer的根命名空间
export default combinceReducer({
  counters,
  list
}, '/test')

// 我们可以通过这样来访问
dispatch('/test/counters/counter1/add')

3.别忘了请求

针对请求这些异步action,我们可以参考我们之前的修改, dispatch一个对象

export const getList = params => dispatch => {
  return dispatch({
    //对应到我们想要dispatch的命名空间
    action: '/list/getList',
    url: '/api/getList',
    params,
    handleResponse: res => res.data.list,
    handleError: error => error
  })
}

同时,我们在reducer中进行简单的处理即可,依旧可以进行默认的三个状态处理

const list = {
  // 定义reducer头,会自动变为getList(开始请求),getListSuccess,getListFailed
  // 并进行loading等默认处理
  fetch: 'getList'
  state: {
    list: []
  },
  reducers: {
    // 二次处理
    getListSuccess: (state, payload) => ({
      ...state,
      list: payload
    })
  }
}

与项目进行整合

我们会看到,我们已经将redux的api进行了极大的简化,但是依旧保持了原有的结构。目的有以下几点:

  1. 依旧遵循默认原则,保证项目的规范性
  2. 通过约定和命名空间来保证action和reducer的match
  3. 底层还是使用redux实现,这些只不过是语法糖
  4. 保证与老项目的兼容性

原有的数据流变成了这样:
图片描述

因此,我们是在redux的基础上进行二次封装的,我们依然保证了原有的Redux数据流,保证数据的可回溯性,增强业务的可预测性与错误定位能力。这样能极大的保证与老项目的兼容性,所以我们需要做的,只是对action和reducer的转化工作

1.combinceReducer返回原格式的reducer

我们通过新的combinceReducer,将新的格式,转化为之前的reducer格式,并保存各个reducer其和对应的action的命名空间。

代码简单示意:

//获取各reducers里的方法
const actionNames = Object.keys(reducers)
const resultActions = actionNames.map(action => {
  const childNamespace = `${namespace}/${action}`
  // 将action存入namespace
  Namespace.setActionByNamespace(childNamespace)
  return {
    name: Namespace.toAction(childNamespace),
    fn: reducers[action]
  }
})

// 返回默认格式
return (state = inititalState, action) => {
  // 查询action对应的新的reducer里的方法
  const actionFn = resultActions.find(cur => cur.name === action.type)
  if (actionFn) {
    return actionFn.fn && actionFn.fn(state, action.payload)
  }
  return state
}

2.新的action创建函数最终dispatch出原格式的action

我们需要把这样格式的函数,转化成这样

count => dispatch => dispatch('/count/add', count)

//or
params => dispatch => { dispatch('/count/add', 1), dispatch('/count/sub', 2) }

//结果
count => ({ type: 'count_add', payload: count })

这里的处理比较复杂,其实就是改造我们的dispatch函数

action => params => (dispatch, getstate) => {
  const retDispatch = (namespace, payload) => {
    return dispatch({
      type: Namespace.get(namespace),
      payload
    })
  }
  return action(params)(retDispatch, getstate)
}

总结

通过对Redux Api的改造,相当于二次封装,已经很大的简化了目前在项目中的样板代码,并且在项目中很顺畅的使用。

针对整个过程,其实还有几个可以改进的地方:

  • actions的转化过程,交由中间件处理
  • 性能问题,目前相当于多做了一层转化,但是目前影响不大
  • reducer,action复用

有兴趣的话,欢迎探讨~ 附上github easy-redux

查看原文

cool_油 回答了问题 · 2018-03-20

微信上的display:block怎么没有显示出来啊

具体是什么情况呢 题主能不能show代码??

关注 2 回答 1

cool_油 赞了文章 · 2018-03-19

HTTP报文格式、TCP、IP报头,以及连接过程总结

自己总结了一下tcp报头,,ip报头和三次握手四次挥手的过程,以及状态连接图中连接断开的过程,以此记录。

关于TCP、IP报头,以及连接过程总结

TCP报头

图片描述

  1. 首先是源端口号和目标端口号,各占16位,标志的连接的端口号和被连接的端口号,它和IP报头中的源IP以及目的IP一起标识出一条TCP的链接。
  2. 然后是序号seq,这个占32位,增长由1-2^32-1,它由数据字节号增长,增长范围在0~2^32-1,换句话说也就是说对于发送数据的长度计数。
  3. 下来则是确认序号ack,也是占32位,当ACK为1时候有效,它的值一般为seq+1。
  4. 首部长度,占4位,,用来记录TCP的报头长度,它的值可以直接偏移到数据位置。
  5. 后面则是保留了6位,规定不能使用。
  6. URG,占一位,当为1时标志紧急指针有效。
  7. ACK,占一位,当为1时标志ack确认序号有效。
  8. PSH,占一位,当为1时标志紧急传输,也就是说这个包应当不用等待缓冲区放满就可以发送。
  9. RST,占一位,当为1时标志重置连接,用于复位。
  10. SYN,占一位,当为1时标志发起连接请求。
  11. FIN,占一位,当为1时标志发起断开连接的请求。
  12. 窗口大小,占16位,指出接收方的接收窗口的大小,控制流量。
  13. 校验和,占16位,将总TCP报文计算出一个校验值放入其中,用于检验。
  14. 紧急指针,占16位,TCP是面向数据流的协议,但当有时一些应用程序在某些紧急情况下(如在某些连接中进行强制中断),要求在接收方在没有处理完数据之前就能够发送一些紧急数据,这就使得发送方将CODE字段的URG置为1 即紧急指针字段有效,这样可以不必考虑你发送的紧急数据在数据流中的位置,也就是相当于优先级最高,紧急指针指出的是紧急数据在报文段中结束的位置。
  15. 选项,在下面单独说。

TCP报头选项

图片描述

  1. 0:选项结束
  2. 1:空选项,一般用来将报文填充到4字节整数倍
  3. 2:

    • 最大报文段长度选项,通信双方在连接初始化的时候用此选项来协商最大报文段长度MSS。
    • TCP模块一般将MSS设置为(MTU-40)字节(减去TCP报头20字节,减去IP报头20字节),这样携带TCP报文段的IP数据报长度就不会超过MTU,从而避免IP分片。
    • 一般MSS值为1460字节(1500-40)。
  4. 3:

    • 是窗口扩大选项,TCP连接初始化时候双方用该选项来协商接收窗口的扩大因子,在TCP头部中,接收通告窗口大小是用15位来表示的,因此最大为65535字节。但实际TCP模块允许的窗口大小远不止这个数(提高TCP通信吞吐量)。
    • 扩大选项是一个移位数,如果窗口大小为M,移位数为n,那么相当于M*2n字节,也就是M左移n位
  5. 4:确认性选择(Selectibe Acknoewledgment, SACK)选项。TCP通信时,某个报文段丢失则TCP模块会重传被确认的TCP报文段后面所有的后续报文。SACK即是改善这种情况使TCP只发送丢失的报文段,不用发送所有的未确认报文段的技术,在TCP连接初始化时,用该选项来确认是否支持SACK技术
  6. 5:是SACK技术工作时候的选项。该选项告诉发送端本端已经接收并缓存且不连续的数据块,使得发送方检查重发丢失的报文段。
  7. 8:时间戳选项,该选项提供了较准确的计算通信双方之间的回路时间(RTT)的方法,从而为TCP流量控制提供重要信息。

IP报头

图片描述

  1. 版本号,占4位,用来指明IP版本号。
  2. IP头长度,占4位,用来指明IP报头长度值,可以用来偏移到数据位置。
  3. 服务类型,占用8位

    有3bits的优先权字段,现在已经闲置。
    4bitsTOS字段,这四位中只能有一个为1,用来选中选项,分别是最小时延,最大吞吐量,最高可靠性,最小费用。
    1bit弃用。
  4. 总长度,占16位,记录整个报文长度。
  5. 标识,占16位,用来标识数据报序号。
  6. 标志,占3位。

    【2】为1标志这是最后一个分段。
    【1】为1标志这个数据报可分段。
    【0】保留,统一置零。
  7. 片偏移,占13位,,标志这段数据在原本数据中所处的位置。
  8. 生存时间,TTL,占8位,由DNS查询到所访问的目标服务器这个过程来计时,然后保存在DNS缓存中,当DNS缓存过期时再重复这个过程。与其说它是个时间,不如说它更像一个跳数,每经过一个路由,规定路由对于这个字段的值至少减1,当它为零时候这个数据报会被抛弃,以用来处理永远抵达不了的数据。
  9. 协议,占8位,用来指明处理后由哪个上层协议来接收,例如TCP。
  10. 头部校验和,占16位,确保IP报头信息。
  11. 源IP,占用32位,标记发送方的IP地址。
  12. 目的IP,占用32位,标记目标方的IP地址。
  13. 选项,可选,最多40位,会用0填充以保证是32的倍数。

三次握手

图片描述

第一二次连接时候不能加数据,也必须消耗序号。
在第三次握手时候,可以加入数据,那么会消耗序号,但是如果不携带数据的话是不消耗数据的,下次发送时候seq = x + 1。

四次挥手

图片描述

四次挥手这个过程也是可以在三次完成的,也就是将二三合一,服务器同时将ACK和FIN返回给客户端,那么就可以简洁到三次挥手。

状态转移图断开

图片描述

图片描述


加入一个HTTP的报文格式,emmmmm,好像违背了开闭原则哈,连题目都改了。。。。。

HTTP请求报文格式:

图片描述

  1. 请求方法

    GET:请求获取Request——URL所标识的资源
    POST:在Request——URL所标识的资源后附加资源
    HEAD:请求获取由Request——URL所标识的资源的响应消息报头
    PUT:请求服务器存储一个资源,由Request——URL作为其标识
    DELETE:请求服务器删除由Request——URL所标识的资源
    TRACE:请求服务器回送收到的请求信息(用于测试和诊断)
    CONNECT:保留
    OPTIONS:请求查询服务器性能
  2. URL
    URI全名为Uniform Resource Indentifier(统一资源标识),用来唯一的标识一个资源,是一个通用的概念,URI由两个主要的子集URL和URN组成。URL全名为Uniform Resource Locator(统一资源定位),通过描述资源的位置来标识资源。URN全名为Uniform Resource Name(统一资源命名),通过资源的名字来标识资源,与其所处的位置无关,这样即使资源的位置发生变动,其URN也不会变化。
  3. 协议版本
    格式为 HTTP/主版本号.次版本号,常用为:HTTP/1.1 HTTP/1.0
  4. 请求头部

    Host:接受请求的服务器地址,可以是IP或者是域名
    User-Agent:发送请求的应用名称
    Connection:指定与连接相关的属性,例如(Keep_Alive,长连接)
    Accept-Charset:通知服务器端可以发送的编码格式
    Accept-Encoding:通知服务器端可以发送的数据压缩格式
    Accept-Language:通知服务器端可以发送的语言

HTTP响应报文:

图片描述

    1. 协议版本,同请求报文
    2. 状态码,100~199表示请求已收到继续处理,200~299表示成功,300~399表示资源重定向,400~499表示客户端请求出错,500~599表示服务器端出错

      200:响应成功
      302:跳转,重定向
      400:客户端有语法错误
      403:服务器拒绝提供服务
      404:请求资源不存在
      500:服务器内部错误
    1. 响应头部

      Server:服务器应用软件的名称和版本
      Content-Type:响应正文的类型
      Content-Length:响应正文的长度
      Content-Charset:响应正文所使用的编码
      Content-Encoding:响应正文使用的数据压缩格式
      Content-Language:响应正文使用的语言
    查看原文

    赞 2 收藏 5 评论 0

    cool_油 赞了回答 · 2018-03-13

    解决如何处理订单创建后价格变动?

    我思考了一下,看你这个问题的意思应该是说,前端下单的时候看到的时间是15:59,等到真正把数据提交到后端的时候价格已经变成原本正常价格了。

    我梳理了一下,想到了以下做法:

    1. 用一个专门做控价的表存储控价时间段以及价格,下单的时候带上商品的价格以及发起下单的时间,然后到这个控价表里面去做验证,但是这种情况的话,用户可以伪造数据;
    2. 基于1的考虑,用户访问带有控价商品详情页的时候,可以做一套hash生成算法,比如这个hash产生的方式为:页面访问时间戳,毫秒为单位+商品价格+商品控价时间段差;然后进行md5得到hash,并把这个hash进行缓存,下单的时候带上1里说的东西,然后带上这个hash,如果在后台进行hash验证的时候也通过了验证,那么就表示这个用户下单的时候的确是在这个控价范围内。用完后这个hash进行销毁。

    关注 5 回答 4

    cool_油 赞了文章 · 2018-03-05

    zt坑人无数的Redis面试题

    Redis在互联网技术存储方面使用如此广泛,几乎所有的后端技术面试官都要在Redis的使用和原理方面对小伙伴们进行各种刁难。作为一名在互联网技术行业打击过成百上千名【请允许我夸张一下】的资深技术面试官,看过了无数落寞的身影失望的离开,略感愧疚,故献上此文,希望各位读者以后面试势如破竹,永无失败!

    Redis有哪些数据结构?

    字符串String、字典Hash、列表List、集合Set、有序集合SortedSet。

    如果你是Redis中高级用户,还需要加上下面几种数据结构HyperLogLog、Geo、Pub/Sub。

    如果你说还玩过Redis Module,像BloomFilter,RedisSearch,Redis-ML,面试官得眼睛就开始发亮了。

    使用过Redis分布式锁么,它是什么回事?

    先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放。

    这时候对方会告诉你说你回答得不错,然后接着问如果在setnx之后执行expire之前进程意外crash或者要重启维护了,那会怎么样?

    这时候你要给予惊讶的反馈:唉,是喔,这个锁就永远得不到释放了。紧接着你需要抓一抓自己得脑袋,故作思考片刻,好像接下来的结果是你主动思考出来的,然后回答:我记得set指令有非常复杂的参数,这个应该是可以同时把setnx和expire合成一条指令来用的!对方这时会显露笑容,心里开始默念:摁,这小子还不错。

    假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如果将它们全部找出来?

    使用keys指令可以扫出指定模式的key列表。

    对方接着追问:如果这个redis正在给线上的业务提供服务,那使用keys指令会有什么问题?

    这个时候你要回答redis关键的一个特性:redis的单线程的。keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长。

    使用过Redis做异步队列么,你是怎么用的?

    一般使用list结构作为队列,rpush生产消息,lpop消费消息。当lpop没有消息的时候,要适当sleep一会再重试。

    如果对方追问可不可以不用sleep呢?list还有个指令叫blpop,在没有消息的时候,它会阻塞住直到消息到来。

    如果对方追问能不能生产一次消费多次呢?使用pub/sub主题订阅者模式,可以实现1:N的消息队列。

    如果对方追问pub/sub有什么缺点?在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如rabbitmq等。

    如果对方追问redis如何实现延时队列?我估计现在你很想把面试官一棒打死如果你手上有一根棒球棍的话,怎么问的这么详细。但是你很克制,然后神态自若的回答道:使用sortedset,拿时间戳作为score,消息内容作为key调用zadd来生产消息,消费者用zrangebyscore指令获取N秒之前的数据轮询进行处理。

    到这里,面试官暗地里已经对你竖起了大拇指。但是他不知道的是此刻你却竖起了中指,在椅子背后。

    如果有大量的key需要设置同一时间过期,一般需要注意什么?

    如果大量的key过期时间设置的过于集中,到过期的那个时间点,redis可能会出现短暂的卡顿现象。一般需要在时间上加一个随机值,使得过期时间分散一些。

    Redis如何做持久化的?

    bgsave做镜像全量持久化,aof做增量持久化。因为bgsave会耗费较长时间,不够实时,在停机的时候会导致大量丢失数据,所以需要aof来配合使用。在redis实例重启时,会使用bgsave持久化文件重新构建内存,再使用aof重放近期的操作指令来实现完整恢复重启之前的状态。

    对方追问那如果突然机器掉电会怎样?取决于aof日志sync属性的配置,如果不要求性能,在每条写指令时都sync一下磁盘,就不会丢失数据。但是在高性能的要求下每次都sync是不现实的,一般都使用定时sync,比如1s1次,这个时候最多就会丢失1s的数据。

    对方追问bgsave的原理是什么?你给出两个词汇就可以了,fork和cow。fork是指redis通过创建子进程来进行bgsave操作,cow指的是copy on write,子进程创建后,父子进程共享数据段,父进程继续提供读写服务,写脏的页面数据会逐渐和子进程分离开来。

    Pipeline有什么好处,为什么要用pipeline?

    可以将多次IO往返的时间缩减为一次,前提是pipeline执行的指令之间没有因果相关性。使用redis-benchmark进行压测的时候可以发现影响redis的QPS峰值的一个重要因素是pipeline批次指令的数目。

    Redis的同步机制了解么?

    Redis可以使用主从同步,从从同步。第一次同步时,主节点做一次bgsave,并同时将后续修改操作记录到内存buffer,待完成后将rdb文件全量同步到复制节点,复制节点接受完成后将rdb镜像加载到内存。加载完成后,再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程。

    是否使用过Redis集群,集群的原理是什么?

    Redis Sentinal着眼于高可用,在master宕机时会自动将slave提升为master,继续提供服务。

    Redis Cluster着眼于扩展性,在单个redis内存不足时,使用Cluster进行分片存储。

    查看原文

    赞 12 收藏 10 评论 0

    cool_油 回答了问题 · 2018-02-27

    window10下php的exec执行shell脚本的权限问题

    给服务器那个用户执行脚本的权限

    关注 3 回答 2

    cool_油 提出了问题 · 2018-02-26

    不改变内存限制用递归实现斐波拉契

    实现斐波拉契数列,在用php中用递归的时候会出现超过限制内存的错误提醒。
    es6里面有尾递归优化,php里面有什么优化的方式么?

    先贴两种网上找到的解决方案,
    第一种:http://blog.csdn.net/h3305319...
    第二种:https://www.cnblogs.com/zhenb...
    第一种利用高阶函数回调方式经过试验还是不可以,不过第二种方案在实现过程中还是可以满足的。
    如下图:

    clipboard.png

    好的 那么问题来了,
    如何解决不改变内存限制,利用递归实现?
    第一种方案为何不行?
    第二种为什么又可以了??

    关注 3 回答 3

    cool_油 赞了回答 · 2018-02-26

    报这个错“Undefined index: REQUEST_SCHEME”,这是什么情况?

    报错提示:$_SERVER这个数组里没有下标为REQUEST_SCHEME

    原因:PHP语言本身没有提供$_SERVER['REQUEST_SCHEME']
    参考:http://php.net/manual/en/rese...(文档里根本没有它的描述)

    正如你发的Stack Overflow链接(其实那个帖子里已经说的很详细了)里说的:

    it is not reliable

    $_SERVER['REQUEST_SCHEME']只在部分环境里被支持

    解决方案:

    要么改代码,让代码适配环境

    添加逻辑,先检测$_SERVER['REQUEST_SCHEME']存不存在isset($_SERVER['REQUEST_SCHEME']),然后不存在的时候怎么处理

    要么改环境,让环境适配代码

    改成支持$_SERVER['REQUEST_SCHEME']变量

    换环境也好,更改环境的设置使之添加这个变量也好

    关注 3 回答 3

    cool_油 回答了问题 · 2018-02-08

    求助:统计有效评论长度,正则替换怎么写

    /[u4e00-u9fa5]/,匹配出所有中文,然后在根据mb_strlen计算长度。

    关注 4 回答 3

    cool_油 回答了问题 · 2018-02-07

    阿里云导出的数据库用navicat工具导入到本地库为啥不行,总是提示语法错误。

    用navicate会出现问题,我之前导出的时候会发现数据不完整。所以我还是一般会用mysqldump来进行数据导出操作。然后 mysql -uroot –padmin databaseName <d:dump.sql 导入。。。
    还有你说的提示语法错误,能不能来个明确一点的报错信息?不然这样也不能知道navicate导出来的哪里有问题啊

    关注 4 回答 4

    认证与成就

    • 获得 43 次点赞
    • 获得 31 枚徽章 获得 0 枚金徽章, 获得 7 枚银徽章, 获得 24 枚铜徽章

    擅长技能
    编辑

    开源项目 & 著作
    编辑

    • PerMVC

      个人框架 适用于小型项目

    • 普宙无人机官网

      为公司所做官网

    • GDU-CMS

      资源管理平台,在tp5基础上面进行开发,结合页面自动化生成工具。能快速进行页面+业务逻辑开发

    注册于 2016-11-07
    个人主页被 1k 人浏览