前端测试框架 Jest

前端测试工具一览

前端测试工具也和前端的框架一样纷繁复杂,其中常见的测试工具,大致可分为测试框架、断言库、测试覆盖率工具等几类。在正式开始本文之前,我们先来大致了解下它们:

测试框架

测试框架的作用是提供一些方便的语法来描述测试用例,以及对用例进行分组。

测试框架可分为两种: TDD (测试驱动开发)和 BDD (行为驱动开发),我理解两者间的区别主要是一些语法上的不同,其中 BDD 提供了提供了可读性更好的用例语法,至于详细的区别可参见 The Difference Between TDD and BDD 一文。

常见的测试框架有 Jasmine, Mocha 以及本文要介绍的 Jest

断言库

断言库主要提供语义化方法,用于对参与测试的值做各种各样的判断。这些语义化方法会返回测试的结果,要么成功、要么失败。常见的断言库有 Should.jsChai.js 等。

测试覆盖率工具

用于统计测试用例对代码的测试情况,生成相应的报表,比如 istanbul

Jest

为什么选择 Jest

Jest 是 Facebook 出品的一个测试框架,相对其他测试框架,其一大特点就是就是内置了常用的测试工具,比如自带断言、测试覆盖率工具,实现了开箱即用。

而作为一个面向前端的测试框架, Jest 可以利用其特有的快照测试功能,通过比对 UI 代码生成的快照文件,实现对 React 等常见框架的自动测试。

此外, Jest 的测试用例是并行执行的,而且只执行发生改变的文件所对应的测试,提升了测试速度。目前在 Github 上其 star 数已经破万;而除了 Facebook 外,业内其他公司也开始从其它测试框架转向 Jest ,比如 Airbnb 的尝试 ,相信未来 Jest 的发展趋势仍会比较迅猛。

安装

Jest 可以通过 npm 或 yarn 进行安装。以 npm 为例,既可用npm install -g jest进行全局安装;也可以只局部安装、并在 package.json 中指定 test 脚本:

{
  "scripts": {
    "test": "jest"
  }
}
复制代码

Jest 的测试脚本名形如*.test.js,不论 Jest 是全局运行还是通过npm test运行,它都会执行当前目录下所有的*.test.js*.spec.js 文件、完成测试。

基本使用

用例的表示

表示测试用例是一个测试框架提供的最基本的 API , Jest 内部使用了 Jasmine 2 来进行测试,故其用例语法与 Jasmine 相同。test()函数来描述一个测试用例,举个简单的例子:

// hello.js
module.exports = () => 'Hello world'
复制代码
// hello.test.js
let hello = require('hello.js')

test('should get "Hello world"', () => {
    expect(hello()).toBe('Hello world') // 测试成功
    // expect(hello()).toBe('Hello') // 测试失败
})
复制代码

其中toBe('Hello world')便是一句断言( Jest 管它叫 “matcher” ,想了解更多 matcher 请参考文档)。写完了用例,运行在项目目录下执行npm test,即可看到测试结果:

若测试失败,会标识出失败的断言位置,结果如下:

用例的预处理或后处理

有时我们想在测试开始之前进行下环境的检查、或者在测试结束之后作一些清理操作,这就需要对用例进行预处理或后处理。对测试文件中所有的用例进行统一的预处理,可以使用 beforeAll() 函数;而如果想在每个用例开始前进行都预处理,则可使用 beforeEach() 函数。至于后处理,也有对应的 afterAll()afterEach() 函数。

如果只是想对某几个用例进行同样的预处理或后处理,可以将先将这几个用例归为一组。使用 describe() 函数即可表示一组用例,再将上面提到的四个处理函数置于 describe() 的处理回调内,就实现了对一组用例的预处理或后处理:

describe('test testObject', () => {
    beforeAll(() => {
        // 预处理操作
    })

    test('is foo', () => {
       expect(testObject.foo).toBeTruthy()
    })

    test('is not bar', () => {
        expect(testObject.bar).toBeFalsy()
    })

    afterAll(() => {
        // 后处理操作
    })
})
复制代码

测试异步代码

