热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

由一次年会系统大战所想到的(1)

上个月接到了我公司年会系统的需求,觉得做起来有些困难。后来硬着头皮接下来了。年会1月6号顺利举办结束(由于是给程序员开的年会,所以年会过程中遇到了XSS注入,SQL注入,命令注入等

上个月接到了我公司年会系统的需求,觉得做起来有些困难。后来硬着头皮接下来了。年会1月6号顺利举办结束,整体上还算是成功,但是最后的摇一摇比赛出了些问题。在这里记录下用到的技术,遇到的困难和选择,以及做的处理和不足。希望对于大家有些参考。


先上一点结论和感想

1.做一个系统,需要权衡的维度,有如下几个:
技术分享
这就好比经典的CAP理论,鱼和熊掌不可兼得。这里追求了时间(只有两周多的开发时间),成本(实际上不应该过分压缩成本),功能(做全所有功能),放低了安全与严密的要求(例如消息传递没有加密,传递的消息没有盖时间戳验证流程,没有完整的会话保持与权限控制等等),而且把代码放到了GitHub上。
对于一个针对于普通大众的年会,这么做可能是没问题的。但是对于一个纯程序员的年会,这么做就难免出问题(我们现场系统受到了js注入,XSS注入,SQL注入还有指令注入攻击。我们现场改代码热部署)。
现在回想,应该把一些功能做的更严密些,不应该过分压缩成本(其实就是多买两台服务器的事。。。)

2.对于你做的系统,涉及到现场屏幕视觉设计的,一定要提早模拟下视觉匹配

3.之前对于Websocket的理解有误,只在,对于需要单向推送到客户端(手机浏览器)上的消息,应该都用Websocket,而不是采用客户端轮询。轮询对于服务器消耗太大。然后,其实更多情景应该用SSE

4. 对于产品设计上,可能需要改变下自己程序员的思维。程序员都有点because we can的思维,这并不都是缺点。但是把这个思维用在设计产品上就挂了。这里的例子就是摇一摇抽奖。这里我们没用微信摇一摇的功能,而是用js监控陀螺仪移动而做的摇一摇,显示的次数并不是准确的你摇动的次数,可能会有很大偏差。但是我们把这个数字展示出来了,并且没做说明,让很多用户认为这个次数不公正,是我们私下做了手脚。

5.流程太繁琐,走简单流程抢时间难免出问题。目前,内部生产上线流程繁琐而且时间长。我们如果采用的话,全部开发时间都得用来走上线流程。所以,没采用公司资源,自己购买的腾讯云部署的应用,最后安全性出问题。如果走公司内部流程做足检查就不会出这些问题,但是时间上不允许。估计等公司变革完,这个情况会改善很多。

6. 弹幕做了服务降级,其实摇一摇那里也应该做服务降级

1. 需求的确立与任务的分配

刚开始,接到的需求主要有这几个模块:微信签到上墙,CP签到抽奖,弹幕上墙,节目打赏,抽奖,摇一摇比赛还有红包链接展示。时间比较紧,基本上只有两周多的时间去开发。
团队里面算上我一共四人,都是新人(我是最老的员工,刚毕业1.5年。。)。划分了下任务,A同学负责签到前端,抽奖前后端,B同学负责节目管理打赏前端,摇一摇前端,C同学负责节目管理打赏前端,红包链接展示前后端,CP签到抽奖,我负责微信签到后端,微信接口调试和弹幕上前前后端。


2. 微信签到开发

整体逻辑架构设计:
技术分享
微信开发还比较容易,文档全,但是文档有的更新不够新,而且管理界面有时让人第一次使用摸不着头脑。不过尝试出来如何配置后,还比较容易的。
首先,你得先去申请个微信公众号,我们这里要用的微信功能有:网页服务中的网页账号服务,微信JSAPI。摇一摇我们没用微信的摇一摇功能,用的是js的振东事件。对于微信签到,我们只用到网页服务中的网页账号服务,其他的其他功能会用到。
对于公众号,如果需要网页账号服务,则需要你的公众号经过认证。摇一摇需要其他资质认证,比较麻烦所以我没用。
对于测试,可以先申请个微信测试号:http://mp.weixin.qq.com/debug/cgi-bin/sandboxinfo?action=showinfo&t=sandbox/index
申请好后,我们看到:
技术分享


2.1. 接口配置信息验证

这个是为了测试你的服务器是否认证良好,并且信任这台服务器并把消息转发给这台服务器。在配置时,微信服务器会发一条消息到你配置的服务器,如果返回的结果正确,则配置成功(这里可以填写域名或者IP,正式的公众号必须用域名,而且这个域名是ICP备案过的)。由于我们不做消息处理,而且我们只想简单的启用这个测试号,所以这里,我们只写了一个简单的直接返回结果的认证方法,代码如下:

@ResponseBody
@RequestMapping(value = "/weixin/message", method = RequestMethod.GET)
public String getWXUserInfo(@RequestParam("signature") String signature, @RequestParam("timestamp") String timestamp, @RequestParam("nonce") String nonce, @RequestParam("echostr") String echostr) {
//加解密省略。。。直接返回成功
return echostr;
}

2.2. 网页服务认证

首先先要配置:
技术分享
同样的,这里可以填写域名或者IP,正式的公众号必须用域名,而且这个域名是ICP备案过的
测试号信息中的appID还有appSecret是你的app开放认证信息的证书。
一般的,开放平台都是利用OAuth2.0协议:
技术分享

对于微信,流程如下:
技术分享

第一步:拼接自己的连接:
























appId wx0c7b8ab55037d5ca
scope 应用授权作用域,snsapi_base (不弹出授权页面,直接跳转,只能获取用户openid),snsapi_userinfo (弹出授权页面,可通过openid拿到昵称、性别、所在地。并且,即使在未关注的情况下,只要用户授权,也能获取其信息),这里我们需要用snsapi_userinfo
response_type 只能填写code
state 重定向到你的页面时会带上这个state参数,没用的话随便填写就行了
redirect_uri 域名一定要和你配置的一样,否则会报redirect_uri错误,需要url编码

跳转的链接需要接收两个参数,一个是code,一个是state;假设我们这里跳转的地址为“/weixin/login”,则地址路径为:http://127.0.0.1/weixin/login,经过url编码为:http%3A%2F%2F127.0.0.1%2Fweixin%2Flogin

所以,最后的连接为:
https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx0c7b8ab55037d5ca&redirect_uri=http%3A%2F%2F127.0.0.1%2Fweixin%2Flogin&response_type=code&scope=snsapi_base&state=123#wechat_redirect

通过这个链接开始调试你的公众号。

建议用QQ浏览器,这样能调试微信的链接。

第二步,编写微信返回类:

微信的所有返回返回信息都是json形式的,如果参数有误,返回的结果都包含errcode和errmsg,所以编写微信返回基类:

public class BaseReturn implements Serializable {
private int errcode;
private String errmsg;
public int getErrcode() {
return errcode;
}
public void setErrcode(int errcode) {
this.errcode = errcode;
}
public String getErrmsg() {
return errmsg;
}
public void setErrmsg(String errmsg) {
this.errmsg = errmsg;
}
public boolean isSuccessful() {
return this.errcode == 0;
}
}

客户端根据临时令牌code从服务提供方那里获取访问令牌access token的返回的类如下:

public class UserAuthorizationReturn extends BaseReturn {
private String access_token;// 网页授权接口调用凭证
private int expires_in;//access_token接口调用凭证超时时间,单位(秒)由于access_token拥有较短的有效期,当access_token超时后,可以使用refresh_token进行刷新,refresh_token拥有较长的有效期(7天、30天、60天、90天),当refresh_token失效的后,需要用户重新授权。
private String refresh_token;// 用户刷新access_token
private String openid;// 用户唯一标识,请注意,在未关注公众号时,用户访问公众号的网页,也会产生一个用户和公众号唯一的OpenID
private String scope;//用户授权的作用域,使用逗号(,)分隔
public String getScope() {
return scope;
}
public void setScope(String scope) {
this.scope = scope;
}
public String getOpenid() {
return openid;
}
public void setOpenid(String openid) {
this.openid = openid;
}
public String getRefresh_token() {
return refresh_token;
}
public void setRefresh_token(String refresh_token) {
this.refresh_token = refresh_token;
}
public int getExpires_in() {
return expires_in;
}
public void setExpires_in(int expires_in) {
this.expires_in = expires_in;
}
public String getAccess_token() {
return access_token;
}
public void setAccess_token(String access_token) {
this.access_token = access_token;
}
}

由于年会只有一个晚上,我们不用更新用户信息,所以对这里的expires_in并不做处理。
之后通过accessToken拿取用户信息返回的类如下:

public class UserInfoReturn extends BaseReturn {
private String openid;//用户的唯一标识
private String nickname;//用户昵称
private int sex;//用户的性别,值为1时是男性,值为2时是女性,值为0时是未知
private String province;//用户个人资料填写的省份
private String city;// 普通用户个人资料填写的城市
private String country;//国家,如中国为CN
private String headimgurl;//用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),用户没有头像时该项为空。若用户更换头像,原有头像URL将失效。
private String privilege;//用户特权信息,json 数组,如微信沃卡用户为(chinaunicom)
private String unionid;//只有在用户将公众号绑定到微信开放平台帐号后,才会出现该字段。
public String getOpenid() {
return openid;
}
public void setOpenid(String openid) {
this.openid = openid;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public int getSex() {
return sex;
}
public void setSex(int sex) {
this.sex = sex;
}
public String getProvince() {
return province;
}
public void setProvince(String province) {
this.province = province;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getHeadimgurl() {
return headimgurl;
}
public void setHeadimgurl(String headimgurl) {
this.headimgurl = headimgurl;
}
public String getPrivilege() {
return privilege;
}
public void setPrivilege(String privilege) {
this.privilege = privilege;
}
public String getUnionid() {
return unionid;
}
public void setUnionid(String unionid) {
this.uniOnid= unionid;
}
}

第三步,根据上面的流程,编写下面代码,拿取用户信息:

@RequestMapping(value = "/weixin/login", method = RequestMethod.GET)
public String getWXUserInfo(@RequestParam("code") String code, HttpServletResponse response) {
try {
String s = httpRequest.sendGet("https://api.weixin.qq.com/sns/oauth2/access_token",
"appid=" + appId + "&secret=" + appSecret + "&code=" + code + "&grant_type=authorization_code");
UserAuthorizationReturn userAuthorizatiOnReturn= JSON.parseObject(s, UserAuthorizationReturn.class);
s = httpRequest.sendGet("https://api.weixin.qq.com/sns/userinfo",
"access_token=" + userAuthorizationReturn.getAccess_token() + "&openid=" + userAuthorizationReturn.getOpenid() + "&lang=zh_CN");
Integer userId = userService.isSignedByWxInfo(userAuthorizationReturn.getOpenid());
log.info("微信返回:" + s);
//之后代码略
}

2.3. 登陆逻辑

可以参加晚会的人名单是固定的,除了这些人,其他人不能参与晚会。我们先把所有的人名单导入到数据库中。
我们使用工号姓名登陆。工号全是数字,有人有在工号前面加0的习惯,为了都能登录,我们保存在数据库中的类型是数字,前端传输过来的字符串会转换成数字与数据库中的比对。只有工号姓名匹配的用户才能登陆系统。
对于已授权的微信用户,如果登陆过的话,则不用再登陆一次。直接进入年会主界面。
用户输入工号姓名后,它的用户信息会被保存到数据库(包括工号姓名还有微信用户信息)中。由于微信信息中的openid是唯一的,所以根据这个是否在数据库中存在,判断是否是第一次登陆。

完整的代码:

@RequestMapping(value = "/weixin/login", method = RequestMethod.GET)
public String getWXUserInfo(@RequestParam("code") String code, HttpServletResponse response) {
try {
//根据code取得accessToken
String s = httpRequest.sendGet("https://api.weixin.qq.com/sns/oauth2/access_token",
"appid=" + appId + "&secret=" + appSecret + "&code=" + code + "&grant_type=authorization_code");
UserAuthorizationReturn userAuthorizationReturn = JSON.parseObject(s, UserAuthorizationReturn.class);
s = httpRequest.sendGet("https://api.weixin.qq.com/sns/userinfo",
"access_token=" + userAuthorizationReturn.getAccess_token() + "&openid=" + userAuthorizationReturn.getOpenid() + "&lang=zh_CN");
Integer userId = userService.isSignedByWxInfo(userAuthorizationReturn.getOpenid());
log.info("微信返回:" + s);
if (userId != null) { //已签到
COOKIEsUtil.addCOOKIE(response, "userId", String.valueOf(userId), 86400);
return "redirect:/frontend/main.html";
} else { //未签到
COOKIEsUtil.addCOOKIE(response, "userJson", URLEncoder.encode(s, "UTF-8"), 86400);
return "redirect:/frontend/login.html";
}
} catch (Exception e) {
log.warn(ExceptionUtils.getStackTrace(e));
}
return "redirect:/frontend/404.html";
}

这里我们偷懒了,并没有严格的会话和登录权限控制,只是做了简单的COOKIE。面对都是程序员的晚会,不应该做这么简单的登陆控制。


2.4. 签到上墙Websocket

在用户第一次成功登陆也就是签到成功时,服务器需要将这个签到消息推送给客户端。这种单项推送的技术,有很多可以选择:


  1. HTTP轮询和长轮询:最容易实现,但是比较繁琐而且耗费服务器资源. 简易轮询由于其本身的缺陷,并不推荐使用。Comet 技术并不是 HTML 5 标准的一部分,从兼容标准的角度出发,也不推荐使用。

  2. Websocket:全双工协议,可以用。市面上所有浏览器都支持,对于Spring有很好的集成,但是是从Spring 4.0开始的,我们用的框架基于Spring 3.X,来不及升级。但是Tomcat 7.X有现成的websocket实现。WebSocket 规范和服务器推送技术都是 HTML 5 标准的组成部分,在主流浏览器上都提供了原生的支持,是推荐使用的。

  3. SSE服务器发送事件:对于简单的服务器数据推送的场景,使用服务器推送(SSE技术)事件就足够了。这个是最适合的,可惜当时我不知道这个技术。可以参考:http://www.cnblogs.com/imstudy/p/5682555.html.

服务器通过Websocket通道,将人员签到的信息推送至签到墙页面,这里我运用的是最简单的tomcat 7的websocket实现。



推荐阅读
  • Java验证码——kaptcha的使用配置及样式
    本文介绍了如何使用kaptcha库来实现Java验证码的配置和样式设置,包括pom.xml的依赖配置和web.xml中servlet的配置。 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • Redis底层数据结构之压缩列表的介绍及实现原理
    本文介绍了Redis底层数据结构之压缩列表的概念、实现原理以及使用场景。压缩列表是Redis为了节约内存而开发的一种顺序数据结构,由特殊编码的连续内存块组成。文章详细解释了压缩列表的构成和各个属性的含义,以及如何通过指针来计算表尾节点的地址。压缩列表适用于列表键和哈希键中只包含少量小整数值和短字符串的情况。通过使用压缩列表,可以有效减少内存占用,提升Redis的性能。 ... [详细]
  • 本文内容为asp.net微信公众平台开发的目录汇总,包括数据库设计、多层架构框架搭建和入口实现、微信消息封装及反射赋值、关注事件、用户记录、回复文本消息、图文消息、服务搭建(接入)、自定义菜单等。同时提供了示例代码和相关的后台管理功能。内容涵盖了多个方面,适合综合运用。 ... [详细]
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • 基于layUI的图片上传前预览功能的2种实现方式
    本文介绍了基于layUI的图片上传前预览功能的两种实现方式:一种是使用blob+FileReader,另一种是使用layUI自带的参数。通过选择文件后点击文件名,在页面中间弹窗内预览图片。其中,layUI自带的参数实现了图片预览功能。该功能依赖于layUI的上传模块,并使用了blob和FileReader来读取本地文件并获取图像的base64编码。点击文件名时会执行See()函数。摘要长度为169字。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • HDU 2372 El Dorado(DP)的最长上升子序列长度求解方法
    本文介绍了解决HDU 2372 El Dorado问题的一种动态规划方法,通过循环k的方式求解最长上升子序列的长度。具体实现过程包括初始化dp数组、读取数列、计算最长上升子序列长度等步骤。 ... [详细]
  • 本文讨论了Alink回归预测的不完善问题,指出目前主要针对Python做案例,对其他语言支持不足。同时介绍了pom.xml文件的基本结构和使用方法,以及Maven的相关知识。最后,对Alink回归预测的未来发展提出了期待。 ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • CentOS 6.5安装VMware Tools及共享文件夹显示问题解决方法
    本文介绍了在CentOS 6.5上安装VMware Tools及解决共享文件夹显示问题的方法。包括清空CD/DVD使用的ISO镜像文件、创建挂载目录、改变光驱设备的读写权限等步骤。最后给出了拷贝解压VMware Tools的操作。 ... [详细]
  • 深入理解CSS中的margin属性及其应用场景
    本文主要介绍了CSS中的margin属性及其应用场景,包括垂直外边距合并、padding的使用时机、行内替换元素与费替换元素的区别、margin的基线、盒子的物理大小、显示大小、逻辑大小等知识点。通过深入理解这些概念,读者可以更好地掌握margin的用法和原理。同时,文中提供了一些相关的文档和规范供读者参考。 ... [详细]
  • 本文介绍了django中视图函数的使用方法,包括如何接收Web请求并返回Web响应,以及如何处理GET请求和POST请求。同时还介绍了urls.py和views.py文件的配置方式。 ... [详细]
  • 在编写业务代码时,常常会遇到复杂的业务逻辑导致代码冗长混乱的情况。为了解决这个问题,可以利用中间件模式来简化代码逻辑。中间件模式可以帮助我们更好地设计架构和代码,提高代码质量。本文介绍了中间件模式的基本概念和用法。 ... [详细]
  • 导出功能protectedvoidbtnExport(objectsender,EventArgse){用来打开下载窗口stringfileName中 ... [详细]
author-avatar
jgfioirejmf
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有