一个支持微信、支付宝“一码多付”共享按摩系统的诞生

       故事开头怎么写呢,大概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_id
	private 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数据库的使用(缓存订单,保存每台设备的定价,设备启动倒计时)
  • 短信网关(绑定手机号码赠送代金券)
  • 邮件系统(发送提醒邮件)
  • 系统部署在阿里云上
  • 域名的备案
  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值