异步代码的测试,关键点在于告知测试框架测试何时完成,让其在恰当的时机进行断言。针对几种常见的异步代码形式, Jest 也提供了相应的异步测试语法。首先对于异步回调,向其传入并执行 done 函数, Jest 会等 done 回调执行结束后,结束测试:

// asyncHello.js
module.exports = (name, cb) => setTimeout(() => cb(`Hello ${name}`), 1000)
复制代码
// asyncHello.test.js
let asyncHello = require('asyncHello.js')

test('should get "Hello world"', (done) => {
    asyncHello('world', (result) => {
        expect(result).toBe('Hello world')
        done()
    })
})
复制代码

此外,对于 Promise 控制的异步代码,可以直接在 then 回调中进行断言,只要保证在用例中返回该 Promise 对象即可:

// promiseHello.js
module.exports = (name) => {
    return new Promise((resolve) => {
        setTimeout(() => resolve(`Hello ${name}`), 1000)
    })
}
复制代码
// promiseHello.test.js
let promiseHello = require('promiseHello.js')

it('should get "Hello world"', () => {
    expect.assertions(1); // 确保至少有一个断言被调用,否则测试失败
    return promiseHello('world').then((data) => {
        expect(data).toBe('Hello world')
    })
})
复制代码

Jest 也支持 async/await 语法的测试,无需多余的操作,只要在 await 后进行断言即可,和同步测试的写法一致。

测试覆盖率

Jest 内置了测试覆盖率工具istanbul,要开启,可以直接在命令中添加 --coverage 参数,或者在 package.json 文件进行更详细的配置

运行 istanbul 除了会再终端展示测试覆盖率情况,还会在项目下生产一个 coverage 目录,内附一个测试覆盖率的报告,让我们可以清晰看到分支的代码的测试情况。比如下面这个例子:

// branches.js
module.exports = (name) => {
    if (name === 'Levon') {
        return `Hello Levon`
    } else {
        return `Hello ${name}`
    }
}
复制代码
// branches.test.js
let branches = require('../branches.js')

describe('Multiple branches test', ()=> {
    test('should get Hello Levon', ()=> {
          expect(branches('Levon')).toBe('Hello Levon')
    });
    // test('should get Hello World', ()=> {
    //       expect(branches('World')).toBe('Hello World')
    // });  
})
复制代码

运行 jest --coverage 可看到产生的报告里展示了代码的覆盖率和未测试的行数:

如果我们把branches.test.js中的注释去掉,跑遍测试对象中的所有分支,测试覆盖率就是100%了:

在前端项目中使用

搭配React和其它框架

针对前端框架的测试, Jest 的一大特色就是提供了快照测试功能。首次运行快照测试,会让 UI 框架生产一个可读的快照,再次测试时便会通过比对快照文件和新 UI 框架产生的快照判断测试是否通过。对于 React ,我们可以通过下面的方法生产一个快照:

import React from 'react';
import Link from '../Link.react';
import renderer from 'react-test-renderer';

it('renders correctly', () => {
    const tree = renderer.create(
        <Link page="http://www.facebook.com">Facebook</Link>
    ).toJSON();
    expect(tree).toMatchSnapshot();
});
复制代码

运行测试,我们可以看到生成一个快照文件如下:

exports[`renders correctly 1`] = `
<a
    className="normal"
    href="http://www.facebook.com"
    onMouseEnter={[Function]}
    onMouseLeave={[Function]}
>
    Facebook
</a>
`;
复制代码

这个可读的快照文件以可读的形式展示了 React 渲染出的 DOM 结构。相比于肉眼观察效果的 UI 测试,快照测试直接由Jest进行比对、速度更快;而且由于直接展示了 DOM 结构,也能让我们在检查快照的时候,快速、准确地发现问题。

除了 React ,Jest 文档中也提供了针对其他框架进行测试的指南

无缝迁移

如果你的项目中已经使用了别的测试框架,比如 Mocha,有一个第三方工具jest-codemods可以自动把用例迁移成 Jest 的用例,降低了迁移成本。

后记:前端自动化测试,值不值得?

