From 9634244f5ad3c8493fb28e3a32d7526977cdd160 Mon Sep 17 00:00:00 2001
From: KKSU <15274802129@163.com>
Date: Wed, 05 Feb 2025 11:58:50 +0800
Subject: [PATCH] feat(pay): 添加 FIUU支付功能
---
src/main/java/cc/mrbird/febs/pay/controller/XcxPayController.java | 511 ++++++++++++++++++++++++++++++++++++++++++--------------
1 files changed, 380 insertions(+), 131 deletions(-)
diff --git a/src/main/java/cc/mrbird/febs/pay/controller/XcxPayController.java b/src/main/java/cc/mrbird/febs/pay/controller/XcxPayController.java
index 4175218..54c4d4d 100644
--- a/src/main/java/cc/mrbird/febs/pay/controller/XcxPayController.java
+++ b/src/main/java/cc/mrbird/febs/pay/controller/XcxPayController.java
@@ -1,131 +1,380 @@
-package cc.mrbird.febs.pay.controller;
-
-import cc.mrbird.febs.common.entity.FebsResponse;
-import cc.mrbird.febs.common.enumerates.OrderStatusEnum;
-import cc.mrbird.febs.mall.entity.MallOrderInfo;
-import cc.mrbird.febs.mall.mapper.MallOrderInfoMapper;
-import cc.mrbird.febs.pay.model.NotifyData;
-import cc.mrbird.febs.pay.util.PayThreadPool;
-import cc.mrbird.febs.pay.util.Signature;
-import cc.mrbird.febs.pay.util.Util;
-import cc.mrbird.febs.pay.util.WechatConfigure;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.BeanUtils;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.transaction.annotation.Transactional;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-
-import javax.servlet.ServletOutputStream;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-
-@Slf4j
-@RestController
-@RequestMapping(value = "/api/xcxPay")
-public class XcxPayController {
-
- @Autowired
- MallOrderInfoMapper mallOrderInfoMapper;
- /**
- * 微信支付回调接口
- */
- @Transactional(rollbackFor = Exception.class)
- @RequestMapping(value = "/wxpayCallback")
- public void payCallBack(HttpServletResponse response, HttpServletRequest request) throws IOException {
- log.info("微信支付回调start....");
-
- // 获取输入参数
- String inputLine;
- StringBuffer notityXml = new StringBuffer();
- String resXml = "";
- String orderId = "";
-
- FebsResponse threadResult = new FebsResponse();
- try {
- while ((inputLine = request.getReader().readLine()) != null) {
- notityXml.append(inputLine);
- }
- request.getReader().close();
- log.info("notityXml ---- :{} ", notityXml);
-
-
- // XMl转对象
- Object bb = Util.getObjectFromXML(notityXml.toString(), NotifyData.class);
- NotifyData data = new NotifyData();
- BeanUtils.copyProperties(bb,data);
- log.info("----return_code = {}", data.getReturn_code());
-
-
- // 返回状态码 SUCCESS/FAIL
- if (WechatConfigure.CODE_SUCCESS.equals(data.getReturn_code())) {
-
- orderId = data.getAttach();
- // 检验订单状态
- MallOrderInfo order = mallOrderInfoMapper.selectById(Long.valueOf(orderId));
-
- // 校验签名
- String paySecret = WechatConfigure.WECHARPAY_SECRET;
- if (Signature.checkIsSignValidFromResponseString(notityXml.toString(),paySecret)) {
- // 校验业务结果
- if (WechatConfigure.CODE_SUCCESS.equals(data.getResult_code())) {
- // 返回SUCCESS报文
- resXml = WechatConfigure.RESULT_XML_SUCCESS;
- // 支付费用
- Double total_fee = Double.parseDouble(data.getTotal_fee());
- // 商户订单号
- String payNum = data.getOut_trade_no();
-
- log.info("支付回调关键信息---total_fee:{},payNum:{},orderId:{}", total_fee, payNum, orderId);
- // 订单ID
- BigDecimal payMoney = new BigDecimal(total_fee).divide(new BigDecimal(100), 2,
- RoundingMode.HALF_UP);
-
-
- if (order != null && OrderStatusEnum.WAIT_PAY.getValue() == order.getStatus()) {
- log.debug("检查支付金额payMoney={},order.getPayMoney()={}", payMoney, order.getAmount());
- order.setStatus(OrderStatusEnum.WAIT_SHIPPING.getValue());
- mallOrderInfoMapper.updateById(order);
- threadResult.success().message("支付成功");
- } else {
- log.info("订单状态不为待付款,order status=", order.getStatus());
- }
- } else {
- log.info("微信标识业务是失败");
- threadResult.fail().message("查询支付信息失败,请联系客服或者刷新支付信息(错误码:001)");
-// resXml = AppConstance.RESULT_XML_FAIL.replace(ERRORMSG, "微信标识业务是失败");
- }
- } else {
- log.info("无效签名");
- threadResult.fail().message("查询支付信息失败,请联系客服或者刷新支付信息(错误码:002)");
-// resXml = AppConstance.RESULT_XML_FAIL.replace(ERRORMSG, "微信标识业务是失败");
- }
- } else {
- log.info("通信标识失败");
- threadResult.fail().message("查询支付信息失败,请联系客服或者刷新支付信息(错误码:003)");
-// resXml = AppConstance.RESULT_XML_FAIL.replace(ERRORMSG, "通信标识失败");
- }
- } catch (Exception e) {
- log.error("支付回调签名错误", e);
- threadResult.fail().message("查询支付信息失败,请联系客服或者刷新支付信息(错误码:004)");
-// resXml = AppConstance.RESULT_XML_FAIL.replace(ERRORMSG, "支付回调签名错误");
- } finally {
- // 通知线程消息
- PayThreadPool.notifyThread(Integer.valueOf(orderId), threadResult);
- sendResultBack(response, resXml);
- }
- return;
-
- }
-
- private void sendResultBack(HttpServletResponse response, String resXml) throws IOException {
- log.info("返回微信数据={}", resXml);
- ServletOutputStream out = response.getOutputStream();
- out.write(resXml.getBytes());
- out.flush();
- out.close();
- }
-}
+//package cc.mrbird.febs.pay.controller;
+//
+//import cc.mrbird.febs.common.entity.FebsResponse;
+//import cc.mrbird.febs.common.enumerates.*;
+//import cc.mrbird.febs.common.properties.XcxProperties;
+//import cc.mrbird.febs.common.utils.RedisUtils;
+//import cc.mrbird.febs.common.utils.SpringContextHolder;
+//import cc.mrbird.febs.mall.dto.RechargeWalletMessageSendDto;
+//import cc.mrbird.febs.mall.entity.*;
+//import cc.mrbird.febs.mall.mapper.*;
+//import cc.mrbird.febs.mall.service.IApiMallMemberService;
+//import cc.mrbird.febs.mall.service.IApiMallMemberWalletService;
+//import cc.mrbird.febs.mall.service.IMallMoneyFlowService;
+//import cc.mrbird.febs.pay.model.NotifyData;
+//import cc.mrbird.febs.pay.service.IXcxPayService;
+//import cc.mrbird.febs.pay.service.WxFaPiaoService;
+//import cc.mrbird.febs.pay.util.Signature;
+//import cc.mrbird.febs.pay.util.Util;
+//import cc.mrbird.febs.pay.util.WechatConfigure;
+//import cc.mrbird.febs.rabbit.producter.AgentProducer;
+//import cn.hutool.core.date.DateUtil;
+//import cn.hutool.core.util.ObjectUtil;
+//import cn.hutool.json.JSONObject;
+//import cn.hutool.json.JSONUtil;
+//import com.wechat.pay.contrib.apache.httpclient.notification.NotificationRequest;
+//import lombok.extern.slf4j.Slf4j;
+//import org.springframework.beans.BeanUtils;
+//import org.springframework.beans.factory.annotation.Autowired;
+//import org.springframework.transaction.annotation.Transactional;
+//import org.springframework.web.bind.annotation.RequestBody;
+//import org.springframework.web.bind.annotation.RequestMapping;
+//import org.springframework.web.bind.annotation.RestController;
+//
+//import javax.servlet.ServletOutputStream;
+//import javax.servlet.http.HttpServletRequest;
+//import javax.servlet.http.HttpServletResponse;
+//import java.io.BufferedReader;
+//import java.io.IOException;
+//import java.math.BigDecimal;
+//import java.math.RoundingMode;
+//import java.nio.charset.StandardCharsets;
+//import java.security.InvalidKeyException;
+//import java.security.NoSuchAlgorithmException;
+//import java.security.SignatureException;
+//import java.text.ParseException;
+//import java.util.Date;
+//import java.util.HashMap;
+//import java.util.Map;
+//
+//@Slf4j
+//@RestController
+//@RequestMapping(value = "/api/xcxPay")
+//public class XcxPayController {
+//
+// @Autowired
+// MallOrderInfoMapper mallOrderInfoMapper;
+// @Autowired
+// MallMoneyFlowMapper mallMoneyFlowMapper;
+// @Autowired
+// MallMemberWalletMapper mallMemberWalletMapper;
+// @Autowired
+// MallMemberMapper mallMemberMapper;
+// @Autowired
+// DataDictionaryCustomMapper dataDictionaryCustomMapper;
+// @Autowired
+// IMallMoneyFlowService mallMoneyFlowService;
+// @Autowired
+// RedisUtils redisUtils;
+// @Autowired
+// private IApiMallMemberWalletService memberWalletService;
+// @Autowired
+// private IApiMallMemberService mallMemberService;
+// @Autowired
+// private MallAgentRecordMapper mallAgentRecordMapper;
+// @Autowired
+// private AgentProducer agentProducer;
+//
+// @Autowired
+// private IXcxPayService iXcxPayService;
+//
+// @Autowired
+// private WxFaPiaoService wxFaPiaoService;
+//
+// private final XcxProperties xcxProperties = SpringContextHolder.getBean(XcxProperties.class);
+// /**
+// * 微信充值回调接口
+// */
+// @Transactional(rollbackFor = Exception.class)
+// @RequestMapping(value = "/rechargeCallBack")
+// public void rechargeCallBack(HttpServletResponse response, HttpServletRequest request) throws IOException {
+// log.info("微信充值回调start....");
+//
+// // 获取输入参数
+// String inputLine;
+// StringBuffer notityXml = new StringBuffer();
+// String resXml = "";
+//
+//// String attrStr = "{'rechargeNo':"+rechargeNo+",'memberId':"+mallMember.getId()+"}";
+// String attrStr = "";
+//
+// FebsResponse threadResult = new FebsResponse();
+// try {
+// while ((inputLine = request.getReader().readLine()) != null) {
+// notityXml.append(inputLine);
+// }
+// request.getReader().close();
+// log.info("notityXml ---- :{} ", notityXml);
+//
+//
+// // XMl转对象
+// Object bb = Util.getObjectFromXML(notityXml.toString(), NotifyData.class);
+// NotifyData data = new NotifyData();
+// BeanUtils.copyProperties(bb,data);
+// log.info("----return_code = {}", data.getReturn_code());
+// // 返回状态码 SUCCESS/FAIL
+// if (WechatConfigure.CODE_SUCCESS.equals(data.getReturn_code())) {
+// attrStr = data.getAttach();
+// JSONObject jsonObject = JSONUtil.parseObj(attrStr);
+// String rechargeNo = (String) jsonObject.get("rechargeNo");
+// Integer type = (Integer) jsonObject.get("type");
+// Long memberId = Long.parseLong(jsonObject.get("memberId").toString());
+// Long agentApplyId = Long.parseLong(jsonObject.get("agentApplyId").toString());
+// // 检验订单状态
+// MallMoneyFlow mallMoneyFlow = mallMoneyFlowMapper.selectOneByOrderNoAndMemberId(rechargeNo,memberId);
+// // 校验签名
+//// String paySecret = WechatConfigure.WECHARPAY_SECRET;
+// String paySecret = xcxProperties.getWecharpaySecret();
+// if (Signature.checkIsSignValidFromResponseString(notityXml.toString(),paySecret)) {
+// // 校验业务结果
+// if (WechatConfigure.CODE_SUCCESS.equals(data.getResult_code())) {
+// // 返回SUCCESS报文
+// resXml = WechatConfigure.RESULT_XML_SUCCESS;
+// // 支付费用
+// Double total_fee = Double.parseDouble(data.getTotal_fee());
+// // 商户订单号
+// String payNum = data.getOut_trade_no();
+//
+// log.info("支付回调关键信息---total_fee:{},payNum:{},rechargeNo:{}", total_fee, payNum, rechargeNo);
+// // 订单ID
+// BigDecimal payMoney = new BigDecimal(total_fee).divide(new BigDecimal(100), 2,
+// RoundingMode.HALF_UP);
+//
+// if (ObjectUtil.isNotEmpty(mallMoneyFlow)) {
+// log.debug("检查支付金额payMoney={},mallMoneyFlow.getPayMoney()={}", payMoney, mallMoneyFlow.getAmount());
+// //合伙人申请的充值,要更新会员状态为FIRST_LEVEL
+// if(2 == type){
+// log.info("微信充值回调" + agentApplyId);
+// MallAgentRecord mallAgentRecord = mallAgentRecordMapper.selectById(agentApplyId);
+// mallMemberService.updateMemberAgent(agentApplyId,mallAgentRecord.getAgentLevel());
+// }
+// memberWalletService.addBalance(payMoney,memberId);
+// mallMoneyFlow.setStatus(2);
+// mallMoneyFlowMapper.updateById(mallMoneyFlow);
+//
+// /**
+// * 充值赠送金额
+// */
+// DataDictionaryCustom giveStateDic = dataDictionaryCustomMapper.selectDicDataByTypeAndCode(
+// DataDictionaryEnum.GIVE_STATE.getType(),
+// DataDictionaryEnum.GIVE_STATE.getCode());
+//
+// DataDictionaryCustom giveAmountDic = dataDictionaryCustomMapper.selectDicDataByTypeAndCode(
+// DataDictionaryEnum.GIVE_AMOUNT.getType(),
+// DataDictionaryEnum.GIVE_AMOUNT.getCode());
+//
+// DataDictionaryCustom chargeAmountDic = dataDictionaryCustomMapper.selectDicDataByTypeAndCode(
+// DataDictionaryEnum.CHARGE_AMOUNT.getType(),
+// DataDictionaryEnum.CHARGE_AMOUNT.getCode());
+// /**
+// * 普通充值
+// * 开启了充值赠送
+// * 系统设置的赠送金额和充值金额不为空
+// * 充值金额大于等于系统设置的充值金额
+// */
+// if(1 == type
+// && ObjectUtil.isNotEmpty(giveStateDic)
+// && "1".equals(giveStateDic.getValue())
+// && ObjectUtil.isNotEmpty(giveAmountDic)
+// && ObjectUtil.isNotEmpty(chargeAmountDic)){
+// BigDecimal giveAmount = ObjectUtil.isEmpty(giveAmountDic.getValue()) ?
+// BigDecimal.ZERO :
+// new BigDecimal(giveAmountDic.getValue()).abs().setScale(2,BigDecimal.ROUND_DOWN);
+// BigDecimal chargeAmount = ObjectUtil.isEmpty(chargeAmountDic.getValue()) ?
+// BigDecimal.ZERO :
+// new BigDecimal(chargeAmountDic.getValue()).abs().setScale(2,BigDecimal.ROUND_DOWN);
+// if(payMoney.compareTo(chargeAmount) >= 0){
+// mallMoneyFlowService.addMoneyFlow(
+// memberId,
+// giveAmount,
+// MoneyFlowTypeEnum.RECHARGE_SEND.getValue(),
+// rechargeNo+"ZS",
+// FlowTypeEnum.BALANCE.getValue(),
+// "充值赠送金额",
+// 2);
+//
+// memberWalletService.addBalance(giveAmount,memberId);
+// }
+// }
+//
+// RechargeWalletMessageSendDto rechargeWalletMessageSendDto = new RechargeWalletMessageSendDto();
+// rechargeWalletMessageSendDto.setRechargeNo(rechargeNo);
+// rechargeWalletMessageSendDto.setRechargeAmount(payMoney.toString());
+//
+// MallMemberWallet mallMemberWallet = mallMemberWalletMapper.selectWalletByMemberId(memberId);
+// rechargeWalletMessageSendDto.setBalance(mallMemberWallet.getBalance().toString());
+// rechargeWalletMessageSendDto.setCreateTime(DateUtil.now());
+// rechargeWalletMessageSendDto.setOpenId(mallMemberMapper.selectById(memberId).getOpenId());
+// DataDictionaryCustom dataDictionaryCustom = dataDictionaryCustomMapper.selectDicDataByTypeAndCode(DataDictionaryEnum.WX_TEMPLATE_ID_TWO.getType(), DataDictionaryEnum.WX_TEMPLATE_ID_TWO.getCode());
+// rechargeWalletMessageSendDto.setTemplateId(dataDictionaryCustom.getValue());
+// iXcxPayService.rechargeWalletMessageSend(rechargeWalletMessageSendDto);
+// threadResult.success().message("充值成功");
+// } else {
+// log.info("充值失败", attrStr);
+// }
+// } else {
+// log.info("微信标识业务是失败");
+// threadResult.fail().message("查询支付信息失败,请联系客服或者刷新支付信息(错误码:001)");
+//// resXml = AppConstance.RESULT_XML_FAIL.replace(ERRORMSG, "微信标识业务是失败");
+// }
+// } else {
+// log.info("无效签名");
+// threadResult.fail().message("查询支付信息失败,请联系客服或者刷新支付信息(错误码:002)");
+//// resXml = AppConstance.RESULT_XML_FAIL.replace(ERRORMSG, "微信标识业务是失败");
+// }
+// } else {
+// log.info("通信标识失败");
+// threadResult.fail().message("查询支付信息失败,请联系客服或者刷新支付信息(错误码:003)");
+//// resXml = AppConstance.RESULT_XML_FAIL.replace(ERRORMSG, "通信标识失败");
+// }
+// } catch (Exception e) {
+// log.error("支付回调签名错误", e);
+// threadResult.fail().message("查询支付信息失败,请联系客服或者刷新支付信息(错误码:004)");
+//// resXml = AppConstance.RESULT_XML_FAIL.replace(ERRORMSG, "支付回调签名错误");
+// } finally {
+// // 通知线程消息
+//// PayThreadPool.notifyThread(Integer.valueOf(orderId), threadResult);
+// sendResultBack(response, resXml);
+// }
+// return;
+//
+// }
+//
+//// public static void main(String[] args) {
+//// String attach="{'rechargeNo':CZ_2022083117160259880,'memberId':47}";
+//// JSONObject jsonObject = JSONUtil.parseObj(attach);
+//// String rechargeNo = (String) jsonObject.get("rechargeNo");
+//// Long memberId = Long.parseLong(jsonObject.get("memberId").toString());
+//// System.out.println(memberId);
+//// System.out.println(rechargeNo);
+//// }
+// /**
+// * 微信电子发票回调接口
+// * POST方式回调
+// * @return
+// */
+// @Transactional(rollbackFor = Exception.class)
+// @RequestMapping(value = "/fapiaoCallBack")
+// public Map<String, Object> fapiaoCallBack(HttpServletRequest request, @RequestBody Map<String, Object> requestBody) {
+// return wxFaPiaoService.fapiaoCallBack(request,requestBody);
+//
+// }
+//
+// /**
+// * 微信支付回调接口
+// */
+// @Transactional(rollbackFor = Exception.class)
+// @RequestMapping(value = "/wxpayCallback")
+// public void payCallBack(HttpServletResponse response, HttpServletRequest request) throws IOException {
+// log.info("微信支付回调start....");
+//
+// // 获取输入参数
+// String inputLine;
+// StringBuffer notityXml = new StringBuffer();
+// String resXml = "";
+// String orderId = "";
+//
+// FebsResponse threadResult = new FebsResponse();
+// try {
+// while ((inputLine = request.getReader().readLine()) != null) {
+// notityXml.append(inputLine);
+// }
+// request.getReader().close();
+// log.info("notityXml ---- :{} ", notityXml);
+//
+//
+// // XMl转对象
+// Object bb = Util.getObjectFromXML(notityXml.toString(), NotifyData.class);
+// NotifyData data = new NotifyData();
+// BeanUtils.copyProperties(bb,data);
+// log.info("----return_code = {}", data.getReturn_code());
+//
+// // 返回状态码 SUCCESS/FAIL
+// if (WechatConfigure.CODE_SUCCESS.equals(data.getReturn_code())) {
+//
+// orderId = data.getAttach();
+// // 检验订单状态
+// MallOrderInfo order = mallOrderInfoMapper.selectById(Long.valueOf(orderId));
+//
+// // 校验签名
+//// String paySecret = WechatConfigure.WECHARPAY_SECRET;
+// String paySecret = xcxProperties.getWecharpaySecret();
+// if (Signature.checkIsSignValidFromResponseString(notityXml.toString(),paySecret)) {
+// // 校验业务结果
+// if (WechatConfigure.CODE_SUCCESS.equals(data.getResult_code())) {
+// // 返回SUCCESS报文
+// resXml = WechatConfigure.RESULT_XML_SUCCESS;
+// // 支付费用
+// Double total_fee = Double.parseDouble(data.getTotal_fee());
+// //微信支付订单号
+// String transaction_id = data.getTransaction_id();
+// // 商户订单号
+// String payNum = data.getOut_trade_no();
+//
+// log.info("支付回调关键信息---total_fee:{},payNum:{},orderId:{}", total_fee, payNum, orderId);
+// // 订单ID
+// BigDecimal payMoney = new BigDecimal(total_fee).divide(new BigDecimal(100), 2,
+// RoundingMode.HALF_UP);
+//
+//
+// if (order != null && OrderStatusEnum.WAIT_PAY.getValue() == order.getStatus()) {
+// log.debug("检查支付金额payMoney={},order.getPayMoney()={}", payMoney, order.getAmount());
+// order.setStatus(OrderStatusEnum.WAIT_SHIPPING.getValue());
+// order.setPayResult("1");
+// order.setPayTime(new Date());
+// order.setDeliveryState(OrderDeliveryStateEnum.DELIVERY_WAIT.getValue());
+// order.setPayOrderNo(transaction_id);
+// mallOrderInfoMapper.updateById(order);
+//
+// agentProducer.sendOrderCoupon(order.getId());
+//// agentProducer.sendGetScoreMsg(order.getId());
+//
+// mallMoneyFlowService.addMoneyFlow(
+// order.getMemberId(),
+// order.getAmount().negate(),
+// MoneyFlowTypeEnum.WECHAT_PAY.getValue(),
+// order.getOrderNo(),
+// FlowTypeEnum.WECHAT.getValue(),
+// "微信支付",
+// 2);
+// threadResult.success().message("支付成功");
+// } else {
+// log.info("订单状态不为待付款,order status=", order.getStatus());
+// }
+// } else {
+// log.info("微信标识业务是失败");
+// threadResult.fail().message("查询支付信息失败,请联系客服或者刷新支付信息(错误码:001)");
+//// resXml = AppConstance.RESULT_XML_FAIL.replace(ERRORMSG, "微信标识业务是失败");
+// }
+// } else {
+// log.info("无效签名");
+// threadResult.fail().message("查询支付信息失败,请联系客服或者刷新支付信息(错误码:002)");
+//// resXml = AppConstance.RESULT_XML_FAIL.replace(ERRORMSG, "微信标识业务是失败");
+// }
+// } else {
+// log.info("通信标识失败");
+// threadResult.fail().message("查询支付信息失败,请联系客服或者刷新支付信息(错误码:003)");
+//// resXml = AppConstance.RESULT_XML_FAIL.replace(ERRORMSG, "通信标识失败");
+// }
+// } catch (Exception e) {
+// log.error("支付回调签名错误", e);
+// threadResult.fail().message("查询支付信息失败,请联系客服或者刷新支付信息(错误码:004)");
+//// resXml = AppConstance.RESULT_XML_FAIL.replace(ERRORMSG, "支付回调签名错误");
+// } finally {
+// // 通知线程消息
+//// PayThreadPool.notifyThread(Integer.valueOf(orderId), threadResult);
+// sendResultBack(response, resXml);
+// }
+// return;
+//
+// }
+//
+// private void sendResultBack(HttpServletResponse response, String resXml) throws IOException {
+// log.info("返回微信数据={}", resXml);
+// ServletOutputStream out = response.getOutputStream();
+// out.write(resXml.getBytes());
+// out.flush();
+// out.close();
+// }
+//
+//}
--
Gitblit v1.9.1