src/main/java/cc/mrbird/febs/common/configure/WebMvcConfigure.java
@@ -31,5 +31,6 @@ registration.excludePathPatterns("/api/leader/noLoginLeaderTitle"); registration.excludePathPatterns("/api/xcxPay/wxpayCallback"); registration.excludePathPatterns("/api/xcxPay/rechargeCallBack"); registration.excludePathPatterns("/api/wechat/reply/event"); } } src/main/java/cc/mrbird/febs/common/properties/XcxProperties.java
@@ -14,6 +14,8 @@ private String xcxAppid; private String xcxSecret; //微信 private String wechatToken; private String wecharPaynotifyUrl; src/main/java/cc/mrbird/febs/common/runner/FebsStartedUpRunner.java
@@ -63,7 +63,7 @@ log.info("/ /` / / \\ | |\\/| | |_) | | | |_ | | | |_ "); log.info("\\_\\_, \\_\\_/ |_| | |_| |_|__ |_|__ |_| |_|__ "); log.info(" "); log.info("钱阿姨 权限系统启动完毕,地址:{}", url); log.info("药王谷铺子 权限系统启动完毕,地址:{}", url); boolean auto = febsProperties.isAutoOpenBrowser(); if (auto && StringUtils.equalsIgnoreCase(active, FebsConstant.DEVELOP)) { src/main/java/cc/mrbird/febs/common/utils/CheckoutUtil.java
New file @@ -0,0 +1,85 @@ package cc.mrbird.febs.common.utils; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; public class CheckoutUtil { // 与接口配置信息中的Token要一致 private static String token = "ywgpzfwh"; /** * 验证签名 * * @param signature * @param timestamp * @param nonce * @return */ public static boolean checkSignature(String signature, String timestamp, String nonce) { String[] arr = new String[] { token, timestamp, nonce }; // 将token、timestamp、nonce三个参数进行字典序排序 // Arrays.sort(arr); sort(arr); StringBuilder content = new StringBuilder(); for (int i = 0; i < arr.length; i++) { content.append(arr[i]); } MessageDigest md = null; String tmpStr = null; try { md = MessageDigest.getInstance("SHA-1"); // 将三个参数字符串拼接成一个字符串进行sha1加密 byte[] digest = md.digest(content.toString().getBytes()); tmpStr = byteToStr(digest); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } content = null; // 将sha1加密后的字符串可与signature对比,标识该请求来源于微信 return tmpStr != null ? tmpStr.equals(signature.toUpperCase()) : false; } /** * 将字节数组转换为十六进制字符串 * * @param byteArray * @return */ private static String byteToStr(byte[] byteArray) { String strDigest = ""; for (int i = 0; i < byteArray.length; i++) { strDigest += byteToHexStr(byteArray[i]); } return strDigest; } /** * 将字节转换为十六进制字符串 * * @param mByte * @return */ private static String byteToHexStr(byte mByte) { char[] Digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; char[] tempArr = new char[2]; tempArr[0] = Digit[(mByte >>> 4) & 0X0F]; tempArr[1] = Digit[mByte & 0X0F]; String s = new String(tempArr); return s; } public static void sort(String a[]) { for (int i = 0; i < a.length - 1; i++) { for (int j = i + 1; j < a.length; j++) { if (a[j].compareTo(a[i]) < 0) { String temp = a[i]; a[i] = a[j]; a[j] = temp; } } } } } src/main/java/cc/mrbird/febs/mall/entity/MallGoods.java
@@ -70,7 +70,7 @@ private Integer isSku; /** * 是否普通商品 1-普通商品 2-套餐 * 是否普通商品 1-普通商品 2-积分商品 3-双卷商品 */ private Integer isNormal; src/main/java/cc/mrbird/febs/pay/controller/AutoReplyController.java
New file @@ -0,0 +1,73 @@ package cc.mrbird.febs.pay.controller; import cc.mrbird.febs.common.properties.XcxProperties; import cc.mrbird.febs.common.utils.SpringContextHolder; import cc.mrbird.febs.pay.util.WechatEventUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; /** * 微信事件通知 - 控制层 */ @Slf4j @RestController @RequestMapping(value = "/api/wechat/reply") public class AutoReplyController { private final XcxProperties xcxProperties = SpringContextHolder.getBean(XcxProperties.class); // @Value("${wechat.appId}") // private String wechatAppId; // // @Value("${wechat.appSecret}") // private String wechatAppSecret; // // // 这个Token就是微信后台 服务器配置 中的Token // @Value("${wechat.token}") // private String wechatToken; /** * 服务器校验 * @param req 请求 * @return 校验结果 */ @GetMapping("/event") public String verification(HttpServletRequest req) { return WechatEventUtil.verification(req, xcxProperties.getWechatToken()); } /** * 消息事件 * @param req 请求 * @return 结果 */ @PostMapping("/event") public void messageEvent(HttpServletRequest req, HttpServletResponse resp) throws IOException { req.setCharacterEncoding("UTF-8"); resp.setCharacterEncoding("UTF-8"); String message = "success"; PrintWriter out = resp.getWriter(); try { String messageEvent = WechatEventUtil.messageEvent(req); if(messageEvent != null) { message = messageEvent; } } catch (Exception e) { e.printStackTrace(); } finally { log.info("消息事件:"+message); out.write(message); if (out != null) { out.close(); } } } } src/main/java/cc/mrbird/febs/pay/util/WechatConfigure.java
@@ -6,7 +6,7 @@ public static final String SEND_INFO_URL = "https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token="; public static final String WX_ACCESS_TOKEN_REDIS_KEY = "access_token"; public static final String WX_ACCESS_TOKEN_REDIS_KEY = "ywgpz_access_token"; public static final String BAIDU_ACCESS_TOKEN_REDIS_KEY = "bd_access_token"; public static final String UNIFIEDORDER = "https://api.mch.weixin.qq.com/pay/unifiedorder"; src/main/java/cc/mrbird/febs/pay/util/WechatEventUtil.java
New file @@ -0,0 +1,187 @@ package cc.mrbird.febs.pay.util; import org.dom4j.io.SAXReader; import javax.servlet.http.HttpServletRequest; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.HashMap; import org.dom4j.Document; import org.dom4j.Element; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.InputStream; import java.util.*; /** * <pre> * 微信消息工具类:事件通知、自动回复、 * </pre> * * @author shenshao */ public class WechatEventUtil { private static Logger log = LoggerFactory.getLogger(WechatEventUtil.class); /** * 公众号《事件通知》服务器校验 * * @param req 请求 * @return 校验结果 */ public static String verification(HttpServletRequest req, String wechatToken) { // 接收微信服务器发送请求时传递过来的参数 String signature = req.getParameter("signature"); String timestamp = req.getParameter("timestamp"); String nonce = req.getParameter("nonce"); // 将token、timestamp、nonce三个参数进行字典序排序,并拼接为一个字符串 String sortStr = sort(wechatToken, timestamp, nonce); // 字符串进行shal加密 String mySignature = shal(sortStr); // 校验微信服务器传递过来的签名 和 加密后的字符串是否一致, 若一致则签名通过 if (!"".equals(signature) && !"".equals(mySignature) && signature.equals(mySignature)) { log.info("-----签名校验通过-----"); return req.getParameter("echostr"); } else { log.error("-----校验签名失败-----"); return ""; } } /** * 消息事件监控:关注、取消关注等事件 * @param req * @return */ public static String messageEvent(HttpServletRequest req) { String result = null; Map<String, String> map = xmlToMap(req); String fromUserName = map.get("FromUserName"); String toUserName = map.get("ToUserName"); String msgType = map.get("MsgType"); String content = map.get("Content"); String eventType = map.get("Event"); log.info("事件处理:event:{}、msgType:{}、toUserName:{}、fromUserName:{}", eventType, msgType, toUserName, fromUserName); if ("event".equals(msgType)) { if ("subscribe".equals(eventType)) { result = imgTextMsg(toUserName, fromUserName, "新用户注册", "仅限上海地区手机号用户", "图片地址", "点击后跳转的路径"); log.info("新增关注事件:toUserName{}、fromUserName{}", toUserName, fromUserName); } else if ("unsubscribe".equals(eventType)) { log.info("取消关注事件:toUserName{}、fromUserName{}", toUserName, fromUserName); } } return result; } /** * 组装图文消息(懒得封装对象转XML方法,直接字符串拼装,大伙勿喷) * @param toUserName 开发者微信号 * @param fromUserName 接收人openId * @param title 图文消息上的备注 * @param description 图文消息上的备注 * @param picUrl 图文消息上的图片 * @param url 点击图文消息跳转的路径 * @return */ public static String imgTextMsg(String toUserName, String fromUserName, String title, String description, String picUrl, String url){ return "<xml>" + "<ToUserName><![CDATA["+fromUserName+"]]></ToUserName>" + "<FromUserName><![CDATA["+toUserName+"]]></FromUserName>" + "<CreateTime>"+new Date().getTime()+"</CreateTime>" + "<MsgType><![CDATA[news]]></MsgType>" + "<ArticleCount>1</ArticleCount>" + "<Articles>" + "<item>" + "<Title><![CDATA["+title+"]]></Title>" + "<Description><![CDATA["+description+"]]></Description>" + "<PicUrl><![CDATA["+picUrl+"]]></PicUrl>" + "<Url><![CDATA["+url+"]]></Url>" + "</item>" + "</Articles>" + "</xml>"; } /** * 解析微信发来的请求(XML) * * @param request 请求 * @return map * @throws Exception 异常 */ public static Map<String, String> xmlToMap(HttpServletRequest request) { // 将解析结果存储在HashMap中 Map<String, String> map = new HashMap<>(16); // 从request中取得输入流 try (InputStream inputStream = request.getInputStream()) { System.out.println("获取输入流"); // 读取输入流 SAXReader reader = new SAXReader(); Document document = reader.read(inputStream); // 得到xml根元素 Element root = document.getRootElement(); // 得到根元素的所有子节点 List<Element> elementList = root.elements(); // 遍历所有子节点 for (Element e : elementList) { System.out.println(e.getName() + " | " + e.getText()); map.put(e.getName(), e.getText()); } } catch (Exception e) { e.printStackTrace(); } return map; } /** * 参数排序 * * @param token 服务器配置,自定义的Token * @param timestamp timestamp * @param nonce nonce * @return 排序后拼接字符串 */ public static String sort(String token, String timestamp, String nonce) { String[] strArray = {token, timestamp, nonce}; Arrays.sort(strArray); StringBuilder sb = new StringBuilder(); for (String str : strArray) { sb.append(str); } return sb.toString(); } /** * 字符串进行shal加密 * * @param str 字符串 * @return 密文 */ public static String shal(String str) { try { MessageDigest digest = MessageDigest.getInstance("SHA-1"); digest.update(str.getBytes()); byte messageDigest[] = digest.digest(); StringBuilder hexString = new StringBuilder(); // 字节数组转换为 十六进制 数 for (int i = 0; i < messageDigest.length; i++) { String shaHex = Integer.toHexString(messageDigest[i] & 0xFF); if (shaHex.length() < 2) { hexString.append(0); } hexString.append(shaHex); } return hexString.toString(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return ""; } } src/main/resources/application-dev.yml
@@ -15,14 +15,14 @@ datasource: # 数据源-1,名称为 base base: # username: ct_test # password: 123456 # driver-class-name: com.mysql.cj.jdbc.Driver # url: jdbc:mysql://120.27.238.55:3306/db_pingtuan?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2b8 username: db_mall_qianayi password: db_mall_qianayi123!@#123 username: ct_test password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://121.37.162.173:3306/db_mall_qianayi?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2b8 url: jdbc:mysql://120.27.238.55:3406/db_yaowanggu?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2b8 # username: db_mall_qianayi # password: db_mall_qianayi123!@#123 # driver-class-name: com.mysql.cj.jdbc.Driver # url: jdbc:mysql://121.37.162.173:3306/db_mall_qianayi?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2b8 # username: db_mall # password: mall!@#123 # driver-class-name: com.mysql.cj.jdbc.Driver @@ -30,13 +30,13 @@ redis: # Redis数据库索引(默认为 0) database: 8 database: 11 # Redis服务器地址 host: 121.37.162.173 host: 120.27.238.55 # Redis服务器连接端口 port: 6379 port: 6479 # Redis 密码 password: d32ncxe@i3#!dV password: d3y6dsdl;f.327 lettuce: pool: # 连接池中的最小空闲连接 @@ -50,10 +50,10 @@ # 连接超时时间(毫秒) timeout: 5000 rabbitmq: host: 121.37.162.173 host: 120.27.238.55 port: 5672 username: hibit password: hibit123 username: ct_rabbit password: 123456 publisher-confirm-type: correlated pay: @@ -69,16 +69,17 @@ xcx: wechar_login_url: https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code xcx_appid: wxfb1acf504eb9d058 xcx_secret: 7ee226a964b1be7cfb96f5e0dc739c44 xcx_appid: wx052716b1a388b13c xcx_secret: e7cf355e7234f035d8fea6a3a19513b9 debug: false wecharPaynotifyUrl: http://qianayi.csxuncong.com/api/xcxPay/wxpayCallback wecharRechargePaynotifyUrl: http://qianayi.csxuncong.com/api/xcxPay/rechargeCallBack wecharPaynotifyUrl: http://ywgpz.csxuncong.com/api/xcxPay/wxpayCallback wecharRechargePaynotifyUrl: http://ywgpz.csxuncong.com/api/xcxPay/rechargeCallBack certLocalPath: /home/qianayiCert/apiclient_cert.p12 wecharpayMchid: 1638095860 wecharpaySecret: QIANAYIZONGZI1234567899876543212 gaodeKey: 95ede7157929f5f6b6c758971be924b1 serviceName: yiyuanshucai wechatToken:ywgpzfwh system: job: true src/main/resources/application.yml
@@ -1,5 +1,5 @@ server: port: 8085 port: 8184 tomcat: uri-encoding: utf-8 src/main/resources/templates/error/403.html
@@ -2,7 +2,7 @@ <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="utf-8"> <title>钱阿姨 权限系统</title> <title>药王谷铺子 权限系统</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <meta name="renderer" content="webkit"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> src/main/resources/templates/error/404.html
@@ -2,7 +2,7 @@ <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="utf-8"> <title>钱阿姨 权限系统</title> <title>药王谷铺子 权限系统</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <meta name="renderer" content="webkit"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> src/main/resources/templates/error/500.html
@@ -2,7 +2,7 @@ <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="utf-8"> <title>钱阿姨 权限系统</title> <title>药王谷铺子 权限系统</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <meta name="renderer" content="webkit"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> src/main/resources/templates/febs/views/layout.html
@@ -64,7 +64,7 @@ <div class="layui-side-scroll"> <div class="layui-logo" style="cursor: pointer"> <img data-th-src="@{febs/images/logo.png}"> <span>钱阿姨 权限系统</span> <span>药王谷铺子 权限系统</span> </div> <script type="text/html" src/main/resources/templates/febs/views/login.html
@@ -2,7 +2,7 @@ <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="utf-8"> <title>钱阿姨 权限系统</title> <title>药王谷铺子 权限系统</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <meta name="renderer" content="webkit"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> @@ -19,7 +19,7 @@ <div class="layui-container"> <div class="layui-row"> <div class="layui-col-xs12 layui-col-lg4 layui-col-lg-offset4 febs-tc"> <div class="layui-logo"><span><b>钱阿姨</b> 权限系统</span></div> <div class="layui-logo"><span><b>药王谷铺子</b> 权限系统</span></div> </div> <div class="layui-col-xs12 layui-col-lg4 layui-col-lg-offset4" id="login-div"> <div class="layui-form" lay-filter="login-form"> src/main/resources/templates/febs/views/modules/goods/goodsAddNew.html
@@ -97,8 +97,9 @@ <label class="layui-form-label febs-form-item-require">商品类型</label> <div class="layui-input-block"> <select name="isNormal" class="goods-type" lay-filter="goods-type-select"> <option value="1">普通商品区</option> <!-- <option value="2">套餐区</option>--> <option value="1">普通商品</option> <option value="2">积分商品</option> <option value="2">双卷商品</option> </select> </div> </div> src/main/resources/templates/febs/views/modules/goods/goodsUpdateNew.html
@@ -106,30 +106,31 @@ <div class="layui-input-block"> <select name="isNormal" class="goods-type" lay-filter="goods-type-select"> <option value="1">普通商品区</option> <!-- <option value="2">套餐区</option>--> <option value="1">普通商品</option> <option value="2">积分商品</option> <option value="2">双卷商品</option> </select> </div> </div> <div class="layui-form-item febs-hide tc-set"> <label class="layui-form-label">静态倍数</label> <div class="layui-input-block"> <input type="text" name="staticMulti" placeholder="请输入静态倍数" autoComplete="off" class="layui-input"> <div class="layui-form-mid layui-word-aux">支付后,赠送(购买金额*静态倍数)的赠送积分 </div> </div> </div> <!-- <div class="layui-form-item febs-hide tc-set">--> <!-- <label class="layui-form-label">静态倍数</label>--> <!-- <div class="layui-input-block">--> <!-- <input type="text" name="staticMulti" placeholder="请输入静态倍数"--> <!-- autoComplete="off" class="layui-input">--> <!-- <div class="layui-form-mid layui-word-aux">支付后,赠送(购买金额*静态倍数)的赠送积分--> <!-- </div>--> <!-- </div>--> <!-- </div>--> <div class="layui-form-item febs-hide tc-set"> <label class="layui-form-label">静态占比(%)</label> <div class="layui-input-block"> <input type="text" name="staticProp" placeholder="请输入静态占比" autoComplete="off" class="layui-input"> <div class="layui-form-mid layui-word-aux">所有套餐静态占比相加应等于100%</div> </div> </div> <!-- <div class="layui-form-item febs-hide tc-set">--> <!-- <label class="layui-form-label">静态占比(%)</label>--> <!-- <div class="layui-input-block">--> <!-- <input type="text" name="staticProp" placeholder="请输入静态占比"--> <!-- autoComplete="off" class="layui-input">--> <!-- <div class="layui-form-mid layui-word-aux">所有套餐静态占比相加应等于100%</div>--> <!-- </div>--> <!-- </div>--> </div> <div class="layui-tab-item"> src/main/resources/templates/index.html
@@ -3,7 +3,7 @@ xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"> <head> <meta charset="utf-8"> <title>钱阿姨 权限系统</title> <title>药王谷铺子 权限系统</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <meta name="renderer" content="webkit"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">