近几年前端工程化的发展风起云涌,但是前端自动化测试这块内容大家却似乎不太重视。虽然项目迭代过程中会有专门的测试人员进行测试,但等他们来进行测试时,代码已经开发完成的状态。与之相比,如果我们在开发过程中就进行了测试(直接采用 TDD 开发模式、或者针对既有的模块写用例),会有如下的好处:

  • 保障代码质量和功能的实现的完整度
  • 提升开发效率,在开发过程中进行测试能让我们提前发现 bug ,此时进行问题定位和修复的速度自然比开发完再被叫去修 bug 要快许多
  • 便于项目维护,后续任何代码更新也必须跑通测试用例,即使进行重构或开发人员发生变化也能保障预期功能的实现

当然,凡事都有两面性,好处虽然明显,却并不是所有的项目都值得引入测试框架,毕竟维护测试用例也是需要成本的。对于一些需求频繁变更、复用性较低的内容,比如活动页面,让开发专门抽出人力来写测试用例确实得不偿失。

而那些适合引入测试场景大概有这么几个:

  • 需要长期维护的项目。它们需要测试来保障代码可维护性、功能的稳定性
  • 较为稳定的项目、或项目中较为稳定的部分。给它们写测试用例,维护成本低
  • 被多次复用的部分,比如一些通用组件和库函数。因为多处复用,更要保障质量

以上就是我对前端测试的一点浅见,欢迎斧正。
作者:美团点评点餐
链接:https://juejin.im/post/597aa518f265da3e345f3262
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

网站被黑客攻击从定位到修复(一)

从黑产中学习和成长

之前做了几个网站,管理员反馈网站除了首页,啥页面都点击不进去了。疑似被攻击

网站首页看似毫无破绽,点看源代码

HTML源代码

首页备注里一些base64编码的文案,用搜索引擎搜索网站地址,得到如下的

搜索引擎内容

得到的结论是:被黑产注入了代码来获取该网站的搜索引起流量。(情况非常严重)

开始排除问题:

  1. 先备份代码和数据库
  2. 重装系统(这是防止黑客在服务器上面留了后门,先绝后患)
  3. 用的是阿里云服务器,近期并没有收到异常登陆服务器的通知,基本排除黑客是通过登陆服务器方式来安装木马的
  4. 从上传的目录里面找问题。很快就定位了,public的上传文件里面有一个异常的PHP文件
  5. 该文件是一个加密的PHP文件(运行起来如下)

WEB 越权攻击界面

该界面功能之全,覆盖之广,真是令人震惊。我将会在后面几天文章的重点讲解这个工具的使用和原理。

只有知道那敌人的招数之后,我们才能有效的防御。并且从中学习和成长

造成漏洞的原因:文件上传被黑客利用,并且通过管理后台的编辑器上传了PHP文件,该PHP文件有调用了一些系统函数获取了系统的权限对原本的首页文件进行了修改。

解决办法:

1. 先关闭该文件中提到的所有的系统级函数(PHP安装的时候配置文件默认的不够,具体信息下一篇文章会调到)

2. 关闭PHP通过URL操作文件的权限(这个很重要)

3. 修改管理后台附件上传组件,严格现在上传文件类型。

入职场三年,我学会了这些

最近入职腾讯了(需要内推的加群,私聊我),上来就接了一个很重要,但又很繁琐的工作,就是做管理后台,写原生PHP代码。

从2015年6月24号入职深圳迅雷开始,算是正式从广州根据地转战了深圳,并且积极的开辟第二战场。在迅雷差不多两年的时间里,首先是感恩,感谢迅雷牛X游戏给我的工作和学习的机会,并且培养了我。在这两年里,认识了我组长还有七七八八的小伙伴们,因为你们的存在,才让我在迅雷的生活更多姿多彩。

最近听说迅雷牛X裁员了,在我离职后的一周。这里不做过多的评论,这是页游行业的一个必然的趋势,2014年在广州4399的时候,就知道页游市场已经在紧缩,我也算见证了一个行业的浪潮起又落了。

但不管环境和行业怎么变换,只要你技术在,那工作机会就在。

跟腾讯的情节应该是2014年,参加校招的时候就开始了,那时候我在学校做过几个校园项目,觉得信心满满,然后碰了一鼻子灰。我连英文名都取号了,然后你告诉我,你不要我。(内心是失落的)

