发布通告功能

This commit is contained in:
13405411873 2025-04-15 17:41:00 +08:00
parent 79a2286006
commit 4d25ae90c8
20 changed files with 875 additions and 43 deletions

View File

@ -16,7 +16,17 @@
</description>
<dependencies>
<!--微信支付SDK-->
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.4.7</version>
</dependency>
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-java</artifactId>
<version>0.2.12</version>
</dependency>
<!-- spring-boot-devtools -->
<dependency>
<groupId>org.springframework.boot</groupId>

View File

@ -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;
/**
* typeh5jsapiappnativesub_jsapi
* @param type
* @return
*/
@ApiOperation(value = "统一下单-统一接口", notes = "统一下单-统一接口")
@GetMapping("/prepayment")
public Map<String,Object> transactions(String type, Long orderNo, String payType) throws SignatureException, NoSuchAlgorithmException, InvalidKeyException, IOException {
SysUser user = SecurityUtils.getLoginUser().getUser();
// 统一参数封装
Map<String, Object> 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<String, Object> amountMap = new HashMap<>(4);
// 金额单位为分
amountMap.put("total", amount.intValue());
//人民币
amountMap.put("currency", "CNY");
params.put("amount", amountMap);
// 场景信息
Map<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> resMap = JSONObject.parseObject(resStr, new TypeReference<Map<String, Object>>(){});
return paySignMsg(resMap.get("prepay_id").toString(), wechatPayConfig.getAppId(),null);
}
@ApiOperation(value = "支付回调", notes = "支付回调")
@PostMapping("/payNotify")
public Map<String, String> 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<String, String> 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<String, Object> 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<String, Object> resMap = new HashMap<>();
resMap.put("nonceStr",nonceStr);
resMap.put("timeStamp",timeStamp);
resMap.put("appId",appId);
resMap.put("package", packageStr);
// 使用字段appIdtimeStampnonceStrpackage进行签名
//从下往上依次生成
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;
}
}

View File

@ -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<BusiNoticeSign> page = new Page<>(pageNum, pageSize);
IPage<BusiNoticeSign> 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);
}
}

View File

@ -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<BusiNoticeSign> list = busiNoticeSignService.list();
BusiNotice notice = noticeService.getById(noticeId);
AppNoticeQuery query =new AppNoticeQuery();
query.setNoticeId(noticeId);
query.setStatus("02");
Page<BusiNoticeSign> page = new Page<>(1, 5000);
List<BusiNoticeSign> records = noticeService.reportList(query, page).getRecords();
ExcelUtil<BusiNoticeSign> util = new ExcelUtil<BusiNoticeSign>(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();
}
}

View File

@ -133,6 +133,7 @@ public class BusiNotice extends DlBaseEntity
@Excel(name = "微信号")
private String wechat;
/** 电话 */
@Excel(name = "电话")
private String tel;

View File

@ -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;
}

View File

@ -6,6 +6,7 @@ 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;
@ -21,6 +22,10 @@ public interface BusiNoticeSignMapper extends BaseMapper<BusiNoticeSign>
{
IPage<BusiNoticeSign> queryListPage(@Param("entity") BusiNoticeSign entity, Page<BusiNoticeSign> page);
IPage<BusiNoticeSign> reportList( Page<BusiNoticeSign> page,@Param("entity") AppNoticeQuery entity);
/**
* 查某些公告的报名数量
* @author vinjor-M

View File

@ -36,5 +36,6 @@ public class AppNoticeQuery {
//'进行中', '已关闭'
private String status;
private String noticeId;
}

View File

@ -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<BusiNotice>
IPage<BusiNoticeVo> myNoticeList(AppNoticeQuery query, Page<BusiNotice> page);
IPage<BusiNoticeVo> myPublishNoticeList(AppNoticeQuery query, Page<BusiNotice> page);
IPage<BusiNoticeSign> reportList(AppNoticeQuery query, Page<BusiNoticeSign> page);
JSONObject reportNum(AppNoticeQuery query);

View File

@ -17,4 +17,5 @@ public interface IBusiNoticeSignService extends IService<BusiNoticeSign>
{
IPage<BusiNoticeSign> queryListPage(BusiNoticeSign pageReqVO, Page<BusiNoticeSign> page);
void userSign(AppNoticeSign appNoticeSign) throws Exception;
void chooseSign(String[] signIds);
}

View File

@ -589,5 +589,29 @@ public class BusiNoticeServiceImpl extends ServiceImpl<BusiNoticeMapper,BusiNoti
return pageList;
}
@Override
public IPage<BusiNoticeSign> reportList(AppNoticeQuery query, Page<BusiNoticeSign> page) {
return busiNoticeSignMapper.reportList(page,query);
}
/**
* 获取报名数量
* @param query
* @return
*/
@Override
public JSONObject reportNum(AppNoticeQuery query) {
JSONObject result = new JSONObject();
LambdaQueryWrapper<BusiNoticeSign> 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;
}
}

