diff --git a/pom.xml b/pom.xml
index ecfefb32..91efa6fa 100644
--- a/pom.xml
+++ b/pom.xml
@@ -25,6 +25,30 @@
UTF-8
+
+
+ com.github.javen205
+ IJPay-All
+ 2.9.10
+
+
+
+ com.alibaba
+ dashscope-sdk-java
+ 2.14.4
+
+
+ org.slf4j
+ slf4j-simple
+
+
+
+
+ com.alipay.sdk
+ alipay-sdk-java
+ 4.35.37.ALL
+
+
org.apache.axis
axis
diff --git a/src/main/java/com/budwk/app/base/constant/BpmProcessConstant.java b/src/main/java/com/budwk/app/base/constant/BpmProcessConstant.java
index b97b0b75..c023259d 100644
--- a/src/main/java/com/budwk/app/base/constant/BpmProcessConstant.java
+++ b/src/main/java/com/budwk/app/base/constant/BpmProcessConstant.java
@@ -25,6 +25,7 @@ public enum BpmProcessConstant {
GRASSROOTS_CONGRESS_MATERIALS("基层双代会会议资料"),
MEMBER_SINGLE_APPLY("会员入会申请"),
+ MEMBER_CHANGE("会员入会申请"),
MEMBER_BRANCH_UNION_CHANGE("会员分工会变更"),
diff --git a/src/main/java/com/budwk/app/base/constant/RedisConstant.java b/src/main/java/com/budwk/app/base/constant/RedisConstant.java
index e0d67dc7..959e6f7e 100644
--- a/src/main/java/com/budwk/app/base/constant/RedisConstant.java
+++ b/src/main/java/com/budwk/app/base/constant/RedisConstant.java
@@ -20,6 +20,9 @@ public class RedisConstant {
//企业微信token
public final static String REDIS_KEY_WX_WORK_TOKEN = PLATFORM_REDIS_PREFIX + "wxwork:token:";
+ //健步走小程序TOKEN
+ public final static String REDIS_KEY_WE_APP_ACCESS_TOKEN = PLATFORM_REDIS_PREFIX + "weapp:token:";
+
//图形验证码
public final static String REDIS_CAPTCHA_KEY = PLATFORM_REDIS_PREFIX + "platfrom:captcha:";
diff --git a/src/main/java/com/budwk/app/base/payment/alipay/controller/AlipayClientController.java b/src/main/java/com/budwk/app/base/payment/alipay/controller/AlipayClientController.java
new file mode 100644
index 00000000..acae258d
--- /dev/null
+++ b/src/main/java/com/budwk/app/base/payment/alipay/controller/AlipayClientController.java
@@ -0,0 +1,68 @@
+package com.budwk.app.base.payment.alipay.controller;
+
+import com.budwk.app.base.payment.alipay.service.AlipayService;
+import lombok.extern.slf4j.Slf4j;
+import org.nutz.ioc.loader.annotation.Inject;
+import org.nutz.ioc.loader.annotation.IocBean;
+import org.nutz.mvc.annotation.At;
+import org.nutz.mvc.annotation.Ok;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * @ClassName AlipayClientController
+ * @Description TODO
+ * @Author zhf
+ * @Date 2024/1/25 15:36
+ */
+@IocBean
+@Slf4j
+@Ok("json:full")
+@At("/mobile/memberCost/AlipayClient")
+public class AlipayClientController {
+
+
+ @Inject
+ private AlipayService alipayService;
+
+
+ @At("/tradePagePay")
+ public String tradePagePay(String money, String paymentProjectId) throws Exception {
+ log.info("统一收单下单并支付接口调用");
+ String formStr = alipayService.tradeCreate(money, paymentProjectId);
+ return formStr;
+ }
+
+ @At("/tradeNotify")
+ @Ok("re")
+ public String tradeNotify(HttpServletRequest request) {
+ try {
+ Map params = new HashMap();
+ Map requestParams = request.getParameterMap();
+ for (Iterator iter = requestParams.keySet().iterator(); iter.hasNext(); ) {
+ String name = (String) iter.next();
+ String[] values = (String[]) requestParams.get(name);
+ String valueStr = "";
+ for (int i = 0; i < values.length; i++) {
+ valueStr = (i == values.length - 1) ? valueStr + values[i]
+ : valueStr + values[i] + ",";
+ }
+ //乱码解决,这段代码在出现乱码时使用
+
+ valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
+
+ params.put(name, valueStr);
+ }
+ log.info("支付通知,正在执行,通知参数:{}");
+ return alipayService.tradeNotify(params);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+
+ }
+
+}
diff --git a/src/main/java/com/budwk/app/base/payment/alipay/service/AlipayClientConfig.java b/src/main/java/com/budwk/app/base/payment/alipay/service/AlipayClientConfig.java
new file mode 100644
index 00000000..0508909e
--- /dev/null
+++ b/src/main/java/com/budwk/app/base/payment/alipay/service/AlipayClientConfig.java
@@ -0,0 +1,22 @@
+package com.budwk.app.base.payment.alipay.service;
+
+/**
+ * @ClassName AlipayClientConfig
+ * @Description TODO
+ * @Author zhf
+ * @Date 2024/1/25 15:33
+ */
+public interface AlipayClientConfig {
+ String serverUrl = "https://openapi.alipay.com/gateway.do";
+ String appId = "2021004133670087";
+ String sellerId = "2088002638762620";
+ String privateKey = "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCEJBWNlUIY/2Tave1OtjTgvA21GZ5tdvZZD3jFmixIFTkEdZaRUpXCvlKuZ9heoL1IQwpbuOErJ4dM2f2Ju/Oc7QtUOjEOX6CnElR73KK5OSY8d8f5822LUOgp2zg5FHI6C7olYmjN4Nw26G6BGyqQBmWs4/sKzghKelEaH9AqkR8DZ+yOvuYtIg5KEvVixsM27TMhd6/3BuNXDIiQDVH9ZeQVb9IKuynZ6hnUJS0T4MkS41qehS1u8LgplGU5yvrNuUNeQtybADsiGHDNmNkl5lVt0QNh+N/lo56//duf5PJwh8ApDB22eXptiS6p321PSrYowy5VeFpHC07Tref9AgMBAAECggEAPQddXPKegGUT82LcgwnsCg9QVV/rp6rVw/Q9VyYJZZFHsCOO0rdNq4+zKBGWAL5ELWepFQuiEwhCmN/lFQPVU5i3LsNhfL31uLPiiQIxtVILssmvdmVTJpDLaOlyYgprZxubcUCuHsfHbSSURNSDJ9mMg/kXLLpTYfJ0Gpiuz7lkrMuAxncdz88Sp2r0O9k2T+TcnHFpRXHs+C/QAaa15EF18ieDSBLPkY0ti1R8J91LGAzDrjwcP7pNlq4u7TasZ7djv47ZFwQ3yjWoo13d9ab0MBYeQx9Grf5N+JGNf33YLyulQ0IshcuwNyB+RYhyWGjNmjnJdytzKRMOYSpPgQKBgQC6iqMPX+LGunyI+3T9l/AwAVWRJXzEHPflNxDYfQbicB5awlRixxH5T1qyZ46KZb/pBt01qDULXy0bEObVFSjAn1wXPzC9Fwb+BtVZUroYBTKV3tOAMpVCz2AAC34OZwG/3X//sRtW9Sr0Dr+yCcx8ULMiFS1BuCZfhSNXA516NQKBgQC1V+rge1pYKGnjtWqML4qJy4FEIDI4fSpGIizxumYDMxdXoUdNKoG2NHQkznHU4fIHJPVvIN/Tm82mfj2rhH9eFuu1FB8TL2soe5nPgzSJ9AmP7rZ7KBo3/SczvZqUEpii8+PjHu+Gx95BmgTzOFMnYz8xzitb4IBGTEGxVzavqQKBgCL28XFkS6FsOwOB/n+6Bu84kM1P9X7c3yo7lBC//pZa5ZWlbyPEpH3SffGKr5EJWVtYSgZQrQCgqle0Z0/xU14ngJ9q3LvUoR7kMQE5HGB8OOsWhRY2ZPvM2vWnMc2JSS6pWeCXVcSK164/RXcdlxKGHc15oq360lZ9Ig6hQtcxAoGAIYijd2V4HGtEwYXvsQKtlLOcMNPfVZDXu2Zgd0bnzFmrTFoXjE569A2ZGKFQbXRmxClydV1X5pKeIdagYtI6l1SXu8F/yZVwNGk+1R/HHGeTSRl0hyuwRYA7upaqOg2CSkfdcUdsNblKAUmQ9sUIo3T336bnX7MRJW6+iyO8ihkCgYBG8vG1fVUwPmqlRK+QanHxicR1+UyjRAN+aW6dgJUNMlC+tmPeHj21+WGkDdKPdhinHziLihLmzbmpwWSDwuGMMQPNQm8gSjiaqy1tWcP6XKDxjpG5biTzwEyHNuCIY6odS4HTeDOrrU4LvN6PxirN8xKBIpq+A2cgpGo+Mn4x+g==";
+ String alipayPublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjO/QUy+ujNBlToL6Kt8YjvTRGpEwmXjqc9pFe7GIORVkzBXP3F/UC/w5mPiwXhhyymXAe7bUwLANL/QMUZhvpjjCdLENtFWC9bd6hq3m2oHiTu0oicbzqCuaoQkzik2NybrNjuH+aYVi4RU0eNYpdewQoHHW+hbfxkSYUWmx7NF4SuG93edYlpc/G/ImdrweATLE0b6W9il4H/BxVUweCraPrispiOM8r6TN3fiIKUQRP15HKQo8Xsr4pMJzfi37I9aN8AThN/xBf6sual8IZZsH5hC3PS37LrYkXbUjt0dK5tqKl3SsZPjvvHGyz9b9JC225+0q09Y6B8/axOlihwIDAQAB";
+
+ /* String serverUrl = "https://openapi-sandbox.dl.alipaydev.com/gateway.do";
+ String appId = "9021000134641646";
+ String privateKey = "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCBFTF8qFbK+NiqUbKf1/jWHL/s0hfUXK6cyKwZCEXkEMZH1Lu6kZQl3kQye9Fsasawxn1vLmKz+OKjMiE+TdTytHDu5ULdLlSHN3bY1qf91i8L+RJfmZpb1p+NQOk9zImhhSauuk+itMkTC3bTiwwAkg/utOxkNB7YaynpAb3evhXnlX3LUVTHaMnCQFeV7ithh7CDKx0LPlxOpOvxnkWDBR3nsdcfjDqYNDVHXnUxEk0KQV4Ik6fA40+Gq/HXTByJolCSAaTk0OaH2ULp1RZAklWLqhiNEhytTZAlJUx8cSm36mm0c4jhgSXNdRz0Yi1BlPY9yxQGStpH5ue3+EfvAgMBAAECggEAda/D4Zp9HysIJyi7SZ6XwWaO2TSnAzfI/yq6NUxkpE1i6cSbr7rgrjHaTYiz5DvpkOw3GnHZDarhvtaKlopRl0JKIJSnhI6kBG/eDE6Q9Q4c8LaSONULdaXU6+z9ehmMddW28eDtkDNxi8qM+MuyEqcYnZMYXzdh1ADIQicTXArxo+9fpvlKUMNxOTO5LoOf0DtJg/xE1CD9PtWVc+l2G8S3+C52+RhfZ214YQdxwayMlvJQlWzLTbfzJCp8w4F2up1nJdwp7EY8k8xDUngEnG6BWm8ztWUVTTg7qp/+SC9yMTD2dl86wDkJt1lTtwcKaWxHYh2ZIn8a8+icHFykaQKBgQDgBEWAmuj0pgpny5Q0GRsUYPq42SwpN70u4rmD5UYFCeDHaKb4U3kyvA1V+1Tp0XWzFi7GU855h1n8dnQX64J3Vm4GfhftWwTJgB5ibbY53cZVEoV5nZe0bOO4hxxMdNy3BKjPovkT0REPpCkVas/RRLAFCdnvi6vmDkFxvv9JewKBgQCTgx9NZZCqSzzCJYpLTDOCitooOQmR8kAlgBiDCcHeH2wNVMh5O2Dl/OyTGauKUJFWIOSD8FXVTrNUbOEmx58msnS/+Mc5SnbEka5ptVEDp73gf6ctB+eZAMdO1kLsBT3iKnuyNa7pRyR/D2A692RHOTQZIvRHE1N+kM1r+iBPHQKBgCZRVLocxUAjrp/2eJE4wiMIO5qmuyEJwbhlW6ot634tJF+3jOfpD1AYToeoh+u+wk0xvCp8Z1LuTfwbHtyAkj5rcEJ4Cwu0h+sXmRro/USEpMokcuRMioh4H7OOmM6TJrW/fEiX7dbv6eUtBUH+lTGMeDdEahzJXUvqoFMegnhFAoGAa2eflRUMflIFzT7UMPsszjIXog3VoASJHETmlBK4q+ipHhPLXCoV5Z+BCaI5Op5rFBEjEcYeWQ/Slvgn/7p9cQmi5FxZgqdrTrjcC2/Pqx+pqxqHOMx2nvroI8jyNTOj2VchdaKM1qHMOw+Q+55Bz3IhMuQ2hMHXm2IHkEUw3SkCgYBjcqcWbJcq26L8yYVpU2JFzOvu7iHuV99TpEvF9Ym3cAcSNuoR5kGXzGS0MadYY87cd4HK9HvmXQmthLY14buKYxq27ZFfNbK/Auz8vk0Wbrm1Ym4Z1yMqClQqxKdr4hV29ijn1nPProRqgnVsGH3EtFmNxDfilZoVzh9ERaZe7w==";
+ String alipayPublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArhElUiFMpxqFCs+8zDHa4OVuhdWgKUUGVKgWnfLktPgy9mDZkuGPKk295k8ktWQSUZsnFvT8ikJIKdGYfbSWzlXvCAn6lDDDlsjfOJWMQ6zLmb9xygCih2OjwN5xt9QFIIthnWq8sOXpWvqmoRpIFQHolxxmvvCmUZ/VwK6mwgwU/yVvVypruQ/FGrJA70LAcJ764spWAMy/pWIoIs5UewOlJa3TAcZQIqRnjupL2/sI/TpIWFWF90a/rahwLfBgZ83VH7MA4Tl9uFLc3aeNFLllDF4X7lRdzaHIMYgUUKte9yvlDy5Ihrt+ssjsgdRhqLHvFn00u/g7BCvO5ZmIUwIDAQAB";
+*/
+
+}
diff --git a/src/main/java/com/budwk/app/base/payment/alipay/service/AlipayService.java b/src/main/java/com/budwk/app/base/payment/alipay/service/AlipayService.java
new file mode 100644
index 00000000..fa4486b0
--- /dev/null
+++ b/src/main/java/com/budwk/app/base/payment/alipay/service/AlipayService.java
@@ -0,0 +1,15 @@
+package com.budwk.app.base.payment.alipay.service;
+
+
+import com.budwk.app.base.service.BaseService;
+
+import java.util.Map;
+
+public interface AlipayService extends BaseService {
+ /**
+ * 支付宝开放平台接收 request请求对象后
+ * @return
+ */
+ String tradeCreate( String money, String paymentProjectId) throws Exception;
+ String tradeNotify(Map params) ;
+}
diff --git a/src/main/java/com/budwk/app/base/payment/alipay/service/impl/AlipayNewServiceImpl.java b/src/main/java/com/budwk/app/base/payment/alipay/service/impl/AlipayNewServiceImpl.java
new file mode 100644
index 00000000..553ac948
--- /dev/null
+++ b/src/main/java/com/budwk/app/base/payment/alipay/service/impl/AlipayNewServiceImpl.java
@@ -0,0 +1,140 @@
+package com.budwk.app.base.payment.alipay.service.impl;
+
+import com.alipay.api.AlipayApiException;
+import com.alipay.api.AlipayConstants;
+import com.alipay.api.DefaultAlipayClient;
+import com.alipay.api.domain.AlipayTradeWapPayModel;
+import com.alipay.api.internal.util.AlipaySignature;
+import com.alipay.api.request.AlipayTradeWapPayRequest;
+import com.alipay.api.response.AlipayTradeWapPayResponse;
+import com.budwk.app.base.payment.alipay.service.AlipayClientConfig;
+import com.budwk.app.base.payment.alipay.service.AlipayService;
+import com.budwk.app.base.service.impl.BaseServiceImpl;
+import com.budwk.app.web.commons.auth.utils.SecurityUtil;
+import com.budwk.app.zhgh.staffmanage.memberCost.model.MemberPaymentInfo;
+import lombok.extern.slf4j.Slf4j;
+import org.nutz.dao.Chain;
+import org.nutz.dao.Cnd;
+import org.nutz.dao.Dao;
+import org.nutz.ioc.loader.annotation.IocBean;
+
+import java.util.Date;
+import java.util.Map;
+
+/**
+ * @ClassName AlipayNewServiceImpl
+ * @Description TODO
+ * @Author zhf
+ * @Date 2024/1/25 15:38
+ */
+
+@Slf4j
+@IocBean(args = {"refer:dao"})
+public class AlipayNewServiceImpl extends BaseServiceImpl implements AlipayService {
+ public AlipayNewServiceImpl(Dao dao) {
+ super(dao);
+ }
+
+ DefaultAlipayClient alipayClient = new DefaultAlipayClient(AlipayClientConfig.serverUrl,
+ AlipayClientConfig.appId,
+ AlipayClientConfig.privateKey,
+ AlipayConstants.FORMAT_JSON,
+ AlipayConstants.CHARSET_UTF8,
+ AlipayClientConfig.alipayPublicKey,
+ AlipayConstants.SIGN_TYPE_RSA2);
+
+ @Override
+ public String tradeCreate(String money, String paymentProjectId) throws Exception {
+ String outTradeNo = String.valueOf(new Date().getTime());
+ AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();
+ //支付宝公共参数
+ request.setReturnUrl("http://sansi.ltd/mobile/memberCost/AlipayClient/tradeNotify");
+
+
+ //面向对象封装业务参数
+ AlipayTradeWapPayModel model = new AlipayTradeWapPayModel();
+ model.setOutTradeNo(outTradeNo);
+ model.setTotalAmount(money);
+ model.setSubject("会员入会会费");
+ model.setProductCode("QUICK_WAP_WAY");
+ model.setSellerId(AlipayClientConfig.sellerId);
+ model.setTimeoutExpress("30m");
+ model.setQuitUrl("http://sansi.ltd/mobile/memberCost/list");
+
+ request.setBizModel(model);
+
+ //执行请求,调用支付宝
+ AlipayTradeWapPayResponse response = alipayClient.pageExecute(request, "POST");
+ if (response.isSuccess()) {
+ dao().update(MemberPaymentInfo.class, Chain.make("payId", outTradeNo), Cnd.where("paymentProjectId", "=", paymentProjectId)
+ .and("userId", "=", SecurityUtil.getUserId()));
+ log.info("调用成功,返回结果:[{}]", response.getBody());
+ return response.getBody();
+ } else {
+ log.info("调用失败!!");
+ }
+ return null;
+ }
+
+ @Override
+ public String tradeNotify(Map params) {
+ String total_amount = params.get("total_amount");
+ String out_trade_no = params.get("out_trade_no");
+ String trade_no = params.get("trade_no");
+
+//
+ String result = "failure";
+ try {
+ //异步通知验签
+ boolean signVerified = AlipaySignature.rsaCheckV1(params,
+ AlipayClientConfig.alipayPublicKey,
+ AlipayConstants.CHARSET_UTF8,
+ AlipayConstants.SIGN_TYPE_RSA2);
+ if (!signVerified) {
+ // TODO 验签失败则记录异常日志,并在response中返回failure.
+ log.error("支付成功,异步通知验签失败!");
+ return result;
+ }
+ log.info("支付成功,异步通知验签成功!");
+ //TODO 验签成功后,按照支付结果异步通知中的描述,对支付结果中的业务内容进行二次校验
+ //1.验证out_trade_no 是否为商家系统中创建的订单号
+ String outTradeNo = params.get("out_trade_no");
+ if (!outTradeNo.equals(out_trade_no)) {
+ log.error("订单号不一致");
+ return result;
+ }
+ //2.判断 total_amount 是否确实为该订单的实际金额
+ String totalAmount = params.get("total_amount");
+ if (!totalAmount.equals(total_amount)) {
+ log.error("订单的实际金额不一致");
+ return result;
+ }
+ //3.校验通知中的 seller_id是否为 out_trade_no 这笔单据的对应的操作方
+ String sellerId = params.get("seller_id");
+ if (!sellerId.equals(AlipayClientConfig.sellerId)) {
+ log.error("商家PID校验失败");
+ return result;
+ }
+ //4.验证 app_id 是否为该商家本身
+ String appId = params.get("app_id");
+ if (!appId.equals(AlipayClientConfig.appId)) {
+ log.error("app_id校验失败");
+ return result;
+ }
+
+ dao().update(MemberPaymentInfo.class,
+ Chain.make("isPayment", true)
+ .add("paymentMoney", total_amount)
+ .add("paymentTime", new Date())
+ .add("tradeNo", trade_no),
+ Cnd.where("userId", "=", SecurityUtil.getUserId()).and("payId", "=", out_trade_no));
+
+ //TODO 处理自身业务
+ result = "->:/mobile/memberCost/list/paySuccess";
+ } catch (AlipayApiException e) {
+ e.printStackTrace();
+ }
+
+ return result;
+ }
+}
diff --git a/src/main/java/com/budwk/app/base/payment/enums/RefundStateEnum.java b/src/main/java/com/budwk/app/base/payment/enums/RefundStateEnum.java
new file mode 100644
index 00000000..39b33ca3
--- /dev/null
+++ b/src/main/java/com/budwk/app/base/payment/enums/RefundStateEnum.java
@@ -0,0 +1,24 @@
+package com.budwk.app.base.payment.enums;
+
+import com.budwk.app.base.annotation.DictEnum;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 退款状态枚举
+ */
+@Getter
+@AllArgsConstructor
+@DictEnum(key = "RefundStateEnum", name = "退款状态枚举")
+public enum RefundStateEnum {
+
+ INITIATE_REFUND("发起退款"),
+ REFUNDING("退款中"),
+ REFUND_SUCCESS("退款成功"),
+ REFUND_FAILED("退款失败");
+
+ /**
+ * 避免和枚举类的name冲突
+ */
+ public final String refundStateName;
+}
diff --git a/src/main/java/com/budwk/app/base/payment/enums/TradingChannelEnum.java b/src/main/java/com/budwk/app/base/payment/enums/TradingChannelEnum.java
new file mode 100644
index 00000000..c8e0fd3f
--- /dev/null
+++ b/src/main/java/com/budwk/app/base/payment/enums/TradingChannelEnum.java
@@ -0,0 +1,25 @@
+package com.budwk.app.base.payment.enums;
+
+import com.budwk.app.base.annotation.DictEnum;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 交易渠道枚举
+ */
+@Getter
+@AllArgsConstructor
+@DictEnum(key = "TradingChannelEnum", name = "交易渠道枚举")
+public enum TradingChannelEnum {
+
+ QQ_PAY("QQ"),
+ WX_PAY("微信"),
+ JD_PAY("京东"),
+ ALI_PAY("支付宝"),
+ UNION_PAY("云闪付");
+
+ /**
+ * 避免和枚举类的name冲突
+ */
+ public final String tradingChannelName;
+}
diff --git a/src/main/java/com/budwk/app/base/payment/enums/TradingStateEnum.java b/src/main/java/com/budwk/app/base/payment/enums/TradingStateEnum.java
new file mode 100644
index 00000000..8e2c3a63
--- /dev/null
+++ b/src/main/java/com/budwk/app/base/payment/enums/TradingStateEnum.java
@@ -0,0 +1,24 @@
+package com.budwk.app.base.payment.enums;
+
+import com.budwk.app.base.annotation.DictEnum;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+
+/**
+ * 支付状态枚举
+ */
+@Getter
+@AllArgsConstructor
+@DictEnum(key = "TradingStateEnum", name = "支付状态枚举")
+public enum TradingStateEnum {
+
+ WAITING_FOR_PAYMENT("等待支付"),
+ PAY_SUCCESS("支付成功"),
+ CANCEL_PAYMENT("取消支付");
+
+ /**
+ * 避免和枚举类的name冲突
+ */
+ public final String tradingStateName;
+}
diff --git a/src/main/java/com/budwk/app/base/payment/enums/TradingTypeEnum.java b/src/main/java/com/budwk/app/base/payment/enums/TradingTypeEnum.java
new file mode 100644
index 00000000..b4ba9379
--- /dev/null
+++ b/src/main/java/com/budwk/app/base/payment/enums/TradingTypeEnum.java
@@ -0,0 +1,22 @@
+package com.budwk.app.base.payment.enums;
+
+import com.budwk.app.base.annotation.DictEnum;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 交易类型枚举
+ */
+@Getter
+@AllArgsConstructor
+@DictEnum(key = "TradingTypeEnum", name = "交易类型枚举")
+public enum TradingTypeEnum {
+
+ PAYMENT("付款"),
+ REFUND("退款");
+
+ /**
+ * 避免和枚举类的name冲突
+ */
+ public final String tradingTypeName;
+}
diff --git a/src/main/java/com/budwk/app/base/payment/enums/WxPayResultCode.java b/src/main/java/com/budwk/app/base/payment/enums/WxPayResultCode.java
new file mode 100644
index 00000000..d8912181
--- /dev/null
+++ b/src/main/java/com/budwk/app/base/payment/enums/WxPayResultCode.java
@@ -0,0 +1,30 @@
+package com.budwk.app.base.payment.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @version 1.0
+ * @Author zzr
+ * @name:WxPayResultCode
+ * @Date 2025/3/10 10:27
+ * @注释 微信支付返回码
+ */
+@Getter
+@AllArgsConstructor
+public enum WxPayResultCode {
+
+ SUCCESS(200, "操作成功"),
+ APPID_MCHID_NOT_MATCH(400, "商户ID和服务号或企业微信号不匹配"),
+ INVALID_REQUEST(400, "无效请求"),
+ MCH_NOT_EXISTS(400, "商户号不存在"),
+ PARAM_ERROR(400, "参数错误"),
+ SIGN_ERROR(401, "签名错误"),
+ NO_AUTH(403, "商户无权限"),
+ OUT_TRADE_NO_USED(403, "商户订单号重复"),
+ FREQUENCY_LIMITED(429, "频率超限"),
+ SYSTEM_ERROR(500, "系统错误");
+
+ final int code;
+ final String msg;
+}
diff --git a/src/main/java/com/budwk/app/base/payment/model/PaymentRefundRecord.java b/src/main/java/com/budwk/app/base/payment/model/PaymentRefundRecord.java
new file mode 100644
index 00000000..76bb109a
--- /dev/null
+++ b/src/main/java/com/budwk/app/base/payment/model/PaymentRefundRecord.java
@@ -0,0 +1,61 @@
+package com.budwk.app.base.payment.model;
+
+import com.budwk.app.base.model.BaseModel;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.nutz.dao.entity.annotation.*;
+import org.nutz.dao.interceptor.annotation.PrevInsert;
+
+import java.math.BigDecimal;
+
+/**
+ * 退款记录表
+ */
+@Data
+@Table
+@Comment("退款记录表")
+@EqualsAndHashCode(callSuper = true)
+public class PaymentRefundRecord extends BaseModel {
+
+ @Column
+ @Name
+ @Comment("ID")
+ @ColDefine(type = ColType.VARCHAR, width = 32)
+ @PrevInsert(uu32 = true)
+ private String id;
+
+ @Column
+ @Comment("关联系统交易订单表Id")
+ @ColDefine(type = ColType.VARCHAR,width = 32)
+ private String paymentTransactionId;
+
+ @Column
+ @Comment("本次退款订单号")
+ @ColDefine(type = ColType.VARCHAR,width = 128)
+ private String outRefundTradeNo;
+
+ @Column
+ @Comment("第三方支付的退款单号")
+ @ColDefine(type = ColType.VARCHAR,width = 128)
+ private String refundNo;
+
+ @Column
+ @Comment("退款状态")
+ @ColDefine(type = ColType.VARCHAR,width = 30)
+ private String refundState;
+
+ @Column
+ @Comment("本次退款金额")
+ @ColDefine(type = ColType.INT)
+ private Integer refundAmount;
+
+ @Column
+ @Comment("原订单金额")
+ @ColDefine(type = ColType.INT)
+ private Integer total;
+
+ @Column
+ @Comment("交易返回数据")
+ @ColDefine(type = ColType.MYSQL_JSON)
+ private String resultJson;
+}
diff --git a/src/main/java/com/budwk/app/base/payment/model/PaymentTransaction.java b/src/main/java/com/budwk/app/base/payment/model/PaymentTransaction.java
new file mode 100644
index 00000000..b1961339
--- /dev/null
+++ b/src/main/java/com/budwk/app/base/payment/model/PaymentTransaction.java
@@ -0,0 +1,131 @@
+package com.budwk.app.base.payment.model;
+
+import com.budwk.app.base.model.BaseModel;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.nutz.dao.entity.annotation.*;
+import org.nutz.dao.interceptor.annotation.PrevInsert;
+
+import java.math.BigDecimal;
+
+/**
+ * 交易单表
+ */
+@Data
+@Table
+@Comment("交易订单表")
+@EqualsAndHashCode(callSuper = true)
+public class PaymentTransaction extends BaseModel {
+
+ @Column
+ @Name
+ @Comment("ID")
+ @ColDefine(type = ColType.VARCHAR, width = 32)
+ @PrevInsert(uu32 = true)
+ private String id;
+
+ @Column
+ @Comment("商户号")
+ @ColDefine(type = ColType.VARCHAR,width = 128)
+ private String mchId;
+
+ @Column
+ @Comment("订单号,传递给交易系统,交易系统订单号")
+ @ColDefine(type = ColType.VARCHAR,width = 128)
+ private String outTradeNo;
+
+ @Column
+ @Comment("交易系统交易号")
+ @ColDefine(type = ColType.VARCHAR,width = 128)
+ private String transactionId;
+
+ @Column
+ @Comment("交易渠道")
+ @ColDefine(type = ColType.VARCHAR,width = 50)
+ private String tradingChannel;
+
+ @Column
+ @Comment("交易类型")
+ @ColDefine(type = ColType.VARCHAR,width = 30)
+ private String tradingType;
+
+ @Column
+ @Comment("交易状态")
+ @ColDefine(type = ColType.VARCHAR,width = 50)
+ private String tradingState;
+
+ @Column
+ @Comment("用户ID")
+ @ColDefine(type = ColType.VARCHAR,width = 32)
+ private String userId;
+
+ @Column
+ @Comment("用户工号")
+ @ColDefine(type = ColType.VARCHAR,width = 50)
+ private String loginName;
+
+ @Column
+ @Comment("用户姓名")
+ @ColDefine(type = ColType.VARCHAR,width = 50)
+ private String userName;
+
+ @Column
+ @Comment("工会id")
+ @ColDefine(type = ColType.VARCHAR,width = 32)
+ private String unionId;
+
+ @Column
+ @Comment("用户工会")
+ @ColDefine(type = ColType.VARCHAR,width = 50)
+ private String unionName;
+
+ @Column
+ @Comment("单位id")
+ @ColDefine(type = ColType.VARCHAR,width = 32)
+ private String unitId;
+
+ @Column
+ @Comment("用户单位")
+ @ColDefine(type = ColType.VARCHAR,width = 50)
+ private String unitName;
+
+ @Column
+ @Comment("交易金额")
+ @ColDefine(type = ColType.INT)
+ private Integer tradingAmount;
+
+ @Column
+ @Comment("是否退款")
+ @ColDefine(type = ColType.BOOLEAN)
+ private Boolean isRefund;
+
+ @Column
+ @Comment("退款金额")
+ @ColDefine(type = ColType.INT)
+ private Integer refundAmount;
+
+ @Column
+ @Comment("订单描述")
+ @ColDefine(type = ColType.VARCHAR,width = 128)
+ private String description;
+
+ @Column
+ @Comment("附加详细描述")
+ @ColDefine(type = ColType.TEXT)
+ private String attach;
+
+ @Column
+ @Comment("微信支付用户openId")
+ @ColDefine(type = ColType.VARCHAR,width = 50)
+ private String openId;
+
+ @Column
+ @Comment("交易返回数据")
+ @ColDefine(type = ColType.MYSQL_JSON)
+ private String resultJson;
+
+ @Column
+ @Comment("交易名单的主键id")
+ @ColDefine(type = ColType.VARCHAR, width = 32)
+ private String memberPaymentInfoId;
+}
diff --git a/src/main/java/com/budwk/app/base/payment/wxpay/bean/WxPayV3Bean.java b/src/main/java/com/budwk/app/base/payment/wxpay/bean/WxPayV3Bean.java
new file mode 100644
index 00000000..c3ef48be
--- /dev/null
+++ b/src/main/java/com/budwk/app/base/payment/wxpay/bean/WxPayV3Bean.java
@@ -0,0 +1,53 @@
+package com.budwk.app.base.payment.wxpay.bean;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @version 1.0
+ * @Author zzr
+ * @name:WxPayV3Bean
+ * @Date 2025/3/6 9:54
+ * @注释
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class WxPayV3Bean {
+
+ /**
+ * 服务号
+ * appid: wxf12fa6175ec7c75f
+ * app_secret: f65bbb41d3de4a93c8c93d8e2fd50369
+ */
+
+ private String appId;
+ private String keyPath;
+ private String publicKeyPath;
+ private String certPath;
+ private String certP12Path;
+ private String platformCertPath;
+ private String mchId;
+ private String apiKey;
+ private String apiKey3;
+ private String domain;
+ private String publicKeyId;
+
+ @Override
+ public String toString() {
+ return "WxPayV3Bean{" +
+ "appId='" + appId + '\'' +
+ ", keyPath='" + keyPath + '\'' +
+ ", publicKeyPath='" + publicKeyPath + '\'' +
+ ", certPath='" + certPath + '\'' +
+ ", certP12Path='" + certP12Path + '\'' +
+ ", platformCertPath='" + platformCertPath + '\'' +
+ ", mchId='" + mchId + '\'' +
+ ", apiKey='" + apiKey + '\'' +
+ ", apiKey3='" + apiKey3 + '\'' +
+ ", domain='" + domain + '\'' +
+ ", publicKeyId='" + publicKeyId + '\'' +
+ '}';
+ }
+}
diff --git a/src/main/java/com/budwk/app/base/payment/wxpay/controller/WxPayCommonController.java b/src/main/java/com/budwk/app/base/payment/wxpay/controller/WxPayCommonController.java
new file mode 100644
index 00000000..598272ec
--- /dev/null
+++ b/src/main/java/com/budwk/app/base/payment/wxpay/controller/WxPayCommonController.java
@@ -0,0 +1,44 @@
+package com.budwk.app.base.payment.wxpay.controller;
+
+import com.budwk.app.base.payment.wxpay.service.WxPayCommonService;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.nutz.ioc.loader.annotation.Inject;
+import org.nutz.ioc.loader.annotation.IocBean;
+import org.nutz.mvc.annotation.At;
+import org.nutz.mvc.annotation.Ok;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * @version 1.0
+ * @Author zzr
+ * @name:WxPayCommonController
+ * @Date 2025/3/10 19:28
+ * @注释 微信支付回调
+ */
+@Slf4j
+@IocBean
+@Ok("json:full")
+@At("/platform/wxPay")
+public class WxPayCommonController {
+
+ @Inject
+ private WxPayCommonService wxPayCommonService;
+
+
+ @At
+ @Ok("void")
+ @ApiOperation("微信支付-付款回调")
+ public void payNotify(HttpServletRequest request, HttpServletResponse response){
+ wxPayCommonService.payNotify(request, response);
+ }
+
+
+ @At
+ @Ok("void")
+ public void refundNotify(HttpServletRequest request, HttpServletResponse response){
+ wxPayCommonService.refundNotify(request, response);
+ }
+}
diff --git a/src/main/java/com/budwk/app/base/payment/wxpay/pem/apiclient_cert.pem b/src/main/java/com/budwk/app/base/payment/wxpay/pem/apiclient_cert.pem
new file mode 100644
index 00000000..ee557fe9
--- /dev/null
+++ b/src/main/java/com/budwk/app/base/payment/wxpay/pem/apiclient_cert.pem
@@ -0,0 +1,25 @@
+-----BEGIN CERTIFICATE-----
+MIIEKDCCAxCgAwIBAgIUONYpzvxVfV/RrWnLGPlL6/nb/AcwDQYJKoZIhvcNAQEL
+BQAwXjELMAkGA1UEBhMCQ04xEzARBgNVBAoTClRlbnBheS5jb20xHTAbBgNVBAsT
+FFRlbnBheS5jb20gQ0EgQ2VudGVyMRswGQYDVQQDExJUZW5wYXkuY29tIFJvb3Qg
+Q0EwHhcNMjUwMzA2MTQwNDAzWhcNMzAwMzA1MTQwNDAzWjCBgTETMBEGA1UEAwwK
+MTY2NjkzMDk4MDEbMBkGA1UECgwS5b6u5L+h5ZWG5oi357O757ufMS0wKwYDVQQL
+DCTmrabmsYnlnLDlpKfkuInmgJ3np5HmioDmnInpmZDlhazlj7gxCzAJBgNVBAYT
+AkNOMREwDwYDVQQHDAhTaGVuWmhlbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBALlDML+FJEW+m2hXp/qUH1cU6xlwFllLU4LjFusJkxwyzzcZO4nY0mOi
+3woz8xIJipvUWj2Y413WYwMkfcJcRgghupZkfYwdQtSyPfUu7xQBUm8TLWGH88Re
+kyyVABEB5T+ne6r3LesXS4IuZknm5N/iaa4z9mbbscGqF0GLvMUqZCRNyTkyfK9r
+Bkvs/vspi+Jcjx9I7aU++h/vwzsGB6Bf/uB3kLMb+lj/i3xY20sdrrW8JD2sqONQ
+NcRIKmNXSp4zsNy+d6kIKWE3P5609ZTta1ogkeGBNdGXMRNNy6vLIudQc7zny/9H
+S4LxGsIrsYmZNhGljRng56X+PG3w1CUCAwEAAaOBuTCBtjAJBgNVHRMEAjAAMAsG
+A1UdDwQEAwID+DCBmwYDVR0fBIGTMIGQMIGNoIGKoIGHhoGEaHR0cDovL2V2Y2Eu
+aXRydXMuY29tLmNuL3B1YmxpYy9pdHJ1c2NybD9DQT0xQkQ0MjIwRTUwREJDMDRC
+MDZBRDM5NzU0OTg0NkMwMUMzRThFQkQyJnNnPUhBQ0M0NzFCNjU0MjJFMTJCMjdB
+OUQzM0E4N0FEMUNERjU5MjZFMTQwMzcxMA0GCSqGSIb3DQEBCwUAA4IBAQBQ5e8U
+1pQ40gfRoZyO0s+QF16L8pAcVLVwHffo0mmJpIRB4bu8W5IJ9kKhksJ0CzZ0YEwB
+Yv7/LTC1qpi7PE73nZ4Ytm9oZeH1Kk5KIlQS+2H3IwOpoV4wucMc34y6MVQJjC7E
+u5OnglBPSAopwNP4p+Us9A3ah7Ks48XCw+LCEWpA/setJwo9C13Qq2AQm8qLpogK
+pNBwm/oylxb8/kZK1lLPgcXd7geLDhl/Str+aOzBXyd7YSEMxS8zWiZhWozu6rCq
+dg10QFKicPGDkDzNQwBz7cDXvlJXSWRDBK5r1li1YgX+QD5WJYPchATo5SlvF4Lr
+5/V/dZzI7BJTinr1
+-----END CERTIFICATE-----
diff --git a/src/main/java/com/budwk/app/base/payment/wxpay/pem/apiclient_key.pem b/src/main/java/com/budwk/app/base/payment/wxpay/pem/apiclient_key.pem
new file mode 100644
index 00000000..36b4b679
--- /dev/null
+++ b/src/main/java/com/budwk/app/base/payment/wxpay/pem/apiclient_key.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC5QzC/hSRFvpto
+V6f6lB9XFOsZcBZZS1OC4xbrCZMcMs83GTuJ2NJjot8KM/MSCYqb1Fo9mONd1mMD
+JH3CXEYIIbqWZH2MHULUsj31Lu8UAVJvEy1hh/PEXpMslQARAeU/p3uq9y3rF0uC
+LmZJ5uTf4mmuM/Zm27HBqhdBi7zFKmQkTck5MnyvawZL7P77KYviXI8fSO2lPvof
+78M7BgegX/7gd5CzG/pY/4t8WNtLHa61vCQ9rKjjUDXESCpjV0qeM7DcvnepCClh
+Nz+etPWU7WtaIJHhgTXRlzETTcuryyLnUHO858v/R0uC8RrCK7GJmTYRpY0Z4Oel
+/jxt8NQlAgMBAAECggEBAKGQqTi9Ii741Ysftsze1J4bDhS3HaZt/LyO0vPS+8qB
+vRiuFrBWuNRPu/en6nl2fLz9al2Bsfep+dtJvar80c5Rq+lwZdUFx7y6XTIv5iwq
+IvvfW5pM+Bfu6qbcopwybP0gUMd+nTsCq8UyNd6JUFy3lbZodqCpQ9JDRdjRxqSi
+oMonve36Q9kJyDs0mS2F0w+Y9oYFtpE9WvGMBthwT7VfzWuDa+YC/Gh0RhT2V9OG
+uDC8kxmfaIbk0IITDVFWVMMhScXiqmJL1II9yQWsCHH73dZS+dDxwwH0ObRD3kOx
+hHptxpRzhtuNxLjCOZDGhneyvUHIftpqf4wdOgQ8iUECgYEA6TDo36s3XDFIs8SH
+lpVYrhie0nmaNET50CtOc5C2M1OotrkMUrQTMoa4F263UloJfqweW9AKZX11wPp2
+8h7af2sZsGawjuwv7mMthZJoi0fc5J2+z6BnVGgFo7uZbcs1Y/+B0tNrDKIHVrqb
+Lywmh18iKDXrMiMDMRp8mtiNyU0CgYEAy2Im0oGb1rmAh9gPvQIENmNEgRsyIU4x
+WPAFb5KO7jnseO9KrC9uiRSO5JuwZu/QNGlga2w77Gu7n+Q4HfSU2KFlNhR6pKjb
+mqXzPq1quryzbtWay520VsRKz3F/Tfp1FUpIuZvj8x7FrL8z8vjbLkbW4p+Z7HE4
+heYdmk2KCjkCgYBC6xU48pJtc3swk2pxjSQoucoCDoFxFIY39eWKhnGy4wQcg5gE
+t8Z2jnCP0ObszAfDgTMpF9eT7l/3ZCj0LGR5gkuZ8q4iJadPmsh6d1xm89VxCc+z
+F43vi/ZqWTCbcGA+9RjVXnRu6KSAgAZ+kAXyfRt5PJflCGUnFznA2fo+VQKBgG6s
+n7Gno/UKBv5lPdM9mKs8A5g6A2KKWWIrYtnfJ/ZNAPiiRsFCImVoEYYnjqvFTkhn
+G3f5cvZpfbU5jKwIX0MvOJNgO4kE9lPGgIKX/d9WNlJW3KB21h7tPER8X6lAZJxM
+SxztzvKKyUkC1zMd5q/dDqxN64X16qs6mNtR3I9JAoGBAJbqQtg59Szi2m4jZ9Xo
+0cKvP7+PSKgysdWsemTkU0ji1rcMpGRTtcUGRWlawLjRSInQS+xNdL6GVU5mHZWe
+gNwXIXEpape4IJta0B1PYAhNcIvOSh5CzcUnVTeo6q0CTSVQPdsivlrObLKZ4uGQ
+eewDw82Ej6YoSgczjf99ug0x
+-----END PRIVATE KEY-----
diff --git a/src/main/java/com/budwk/app/base/payment/wxpay/pem/wechatpay.pem b/src/main/java/com/budwk/app/base/payment/wxpay/pem/wechatpay.pem
new file mode 100644
index 00000000..aaa69381
--- /dev/null
+++ b/src/main/java/com/budwk/app/base/payment/wxpay/pem/wechatpay.pem
@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIIEFDCCAvygAwIBAgIUKN05Gm0Q9n9mLPPltUh8qjKKegQwDQYJKoZIhvcNAQEL
+BQAwXjELMAkGA1UEBhMCQ04xEzARBgNVBAoTClRlbnBheS5jb20xHTAbBgNVBAsT
+FFRlbnBheS5jb20gQ0EgQ2VudGVyMRswGQYDVQQDExJUZW5wYXkuY29tIFJvb3Qg
+Q0EwHhcNMjUwMzA2MTQwNDAzWhcNMzAwMzA1MTQwNDAzWjBuMRgwFgYDVQQDDA9U
+ZW5wYXkuY29tIHNpZ24xEzARBgNVBAoMClRlbnBheS5jb20xHTAbBgNVBAsMFFRl
+bnBheS5jb20gQ0EgQ2VudGVyMQswCQYDVQQGEwJDTjERMA8GA1UEBwwIU2hlblpo
+ZW4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDbMenF0GNlcUYErPpj
+Aj+WOuzRFz+PJcrtgkI81S0WDh8flMtq1A2nwKARNyPXx+UPd9it657nH5G8E9kL
+Ea5u5rENpn+JZc4cAIvZqazteSAUg2pfQH9ewW+/CxNOWarjXPNRYa0OjZWf+5F8
+ZBOT/x8sJ2+jou9lxKNe1skha+jzmiQ8m3B2vPXqR87qgL/fyQjjpLvBGpvearh/
+hxTMBEfGeB4F6Fu52Fz3/VCWe38ubxwj8EMcRSPp4xsl2BTaRVMMT525Hul2N49C
+vXCSVsCNt3ND8UDffXnb19Zgpir4KqpECTbfqcT7YX+U34P1Av8gcDl6rRm+7Wpj
+PY4RAgMBAAGjgbkwgbYwCQYDVR0TBAIwADALBgNVHQ8EBAMCA/gwgZsGA1UdHwSB
+kzCBkDCBjaCBiqCBh4aBhGh0dHA6Ly9ldmNhLml0cnVzLmNvbS5jbi9wdWJsaWMv
+aXRydXNjcmw/Q0E9MUJENDIyMEU1MERCQzA0QjA2QUQzOTc1NDk4NDZDMDFDM0U4
+RUJEMiZzZz1IQUNDNDcxQjY1NDIyRTEyQjI3QTlEMzNBODdBRDFDREY1OTI2RTE0
+MDM3MTANBgkqhkiG9w0BAQsFAAOCAQEAUAvXCx55ZGmd3ATi28W4mirnUHQb2RAw
+X5/0OC2SkuBdzxI5RbY4YAuugP6nQIAU8TXCANzno2oqZ6vmFZ4dBnJmLewVFMoM
+qp18A4RWmnsGnmEIgMm6N1kB+RbM11oLCLdy2KK0SvmqDmTBFJyjyUGqEmRshnV/
+QfKn4UUzJ4suDFAolGX4DyDIOtM0D+SCHwwxGI4yf2+nj4IB8XgmmOeschxoSvu9
+T3E6BgQ271YVDwg+3r9CiFVI5JdyftvQagOEQmZJMPINEKgFn5LzrZJW7mH42+N+
+i4RFz04wpGUsrSFDESh3JxYSNhq/HulpMLinntWxEBHFhpkjyyGDGg==
+-----END CERTIFICATE-----
\ No newline at end of file
diff --git a/src/main/java/com/budwk/app/base/payment/wxpay/service/WxPayCommonService.java b/src/main/java/com/budwk/app/base/payment/wxpay/service/WxPayCommonService.java
new file mode 100644
index 00000000..0333726b
--- /dev/null
+++ b/src/main/java/com/budwk/app/base/payment/wxpay/service/WxPayCommonService.java
@@ -0,0 +1,71 @@
+package com.budwk.app.base.payment.wxpay.service;
+
+
+import com.budwk.app.base.payment.wxpay.vo.WxPayNeedParamVo;
+import com.budwk.app.base.result.Result;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * @version 1.0
+ * @Author zzr
+ * @name:WxPayCommonService
+ * @Date 2025/3/6 11:40
+ * @注释
+ */
+public interface WxPayCommonService {
+
+
+ /**
+ * 二维码统一下单,返回支付二维码链接
+ * @param paramVo 需要传递的信息,请自定义付款金额Amount 和 付款描述Description,其他关键信息请勿修改
+ * 注意Amount的总价total是int类型,会自动乘以0.01,例如传递100元,会自动转换为1元
+ * @return
+ */
+ Result nativePay(WxPayNeedParamVo paramVo);
+
+
+ /**
+ * JSAPI支付,返回支付参数
+ * @param paramVo 需要传递的信息,请自定义付款金额Amount 和 付款描述Description,其他关键信息请勿修改
+ * 注意Amount的总价total是int类型,会自动乘以0.01,例如传递100元,会自动转换为1元
+ * JsAPI支付,需要传递openId,请自行获取
+ * @return
+ */
+ Result jsApiPay(WxPayNeedParamVo paramVo);
+
+ /**
+ * WAP支付,返回支付参数
+ * @param paramVo 需要传递的信息,请自定义付款金额Amount 和 付款描述Description,其他关键信息请勿修改
+ * 注意Amount的总价total是int类型,会自动乘以0.01,例如传递100元,会自动转换为1元
+ * @return
+ */
+ Result wapPay(WxPayNeedParamVo paramVo);
+
+
+ /**
+ * 支付回调,验证是否支付成功
+ * @param request 请求
+ * @param response 响应
+ */
+ void payNotify(HttpServletRequest request, HttpServletResponse response);
+
+
+ /**
+ * 退款
+ * @param transactionId 微信订单号
+ * @param outTradeNo 商户订单号
+ * @return
+ */
+ Result refund(String transactionId, String outTradeNo);
+
+
+ /**
+ * 退款回调
+ * @param request 请求
+ * @param response 响应
+ */
+ void refundNotify(HttpServletRequest request, HttpServletResponse response);
+
+}
diff --git a/src/main/java/com/budwk/app/base/payment/wxpay/service/impl/WxPayCommonServiceImpl.java b/src/main/java/com/budwk/app/base/payment/wxpay/service/impl/WxPayCommonServiceImpl.java
new file mode 100644
index 00000000..0b3cd689
--- /dev/null
+++ b/src/main/java/com/budwk/app/base/payment/wxpay/service/impl/WxPayCommonServiceImpl.java
@@ -0,0 +1,612 @@
+package com.budwk.app.base.payment.wxpay.service.impl;
+
+import cn.hutool.core.date.DatePattern;
+import cn.hutool.core.date.DateTime;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.http.ContentType;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import com.budwk.app.base.payment.enums.*;
+import com.budwk.app.base.payment.model.PaymentRefundRecord;
+import com.budwk.app.base.payment.model.PaymentTransaction;
+import com.budwk.app.base.payment.wxpay.bean.WxPayV3Bean;
+import com.budwk.app.base.payment.wxpay.service.WxPayCommonService;
+import com.budwk.app.base.payment.wxpay.vo.WxPayNeedParamVo;
+import com.budwk.app.base.result.Result;
+import com.budwk.app.sys.models.Sys_user;
+import com.budwk.app.sys.services.SysUserService;
+import com.budwk.app.sys.views.View_user;
+import com.budwk.app.web.commons.auth.utils.SecurityUtil;
+import com.budwk.app.zhgh.staffmanage.memberCost.model.MemberPaymentInfo;
+import com.ijpay.core.IJPayHttpResponse;
+import com.ijpay.core.enums.AuthTypeEnum;
+import com.ijpay.core.enums.RequestMethodEnum;
+import com.ijpay.core.kit.AesUtil;
+import com.ijpay.core.kit.HttpKit;
+import com.ijpay.core.kit.PayKit;
+import com.ijpay.core.kit.WxPayKit;
+import com.ijpay.wxpay.WxPayApi;
+import com.ijpay.wxpay.enums.WxDomainEnum;
+import com.ijpay.wxpay.enums.v3.BasePayApiEnum;
+import com.ijpay.wxpay.model.v3.*;
+import lombok.extern.slf4j.Slf4j;
+import org.nutz.aop.interceptor.ioc.TransAop;
+import org.nutz.dao.Chain;
+import org.nutz.dao.Cnd;
+import org.nutz.dao.Dao;
+import org.nutz.ioc.aop.Aop;
+import org.nutz.ioc.loader.annotation.Inject;
+import org.nutz.ioc.loader.annotation.IocBean;
+import org.nutz.lang.random.R;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.ByteArrayInputStream;
+import java.io.FileWriter;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.nio.charset.StandardCharsets;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @version 1.0
+ * @Author zzr
+ * @name:WxPayCommonServiceImpl
+ * @Date 2025/3/6 11:41
+ * @注释
+ */
+@Slf4j
+@IocBean
+public class WxPayCommonServiceImpl implements WxPayCommonService {
+
+ // 判断下单接口成功返回码
+ private final static int OK = 200;
+
+ // 1666930980 商户号
+ // ww3eda050a1b5ad96e 企业微信
+ // wxf12fa6175ec7c75f 微信服务号
+ // 微信V3必要的支付参数,后期优化
+ private final static WxPayV3Bean WX_PAY_V3_BEAN = new WxPayV3Bean(
+ "wxf12fa6175ec7c75f",
+ "src/main/java/com/budwk/app/base/payment/wxpay/pem/apiclient_key.pem",
+ "src/main/java/com/budwk/app/base/payment/wxpay/pem/apiclient_key.pem",
+ "src/main/java/com/budwk/app/base/payment/wxpay/pem/apiclient_cert.pem",
+ "src/main/java/com/budwk/app/base/payment/wxpay/pem/apiclient_cert.pem",
+ "src/main/java/com/budwk/app/base/payment/wxpay/pem/wechatpay.pem",
+ "1666930980",
+ "",
+ "4ebd020883285d698c44ec50939c0967",
+ "https://schoolhd.top/platform",
+ "");
+
+ // 自定义参数,内容是统一的,但会被多次调用,直接声明
+ private String serialNo;
+ private String platSerialNo;
+ private final String payNotifyUrl = WX_PAY_V3_BEAN.getDomain().concat("/wxPay/payNotify");
+ private final String refundNotifyUrl = WX_PAY_V3_BEAN.getDomain().concat("/wxPay/refundNotify");
+
+ @Inject
+ private Dao dao;
+ @Inject
+ private SysUserService sysUserService;
+
+ /**
+ * 微信扫描二维码支付
+ * @param paramVo 需要传递的信息,请自定义付款金额Amount 和 付款描述Description,其他关键信息请勿修改
+ * 注意Amount的总价total是int类型,会自动乘以0.01,例如传递100元,会自动转换为1元
+ * @return
+ */
+ @Override
+ @Aop(TransAop.READ_COMMITTED)
+ public Result nativePay(WxPayNeedParamVo paramVo) {
+ try {
+ // 商户订单号,存储当前用户的订单
+ String outTradeNo = PayKit.generateStr();
+
+ String timeExpire = getNowTimeExpire();
+ UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel()
+ .setAppid(WX_PAY_V3_BEAN.getAppId())
+ .setMchid(WX_PAY_V3_BEAN.getMchId())
+ // 描述信息
+ .setDescription(paramVo.getDescription())
+ .setOut_trade_no(outTradeNo)
+ .setTime_expire(timeExpire)
+ .setAttach(paramVo.getAttach())
+ .setNotify_url(payNotifyUrl)
+ // 订单金额
+ .setAmount(paramVo.getAmount());
+
+ log.info("二维码支付统一下单参数 {}", JSONUtil.toJsonStr(unifiedOrderModel));
+ IJPayHttpResponse response = WxPayApi.v3(
+ RequestMethodEnum.POST,
+ WxDomainEnum.CHINA.toString(),
+ BasePayApiEnum.NATIVE_PAY.toString(),
+ WX_PAY_V3_BEAN.getMchId(),
+ getSerialNumber(),
+ null,
+ WX_PAY_V3_BEAN.getKeyPath(),
+ JSONUtil.toJsonStr(unifiedOrderModel),
+ AuthTypeEnum.RSA.getCode()
+ );
+ log.info("统一下单响应 {}", response);
+ // 根据证书序列号查询对应的证书来验证签名结果
+ boolean verifySignature = WxPayKit.verifySignature(response, WX_PAY_V3_BEAN.getPlatformCertPath());
+ log.info("verifySignature: {}", verifySignature);
+ // 下单返回数据
+ String result = response.getBody();
+
+ JSONObject jsonObject = JSONUtil.parseObj(result);
+
+ if (response.getStatus() == OK) {
+ // 统一下单成功,向数据库插入订单记录
+ insertTradingOrder(paramVo, outTradeNo, result);
+ return Result.success("请在三分钟内完成支付,超时将自动取消订单").addData(result);
+ } else {
+ WxPayResultCode code = WxPayResultCode.valueOf(WxPayResultCode.class, jsonObject.getStr("code"));
+ return Result.error(code.getMsg());
+ }
+ } catch (Exception e) {
+ log.error("系统异常", e);
+ return Result.error(e.getMessage());
+ }
+ }
+
+
+ @Override
+ @Aop(TransAop.READ_COMMITTED)
+ public Result jsApiPay(WxPayNeedParamVo paramVo) {
+ try {
+ Sys_user user = dao.fetch(Sys_user.class, SecurityUtil.getUserId());
+ if (StrUtil.isBlank(user.getOpenId())) {
+ return Result.error("未获取到当前用户的openid,请重新登录");
+ }
+ // 商户订单号,存储当前用户的订单
+ String outTradeNo = PayKit.generateStr();
+ // 订单关闭时间
+ String timeExpire = getNowTimeExpire();
+ UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel()
+ .setAppid(WX_PAY_V3_BEAN.getAppId())
+ .setMchid(WX_PAY_V3_BEAN.getMchId())
+ .setDescription(paramVo.getDescription())
+ .setOut_trade_no(outTradeNo)
+ .setTime_expire(timeExpire)
+ .setAttach(paramVo.getAttach())
+ .setNotify_url(payNotifyUrl)
+ .setAmount(paramVo.getAmount())
+ .setPayer(new Payer().setOpenid(user.getOpenId()));
+
+ log.info("统一下单参数 {}", JSONUtil.toJsonStr(unifiedOrderModel));
+ IJPayHttpResponse response = WxPayApi.v3(
+ RequestMethodEnum.POST,
+ WxDomainEnum.CHINA.toString(),
+ BasePayApiEnum.JS_API_PAY.toString(),
+ WX_PAY_V3_BEAN.getMchId(),
+ getSerialNumber(),
+ null,
+ WX_PAY_V3_BEAN.getKeyPath(),
+ JSONUtil.toJsonStr(unifiedOrderModel)
+ );
+ log.info("统一下单响应 {}", response);
+ // 根据证书序列号查询对应的证书来验证签名结果
+ boolean verifySignature = WxPayKit.verifySignature(response, WX_PAY_V3_BEAN.getPlatformCertPath());
+ log.info("verifySignature: {}", verifySignature);
+ if (response.getStatus() == OK && verifySignature) {
+ String body = response.getBody();
+ JSONObject jsonObject = JSONUtil.parseObj(body);
+ String prepayId = jsonObject.getStr("prepay_id");
+ Map map = WxPayKit.jsApiCreateSign(WX_PAY_V3_BEAN.getAppId(), prepayId, WX_PAY_V3_BEAN.getKeyPath());
+ log.info("唤起支付参数:{}", map);
+ // 唤起支付成功,向数据库插入订单参数
+ insertTradingOrder(paramVo, outTradeNo, body);
+ return Result.success().addData(JSONUtil.toJsonStr(map));
+ }
+// return JSONUtil.toJsonStr(response);
+ return Result.error("唤起支付失败");
+ } catch (Exception e) {
+ log.error("系统异常", e);
+ return Result.error(e.getMessage());
+ }
+ }
+
+
+ @Override
+ @Aop(TransAop.READ_COMMITTED)
+ public Result wapPay(WxPayNeedParamVo paramVo) {
+ try {
+ // 商户订单号,存储当前用户的订单
+ String outTradeNo = PayKit.generateStr();
+ // 订单关闭时间
+ String timeExpire = getNowTimeExpire();
+ UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel()
+ .setAppid(WX_PAY_V3_BEAN.getAppId())
+ .setMchid(WX_PAY_V3_BEAN.getMchId())
+ .setDescription(paramVo.getDescription())
+ .setOut_trade_no(outTradeNo)
+ .setTime_expire(timeExpire)
+ .setAttach(paramVo.getAttach())
+ .setNotify_url(payNotifyUrl)
+ .setAmount(paramVo.getAmount())
+ .setScene_info(
+ new SceneInfo()
+ .setPayer_client_ip("").
+ setH5_info(new H5Info()
+ .setType("Wap")));
+
+ log.info("统一下单参数 {}", JSONUtil.toJsonStr(unifiedOrderModel));
+ IJPayHttpResponse response = WxPayApi.v3(
+ RequestMethodEnum.POST,
+ WxDomainEnum.CHINA.toString(),
+ BasePayApiEnum.H5_PAY.toString(),
+ WX_PAY_V3_BEAN.getMchId(),
+ getSerialNumber(),
+ null,
+ WX_PAY_V3_BEAN.getKeyPath(),
+ JSONUtil.toJsonStr(unifiedOrderModel)
+ );
+ log.info("统一下单响应 {}", response);
+ // 根据证书序列号查询对应的证书来验证签名结果
+ boolean verifySignature = WxPayKit.verifySignature(response, WX_PAY_V3_BEAN.getPlatformCertPath());
+ log.info("verifySignature: {}", verifySignature);
+ if (response.getStatus() == OK && verifySignature) {
+ String body = response.getBody();
+ insertTradingOrder(paramVo, outTradeNo, body);
+ return Result.success(JSONUtil.toJsonStr(body));
+ }
+// return JSONUtil.toJsonStr(response);
+ return Result.error();
+ } catch (Exception e) {
+ log.error("系统异常", e);
+ return Result.error(e.getMessage());
+ }
+ }
+
+
+ /**
+ * 支付回调
+ * @param request
+ * @param response
+ */
+ @Override
+ public void payNotify(HttpServletRequest request, HttpServletResponse response) {
+ Map map = new HashMap<>(12);
+ try {
+ String timestamp = request.getHeader("Wechatpay-Timestamp");
+ String nonce = request.getHeader("Wechatpay-Nonce");
+ String serialNo = request.getHeader("Wechatpay-Serial");
+ String signature = request.getHeader("Wechatpay-Signature");
+
+ log.info("timestamp:{} nonce:{} serialNo:{} signature:{}", timestamp, nonce, serialNo, signature);
+ String result = HttpKit.readData(request);
+ log.info("支付通知密文 {}", result);
+// String plainText = null;
+ // 需要通过证书序列号查找对应的证书,verifyNotify 中有验证证书的序列号
+ String plainText = WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp,
+ WX_PAY_V3_BEAN.getApiKey3(), WX_PAY_V3_BEAN.getPlatformCertPath());
+ // 微信公钥验证签名并解密
+// if (ObjectUtil.equals(serialNo, WX_PAY_V3_BEAN.getPublicKeyId())) {
+// plainText = WxPayKit.verifyPublicKeyNotify(result, signature, nonce, timestamp,
+// WX_PAY_V3_BEAN.getApiKey3(), WX_PAY_V3_BEAN.getPlatformCertPath());
+// }
+ log.info("支付通知明文 {}", plainText);
+ if (StrUtil.isNotEmpty(plainText)) {
+ // 支付结果
+ JSONObject jsonResult = JSONUtil.parseObj(plainText);
+ // 获取缴费记录,并修改缴费结果
+ PaymentTransaction transaction = sysUserService.dao().fetch(PaymentTransaction.class, Cnd.where("outTradeNo", "=", jsonResult.getStr("out_trade_no")));
+ transaction.setTradingState(TradingStateEnum.PAY_SUCCESS.name());
+ transaction.setTransactionId(jsonResult.getStr("transaction_id"));
+ sysUserService.dao().update(transaction);
+
+ sysUserService.dao().update(MemberPaymentInfo.class,
+ Chain.make("isPayment", true).add("paymentTime", DateUtil.date())
+ .add("paymentMoney", transaction.getTradingAmount()),
+ Cnd.where("id", "=", transaction.getMemberPaymentInfoId())
+ );
+
+ response.setStatus(200);
+ map.put("code", "SUCCESS");
+ map.put("message", "SUCCESS");
+ } else {
+ response.setStatus(500);
+ map.put("code", "ERROR");
+ map.put("message", "签名错误");
+ log.error("签名错误");
+ }
+
+ // 支付回调,返回给微信,自己也接收不到结果,这里的返回没用
+ response.setHeader("Content-type", ContentType.JSON.toString());
+ response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));
+ response.flushBuffer();
+ } catch (Exception e) {
+ log.error("系统异常", e);
+ }
+ }
+
+
+ /**
+ * 退款
+ * @param transactionId 微信订单号
+ * @param outTradeNo 商户订单号
+ * @return
+ */
+ @Override
+ public Result refund(String transactionId, String outTradeNo) {
+ try {
+ // 查询商户订单
+ PaymentTransaction transaction = sysUserService.dao().fetch(PaymentTransaction.class, Cnd.where("outTradeNo", "=", outTradeNo));
+ Integer tradingAmount = transaction.getTradingAmount();
+ String outRefundNo = PayKit.generateStr();
+ log.info("商户退款单号: {}", outRefundNo);
+
+ // 没用
+// List list = new ArrayList<>();
+// RefundGoodsDetail refundGoodsDetail = new RefundGoodsDetail()
+// .setMerchant_goods_id("123")
+// .setGoods_name("测试")
+// .setUnit_price(1)
+// .setRefund_amount(1)
+// .setRefund_quantity(1);
+// list.add(refundGoodsDetail);
+
+ RefundModel refundModel = new RefundModel()
+ .setOut_refund_no(outRefundNo)
+ .setReason(transaction.getDescription() + "退款")
+ .setNotify_url(refundNotifyUrl)
+ .setAmount(new RefundAmount().setRefund(tradingAmount).setTotal(tradingAmount).setCurrency("CNY"));
+// .setGoods_detail(list);
+
+ if (StrUtil.isNotEmpty(transactionId)) {
+ refundModel.setTransaction_id(transactionId);
+ }
+ if (StrUtil.isNotEmpty(outTradeNo)) {
+ refundModel.setOut_trade_no(outTradeNo);
+ }
+ log.info("退款参数 {}", JSONUtil.toJsonStr(refundModel));
+ IJPayHttpResponse response = WxPayApi.v3(
+ RequestMethodEnum.POST,
+ WxDomainEnum.CHINA.toString(),
+ BasePayApiEnum.REFUND.toString(),
+ WX_PAY_V3_BEAN.getMchId(),
+ getSerialNumber(),
+ null,
+ WX_PAY_V3_BEAN.getKeyPath(),
+ JSONUtil.toJsonStr(refundModel)
+ );
+ // 根据证书序列号查询对应的证书来验证签名结果
+ boolean verifySignature = WxPayKit.verifySignature(response, WX_PAY_V3_BEAN.getPlatformCertPath());
+ // 微信支付公钥验证签名
+// boolean verifySignature = WxPayKit.verifyPublicKeySignature(response, WX_PAY_V3_BEAN.getPlatformCertPath());
+ log.info("verifySignature: {}", verifySignature);
+ log.info("退款响应 {}", response);
+
+ if (verifySignature) {
+ // 用户发起退款,存一个退款记录
+ PaymentRefundRecord record = new PaymentRefundRecord();
+ record.setId(R.UU32());
+ record.setPaymentTransactionId(transaction.getId());
+ record.setOutRefundTradeNo(outRefundNo);
+ record.setRefundNo(null);
+ record.setRefundState(RefundStateEnum.REFUNDING.name());
+ record.setRefundAmount(tradingAmount);
+ record.setTotal(tradingAmount);
+ record.setResultJson(response.getBody());
+ dao.insert(record);
+
+ dao.update(MemberPaymentInfo.class, Chain.make("isRefund", true)
+ .add("refundState", RefundStateEnum.REFUNDING.name()),
+ Cnd.where("id", "=", transaction.getMemberPaymentInfoId()));
+
+ // 返回退款下单结果
+ return Result.success();
+ }
+ return Result.error("退款失败");
+ } catch (Exception e) {
+ log.error("系统异常", e);
+ return Result.error(e.getMessage());
+ }
+ }
+
+
+ /**
+ * 退款回调
+ * @param request
+ * @param response
+ */
+ @Override
+ @Aop(TransAop.READ_COMMITTED)
+ public void refundNotify(HttpServletRequest request, HttpServletResponse response) {
+ Map map = new HashMap<>(12);
+ try {
+ String timestamp = request.getHeader("Wechatpay-Timestamp");
+ String nonce = request.getHeader("Wechatpay-Nonce");
+ String serialNo = request.getHeader("Wechatpay-Serial");
+ String signature = request.getHeader("Wechatpay-Signature");
+
+ log.info("退款通知 timestamp:{} nonce:{} serialNo:{} signature:{}", timestamp, nonce, serialNo, signature);
+ String result = HttpKit.readData(request);
+ log.info("退款通知密文 {}", result);
+// String plainText = null;
+ // 需要通过证书序列号查找对应的证书,verifyNotify 中有验证证书的序列号
+ String plainText = WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp,
+ WX_PAY_V3_BEAN.getApiKey3(), WX_PAY_V3_BEAN.getPlatformCertPath());
+ // 微信公钥验证签名并解密
+// if (ObjectUtil.equals(serialNo, WX_PAY_V3_BEAN.getPublicKeyId())) {
+// plainText = WxPayKit.verifyPublicKeyNotify(result, signature, nonce, timestamp,
+// WX_PAY_V3_BEAN.getApiKey3(), WX_PAY_V3_BEAN.getPlatformCertPath());
+// }
+ log.info("退款通知明文 {}", plainText);
+
+ if (StrUtil.isNotEmpty(plainText)) {
+ // 取出明文,修改退款的结果
+ JSONObject jsonObject = JSONUtil.parseObj(plainText);
+ // 查询出退款记录,存储关键信息并修改
+ PaymentRefundRecord record = dao.fetch(PaymentRefundRecord.class, Cnd.where("outRefundTradeNo", "=", jsonObject.getStr("out_refund_no")));
+ record.setRefundNo(jsonObject.getStr("refund_id"));
+ record.setRefundState(RefundStateEnum.REFUND_SUCCESS.name());
+ // 到此是退款成功了,还要去修改订单表的数据
+ PaymentTransaction transaction = dao.fetch(PaymentTransaction.class, record.getPaymentTransactionId());
+ dao.update(PaymentTransaction.class, Chain.make("isRefund", true)
+ .add("refundAmount", record.getRefundAmount()),
+ Cnd.where("id", "=", record.getPaymentTransactionId()));
+ dao.update(MemberPaymentInfo.class, Chain.make("isPayment", null).add("paymentMoney", null)
+ .add("refundState", RefundStateEnum.REFUND_SUCCESS.name()),
+ Cnd.where("id", "=", transaction.getMemberPaymentInfoId()));
+
+ dao.update(record);
+ response.setStatus(200);
+ map.put("code", "SUCCESS");
+ map.put("message", "SUCCESS");
+ } else {
+ response.setStatus(500);
+ map.put("code", "ERROR");
+ map.put("message", "签名错误");
+ }
+ // 没用
+ response.setHeader("Content-type", ContentType.JSON.toString());
+ response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));
+ response.flushBuffer();
+ } catch (Exception e) {
+ log.error("系统异常", e);
+ }
+ }
+
+
+ /**
+ * 公共方法,获取证书序列号
+ * @return
+ */
+ private String getSerialNumber() {
+ if (StrUtil.isEmpty(serialNo)) {
+ // 获取证书序列号
+ X509Certificate certificate = PayKit.getCertificate(WX_PAY_V3_BEAN.getCertPath());
+ if (null != certificate) {
+ serialNo = certificate.getSerialNumber().toString(16).toUpperCase();
+ // 提前两天检查证书是否有效
+ boolean isValid = PayKit.checkCertificateIsValid(certificate, WX_PAY_V3_BEAN.getMchId(), -2);
+ log.info("证书是否可用 {} 证书有效期为 {}", isValid, DateUtil.format(certificate.getNotAfter(), DatePattern.NORM_DATETIME_PATTERN));
+ }
+// System.out.println("输出证书信息:\n" + certificate.toString());
+// // 输出关键信息,截取部分并进行标记
+// System.out.println("证书序列号:" + certificate.getSerialNumber().toString(16));
+// System.out.println("版本号:" + certificate.getVersion());
+// System.out.println("签发者:" + certificate.getIssuerDN());
+// System.out.println("有效起始日期:" + certificate.getNotBefore());
+// System.out.println("有效终止日期:" + certificate.getNotAfter());
+// System.out.println("主体名:" + certificate.getSubjectDN());
+// System.out.println("签名算法:" + certificate.getSigAlgName());
+// System.out.println("签名:" + certificate.getSignature().toString());
+ }
+ System.out.println("serialNo:" + serialNo);
+ return serialNo;
+ }
+
+
+ /**
+ * 公共方法,获取平台证书序列号
+ * @return
+ */
+ private String getPlatSerialNumber() {
+ if (StrUtil.isEmpty(platSerialNo)) {
+ // 获取平台证书序列号
+ X509Certificate certificate = PayKit.getCertificate(FileUtil.getInputStream(WX_PAY_V3_BEAN.getPlatformCertPath()));
+ platSerialNo = certificate.getSerialNumber().toString(16).toUpperCase();
+ }
+ System.out.println("platSerialNo:" + platSerialNo);
+ return platSerialNo;
+ }
+
+
+ /**
+ * 公共方法,保存平台证书
+ * @param associatedData
+ * @param nonce
+ * @param cipherText
+ * @param algorithm
+ * @param certPath
+ * @return
+ */
+ private String savePlatformCert(String associatedData, String nonce, String cipherText, String algorithm, String certPath) {
+ try {
+ String key3 = WX_PAY_V3_BEAN.getApiKey3();
+ String publicKey;
+ if (StrUtil.equals(algorithm, AuthTypeEnum.SM2.getPlatformCertAlgorithm())) {
+ publicKey = PayKit.sm4DecryptToString(key3, cipherText, nonce, associatedData);
+ } else {
+ AesUtil aesUtil = new AesUtil(WX_PAY_V3_BEAN.getApiKey3().getBytes(StandardCharsets.UTF_8));
+ // 平台证书密文解密
+ // encrypt_certificate 中的 associated_data nonce ciphertext
+ publicKey = aesUtil.decryptToString(
+ associatedData.getBytes(StandardCharsets.UTF_8),
+ nonce.getBytes(StandardCharsets.UTF_8),
+ cipherText
+ );
+ }
+ if (StrUtil.isNotEmpty(publicKey)) {
+ // 保存证书
+ FileWriter writer = new FileWriter(certPath);
+ writer.write(publicKey);
+ // 获取平台证书序列号
+ X509Certificate certificate = PayKit.getCertificate(new ByteArrayInputStream(publicKey.getBytes()));
+ return certificate.getSerialNumber().toString(16).toUpperCase();
+ }
+ return "";
+ } catch (Exception e) {
+ log.error("保存平台证书异常", e);
+ return e.getMessage();
+ }
+ }
+
+
+ /**
+ * 公共方法,构建订单关闭时间,默认3分钟
+ * @return
+ */
+ private String getNowTimeExpire() {
+ long currentTimeMillis = System.currentTimeMillis() + 1000 * 60 * 3;
+ // 将时间戳转换为 DateTime 对象
+ DateTime dateTime = DateUtil.date(currentTimeMillis);
+ return DateUtil.format(dateTime, "yyyy-MM-dd'T'HH:mm:ssXXX");
+ }
+
+
+ /**
+ * 公共方法,插入交易订单
+ * @param paramVo 交易必填参数Vo
+ * @param outTradeNo 商户交易号
+ * @param result 下单返回结构
+ */
+ private void insertTradingOrder(WxPayNeedParamVo paramVo, String outTradeNo, String result){
+ View_user user = sysUserService.dao().fetch(View_user.class, Cnd.where("id", "=", SecurityUtil.getUserId()));
+ PaymentTransaction transaction = new PaymentTransaction();
+ transaction.setId(R.UU32());
+ transaction.setMchId(WX_PAY_V3_BEAN.getMchId());
+ transaction.setOutTradeNo(outTradeNo);
+ transaction.setTransactionId(null);
+ transaction.setTradingChannel(TradingChannelEnum.WX_PAY.name());
+ transaction.setTradingType(TradingTypeEnum.PAYMENT.name());
+ transaction.setUserId(user.getId());
+ transaction.setLoginName(user.getLoginname());
+ transaction.setUserName(user.getUsername());
+ transaction.setUnitId(user.getUnitId());
+ transaction.setUnitName(user.getUnitName());
+ transaction.setUnionId(user.getUnionId());
+ transaction.setUnionName(user.getUnionName());
+ transaction.setTradingState(TradingStateEnum.WAITING_FOR_PAYMENT.name());
+ // 获取订单金额
+ transaction.setTradingAmount(paramVo.getAmount().getTotal());
+ transaction.setIsRefund(false);
+ transaction.setRefundAmount(null);
+ transaction.setDescription(paramVo.getDescription());
+ transaction.setAttach(paramVo.getAttach());
+ transaction.setOpenId(null);
+ transaction.setResultJson(result);
+ transaction.setMemberPaymentInfoId(paramVo.getMemberPaymentInfoId());
+ sysUserService.dao().insert(transaction);
+ }
+}
diff --git a/src/main/java/com/budwk/app/base/payment/wxpay/util/WeChatUtils.java b/src/main/java/com/budwk/app/base/payment/wxpay/util/WeChatUtils.java
new file mode 100644
index 00000000..2137a285
--- /dev/null
+++ b/src/main/java/com/budwk/app/base/payment/wxpay/util/WeChatUtils.java
@@ -0,0 +1,132 @@
+package com.budwk.app.base.payment.wxpay.util;
+
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.http.HttpUtil;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import com.budwk.app.base.constant.RedisConstant;
+import lombok.extern.slf4j.Slf4j;
+import org.nutz.http.Http;
+import org.nutz.integration.jedis.RedisService;
+import org.nutz.ioc.loader.annotation.Inject;
+import org.nutz.ioc.loader.annotation.IocBean;
+import org.nutz.json.Json;
+import org.nutz.lang.util.NutMap;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.SecretKeySpec;
+import java.net.URLEncoder;
+import java.util.Base64;
+
+/**
+ * @version 1.0
+ * @Author zzr
+ * @name:WeChatUtils
+ * @Date 2025/3/10 14:13
+ * @注释
+ */
+@Slf4j
+@IocBean
+public class WeChatUtils {
+
+ private static final String ALGORITHM = "AES";
+ private static final String KEY = "4eXvXq1ZJmZj8z5z6yXvXq1ZJmZj8z5z";
+
+ @Inject
+ private RedisService redisService;
+
+ public static final String APPID = "wxf12fa6175ec7c75f";
+
+ public static final String SECRET = "f65bbb41d3de4a93c8c93d8e2fd50369";
+
+ // 获取code
+ public final static String CODE_URL = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect";
+
+ //获取网页access_token的接口地址
+ public final static String WEB_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=APPSECRET&code=CODE&grant_type=authorization_code";
+
+
+ //获取用户信息地址
+ public final static String USERINFO_URL = "https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN";
+
+ // 获取access_token的接口地址(GET) 限2000(次/天)
+ public final static String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";
+ // 发送模板消息
+ public static final String SEND_MESSAGE_URL = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=ACCESS_TOKEN";
+
+
+ /**
+ * 生成获取code的url
+ *
+ * @param redirectUrl
+ * @return
+ */
+ public String getCodeUrl(String redirectUrl, String state) {
+ String requestUrl = null;
+ state = state == null ? "" : state;
+ try {
+ requestUrl = CODE_URL.replace("APPID", APPID).replace("REDIRECT_URI", URLEncoder.encode(redirectUrl, "UTF-8")).replace("STATE", state);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return requestUrl;
+ }
+
+ /**
+ * 获取微信网页access_token 和 openid
+ *
+ * @param code
+ * @return
+ */
+ public JSONObject getWebAccessTokenAndOpenid(String code) {
+ String requestUrl = WEB_ACCESS_TOKEN_URL.replace("APPID", APPID).replace("APPSECRET", SECRET).replace("CODE", code);
+ return JSONUtil.parseObj(HttpUtil.post(requestUrl, new JSONObject()));
+ }
+
+ /**
+ * 网页授权作用域为snsapi_userinfo,则此时开发者可以通过access_token和openid拉取用户信息了
+ * ...
+ *
+ * @param webAccessToken
+ * @param openid
+ * @return
+ */
+ public JSONObject getUserInfo(String webAccessToken, String openid) {
+ String requestUrl = USERINFO_URL.replace("ACCESS_TOKEN", webAccessToken).replace("OPENID", openid);
+ return JSONUtil.parseObj(HttpUtil.post(requestUrl, new JSONObject()));
+ }
+
+ /**
+ * 获取access_token 先从Redis获取,如果没有再调微信获取并存Redis
+ *
+ * @return
+ */
+ public String getAccessToken() {
+ String token = redisService.get(RedisConstant.REDIS_KEY_WX_TOKEN);
+ if (StrUtil.isNotBlank(token)) {
+ return token;
+ }
+ String accessTokenUrl = ACCESS_TOKEN_URL.formatted(APPID, SECRET);
+ String accessToken = Json.fromJson(NutMap.class, Http.get(accessTokenUrl).getContent()).getString("access_token");
+ redisService.setex(RedisConstant.REDIS_KEY_WX_TOKEN, 60 * 60 * 2, accessToken);
+ return accessToken;
+ }
+
+
+ public String encrypt(String data) throws Exception {
+ SecretKeySpec keySpec = new SecretKeySpec(KEY.getBytes(), ALGORITHM);
+ Cipher cipher = Cipher.getInstance(ALGORITHM);
+ cipher.init(Cipher.ENCRYPT_MODE, keySpec);
+ byte[] encryptedBytes = cipher.doFinal(data.getBytes());
+ return Base64.getEncoder().encodeToString(encryptedBytes);
+ }
+
+ public String decrypt(String encryptedData) throws Exception {
+ SecretKeySpec keySpec = new SecretKeySpec(KEY.getBytes(), ALGORITHM);
+ Cipher cipher = Cipher.getInstance(ALGORITHM);
+ cipher.init(Cipher.DECRYPT_MODE, keySpec);
+ byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedData));
+ return new String(decryptedBytes);
+ }
+
+}
diff --git a/src/main/java/com/budwk/app/base/payment/wxpay/vo/WxPayNeedParamVo.java b/src/main/java/com/budwk/app/base/payment/wxpay/vo/WxPayNeedParamVo.java
new file mode 100644
index 00000000..5131aa6d
--- /dev/null
+++ b/src/main/java/com/budwk/app/base/payment/wxpay/vo/WxPayNeedParamVo.java
@@ -0,0 +1,37 @@
+package com.budwk.app.base.payment.wxpay.vo;
+
+import com.ijpay.wxpay.model.v3.Amount;
+import com.ijpay.wxpay.model.v3.Payer;
+import lombok.Data;
+
+/**
+ * @version 1.0
+ * @Author zzr
+ * @name:WxPayNeedParamVo
+ * @Date 2025/3/7 18:43
+ * @注释
+ */
+@Data
+public class WxPayNeedParamVo {
+
+ /**
+ * 订单描述,会显示在支付界面的标题,最大长度127,必填
+ */
+ private String description;
+
+ /**
+ * 附加数据,订单的详细描述信息,不必填
+ */
+ private String attach;
+
+ /**
+ * 金额对象,对象中的 total 是金额,必填;
+ * 其余参数可不传递也可不填
+ */
+ private Amount amount;
+
+ /**
+ * 会员支付信息ID,必填
+ */
+ private String memberPaymentInfoId;
+}
diff --git a/src/main/java/com/budwk/app/sys/models/Sys_user_history.java b/src/main/java/com/budwk/app/sys/models/Sys_user_history.java
index f82977df..f0b60c5e 100644
--- a/src/main/java/com/budwk/app/sys/models/Sys_user_history.java
+++ b/src/main/java/com/budwk/app/sys/models/Sys_user_history.java
@@ -1,196 +1,61 @@
package com.budwk.app.sys.models;
-import cn.hutool.json.JSONObject;
-import com.budwk.app.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import org.nutz.lang.util.NutMap;
-import java.io.Serializable;
import java.util.Date;
import java.util.List;
@Data
+@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
@Table("sys_user_history")
@TableMeta("{'mysql-charset':'utf8mb4'}")
-@TableIndexes({@Index(name = "INDEX_SYS_USER_HISTORY_LOGINNAMAE", fields = {"loginname"}, unique = false)})
+@TableIndexes({
+ @Index(name = "INDEX_HISTORY_USER_LOGINNAMAE", fields = {"loginname"}, unique = false),
+ @Index(name = "INDEX_HISTORY_USER_UNIT", fields = {"unitId"}, unique = false),
+})
@Comment("用户历史记录")
-public class Sys_user_history extends BaseModel implements Serializable {
-
+public class Sys_user_history extends Sys_user {
@Name
+ @Column
@Comment("ID")
- @ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
+ @ColDefine(type = ColType.VARCHAR, width = 32)
private String id;
@Column
- @Comment("用户名")
- @ColDefine(type = ColType.VARCHAR, width = 120)
- private String loginname;
+ @Comment("更新时间")
+ @ColDefine(type = ColType.DATETIME)
+ private Date changeTime;
+ /**
+ * @see com.budwk.app.zhgh.staffmanage.member.constant.MemberChangeType
+ */
@Column
- @Comment("姓名")
- @ColDefine(type = ColType.VARCHAR, width = 100)
- private String username;
-
- @Column
- @Comment("性别")
- @ColDefine(type = ColType.VARCHAR, width = 20)
- private String sex;
-
- @Column
- @Comment("电子邮箱")
- @ColDefine(type = ColType.VARCHAR, width = 255)
- private String email;
-
- @Column
- @Comment("手机号码")
- @ColDefine(type = ColType.VARCHAR, width = 32)
- private String mobile;
-
- @Column
- @Comment("工会ID")
- @ColDefine(type = ColType.VARCHAR, width = 32)
- private String unionId;
-
- @Column
- @Comment("工会名称")
- @ColDefine(type = ColType.VARCHAR, width = 200)
- private String unionName;
-
- @Column
- @Comment("单位ID")
- @ColDefine(type = ColType.VARCHAR, width = 32)
- private String unitId;
-
- @Column
- @Comment("单位名称")
- @ColDefine(type = ColType.VARCHAR, width = 200)
- private String unitName;
-
- @Column
- @Comment("在职状态")
- @ColDefine(type = ColType.VARCHAR, width = 20)
- private String userState;
-
- @Column
- @Comment("人事编制")
- @ColDefine(type = ColType.VARCHAR, width = 30)
- private String preparedBy;
-
- @Column
- @Comment("人员类型")
- @ColDefine(type = ColType.VARCHAR, width = 20)
- private String personType;
-
- @Column
- @Comment("来校年月")
- @ColDefine(type = ColType.VARCHAR, width = 30)
- private String comeSchoolDate;
-
- @Column
- @Comment("出生日期")
- @ColDefine(type = ColType.DATE)
- private Date birthday;
-
- @Column
- @Comment("党政职务")
- @ColDefine(type = ColType.VARCHAR, width = 50)
- private String governmentPosition;
-
- @Column
- @Comment("技术职称")
- @ColDefine(type = ColType.VARCHAR, width = 30)
- private String technicalTitle;
-
- @Column
- @Comment("民族")
- @ColDefine(type = ColType.VARCHAR, width = 15)
- private String nation;
-
- @Column
- @Comment("国籍")
- @ColDefine(type = ColType.VARCHAR, width = 30)
- private String nationality;
-
- @Column
- @Comment("证件类型")
- @ColDefine(type = ColType.VARCHAR, width = 30)
- private String icCardType;
-
- @Column
- @Comment("证件号码")
- @ColDefine(type = ColType.VARCHAR, width = 30)
- private String idCard;
-
- @Column
- @Comment("政治面貌")
- @ColDefine(type = ColType.VARCHAR, width = 20)
- private String political;
-
- @Column
- @Comment("学历")
- @ColDefine(type = ColType.VARCHAR, width = 50)
- private String education;
-
- @Column
- @Comment("学位")
- @ColDefine(type = ColType.VARCHAR, width = 50)
- private String academicDegree;
-
- @Column
- @Comment("个人简况")
- @ColDefine(type = ColType.VARCHAR, customType = "text")
- private String personalData;
-
- @Column
- @Comment("是否双肩挑")
- @ColDefine(type = ColType.BOOLEAN)
- private Boolean manyUnit;
-
- @Column
- @Comment("是否会员")
- @ColDefine(type = ColType.AUTO)
- private Boolean member;
-
- @Column
- @Comment("是否愿意加入重大疾病爱心基金互助会")
- @ColDefine(type = ColType.BOOLEAN)
- private Boolean wantJoinLoveMutualAidAssociation;
-
- @Column
- @Comment("是否福利会员")
- @ColDefine(type = ColType.AUTO)
- private Boolean welfareMember;
-
- @Column
- @Comment("异动类型")
+ @Comment("变更类型(多种)")
@ColDefine(type = ColType.MYSQL_JSON)
private List changeTypes;
@Column
- @Comment("家庭成员")
- @ColDefine(type = ColType.MYSQL_JSON)
- private List families;
-
- @Column
- @Comment("博士后进站时间")
- @ColDefine(type = ColType.DATE)
- private Date postDoctoralJoinDate;
-
- @Column
- @Comment("异动时间")
- @ColDefine(type = ColType.DATETIME)
- private Date changeTime;
-
- @Column
- @Comment("异动来源")
- @ColDefine(type = ColType.VARCHAR, width = 20)
+ @Comment("变更来源")
+ @ColDefine(type = ColType.VARCHAR, width = 50)
private String changeOrigin;
+ @Column
+ @ColDefine(type = ColType.VARCHAR, width = 100)
+ @Comment("变更原因")
+ private String changeReason;
+
+ @Column
+ @Comment("会员变更表记录id")
+ @ColDefine(type = ColType.VARCHAR, width = 32)
+ private String recordId;
+
@Column
@Comment("详细变更记录,用于查询、列表展示")
@ColDefine(type = ColType.MYSQL_JSON)
@@ -200,19 +65,4 @@ public class Sys_user_history extends BaseModel implements Serializable {
@Comment("详细变更记录,文本类型,可用于导出")
@ColDefine(type = ColType.TEXT)
private String changeInfosStr;
-
- @Column
- @Comment("变更原因")
- @ColDefine(type = ColType.VARCHAR, width = 100)
- private String changeReason;
-
- @Column
- @Comment("会员变更记录表")
- @ColDefine(type = ColType.VARCHAR, width = 32)
- private String recordId;
-
- @Column
- @Comment("变更说明")
- @ColDefine(type = ColType.TEXT)
- private String remark;
}
diff --git a/src/main/java/com/budwk/app/sys/services/impl/SysDataUserAllUpdateServiceImpl.java b/src/main/java/com/budwk/app/sys/services/impl/SysDataUserAllUpdateServiceImpl.java
index 256dc7c0..09d26758 100644
--- a/src/main/java/com/budwk/app/sys/services/impl/SysDataUserAllUpdateServiceImpl.java
+++ b/src/main/java/com/budwk/app/sys/services/impl/SysDataUserAllUpdateServiceImpl.java
@@ -1,23 +1,13 @@
package com.budwk.app.sys.services.impl;
-import cn.afterturn.easypoi.excel.ExcelImportUtil;
-import cn.afterturn.easypoi.excel.entity.ImportParams;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.ListUtil;
-import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateField;
-import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
-import cn.hutool.core.thread.AsyncUtil;
-import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.ObjectUtil;
-import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HtmlUtil;
import com.budwk.app.base.constant.RoleConstant;
-import com.budwk.app.base.event.user.SysUserEvent;
-import com.budwk.app.base.event.user.SysUserPublisher;
import com.budwk.app.base.utils.ConditionGroupUtil;
-import com.budwk.app.base.utils.ManyAddOrRenewUtil;
import com.budwk.app.base.utils.PwdUtil;
import com.budwk.app.sys.annotation.DataCenterColumn;
import com.budwk.app.sys.models.*;
@@ -28,33 +18,23 @@ import com.budwk.app.sys.services.SysRoleService;
import com.budwk.app.sys.services.SysUserService;
import com.budwk.app.zhgh.staffmanage.member.constant.MemberChangeOrigin;
import com.budwk.app.zhgh.staffmanage.member.constant.MemberChangeType;
-import com.budwk.app.zhgh.staffmanage.member.service.MemberManageService;
-import com.budwk.app.zhgh.staffmanage.specialstaff.model.SpecialStaff;
import lombok.extern.slf4j.Slf4j;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
-import org.nutz.dao.Sqls;
-import org.nutz.dao.sql.Sql;
-import org.nutz.dao.util.cri.SqlExpressionGroup;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Lang;
import org.nutz.lang.random.R;
import org.nutz.lang.util.NutMap;
-import org.nutz.mvc.Mvcs;
import org.springframework.beans.BeanUtils;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
-import java.io.File;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.*;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
/**
diff --git a/src/main/java/com/budwk/app/sys/views/View_user.java b/src/main/java/com/budwk/app/sys/views/View_user.java
index f1570a5e..5f11ec3a 100644
--- a/src/main/java/com/budwk/app/sys/views/View_user.java
+++ b/src/main/java/com/budwk/app/sys/views/View_user.java
@@ -170,4 +170,8 @@ public class View_user {
@Column
private Date postDoctoralJoinDate;
+
+ // 福利享受截止日期
+ @Column
+ private Date welfareStopDate;
}
diff --git a/src/main/java/com/budwk/app/task/services/TaskPlatformService.java b/src/main/java/com/budwk/app/task/services/TaskPlatformService.java
index 2ce4dd75..9a05533a 100644
--- a/src/main/java/com/budwk/app/task/services/TaskPlatformService.java
+++ b/src/main/java/com/budwk/app/task/services/TaskPlatformService.java
@@ -48,4 +48,6 @@ public interface TaskPlatformService {
* @return
*/
List getCronExeTimes(String cronExpression) throws Exception;
+
+ List getCronExeTimesPlus(String cronExpression) throws Exception;
}
diff --git a/src/main/java/com/budwk/app/task/services/impl/TaskPlatformServiceImpl.java b/src/main/java/com/budwk/app/task/services/impl/TaskPlatformServiceImpl.java
index 9330d53b..d1baedaa 100644
--- a/src/main/java/com/budwk/app/task/services/impl/TaskPlatformServiceImpl.java
+++ b/src/main/java/com/budwk/app/task/services/impl/TaskPlatformServiceImpl.java
@@ -5,6 +5,7 @@ import org.nutz.integration.quartz.QuartzJob;
import org.nutz.integration.quartz.QuartzManager;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
+import org.quartz.CronExpression;
import org.quartz.JobKey;
import org.quartz.TriggerUtils;
import org.quartz.impl.triggers.CronTriggerImpl;
@@ -96,4 +97,22 @@ public class TaskPlatformServiceImpl implements TaskPlatformService {
}
return list;
}
+
+
+ @Override
+ public List getCronExeTimesPlus(String cronExpression) throws Exception {
+ // 创建一个列表来存储执行时间
+ List executionTimes = new ArrayList<>();
+ // 创建 CronTriggerImpl 对象,并设置 cron 表达式
+ CronTriggerImpl cronTrigger = new CronTriggerImpl();
+ cronTrigger.setCronExpression(new CronExpression(cronExpression));
+ // 计算未来的执行时间
+ List nextExecutionTimes = TriggerUtils.computeFireTimes(cronTrigger, null, 5);
+ // 格式化日期并添加到执行时间列表中
+ SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ for (Date date : nextExecutionTimes) {
+ executionTimes.add(dateFormat.format(date));
+ }
+ return executionTimes;
+ }
}
diff --git a/src/main/java/com/budwk/app/web/commons/base/Globals.java b/src/main/java/com/budwk/app/web/commons/base/Globals.java
index bf67c7b4..02dbf1fd 100644
--- a/src/main/java/com/budwk/app/web/commons/base/Globals.java
+++ b/src/main/java/com/budwk/app/web/commons/base/Globals.java
@@ -37,6 +37,8 @@ public class Globals {
public static String AppDomain = "http://127.0.0.1";
//文件访问域名
public static String AppFileDomain = "";
+ //学校代码
+ public static String SchoolCode = "HMC";
//系统自定义参数
public static NutMap MyConfig = NutMap.NEW();
//自定义路由
diff --git a/src/main/java/com/budwk/app/zhgh/activity/declarereimbursement/declare/controller/ActivityDeclareApplyController.java b/src/main/java/com/budwk/app/zhgh/activity/declarereimbursement/declare/controller/ActivityDeclareApplyController.java
new file mode 100644
index 00000000..535d886d
--- /dev/null
+++ b/src/main/java/com/budwk/app/zhgh/activity/declarereimbursement/declare/controller/ActivityDeclareApplyController.java
@@ -0,0 +1,11 @@
+package com.budwk.app.zhgh.activity.declarereimbursement.declare.controller;
+
+/**
+ * @version 1.0
+ * @Author zzr
+ * @name:ActivityDeclareApplyController
+ * @Date 2025/7/31 15:46
+ * @注释
+ */
+public class ActivityDeclareApplyController {
+}
diff --git a/src/main/java/com/budwk/app/zhgh/activity/declarereimbursement/declare/models/ActivityDeclareInfo.java b/src/main/java/com/budwk/app/zhgh/activity/declarereimbursement/declare/models/ActivityDeclareInfo.java
new file mode 100644
index 00000000..7aac3faa
--- /dev/null
+++ b/src/main/java/com/budwk/app/zhgh/activity/declarereimbursement/declare/models/ActivityDeclareInfo.java
@@ -0,0 +1,18 @@
+package com.budwk.app.zhgh.activity.declarereimbursement.declare.models;
+
+import com.budwk.app.base.model.BaseModel;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.nutz.dao.entity.annotation.Table;
+
+/**
+ * @version 1.0
+ * @Author zzr
+ * @name:ActivityDeclareInfo
+ * @Date 2025/7/31 15:38
+ * @注释
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class ActivityDeclareInfo extends BaseModel {
+}
diff --git a/src/main/java/com/budwk/app/zhgh/activity/declarereimbursement/reimbursement/controller/ActivityReimbursementApplyController.java b/src/main/java/com/budwk/app/zhgh/activity/declarereimbursement/reimbursement/controller/ActivityReimbursementApplyController.java
new file mode 100644
index 00000000..b36571e5
--- /dev/null
+++ b/src/main/java/com/budwk/app/zhgh/activity/declarereimbursement/reimbursement/controller/ActivityReimbursementApplyController.java
@@ -0,0 +1,11 @@
+package com.budwk.app.zhgh.activity.declarereimbursement.reimbursement.controller;
+
+/**
+ * @version 1.0
+ * @Author zzr
+ * @name:ActivityReimbursementApplyController
+ * @Date 2025/7/31 15:47
+ * @注释
+ */
+public class ActivityReimbursementApplyController {
+}
diff --git a/src/main/java/com/budwk/app/zhgh/activity/declarereimbursement/reimbursement/models/ActivityReimbursementInfo.java b/src/main/java/com/budwk/app/zhgh/activity/declarereimbursement/reimbursement/models/ActivityReimbursementInfo.java
new file mode 100644
index 00000000..bfed79bb
--- /dev/null
+++ b/src/main/java/com/budwk/app/zhgh/activity/declarereimbursement/reimbursement/models/ActivityReimbursementInfo.java
@@ -0,0 +1,18 @@
+package com.budwk.app.zhgh.activity.declarereimbursement.reimbursement.models;
+
+import com.budwk.app.base.model.BaseModel;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.nutz.dao.entity.annotation.Table;
+
+/**
+ * @version 1.0
+ * @Author zzr
+ * @name:ActivityReimbursement
+ * @Date 2025/7/31 15:43
+ * @注释
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class ActivityReimbursementInfo extends BaseModel {
+}
diff --git a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/contants/Cascader.java b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/contants/Cascader.java
new file mode 100644
index 00000000..2e22f541
--- /dev/null
+++ b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/contants/Cascader.java
@@ -0,0 +1,16 @@
+package com.budwk.app.zhgh.activity.fitnesswalk.contants;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 获取活动的树形结构
+ */
+@Data
+public class Cascader {
+
+ private String value;
+ private String label;
+ private List children;
+}
diff --git a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/contants/FitnessWalkMode.java b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/contants/FitnessWalkMode.java
new file mode 100644
index 00000000..ad699815
--- /dev/null
+++ b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/contants/FitnessWalkMode.java
@@ -0,0 +1,18 @@
+package com.budwk.app.zhgh.activity.fitnesswalk.contants;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@Getter
+@AllArgsConstructor
+public enum FitnessWalkMode {
+
+ PUNCH("punch", "打卡"),
+ STEP_COUNT("stepCount", "计步"),
+ GPS("gps", "GPS");
+
+
+ private final String value;
+ private final String desc;
+
+}
diff --git a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkActivityBindingRecordController.java b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkActivityBindingRecordController.java
new file mode 100644
index 00000000..8bb51c65
--- /dev/null
+++ b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkActivityBindingRecordController.java
@@ -0,0 +1,133 @@
+package com.budwk.app.zhgh.activity.fitnesswalk.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import com.budwk.app.base.annotation.SLog;
+import com.budwk.app.base.constant.RoleConstant;
+import com.budwk.app.base.page.Pagination;
+import com.budwk.app.base.result.Result;
+import com.budwk.app.web.commons.auth.utils.AuthUtil;
+import com.budwk.app.web.commons.auth.utils.SecurityUtil;
+import com.budwk.app.zhgh.activity.fitnesswalk.utils.WeAppCloudUtil;
+import io.swagger.annotations.ApiOperation;
+import org.nutz.ioc.loader.annotation.Inject;
+import org.nutz.ioc.loader.annotation.IocBean;
+import org.nutz.json.Json;
+import org.nutz.lang.Strings;
+import org.nutz.lang.util.NutMap;
+import org.nutz.mvc.annotation.At;
+import org.nutz.mvc.annotation.Ok;
+import org.nutz.mvc.annotation.Param;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@IocBean
+@Ok("json")
+@ApiOperation("健步走-同步绑定记录")
+@At("/platform/fitnessWalk/bindingRecord")
+public class FitnessWalkActivityBindingRecordController {
+
+ @Inject
+ private WeAppCloudUtil weAppCloudUtil;
+
+ @At("")
+ @Ok("beetl:/platform/zhgh/activity/fitnesswalk/bindingRecord.html")
+ @SaCheckPermission("fitnessWalk.bindingRecord")
+ public void index() {
+ }
+
+ /**
+ * 获取用户绑定记录
+ * @param keyWord
+ * @param unionId
+ * @param searchName
+ * @param searchKeyword
+ * @param pageNumber
+ * @param pageSize
+ * @param pageOrderName
+ * @param pageOrderBy
+ * @return
+ * @throws IOException
+ */
+ @At
+ @ApiOperation("获取用户绑定记录")
+ @SaCheckPermission("fitnessWalk.bindingRecord")
+ public Result pageData(@Param("keyWord") String keyWord,
+ @Param("unionId") String unionId,
+ @Param("searchName") String searchName,
+ @Param("searchKeyword") String searchKeyword,
+ @Param("pageNumber") int pageNumber, @Param("pageSize") int pageSize,
+ @Param("pageOrderName") String pageOrderName,
+ @Param("pageOrderBy") String pageOrderBy) {
+ int skip = (pageNumber == 1 ? 0 : (pageNumber - 1)) * pageSize;
+
+ StringBuilder sql = new StringBuilder("db.collection('login_record')");
+ if (StrUtil.isNotBlank(keyWord)) {
+ sql.append(".where(");
+ sql.append("_.or([");
+ sql.append("{loginname:{$regex:'").append(keyWord).append("',$options:'i'}},");
+ sql.append("{username:{$regex:'").append(keyWord).append("',$options:'i'}}");
+ sql.append("])");
+ sql.append(")");
+ }
+ if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
+ sql.append(".where({'data.unionid':'").append(SecurityUtil.getUnionId()).append("'})");
+ } else {
+ if (Strings.isNotBlank(unionId)) {
+ sql.append(".where({'data.unionid':'").append(unionId).append("'})");
+ }
+ }
+ sql.append(".field({ data: false })");
+ sql.append(".skip(").append(skip).append(")");
+ sql.append(".limit(").append(pageSize).append(")");
+ sql.append(".get()");
+
+ try {
+ JSONObject jsonObject = weAppCloudUtil.request(WeAppCloudUtil.CRUD.QUERY, sql.toString());
+ List data = jsonObject.getJSONArray("data").stream()
+ .map(v -> JSONUtil.toBean((String) v, NutMap.class))
+ .collect(Collectors.toList());
+ JSONObject pager = jsonObject.getJSONObject("pager");
+ Pagination pagination = new Pagination(pageNumber, pageSize, pager.getInt("Total"), data);
+ return Result.success().addData(pagination);
+ } catch (Exception e) {
+ return Result.error(e.getMessage());
+ }
+ }
+
+
+ /**
+ * 删除绑定记录
+ * @param ids
+ * @return
+ */
+ @At
+ @ApiOperation("删除绑定记录")
+ @SLog(tag = "健步走-同步绑定记录", msg = "删除绑定记录")
+ @SaCheckPermission("fitnessWalk.bindingRecord")
+ public Result delete(String[] ids) {
+ String sql = "db.collection('login_record').where({_id:_.in(" + Json.toJson(ids) + ")}).remove()";
+ weAppCloudUtil.request(WeAppCloudUtil.CRUD.DELETE, sql);
+ return Result.success();
+ }
+
+ /**
+ * 删除所有绑定记录
+ * @return
+ */
+ @At
+ @ApiOperation("删除所有绑定记录")
+ @SLog(tag = "健步走-同步绑定记录", msg = "删除所有绑定记录")
+ @SaCheckPermission("fitnessWalk.bindingRecord")
+ public Result deleteAll() {
+ String sql = "db.collection('login_record').where({_id: _.neq('0')}).remove()";
+ weAppCloudUtil.request(WeAppCloudUtil.CRUD.DELETE, sql);
+ return Result.success();
+ }
+
+
+}
diff --git a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkActivityManageController.java b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkActivityManageController.java
new file mode 100644
index 00000000..81a3cd25
--- /dev/null
+++ b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkActivityManageController.java
@@ -0,0 +1,337 @@
+package com.budwk.app.zhgh.activity.fitnesswalk.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.hutool.core.date.DateTime;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import com.budwk.app.base.annotation.SLog;
+import com.budwk.app.base.constant.RoleConstant;
+import com.budwk.app.base.page.Pagination;
+import com.budwk.app.base.result.Result;
+import com.budwk.app.sys.models.Sys_task;
+import com.budwk.app.sys.services.SysTaskService;
+import com.budwk.app.sys.services.SysUserService;
+import com.budwk.app.task.services.TaskPlatformService;
+import com.budwk.app.web.commons.auth.utils.AuthUtil;
+import com.budwk.app.web.commons.auth.utils.SecurityUtil;
+import com.budwk.app.zhgh.activity.fitnesswalk.model.FitnessWalkActivity;
+import com.budwk.app.zhgh.activity.fitnesswalk.service.FitnessWalkCommonService;
+import com.budwk.app.zhgh.activity.fitnesswalk.utils.WeAppCloudUtil;
+import io.swagger.annotations.ApiOperation;
+import org.nutz.dao.Cnd;
+import org.nutz.ioc.loader.annotation.Inject;
+import org.nutz.ioc.loader.annotation.IocBean;
+import org.nutz.json.Json;
+import org.nutz.lang.Lang;
+import org.nutz.lang.Strings;
+import org.nutz.lang.util.NutMap;
+import org.nutz.mvc.annotation.AdaptBy;
+import org.nutz.mvc.annotation.At;
+import org.nutz.mvc.annotation.Ok;
+import org.nutz.mvc.annotation.Param;
+import org.nutz.mvc.upload.TempFile;
+import org.nutz.mvc.upload.UploadAdaptor;
+
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.util.stream.Collectors;
+
+
+@IocBean
+@Ok("json")
+@ApiOperation("健步走-活动管理")
+@At("/platform/fitnessWalk/activityManage")
+public class FitnessWalkActivityManageController {
+ @Inject
+ private WeAppCloudUtil weAppCloudUtil;
+ @Inject
+ private SysTaskService sysTaskService;
+ @Inject
+ private SysUserService sysUserService;
+ @Inject
+ private TaskPlatformService taskPlatformService;
+ @Inject
+ private FitnessWalkCommonService fitnessWalkCommonService;
+
+ @At("")
+ @Ok("beetl:/platform/zhgh/activity/fitnesswalk/activityManage.html")
+ @SaCheckPermission("fitnessWalk.activityManage")
+ public void index() {
+ }
+
+ @At
+ @ApiOperation("获取活动数据")
+ @SaCheckPermission("fitnessWalk.activityManage")
+ public Result pageData(@Param("year") Integer year, @Param("unionId") String unionId, @Param("searchName") String searchName,
+ @Param("searchKeyword") String searchKeyword, @Param("pageNumber") int pageNumber,
+ @Param("pageSize") int pageSize, @Param("pageOrderName") String pageOrderName,
+ @Param("pageOrderBy") String pageOrderBy) {
+
+ int skip = (pageNumber == 1 ? 0 : (pageNumber - 1)) * pageSize;
+
+ StringBuilder sql = new StringBuilder("db.collection('activity')");
+ if (Strings.isNotBlank(searchName) && Strings.isNotBlank(searchKeyword)) {
+ sql.append("where(");
+ sql.append("_.or([");
+ sql.append("{name:{$regex:'").append(searchKeyword).append("',$options:'i'}}");
+ sql.append("{tag:{$regex:'").append(searchKeyword).append("',$options:'i'}}");
+ sql.append("])");
+ sql.append(")");
+ }
+ if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
+ sql.append(".where({unionId:'").append(SecurityUtil.getUnionId()).append("'})");
+ } else if (StrUtil.isNotBlank(unionId)) {
+ sql.append(".where({unionId:'").append(unionId).append("'})");
+ }
+ sql.append(".skip(").append(skip).append(")");
+ sql.append(".limit(").append(pageSize).append(")");
+ sql.append(".get()");
+ try {
+ JSONObject jsonObject = weAppCloudUtil.request(WeAppCloudUtil.CRUD.QUERY, sql.toString());
+ List data = jsonObject.getJSONArray("data").stream()
+ .map(v -> JSONUtil.toBean((String) v, FitnessWalkActivity.class))
+ .collect(Collectors.toList());
+ JSONObject pager = jsonObject.getJSONObject("pager");
+ Pagination pagination = new Pagination(pageNumber, pageSize, pager.getInt("Total"), data);
+ return Result.success().addData(pagination);
+ } catch (Exception e) {
+ return Result.error(e.getMessage());
+ }
+
+ }
+
+
+ @At
+ @ApiOperation("添加活动")
+ @SLog(tag = "健步走-活动管理", msg = "添加活动")
+ @SaCheckPermission("fitnessWalk.activityManage")
+ @AdaptBy(type = UploadAdaptor.class, args = {"ioc:fileUpload"})
+ public Result doAdd(@Param("fitnessWalkActivity") FitnessWalkActivity fitnessWalkActivity,
+ @Param("file") TempFile tempFile,
+ @Param("stepFiles") TempFile[] stepFiles,
+ @Param("certificateFile") TempFile certificateTempFile) {
+ try {
+ //设置时间
+ if ("punch".equals(fitnessWalkActivity.getActivityModel())) {
+ fitnessWalkActivity.setApplyStartTime(fitnessWalkActivity.getApplyTime()[0]);
+ fitnessWalkActivity.setApplyEndTime(fitnessWalkActivity.getApplyTime()[1]);
+ }
+ fitnessWalkActivity.setStartTime(fitnessWalkActivity.getTime()[0]);
+ fitnessWalkActivity.setEndTime(fitnessWalkActivity.getTime()[1]);
+
+ if (tempFile != null) {
+ String fileId = weAppCloudUtil.uploadFile("activity/", tempFile.getFile());
+ fitnessWalkActivity.setCover(fileId);
+ }
+
+ if (Lang.isNotEmpty(stepFiles)) {
+ List stepFileIdList = new ArrayList<>();
+ for (TempFile file : stepFiles) {
+ String fileId = weAppCloudUtil.uploadFile("activity/", file.getFile());
+ stepFileIdList.add(fileId);
+ }
+ fitnessWalkActivity.setRandomPics(stepFileIdList);
+ }
+
+ if (certificateTempFile != null) {
+ String fileId = weAppCloudUtil.uploadFile("activity/", certificateTempFile.getFile());
+ fitnessWalkActivity.setCompletionCertificateCover(fileId);
+ }
+
+ if (AuthUtil.hasRoleOr(RoleConstant.BRANCH_UNION_CHAIRMAN.name(), RoleConstant.BRANCH_UNION_OPERATOR.name(), RoleConstant.BRANCH_UNION_ADMIN.name())
+ && !AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
+ fitnessWalkActivity.setUnionId(SecurityUtil.getUnionId());
+ } else {
+ fitnessWalkActivity.setUnionId("");
+ }
+
+ fitnessWalkActivity.setNote(fitnessWalkActivity.getNote().replace("\"", "'"));
+ StringBuilder sql = new StringBuilder();
+ sql.append("db.collection('activity').add({data:").append(Json.toJson(fitnessWalkActivity)).append("})");
+
+ try {
+ JSONObject jsonObject = weAppCloudUtil.request(WeAppCloudUtil.CRUD.INSERT, sql.toString());
+ fitnessWalkCommonService.addLotteryTask(fitnessWalkActivity);
+ fitnessWalkCommonService.addOrEditDoSaveRedis(fitnessWalkActivity);
+ return Result.success();
+ } catch (Exception e) {
+ return Result.error(e.getMessage());
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ return Result.error();
+ }
+ }
+
+
+ @At
+ @Ok("json:full")
+ @ApiOperation("根据Id获取健步走活动")
+ @SaCheckPermission("fitnessWalk.activityManage")
+ public Result findOne(String id) {
+ return Result.success(fitnessWalkCommonService.getActivity(id));
+ }
+
+ @At
+ @Ok("json")
+ @ApiOperation("获取附件")
+ @SaCheckPermission("fitnessWalk.activityManage")
+ public Result getFile(String pic) {
+ try {
+ NutMap res = NutMap.NEW();
+ String imgurl = weAppCloudUtil.httpFile(pic);
+ HashMap imgmap = new HashMap<>();
+ imgmap.put("status", "success");
+ imgmap.put("name", imgurl.substring(imgurl.lastIndexOf("/") + 1));
+ imgmap.put("id", imgurl);
+ imgmap.put("url", imgurl);
+ res.setv("img", imgmap);
+ return Result.success().addData(res);
+ } catch (Exception e) {
+ return Result.error();
+ }
+ }
+
+
+ @At
+ @ApiOperation("修改健步走活动")
+ @SaCheckPermission("fitnessWalk.activityManage")
+ @AdaptBy(type = UploadAdaptor.class, args = {"ioc:fileUpload"})
+ public Result doEdit(@Param("fitnessWalkActivity") FitnessWalkActivity fitnessWalkActivity,
+ @Param("file") TempFile tempFile,
+ @Param("stepFiles") TempFile[] stepFiles,
+ @Param("certificateFile") TempFile certificateTempFile) {
+ //设置时间
+ if ("punch".equals(fitnessWalkActivity.getActivityModel())) {
+ fitnessWalkActivity.setApplyStartTime(fitnessWalkActivity.getApplyTime()[0]);
+ fitnessWalkActivity.setApplyEndTime(fitnessWalkActivity.getApplyTime()[1]);
+ }
+ fitnessWalkActivity.setStartTime(fitnessWalkActivity.getTime()[0]);
+ fitnessWalkActivity.setEndTime(fitnessWalkActivity.getTime()[1]);
+
+ if (tempFile != null) {
+ String fileId = weAppCloudUtil.uploadFile("activity/", tempFile.getFile());
+ fitnessWalkActivity.setCover(fileId);
+ }
+
+ if (Lang.isNotEmpty(stepFiles)) {
+ List stepFileIdList = new ArrayList<>();
+ for (TempFile file : stepFiles) {
+ String fileId = weAppCloudUtil.uploadFile("activity/", file.getFile());
+ stepFileIdList.add(fileId);
+ }
+ fitnessWalkActivity.setRandomPics(stepFileIdList);
+ }
+
+ if (certificateTempFile != null) {
+ String fileId = weAppCloudUtil.uploadFile("activity/", certificateTempFile.getFile());
+ fitnessWalkActivity.setCompletionCertificateCover(fileId);
+ }
+
+
+ if (AuthUtil.hasRoleOr(RoleConstant.BRANCH_UNION_CHAIRMAN.name(), RoleConstant.BRANCH_UNION_OPERATOR.name(), RoleConstant.BRANCH_UNION_ADMIN.name())
+ && !AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
+ fitnessWalkActivity.setUnionId(SecurityUtil.getUnionId());
+ } else {
+ fitnessWalkActivity.setUnionId("");
+ }
+
+ fitnessWalkActivity.setNote(fitnessWalkActivity.getNote().replace("\"", "'"));
+
+ StringBuilder sql = new StringBuilder();
+ sql.append("db.collection('activity').doc('")
+ .append(fitnessWalkActivity.get_id())
+ .append("').update({data:").append(Json.toJson(fitnessWalkActivity)).append("})");
+
+ try {
+ JSONObject jsonObject = weAppCloudUtil.request(WeAppCloudUtil.CRUD.UPDATE, sql.toString());
+ fitnessWalkCommonService.addLotteryTask(fitnessWalkActivity);
+ fitnessWalkCommonService.addOrEditDoSaveRedis(fitnessWalkActivity);
+ return Result.success();
+ } catch (Exception e) {
+ return Result.error(e.getMessage());
+ }
+ }
+
+
+ @At
+ @ApiOperation("删除健步走活动")
+ @SLog(tag = "健步走-活动管理", msg = "删除活动")
+ @SaCheckPermission("fitnessWalk.activityManage")
+ public Result delete(String id) {
+ try {
+ weAppCloudUtil.request(WeAppCloudUtil.CRUD.DELETE, "db.collection('activity').doc('" + id + "').remove()");
+ weAppCloudUtil.request(WeAppCloudUtil.CRUD.DELETE, "db.collection('register').where({activity_id:'" + id + "'}).remove()");
+ weAppCloudUtil.request(WeAppCloudUtil.CRUD.DELETE, "db.collection('sign').where({activity_id:'" + id + "'}).remove()");
+ weAppCloudUtil.request(WeAppCloudUtil.CRUD.DELETE, "db.collection('gift_voucher').where({activity_id:'" + id + "'}).remove()");
+
+ List sys_tasks = sysTaskService.query(Cnd.where("note", "=", id));
+ if (sys_tasks != null) {
+ sys_tasks.forEach(v -> {
+ taskPlatformService.delete(v.getId(), v.getId());
+ sysTaskService.delete(v.getId());
+ });
+
+ }
+ return Result.success();
+ } catch (Exception e) {
+ return Result.error();
+ }
+ }
+
+ /**
+ * 健身走健步模式通知提醒
+ *
+ * @param map
+ */
+ private void addNotifyTask(NutMap map) {
+ if (map.getBoolean("sendNotify")) {
+ String id = map.getString("_id");
+ String name = map.getString("name");
+ String groupId = map.getString("groupId");
+
+ //提醒频率
+ int notifyGap = 10;
+
+ List sys_tasks = sysTaskService.query(Cnd.where("note", "=", id));
+ if (Lang.isNotEmpty(sys_tasks)) {
+ sys_tasks.forEach(v -> {
+ taskPlatformService.delete(v.getId(), v.getId());
+ sysTaskService.delete(v.getId());
+ });
+ }
+
+ DateTime startTime = DateUtil.date(map.getLong("start_time"));
+ DateTime endTime = DateUtil.date(map.getLong("end_time"));
+
+ Set result = new HashSet<>();
+ DateTime offsetDay = DateUtil.offsetDay(startTime, notifyGap);
+ while (offsetDay.isBefore(endTime) && offsetDay.isAfter(new Date())) {
+ result.add(offsetDay);
+ offsetDay = DateUtil.offsetDay(offsetDay, notifyGap);
+ }
+ //活动结束前一天再添加一个日期提醒
+ result.add(DateUtil.offsetDay(endTime, -1));
+
+ for (DateTime dateTime : result) {
+ Sys_task sys_task = new Sys_task();
+ sys_task.setName(name);
+ sys_task.setNote(id);
+ sys_task.setJobClass("io.v.nutz.task.job.walking.WalkingNotifyJob");
+ sys_task.setData(Json.toJson(Map.of("groupId", groupId)));
+ sys_task.setDisabled(false);
+
+ String dateFormat = "ss mm HH dd MM ? yyyy";
+ SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
+ String corn = sdf.format(dateTime);
+ sys_task.setCron(corn);
+
+ sysTaskService.insert(sys_task);
+
+ taskPlatformService.add(sys_task.getId(), sys_task.getId(), sys_task.getJobClass(), sys_task.getCron(), sys_task.getNote(), sys_task.getData());
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkCommonController.java b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkCommonController.java
new file mode 100644
index 00000000..ae74afdf
--- /dev/null
+++ b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkCommonController.java
@@ -0,0 +1,52 @@
+package com.budwk.app.zhgh.activity.fitnesswalk.controller;
+
+import org.nutz.ioc.loader.annotation.IocBean;
+import org.nutz.mvc.annotation.At;
+import org.nutz.mvc.annotation.Ok;
+
+/**
+ * 健身走公共答题
+ */
+@IocBean
+@At("/platform/fitnessWalk/common")
+@Ok("json:full")
+public class FitnessWalkCommonController {
+
+// @Inject
+// private QuestionService questionService;
+//
+// @Inject
+// private QuestionIssueService questionIssueService;
+//
+// @Inject
+// private QuestionWorService questionWorService;
+//
+// @Inject
+// private QuestionReplyService questionReplyService;
+//
+// @At
+// public Object getQuestion() {
+// return questionService.query(Cnd.where("qflag", "=", 2));
+// }
+//
+// @At
+// public Object getIssueById(String id) {
+// List questionIssues = questionIssueService.query(Cnd.where("iqid", "=", id).asc("ipxbh"));
+// questionIssues.forEach(issue -> {
+// issue.setWors(questionWorService.query(Cnd.where("wiid", "=", issue.getIid()).asc("wpxbh")));
+// });
+// return questionIssues;
+// }
+//
+// @At
+// public Object saveAnswer(@Param("reply") Question_reply[] replys) {
+// for (Question_reply reply : replys) {
+// reply.setSave(false);
+// reply.setRidentity(R.UU32());
+// reply.setRtime(DateUtil.getDate());
+// questionReplyService.insert(reply);
+// }
+// return null;
+// }
+
+}
diff --git a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkLoginController.java b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkLoginController.java
new file mode 100644
index 00000000..0aa14a13
--- /dev/null
+++ b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkLoginController.java
@@ -0,0 +1,75 @@
+package com.budwk.app.zhgh.activity.fitnesswalk.controller;
+
+import cn.hutool.core.util.StrUtil;
+import com.budwk.app.base.result.Result;
+import com.budwk.app.sys.services.SysUserService;
+import org.nutz.dao.Cnd;
+import org.nutz.dao.Sqls;
+import org.nutz.dao.sql.Sql;
+import org.nutz.ioc.loader.annotation.Inject;
+import org.nutz.ioc.loader.annotation.IocBean;
+import org.nutz.lang.util.NutMap;
+import org.nutz.mvc.annotation.At;
+import org.nutz.mvc.annotation.Ok;
+
+/**
+ * @author: Aaron
+ * @create: 2020-09-11 13:50
+ * @description: 小程序登录
+ **/
+@At("/platform/fitnessWalk/login")
+@Ok("json:full")
+@IocBean
+public class FitnessWalkLoginController {
+
+ @Inject
+ private SysUserService sysUserService;
+
+ @At("/")
+ public Object index(String username, String loginname) {
+ try {
+ Sql sql = Sqls.create("""
+ SELECT
+ u.id userid,
+ u.username,
+ u.loginname,
+ u.sex,
+ u.mobile,
+ u.birthday,
+ unit.`name` unitname,
+ u.unitid,
+ u.member,
+ unit.unionid,
+ un.unionname,
+ group_concat(sr.`code`) roles
+ FROM
+ sys_user u
+ LEFT JOIN sys_unit unit ON u.unitid = unit.id
+ LEFT JOIN sys_union un ON unit.unionid = un.id
+ LEFT JOIN sys_user_role sur ON sur.userId = u.id
+ LEFT JOIN sys_role sr ON sr.id = sur.roleId
+ $condition
+ """).setParam("loginname", loginname).setParam("username", username);
+
+ Cnd cnd = Cnd.NEW();
+ cnd.and("LOWER(u.loginname)", "=", loginname.trim().toLowerCase());
+ cnd.and("LOWER(u.username)", "=", username.trim().toLowerCase());
+ sql.setCondition(cnd);
+ sql.setCallback(Sqls.callback.map());
+ sysUserService.dao().execute(sql);
+ NutMap user = (NutMap) sql.getResult();
+ if (StrUtil.isBlank(user.getString("userid"))) {
+ return Result.error().addMsg("系统查询不到您的信息,请与工会管理员联系!");
+ }
+// if(user.getInt("member",0)==0){
+// return Result.error().addMsg("您没有权限参加本次健步行,请与工会管理员联系!");
+// }
+ /*if (user.getInt("member", 0) != 1 && !user.getString("sex", "").equals("女")) {
+ return Result.error().addMsg("您没有权限参加本次健步行,请与工会管理员联系!");
+ }*/
+ return Result.success().addData(user);
+ } catch (Exception e) {
+ return Result.error();
+ }
+ }
+}
diff --git a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkPunchLotteryController.java b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkPunchLotteryController.java
new file mode 100644
index 00000000..d1fbebd6
--- /dev/null
+++ b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkPunchLotteryController.java
@@ -0,0 +1,258 @@
+package com.budwk.app.zhgh.activity.fitnesswalk.controller;
+
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import com.budwk.app.base.result.Result;
+import com.budwk.app.sys.models.Sys_user;
+import com.budwk.app.zhgh.activity.fitnesswalk.model.FitnessWalkActivity;
+import com.budwk.app.zhgh.activity.fitnesswalk.model.FitnessWalkPunchLottery;
+import com.budwk.app.zhgh.activity.fitnesswalk.service.FitnessWalkCommonService;
+import com.budwk.app.zhgh.activity.fitnesswalk.utils.WeAppCloudUtil;
+import io.swagger.annotations.ApiOperation;
+import org.nutz.dao.Chain;
+import org.nutz.dao.Cnd;
+import org.nutz.integration.jedis.RedisService;
+import org.nutz.ioc.loader.annotation.Inject;
+import org.nutz.ioc.loader.annotation.IocBean;
+import org.nutz.lang.Lang;
+import org.nutz.lang.random.R;
+import org.nutz.mvc.annotation.At;
+import org.nutz.mvc.annotation.Ok;
+
+import java.util.*;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.stream.Collectors;
+
+/**
+ * 打卡抽奖
+ */
+@IocBean
+@Ok("json")
+@ApiOperation("健步走打卡抽奖")
+@At("/platform/fitnessWalk/punchLottery")
+public class FitnessWalkPunchLotteryController {
+
+ private final ReentrantLock lock = new ReentrantLock(true);
+
+ @Inject
+ private FitnessWalkCommonService fitnessWalkCommonService;
+
+ @Inject
+ private WeAppCloudUtil weAppCloudUtil;
+
+ @Inject
+ private RedisService redisService;
+
+ @At("")
+ @Ok("beetl:/platform/zhgh/activity/fitnesswalk/punchLottery.html")
+ public void index() {
+ }
+
+
+ /**
+ * 打卡完成之后抽奖
+ *
+ * @param activityId 活动Id
+ * @param userId 用户Id
+ * @return
+ */
+ @At
+ @ApiOperation("打卡模式的抽奖")
+ public Result judgeWinningToPunch(String activityId, String userId) {
+ try {
+ if (StrUtil.isBlank(activityId) && StrUtil.isBlank(userId)) {
+ return Result.error("参数错误");
+ }
+
+ lock.lock();
+ int winingCount = fitnessWalkCommonService.dao().count(FitnessWalkPunchLottery.class, Cnd.where("activityId", "=", activityId).and("userId", "=", userId));
+ if (winingCount > 0) {
+ return null;
+ }
+
+ // 把中奖的用户直接存到redis里面去
+ String redisKey = this.getClass().getName() + "#judgeWinningToPunch#activityid:" + activityId + "#PRIZELIST:";
+ String getPrizeIndexesKey = this.getClass().getName() + "#judgeWinningToPunch#activityid:" + activityId + "#GETPRIZEINDEXES:";
+
+// redisService.del(getPrizeIndexesKey);
+ // 获取奖品列表
+ List punchLotteryPrizes = new ArrayList<>();
+ String punchLotteryPrizesJson = redisService.get(redisKey);
+ if (StrUtil.isNotBlank(punchLotteryPrizesJson)) {
+ punchLotteryPrizes = JSONUtil.toList(punchLotteryPrizesJson, FitnessWalkActivity.PunchLotteryPrize.class);
+ } else {
+ JSONObject prizeJsonObject = weAppCloudUtil.request(WeAppCloudUtil.CRUD.QUERY, "db.collection('activity').doc('%s').field({punchLotteryPrizes: true}).get()".formatted(activityId));
+ List prizeList = JSONUtil.parseObj((String) prizeJsonObject.getJSONArray("data").get(0)).getJSONArray("punchLotteryPrizes").toList(FitnessWalkActivity.PunchLotteryPrize.class);
+ redisService.set(redisKey, JSONUtil.toJsonStr(prizeList));
+ punchLotteryPrizes = prizeList;
+ }
+
+ List winningUserIndexList = null;
+ //奖项,抽取多少个
+ int sum = punchLotteryPrizes.stream().mapToInt(v -> Integer.parseInt(v.getNum())).sum();
+ //判断是否有key,可以获得奖项的下标数组
+ if (!redisService.exists(getPrizeIndexesKey)) {
+ JSONObject jsonObject = weAppCloudUtil.request(WeAppCloudUtil.CRUD.QUERY, "db.collection('register').where({activity_id:'%s'}).count()".formatted(activityId));
+ JSONObject pager = jsonObject.getJSONObject("pager");
+ String total = pager.getStr("Total");
+ //报名人数的一半,从这里的人中取数(最多抽取人数)
+ Integer lotteryNum = (int) Math.ceil(Double.parseDouble(total) / 2);
+ winningUserIndexList = generateRandomNumber(1, lotteryNum, sum);
+ redisService.set(getPrizeIndexesKey, JSONUtil.toJsonStr(winningUserIndexList));
+ } else {
+ String data = redisService.get(getPrizeIndexesKey);
+ winningUserIndexList = JSONUtil.toList(data, Integer.class);
+ }
+
+ List punchLotteryList = fitnessWalkCommonService.dao().query(FitnessWalkPunchLottery.class, Cnd.where("activityId", "=", activityId));
+
+ Sys_user user = fitnessWalkCommonService.dao().fetch(Sys_user.class, Cnd.where("id", "=", userId));
+
+ FitnessWalkPunchLottery punchLottery = new FitnessWalkPunchLottery();
+ punchLottery.setId(R.UU32());
+ punchLottery.setActivityId(activityId);
+ punchLottery.setUserId(userId);
+ punchLottery.setLoginName(user.getLoginname());
+ punchLottery.setUserName(user.getUsername());
+ punchLottery.setWinDate(new Date());
+ punchLottery.setIsRead(false);
+ punchLottery.setIsWin(false);
+ punchLottery.setIsExchange(false);
+
+ if (winningUserIndexList.contains((punchLotteryList.size()))) {
+
+ //奖项map
+ Map prizeMap = punchLotteryPrizes.stream().collect(Collectors.toMap(FitnessWalkActivity.PunchLotteryPrize::getValue, FitnessWalkActivity.PunchLotteryPrize::getName));
+
+ //奖项池
+ List prizeIdList = new ArrayList<>();
+
+ punchLotteryPrizes.forEach(v -> {
+ for (int i = 0; i < Integer.parseInt(v.getNum()); i++) {
+ prizeIdList.add(v.getValue());
+ }
+ });
+
+ //已经中奖的奖项ID集合
+ List winPrizeIdList = punchLotteryList.stream().filter(FitnessWalkPunchLottery::getIsWin).map(FitnessWalkPunchLottery::getPrizeId).toList();
+ winPrizeIdList.forEach(prizeIdList::remove);
+
+ Random random = new Random();
+ if (Lang.isNotEmpty(prizeIdList.size())) {
+ int index = random.nextInt(prizeIdList.size());
+
+ // 获取随机元素
+ String winingPrizeId = prizeIdList.get(index);
+ String winingPrizeName = prizeMap.get(winingPrizeId);
+ punchLottery.setPrizeName(winingPrizeName);
+ punchLottery.setPrizeId(winingPrizeId);
+ punchLottery.setIsWin(true);
+
+ }
+
+ }
+
+ fitnessWalkCommonService.dao().insert(punchLottery);
+ if (punchLottery.getIsWin()) {
+ return Result.success().addData(punchLottery.getPrizeId());
+ } else {
+ return Result.success().addData(null);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ } finally {
+ if (lock.isLocked()) {
+ lock.unlock();
+ }
+ }
+ }
+
+ @At
+ @ApiOperation("获取中奖记录")
+ public Result getLotteryRecord(String activityId, String userId) {
+ List list = fitnessWalkCommonService.dao().query(FitnessWalkPunchLottery.class, Cnd.where("activityId", "=", activityId)
+ .and("userId", "=", userId));
+ return Result.success(list);
+ }
+
+
+ /**
+ * 兑奖
+ *
+ * @param id
+ * @return
+ */
+ @At
+ @ApiOperation("兑奖")
+ public Result doExchange(String id) {
+
+ int count = fitnessWalkCommonService.dao().count(FitnessWalkPunchLottery.class, Cnd.where("id", "=", id).and("isExchange", "=", 1));
+ //返回2 "该二维码已兑奖!"
+ if (count > 0) {
+ return Result.success(2);
+ }
+
+ int update = fitnessWalkCommonService.dao().update(FitnessWalkPunchLottery.class,
+ Chain.make("isExchange", 1).add("exchangeDate", new Date()),
+ Cnd.where("id", "=", id));
+ return update > 0 ? Result.success() : Result.error();
+ }
+
+
+ /**
+ * 改为已读
+ *
+ * @param activityId
+ * @param userId
+ * @return
+ */
+ @At
+ @ApiOperation("改为已读")
+ public Result doReadLottery(String activityId, String userId) {
+ fitnessWalkCommonService.dao().update(FitnessWalkPunchLottery.class, Chain.make("isRead", 1),
+ Cnd.where("userId", "=", userId).and("activityId", "=", activityId));
+ return Result.success();
+ }
+
+
+ /**
+ * 查看获奖人员
+ *
+ * @return
+ */
+ @At
+ @ApiOperation("查看获奖人员")
+ public Result getPunchWining(String activityId) {
+ List punchLotteryList = fitnessWalkCommonService.dao().query(FitnessWalkPunchLottery.class,
+ Cnd.where("isWin", "=", 1).and("activityId", "=", activityId));
+ return Result.success(punchLotteryList);
+ }
+
+
+ /**
+ * 抽奖方法的工具类,生成随机数,该随机数为中奖人
+ *
+ * @param minNum 最小值
+ * @param maxNum 最多抽取多少人
+ * @param count 抽几个
+ * @return
+ */
+ public List generateRandomNumber(Integer minNum, Integer maxNum, Integer count) {
+ Set randomNumbers = new HashSet<>();
+ Random rand = new Random();
+ //如果是 抽奖人数小于了 奖品数量,会陷入死循环
+ if (maxNum <= count) {
+ for (int i = 0; i <= maxNum; i++) {
+ randomNumbers.add(i + 1);
+ }
+ } else {
+ while (randomNumbers.size() < count) {
+ int randomNum = rand.nextInt(maxNum - minNum + 1) + minNum;
+ randomNumbers.add(randomNum);
+ }
+ }
+ return new ArrayList<>(randomNumbers);
+ }
+}
diff --git a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkPunchStatisticsController.java b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkPunchStatisticsController.java
new file mode 100644
index 00000000..a26ffc63
--- /dev/null
+++ b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkPunchStatisticsController.java
@@ -0,0 +1,247 @@
+package com.budwk.app.zhgh.activity.fitnesswalk.controller;
+
+import cn.afterturn.easypoi.excel.ExcelExportUtil;
+import cn.afterturn.easypoi.excel.entity.ExportParams;
+import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.hutool.json.JSONObject;
+import com.budwk.app.base.result.Result;
+import com.budwk.app.base.service.BaseService;
+import com.budwk.app.base.utils.CommonDownloadUtil;
+import com.budwk.app.sys.models.Sys_union;
+import com.budwk.app.zhgh.activity.fitnesswalk.contants.FitnessWalkMode;
+import com.budwk.app.zhgh.activity.fitnesswalk.service.FitnessWalkCommonService;
+import com.budwk.app.zhgh.activity.fitnesswalk.service.FitnessWalkPunchStatisticsService;
+import com.budwk.app.zhgh.activity.fitnesswalk.utils.WeAppCloudUtil;
+import io.swagger.annotations.ApiOperation;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.nutz.dao.Dao;
+import org.nutz.ioc.loader.annotation.Inject;
+import org.nutz.ioc.loader.annotation.IocBean;
+import org.nutz.lang.util.NutMap;
+import org.nutz.mvc.annotation.At;
+import org.nutz.mvc.annotation.Ok;
+import org.nutz.mvc.annotation.Param;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * 打卡统计
+ */
+@IocBean
+@Ok("json")
+@ApiOperation("健步走打卡统计")
+@At("/platform/fitnessWalk/punchStatistics")
+public class FitnessWalkPunchStatisticsController {
+
+ @Inject
+ private Dao dao;
+
+ @Inject
+ private BaseService baseService;
+
+ @Inject
+ private FitnessWalkCommonService fitnessWalkCommonService;
+
+ @Inject
+ private WeAppCloudUtil weAppCloudUtil;
+
+ @Inject
+ private FitnessWalkPunchStatisticsService fitnessWalkPunchStatisticsService;
+
+ @At("")
+ @Ok("beetl:/platform/zhgh/activity/fitnesswalk/punchStatistics.html")
+ @SaCheckPermission("fitnessWalk.punchStatistics")
+ public void index() {
+ }
+
+ /**
+ * 分工会统计
+ *
+ * @param activityId
+ * @param unionId
+ * @return
+ */
+ @At
+ @Ok("json:full")
+ @ApiOperation("打卡记录统计")
+ @SaCheckPermission("fitnessWalk.punchStatistics")
+ public Result pageData(String activityId, String unionId) {
+ List list = fitnessWalkPunchStatisticsService.unionRegCheckInNum(activityId, unionId);
+ return Result.success(list);
+ }
+
+ @At
+ @ApiOperation("获取活动列表")
+ @SaCheckPermission("fitnessWalk.punchStatistics")
+ public Result getCascadersActivity(@Param("year") int year) {
+ return Result.success().addData(fitnessWalkCommonService.cascaderActivity(year, FitnessWalkMode.PUNCH));
+ }
+
+ @At
+ @Ok("json:full")
+ @ApiOperation("根据工会查询获取报名人员")
+ @SaCheckPermission("fitnessWalk.punchStatistics")
+ public Result unionRegistrationData(String activityId, String unionId) {
+ List list = fitnessWalkPunchStatisticsService.unionRegistrationData(activityId, unionId);
+ return Result.success(list);
+ }
+
+ @At
+ @Ok("json:full")
+ @ApiOperation("根据工会查询获取打卡人员")
+ @SaCheckPermission("fitnessWalk.punchStatistics")
+ public Result unionCheckInData(String activityId, String unionId) {
+ List list = fitnessWalkPunchStatisticsService.unionCheckInData(activityId, unionId);
+ return Result.success(list);
+ }
+
+
+ @At
+ @Ok("void")
+ @ApiOperation("导出各分工会报名人数、打卡人数")
+ @SaCheckPermission("fitnessWalk.punchStatistics")
+ public void exportUnionRegCheckInNum(HttpServletResponse response, String activityId) {
+ List list = fitnessWalkPunchStatisticsService.unionRegCheckInNum(activityId, null);
+ List entities = new ArrayList<>();
+ entities.add(new ExcelExportEntity("分工会", "unionname", 20));
+ entities.add(new ExcelExportEntity("报名人数", "regCount", 20));
+ entities.add(new ExcelExportEntity("打卡人数", "checkInCount", 20));
+
+ try {
+ Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams(), entities, list);
+ workbook.write(response.getOutputStream());
+
+ CommonDownloadUtil.download("各分工会统计人数.xlsx", workbook, response);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+
+ @At
+ @Ok("void")
+ @ApiOperation("导出某个分工会的报名人员")
+ @SaCheckPermission("fitnessWalk.punchStatistics")
+ public void exportRegUsers(HttpServletResponse response, String activityId, String unionId) {
+ Sys_union union = dao.fetch(Sys_union.class, unionId);
+ String unionName = Optional.ofNullable(union).map(Sys_union::getName).orElse(null);
+
+ List list = fitnessWalkPunchStatisticsService.unionRegistrationData(activityId, unionId);
+ List entities = new ArrayList<>();
+ entities.add(new ExcelExportEntity("工号", "loginname", 20));
+ entities.add(new ExcelExportEntity("姓名", "username", 20));
+ entities.add(new ExcelExportEntity("单位", "unitname", 20));
+ entities.add(new ExcelExportEntity("手机号", "mobile", 20));
+ entities.add(new ExcelExportEntity("报名时间", "register_time_str", 20));
+
+ try {
+ Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams(), entities, list);
+ workbook.write(response.getOutputStream());
+ CommonDownloadUtil.download("分工会报名人员.xlsx", workbook, response);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+
+ @At
+ @Ok("void")
+ @ApiOperation("导出某个分工会的打卡人员")
+ @SaCheckPermission("fitnessWalk.punchStatistics")
+ public void exportCheckInUsers(HttpServletResponse response, String activityId, String unionId) {
+ Sys_union union = dao.fetch(Sys_union.class, unionId);
+ String unionName = Optional.ofNullable(union).map(Sys_union::getName).orElse(null);
+
+ List list = fitnessWalkPunchStatisticsService.unionCheckInData(activityId, unionId);
+
+ for (JSONObject jsonObject : list) {
+ Boolean used = jsonObject.getBool("used");
+ if (used == null) {
+ jsonObject.put("used", "未取得");
+ } else if (used) {
+ jsonObject.put("used", "已领取");
+ } else {
+ jsonObject.put("used", "未领取");
+ }
+ }
+
+ List entities = new ArrayList<>();
+ entities.add(new ExcelExportEntity("工号", "loginname", 20));
+ entities.add(new ExcelExportEntity("姓名", "username", 20));
+ entities.add(new ExcelExportEntity("单位", "unitname", 20));
+ entities.add(new ExcelExportEntity("手机号", "mobile", 20));
+ entities.add(new ExcelExportEntity("点位总数", "pts_size", 20));
+ entities.add(new ExcelExportEntity("已打卡点位数", "sign_size", 20));
+ entities.add(new ExcelExportEntity("礼品券", "used", 20));
+
+ try {
+ Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams(), entities, list);
+ workbook.write(response.getOutputStream());
+ CommonDownloadUtil.download(unionName + "分工会报名人员.xlsx", workbook, response);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+// /**
+// * 导出礼品领取情况
+// * @param response
+// * @param activityId
+// * @param unionId 工会id
+// * @param used
+// */
+// @At
+// @Ok("void")
+// public void exportGiftInfo(HttpServletResponse response, String activityId, String unionId, boolean used) {
+//
+//
+//
+// }
+
+
+ @At
+ @Ok("void")
+ @ApiOperation("导出未领取得奖品券用户")
+ @SaCheckPermission("fitnessWalk.punchStatistics")
+ public void exportNotGetGiftVoucherUsers(HttpServletResponse response, String activityId) {
+ List list = fitnessWalkPunchStatisticsService.exportNotGetGiftVoucherUsers(activityId);
+ List entities = new ArrayList<>();
+ entities.add(new ExcelExportEntity("工号", "loginname", 20));
+ entities.add(new ExcelExportEntity("姓名", "username", 20));
+ entities.add(new ExcelExportEntity("单位", "unitname", 20));
+ entities.add(new ExcelExportEntity("手机号", "mobile", 20));
+ try {
+ Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams(), entities, list);
+ workbook.write(response.getOutputStream());
+ CommonDownloadUtil.download("未领取得奖品券人员.xlsx", workbook, response);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @At
+ @Ok("void")
+ @ApiOperation("导出未使用奖品券用户")
+ @SaCheckPermission("fitnessWalk.punchStatistics")
+ public void exportNotUseGiftVoucherUsers(HttpServletResponse response, String activityId) {
+ List list = fitnessWalkPunchStatisticsService.exportNotUseGiftVoucherUsers(activityId);
+ List entities = new ArrayList<>();
+ entities.add(new ExcelExportEntity("工号", "loginname", 20));
+ entities.add(new ExcelExportEntity("姓名", "username", 20));
+ entities.add(new ExcelExportEntity("单位", "unitname", 20));
+ entities.add(new ExcelExportEntity("手机号", "mobile", 20));
+ try {
+ Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams(), entities, list);
+ workbook.write(response.getOutputStream());
+ CommonDownloadUtil.download("导出未使用礼品券用户.xlsx", workbook, response);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+}
diff --git a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkStepManageController.java b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkStepManageController.java
new file mode 100644
index 00000000..fd00ffea
--- /dev/null
+++ b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkStepManageController.java
@@ -0,0 +1,403 @@
+package com.budwk.app.zhgh.activity.fitnesswalk.controller;
+
+import cn.hutool.core.date.DateTime;
+import cn.hutool.core.date.DateUnit;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.StrUtil;
+import com.budwk.app.base.annotation.SLog;
+import com.budwk.app.base.result.Result;
+import com.budwk.app.base.service.BaseService;
+import com.budwk.app.zhgh.activity.fitnesswalk.model.FitnessWalkActivity;
+import com.budwk.app.zhgh.activity.fitnesswalk.model.FitnessWalkStep;
+import com.budwk.app.zhgh.activity.fitnesswalk.service.FitnessWalkCommonService;
+import com.budwk.app.zhgh.activity.fitnesswalk.utils.WeAppCloudUtil;
+import com.budwk.app.zhgh.integral.model.IntegralDetail;
+import io.swagger.annotations.ApiOperation;
+import org.nutz.aop.interceptor.ioc.TransAop;
+import org.nutz.dao.Cnd;
+import org.nutz.dao.Dao;
+import org.nutz.dao.Sqls;
+import org.nutz.dao.sql.Sql;
+import org.nutz.integration.jedis.RedisService;
+import org.nutz.ioc.aop.Aop;
+import org.nutz.ioc.loader.annotation.Inject;
+import org.nutz.ioc.loader.annotation.IocBean;
+import org.nutz.json.Json;
+import org.nutz.lang.Lang;
+import org.nutz.lang.util.NutMap;
+import org.nutz.mvc.annotation.At;
+import org.nutz.mvc.annotation.Ok;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.text.DecimalFormat;
+import java.util.*;
+import java.util.function.BinaryOperator;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+@IocBean
+@Ok("json")
+@ApiOperation("健步走步数管理")
+@At("/platform/fitnessWalk/stepManage")
+public class FitnessWalkStepManageController {
+
+ @Inject
+ private Dao dao;
+ @Inject
+ private BaseService baseService;
+ @Inject
+ private RedisService redisService;
+ @Inject
+ private WeAppCloudUtil weAppCloudUtil;
+ @Inject
+ private FitnessWalkCommonService fitnessWalkCommonService;
+
+
+ /**
+ * 用户上传一个近31天的步数
+ *
+ * @param activityId 活动id
+ * @param userId 用户id
+ * @param monthSteps 步数(当天往前推30天)
+ * @return
+ */
+ @At
+ @Ok("json:full")
+ @Aop(TransAop.READ_COMMITTED)
+ @SLog(tag = "健步走步数管理", msg = "微信步数上传")
+ @ApiOperation("上传微信步数,再根据是否开启了积分,自动将步数转化成积分")
+ public Result updateStepMonth(String activityId, String userId, String monthSteps) {
+ try {
+ if (StrUtil.isBlank(userId) || StrUtil.isBlank(monthSteps) || StrUtil.isBlank(activityId)) {
+ return Result.error("必要参数未传递");
+ }
+
+ // 健步走的活动数据
+ FitnessWalkActivity activity = fitnessWalkCommonService.getActivity(activityId);
+ // 是否启用了积分
+ boolean isPoints = activity.getIsPoints() != null && activity.getIsPoints();
+
+ // 使用对象解构获取活动参数
+ final Integer standSteps = activity.getLotteryQualificationSteps();
+ final Integer standPoints = activity.getLotteryQualificationPoints();
+ final Integer otherSteps = activity.getOtherSteps();
+ final Integer otherPoints = activity.getOtherPoints();
+ final Integer maxPoints = activity.getMaxPoints();
+
+ String integralNotes = activity.getMasterActivityName() + "所获积分";
+
+ // 获取到的用户的步数数据
+ List steps = Json.fromJsonAsList(NutMap.class, monthSteps);
+
+ //判断数据是否完整 微信运动会在晚上10点多进行步数的更新 会导致31天前的步数获取变为0 所以这里去除掉第一个元素 即为31天前的数据 每次只保存最近30天的数据
+ if (Lang.isNotEmpty(steps) && steps.size() == 31) {
+ steps.remove(0);
+ }
+
+ // 传递过来的步数数据
+ List wxSteps = steps.stream().map(v -> {
+ FitnessWalkStep step = new FitnessWalkStep();
+ step.setActivityId(activityId);
+ step.setUserId(userId);
+ step.setStep(v.getInt("step"));
+ // 计算积分,判断有没有设置,超过达标步数后,每达标XX步数,获得XX积分
+ int calculatedPoints = calculatePoints(isPoints, standSteps, standPoints, otherSteps, otherPoints, maxPoints, step.getStep());
+ step.setPoints(calculatedPoints);
+ DateTime date = DateUtil.parse(DateUtil.format(DateUtil.date(v.getLong("timestamp") * 1000), "yyyy-MM-dd"), "yyyy-MM-dd");
+ step.setApplyDate(date);
+ return step;
+ }).toList();
+
+ // 传递过来的所有日期的集合
+ List dateList = wxSteps.stream().map(FitnessWalkStep::getApplyDate).collect(Collectors.toList());
+
+ // 去数据库查询日期,这个玩意还要保证插入到数据库的单个日期只有一条,并且这个日期数据库之前有步数,传过来没步数那就要保留数据库的步数
+ // 上面日期集合数据库的步数
+ List stepList = dao.query(FitnessWalkStep.class, Cnd.where("activityId", "=", activityId)
+ .and("userId", "=", userId).and("applyDate", "in", dateList));
+
+ // 这就是保留每天最大步数的集合,那这个集合里面applyDate就是唯一的了
+ List dupStepList = new ArrayList<>(stepList.stream()
+ .collect(Collectors.toMap(FitnessWalkStep::getApplyDate, Function.identity(),
+ BinaryOperator.maxBy(Comparator.comparing(FitnessWalkStep::getStep))))
+ .values());
+
+ // 这是过滤的相同的applyDate的集合,这些数据要删除
+ List delStepIds = stepList.stream()
+ .filter(step -> !dupStepList.contains(step))
+ .map(FitnessWalkStep::getId)
+ .collect(Collectors.toList());
+
+ // 数据库的数据 这个map就用于判断数据库的日期和传递过来日期的步数,两个是不是相等的,保留步数多的数据
+ Map dateStepMap = dupStepList.stream().collect(Collectors.toMap(FitnessWalkStep::getApplyDate, v -> v));
+
+ List resultStepList = new ArrayList<>();
+ // 这里循环就是,循环的传递过来的数据
+ for (FitnessWalkStep step : wxSteps) {
+ // 数据库存储的步数
+ FitnessWalkStep walkStep = dateStepMap.get(step.getApplyDate());
+ // 如果数据库没有,那就要新增
+ if (Lang.isEmpty(walkStep)) {
+ resultStepList.add(step);
+ } else {
+ // 如果数据库有,那就要保留步数多的
+ if (walkStep.getStep() < step.getStep()) {
+ walkStep.setStep(step.getStep());
+ resultStepList.add(walkStep);
+ }
+ }
+ }
+
+ // 最后删除重复日期,更新步数数据
+ if (Lang.isNotEmpty(delStepIds)) {
+ dao.clear(FitnessWalkStep.class, Cnd.where("id", "in", delStepIds));
+ if (standSteps != null && standSteps > 0) {
+ // 删除积分明细
+ dao.clear(IntegralDetail.class, Cnd.where("id", "in", delStepIds));
+ }
+ }
+ if (Lang.isNotEmpty(resultStepList)) {
+ dao.insertOrUpdate(resultStepList);
+ if (standSteps != null && standSteps > 0) {
+ // 增加积分明细
+ int year = DateUtil.thisYear();
+ List integralDetailList = resultStepList.stream().map(v -> {
+ IntegralDetail detail = new IntegralDetail();
+ detail.setId(v.getId());
+ detail.setYear(String.valueOf(year));
+ detail.setUserId(userId);
+ detail.setIntegral(String.valueOf(v.getPoints()));
+ detail.setIntegralNotes(integralNotes);
+ detail.setBizId(activityId);
+ detail.setIntegralTime(v.getApplyDate());
+ return detail;
+ }).toList();
+
+ List list = integralDetailList.stream().filter(v -> !"0".equals(v.getIntegral())).toList();
+ if (Lang.isNotEmpty(list)) {
+ dao.insertOrUpdate(list);
+ }
+ }
+ }
+ return Result.success();
+ } catch (Exception e){
+ e.printStackTrace();
+ return Result.error("步数上传失败,请稍后重试,必要时请及时联系管理员");
+ }
+ }
+
+
+ /**
+ * 小程序获取开始时间至今的步数
+ *
+ * @param activityId
+ * @param userId
+ * @return
+ */
+ @At
+ @Ok("json:full")
+ @ApiOperation("小程序获取开始时间至今的步数")
+ public Result getActivityDateStep(String activityId, String userId) {
+ try {
+ Cnd cnd = Cnd.NEW();
+
+ FitnessWalkActivity activity = fitnessWalkCommonService.getActivity(activityId);
+ DateTime startTime = DateUtil.date(activity.getStartTime());
+ DateTime endTime = DateUtil.date(activity.getEndTime());
+
+ cnd.and("activityId", "=", activityId);
+ cnd.and("userId", "=", userId);
+ cnd.and("applyDate", ">=", startTime);
+ cnd.and("applyDate", "<=", endTime);
+ cnd.asc("applyDate");
+ cnd.groupBy("applyDate");
+ List stepList = baseService.dao().query(FitnessWalkStep.class, cnd);
+
+ List