只能曲线救国,从基础开始,成功应聘到了酷狗音乐做了实习生,哪算是第一次接触工作,接触社会。

这些年不管工作怎么变,我一直保质我那份真诚不变,不管是对待同事还是对待工作。都保持着能做100分,就不做80分的态度,尽量苛求自己,不麻烦别人。

回顾这三年,我明白了一个道理:就算在做一件大家都觉得没啥意义的工作,也要先把事情做完,再评论。

在迅雷的日子里,真的成长特别快,刚来的时候,刚好赶上公司业务发展高峰,事情特别多,人手不够,我们加班做。活动一个接一个,幸好有组长在前面扛了一大波伤害,不然我真怕的坚持不住。

想起自己成一个打下手的小伙计,到现在可以独当一面的小人物。我感慨很多,谢谢一路上帮助,批评,教育过我的人,谢谢你们没有放弃我,在我犯错误的时候给我一次又一次的机会。让我走到了今天。

在腾讯我的职位是WEB前端开发工程师,但我暂时还是从事的PHP工作,因为这边WEB前端的要求是会PHP。

加入腾讯之后,发现真的跟以前不一样。真的是一副没见过世面的样子。暂时还没发现啥不好的地方。等再呆一段时间看看。

   关于培训

腾讯的培训特别多,讲座也多,以前在大学(湖南工业大学)的讲座基本都是商业性质的讲座没啥意思,所以也没培养啥听讲座的习惯。参加工作以后公司都会有讲座,但腾讯的讲座频率那叫一个频繁呀,基本天天都有,还有外籍的。只要你有学习的方向,在腾讯学院都是找到你的想要的资料。

   关于企业文化

腾讯的一直很强调企业文化,之前只是知道企业文化有这么一个名称,没啥感觉,参加工作之后,基本以工作为重。给我的感觉就是做好本质工作,比啥都重要。互联网公司都是以自由,开放著称的,其实这跟领导有很大的关系。腾讯会有大概花三天的时间给新员工介绍公司,介绍公司的发展,然后到了部门又会开部门级别的文化培训,介绍自己的部门的发展。然后你会发现,这些还是跟你直属领导有关。

   关于薪资福利

行业标杆,不是最多的,但也不是最少的,都是良心价,每年都有加薪普调机会。但并不能保证公司在不能赚钱的时候也给你加薪,之前公司就是这样。同一个职业等,工资是有区间的,年终奖跟你所在部门和所在项目相关的,公司承若的年薪是14+N,这个N就指部门奖金。福利的话,该有的都有,也不是方方面面都具到。比如我之前的公司提供免费的饮料,这边有一个易宝。

因为入职的时间还不是很长,还需要认真做出成绩。以后我在好好总结工作遇到的技术问题和大家一起分享。

年终总结

时光荏苒,岁月如梭,不知不觉已经从业快三年了,对于互联网这个行业来说,我还是一个小学生。一直抱着一颗学习的态度在工作,在生活

我总结一下我这一年在工作岗位上收获和成绩。

首先今年一年特别要感谢我领导对我的的支持和鼓励,并且为我在一年工作中因编码考虑不全造成的活动bug,表示歉意。我会深刻反思问题出现的原因,避免同样问题再次发生。

我一直觉得作为一个90年后员工,我一直挺任性的,我能活到现在,一定要感激我老大和组长的包容教育。

我一直记得,我上家公司老大一直教育我说;“做事要稳,不然没人会放心把事情交给你做的”。其实我开始并不太理会这句话,我一直觉得我在上家公司做不好时因为没有测试,如果有了测试参与,肯定不会出现这些问题的。然后到了现在这家公司,被啪啪啪的打脸。

我一直觉得一个程序员能写出bug,简直就是对程序员的侮辱,(自从被侮辱习惯以后,我就…..)然后,我都是痛定思痛,我组长也一直在总结,问题到底出在哪里,到底是选错了人,还是模式有问题。最后,他就事论事,想出了一套比较适合现在问题的活动发布流程。我觉得组长是我学习的榜样。

