故事开头怎么写呢,大概2016年10月份吧,当时出差飞机刚落地,久未联系的同学打来电话,说在创业做共享按摩椅,需求说的很简单,扫码支付启动。当时市场上共享按摩椅才兴起,由于平时工作不涉及支付,相关硬件通信模块更是了解甚少,就给回绝了。然后在回去的路上在想,不可能一辈子coding吧,支付、物联网会越来越火,怎么就不能尝试一下呢。
俗话说隔行如隔山,外行看来很简单的需求,不就扫码支付成功后硬件启动。既然想尝试,就详细了解下需求,然后把功能分解一下
过程娓娓道来,先看系统吧。http://m.xyxspace.com 【测试系统地址 】
登录账号可关注上面微信公众号,回复“测试账号”获得。若登录失败,密码可能被其他人修改,可在公众号回复“重置密码”后,后台会自动初始化账号信息,此功能可在系统后台配置。系统demo发邮件至540769049@qq.com,标明来由。
系统架构图
系统业务逻辑流程图
系统功能
1)后台管理模块
a)硬件设备管理
- 设备的增删改查,导出
- 设备上所贴二维码的生成
- 设备的定价
- 设备在线状态、信号强度、grps坐标的更新
b)会员管理
- 会员的增删改查,导出
- 会员余额,积分的管理
c)代金券管理
- 关注公众号、绑定手机号码,自动赠送代金券
- 给指定合作商户批量生成代金券,给指定用户批量绑定代金券,代金券的批量导出
- 代金券的增删改查
- 代金券的过期失效
d)订单管理
- 订单的删查,导出
e)统计功能
- 收入明细
- 按所属网点、每一台设备从微信、支付宝、投币维度统计指定时间点的收入详情
- 统计指定时间范围内每天的收入趋势
- 统计指定时间范围内每小时的收入趋势
f)充值功能
2)支付模块
支持微信、支付宝扫码支付。
3)物联网模块
与硬件设备的通信,控制硬件设备。
一:从无到有,系统搭建
很多系统功能如用户注册、登录、权限,菜单管理等等,都是通用的,这里借鉴了一个成熟的通用框架(https://blog.csdn.net/wernisng090/article/details/50864520),写的挺好,简单了解一下实现过程,直接在此基础上修改了。
二:按照业务流程图一一道来
1.用户扫码
- 入口
@RequestMapping(value = "/{numericPart:[\\d]*}")public ModelAndView scaning() {String url = request.getRequestURL().toString();String mcode = url.substring(url.lastIndexOf("/") + 1);session.setAttribute("mcode", mcode);String id = "";if (Constants.WEIXIN.equals(appType)) {// 来自微信logger.info("微信扫码...");id = (String) session.getAttribute("openid");} else if (Constants.ALIPAY.equals(appType)) {// 来自支付宝logger.info("支付宝扫码...");id = (String) session.getAttribute("user_id");}if (StringUtils.isBlank(id)) {// session中未存在用户信息时,授权重新获取用户信息return indexService.createRedirectURL(appType,mcode);} else {return commonService.trunView(id, mcode, basePath);// 跳转到首页价格页面}}public ModelAndView createRedirectURL(String appType,String mcode) {if (Constants.WEIXIN.equals(appType)) {// 微信客户端String redirectUrl = OAuthManager.generateRedirectURI(WXConstants.REDIRECT_URI, WXConstants.SCOPE, mcode);return new ModelAndView("redirect:" + redirectUrl);} else if (Constants.ALIPAY.equals(appType)) {// 支付宝客户端String redirectUrl = OAuthALiService.generateRedirectURI(AlipayConfig.REDIRECTURI, AlipayConfig.ALIPAY_SCOPE, mcode);return new ModelAndView("redirect:" + redirectUrl);} else {ModelAndView mv = new ModelAndView();mv.setViewName("view/index/unAllow");return mv;}}
- 判断客户端类型
String userAgent = request.getHeader("user-agent");if (StringUtils.isNotBlank(userAgent)) {userAgent = userAgent.toLowerCase();if (userAgent.indexOf("micromessenger") > -1) {// 微信客户端return Constants.WEIXIN;} else if (userAgent.indexOf("alipayclient") > -1) {return Constants.ALIPAY;}}
2.授权获取用户信息
- 微信
2018-08-10 14:29:29,927 INFO [com.xyx.controller.IndexController] - <微信扫码...>
2018-08-10 14:29:34,583 INFO [com.xyx.wx.controller.WXController] - <weixin回调方法...>
2018-08-10 14:29:34,852 INFO [com.xyx.wx.controller.WXController] -
<oauth2获取到的用户信息:[GetUserinfoResponse{openid='o6QuCwb26Pxx1wKkR8CfF9WXoRjU', nickname='她', sex='1', province='河南', city='郑州', country='中国', headimgurl='http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoSibuECOFWda0OseruXfvAWp2GLs0LiceQ87mImXkMRDpAwz9ddNxZatgzroqamaRvWd9hEHTs4xCA/132', privilege=[], unionid='null'}]>
- 支付宝
2018-08-10 14:31:55,456 INFO [com.xyx.controller.IndexController] - <支付宝扫码...>
2018-08-10 14:32:01,387 INFO [com.xyx.alipay.controller.AlipayController] - <ali授权回调方法>
2018-08-10 14:32:01,963 INFO [com.xyx.alipay.controller.AlipayController] -
<{"alipay_user_info_share_response":{"code":"10000","msg":"Success","avatar":"https:\/\/tfs.alipayobjects.com\/images\/partner\/T1fV0vXhpbXXXXXXXX","city":"北京市","gender":"m","is_certified":"T","is_student_certified":"T","nick_name":"neversayd","province":"北京","user_id":"2088012705591342","user_status":"T","user_type":"2"},"sign":"aONatLtOKIl0eWhapZ9VwA+2FupRWE5JM8c353ZrA+VEvmPSvY6E8RObNnXewTvKWL7Jom4WDAOBO8NqE7QfwKYU1WpBEPTe/Ysms+d3ROcvRbhNZiPAgTIpD1hwLR+49oyTtdKrZaUgE+u+T5LE/GL8mx0XHR2Msd9nfJ8O2LsoCtT2EeKSlBazRATcErAoGXciVik7DS9mj9CCXUyYR17kVZgYbROVbvKJNPpT+bPLRL3jH7ofPgh3VR4xmngT2cUHg/qyMaH6vWX4ZsYQgRdLldLga43HWLlU4Nya9haQZFIPzKZNZbhzOqyA7upUaFJFM9vHDkuWQHNM4/ojaA=="}>
- 用户列表
二:支付模块
一码多付,使用微信公众号支付,支付宝手机网站支付功能。
可参考之前写的一个blog,所示代码都是从本系统里摘抄的,https://mp.csdn.net/postedit/79003172
三:微信公众号
- 微信消息事件分发,event事件分发(接口中相应抽象方法已定义,子类继承实现即可)
/*** 消息事件分发*/private void dispatchMessage(){logger.info("distributeMessage start");if(StringUtils.isBlank(wechatRequest.getMsgType())){logger.info("msgType is null");}MsgType msgType = MsgType.valueOf(wechatRequest.getMsgType());logger.info("msgType is " + msgType.name());switch (msgType) {case event:dispatchEvent();break;case text:onText();break;case image:onImage();break;case voice:onVoice();break;case video:onVideo();break;case shortvideo:onShortVideo();break;case location:onLocation();break;case link:onLink();break;default:onUnknown();break;}}/*** event事件分发*/private void dispatchEvent() {EventType event = EventType.valueOf(wechatRequest.getEvent());logger.info("dispatch event,event is " + event.name());switch (event) {case CLICK:click();break;case subscribe:subscribe();break;case unsubscribe:unSubscribe();break;case SCAN:scan();break;case LOCATION:location();break;case VIEW:view();break;case TEMPLATESENDJOBFINISH:templateMsgCallback();break;case scancode_push:scanCodePush();break;case scancode_waitmsg:scanCodeWaitMsg();break;case pic_sysphoto:picSysPhoto();break;case pic_photo_or_album:picPhotoOrAlbum();break;case pic_weixin:picWeixin();break;case location_select:locationSelect();break;case kf_create_session:kfCreateSession();break;case kf_close_session:kfCloseSession();break;case kf_switch_session:kfSwitchSession();break;default:break;}}
/*** 微信消息类型,大小写对应微信接口,msgType的枚举值*/
public enum MsgType {event, //事件text, //文本消息image,location,link,voice,video,shortvideo, //小视频消息music,news,transfer_customer_service;//客服系统
}/*** 微信事件类型*/
public enum EventType {subscribe, //关注unsubscribe, //取消关注/** 创建菜单使用 */click, CLICK, //点击/** 创建菜单使用 */view, VIEW, //跳转链接SCAN, //扫描LOCATION, //上报地理位置TEMPLATESENDJOBFINISH, //模板消息发送成功之后事件scancode_push, //扫码推事件scancode_waitmsg, //扫码推事件且弹出“消息接收中”提示框的事件pic_sysphoto, //弹出系统拍照发图的事件pic_photo_or_album, //弹出拍照或者相册发图的事件pic_weixin, //弹出微信相册发图器的事件location_select, //弹出地理位置选择器的事件media_id, //下发消息(除文本消息)view_limited, //跳转图文消息URL kf_create_session, //接入会话kf_close_session, //关闭会话kf_switch_session, //转接会话
}
- 微信access_token、jsapi_ticket中控服务器
可参考之前写的另一篇blog,所示代码都是从本系统里摘抄的,https://blog.csdn.net/gotohomebye/article/details/78768112
- 微信模板消息(支付成功后的消息通知,账户信息通知)
- 微信菜单管理
/*** 微信菜单*/
public class Menu {private List<MenuButton> button;public List<MenuButton> getButton() {return button;}public void setButton(List<MenuButton> button) {this.button = button;}}
/*** 菜单按钮*/
public class MenuButton {private EventType type;//菜单的响应动作类型private String name;//菜单标题,不超过16个字节,子菜单不超过40个字节private String key;//click等点击类型必须 菜单KEY值,用于消息接口推送,不超过128字节private String url;//view类型必须 网页链接,用户点击菜单可打开链接,不超过256字节private String mediaId;//media_id类型和view_limited类型必须 调用新增永久素材接口返回的合法media_idprivate List<MenuButton> subButton;//子菜单,每个一级菜单最多包含5个二级菜单}/*** 菜单按钮类型*/
public enum MenuButtonType {/** 点击 */click,/** 跳转URL */view,/** 扫码推事件 */scancode_push,/** 扫码推事件且弹出“消息接收中”提示框 */scancode_waitmsg,/** 弹出系统拍照发图 */pic_sysphoto,/** 弹出拍照或者相册发图 */pic_photo_or_album,/** 弹出微信相册发图器 */pic_weixin,/** 弹出地理位置选择器 */location_select,/** //下发消息(除文本消息) */media_id,/** 跳转图文消息URL */view_limited;
}/*** 微信菜单操作*/
public class MenuManager {private static Logger logger = Logger.getLogger(MenuManager.class);private static final String MENU_CREATE_POST_URL = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=";private static final String MENU_GET_GET_URL = "https://api.weixin.qq.com/cgi-bin/menu/get?access_token=";private static final String MENU_DEL_GET_URL = "https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=";private String accessToken;public MenuManager() {this.accessToken = TokenProxy.accessToken();}/*** 创建菜单* @throws WeChatException */public void create(Menu menu) throws WeChatException{logger.info("创建菜单");String resultStr = HttpUtils.post(MENU_CREATE_POST_URL+this.accessToken, JSON.toJSONString(menu));WeChatUtil.isSuccess(resultStr);}/*** 查询菜单*/public Menu getMenu() { logger.info("查询菜单");String resultStr = HttpUtils.get(MENU_GET_GET_URL+this.accessToken);try {WeChatUtil.isSuccess(resultStr);} catch (WeChatException e) {e.printStackTrace();return null;}JSONObject menuObject = JSONObject.parseObject(resultStr);Menu menu = menuObject.getObject("menu", Menu.class);return menu;}/*** 删除菜单* @throws WeChatException */public void delete() throws WeChatException{logger.info("删除菜单");String resultStr = HttpUtils.get(MENU_DEL_GET_URL+this.accessToken);WeChatUtil.isSuccess(resultStr);}
四:支付宝支付
五:系统技术
使用springmvc+mybatis3.2后台框架,mysql5.7数据库,HTML5+css3.0+bootstrap前端页面,redis、shiro 、ehcache 、druid等技术。
- mysql5.7 保存微信emoj表情
- 设备上所张贴二维码的自动生成
- redis数据库的使用(缓存订单,保存每台设备的定价,设备启动倒计时)
- 短信网关(绑定手机号码赠送代金券)
- 邮件系统(发送提醒邮件)
- 系统部署在阿里云上
- 域名的备案