由一次年会系统大战所想到的。。。(1)
来源:互联网 发布:3 0和网络教育哪个好 编辑:程序博客网 时间:2024/05/10 11:15
上个月接到了我公司年会系统的需求,觉得做起来有些困难。后来硬着头皮接下来了。年会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协议:
对于微信,流程如下:
第一步:拼接自己的连接:
跳转的链接需要接收两个参数,一个是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
在用户第一次成功登陆也就是签到成功时,服务器需要将这个签到消息推送给客户端。这种单项推送的技术,有很多可以选择:
- HTTP轮询和长轮询:最容易实现,但是比较繁琐而且耗费服务器资源. 简易轮询由于其本身的缺陷,并不推荐使用。Comet 技术并不是 HTML 5 标准的一部分,从兼容标准的角度出发,也不推荐使用。
- Websocket:全双工协议,可以用。市面上所有浏览器都支持,对于Spring有很好的集成,但是是从Spring 4.0开始的,我们用的框架基于Spring 3.X,来不及升级。但是Tomcat 7.X有现成的websocket实现。WebSocket 规范和服务器推送技术都是 HTML 5 标准的组成部分,在主流浏览器上都提供了原生的支持,是推荐使用的。
- SSE服务器发送事件:对于简单的服务器数据推送的场景,使用服务器推送(SSE技术)事件就足够了。这个是最适合的,可惜当时我不知道这个技术。可以参考:http://www.cnblogs.com/imstudy/p/5682555.html.
服务器通过Websocket通道,将人员签到的信息推送至签到墙页面,这里我运用的是最简单的tomcat 7的websocket实现。
- 由一次年会系统大战所想到的。。。(1)
- 由体育所想到的
- 由CreateInstance所想到的......
- 由'\\n'所想到的
- 由“多音字”所想到的
- 由 Runtime 所想到的
- 由不完美安装配置三系统所想到的
- 由assert的定义所想到的
- 由《罗素的故事》所想到的
- 由三层加架构所想到的
- 由安装宽带所想到的
- 由软件升级所想到的
- 【原创】由分水所想到的
- 由挂机程序所想到的!
- 由超图上市所想到的
- 由超图上市所想到的
- 由FoxMail导入联系人所想到的。。
- 由百度暴富所想到的
- Rockchip平台TP驱动详解
- 架构资源汇总
- java类的成员变量和方法内的局部变量
- Windows下安装MySQL zip方式安装
- Python零基础入门二---字符串的格式化
- 由一次年会系统大战所想到的。。。(1)
- Log图文详解(Log.v,Log.d,Log.i,Log.w,Log.e)的用法
- arcgis for ios 让地图加载后显示特定区域
- 17规划
- android 点击一个按钮,选择一张图片,获取路径,然后显示在屏幕
- 重构不是阳春白雪的高端玩意,而是码农编程利器
- php 下载excel模板方法1
- Android方法引用超过65535的优雅解决方式及你需要知道的前世今生
- 2222