下半年我就从活动开发到了管理后台开发,跟我们组的另一个小哥,开始搭配,然后开启了赶进度的模式,为了进度,那代码写的,见者伤心,闻者流泪。但项目还是如期的上线了,虽然在开发过程中,小哥主动分担了前端页面所有的工作。这样就为项目上线提供了可靠的保障,而我的工作是管理后台,那就是简单的增删改查。所有这个项目能上线,我占1%,他占2%,剩下的是老大的(这马屁拍的,我自己都服)

总的来说,这样的自己还是有产出的(没产出不就开除了)但或多或少还是有很多问题需要留到新的一年解决。而去要求自己不要把2016年的问题再在2017年发生,不要再让组长为难,领导操心。至于同组的同事,他们估计只要我不给他们留bug,他们就感恩戴德了。

我也希望自己在新的一年,继续上一年的工作态度和热情,尽量少给其他的添麻烦,努力端正态度。做好自己本分的工作,利用业余时间多学习新的框架思路和新的技术知识。

 

PHP多线程

PHP 在liunx里面是可以实现多进程的,但需要使用liunx扩展:pcntl 扩展

主要代码是

[php]
$pid = pcntl_fork();    //创建子进程
if ($pid == -1) {
die(‘could not fork’); //错误处理:创建子进程失败时返回-1.
}else if ($pid) {
pcntl_wait($status,WNOHANG);
//父进程会得到子进程号,所以这里是父进程执行的逻辑
//如果不需要阻塞进程,而又想得到子进程的退出状态,则可以注释掉pcntl_wait($status)语句,或写成:
//等待子进程中断,防止子进程成为僵尸进程。
}else {
exit(0);//子进程得到的$pid为0, 所以这里是子进程执行的逻辑。
}
[/php]

PHP进程

PHP进程

从这样,可以看出来,父进程和子进程不是同步关系。父进程结束,子进程可以继续执行,父进程在等待子进程结束并回收。

PHP进程

PHP进程

 这里的PHP-fpm 都是fork出来的子进程,并且都是并行执行的。 所以原本单线程需要23分钟执行的代码,这里fork出20个子进程,就可以把时间缩短到30秒

观察者模式

前提:

之前的一家公司,因为人手紧缺就让学JAVA的同学转行来写PHP。

过程:

我维护代码的时候,发现里面有很多Listener方法。开始一直以为只是编写命名规范而已,然后才知道是设计模式

疑惑:

这种设计模式有什么意义?


要想知道,为什么这么写,就想要知道这种设计模式是怎么实现的,它存在的意义。观察者模式的定义,我这里就不复述了。

观察者模式

观察者模式

 

这个图是我从别人代码里面解读出来对观察者模式的理解,该模式的核心是监听。跟Jquery里面的On()绑定事件类似

然后查阅了一些资料。了解到,这种观察者模式,一点是用接口类来实现。当然传统的继承也可以实现类是的方法。但观察者模式还有一个核心:去耦合

代码网上一找一大把,但我一直以为我写的是技术博客的宗旨,不贴点代码好像有点说不过去。但我看别人代码的是发现了另一个东西 SplObserver 

这个是PHP的迭代器,之前我也有提到过,这是PHP提供的抽象类,提供一些使用有高效的处理方法的东西,传送门

[php]
SplObserver {
abstract public void update ( SplSubject $subject ) //一个更新的抽象接口 ,作用只是用来更新被监听者列表
}
[/php]

 

总结:

观察者模式的好处在于耦合逻辑分类,一对多。开发人员并不需要关注后续操作太大,只需要把通知的方法加上。

然后能用PHP迭代器的情况,要尽量用PHP的迭代器来处理数据,因为C语言写的代码,肯定比你PHP处理的要高效

Avalon入门到放弃

自从来一个这家公司之后,我感觉我PHP开发的“尊严”已经丢尽了。我已经沦落到一个靠写JS过日子的后台开发了。

工作除了写不完的活动页面,还有粘贴复制的JS代码,日子越来越枯燥了,生活也越来越无聊了,人生的悲观情绪悠然而生,有的观众朋友就会问:“那你为啥不换一个工作呢?”,“如果这种问题非要回答的话,我只能告诉你,因为没人要我呀!”