View File

@ -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<BusiNoticeSignMapper,
{
@Autowired
private BusiNoticeSignMapper busiNoticeSignMapper;
@Autowired
private ISysUserService userService;
@Override
public IPage<BusiNoticeSign> queryListPage(BusiNoticeSign pageReqVO, Page<BusiNoticeSign> page) {
@ -46,13 +50,14 @@ public class BusiNoticeSignServiceImpl extends ServiceImpl<BusiNoticeSignMapper,
if(busiNoticeSignMapper.selectOne(new LambdaQueryWrapper<BusiNoticeSign>().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<BusiNoticeSignMapper,
busiNoticeSignMapper.insert(busiNoticeSign);
}
}
/**
* 选中报名通过
* @param signIds
*/
@Override
public void chooseSign(String[] signIds) {
for (String signId : signIds) {
BusiNoticeSign noticeSign = this.getById(signId);
noticeSign.setStatus("02");
this.updateById(noticeSign);
}
}
}

View File

@ -0,0 +1,162 @@
package com.ruoyi.payConfig;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
import com.wechat.pay.contrib.apache.httpclient.exception.HttpCodeException;
import com.wechat.pay.contrib.apache.httpclient.exception.NotFoundException;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
/**
* @Author:
* @Description:
**/
@Component
@Data
@Slf4j
@ConfigurationProperties(prefix = "wxpay")
public class WechatPayConfig {
/**
* 应用编号
*/
private String appId;
/**
* 商户号
*/
private String mchId;
/**
* 服务商商户号
*/
private String slMchId;
/**
* APIv2密钥
*/
private String apiKey;
/**
* APIv3密钥
*/
private String apiV3Key;
/**
* 支付通知回调地址
*/
private String notifyUrl;
/**
* 退款回调地址
*/
private String refundNotifyUrl;
/**
* API 证书中的 key.pem
*/
private String keyPemPath;
/**
* 商户序列号
*/
private String serialNo;
private String appSecret;
/**
* 微信支付V3-url前缀
*/
private String baseUrl;
/**
* 获取商户的私钥文件
* @param keyPemPath
* @return
*/
public PrivateKey getPrivateKey(String keyPemPath){
log.info("进入获取");
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(keyPemPath);
if(inputStream==null){
log.info("私钥文件不存在");
throw new RuntimeException("私钥文件不存在");
}
log.info("存在");
return PemUtil.loadPrivateKey(inputStream);
}
/**
* 获取证书管理器实例
* @return
*/
@Bean
public Verifier getVerifier() throws GeneralSecurityException, IOException, HttpCodeException, NotFoundException {
log.info("获取证书管理器实例");
//获取商户私钥
PrivateKey privateKey = getPrivateKey(keyPemPath);
//私钥签名对象
PrivateKeySigner privateKeySigner = new PrivateKeySigner(serialNo, privateKey);
//身份认证对象
WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId, privateKeySigner);
// 使用定时更新的签名验证器不需要传入证书
CertificatesManager certificatesManager = CertificatesManager.getInstance();
certificatesManager.putMerchant(mchId,wechatPay2Credentials,apiV3Key.getBytes(StandardCharsets.UTF_8));
return certificatesManager.getVerifier(mchId);
}
/**
* 获取支付http请求对象
* @param verifier
* @return
*/
@Bean(name = "wxPayClient")
public CloseableHttpClient getWxPayClient(Verifier verifier) {
//获取商户私钥
PrivateKey privateKey = getPrivateKey(keyPemPath);
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(mchId, serialNo, privateKey)
.withValidator(new WechatPay2Validator(verifier));
// 通过WechatPayHttpClientBuilder构造的HttpClient会自动的处理签名和验签并进行证书自动更新
return builder.build();
}
/**
* 获取HttpClient无需进行应答签名验证跳过验签的流程
*/
@Bean(name = "wxPayNoSignClient")
public CloseableHttpClient getWxPayNoSignClient(){
//获取商户私钥
PrivateKey privateKey = getPrivateKey(keyPemPath);
//用于构造HttpClient
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
//设置商户信息
.withMerchant(mchId, serialNo, privateKey)
//无需进行签名验证通过withValidator((response) -> true)实现
.withValidator((response) -> true);
// 通过WechatPayHttpClientBuilder构造的HttpClient会自动的处理签名和验签并进行证书自动更新
return builder.build();
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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<String, Object> decryptFromResource(String resource,String apiV3Key,Integer type) {
String msg = type==1?"支付成功":"退款成功";
log.info(msg+",回调通知,密文解密");
try {
//通知数据
Map<String, String> resourceMap = JSONObject.parseObject(resource, new TypeReference<Map<String, String>>() {
});
//数据密文
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<Map<String, Object>>(){});
}catch (Exception e){
throw new RuntimeException("回调参数,解密失败!");
}
}
}

