diff --git a/ruoyi-admin/pom.xml b/ruoyi-admin/pom.xml index 9617d01..818d9cf 100644 --- a/ruoyi-admin/pom.xml +++ b/ruoyi-admin/pom.xml @@ -16,7 +16,17 @@ - + + + com.github.wechatpay-apiv3 + wechatpay-apache-httpclient + 0.4.7 + + + com.github.wechatpay-apiv3 + wechatpay-java + 0.2.12 + org.springframework.boot @@ -80,17 +90,17 @@ - - org.apache.maven.plugins - maven-war-plugin - 3.1.0 + + org.apache.maven.plugins + maven-war-plugin + 3.1.0 false ${project.artifactId} - - + + ${project.artifactId} - \ No newline at end of file + diff --git a/ruoyi-admin/src/main/java/com/ruoyi/api/PayApi.java b/ruoyi-admin/src/main/java/com/ruoyi/api/PayApi.java new file mode 100644 index 0000000..b040ca7 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/api/PayApi.java @@ -0,0 +1,147 @@ +package com.ruoyi.api; + +import cn.hutool.json.JSONUtil; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.alibaba.fastjson2.TypeReference; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.payConfig.WechatPayConfig; +import com.ruoyi.payConfig.WechatPayRequest; +import com.ruoyi.payConfig.WechatPayUrlEnum; +import com.wechat.pay.contrib.apache.httpclient.util.AesUtil; +import com.wechat.pay.contrib.apache.httpclient.util.PemUtil; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.security.*; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; + +@RestController +@RequestMapping("/payApi") +public class PayApi { + @Resource + private WechatPayConfig wechatPayConfig; + @Resource + private WechatPayRequest wechatPayRequest; + /** + * type:h5、jsapi、app、native、sub_jsapi + * @param type + * @return + */ + @ApiOperation(value = "统一下单-统一接口", notes = "统一下单-统一接口") + @GetMapping("/prepayment") + public Map transactions(String type, Long orderNo, String payType) throws SignatureException, NoSuchAlgorithmException, InvalidKeyException, IOException { + SysUser user = SecurityUtils.getLoginUser().getUser(); + // 统一参数封装 + Map params = new HashMap<>(8); + params.put("appid", wechatPayConfig.getAppId()); + params.put("mchid", wechatPayConfig.getMchId()); + params.put("description", driveSchoolCourse.getName()); + params.put("out_trade_no", orderNo.toString()); + params.put("notify_url", wechatPayConfig.getNotifyUrl()); + Map amountMap = new HashMap<>(4); + + // 金额单位为分 + amountMap.put("total", amount.intValue()); + //人民币 + amountMap.put("currency", "CNY"); + params.put("amount", amountMap); + + // 场景信息 + Map sceneInfoMap = new HashMap<>(4); + // 客户端IP + sceneInfoMap.put("payer_client_ip", "127.0.0.1"); + // 商户端设备号(门店号或收银设备ID) + sceneInfoMap.put("device_id", "127.0.0.1"); + // 除H5与JSAPI有特殊参数外,其他的支付方式都一样 + if (type.equals(WechatPayUrlEnum.H5.getType())) { + Map h5InfoMap = new HashMap<>(4); + // 场景类型:iOS, Android, Wap + h5InfoMap.put("type", "IOS"); + sceneInfoMap.put("h5_info", h5InfoMap); + } else if (type.equals(WechatPayUrlEnum.JSAPI.getType()) || type.equals(WechatPayUrlEnum.SUB_JSAPI.getType())) { + Map payerMap = new HashMap<>(4); + payerMap.put("openid", user.getWxOpenId()); + params.put("payer", payerMap); + } + params.put("scene_info", sceneInfoMap); + String paramsStr = JSON.toJSONString(params); + String resStr = wechatPayRequest.wechatHttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi",paramsStr); + Map resMap = JSONObject.parseObject(resStr, new TypeReference>(){}); + return paySignMsg(resMap.get("prepay_id").toString(), wechatPayConfig.getAppId(),null); + } + + @ApiOperation(value = "支付回调", notes = "支付回调") + @PostMapping("/payNotify") + public Map payNotify(@RequestBody JSONObject jsonObject) throws GeneralSecurityException, IOException { + String key = wechatPayConfig.getApiV3Key(); + String json = jsonObject.toString(); + String associated_data = (String) JSONUtil.getByPath(JSONUtil.parse(json), "resource.associated_data"); + String ciphertext = (String) JSONUtil.getByPath(JSONUtil.parse(json), "resource.ciphertext"); + String nonce = (String) JSONUtil.getByPath(JSONUtil.parse(json), "resource.nonce"); + String decryptData = new AesUtil(key.getBytes(StandardCharsets.UTF_8)).decryptToString(associated_data.getBytes(StandardCharsets.UTF_8), nonce.getBytes(StandardCharsets.UTF_8), ciphertext); + //验签成功 + JSONObject decryptDataObj = JSONObject.parseObject(decryptData, JSONObject.class); + String orderNo = decryptDataObj.get("out_trade_no").toString(); + //调用业务系统 + Map res = new HashMap<>(); + res.put("code", "SUCCESS"); + res.put("message", "成功"); + return res; + } + + String buildMessage(String appId, String timestamp,String nonceStr,String prepay_id) { + + return appId + "\n" + + timestamp + "\n" + + nonceStr + "\n" + + prepay_id + "\n"; + } + + String sign(byte[] message,String privateKeyStr) throws NoSuchAlgorithmException, SignatureException, IOException, InvalidKeyException { + //签名方式 + Signature sign = Signature.getInstance("SHA256withRSA"); + //私钥,通过MyPrivateKey来获取,这是个静态类可以接调用方法 ,需要的是_key.pem文件的绝对路径配上文件名 + PrivateKey privateKey =null; + if (StringUtils.isNotEmpty(privateKeyStr)){ + privateKey = PemUtil.loadPrivateKey(privateKeyStr); + }else { + privateKey = wechatPayConfig.getPrivateKey(wechatPayConfig.getKeyPemPath()); + } + + sign.initSign(privateKey); + sign.update(message); + return Base64.getEncoder().encodeToString(sign.sign()); + } + + private Map paySignMsg(String prepayId,String appId,String privateKeyStr) throws IOException, NoSuchAlgorithmException, InvalidKeyException, SignatureException { + long timeMillis = System.currentTimeMillis(); + String timeStamp = timeMillis/1000+""; + String nonceStr = timeMillis+""; + String packageStr = "prepay_id="+prepayId; + // 公共参数 + Map resMap = new HashMap<>(); + resMap.put("nonceStr",nonceStr); + resMap.put("timeStamp",timeStamp); + resMap.put("appId",appId); + resMap.put("package", packageStr); + // 使用字段appId、timeStamp、nonceStr、package进行签名 + //从下往上依次生成 + String message = buildMessage(appId, timeStamp, nonceStr, packageStr); + //签名 + String paySign = sign(message.getBytes("utf-8"), privateKeyStr); + resMap.put("paySign", paySign); + resMap.put("signType", "RSA"); + return resMap; + } + + + +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/busi/controller/BusiNoticeController.java b/ruoyi-admin/src/main/java/com/ruoyi/busi/controller/BusiNoticeController.java index 99a2832..8580f24 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/busi/controller/BusiNoticeController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/busi/controller/BusiNoticeController.java @@ -11,6 +11,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ruoyi.busi.domain.BusiNoticeForm; +import com.ruoyi.busi.domain.BusiNoticeSign; import com.ruoyi.busi.query.AppNoticeQuery; import com.ruoyi.busi.query.BusiNoticeQuery; import com.ruoyi.busi.service.IBusiNoticeFormService; @@ -309,4 +310,41 @@ public class BusiNoticeController extends BaseController } + /** + * 查看通告报名列表 + * @author zcy + * @date 15:39 2025/3/29 + * @param query 查询条件 + * @param pageNum 1 + * @param pageSize 10 + * @return com.ruoyi.common.core.domain.AjaxResult + **/ + @GetMapping("/reportList") + public AjaxResult reportList(AppNoticeQuery query, + @RequestParam(name = "pageNum", defaultValue = "1") Integer pageNum, + @RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize) + { + Page page = new Page<>(pageNum, pageSize); + IPage list = busiNoticeService.reportList(query,page); + return success(list); + } + + + /** + * 查看通告报名数量 + * @author zcy + * @date 15:39 2025/3/29 + * @param query 查询条件 + * @return com.ruoyi.common.core.domain.AjaxResult + **/ + @GetMapping("/reportNum") + public AjaxResult reportNum(AppNoticeQuery query) + { + JSONObject res = busiNoticeService.reportNum(query); + return success(res); + } + + + + } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/busi/controller/BusiNoticeSignController.java b/ruoyi-admin/src/main/java/com/ruoyi/busi/controller/BusiNoticeSignController.java index 5c8e284..d03b44c 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/busi/controller/BusiNoticeSignController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/busi/controller/BusiNoticeSignController.java @@ -6,7 +6,11 @@ import java.util.List; import javax.servlet.http.HttpServletResponse; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.ruoyi.busi.domain.BusiNotice; +import com.ruoyi.busi.query.AppNoticeQuery; import com.ruoyi.busi.query.AppNoticeSign; +import com.ruoyi.busi.service.IBusiNoticeService; +import com.ruoyi.common.annotation.Anonymous; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @@ -39,6 +43,8 @@ public class BusiNoticeSignController extends BaseController { @Autowired private IBusiNoticeSignService busiNoticeSignService; + @Autowired + private IBusiNoticeService noticeService; /** * 查询通告报名列表 @@ -57,14 +63,20 @@ public class BusiNoticeSignController extends BaseController /** * 导出通告报名列表 */ - @PreAuthorize("@ss.hasPermi('busi:sign:export')") + @Log(title = "通告报名", businessType = BusinessType.EXPORT) - @PostMapping("/export") - public void export(HttpServletResponse response, BusiNoticeSign busiNoticeSign) + @GetMapping("/export") + @Anonymous + public void export(HttpServletResponse response, @RequestParam(name = "noticeId") String noticeId) { - List list = busiNoticeSignService.list(); + BusiNotice notice = noticeService.getById(noticeId); + AppNoticeQuery query =new AppNoticeQuery(); + query.setNoticeId(noticeId); + query.setStatus("02"); + Page page = new Page<>(1, 5000); + List records = noticeService.reportList(query, page).getRecords(); ExcelUtil util = new ExcelUtil(BusiNoticeSign.class); - util.exportExcel(response, list, "通告报名数据"); + util.exportExcel(response, records, "通告:"+notice.getTitle()+"报名名称"); } /** @@ -121,4 +133,14 @@ public class BusiNoticeSignController extends BaseController busiNoticeSignService.userSign(appNoticeSign); return success(); } + + + /** + * 用户通告报名 + */ + @PostMapping("/chooseSign") + public AjaxResult chooseSign(String signIds) throws Exception { + busiNoticeSignService.chooseSign(signIds.split(",")); + return success(); + } } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/busi/domain/BusiNotice.java b/ruoyi-admin/src/main/java/com/ruoyi/busi/domain/BusiNotice.java index 712ad35..f7ab68c 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/busi/domain/BusiNotice.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/busi/domain/BusiNotice.java @@ -133,6 +133,7 @@ public class BusiNotice extends DlBaseEntity @Excel(name = "微信号") private String wechat; + /** 电话 */ @Excel(name = "电话") private String tel; diff --git a/ruoyi-admin/src/main/java/com/ruoyi/busi/domain/BusiNoticeSign.java b/ruoyi-admin/src/main/java/com/ruoyi/busi/domain/BusiNoticeSign.java index 9d58119..32c7325 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/busi/domain/BusiNoticeSign.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/busi/domain/BusiNoticeSign.java @@ -1,5 +1,6 @@ package com.ruoyi.busi.domain; +import com.baomidou.mybatisplus.annotation.TableField; import com.ruoyi.common.annotation.Excel; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; @@ -87,5 +88,20 @@ public class BusiNoticeSign extends DlBaseEntity * 报价 */ private BigDecimal price; + @TableField(exist = false) + private String[] choosed={}; + @TableField(exist = false) + private String platformCode; + @TableField(exist = false) + private String platformName; + @TableField(exist = false) + private String accountName; + @TableField(exist = false) + private Long fansNum; + + + + + } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/busi/mapper/BusiNoticeSignMapper.java b/ruoyi-admin/src/main/java/com/ruoyi/busi/mapper/BusiNoticeSignMapper.java index 608c4bd..a2835d4 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/busi/mapper/BusiNoticeSignMapper.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/busi/mapper/BusiNoticeSignMapper.java @@ -6,13 +6,14 @@ import java.util.Map; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ruoyi.busi.domain.BusiNoticeSign; +import com.ruoyi.busi.query.AppNoticeQuery; import org.apache.ibatis.annotations.Param; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper; /** * 通告报名Mapper接口 - * + * * @author 朱春云 * @date 2025-03-17 */ @@ -21,6 +22,10 @@ public interface BusiNoticeSignMapper extends BaseMapper { IPage queryListPage(@Param("entity") BusiNoticeSign entity, Page page); + IPage reportList( Page page,@Param("entity") AppNoticeQuery entity); + + + /** * 查某些公告的报名数量 * @author vinjor-M diff --git a/ruoyi-admin/src/main/java/com/ruoyi/busi/query/AppNoticeQuery.java b/ruoyi-admin/src/main/java/com/ruoyi/busi/query/AppNoticeQuery.java index 80d6462..2aa7fb4 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/busi/query/AppNoticeQuery.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/busi/query/AppNoticeQuery.java @@ -36,5 +36,6 @@ public class AppNoticeQuery { //'进行中', '已关闭' private String status; + private String noticeId; } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/busi/service/IBusiNoticeService.java b/ruoyi-admin/src/main/java/com/ruoyi/busi/service/IBusiNoticeService.java index 3f29ed8..27c5328 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/busi/service/IBusiNoticeService.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/busi/service/IBusiNoticeService.java @@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.IService; import com.ruoyi.busi.domain.BusiNotice; +import com.ruoyi.busi.domain.BusiNoticeSign; import com.ruoyi.busi.query.AppNoticeQuery; import com.ruoyi.busi.query.BusiNoticeQuery; import com.ruoyi.busi.vo.BusiNoticeVo; @@ -97,6 +98,8 @@ public interface IBusiNoticeService extends IService IPage myNoticeList(AppNoticeQuery query, Page page); IPage myPublishNoticeList(AppNoticeQuery query, Page page); + IPage reportList(AppNoticeQuery query, Page page); + JSONObject reportNum(AppNoticeQuery query); diff --git a/ruoyi-admin/src/main/java/com/ruoyi/busi/service/IBusiNoticeSignService.java b/ruoyi-admin/src/main/java/com/ruoyi/busi/service/IBusiNoticeSignService.java index 7b8193b..8668ec5 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/busi/service/IBusiNoticeSignService.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/busi/service/IBusiNoticeSignService.java @@ -17,4 +17,5 @@ public interface IBusiNoticeSignService extends IService { IPage queryListPage(BusiNoticeSign pageReqVO, Page page); void userSign(AppNoticeSign appNoticeSign) throws Exception; + void chooseSign(String[] signIds); } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/busi/service/impl/BusiNoticeServiceImpl.java b/ruoyi-admin/src/main/java/com/ruoyi/busi/service/impl/BusiNoticeServiceImpl.java index 1a8b036..76efb24 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/busi/service/impl/BusiNoticeServiceImpl.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/busi/service/impl/BusiNoticeServiceImpl.java @@ -589,5 +589,29 @@ public class BusiNoticeServiceImpl extends ServiceImpl reportList(AppNoticeQuery query, Page page) { + return busiNoticeSignMapper.reportList(page,query); + } + + /** + * 获取报名数量 + * @param query + * @return + */ + @Override + public JSONObject reportNum(AppNoticeQuery query) { + JSONObject result = new JSONObject(); + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(BusiNoticeSign::getNoticeId,query.getNoticeId()).eq(BusiNoticeSign::getStatus,"01"); + Integer i = busiNoticeSignMapper.selectCount(queryWrapper); + result.put("waitNum",i); + queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(BusiNoticeSign::getNoticeId,query.getNoticeId()).eq(BusiNoticeSign::getStatus,"02"); + Integer i2 = busiNoticeSignMapper.selectCount(queryWrapper); + result.put("checkedNum",i2); + return result; + } + } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/busi/service/impl/BusiNoticeSignServiceImpl.java b/ruoyi-admin/src/main/java/com/ruoyi/busi/service/impl/BusiNoticeSignServiceImpl.java index d606d79..2a922a5 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/busi/service/impl/BusiNoticeSignServiceImpl.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/busi/service/impl/BusiNoticeSignServiceImpl.java @@ -6,11 +6,13 @@ import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.ruoyi.busi.query.AppNoticeSign; +import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.domain.model.LoginUser; import com.ruoyi.common.utils.DateUtils; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.system.service.ISysUserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; @@ -29,6 +31,8 @@ public class BusiNoticeSignServiceImpl extends ServiceImpl queryListPage(BusiNoticeSign pageReqVO, Page page) { @@ -46,13 +50,14 @@ public class BusiNoticeSignServiceImpl extends ServiceImpl().eq(BusiNoticeSign::getNoticeId, appNoticeSign.getNoticeId()).eq(BusiNoticeSign::getUserId, loginUser.getUserId()).last("limit 1"))!=null){ throw new Exception("请勿重复报名!"); } + SysUser sysUser = userService.selectUserById(loginUser.getUserId()); for (JSONObject cardInfo : appNoticeSign.getCardList()) { BusiNoticeSign busiNoticeSign =new BusiNoticeSign(); busiNoticeSign.setNoticeId(appNoticeSign.getNoticeId()); busiNoticeSign.setUserId(loginUser.getUserId()); - busiNoticeSign.setNickname(loginUser.getUser().getNickName()); - busiNoticeSign.setAvatar(loginUser.getUser().getAvatar()); + busiNoticeSign.setNickname(sysUser.getNickName()); + busiNoticeSign.setAvatar(sysUser.getAvatar()); busiNoticeSign.setStatus("0"); busiNoticeSign.setFormData(JSONArray.toJSONString(appNoticeSign.getCustomForm())); busiNoticeSign.setCardId(cardInfo.getString("id")); @@ -64,4 +69,17 @@ public class BusiNoticeSignServiceImpl extends ServiceImpl true)实现 + .withValidator((response) -> true); + + // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新 + return builder.build(); + } +} + diff --git a/ruoyi-admin/src/main/java/com/ruoyi/payConfig/WechatPayRequest.java b/ruoyi-admin/src/main/java/com/ruoyi/payConfig/WechatPayRequest.java new file mode 100644 index 0000000..252c57b --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/payConfig/WechatPayRequest.java @@ -0,0 +1,75 @@ +package com.ruoyi.payConfig; + + +import lombok.extern.slf4j.Slf4j; +import org.apache.http.HttpEntity; +import org.apache.http.HttpStatus; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.util.EntityUtils; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.io.IOException; + +/** + * @Author: + * @Description: + **/ +@Component +@Slf4j +public class WechatPayRequest { + @Resource + private CloseableHttpClient wxPayClient; + public String wechatHttpGet(String url) { + try { + // 拼接请求参数 + HttpGet httpGet = new HttpGet(url); + httpGet.setHeader("Accept", "application/json"); + + //完成签名并执行请求 + CloseableHttpResponse response = wxPayClient.execute(httpGet); + + return getResponseBody(response); + }catch (Exception e){ + throw new RuntimeException(e.getMessage()); + } + } + + public String wechatHttpPost(String url,String paramsStr) { + try { + HttpPost httpPost = new HttpPost(url); + StringEntity entity = new StringEntity(paramsStr, "utf-8"); + entity.setContentType("application/json"); + httpPost.setEntity(entity); + httpPost.setHeader("Accept", "application/json"); + + CloseableHttpResponse response = wxPayClient.execute(httpPost); + return getResponseBody(response); + }catch (Exception e){ + throw new RuntimeException(e.getMessage()); + } + } + + private String getResponseBody(CloseableHttpResponse response) throws IOException { + + //响应体 + HttpEntity entity = response.getEntity(); + String body = entity==null?"":EntityUtils.toString(entity); + //响应状态码 + int statusCode = response.getStatusLine().getStatusCode(); + + //处理成功,204是,关闭订单时微信返回的正常状态码 + if (statusCode == HttpStatus.SC_OK || statusCode == HttpStatus.SC_NO_CONTENT) { + log.info("成功, 返回结果 = " + body); + } else { + String msg = "微信支付请求失败,响应码 = " + statusCode + ",返回结果 = " + body; + log.error(msg); + throw new RuntimeException(msg); + } + return body; + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/payConfig/WechatPayUrlEnum.java b/ruoyi-admin/src/main/java/com/ruoyi/payConfig/WechatPayUrlEnum.java new file mode 100644 index 0000000..9f13d73 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/payConfig/WechatPayUrlEnum.java @@ -0,0 +1,78 @@ +package com.ruoyi.payConfig; + + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public enum WechatPayUrlEnum { + + + /** + * native + */ + NATIVE("native"), + /** + * app + */ + APP("app"), + /** + * h5 + */ + H5("h5"), + /** + * jsapi + */ + JSAPI("jsapi"), + + /** + * 小程序jsapi + */ + SUB_JSAPI("sub_jsapi"), + + /** + * Native下单 + */ + PAY_TRANSACTIONS("/pay/transactions/"), + + /** + * Native下单 + */ + NATIVE_PAY_V2("/pay/unifiedorder"), + + /** + * 查询订单 + */ + ORDER_QUERY_BY_NO("/pay/transactions/out-trade-no/"), + + /** + * 关闭订单 + */ + CLOSE_ORDER_BY_NO("/pay/transactions/out-trade-no/%s/close"), + + /** + * 申请退款 + */ + DOMESTIC_REFUNDS("/refund/domestic/refunds"), + + /** + * 查询单笔退款 + */ + DOMESTIC_REFUNDS_QUERY("/refund/domestic/refunds/"), + + /** + * 申请交易账单 + */ + TRADE_BILLS("/bill/tradebill"), + + /** + * 申请资金账单 + */ + FUND_FLOW_BILLS("/bill/fundflowbill"); + + /** + * 类型 + */ + private final String type; +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/payConfig/WechatPayValidator.java b/ruoyi-admin/src/main/java/com/ruoyi/payConfig/WechatPayValidator.java new file mode 100644 index 0000000..c24565d --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/payConfig/WechatPayValidator.java @@ -0,0 +1,150 @@ +package com.ruoyi.payConfig; + +import com.alibaba.fastjson2.JSONObject; +import com.alibaba.fastjson2.TypeReference; +import com.wechat.pay.contrib.apache.httpclient.auth.Verifier; +import com.wechat.pay.contrib.apache.httpclient.util.AesUtil; +import lombok.extern.slf4j.Slf4j; +import org.apache.http.HttpEntity; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.util.EntityUtils; + +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.time.DateTimeException; +import java.time.Duration; +import java.time.Instant; +import java.util.Map; + +import static com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders.*; + +/** + * @Author: + * @Description: + **/ +@Slf4j +public class WechatPayValidator { + /** + * 应答超时时间,单位为分钟 + */ + private static final long RESPONSE_EXPIRED_MINUTES = 5; + private final Verifier verifier; + private final String requestId; + private final String body; + + + public WechatPayValidator(Verifier verifier, String requestId, String body) { + this.verifier = verifier; + this.requestId = requestId; + this.body = body; + } + + protected static IllegalArgumentException parameterError(String message, Object... args) { + message = String.format(message, args); + return new IllegalArgumentException("parameter error: " + message); + } + + protected static IllegalArgumentException verifyFail(String message, Object... args) { + message = String.format(message, args); + return new IllegalArgumentException("signature verify fail: " + message); + } + + public final boolean validate(HttpServletRequest request) { + try { + //处理请求参数 + validateParameters(request); + + //构造验签名串 + String message = buildMessage(request); + + String serial = request.getHeader(WECHAT_PAY_SERIAL); + String signature = request.getHeader(WECHAT_PAY_SIGNATURE); + + //验签 + if (!verifier.verify(serial, message.getBytes(StandardCharsets.UTF_8), signature)) { + throw verifyFail("serial=[%s] message=[%s] sign=[%s], request-id=[%s]", + serial, message, signature, requestId); + } + } catch (IllegalArgumentException e) { + log.warn(e.getMessage()); + return false; + } + + return true; + } + + private void validateParameters(HttpServletRequest request) { + + // NOTE: ensure HEADER_WECHAT_PAY_TIMESTAMP at last + String[] headers = {WECHAT_PAY_SERIAL, WECHAT_PAY_SIGNATURE, WECHAT_PAY_NONCE, WECHAT_PAY_TIMESTAMP}; + + String header = null; + for (String headerName : headers) { + header = request.getHeader(headerName); + if (header == null) { + throw parameterError("empty [%s], request-id=[%s]", headerName, requestId); + } + } + + //判断请求是否过期 + String timestampStr = header; + try { + Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestampStr)); + // 拒绝过期请求 + if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= RESPONSE_EXPIRED_MINUTES) { + throw parameterError("timestamp=[%s] expires, request-id=[%s]", timestampStr, requestId); + } + } catch (DateTimeException | NumberFormatException e) { + throw parameterError("invalid timestamp=[%s], request-id=[%s]", timestampStr, requestId); + } + } + + private String buildMessage(HttpServletRequest request) { + String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP); + String nonce = request.getHeader(WECHAT_PAY_NONCE); + return timestamp + "\n" + + nonce + "\n" + + body + "\n"; + } + + private String getResponseBody(CloseableHttpResponse response) throws IOException { + HttpEntity entity = response.getEntity(); + return (entity != null && entity.isRepeatable()) ? EntityUtils.toString(entity) : ""; + } + + /** + * 对称解密,异步通知的加密数据 + * @param resource 加密数据 + * @param apiV3Key apiV3密钥 + * @param type 1-支付,2-退款 + * @return + */ + public static Map decryptFromResource(String resource,String apiV3Key,Integer type) { + + String msg = type==1?"支付成功":"退款成功"; + log.info(msg+",回调通知,密文解密"); + try { + //通知数据 + Map resourceMap = JSONObject.parseObject(resource, new TypeReference>() { + }); + //数据密文 + String ciphertext = resourceMap.get("ciphertext"); + //随机串 + String nonce = resourceMap.get("nonce"); + //附加数据 + String associatedData = resourceMap.get("associated_data"); + + log.info("密文: {}", ciphertext); + AesUtil aesUtil = new AesUtil(apiV3Key.getBytes(StandardCharsets.UTF_8)); + String resourceStr = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8), + nonce.getBytes(StandardCharsets.UTF_8), + ciphertext); + + log.info(msg+",回调通知,解密结果 : {}", resourceStr); + return JSONObject.parseObject(resourceStr, new TypeReference>(){}); + }catch (Exception e){ + throw new RuntimeException("回调参数,解密失败!"); + } + } +} diff --git a/ruoyi-admin/src/main/resources/apiclient_key.pem b/ruoyi-admin/src/main/resources/apiclient_key.pem new file mode 100644 index 0000000..cb3c4ad --- /dev/null +++ b/ruoyi-admin/src/main/resources/apiclient_key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQC7FUOpvxLTMFcf +UaA2g8/B86GhGXXAEn1hSMQMpKAp+oXVvw1H1aW/mJfjiFlhlodVXBQwVsCaVENd +bcwoCsQjYHWgUtkcFv4j8vryMbb13bcTyYrcYEERhpneYDKv6tPNc8tGZZu55ExX +bRrQ+9TloO43rAXkUgabxgRecYEg05WyZBjpJVqFntmyCM394nBmr9wC8H65aDJc +60N2h8v+ZiYBzn4aOatJTUdQAAV+0kzSq7kRn/jG19HDLESLmkgXwFNpSm21hDTw +TbplLXUfI92Jhdx/lQ3aDoj8lVIjbEW7SQnk0wbU4nfDfo2mlgXItRDPNCHiAnYR +cdZCm48vAgMBAAECggEBAIo57eJvYNNVNinXv45H2ast/U/wZcXiY20LvH6y6/vY +NCZ3oLJgsrRbZG4jAhdOhvOUsv38OxZMB/Hx/BPh0IVq8b3hOLS+cEvTbYKhJNOI +W4ptMQIexDTUxL8/SHQeBxwX4CtFS8gPXDCEVnnud6CqRRgxK7FEwck6h2PYbVX4 +e4575fqrPEQHA4raKQB/boUBLK2KxyXbomT1vU2Xehzlu/1Lcc3PMuKcXtTWNg0K +tvUcVdfI36BVde79TIiauultEIai0ZXVj1NUG/9CA2iHyzSGmVtTyWqQQQDEz5Ph +RdRhYCvPARoTEfw7AX0ShCM6trLkZjcTIM68gkuJdWECgYEA3CagoLXpUM08cySW +GzswCP/p06Pe/4NBGLronKj6+k2x32zFVEfZnv++CfdPAXd9d8iXqElRvLFceAsu +NbcxsbtPYKG5eBsc2qS7f5hVSjubiycJATqfR6JlFv+hHYG42snY10qfdzsJpJ8T +SKh7SJeZ54o9wlKqbkaV3ucUpIcCgYEA2Ywlp46INLzsn1LOzn3sV6pcwII33YEl +3JZz3OQWzrpGMuC/HCH9aazh3ZLyc6NXpRFAR5329mLgKKTOIT0yp0c4S3EQ8/M/ +nwUEDye/GrVJjsT7mqoXbSylVZpVfg/4M4SAZ3xbL7aANMtMcCS67J3ByulmHEAh +DvDiyplKEhkCgYEAjQZ5kzm00jwG40OmnJ8XsEwvf5HUAh4Uj0D8TY6556nprdRB +vDGiqIXvOPchtzDSQO9Qp08Aez2qnIOdAG/v94ij2qT+6H+FxlIMgjoVOM5iX4uL +6yugaQUQeOEcVoiI4C65J4D4EirRjJESi1LSVrg2sOoPkfHel3HEA9xHjvUCgYEA +nCCinYADbKXxw94wIa3pwni3nElYQpX/UDKB8JOZcr/oxXbacRxLvF2gs95UNn2R +1xtsYHmT1fvcGA8/CEfdZIQOeMYqfomirUNySFYkJszYf6gLUlKkAWw7NBZRKOnZ +HVAIvzxWTQXTORB2ST1zEYGepTugVsIHd6uVeAVPTXECgYEA2sgzcVHf38EFf16V +x8OfYalGBazfnTXxZUMji4DBaiaA0luH+cmEm21bEVnJkINs8pySrSP8IiGr01r3 +ieMkVZVdA8PulxPTKTGnnIuLEsQzdulD01A2i0f3mNxjyLUltBUX56SO7Sc8/Zoq +lENhDpQlP41ahoLE8X9Ti0uxpsw= +-----END PRIVATE KEY----- diff --git a/ruoyi-admin/src/main/resources/mapper/busi/BusiNoticeSignMapper.xml b/ruoyi-admin/src/main/resources/mapper/busi/BusiNoticeSignMapper.xml index c6f1f23..07ad448 100644 --- a/ruoyi-admin/src/main/resources/mapper/busi/BusiNoticeSignMapper.xml +++ b/ruoyi-admin/src/main/resources/mapper/busi/BusiNoticeSignMapper.xml @@ -3,7 +3,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> - + @@ -38,6 +38,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" and tel like concat('%', #{entity.tel}, '%') and status = #{entity.status} + order by isSuper desc, create_time asc - \ No newline at end of file + + diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java index 37a33a9..2bad104 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java @@ -14,7 +14,7 @@ import com.ruoyi.common.xss.Xss; /** * 用户对象 sys_user - * + * * @author ruoyi */ public class SysUser extends BaseEntity @@ -73,6 +73,14 @@ public class SysUser extends BaseEntity /** openId */ private String openId; + + /** 微信公众号openId */ + private String wxOpenId; + + + /** 微信公众平台id */ + private String unionId; + /** 部门对象 */ @Excels({ @Excel(name = "部门名称", targetAttr = "deptName", type = Type.EXPORT), @@ -331,4 +339,20 @@ public class SysUser extends BaseEntity .append("dept", getDept()) .toString(); } + + public String getWxOpenId() { + return wxOpenId; + } + + public void setWxOpenId(String wxOpenId) { + this.wxOpenId = wxOpenId; + } + + public String getUnionId() { + return unionId; + } + + public void setUnionId(String unionId) { + this.unionId = unionId; + } } diff --git a/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml index e9f5f30..43fbf2b 100644 --- a/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml +++ b/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml @@ -16,6 +16,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + + @@ -27,7 +29,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - + @@ -37,7 +39,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - + @@ -46,9 +48,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - + - select u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status,u.open_id, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark, + select u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status,u.open_id,u.wx_open_id,u.union_id, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark, d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.status as dept_status, r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.status as role_status from sys_user u @@ -56,9 +58,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" left join sys_user_role ur on u.user_id = ur.user_id left join sys_role r on r.role_id = ur.role_id - + - + - + - + - + - + - + - + - + insert into sys_user( user_id, @@ -156,6 +158,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" password, status, open_id, + wx_open_id, + union_id, create_by, remark, create_time @@ -171,12 +175,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" #{password}, #{status}, #{openId}, + #{wxOpenId}, + #{unionId}, #{createBy}, #{remark}, sysdate() ) - + update sys_user @@ -189,6 +195,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" password = #{password}, status = #{status}, open_id = #{openId}, + wx_open_id = #{wxOpenId}, + union_id =#{unionId}, + login_ip = #{loginIp}, login_date = #{loginDate}, update_by = #{updateBy}, @@ -197,28 +206,28 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" where user_id = #{userId} - + update sys_user set status = #{status} where user_id = #{userId} - + update sys_user set avatar = #{avatar} where user_name = #{userName} - + update sys_user set password = #{password} where user_name = #{userName} - + update sys_user set del_flag = '2' where user_id = #{userId} - + update sys_user set del_flag = '2' where user_id in #{userId} - + - \ No newline at end of file +