被逼无奈,只能委屈求全,忍辱负重开始学习新的JS框架,感觉之前的jquery框架太多单一,没法体现出我作为21世纪优秀大学生的优良品种。所以我花了一点时间去了解一下MVC的JS框架,和MVVM的JS框架,然后莫名其妙的学了一个requirJS,了解啥叫AMD模式。人生呀,就是这样,就以为你充满了未知,而更佳精彩(一言不合,我就炖鸡汤)。

言归正传,现在说AVALON这个MVVM框架。。快速入门  看,点这个avalon快速入门

我现在说我,对这个框架的理解

  1. 去DOM化编程,模版渲染。
  2. 支持IE6+

[javascript]

require([‘jquery’,’NiuxGame’], function($) {
gamelist = avalon.define({
$id : "gamelist",
data:[],
reload:function(){
var _this = this;
_this.data = ALL_GAME_INFOS;
$.each(ALL_GAME_INFOS,function(item, value){
_this.data.push(item)
});
}
});
gamelist.reload();
avalon.scan();
});
[/javascript]

[html]

<div ms-controller="gamelist">

<dl ms-repeat-el="data">

<dt>{{el.gameNo}}</dt>

</dl>

</div>

[/html]

上面的代码的目的是:通过动态加载数据,然后通过avalon模版引擎渲染模版avalon.scan()  注意这个东西

我的最终目标是,把大部分页面功能都封装成小的JS文件,然后再用require按需加载。这样活动页面前端开发速度有能有本质的提示。能把异步请求的功能完成,这样的话,后面的开发都会容易很多。

 

微信公众号接口发红包

最近接到一个任务,需要用微信来给用户自动发红包。要完成这个任务需要这么已经一些物料

  1. 微信商户号,已申请微信支付
  2. 微信商户号主体下面的微信公仔号

先看一下效果图

只需要完成后面几步就可以了。

  1. 在微信支付的服务器上面部署红包代码
  2. 在微信公众号服务器上面调用红包代码

[php]

/*
**微信红包功能
*/

public function sendredpack(){

$re_openid = $this->_pg(‘re_openid’);
$inputObj = new sendredpack_pub();

if(!$re_openid){
return "微信红包功能,收红包用户不能为空";
}

$inputObj->setParameter(‘re_openid’,$re_openid); //收红包的用户的openid
$inputObj->setParameter(‘send_name’,"汽配一号铺"); //红包发送者名称
$inputObj->setParameter(‘total_amount’,"100"); //收红包的用户的金额,精确到分
$inputObj->setParameter(‘total_num’,"1"); //收红包的个数
$inputObj->setParameter(‘wishing’,"恭喜发财,谢谢支持,小小心意"); //收红包的用户的openid
$inputObj->setParameter(‘client_ip’,"121.40.157.243"); //调用接口的IP
$inputObj->setParameter(‘act_name’,"小邓感恩红包"); //红包主题
$inputObj->setParameter(‘remark’,"谢谢大家一路一来的支持"); //备注
$response = $inputObj->getResult();

return $response;
}

[/php]

在微信支付辅助工具层加一个类,来完成红包功能

[php]
/**
* 微信发红包接口
**/
class sendredpack_pub extends Wxpay_client_pub
{
function __construct() {
//设置接口链接
$this->url = "https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack";
//设置curl超时时间
$this->curl_timeout = WxPayConf_pub::CURL_TIMEOUT;
}

/**
* 生成接口参数xml
*/
function createXml()
{
try
{
$this->parameters["mch_billno"] = WxPayConf_pub::MCHID.createUnique();//商户订单号
$this->parameters["wxappid"] = WxPayConf_pub::APPID;//公众账号ID
$this->parameters["mch_id"] = WxPayConf_pub::MCHID;//商户号
$this->parameters["nonce_str"] = $this->createNoncestr();//随机字符串
$this->parameters["sign"] = $this->getSign($this->parameters);//签名
return $this->arrayToXml($this->parameters);
}catch (SDKRuntimeException $e) {
die($e->errorMessage());
}
}

/**
* 作用:获取结果,使用证书通信
*/
function getResult()
{
$this->postXmlSSL();
$this->result = $this->xmlToArray($this->response);
return $this->result;
}
}