View File

@ -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-----

View File

@ -38,6 +38,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="entity.tel != null and entity.tel != ''"> and tel like concat('%', #{entity.tel}, '%')</if>
<if test="entity.status != null and entity.status != ''"> and status = #{entity.status}</if>
</where>
order by isSuper desc, create_time asc
</select>
<select id="selectReportNumByIdList" resultType="java.util.Map">
SELECT
@ -54,4 +55,17 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
GROUP BY
dbns.id
</select>
<select id="reportList" resultType="com.ruoyi.busi.domain.BusiNoticeSign">
select sign.*,card.platform_name as platformName,
card.account_name as accountName,card.fans_num as fansNum
,card.platform_code as platformCode
from dl_busi_notice_sign sign
inner join dl_member_busi_card card on sign.card_id = card.id
<where>
<if test="entity.noticeId != null and entity.noticeId != ''"> and sign.notice_id like concat('%', #{entity.noticeId}, '%')</if>
<if test="entity.status != null and entity.status != ''"> and sign.status = #{entity.status}</if>
</where>
order by sign.is_super desc, sign.create_time asc
</select>
</mapper>

View File

@ -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;
}
}

View File

@ -16,6 +16,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="password" column="password" />
<result property="status" column="status" />
<result property="openId" column="open_id" />
<result property="wxOpenId" column="wx_open_id" />
<result property="unionId" column="union_id" />
<result property="delFlag" column="del_flag" />
<result property="loginIp" column="login_ip" />
<result property="loginDate" column="login_date" />
@ -48,7 +50,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</resultMap>
<sql id="selectUserVo">
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
@ -58,7 +60,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</sql>
<select id="selectUserList" parameterType="SysUser" resultMap="SysUserResult">
select u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar, u.phonenumber, u.sex, u.status,u.open_id, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark, d.dept_name, d.leader from sys_user u
select u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar, u.phonenumber, 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_name, d.leader from sys_user u
left join sys_dept d on u.dept_id = d.dept_id
where u.del_flag = '0'
<if test="userId != null and userId != 0">
@ -87,7 +89,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</select>
<select id="selectAllocatedList" parameterType="SysUser" resultMap="SysUserResult">
select distinct u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.phonenumber, u.status,u.open_id, u.create_time
select distinct u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.phonenumber, u.status,u.open_id,u.wx_open_id,u.union_id, u.create_time
from sys_user u
left join sys_dept d on u.dept_id = d.dept_id
left join sys_user_role ur on u.user_id = ur.user_id
@ -104,7 +106,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</select>
<select id="selectUnallocatedList" parameterType="SysUser" resultMap="SysUserResult">
select distinct u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.phonenumber, u.status,u.open_id, u.create_time
select distinct u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.phonenumber, u.status,u.open_id,u.wx_open_id,u.union_id, u.create_time
from sys_user u
left join sys_dept d on u.dept_id = d.dept_id
left join sys_user_role ur on u.user_id = ur.user_id
@ -156,6 +158,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="password != null and password != ''">password,</if>
<if test="status != null and status != ''">status,</if>
<if test="openId != null and openId != ''">open_id,</if>
<if test="wxOpenId != null and wxOpenId != ''">wx_open_id,</if>
<if test="unionId != null and unionId != ''">union_id,</if>
<if test="createBy != null and createBy != ''">create_by,</if>
<if test="remark != null and remark != ''">remark,</if>
create_time
@ -171,6 +175,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="password != null and password != ''">#{password},</if>
<if test="status != null and status != ''">#{status},</if>
<if test="openId != null and openId != ''">#{openId},</if>
<if test="wxOpenId != null and wxOpenId != ''">#{wxOpenId},</if>
<if test="unionId != null and unionId != ''">#{unionId},</if>
<if test="createBy != null and createBy != ''">#{createBy},</if>
<if test="remark != null and remark != ''">#{remark},</if>
sysdate()
@ -189,6 +195,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="password != null and password != ''">password = #{password},</if>
<if test="status != null and status != ''">status = #{status},</if>
<if test="openId != null and openId != ''">open_id = #{openId},</if>
<if test="wxOpenId != null and wxOpenId != ''">wx_open_id = #{wxOpenId},</if>
<if test="unionId != null and unionId != ''">union_id =#{unionId},</if>
<if test="loginIp != null and loginIp != ''">login_ip = #{loginIp},</if>
<if test="loginDate != null">login_date = #{loginDate},</if>
<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
@ -226,6 +235,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="openId!=null and openId!=''">
and u.open_id = #{openId}
</if>
<if test="wxOpenId!=null and wxOpenId!=''">
and u.wx_open_id = #{wxOpenId}
</if>
<if test="unionId!=null and unionId!=''">
and u.union_id = #{unionId}
</if>
<if test="phone!=null and phone!=''">
and u.user_name = #{phone}
</if>