上個月接到了我公司年會系統的需求,覺得做起來有些困難。后來硬著頭皮接下來了。年會1月6號順利舉行結束,整體上還算是成功,但是最后的搖1搖比賽出了些問題。在這里記錄下用到的技術,遇到的困難和選擇,和做的處理和不足。希望對大家有些參考。
1.做1個系統,需要權衡的維度,有以下幾個:
這就好比經典的CAP理論,魚和熊掌不可兼得。這里尋求了時間(只有兩周多的開發時間),本錢(實際上不應當過分緊縮本錢),功能(做全所有功能),放低了安全與周密的要求(例如消息傳遞沒有加密,傳遞的消息沒有蓋時間戳驗證流程,沒有完全的會話保持與權限控制等等),而且把代碼放到了GitHub上。
對1個針對普通大眾的年會,這么做多是沒問題的。但是對1個純程序員的年會,這么做就難免出問題(我們現場系統遭到了js注入,XSS注入,SQL注入還有指令注入攻擊。我們現場改代碼熱部署)。
現在回想,應當把1些功能做的更周密些,不應當過分緊縮本錢(其實就是多買兩臺服務器的事。。。)
2.對你做的系統,觸及到現場屏幕視覺設計的,1定要提早摹擬下視覺匹配
3.之前對Websocket的理解有誤,只在,對需要單向推送到客戶端(手機閱讀器)上的消息,應當都用Websocket,而不是采取客戶端輪詢。輪詢對服務器消耗太大。然后,其實更多情形應當用SSE
4. 對產品設計上,可能需要改變下自己程序員的思惟。程序員都有點because we can的思惟,這其實不都是缺點。但是把這個思惟用在設計產品上就掛了。這里的例子就是搖1搖抽獎。這里我們沒用微信搖1搖的功能,而是用js監控陀螺儀移動而做的搖1搖,顯示的次數其實不是準確的你搖動的次數,可能會有很大偏差。但是我們把這個數字展現出來了,并且沒做說明,讓很多用戶認為這個次數不公正,是我們私下做了手腳。
5.流程太繁瑣,走簡單流程搶時間難免出問題。目前,內部生產上線流程繁瑣而且時間長。我們如果采取的話,全部開發時間都得用來走上線流程。所以,沒采取公司資源,自己購買的騰訊云部署的利用,最后安全性出問題。如果走公司內部流程做足檢查就不會出這些問題,但是時間上不允許。估計等公司變革完,這個情況會改良很多。
6. 彈幕做了服務降級,其實搖1搖那里也應當做服務降級
剛開始,接到的需求主要有這幾個模塊:微信簽到上墻,CP簽到抽獎,彈幕上墻,節目打賞,抽獎,搖1搖比賽還有紅包鏈接展現。時間比較緊,基本上只有兩周多的時間去開發。
團隊里面算上我1共4人,都是新人(我是最老的員工,剛畢業1.5年。。)。劃分了下任務,A同學負責簽到前端,抽獎前后端,B同學負責節目管理打賞前端,搖1搖前端,C同學負責節目管理打賞前端,紅包鏈接展現前后端,CP簽到抽獎,我負責微信簽到后端,微信接口調試和彈幕上前前后端。
整體邏輯架構設計:
微信開發回比較容易,文檔全,但是文檔有的更新不夠新,而且管理界面有時讓人第1次使用摸不著頭腦。不過嘗試出來如何配置后,還比較容易的。
首先,你得先去申請個微信公眾號,我們這里要用的微信功能有:網頁服務中的網頁賬號服務,微信JSAPI。搖1搖我們沒用微信的搖1搖功能,用的是js的振東事件。對微信簽到,我們只用到網頁服務中的網頁賬號服務,其他的其他功能會用到。
對公眾號,如果需要網頁賬號服務,則需要你的公眾號經過認證。搖1搖需要其他資質認證,比較麻煩所以我沒用。
對測試,可以先申請個微信測試號:http://mp.weixin.qq.com/debug/cgi-bin/sandboxinfo?action=showinfo&t=sandbox/index
申請好后,我們看到:
這個是為了測試你的服務器是不是認證良好,并且信任這臺服務器并把消息轉發給這臺服務器。在配置時,微佩服務器會發1條消息到你配置的服務器,如果返回的結果正確,則配置成功(這里可以填寫域名或IP,正式的公眾號必須用域名,而且這個域名是ICP備案過的)。由于我們不做消息處理,而且我們只想簡單的啟用這個測試號,所以這里,我們只寫了1個簡單的直接返回結果的認證方法,代碼以下:
@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;
}
首先先要配置:
一樣的,這里可以填寫域名或IP,正式的公眾號必須用域名,而且這個域名是ICP備案過的
測試號信息中的appID還有appSecret是你的app開放認證信息的證書。
1般的,開放平臺都是利用OAuth2.0協議:
對微信,流程以下:
第1步:拼接自己的連接:
appId | wx0c7b8ab55037d5ca |
---|---|
scope | 利用授權作用域,snsapi_base (不彈出授權頁面,直接跳轉,只能獲得用戶openid),snsapi_userinfo (彈出授權頁面,可通過openid拿到昵稱、性別、所在地。并且,即便在未關注的情況下,只要用戶授權,也能獲得其信息),這里我們需要用snsapi_userinfo |
response_type | 只能填寫code |
state | 重定向到你的頁面時會帶上這個state參數,沒用的話隨意填寫就好了 |
redirect_uri | 域名1定要和你配置的1樣,否則會報redirect_uri毛病,需要url編碼 |
跳轉的鏈接需要接收兩個參數,1個是code,1個是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閱讀器,這樣能調試微信的鏈接。
第2步,編寫微信返回類:
微信的所有返回返回信息都是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;// 用戶唯1標識,請注意,在未關注公眾號時,用戶訪問公眾號的網頁,也會產生1個用戶和公眾號唯1的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;
}
}
由于年會只有1個晚上,我們不用更新用戶信息,所以對這里的expires_in其實不做處理。
以后通過accessToken拿取用戶信息返回的類以下:
public class UserInfoReturn extends BaseReturn {
private String openid;//用戶的唯1標識
private String nickname;//用戶昵稱
private int sex;//用戶的性別,值為1時是男性,值為2時是女性,值為0時是未知
private String province;//用戶個人資料填寫的省分
private String city;// 普通用戶個人資料填寫的城市
private String country;//國家,如中國為CN
private String headimgurl;//用戶頭像,最后1個數值代表正方形頭像大小(有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;
}
}
第3步,根據上面的流程,編寫下面代碼,拿取用戶信息:
@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);
//以后代碼略
}
可以參加晚會的人名單是固定的,除這些人,其他人不能參與晚會。我們先把所有的人名單導入到數據庫中。
我們使用工號姓名登陸。工號全是數字,有人有在工號前面加0的習慣,為了都能登錄,我們保存在數據庫中的類型是數字,前端傳輸過來的字符串會轉換成數字與數據庫中的比對。只有工號姓名匹配的用戶才能登陸系統。
對已授權的微信譽戶,如果登陸過的話,則不用再登陸1次。直接進入年會主界面。
用戶輸入工號姓名后,它的用戶信息會被保存到數據庫(包括工號姓名還有微信譽戶信息)中。由于微信信息中的openid是唯1的,所以根據這個是不是在數據庫中存在,判斷是不是是第1次登陸。
完全的代碼:
@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⑻"), 86400);
return "redirect:/frontend/login.html";
}
} catch (Exception e) {
log.warn(ExceptionUtils.getStackTrace(e));
}
return "redirect:/frontend/404.html";
}
這里我們偷懶了,并沒有嚴格的會話和登錄權限控制,只是做了簡單的cookie。面對都是程序員的晚會,不應當做這么簡單的登陸控制。
在用戶第1次成功登陸也就是簽到成功時,服務器需要將這個簽到消息推送給客戶端。這類單項推送的技術,有很多可以選擇:
服務器通過Websocket通道,將人員簽到的信息推送至簽到墻頁面,這里我應用的是最簡單的tomcat 7的websocket實現。