[/php]

然后部署返微信支付的服务上面,就可以了!!然后在做微信公众号(这个公众号)的服务上面加入“红包”。就能达到上面的效果了

根据文档进行开发
请您仔细阅读接口文档,参照文档进行开发,请注意,为了保证商户资金安全,接口强校验商户号与appid之间的绑定关系,以及appid与openid之间的对应关系(如果商户号与appid之间没有绑定关系,即appid没有申请微信支付或者申请微信支付商户号不匹配,或者openid归属appid错误,接口会返回报错)。

迅雷牛X2015年会

这应该是正式工作的第二个年头了,虽然我已经参加的三个年会(三家不同公司年会)。这次应该算部门酒会。也是我第一次报名当工作人员,参与整个年会的准备。

首先要感谢部门同组的小伙伴,分担了我在开发年会抽奖活动期间部门营收任务,让我一心做这个年会的事情,可惜呀~,我却从此背负了“黑心开发”的骂名(偷笑)

年会抽奖页面

年会抽奖页面

抽奖是根据微信关注公众号,然后再粉丝里面抽取幸运观众。逻辑和流程都很简单,但还是有一些不足的地方:

  1. 微信墙没有支持图片,这是我一个经验不足导致的
  2. 抽奖算法不是真随机,用的是shuffle()将数组打乱,再拿第一个视为中奖观众(理论可行,如果下次还有机会,我要在这个方面再做研究)
  3. 现场抽奖观众参加活动,根据性别抽取。这功能本来是规划做的,但后来砍了
  4. 代码并没有防注入和过滤的功能。
  5. 投票功能做的不太好!!参加门槛和操作门口都很高,导致参与度不够

虽然提前了2周准备这事,但还是挺紧张的。整个晚会一直都很紧张,虽然过程中没出啥岔子。但总感觉上面的几个问题,成了这次活动的遗憾。

再补一张大合作。。。也算是对这次参与年会一次记忆

年会合照

年会合照

(其实我就是想告诉大家:我们迅雷牛X妹子漂亮,,哈哈哈)

PS:顺便告诉一下,我们还招人!!

支付三部曲-银联支付

之前看我博客百度关键字的时候,很多人都是因为银联搜到我这里,一直想做一个demo 给大家,可惜没商户申请不到,这次机缘巧合帮朋友做支付三部曲:微信支付,支付宝支付和银联支付,其他两个支付都烂大街的,这里就只把银联支付分享给大家,因为涉及到客户的信息,所以就不提供源代码下载了。但可有偿服务

银联支付,首先要注意二重要的部分:

  1. PHP运行环境是5.4.18以上
  2. 开了扩展openss

开发手册上面的列子只做参考,因为基本都是错的。你可以试着去官网下一个demo。。。注意现在银联开发,没有测试密钥提供,只能在正式环境开发【20151219】

下面是我用ThinkPHP编写的一个支付类

[php]
/**
* 银联支付 v0.1
* @auther:Summer<dengwz7788@gmail.com>;
* @date:20151202
* **/

