应用场景:
网站某一类型注册类型会员通过微信公众账号进入其账号中心时,可以获取一次领取红包资格,红包资格在某一个特定范围内。
微信红包调用流程:
后台API调用:待进入联调过程时与开发进行详细沟通;
告知服务器:告知服务器接收微信红包的用户openID,告知服务器该用户获得的金额;
从商务号扣款:服务器获取信息后从对应的商务号扣取对应的金额;
调用失败:因不符合发送规则,商务号余额不足等原因造成调用失败,反馈至调用方;
发送成功:以微信红包公众账号发送对应红包至对应用户;
实现过程:
当用户点击领取红包按钮时,通过微信提供的 网页授权获取用户基本信息 不弹出授权页面方式获取用户的openid,根据获取到的openid判断用户是否有资格获取红包,针对有资格的用户,随机生成一个随机范围内的红包金额,并锁定该金额,通过微信提供的接口与微信服务器交互,根据微信服务器的返回结果释放锁定的金额。
数据库方面涉及两个表,一个表(hongbao)用来记录每个用户领取过的红包详情,一个表(c_hongbao)用来保存本次活动发放的红包总金额,已经发放的红包金额和剩余的红包金额。当对hongbao表操作时,通过触发器修改c_hongbao的值。
hongbao表
- @Entity
- @Table(name = "hongbao")
- @Proxy(lazy = false)
- @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
- public class Hongbao implements Serializable {
- private Long hongBaoId; // 红包表主键
- private String billNo; // 红包订单号
- private String openid; // 领取红包的用户ID
- private Integer amount; // 领取红包金额 单位分
- private Date addTime; // 添加时间
- private Integer result; // 领取红包结果 0失败 1成功 2锁定
- private String remark; // 备注 用于保存微信返回的json
- }
- @Entity
- @Table(name = "c_hongbao")
- @Proxy(lazy = false)
- @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
- public class CHongbao implements Serializable {
- private Long cHongbaoId;
- private Integer total; // 红包总金额 单位分
- private Integer balance; // 红包余额 单位分
- private Integer send; // 已经发送红包金额 单位分
- }
- DELIMITER $$
- CREATE
- TRIGGER insert_hongbao AFTER INSERT
- ON hongbao
- FOR EACH ROW BEGIN
- update c_hongbao set balance = balance - new.amount , send = send + new.amount ;
- END$$
- DELIMITER ;
- DELIMITER $$
- CREATE
- TRIGGER update_hongbao AFTER UPDATE
- ON hongbao
- FOR EACH ROW BEGIN
- if new.result = 0 then
- update c_hongbao set balance = balance + old.amount , send = send - old.amount ;
- end if;
- END$$
- DELIMITER ;
- /**
- * 生成随机红包金额
- * @param openid
- * @param billNo
- * @return
- */
- public synchronized Hongbao getAmount(String openid,String billNo){
- //该用户获取的随机红包金额
- int amount = (int) Math.round(Math.random()*(HongBaoUtil.MAX_VALUE-HongBaoUtil.MIN_VALUE)+HongBaoUtil.MIN_VALUE);
- StringBuffer hql = new StringBuffer("from CHongbao");
- //商户的红包总余额
- CHongbao po = service.queryOneByHQL(hql.toString());
- //如果此次随机金额比商户红包余额还要大,则返回商户红包余额
- if(amount > po.getBalance()){
- amount = po.getBalance();
- }
- Hongbao hongbao = new Hongbao();
- hongbao.setAddTime(new Date());
- hongbao.setAmount(amount);
- hongbao.setOpenid(openid);
- hongbao.setResult(HongBaoUtil.LOCK);
- hongbao.setBillNo(billNo);
- //先锁定用户领取的金额,防止领取金额超过预算金额
- service.save(hongbao);
- return hongbao;
- }
HongBaoUtil类
- /**
- * @author userwyh
- * @date 2015年3月5日
- * @time 上午11:53:40
- * @version 1.0
- */
- public class HongBaoUtil {
- public static final String MCH_ID = "XX"; //商户号
- public static final String WXAPPID = "XX"; //公众账号appid
- public static final String NICK_NAME = "XX"; //提供方名称
- public static final String SEND_NAME = "XX"; //商户名称
- public static final int MIN_VALUE = 100; //红包最小金额 单位:分
- public static final int MAX_VALUE = 200; //红包最大金额 单位:分
- public static final int TOTAL_NUM = 1; //红包发放人数
- public static final String WISHING = "XX"; //红包祝福语
- public static final String CLIENT_IP = "XX"; //调用接口的机器IP
- public static final String ACT_NAME = "XX"; //活动名称
- public static final String REMARK = "XX"; //备注
- public static final String KEY = "XX"; //秘钥
- public static final int FAIL = 0; //领取失败
- public static final int SUCCESS = 1; //领取成功
- public static final int LOCK = 2; //已在余额表中锁定该用户的余额,防止领取的红包金额大于预算
- /**
- * 对请求参数名ASCII码从小到大排序后签名
- * @param params
- */
- public static void sign(SortedMap<String, String> params){
- Set<Entry<String,String>> entrys=params.entrySet();
- Iterator<Entry<String,String>> it=entrys.iterator();
- StringBuffer result = new StringBuffer();
- while(it.hasNext())
- {
- Entry<String,String> entry=it.next();
- result.append(entry.getKey())
- .append("=")
- .append(entry.getValue())
- .append("&");
- }
- result.append("key=").append(KEY);
- params.put("sign", MD5Util.MD5(result.toString()));
- }
- /**
- * 生成提交给微信服务器的xml格式参数
- * @param params
- * @return
- */
- public static String getRequestXml(SortedMap<String,String> params){
- StringBuffer sb = new StringBuffer();
- sb.append("<xml>");
- Set es = params.entrySet();
- Iterator it = es.iterator();
- while(it.hasNext()) {
- Map.Entry entry = (Map.Entry)it.next();
- String k = (String)entry.getKey();
- String v = (String)entry.getValue();
- if ("nick_name".equalsIgnoreCase(k)||"send_name".equalsIgnoreCase(k)||"wishing".equalsIgnoreCase(k)||"act_name".equalsIgnoreCase(k)||"remark".equalsIgnoreCase(k)||"sign".equalsIgnoreCase(k)) {
- sb.append("<"+k+">"+"<![CDATA["+v+"]]></"+k+">");
- }else {
- sb.append("<"+k+">"+v+"</"+k+">");
- }
- }
- sb.append("</xml>");
- return sb.toString();
- }
- /**
- * 创建map
- * @param billNo
- * @param openid
- * @param userId
- * @param amount
- * @return
- */
- public static SortedMap<String, String> createMap(String billNo,String openid,String userId,int amount){
- SortedMap<String, String> params = new TreeMap<String, String>();
- params.put("wxappid",WXAPPID);
- params.put("nonce_str",createNonceStr());
- params.put("mch_billno",billNo);
- params.put("mch_id", MCH_ID);
- params.put("nick_name", NICK_NAME);
- params.put("send_name", SEND_NAME);
- params.put("re_openid", openid);
- params.put("total_amount", amount+"");
- params.put("min_value", amount+"");
- params.put("max_value", amount+"");
- params.put("total_num", TOTAL_NUM+"");
- params.put("wishing", WISHING);
- params.put("client_ip", CLIENT_IP);
- params.put("act_name", ACT_NAME);
- params.put("remark", REMARK);
- return params;
- }
- /**
- * 生成随机字符串
- * @return
- */
- public static String createNonceStr() {
- return UUID.randomUUID().toString().toUpperCase().replace("-", "");
- }
- /**
- * 生成商户订单号
- * @param mch_id 商户号
- * @param userId 该用户的userID
- * @return
- */
- public static String createBillNo(String userId){
- //组成: mch_id+yyyymmdd+10位一天内不能重复的数字
- //10位一天内不能重复的数字实现方法如下:
- //因为每个用户绑定了userId,他们的userId不同,加上随机生成的(10-length(userId))可保证这10位数字不一样
- Date dt=new Date();
- SimpleDateFormat df = new SimpleDateFormat("yyyymmdd");
- String nowTime= df.format(dt);
- int length = 10 - userId.length();
- return MCH_ID + nowTime + userId + getRandomNum(length);
- }
- /**
- * 生成特定位数的随机数字
- * @param length
- * @return
- */
- private static String getRandomNum(int length) {
- String val = "";
- Random random = new Random();
- for (int i = 0; i < length; i++) {
- val += String.valueOf(random.nextInt(10));
- }
- return val;
- }
- /**
- * post提交到微信服务器
- * @param requestXML
- * @param instream
- * @return
- * @throws NoSuchAlgorithmException
- * @throws CertificateException
- * @throws IOException
- * @throws KeyManagementException
- * @throws UnrecoverableKeyException
- * @throws KeyStoreException
- */
- public static String post(String requestXML,InputStream instream) throws NoSuchAlgorithmException, CertificateException, IOException, KeyManagementException, UnrecoverableKeyException, KeyStoreException{
- KeyStore keyStore = KeyStore.getInstance("PKCS12");
- try {
- keyStore.load(instream, MCH_ID.toCharArray());
- } finally {
- instream.close();
- }
- SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, MCH_ID.toCharArray()).build();
- SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
- sslcontext,
- new String[] { "TLSv1" },
- null,
- SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
- CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
- String result = "";
- try {
- HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack");
- StringEntity reqEntity = new StringEntity(requestXML,"utf-8"); //如果此处编码不对,可能导致客户端签名跟微信的签名不一致
- reqEntity.setContentType("application/x-www-form-urlencoded");
- httpPost.setEntity(reqEntity);
- CloseableHttpResponse response = httpclient.execute(httpPost);
- try {
- HttpEntity entity = response.getEntity();
- if (entity != null) {
- BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(entity.getContent(),"UTF-8"));
- String text;
- while ((text = bufferedReader.readLine()) != null) {
- result +=text;
- }
- }
- EntityUtils.consume(entity);
- } finally {
- response.close();
- }
- } finally {
- httpclient.close();
- }
- return result;
- }
接受请求的Action
- @Action(value = "hongbao", results = {
- @Result(name = "success", location = "/html/hongbao/result.jsp") }, interceptorRefs = { @InterceptorRef("myDefaultInterceptorStack") })
- public String hongbao() {
- String url = WeixinUtil.getUrl();
- if (StringUtils.isNotEmpty(code)) {
- url = url.replace("CODE", code);
- Map<String, Object> map = JsonUtil.getMapByUrl(url);
- String openid =map.get("openid").toString();
- // 若openid读取有误,则跳转到不合法的页面
- if (StringUtils.isNotEmpty(openid)) {
- StringBuffer hql = new StringBuffer();
- hql.append(" from Bind b where 1=1 ");
- hql.append(" and b.openid = '");
- hql.append(openid).append("'");
- List<Bind> list = service.queryListByHQL(hql.toString());
- if (list != null && list.size() > 0) {
- Bind user = list.get(0);
- //用户类型
- String userType = user.getRegisterusertypeid().toString();
- //只允许MASTER_TYPE类型标识的用户参与红包领取
- if(userType.equals(AppKeysUtil.MASTER_TYPE)){
- hql.setLength(0);
- hql.append("from Hongbao where openid = '").append(openid).append("' order by hongBaoId desc");
- SystemLogger.debug(hql.toString());
- //默认没有领取过红包
- boolean isReceive = false;
- List<Hongbao> hongBaoList = dao.queryListByHQL(hql.toString());
- if(hongBaoList!=null&&hongBaoList.size()>0){
- //最近一条记录领取成功或者处于锁定状态
- if(hongBaoList.get(0).getResult()!=0){
- isReceive = true;
- }
- }
- if(!isReceive){
- String billNo = HongBaoUtil.createBillNo(user.getUserid()+"");
- Hongbao hongbao = getAmount(openid,billNo);
- SortedMap<String, String> map = HongBaoUtil.createMap(billNo, openid, user.getUserid()+"",hongbao.getAmount());
- HongBaoUtil.sign(map);
- String requestXML = HongBaoUtil.getRequestXml(map);
- try {
- HttpServletRequest request = ServletActionContext.getRequest();
- //加载微信提供给商户的证书
- InputStream instream = request.getSession().getServletContext().getResourceAsStream("XXXXXXXXXXXXXXXXXXX");
- //与微信交互并接收返回参数
- String responseXML = HongBaoUtil.post(requestXML,instream);
- //将微信返回的xml格式参数转成Map
- Map<String,String> resultMap = XmlUtil.parseXml(responseXML);
- String return_code = resultMap.get("return_code").toString();
- if("SUCCESS".equals(return_code)){
- hongbao.setResult(HongBaoUtil.SUCCESS);
- message = "红包已经发送";
- }else{
- hongbao.setResult(HongBaoUtil.FAIL);
- message = "领取红包失败,请重试";
- }
- hongbao.setRemark(responseXML);
- service.update(hongbao);
- } catch (KeyManagementException e) {
- e.printStackTrace();
- hongbao.setResult(HongBaoUtil.FAIL);
- message = "领取红包失败,请重试";
- service.update(hongbao);
- }
- }else{
- message = "已经领取过红包";
- }
- }else{
- message = "该用户类型不能参与红包领取";
- }
- } else {
- message = "没有关注";
- }
- }//StringUtils.isNotEmpty(openid)
- }
- return SUCCESS;
- }
测试结果:
随机在100-200分之间生成的一个随机数,写了几百行代码换来的红包