class NetPayAction extends BaseAction{
//在类初始化方法中,引入相关类库
public function _initialize() {
header("Content-type:text/html;charset=utf-8");
vendor(‘Netpay.util.common’,"",".php"); //导入加密核心文件夹
vendor(‘Netpay.util.SecssUtil’,"",".class.php"); //导入加密核心文件夹
vendor(‘Netpay.util.Settings_INI’,"",".php"); //导入加密核心文件夹
vendor(‘Netpay.util.Settings’,"",".php"); //导入加密核心文件夹
$this-&gt;securityPropFile= $_SERVER[‘DOCUMENT_ROOT’] . "/ThinkPHP/Extend/Vendor/Netpay/config/security.properties"; //谁知道这是啥,反正他们要我加的
$this->b2cPaySend = __APP__."/Index/NetPay/b2cPaySend";
$this-&gt;b2cRefundSend = __APP__."/Index/NetPay/b2cRefundSend";
$this->b2cQuerySend = __APP__."/Index/NetPay/b2cQuerySend";
$this->;MerBgUrl = __APP__."/Index/NetPay/MerBgUrl";
$this->MerPageUrl = __APP__."/Index/NetPay/MerPageUrl";
}

public function index()
{
$paramArray=array (
‘MerId’ => ‘商户号’,
‘MerOrderNo’ => ‘0000001944663232’,
‘OrderAmt’ => ‘1’,
‘TranDate’ => ‘20151219’,
‘TranTime’ =>’171248′,
‘TranType’ => ‘0001’,
‘BusiType’ =>’0001′,
‘Version’ => ‘20140728’,
‘CurryNo’ => ‘CNY’,
‘AccessType’ =>; ‘0’,
‘CommodityMsg’ => ‘测试商品1号’,
‘MerPageUrl’ => $this-&gt;MerBgUrl,
‘MerBgUrl’ =>$this-&gt;MerPageUrl,
‘MerResv’ => ‘MerResv’,
);

if (count($paramArray) >0) {
$dispatchUrl = $this->b2cPaySend;
$transResvedJson = array();
$cardInfoJson = array();
$sendMap = array();
foreach ($paramArray as $key => $value) {
if (isEmpty($value)) {
continue;
}
if (startWith($key, "trans_")) {
$key = substr($key, strlen("trans_"));
$transResvedJson[$key] = $value;
} else
if (startWith($key, "card_")) {
$key = substr($key, strlen("card_"));
$cardInfoJson[$key] = $value;
} else {
$sendMap[$key] = $value;
}
}

$transResvedStr = null;
$cardResvedStr = null;
if (count($transResvedJson) >0) {
$transResvedStr = json_encode($transResvedJson);
}
if (count($cardInfoJson) &gt; 0) {
$cardResvedStr = json_encode($cardInfoJson);
}

$secssUtil = new SecssUtil();
if (! isEmpty($transResvedStr)) {
$transResvedStr = $secssUtil->decryptData($transResvedStr);
$sendMap["TranReserved"] = $transResvedStr;
}
if (! isEmpty($cardResvedStr)) {
$cardResvedStr = $secssUtil->decryptData($cardResvedStr);
$sendMap["card_"] = $cardResvedStr;
}
$securityPropFile = $this>securityPropFile;
$secssUtil->init($securityPropFile);
$secssUtil->sign($sendMap);

$sendMap["Signature"] = $secssUtil->getSign();
$_SESSION = $sendMap;
header("Location:" . $dispatchUrl);
}
}

public function b2cPaySend(){
layout(false);
$settings = new Settings_INI();
$settings->oad($this->securityPropFile);
$pay_url = "https://payment.chinapay.com/CTITS/service/rest/page/nref/000000000017/0/0/0/0/0";
$html = "<form name=’payment’ action='{$pay_url}’ method=’POST’ target=’_blank’>;";
$params = "TranReserved;MerId;MerOrderNo;OrderAmt;CurryNo;TranDate;SplitMethod;BusiType;MerPageUrl;MerBgUrl;SplitType;MerSplitMsg;PayTimeOut;MerResv;Version;BankInstNo;CommodityMsg;Signature;AccessType;AcqCode;OrderExpiryTime;TranType;RemoteAddr;Referred;TranTime;TimeStamp;CardTranData";
foreach ($_SESSION as $k =>$v) {
if (strstr($params, $k)) {
$html .= "<input type=’hidden’ name = ‘" . $k . "’ value =’" . $v . "’/>";
}
}

$html .= "<nput type=’button’ type=’hidden’ value=’提交订单’ >";
$html .= "<;/from>";
$this->html = $html;
$this->display();
}

public function pgReturn(){
if ($_POST) {
if (count($_POST) > 0) {
$secssUtil = new SecssUtil();
$securityPropFile = $this>securityPropFile;
$secssUtil->init($securityPropFile);
$text = array();
foreach($_POST as $key=>$value){
$text[$key] = urldecode($value);
}

if ($secssUtil->verify($text)) {
//支付成功
$_SESSION["VERIFY_KEY"] = "success";
} else {
//支付失败
$_SESSION["VERIFY_KEY"] = "fail";
}
}
}
}
}
[/php]

银联支付应该是算比较简单的!!