From d615fc515fc52d6ed970c11d59a017e48de4be32 Mon Sep 17 00:00:00 2001
From: zj <1772600164@qq.com>
Date: Tue, 16 Jun 2026 16:43:58 +0800
Subject: [PATCH] 1
---
src/main/java/com/nq/service/impl/SiteLoginLogServiceImpl.java | 45 +
src/main/java/com/nq/service/impl/UserStockSubscribeServiceImpl.java | 7
src/main/java/com/nq/utils/TradingDayUtil.java | 72 ++
sql/update_buy_fee.sql | 2
src/main/java/com/nq/service/IPayService.java | 6
src/main/java/com/nq/service/impl/UserPositionServiceImpl.java | 282 +++++---
pom.xml | 6
src/main/java/com/nq/utils/sms/SmsConstants.java | 20
src/main/java/com/nq/service/impl/UserServiceImpl.java | 75 +
src/main/java/com/nq/config/MyCorsFilter.java | 78 +
src/main/java/com/nq/pojo/SiteSetting.java | 14
src/main/resources/application.properties | 20
src/main/java/com/nq/service/impl/UserRechargeServiceImpl.java | 9
src/main/java/com/nq/utils/PhoneUtil.java | 77 ++
src/main/java/com/nq/utils/TradeFeeUtil.java | 20
src/main/java/com/nq/service/impl/SmsServiceImpl.java | 136 +++
src/main/java/com/nq/utils/pay/AlipayPayUtil.java | 208 ++++++
src/main/java/com/nq/utils/task/stock/ClosingStayTask.java | 64 -
src/main/java/com/nq/utils/RechargeAmtValidator.java | 34 +
src/main/java/com/nq/utils/sms/SmsBaoClient.java | 93 ++
src/main/java/com/nq/utils/StayFeeUtil.java | 151 ++++
src/main/java/com/nq/service/impl/UserWithdrawServiceImpl.java | 3
src/main/java/com/nq/service/impl/UserIndexPositionServiceImpl.java | 7
src/main/java/com/nq/service/impl/PayServiceImpl.java | 117 +++
src/main/java/com/nq/vo/position/UserPositionVO.java | 30
src/main/resources/mapper/SiteSettingMapper.xml | 21
sql/add_charge_max_amt.sql | 5
src/main/java/com/nq/controller/VerifyCodeController.java | 47 +
src/main/java/com/nq/pojo/SiteProduct.java | 2
src/main/java/com/nq/utils/captcha/CaptchaUtil.java | 45 +
src/main/java/com/nq/controller/SmsApiController.java | 43
src/main/java/com/nq/utils/ip/IpUtils.java | 25
src/main/java/com/nq/service/impl/SiteSettingServiceImpl.java | 8
src/main/java/com/nq/vo/pay/AlipayPayVO.java | 14
src/main/java/com/nq/service/impl/UserFuturesPositionServiceImpl.java | 5
src/main/java/com/nq/controller/PayApiController.java | 26
src/main/java/com/nq/controller/protol/UserPayController.java | 7
src/main/java/com/nq/service/ISmsService.java | 7
src/main/java/com/nq/utils/smsUtil/smsUtil.java | 33
src/main/java/com/nq/service/impl/UserFundsPositionServiceImpl.java | 7
40 files changed, 1,528 insertions(+), 343 deletions(-)
diff --git a/pom.xml b/pom.xml
index 29ac4f2..bef6c69 100644
--- a/pom.xml
+++ b/pom.xml
@@ -233,6 +233,12 @@
<artifactId>pdfbox-tools</artifactId>
<version>2.0.24</version>
</dependency>
+ <!-- 支付宝电脑网站支付 -->
+ <dependency>
+ <groupId>com.alipay.sdk</groupId>
+ <artifactId>alipay-sdk-java</artifactId>
+ <version>4.39.86.ALL</version>
+ </dependency>
</dependencies>
diff --git a/sql/add_charge_max_amt.sql b/sql/add_charge_max_amt.sql
new file mode 100644
index 0000000..85e1eef
--- /dev/null
+++ b/sql/add_charge_max_amt.sql
@@ -0,0 +1,5 @@
+-- 股票风控:最大充值金额
+ALTER TABLE `site_setting`
+ ADD COLUMN `charge_max_amt` int(11) DEFAULT 50000 COMMENT '最大充值金额' AFTER `charge_min_amt`;
+
+UPDATE `site_setting` SET `charge_max_amt` = 50000 WHERE `charge_max_amt` IS NULL;
diff --git a/sql/update_buy_fee.sql b/sql/update_buy_fee.sql
new file mode 100644
index 0000000..edbfe46
--- /dev/null
+++ b/sql/update_buy_fee.sql
@@ -0,0 +1,2 @@
+-- 开仓(买入)手续费改为 0.0035
+UPDATE `site_setting` SET `buy_fee` = 0.0035 WHERE `id` = 1;
diff --git a/src/main/java/com/nq/config/MyCorsFilter.java b/src/main/java/com/nq/config/MyCorsFilter.java
index a5141dd..a9037c6 100644
--- a/src/main/java/com/nq/config/MyCorsFilter.java
+++ b/src/main/java/com/nq/config/MyCorsFilter.java
@@ -1,26 +1,68 @@
package com.nq.config;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
-import org.springframework.web.cors.CorsConfiguration;
-import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
-import org.springframework.web.filter.CorsFilter;
+import org.springframework.core.Ordered;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * 跨域:回显请求 Origin,兼容管理后台/用户端从任意域名访问 API。
+ */
@Configuration
- public class MyCorsFilter{
- private CorsConfiguration corsConfig(){
- CorsConfiguration corsConfiguration = new CorsConfiguration();
- corsConfiguration.addAllowedHeader("*");
- corsConfiguration.addAllowedMethod("*");
- corsConfiguration.addAllowedOrigin("*");
- corsConfiguration.setMaxAge(3600L);
- corsConfiguration.setAllowCredentials(true);
- return corsConfiguration;
+public class MyCorsFilter {
+
+ @Bean
+ public FilterRegistrationBean<Filter> corsFilterRegistration() {
+ FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>();
+ bean.setFilter(new DynamicCorsFilter());
+ bean.addUrlPatterns("/*");
+ bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
+ return bean;
+ }
+
+ static class DynamicCorsFilter implements Filter {
+ @Override
+ public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
+ throws IOException, ServletException {
+ HttpServletRequest request = (HttpServletRequest) req;
+ HttpServletResponse response = (HttpServletResponse) res;
+
+ String origin = request.getHeader("Origin");
+ if (StringUtils.isNotBlank(origin)) {
+ response.setHeader("Access-Control-Allow-Origin", origin);
+ response.setHeader("Access-Control-Allow-Credentials", "true");
+ response.addHeader("Vary", "Origin");
+ }
+
+ response.setHeader("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS");
+
+ // credentials 模式下浏览器不接受 Allow-Headers: *,需回显预检请求头
+ String requestHeaders = request.getHeader("Access-Control-Request-Headers");
+ if (StringUtils.isNotBlank(requestHeaders)) {
+ response.setHeader("Access-Control-Allow-Headers", requestHeaders);
+ } else {
+ response.setHeader("Access-Control-Allow-Headers",
+ "Content-Type, admintoken, USERTOKEN, lang, Authorization, X-Requested-With, Accept, Origin");
+ }
+
+ response.setHeader("Access-Control-Max-Age", "3600");
+
+ if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
+ response.setStatus(HttpServletResponse.SC_OK);
+ return;
+ }
+
+ chain.doFilter(req, res);
}
- @Bean
- public CorsFilter corsFilter(){
- UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
- source.registerCorsConfiguration("/**",corsConfig());
- return new CorsFilter(source);
- }
+ }
}
diff --git a/src/main/java/com/nq/controller/PayApiController.java b/src/main/java/com/nq/controller/PayApiController.java
index 1c90d88..91125bb 100644
--- a/src/main/java/com/nq/controller/PayApiController.java
+++ b/src/main/java/com/nq/controller/PayApiController.java
@@ -189,4 +189,30 @@
log.error("ococn notify error Msg = {}", serverResponse.getMsg());
}
}
+
+ @RequestMapping(value = {"alipayReturn.do"}, method = {RequestMethod.GET, RequestMethod.POST})
+ public void alipayReturn(HttpServletRequest request, HttpServletResponse response) throws IOException {
+ String redirectUrl = this.iPayService.alipayReturn(request);
+ response.setContentType("text/html;charset=UTF-8");
+ response.setCharacterEncoding("UTF-8");
+ String safeUrl = redirectUrl.replace("\\", "\\\\").replace("'", "\\'");
+ response.getWriter().write(
+ "<!DOCTYPE html><html><head><meta charset=\"UTF-8\"><title>跳转中</title>"
+ + "<script>window.location.replace('" + safeUrl + "');</script>"
+ + "</head><body></body></html>"
+ );
+ }
+
+ @RequestMapping(value = {"alipayNotify.do"}, method = {RequestMethod.GET, RequestMethod.POST})
+ @ResponseBody
+ public void alipayNotify(HttpServletRequest request, HttpServletResponse response) throws IOException {
+ ServerResponse serverResponse = this.iPayService.alipayNotify(request);
+ if (serverResponse.isSuccess()) {
+ response.getWriter().write("success");
+ log.info("支付宝支付异步通知处理成功");
+ } else {
+ log.error("alipay notify error Msg = {}", serverResponse.getMsg());
+ response.getWriter().write("failure");
+ }
+ }
}
diff --git a/src/main/java/com/nq/controller/SmsApiController.java b/src/main/java/com/nq/controller/SmsApiController.java
index 8952c49..1627c7f 100644
--- a/src/main/java/com/nq/controller/SmsApiController.java
+++ b/src/main/java/com/nq/controller/SmsApiController.java
@@ -1,27 +1,22 @@
package com.nq.controller;
-
import com.nq.common.ServerResponse;
-
import com.nq.pojo.SiteSmsLog;
import com.nq.service.ISiteSmsLogService;
import com.nq.service.ISmsService;
-
import com.nq.utils.DateTimeUtil;
+import com.nq.utils.PhoneUtil;
+import com.nq.utils.sms.SmsConstants;
import com.nq.utils.smsUtil.smsUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
-
import org.slf4j.LoggerFactory;
-
import org.springframework.beans.factory.annotation.Autowired;
-
import org.springframework.stereotype.Controller;
-
import org.springframework.web.bind.annotation.RequestMapping;
-
import org.springframework.web.bind.annotation.ResponseBody;
+import javax.servlet.http.HttpServletRequest;
@Controller
@RequestMapping({"/api/sms/"})
@@ -34,35 +29,31 @@
@Autowired
ISiteSmsLogService iSiteSmsLogService;
- //注册用户 短信发送
+ /** 注册用户短信发送(需图形验证码) */
@RequestMapping({"sendRegSms.do"})
@ResponseBody
- public ServerResponse sendRegSms(String phoneNum) {
- if (StringUtils.isBlank(phoneNum)) {
- return ServerResponse.createByErrorMsg("发送失败,手机号不能为空");
- }
-
- log.info("smsphone"+phoneNum);
-
- return iSmsService.sendAliyunSMS(phoneNum, "SMS_468990815");
+ public ServerResponse sendRegSms(String phoneNum, String captchaCode, String captchaToken, HttpServletRequest request) {
+ log.info("sendRegSms phone={}", phoneNum);
+ return iSmsService.sendRegSms(phoneNum, captchaCode, captchaToken, request);
}
- //找回密码 短信发送
-// @RequestMapping({"sendForgetSms.do"})
-// @ResponseBody
-// public ServerResponse sendForgetSms(String phoneNum) {
-// return this.iSmsService.sendAliyunSMS(phoneNum, "SMS_174915941");
-// }
- //找回密码 短信发送
+ /** 找回密码短信发送 */
@RequestMapping({"sendForgetSms.do"})
@ResponseBody
public ServerResponse sendForgetSms(String phoneNum) {
+ if (!SmsConstants.isVerifyEnabled()) {
+ return ServerResponse.createBySuccess();
+ }
if (StringUtils.isBlank(phoneNum)) {
return ServerResponse.createByErrorMsg("发送失败,手机号不能为空");
}
+ if (!PhoneUtil.isValidChinaMobile(phoneNum)) {
+ return ServerResponse.createByErrorMsg("请输入正确的手机号码");
+ }
+ phoneNum = PhoneUtil.normalizeChinaMobile(phoneNum);
smsUtil smsUtil = new smsUtil();
- log.info("smsphone"+phoneNum);
+ log.info("sendForgetSms phone={}", phoneNum);
String code = smsUtil.sendForgetSms(phoneNum);
if (!StringUtils.isEmpty(code)) {
SiteSmsLog siteSmsLog = new SiteSmsLog();
@@ -78,6 +69,4 @@
return ServerResponse.createByErrorMsg("短信发送失败,请重试");
}
}
-
-
}
diff --git a/src/main/java/com/nq/controller/VerifyCodeController.java b/src/main/java/com/nq/controller/VerifyCodeController.java
index d5c16df..8290526 100644
--- a/src/main/java/com/nq/controller/VerifyCodeController.java
+++ b/src/main/java/com/nq/controller/VerifyCodeController.java
@@ -1,6 +1,7 @@
package com.nq.controller;
import com.google.code.kaptcha.Producer;
+import com.nq.utils.captcha.CaptchaUtil;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;
@@ -37,7 +38,9 @@
}
@RequestMapping({"getCode.do"})
- public ModelAndView getCode(HttpServletRequest request, HttpServletResponse response, @RequestParam(value = "timestamp", required = false) String timestamp) throws IOException {
+ public ModelAndView getCode(HttpServletRequest request, HttpServletResponse response,
+ @RequestParam(value = "timestamp", required = false) String timestamp,
+ @RequestParam(value = "token", required = false) String token) throws IOException {
ModelAndView mv = new ModelAndView();
if (StringUtils.isEmpty(timestamp)) {
mv.addObject("timestamp", Long.valueOf(System.currentTimeMillis()));
@@ -49,10 +52,23 @@
response.addHeader("Cache-Control", "post-check=0, pre-check=0");
response.setHeader("Pragma", "no-cache");
response.setContentType("image/jpeg");
- String capText = this.captchaProducer.createText();
- HttpSession session = request.getSession();
- session.removeAttribute("KAPTCHA_SESSION_KEY");
- session.setAttribute("KAPTCHA_SESSION_KEY", capText);
+ String capText;
+ if (StringUtils.isNotBlank(token)) {
+ String trimmedToken = token.trim();
+ String existing = CaptchaUtil.get(trimmedToken);
+ if (StringUtils.isNotBlank(existing)) {
+ capText = existing;
+ } else {
+ capText = this.captchaProducer.createText();
+ CaptchaUtil.save(trimmedToken, capText);
+ log.info("图形验证码已写入Redis token={}", trimmedToken);
+ }
+ } else {
+ capText = this.captchaProducer.createText();
+ HttpSession session = request.getSession();
+ session.removeAttribute("KAPTCHA_SESSION_KEY");
+ session.setAttribute("KAPTCHA_SESSION_KEY", capText);
+ }
BufferedImage bi = this.captchaProducer.createImage(capText);
ServletOutputStream out = response.getOutputStream();
ImageIO.write(bi, "jpg", out);
@@ -67,14 +83,21 @@
//验证码
@RequestMapping({"checkCode.do"})
@ResponseBody
- public String checkCode(@RequestParam(value = "timestamp", required = false) String timestamp, @RequestParam(value = "code", required = false) String code, HttpServletRequest request) {
+ public String checkCode(@RequestParam(value = "timestamp", required = false) String timestamp,
+ @RequestParam(value = "code", required = false) String code,
+ @RequestParam(value = "token", required = false) String token,
+ HttpServletRequest request) {
boolean returnStr = false;
- HttpSession session = request.getSession();
- String original = (String) session.getAttribute("KAPTCHA_SESSION_KEY");
- log.info("======用户输入的验证码:" + code);
- log.info("======正确的验证码:" + original);
- if (StringUtils.isNotEmpty(code) && code.equalsIgnoreCase(original)) {
- returnStr = true;
+ if (StringUtils.isNotBlank(token)) {
+ returnStr = CaptchaUtil.verifyAndRemove(token.trim(), code);
+ } else {
+ HttpSession session = request.getSession();
+ String original = (String) session.getAttribute("KAPTCHA_SESSION_KEY");
+ log.info("======用户输入的验证码:" + code);
+ log.info("======正确的验证码:" + original);
+ if (StringUtils.isNotEmpty(code) && code.equalsIgnoreCase(original)) {
+ returnStr = true;
+ }
}
return returnStr + "";
}
diff --git a/src/main/java/com/nq/controller/protol/UserPayController.java b/src/main/java/com/nq/controller/protol/UserPayController.java
index fcdfac8..97c0237 100644
--- a/src/main/java/com/nq/controller/protol/UserPayController.java
+++ b/src/main/java/com/nq/controller/protol/UserPayController.java
@@ -56,4 +56,11 @@
return this.iPayService.ococnPay(payType, payAmt, request);
}
+ @RequestMapping({"alipayPagePay.do"})
+ @ResponseBody
+ public ServerResponse alipayPagePay(@RequestParam("payAmt") String payAmt, HttpServletRequest request) {
+ log.info("发起支付宝网页支付 payAmt = {}", payAmt);
+ return this.iPayService.alipayPagePay(payAmt, request);
+ }
+
}
diff --git a/src/main/java/com/nq/pojo/SiteProduct.java b/src/main/java/com/nq/pojo/SiteProduct.java
index 4ff6710..7d9a858 100644
--- a/src/main/java/com/nq/pojo/SiteProduct.java
+++ b/src/main/java/com/nq/pojo/SiteProduct.java
@@ -45,7 +45,7 @@
private Boolean stockDzDisplay;
/**
- * 节假日开关:1、开启,0、关闭
+ * 节假日开关:1、开启(周末及法定假日本身含落在工作日时休市),0、关闭
*/
private Boolean holidayDisplay;
diff --git a/src/main/java/com/nq/pojo/SiteSetting.java b/src/main/java/com/nq/pojo/SiteSetting.java
index 5896f06..59d6ebc 100644
--- a/src/main/java/com/nq/pojo/SiteSetting.java
+++ b/src/main/java/com/nq/pojo/SiteSetting.java
@@ -25,6 +25,8 @@
private Integer chargeMinAmt;
+ private Integer chargeMaxAmt;
+
private Integer buyMinNum;
private BigDecimal forceStopFee;
@@ -107,7 +109,7 @@
private BigDecimal hkExchangeRate;
- public SiteSetting(Integer id, BigDecimal buyFee, BigDecimal sellFee, BigDecimal stayFee, BigDecimal dutyFee, Integer stayMaxDays, Integer buyMinAmt, Integer chargeMinAmt, Integer buyMinNum, BigDecimal forceStopFee, BigDecimal buyMaxAmtPercent, BigDecimal forceStopPercent, BigDecimal hightAndLow, Integer withMinAmt, BigDecimal creaseMaxPercent, Integer buyMaxNum, Integer withTimeBegin, Integer withTimeEnd, String transAmBegin, String transAmEnd, String transPmBegin, String transPmEnd, String transAmBeginUs, String transAmEndUs, String transPmBeginUs, String transPmEndUs, Integer withFeeSingle, BigDecimal withFeePercent, String siteLever, Integer buySameTimes, Integer buySameNums, Integer buyNumTimes, Integer buyNumLots, Integer cantSellTimes, BigDecimal kcCreaseMaxPercent, Integer stockDays, BigDecimal stockRate, BigDecimal stockChaa, BigDecimal stockChab, BigDecimal stockChac, BigDecimal forceStopRemindRatio, BigDecimal cyCreaseMaxPercent, Byte fallBuyDisplay, String transAmBeginHk, String transAmEndHk, String transPmBeginHk, String transPmEndHk, BigDecimal vipQcMaxAmt, BigDecimal hkExchangeRate) {
+ public SiteSetting(Integer id, BigDecimal buyFee, BigDecimal sellFee, BigDecimal stayFee, BigDecimal dutyFee, Integer stayMaxDays, Integer buyMinAmt, Integer chargeMinAmt, Integer chargeMaxAmt, Integer buyMinNum, BigDecimal forceStopFee, BigDecimal buyMaxAmtPercent, BigDecimal forceStopPercent, BigDecimal hightAndLow, Integer withMinAmt, BigDecimal creaseMaxPercent, Integer buyMaxNum, Integer withTimeBegin, Integer withTimeEnd, String transAmBegin, String transAmEnd, String transPmBegin, String transPmEnd, String transAmBeginUs, String transAmEndUs, String transPmBeginUs, String transPmEndUs, Integer withFeeSingle, BigDecimal withFeePercent, String siteLever, Integer buySameTimes, Integer buySameNums, Integer buyNumTimes, Integer buyNumLots, Integer cantSellTimes, BigDecimal kcCreaseMaxPercent, Integer stockDays, BigDecimal stockRate, BigDecimal stockChaa, BigDecimal stockChab, BigDecimal stockChac, BigDecimal forceStopRemindRatio, BigDecimal cyCreaseMaxPercent, Byte fallBuyDisplay, String transAmBeginHk, String transAmEndHk, String transPmBeginHk, String transPmEndHk, BigDecimal vipQcMaxAmt, BigDecimal hkExchangeRate) {
this.id = id;
this.buyFee = buyFee;
this.sellFee = sellFee;
@@ -116,6 +118,7 @@
this.stayMaxDays = stayMaxDays;
this.buyMinAmt = buyMinAmt;
this.chargeMinAmt = chargeMinAmt;
+ this.chargeMaxAmt = chargeMaxAmt;
this.buyMinNum = buyMinNum;
this.forceStopFee = forceStopFee;
this.buyMaxAmtPercent = buyMaxAmtPercent;
@@ -194,6 +197,10 @@
public void setChargeMinAmt(Integer chargeMinAmt) {
this.chargeMinAmt = chargeMinAmt;
+ }
+
+ public void setChargeMaxAmt(Integer chargeMaxAmt) {
+ this.chargeMaxAmt = chargeMaxAmt;
}
public void setBuyMinNum(Integer buyMinNum) {
@@ -521,6 +528,11 @@
}
+ public Integer getChargeMaxAmt() {
+ return this.chargeMaxAmt;
+ }
+
+
public Integer getBuyMinNum() {
return this.buyMinNum;
}
diff --git a/src/main/java/com/nq/service/IPayService.java b/src/main/java/com/nq/service/IPayService.java
index 0118f77..2314326 100644
--- a/src/main/java/com/nq/service/IPayService.java
+++ b/src/main/java/com/nq/service/IPayService.java
@@ -26,4 +26,10 @@
ServerResponse ococnNotify(HttpServletRequest request);
String ococnReturn(HttpServletRequest request);
+
+ ServerResponse alipayPagePay(String payAmt, HttpServletRequest request);
+
+ ServerResponse alipayNotify(HttpServletRequest request);
+
+ String alipayReturn(HttpServletRequest request);
}
diff --git a/src/main/java/com/nq/service/ISmsService.java b/src/main/java/com/nq/service/ISmsService.java
index a48fd10..aa413a7 100644
--- a/src/main/java/com/nq/service/ISmsService.java
+++ b/src/main/java/com/nq/service/ISmsService.java
@@ -1,8 +1,11 @@
package com.nq.service;
-
import com.nq.common.ServerResponse;
+import javax.servlet.http.HttpServletRequest;
+
public interface ISmsService {
- ServerResponse sendAliyunSMS(String paramString1, String paramString2);
+ ServerResponse sendAliyunSMS(String phoneNum, String aliTemplate);
+
+ ServerResponse sendRegSms(String phoneNum, String captchaCode, String captchaToken, HttpServletRequest request);
}
diff --git a/src/main/java/com/nq/service/impl/PayServiceImpl.java b/src/main/java/com/nq/service/impl/PayServiceImpl.java
index ee87fa9..29ee199 100644
--- a/src/main/java/com/nq/service/impl/PayServiceImpl.java
+++ b/src/main/java/com/nq/service/impl/PayServiceImpl.java
@@ -4,16 +4,22 @@
import com.nq.common.ServerResponse;
import com.nq.dao.UserMapper;
import com.nq.dao.UserRechargeMapper;
+import com.nq.pojo.SiteSetting;
import com.nq.pojo.User;
import com.nq.pojo.UserRecharge;
import com.nq.service.IPayService;
+import com.nq.service.ISiteSettingService;
import com.nq.service.IUserService;
import com.nq.utils.KeyUtils;
import com.nq.utils.PropertiesUtil;
+import com.nq.utils.RechargeAmtValidator;
import com.nq.utils.UserFundUtil;
+import com.nq.utils.pay.AlipayPayUtil;
import com.nq.utils.pay.CmcPayOuterRequestUtil;
import com.nq.utils.pay.CmcPayTool;
import com.nq.utils.pay.OcocnPayUtil;
+import com.alipay.api.AlipayApiException;
+import com.nq.vo.pay.AlipayPayVO;
import com.nq.vo.pay.FlyPayVO;
import com.nq.vo.pay.GuoPayVO;
import com.nq.vo.pay.OcocnPayVO;
@@ -51,6 +57,9 @@
@Autowired
UserMapper userMapper;
+
+ @Autowired
+ ISiteSettingService iSiteSettingService;
private static final String juhe1_key = "vd7omkkexkt7fvl6wm2jl9yan3g79y6i";
@@ -509,6 +518,114 @@
}
@Override
+ public ServerResponse alipayPagePay(String payAmt, HttpServletRequest request) {
+ if (StringUtils.isBlank(payAmt)) {
+ return ServerResponse.createByErrorMsg("参数不能为空");
+ }
+ BigDecimal payAmtBig = new BigDecimal(payAmt);
+ if (payAmtBig.compareTo(BigDecimal.ZERO) <= 0) {
+ return ServerResponse.createByErrorMsg("支付金额必须大于0");
+ }
+ SiteSetting siteSetting = this.iSiteSettingService.getSiteSetting();
+ ServerResponse amtCheck = RechargeAmtValidator.validate(siteSetting, payAmtBig);
+ if (!amtCheck.isSuccess()) {
+ return amtCheck;
+ }
+ String configError = AlipayPayUtil.getConfigError();
+ if (configError != null) {
+ return ServerResponse.createByErrorMsg(configError);
+ }
+
+ User user = this.iUserService.getCurrentRefreshUser(request);
+ if (user == null) {
+ return ServerResponse.createByErrorMsg("请先登录");
+ }
+
+ String apiDomain = trimTrailingSlash(PropertiesUtil.getProperty("website.domain.url"));
+ if (StringUtils.isBlank(apiDomain)) {
+ return ServerResponse.createByErrorMsg("支付配置不完整,请联系管理员");
+ }
+
+ String ordersn = KeyUtils.getRechargeOrderSn();
+ String money = payAmtBig.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString();
+ String notifyUrl = apiDomain + "/api/pay/alipayNotify.do";
+ String returnUrl = apiDomain + "/api/pay/alipayReturn.do";
+ String quitUrl = PropertiesUtil.getProperty("alipay.frontend-redirect",
+ trimTrailingSlash(PropertiesUtil.getProperty("frontend.domain.url", "")) + "/#/rechargelist");
+ String productName = PropertiesUtil.getProperty("alipay.product-name", "账户充值");
+
+ UserRecharge userRecharge = new UserRecharge();
+ userRecharge.setUserId(user.getId());
+ userRecharge.setNickName(user.getRealName());
+ userRecharge.setAgentId(user.getAgentId());
+ userRecharge.setOrderSn(ordersn);
+ userRecharge.setPayChannel("支付宝-WAP");
+ userRecharge.setPayAmt(payAmtBig);
+ userRecharge.setOrderStatus(Integer.valueOf(0));
+ userRecharge.setAddTime(new Date());
+
+ int insertCount = this.userRechargeMapper.insert(userRecharge);
+ if (insertCount <= 0) {
+ return ServerResponse.createByErrorMsg("创建支付订单失败");
+ }
+
+ try {
+ String payForm = AlipayPayUtil.buildWapPayForm(ordersn, money, productName, notifyUrl, returnUrl, quitUrl);
+ AlipayPayVO alipayPayVO = new AlipayPayVO();
+ alipayPayVO.setPayForm(payForm);
+ log.info("支付宝WAP支付,创建订单成功 orderSn={}", ordersn);
+ return ServerResponse.createBySuccess(alipayPayVO);
+ } catch (AlipayApiException e) {
+ log.error("支付宝WAP支付下单失败 orderSn={}", ordersn, e);
+ String msg = e.getMessage();
+ if (msg != null && (msg.contains("私钥") || msg.contains("公钥") || msg.contains("RSA2"))) {
+ return ServerResponse.createByErrorMsg(msg);
+ }
+ return ServerResponse.createByErrorMsg("发起支付宝支付失败,请稍后重试");
+ } catch (Exception e) {
+ log.error("支付宝网页支付下单失败 orderSn={}", ordersn, e);
+ return ServerResponse.createByErrorMsg("发起支付宝支付失败,请稍后重试");
+ }
+ }
+
+ @Override
+ public ServerResponse alipayNotify(HttpServletRequest request) {
+ Map<String, String> params = AlipayPayUtil.parseRequestParams(request);
+ log.info("支付宝支付通知参数: {}", params);
+
+ String outTradeNo = params.get("out_trade_no");
+ String totalAmount = params.get("total_amount");
+ String tradeStatus = params.get("trade_status");
+
+ if (StringUtils.isAnyBlank(outTradeNo, totalAmount, tradeStatus)) {
+ return ServerResponse.createByErrorMsg("回调参数不完整");
+ }
+ if (!AlipayPayUtil.verifyNotify(params)) {
+ log.error("支付宝支付通知签名校验失败, params={}", params);
+ return ServerResponse.createByErrorMsg("签名校验失败");
+ }
+ if (!"TRADE_SUCCESS".equals(tradeStatus) && !"TRADE_FINISHED".equals(tradeStatus)) {
+ return ServerResponse.createByErrorMsg("支付未成功");
+ }
+
+ UserRecharge existing = this.userRechargeMapper.findUserRechargeByOrderSn(outTradeNo);
+ if (existing != null && existing.getOrderStatus().intValue() != 0) {
+ return ServerResponse.createBySuccessMsg("订单已处理");
+ }
+ return doSuccess(outTradeNo, totalAmount);
+ }
+
+ @Override
+ public String alipayReturn(HttpServletRequest request) {
+ Map<String, String> params = AlipayPayUtil.parseRequestParams(request);
+ log.info("支付宝支付同步跳转: {}", params);
+ return StringUtils.defaultIfBlank(
+ PropertiesUtil.getProperty("alipay.frontend-redirect"),
+ trimTrailingSlash(PropertiesUtil.getProperty("frontend.domain.url", "")) + "/#/rechargelist"
+ );
+ }
+
+ @Override
public ServerResponse juhenewpayNotify(HttpServletRequest request) throws UnsupportedEncodingException {
LinkedMap map = new LinkedMap();
String out_trade_no = request.getParameter("out_trade_no");
diff --git a/src/main/java/com/nq/service/impl/SiteLoginLogServiceImpl.java b/src/main/java/com/nq/service/impl/SiteLoginLogServiceImpl.java
index c2a29ca..9af3f9b 100644
--- a/src/main/java/com/nq/service/impl/SiteLoginLogServiceImpl.java
+++ b/src/main/java/com/nq/service/impl/SiteLoginLogServiceImpl.java
@@ -15,6 +15,7 @@
import java.util.List;
import javax.servlet.http.HttpServletRequest;
+import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -32,16 +33,42 @@
public ServerResponse saveLog(User user, HttpServletRequest request) {
- SiteLoginLog siteLoginLog = new SiteLoginLog();
- siteLoginLog.setUserId(user.getId());
- siteLoginLog.setUserName(user.getRealName());
- //String ips = IpUtils.getIp(request);
+ try {
+ SiteLoginLog siteLoginLog = new SiteLoginLog();
+ siteLoginLog.setUserId(user.getId());
+ String userName = user.getRealName();
+ if (StringUtils.isBlank(userName)) {
+ userName = user.getPhone();
+ }
+ if (StringUtils.isBlank(userName)) {
+ userName = String.valueOf(user.getId());
+ }
+ siteLoginLog.setUserName(userName);
- siteLoginLog.setLoginIp("--");
- //String ip_address = JuheIpApi.ip2Add(ips);
- siteLoginLog.setLoginAddress("未知");
- siteLoginLog.setAddTime(new Date());
- this.siteLoginLogMapper.insert(siteLoginLog);
+ String ips = IpUtils.getIp(request);
+ if (StringUtils.isBlank(ips)) {
+ ips = "--";
+ }
+ siteLoginLog.setLoginIp(ips);
+
+ String ipAddress = "未知";
+ if (!"--".equals(ips)) {
+ try {
+ ipAddress = JuheIpApi.ip2Add(ips);
+ if (StringUtils.isBlank(ipAddress)) {
+ ipAddress = "未知";
+ }
+ } catch (Exception e) {
+ log.warn("登录IP地址解析失败 ip={}", ips, e);
+ }
+ }
+ siteLoginLog.setLoginAddress(ipAddress);
+ siteLoginLog.setAddTime(new Date());
+ this.siteLoginLogMapper.insert(siteLoginLog);
+ log.info("登录日志记录成功 userId={} ip={} address={}", user.getId(), ips, ipAddress);
+ } catch (Exception e) {
+ log.error("登录日志记录失败 userId={}", user.getId(), e);
+ }
return ServerResponse.createBySuccess();
}
diff --git a/src/main/java/com/nq/service/impl/SiteSettingServiceImpl.java b/src/main/java/com/nq/service/impl/SiteSettingServiceImpl.java
index bc165ba..50ae9ed 100644
--- a/src/main/java/com/nq/service/impl/SiteSettingServiceImpl.java
+++ b/src/main/java/com/nq/service/impl/SiteSettingServiceImpl.java
@@ -35,7 +35,9 @@
if (list.size() > 0) {
siteSetting = (SiteSetting) list.get(0);
- siteSetting.setBuyFee(TradeFeeUtil.BUY_FEE_RATE);
+ if (siteSetting.getBuyFee() == null) {
+ siteSetting.setBuyFee(TradeFeeUtil.BUY_FEE_RATE);
+ }
}
return siteSetting;
@@ -50,7 +52,9 @@
if (siteSetting == null) {
return ServerResponse.createByErrorMsg("查不到设置记录");
}
- setting.setBuyFee(TradeFeeUtil.BUY_FEE_RATE);
+ if (setting.getBuyFee() == null) {
+ setting.setBuyFee(TradeFeeUtil.BUY_FEE_RATE);
+ }
int updateCount = this.siteSettingMapper.updateByPrimaryKeySelective(setting);
diff --git a/src/main/java/com/nq/service/impl/SmsServiceImpl.java b/src/main/java/com/nq/service/impl/SmsServiceImpl.java
index ce18fed..dee4544 100644
--- a/src/main/java/com/nq/service/impl/SmsServiceImpl.java
+++ b/src/main/java/com/nq/service/impl/SmsServiceImpl.java
@@ -1,14 +1,20 @@
package com.nq.service.impl;
-
-import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
import com.nq.common.ServerResponse;
import com.nq.dao.SiteSmsLogMapper;
+import com.nq.dao.UserMapper;
+import com.nq.pojo.SiteInfo;
import com.nq.pojo.SiteSmsLog;
+import com.nq.pojo.User;
+import com.nq.service.ISiteInfoService;
import com.nq.service.ISmsService;
import com.nq.utils.DateTimeUtil;
+import com.nq.utils.PhoneUtil;
+import com.nq.utils.PropertiesUtil;
import com.nq.utils.redis.RedisShardedPoolUtils;
-import com.nq.utils.sms.ali.AliyunSms;
+import com.nq.utils.captcha.CaptchaUtil;
+import com.nq.utils.sms.SmsBaoClient;
+import com.nq.utils.sms.SmsConstants;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
@@ -16,46 +22,114 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
-
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
@Service("iSmsService")
public class SmsServiceImpl implements ISmsService {
private static final Logger log = LoggerFactory.getLogger(SmsServiceImpl.class);
-
@Autowired
SiteSmsLogMapper siteSmsLogMapper;
- public ServerResponse sendAliyunSMS(String phoneNum, String ali_template) {
- System.out.println(phoneNum);
- if (StringUtils.isBlank(phoneNum)) return ServerResponse.createByErrorMsg("发送失败,手机号不能为空");
+ @Autowired
+ UserMapper userMapper;
+
+ @Autowired
+ ISiteInfoService iSiteInfoService;
+
+ @Override
+ public ServerResponse sendRegSms(String phoneNum, String captchaCode, String captchaToken, HttpServletRequest request) {
+ if (!SmsConstants.isVerifyEnabled()) {
+ return ServerResponse.createBySuccess();
+ }
+ if (StringUtils.isBlank(phoneNum)) {
+ return ServerResponse.createByErrorMsg("发送失败,手机号不能为空");
+ }
+ if (!PhoneUtil.isValidChinaMobile(phoneNum)) {
+ return ServerResponse.createByErrorMsg("请输入正确的手机号码");
+ }
+ if (!verifyCaptcha(request, captchaCode, captchaToken)) {
+ return ServerResponse.createByErrorMsg("图形验证码错误");
+ }
+
+ String phone = PhoneUtil.normalizeChinaMobile(phoneNum);
+ User exist = userMapper.findByPhone(phone);
+ if (exist != null) {
+ return ServerResponse.createByErrorMsg("该手机号已注册");
+ }
+
+ String lockKey = SmsConstants.SMS_SEND_LOCK_PREFIX + phone;
+ if (StringUtils.isNotBlank(RedisShardedPoolUtils.get(lockKey))) {
+ return ServerResponse.createByErrorMsg("请60秒后再试");
+ }
String yzmCode = RandomStringUtils.randomNumeric(4);
- System.out.println("验证码:" + yzmCode);
+ String signName = resolveSignName();
+ String content = "【" + signName + "】您的验证码是" + yzmCode + "。如非本人操作,请忽略本短信";
- SendSmsResponse response = null;
+ boolean sent = SmsBaoClient.send(phone, content);
+ if (!sent) {
+ return ServerResponse.createByErrorMsg("短信发送失败,请稍后重试");
+ }
+
+ String codeKey = SmsConstants.SMS_CODE_PREFIX + phone;
+ RedisShardedPoolUtils.setEx(codeKey, yzmCode, SmsConstants.SMS_CODE_TTL_SECONDS);
+ RedisShardedPoolUtils.setEx(lockKey, "1", SmsConstants.SMS_SEND_INTERVAL_SECONDS);
+
+ SiteSmsLog siteSmsLog = new SiteSmsLog();
+ siteSmsLog.setSmsPhone(phone);
+ siteSmsLog.setSmsTitle("注册验证码");
+ siteSmsLog.setSmsCnt(yzmCode);
+ siteSmsLog.setSmsTemplate("smsbao-reg");
+ siteSmsLog.setSmsStatus(Integer.valueOf(0));
+ siteSmsLog.setAddTime(DateTimeUtil.getCurrentDate());
+ siteSmsLogMapper.insert(siteSmsLog);
+
+ log.info("注册短信发送成功 phone={}", phone);
+ return ServerResponse.createBySuccessMsg("发送成功");
+ }
+
+ @Override
+ public ServerResponse sendAliyunSMS(String phoneNum, String aliTemplate) {
+ return ServerResponse.createByErrorMsg("请先完成图形验证码校验");
+ }
+
+ private boolean verifyCaptcha(HttpServletRequest request, String captchaCode, String captchaToken) {
+ if (StringUtils.isBlank(captchaCode)) {
+ return false;
+ }
+ if (StringUtils.isNotBlank(captchaToken)) {
+ boolean ok = CaptchaUtil.verifyAndRemove(captchaToken.trim(), captchaCode);
+ if (!ok) {
+ log.warn("图形验证码校验失败(Redis) token={}", captchaToken);
+ }
+ return ok;
+ }
+ if (request == null) {
+ return false;
+ }
+ HttpSession session = request.getSession();
+ String original = (String) session.getAttribute("KAPTCHA_SESSION_KEY");
+ session.removeAttribute("KAPTCHA_SESSION_KEY");
+ return StringUtils.isNotEmpty(original) && captchaCode.trim().equalsIgnoreCase(original.trim());
+ }
+
+ private String resolveSignName() {
+ String configured = PropertiesUtil.getProperty("smsbao.sign.name");
+ if (StringUtils.isNotBlank(configured)) {
+ return configured.trim();
+ }
try {
- response = AliyunSms.sendSms(phoneNum, "夜喵科技", ali_template, yzmCode);
+ ServerResponse resp = iSiteInfoService.get();
+ if (resp.isSuccess() && resp.getData() != null) {
+ SiteInfo siteInfo = (SiteInfo) resp.getData();
+ if (StringUtils.isNotBlank(siteInfo.getSiteName())) {
+ return siteInfo.getSiteName().trim();
+ }
+ }
} catch (Exception e) {
- log.error("发送短信异常:{}", e);
+ log.warn("读取站点名称失败,使用默认短信签名", e);
}
- System.out.println("短信接口返回的数据----------------");
- System.out.println("Code=" + response.getCode());
- System.out.println("Message=" + response.getMessage());
- System.out.println("RequestId=" + response.getRequestId());
- System.out.println("BizId=" + response.getBizId());
- if (response.getCode() != null && response.getCode().equals("OK")) {
- String keys = "AliyunSmsCode:" + phoneNum;
- RedisShardedPoolUtils.setEx(keys, yzmCode, 5400);
- SiteSmsLog siteSmsLog = new SiteSmsLog();
- siteSmsLog.setSmsPhone(phoneNum);
- siteSmsLog.setSmsTitle("注册验证码");
- siteSmsLog.setSmsCnt(yzmCode);
- siteSmsLog.setSmsTemplate(ali_template);
- siteSmsLog.setSmsStatus(Integer.valueOf(0));
- siteSmsLog.setAddTime(DateTimeUtil.getCurrentDate());
- this.siteSmsLogMapper.insert(siteSmsLog);
- return ServerResponse.createBySuccessMsg("发送成功");
- }
- return ServerResponse.createByErrorMsg("短信发送失败,请重试");
+ return "滁州炬亿科技";
}
}
diff --git a/src/main/java/com/nq/service/impl/UserFundsPositionServiceImpl.java b/src/main/java/com/nq/service/impl/UserFundsPositionServiceImpl.java
index 1c6c174..56355b4 100644
--- a/src/main/java/com/nq/service/impl/UserFundsPositionServiceImpl.java
+++ b/src/main/java/com/nq/service/impl/UserFundsPositionServiceImpl.java
@@ -9,6 +9,7 @@
import com.nq.dao.UserMapper;
import com.nq.pojo.*;
import com.nq.service.*;
+import com.nq.utils.TradingDayUtil;
import com.nq.utils.DateTimeUtil;
import com.nq.utils.KeyUtils;
import com.nq.utils.stock.BuyAndSellUtils;
@@ -135,7 +136,7 @@
if (siteProduct.getRealNameDisplay() && (StringUtils.isBlank(user.getRealName()) || StringUtils.isBlank(user.getIdCard()))) {
return ServerResponse.createByErrorMsg("下单失败,请先实名认证");
}
- if(siteProduct.getHolidayDisplay()){
+ if(TradingDayUtil.shouldBlockTradingToday(siteProduct)){
return ServerResponse.createByErrorMsg("周末或节假日不能交易!");
}
BigDecimal user_enable_amt = user.getEnableAmt();
@@ -341,7 +342,7 @@
userPosition.setIsLock(Integer.valueOf(0));
userPosition.setOrderLever(lever);
userPosition.setOrderTotalPrice(buy_amt);
- BigDecimal buy_fee_amt = com.nq.utils.TradeFeeUtil.calcBuyFee(buy_amt);
+ BigDecimal buy_fee_amt = com.nq.utils.TradeFeeUtil.calcBuyFee(buy_amt, siteSetting.getBuyFee());
log.info("用户购买手续费(配资后总资金 * 百分比) = {}", buy_fee_amt);
userPosition.setOrderFee(buy_fee_amt);
@@ -429,7 +430,7 @@
if (siteProduct.getRealNameDisplay() && user.getIsLock().intValue() == 1) {
return ServerResponse.createByErrorMsg("平仓失败,用户已被锁定");
}
- if(siteProduct.getHolidayDisplay()){
+ if(TradingDayUtil.shouldBlockTradingToday(siteProduct)){
return ServerResponse.createByErrorMsg("周末或节假日不能交易!");
}
diff --git a/src/main/java/com/nq/service/impl/UserFuturesPositionServiceImpl.java b/src/main/java/com/nq/service/impl/UserFuturesPositionServiceImpl.java
index 3a9422d..182ff52 100644
--- a/src/main/java/com/nq/service/impl/UserFuturesPositionServiceImpl.java
+++ b/src/main/java/com/nq/service/impl/UserFuturesPositionServiceImpl.java
@@ -8,6 +8,7 @@
import com.github.pagehelper.PageInfo;
import com.google.common.collect.Lists;
import com.nq.common.ServerResponse;
+import com.nq.utils.TradingDayUtil;
import com.nq.utils.DateTimeUtil;
import com.nq.utils.KeyUtils;
import com.nq.utils.stock.BuyAndSellUtils;
@@ -76,7 +77,7 @@
return ServerResponse.createByErrorMsg("下单失败,请先实名认证");
}
- if(siteProduct.getHolidayDisplay()){
+ if(TradingDayUtil.shouldBlockTradingToday(siteProduct)){
return ServerResponse.createByErrorMsg("周末或节假日不能交易!");
}
@@ -286,7 +287,7 @@
if (siteProduct.getRealNameDisplay() && user.getIsLock().intValue() == 1) {
return ServerResponse.createByErrorMsg("操作失败,用户已被锁定");
}
- if(siteProduct.getHolidayDisplay()){
+ if(TradingDayUtil.shouldBlockTradingToday(siteProduct)){
return ServerResponse.createByErrorMsg("周末或节假日不能交易!");
}
diff --git a/src/main/java/com/nq/service/impl/UserIndexPositionServiceImpl.java b/src/main/java/com/nq/service/impl/UserIndexPositionServiceImpl.java
index 6fba400..88bc539 100644
--- a/src/main/java/com/nq/service/impl/UserIndexPositionServiceImpl.java
+++ b/src/main/java/com/nq/service/impl/UserIndexPositionServiceImpl.java
@@ -8,6 +8,7 @@
import com.github.pagehelper.PageInfo;
import com.google.common.collect.Lists;
import com.nq.common.ServerResponse;
+import com.nq.utils.TradingDayUtil;
import com.nq.utils.CurrencyUtils;
import com.nq.utils.DateTimeUtil;
import com.nq.utils.KeyUtils;
@@ -83,7 +84,7 @@
return ServerResponse.createByErrorMsg("下单失败,请先实名认证");
}
- if(siteProduct.getHolidayDisplay()){
+ if(TradingDayUtil.shouldBlockTradingToday(siteProduct)){
return ServerResponse.createByErrorMsg("周末或节假日不能交易!");
}
@@ -267,7 +268,7 @@
return ServerResponse.createByErrorMsg("下单失败,请先实名认证");
}
- if(siteProduct.getHolidayDisplay()){
+ if(TradingDayUtil.shouldBlockTradingToday(siteProduct)){
return ServerResponse.createByErrorMsg("周末或节假日不能交易!");
}
@@ -574,7 +575,7 @@
if (siteProduct.getRealNameDisplay() && user.getIsLock().intValue() == 1) {
return ServerResponse.createByErrorMsg("平仓失败,用户已被锁定");
}
- if(siteProduct.getHolidayDisplay()){
+ if(TradingDayUtil.shouldBlockTradingToday(siteProduct)){
return ServerResponse.createByErrorMsg("周末或节假日不能交易!");
}
if (userIndexPosition.getSellOrderPrice() != null) {
diff --git a/src/main/java/com/nq/service/impl/UserPositionServiceImpl.java b/src/main/java/com/nq/service/impl/UserPositionServiceImpl.java
index c37f859..005c265 100644
--- a/src/main/java/com/nq/service/impl/UserPositionServiceImpl.java
+++ b/src/main/java/com/nq/service/impl/UserPositionServiceImpl.java
@@ -11,6 +11,7 @@
import com.google.common.collect.Lists;
import com.nq.common.ServerResponse;
import com.nq.utils.*;
+import com.nq.utils.StayFeeUtil;
import com.nq.utils.TradeFeeUtil;
import com.nq.utils.redis.JsonUtil;
import com.nq.utils.redis.RedisShardedPoolUtils;
@@ -181,7 +182,7 @@
if (!am_flag && !pm_flag) {
return ServerResponse.createByErrorMsg("下单失败,不在港股股交易时段内");
}
- if (siteProduct.getHolidayDisplay()) {
+ if (TradingDayUtil.shouldBlockTradingToday(siteProduct)) {
return ServerResponse.createByErrorMsg("周末或节假日不能交易!");
}
} else {
@@ -196,7 +197,7 @@
if (!am_flag && !pm_flag) {
return ServerResponse.createByErrorMsg("下单失败,不在交易时段内");
}
- if (siteProduct.getHolidayDisplay()) {
+ if (TradingDayUtil.shouldBlockTradingToday(siteProduct)) {
return ServerResponse.createByErrorMsg("周末或节假日不能交易!");
}
}
@@ -388,7 +389,7 @@
}
- BigDecimal buy_fee_amt = TradeFeeUtil.calcBuyFee(buy_amt_autual);
+ BigDecimal buy_fee_amt = TradeFeeUtil.calcBuyFee(buy_amt_autual, siteSetting.getBuyFee());
BigDecimal buy_debit = TradeFeeUtil.calcBuyDebit(buy_amt_autual, buy_fee_amt);
int compareUserAmtInt = user_enable_amt.compareTo(buy_debit);
log.info("用户可用金额 = {} 下单扣款(保证金+手续费) = {}", user_enable_amt, buy_debit);
@@ -499,8 +500,10 @@
@Override
public ServerResponse fee(Integer buyNum, BigDecimal nowPrice, Integer lever) {
int leverValue = (lever == null || lever <= 0) ? 1 : lever;
+ SiteSetting siteSetting = this.iSiteSettingService.getSiteSetting();
BigDecimal buy_amt = nowPrice.multiply(new BigDecimal(buyNum.intValue()));
- BigDecimal buy_fee_amt = TradeFeeUtil.calcBuyFeeByNotional(buy_amt, leverValue).setScale(2, 4);
+ BigDecimal buy_fee_amt = TradeFeeUtil.calcBuyFeeByNotional(buy_amt, leverValue,
+ siteSetting != null ? siteSetting.getBuyFee() : null).setScale(2, RoundingMode.HALF_UP);
return ServerResponse.createBySuccess(buy_fee_amt);
}
@@ -656,7 +659,7 @@
if (!am_flag && !pm_flag && siteProduct.getTranWithdrawDisplay()) {
return ServerResponse.createByErrorMsg("挂单失败,不在交易时段内");
}
- if (siteProduct.getHolidayDisplay() && siteProduct.getTranWithdrawDisplay()) {
+ if (siteProduct.getTranWithdrawDisplay() && TradingDayUtil.shouldBlockTradingToday(siteProduct)) {
return ServerResponse.createByErrorMsg("周末或节假日不能交易!");
}
@@ -795,7 +798,7 @@
.getBuyMaxAmtPercent().multiply(new BigDecimal("100")) + "%");
}
- BigDecimal buy_fee_amt = TradeFeeUtil.calcBuyFee(buy_amt_autual);
+ BigDecimal buy_fee_amt = TradeFeeUtil.calcBuyFee(buy_amt_autual, siteSetting.getBuyFee());
BigDecimal buy_debit = TradeFeeUtil.calcBuyDebit(buy_amt_autual, buy_fee_amt);
if (user_enable_amt.compareTo(buy_debit) == -1) {
return ServerResponse.createByErrorMsg("挂单失败,融资可用金额小于" + buy_debit + "元(含保证金及手续费)");
@@ -994,7 +997,7 @@
return ServerResponse.createByErrorMsg("平仓失败,不在交易时段内");
}
SiteProduct siteProduct = iSiteProductService.getProductSetting();
- if (siteProduct != null && siteProduct.getHolidayDisplay()) {
+ if (TradingDayUtil.shouldBlockTradingToday(siteProduct)) {
return ServerResponse.createByErrorMsg("周末或节假日不能交易!");
}
return null;
@@ -1165,9 +1168,9 @@
BigDecimal sell_fee_amt = all_sell_amt.multiply(siteSetting.getSellFee()).setScale(2, 4);
log.info("卖出手续费 = {}", sell_fee_amt);
- // 买入手续费已在下单时扣除,平仓只结算卖出侧费用
- BigDecimal all_fee_amt = sell_fee_amt.add(orderSpread).add(orderStayFee).add(spreadRatePrice);
- log.info("总的手续费费用 = {}", all_fee_amt);
+ // 买入手续费已在下单时扣除;递延费已在持仓期间从余额实时扣除,平仓不再重复扣款
+ BigDecimal close_fee_amt = sell_fee_amt.add(orderSpread).add(spreadRatePrice);
+ log.info("平仓结算费用(不含递延费) = {}", close_fee_amt);
userPosition.setSellOrderId(GeneratePosition.getPositionId());
userPosition.setSellOrderPrice(now_price);
@@ -1178,8 +1181,10 @@
userPosition.setProfitAndLose(profitLoss);
- BigDecimal all_profit = profitLoss.subtract(all_fee_amt);
- userPosition.setAllProfitAndLose(all_profit);
+ BigDecimal all_profit_balance = profitLoss.subtract(close_fee_amt);
+ BigDecimal all_profit_display = all_profit_balance.subtract(
+ orderStayFee == null ? BigDecimal.ZERO : orderStayFee);
+ userPosition.setAllProfitAndLose(all_profit_display);
userPosition.setStatus(2);
int updatePositionCount = this.userPositionMapper.updateByPrimaryKeySelective(userPosition);
@@ -1193,9 +1198,9 @@
BigDecimal freez_amt = all_buy_amt.divide(new BigDecimal(userPosition.getOrderLever().intValue()), 2, 4);
//BigDecimal freez_amt = all_buy_amt;
- BigDecimal reckon_all = user_all_amt.add(all_profit);
+ BigDecimal reckon_all = user_all_amt.add(all_profit_balance);
//修改用戶可用余額=當前可用余額+總盈虧+買入總金額+追加保證金
- BigDecimal reckon_enable = user_enable_amt.add(all_profit).add(freez_amt).add(userPosition.getMarginAdd());
+ BigDecimal reckon_enable = user_enable_amt.add(all_profit_balance).add(freez_amt).add(userPosition.getMarginAdd());
log.info("用戶平倉後的總資金 = {} , 可用資金 = {}", reckon_all, reckon_enable);
user.setUserAmt(reckon_all);
@@ -1215,8 +1220,10 @@
ucd.setUserId(user.getId());
ucd.setUserName(user.getRealName());
ucd.setDeType("总盈亏");
- ucd.setDeAmt(all_profit);
- ucd.setDeSummary("卖出股票," + userPosition.getStockCode() + "/" + userPosition.getStockName() + ",占用本金:" + freez_amt + ",总手续费:" + order_fee_all + ",盈亏:" + profitLoss + ",总盈亏:" + all_profit);
+ ucd.setDeAmt(all_profit_balance);
+ ucd.setDeSummary("卖出股票," + userPosition.getStockCode() + "/" + userPosition.getStockName() + ",占用本金:" + freez_amt
+ + ",总手续费:" + order_fee_all + ",递延费:" + (orderStayFee == null ? BigDecimal.ZERO : orderStayFee)
+ + ",盈亏:" + profitLoss + ",总盈亏:" + all_profit_display);
ucd.setAddTime(new Date());
ucd.setIsRead(Integer.valueOf(0));
@@ -1372,7 +1379,7 @@
BigDecimal partialMargin = all_buy_amt.divide(
new BigDecimal(userPosition.getOrderLever()), 2, RoundingMode.HALF_UP);
- BigDecimal buy_fee_amt = TradeFeeUtil.calcBuyFee(partialMargin);
+ BigDecimal buy_fee_amt = TradeFeeUtil.calcBuyFee(partialMargin, siteSetting.getBuyFee());
log.info("買入手續費 = {}", buy_fee_amt);
BigDecimal orderSpread = all_buy_amt.multiply(siteSetting.getDutyFee()).setScale(2, 4);
@@ -1508,7 +1515,7 @@
return ServerResponse.createByErrorMsg("不允许追加,请联系管理员");
}
- if (siteProduct.getHolidayDisplay()) {
+ if (TradingDayUtil.shouldBlockTradingToday(siteProduct)) {
return ServerResponse.createByErrorMsg("周末或节假日不能交易!");
}
@@ -1929,7 +1936,7 @@
BigDecimal buy_amt_autual = buy_amt.divide(new BigDecimal(lever.intValue()), 2, 4);
- BigDecimal buy_fee_amt_check = TradeFeeUtil.calcBuyFee(buy_amt_autual);
+ BigDecimal buy_fee_amt_check = TradeFeeUtil.calcBuyFee(buy_amt_autual, siteSetting.getBuyFee());
BigDecimal buy_debit_check = TradeFeeUtil.calcBuyDebit(buy_amt_autual, buy_fee_amt_check);
if (user_enable_amt.compareTo(buy_debit_check) < 0) {
log.info("下单失败,用户可用金额小于{}元(含保证金及手续费)", buy_debit_check);
@@ -1990,7 +1997,7 @@
userPosition.setOrderStayDays(1);
- BigDecimal buy_fee_amt = TradeFeeUtil.calcBuyFee(buy_amt_autual).setScale(2, 4);
+ BigDecimal buy_fee_amt = TradeFeeUtil.calcBuyFee(buy_amt_autual, siteSetting.getBuyFee()).setScale(2, 4);
log.info("创建模拟持仓 手续费(保证金 * 百分比) = {}", buy_fee_amt);
userPosition.setOrderFee(buy_fee_amt);
@@ -2061,127 +2068,165 @@
public void doClosingStayTask() {
List<UserPosition> userPositions = this.userPositionMapper.findAllStayPosition();
-
-
- if (userPositions.size() > 0) {
- log.info("查询到正在持仓的订单数量 = {}", Integer.valueOf(userPositions.size()));
-
- SiteProduct siteProduct = iSiteProductService.getProductSetting();
- if (!siteProduct.getHolidayDisplay()) {
- for (UserPosition position : userPositions) {
- int stayDays = GetStayDays.getDays(GetStayDays.getBeginDate(position.getBuyOrderTime()));
- //递延费特殊处理
- stayDays = stayDays + 1;
-
- log.info("");
- log.info("开始处理 持仓订单id = {} 订单号 = {} 用户id = {} realName = {} 留仓天数 = {}", new Object[]{position
- .getId(), position.getPositionSn(), position.getUserId(), position
- .getNickName(), Integer.valueOf(stayDays)});
-
- if (stayDays != 0) {
- log.info(" 开始收取 {} 天 留仓费", Integer.valueOf(stayDays));
- try {
- closingStayTask(position, Integer.valueOf(stayDays));
- } catch (Exception e) {
- log.error("doClosingStayTask = ", e);
-
-
- }
-
-
- } else {
-
-
- log.info("持仓订单 = {} ,持仓天数0天,不需要处理...", position.getId());
- }
-
- log.info("修改留仓费 处理结束。");
- log.info("");
- }
-
- SiteTaskLog stl = new SiteTaskLog();
- stl.setTaskType("扣除留仓费");
- stl.setAddTime(new Date());
- stl.setIsSuccess(Integer.valueOf(0));
- stl.setTaskTarget("扣除留仓费,订单数量为" + userPositions.size());
- this.siteTaskLogMapper.insert(stl);
- }
- } else {
+ if (userPositions.isEmpty()) {
log.info("doClosingStayTask没有正在持仓的订单");
+ return;
}
+
+ log.info("查询到正在持仓的订单数量 = {}", userPositions.size());
+ SiteProduct siteProduct = iSiteProductService.getProductSetting();
+ boolean respectHoliday = TradingDayUtil.isHolidayRestrictionEnabled(siteProduct);
+ if (StayFeeUtil.shouldSkipStayFeeTaskToday(respectHoliday)) {
+ log.info("节假日休市开关已开启且今日休市,跳过递延费扣款");
+ return;
+ }
+
+ SiteSetting siteSetting = this.iSiteSettingService.getSiteSetting();
+ if (siteSetting == null) {
+ log.error("递延费扣款失败,网站设置表不存在");
+ return;
+ }
+
+ int processed = 0;
+ for (UserPosition position : userPositions) {
+ log.info("开始处理递延费 持仓id={} 订单号={} 用户={}", position.getId(), position.getPositionSn(), position.getUserId());
+ try {
+ int billedDays = position.getOrderStayDays() == null ? 0 : position.getOrderStayDays();
+ if (siteSetting.getStayMaxDays() != null && billedDays >= siteSetting.getStayMaxDays().intValue()) {
+ log.info("递延费已达最大天数{},强制平仓 positionSn={}", siteSetting.getStayMaxDays(), position.getPositionSn());
+ this.sell(position.getPositionSn(), 0);
+ continue;
+ }
+ if (!StayFeeUtil.isEligibleForCharge(position.getBuyOrderTime(), position.getOrderStayDays(), respectHoliday)) {
+ log.info("持仓订单={} 未到T+1递延费扣款日或今日已扣,跳过", position.getId());
+ continue;
+ }
+ closingStayTask(position, null);
+ processed++;
+ } catch (Exception e) {
+ log.error("doClosingStayTask positionSn=" + position.getPositionSn(), e);
+ }
+ }
+
+ SiteTaskLog stl = new SiteTaskLog();
+ stl.setTaskType("扣除递延费");
+ stl.setAddTime(new Date());
+ stl.setIsSuccess(Integer.valueOf(0));
+ stl.setTaskTarget("扣除递延费,持仓数量" + userPositions.size() + ",实际扣款" + processed);
+ this.siteTaskLogMapper.insert(stl);
}
- /*留仓到期强制平仓,每天15点执行*/
+ /*递延费达到最大天数强制平仓*/
public void expireStayUnwindTask() {
List<UserPosition> userPositions = this.userPositionMapper.findAllStayPosition();
+ if (userPositions.isEmpty()) {
+ log.info("expireStayUnwindTask没有正在持仓的订单");
+ return;
+ }
+ SiteProduct siteProduct = iSiteProductService.getProductSetting();
+ boolean respectHoliday = TradingDayUtil.isHolidayRestrictionEnabled(siteProduct);
+ if (StayFeeUtil.shouldSkipStayFeeTaskToday(respectHoliday)) {
+ log.info("节假日休市开关已开启且今日休市,跳过递延费到期强平");
+ return;
+ }
- if (userPositions.size() > 0) {
- log.info("查询到正在持仓的订单数量 = {}", Integer.valueOf(userPositions.size()));
+ SiteSetting siteSetting = this.iSiteSettingService.getSiteSetting();
+ if (siteSetting == null || siteSetting.getStayMaxDays() == null) {
+ return;
+ }
+ int maxDays = siteSetting.getStayMaxDays().intValue();
- SiteSetting siteSetting = this.iSiteSettingService.getSiteSetting();
- for (UserPosition position : userPositions) {
- int stayDays = GetStayDays.getDays(GetStayDays.getBeginDate(position.getBuyOrderTime()));
-
- log.info("");
- log.info("开始处理 持仓订单id = {} 订单号 = {} 用户id = {} realName = {} 留仓天数 = {}", new Object[]{position
- .getId(), position.getPositionSn(), position.getUserId(), position
- .getNickName(), Integer.valueOf(stayDays)});
-
- //留仓达到最大天数
- if (stayDays >= siteSetting.getStayMaxDays().intValue()) {
- log.info(" 开始强平 {} 天", Integer.valueOf(stayDays));
- try {
- this.sell(position.getPositionSn(), 0);
- } catch (Exception e) {
- log.error("expireStayUnwindTask = ", e);
- }
- } else {
- log.info("持仓订单 = {} ,持仓天数0天,不需要处理...", position.getId());
+ for (UserPosition position : userPositions) {
+ int billedDays = position.getOrderStayDays() == null ? 0 : position.getOrderStayDays();
+ if (billedDays >= maxDays) {
+ log.info("递延费天数{}达上限{},强制平仓 positionSn={}", billedDays, maxDays, position.getPositionSn());
+ try {
+ this.sell(position.getPositionSn(), 0);
+ } catch (Exception e) {
+ log.error("expireStayUnwindTask positionSn=" + position.getPositionSn(), e);
}
}
- } else {
- log.info("doClosingStayTask没有正在持仓的订单");
}
}
@Transactional
public ServerResponse closingStayTask(UserPosition position, Integer stayDays) throws Exception {
- log.info("=================closingStayTask====================");
- log.info("修改留仓费,持仓id={},持仓天数={}", position.getId(), stayDays);
-
+ log.info("=================closingStayTask 递延费扣款====================");
SiteSetting siteSetting = this.iSiteSettingService.getSiteSetting();
if (siteSetting == null) {
- log.error("修改留仓费出错,网站设置表不存在");
- return ServerResponse.createByErrorMsg("修改留仓费出错,网站设置表不存在");
+ log.error("递延费扣款失败,网站设置表不存在");
+ return ServerResponse.createByErrorMsg("递延费扣款失败,网站设置表不存在");
}
+ SiteProduct siteProduct = iSiteProductService.getProductSetting();
+ boolean respectHoliday = TradingDayUtil.isHolidayRestrictionEnabled(siteProduct);
+ int daysToCharge = StayFeeUtil.getDaysToChargeThisRun(
+ position.getBuyOrderTime(), position.getOrderStayDays(), respectHoliday);
+ if (daysToCharge <= 0) {
+ return ServerResponse.createBySuccess();
+ }
- BigDecimal stayFee = position.getOrderTotalPrice().multiply(siteSetting.getStayFee());
+ PositionProfitVO profitVO = getPositionProfitVO(position);
+ BigDecimal marketValue = profitVO.getOrderTotalPrice();
+ BigDecimal billingMarketValue = StayFeeUtil.calcBillingMarketValue(marketValue);
+ BigDecimal dailyFee = StayFeeUtil.calcDailyStayFee(marketValue, siteSetting.getStayFee());
+ BigDecimal totalFee = dailyFee.multiply(new BigDecimal(daysToCharge)).setScale(2, RoundingMode.HALF_UP);
+ log.info("持仓id={} 市值={} 计费基数={} 单日递延费={} 本次扣费天数={} 合计={}",
+ position.getId(), marketValue, billingMarketValue, dailyFee, daysToCharge, totalFee);
- BigDecimal allStayFee = stayFee.multiply(new BigDecimal(stayDays.intValue()));
+ if (totalFee.compareTo(BigDecimal.ZERO) <= 0) {
+ return ServerResponse.createBySuccess();
+ }
- log.info("总留仓费 = {}", allStayFee);
+ User user = this.userMapper.selectByPrimaryKey(position.getUserId());
+ if (user == null) {
+ throw new Exception("递延费扣款失败,用户不存在");
+ }
+ BigDecimal enableAmt = user.getEnableAmt() == null ? BigDecimal.ZERO : user.getEnableAmt();
+ if (enableAmt.compareTo(totalFee) < 0) {
+ log.info("余额不足支付递延费{},可用{},强制平仓 positionSn={}", totalFee, enableAmt, position.getPositionSn());
+ this.sell(position.getPositionSn(), 0);
+ return ServerResponse.createBySuccess();
+ }
- position.setOrderStayFee(allStayFee);
- position.setOrderStayDays(stayDays);
+ BigDecimal userAmt = user.getUserAmt() == null ? BigDecimal.ZERO : user.getUserAmt();
+ user.setUserAmt(userAmt.subtract(totalFee));
+ UserFundUtil.debitUserBalance(user, totalFee);
+ int userRows = this.userMapper.updateByPrimaryKeySelective(user);
+ if (userRows <= 0) {
+ throw new Exception("递延费扣款失败,更新用户余额出错");
+ }
- BigDecimal all_profit = position.getAllProfitAndLose().subtract(allStayFee);
- position.setAllProfitAndLose(all_profit);
+ BigDecimal accumulated = (position.getOrderStayFee() == null ? BigDecimal.ZERO : position.getOrderStayFee()).add(totalFee);
+ int billedDays = (position.getOrderStayDays() == null ? 0 : position.getOrderStayDays()) + daysToCharge;
+ position.setOrderStayFee(accumulated);
+ position.setOrderStayDays(billedDays);
int updateCount = this.userPositionMapper.updateByPrimaryKeySelective(position);
- if (updateCount > 0) {
- //核算代理收入-延递费
- iAgentAgencyFeeService.AgencyFeeIncome(3, position.getPositionSn());
- log.info("【closingStayTask收持仓费】修改持仓记录成功");
- } else {
- log.error("【closingStayTask收持仓费】修改持仓记录出错");
- throw new Exception("【closingStayTask收持仓费】修改持仓记录出错");
+ if (updateCount <= 0) {
+ throw new Exception("递延费扣款失败,更新持仓记录出错");
}
+ UserCashDetail ucd = new UserCashDetail();
+ ucd.setPositionId(position.getId());
+ ucd.setAgentId(user.getAgentId());
+ ucd.setAgentName(user.getAgentName());
+ ucd.setUserId(user.getId());
+ ucd.setUserName(user.getRealName());
+ ucd.setDeType("递延费");
+ ucd.setDeAmt(totalFee.negate());
+ ucd.setDeSummary("递延费扣款," + position.getStockCode() + "/" + position.getStockName()
+ + ",持仓市值:" + marketValue + ",计费基数:" + billingMarketValue + ",单日:" + dailyFee
+ + ",扣费天数:" + daysToCharge + ",本次扣费:" + totalFee + ",累计递延费:" + accumulated
+ + ",递延天数:" + billedDays);
+ ucd.setAddTime(new Date());
+ ucd.setIsRead(Integer.valueOf(0));
+ this.userCashDetailMapper.insert(ucd);
- log.info("=======================================================");
+ iAgentAgencyFeeService.AgencyFeeIncome(3, position.getPositionSn());
+ log.info("【递延费扣款成功】positionSn={} 天数={} 本次={} 累计={}", position.getPositionSn(), daysToCharge, totalFee, accumulated);
return ServerResponse.createBySuccess();
}
@@ -2337,6 +2382,15 @@
position.getBuyOrderPrice()
.multiply(new BigDecimal(position.getOrderNum()))
.divide(new BigDecimal(position.getOrderLever()), 2, RoundingMode.HALF_UP));
+ }
+
+ if (position.getSellOrderId() == null && Integer.valueOf(1).equals(position.getStatus())) {
+ SiteProduct siteProduct = iSiteProductService.getProductSetting();
+ boolean respectHoliday = TradingDayUtil.isHolidayRestrictionEnabled(siteProduct);
+ long nextTs = StayFeeUtil.getNextStayFeeTimestamp(position.getBuyOrderTime(), position.getOrderStayDays(), respectHoliday);
+ userPositionVO.setNextStayFeeTime(nextTs);
+ userPositionVO.setStayFeeCountdownSec(StayFeeUtil.getStayFeeCountdownSec(nextTs));
+ userPositionVO.setRespectHoliday(respectHoliday);
}
return userPositionVO;
@@ -2628,7 +2682,7 @@
// if (!pm_flag) {
// return ServerResponse.createByErrorMsg("下單失敗,不在交易時段內");
// }
- if (siteProduct.getHolidayDisplay()) {
+ if (TradingDayUtil.shouldBlockTradingToday(siteProduct)) {
return ServerResponse.createByErrorMsg("周末或节假日不能交易!");
}
// if (!Objects.equals(stockDz.getPassword(), password)) {
@@ -2780,7 +2834,7 @@
}
- BigDecimal buy_fee_amt_dz = TradeFeeUtil.calcBuyFee(buy_amt_autual);
+ BigDecimal buy_fee_amt_dz = TradeFeeUtil.calcBuyFee(buy_amt_autual, siteSetting.getBuyFee());
BigDecimal buy_debit_dz = TradeFeeUtil.calcBuyDebit(buy_amt_autual, buy_fee_amt_dz);
if (user_enable_amt.compareTo(buy_debit_dz) == -1) {
return ServerResponse.createByErrorMsg("下单失败,融资可用金额小于" + buy_debit_dz + "元(含保证金及手续费)");
@@ -2929,7 +2983,7 @@
if (!am_flag && !pm_flag) {
return ServerResponse.createByErrorMsg("下單失敗,不在交易時段內");
}
- if (siteProduct.getHolidayDisplay()) {
+ if (TradingDayUtil.shouldBlockTradingToday(siteProduct)) {
return ServerResponse.createByErrorMsg("周末或節假日不能交易!");
}
@@ -3088,7 +3142,7 @@
}
- BigDecimal buy_fee_amt = TradeFeeUtil.calcBuyFee(buy_amt_autual);
+ BigDecimal buy_fee_amt = TradeFeeUtil.calcBuyFee(buy_amt_autual, siteSetting.getBuyFee());
BigDecimal buy_debit = TradeFeeUtil.calcBuyDebit(buy_amt_autual, buy_fee_amt);
if (user_enable_amt.compareTo(buy_debit) == -1) {
return ServerResponse.createByErrorMsg("下單失敗,可用金額小於" + buy_debit + "元(含保证金及手续费)");
diff --git a/src/main/java/com/nq/service/impl/UserRechargeServiceImpl.java b/src/main/java/com/nq/service/impl/UserRechargeServiceImpl.java
index 3f6b468..880203b 100644
--- a/src/main/java/com/nq/service/impl/UserRechargeServiceImpl.java
+++ b/src/main/java/com/nq/service/impl/UserRechargeServiceImpl.java
@@ -18,6 +18,7 @@
import com.nq.pojo.UserRecharge;
import com.nq.utils.DateTimeUtil;
import com.nq.utils.KeyUtils;
+import com.nq.utils.RechargeAmtValidator;
import com.nq.utils.UserFundUtil;
import com.nq.utils.redis.RedisShardedPoolUtils;
@@ -73,11 +74,9 @@
return ServerResponse.createByErrorMsg("参数不能为空");
}
SiteSetting siteSetting = this.iSiteSettingService.getSiteSetting();
- if (siteSetting == null) {
- return ServerResponse.createByErrorMsg("设置set未初始化");
- }
- if ((new BigDecimal(siteSetting.getChargeMinAmt() + "")).compareTo(new BigDecimal(amt)) == 1) {
- return ServerResponse.createByErrorMsg("充值金额不得低于" + siteSetting.getChargeMinAmt() + "元");
+ ServerResponse amtCheck = RechargeAmtValidator.validate(siteSetting, new BigDecimal(amt));
+ if (!amtCheck.isSuccess()) {
+ return amtCheck;
}
diff --git a/src/main/java/com/nq/service/impl/UserServiceImpl.java b/src/main/java/com/nq/service/impl/UserServiceImpl.java
index 609ffcd..4305c74 100644
--- a/src/main/java/com/nq/service/impl/UserServiceImpl.java
+++ b/src/main/java/com/nq/service/impl/UserServiceImpl.java
@@ -120,15 +120,25 @@
return ServerResponse.createByErrorMsg("注册失败, 参数不能为空");
}
+ if (!com.nq.utils.PhoneUtil.isValidChinaMobile(phone)) {
+ return ServerResponse.createByErrorMsg("注册失败, 手机号格式不正确");
+ }
+ phone = com.nq.utils.PhoneUtil.normalizeChinaMobile(phone);
-// String keys = "AliyunSmsCode:" + phone;
-// String redis_yzm = RedisShardedPoolUtils.get(keys);
-//
-// log.info("redis_yzm = {},yzmCode = {}", redis_yzm, yzmCode);
-// if (!yzmCode.equals(redis_yzm) && !"6666".equals(yzmCode)) {
-// return ServerResponse.createByErrorMsg("注册失败, 验证码错误");
-// }
+ if (com.nq.utils.sms.SmsConstants.isVerifyEnabled()) {
+ if (StringUtils.isBlank(yzmCode)) {
+ return ServerResponse.createByErrorMsg("注册失败, 请输入短信验证码");
+ }
+ String keys = com.nq.utils.sms.SmsConstants.SMS_CODE_PREFIX + phone;
+ String redis_yzm = RedisShardedPoolUtils.get(keys);
+
+ log.info("redis_yzm = {},yzmCode = {}", redis_yzm, yzmCode);
+ if (StringUtils.isBlank(redis_yzm) || !yzmCode.equals(redis_yzm)) {
+ return ServerResponse.createByErrorMsg("注册失败, 验证码错误或已过期");
+ }
+ RedisShardedPoolUtils.del(keys);
+ }
AgentUser agentUser = this.iAgentUserService.findByCode(agentCode);
if (agentUser == null) {
@@ -157,19 +167,10 @@
user.setIsActive(Integer.valueOf(0));
user.setRegTime(new Date());
-// String uip = IpUtils.getIp(request);
-// user.setRegIp(uip);
-// // IP地址查询改为异步处理,避免阻塞注册接口
-// final String finalUip = uip;
-// final String finalPhone = phone;
-// new Thread(() -> {
-// try {
-// String uadd = JuheIpApi.ip2Add(finalUip);
-// log.info("用户注册IP地址查询完成 手机 {} , ip = {} 地址 = {}", finalPhone, finalUip, uadd);
-// } catch (Exception e) {
-// log.error("IP地址查询异常", e);
-// }
-// }).start();
+ String uip = IpUtils.getIp(request);
+ user.setRegIp(StringUtils.isBlank(uip) ? "--" : uip);
+ String uadd = JuheIpApi.ip2Add(uip);
+ user.setRegAddress(StringUtils.isBlank(uadd) ? "未知" : uadd);
user.setIsLogin(Integer.valueOf(0));
@@ -195,7 +196,7 @@
String newAgentCode = generateUniqueAgentCode();
agentUser.setAgentCode(newAgentCode);
iAgentUserService.updateAgentCode(agentUser);
- log.info("用户注册成功 手机 {} , ip = {}", phone, "123123");
+ log.info("用户注册成功 手机 {} , ip = {}", phone, uip);
return ServerResponse.createBySuccessMsg("注册成功.请登录");
}
return ServerResponse.createBySuccessMsg("注册出错, 请重试");
@@ -219,6 +220,10 @@
if (StringUtils.isBlank(phone) || StringUtils.isBlank(userPwd)) {
return ServerResponse.createByErrorMsg("手机号和密码不能为空");
}
+ if (!com.nq.utils.PhoneUtil.isValidChinaMobile(phone)) {
+ return ServerResponse.createByErrorMsg("手机号格式不正确");
+ }
+ phone = com.nq.utils.PhoneUtil.normalizeChinaMobile(phone);
userPwd = SymmetricCryptoUtil.encryptPassword(userPwd);
User user = this.userMapper.login(phone, userPwd);
if (user != null) {
@@ -442,6 +447,10 @@
public ServerResponse checkPhone(String phone) {
+ if (!com.nq.utils.PhoneUtil.isValidChinaMobile(phone)) {
+ return ServerResponse.createByErrorMsg("手机号格式不正确");
+ }
+ phone = com.nq.utils.PhoneUtil.normalizeChinaMobile(phone);
User user = this.userMapper.findByPhone(phone);
if (user != null) {
return ServerResponse.createBySuccessMsg("用户已存在");
@@ -452,19 +461,25 @@
public ServerResponse updatePwd(String phone, String code, String newPwd) {
- if (StringUtils.isBlank(phone) ||
- StringUtils.isBlank(code) ||
- StringUtils.isBlank(newPwd)) {
+ if (StringUtils.isBlank(phone) || StringUtils.isBlank(newPwd)) {
return ServerResponse.createByErrorMsg("参数不能为空");
}
+ if (com.nq.utils.sms.SmsConstants.isVerifyEnabled() && StringUtils.isBlank(code)) {
+ return ServerResponse.createByErrorMsg("参数不能为空");
+ }
+ if (!com.nq.utils.PhoneUtil.isValidChinaMobile(phone)) {
+ return ServerResponse.createByErrorMsg("手机号格式不正确");
+ }
+ phone = com.nq.utils.PhoneUtil.normalizeChinaMobile(phone);
+ if (com.nq.utils.sms.SmsConstants.isVerifyEnabled()) {
+ String keys = "AliyunSmsCode:" + phone;
+ String redis_yzm = RedisShardedPoolUtils.get(keys);
- String keys = "AliyunSmsCode:" + phone;
- String redis_yzm = RedisShardedPoolUtils.get(keys);
-
- log.info("redis_yzm = {} , code = {}", redis_yzm, code);
- if (!code.equals(redis_yzm)) {
- return ServerResponse.createByErrorMsg("修改密码失败,验证码错误");
+ log.info("redis_yzm = {} , code = {}", redis_yzm, code);
+ if (!code.equals(redis_yzm)) {
+ return ServerResponse.createByErrorMsg("修改密码失败,验证码错误");
+ }
}
User user = this.userMapper.findByPhone(phone);
diff --git a/src/main/java/com/nq/service/impl/UserStockSubscribeServiceImpl.java b/src/main/java/com/nq/service/impl/UserStockSubscribeServiceImpl.java
index f8f8740..69fd170 100644
--- a/src/main/java/com/nq/service/impl/UserStockSubscribeServiceImpl.java
+++ b/src/main/java/com/nq/service/impl/UserStockSubscribeServiceImpl.java
@@ -11,6 +11,7 @@
import com.nq.dao.*;
import com.nq.pojo.*;
import com.nq.service.*;
+import com.nq.utils.TradingDayUtil;
import com.nq.utils.DateTimeUtil;
import com.nq.utils.KeyUtils;
import com.nq.utils.UserFundUtil;
@@ -109,7 +110,7 @@
return ServerResponse.createByErrorMsg("下单失败,请先实名认证");
}
// 判断休息日不能买入
- if (siteProduct.getHolidayDisplay()) {
+ if (TradingDayUtil.shouldBlockTradingToday(siteProduct)) {
return ServerResponse.createByErrorMsg("周末或节假日不能交易!");
}
//重复申购限制
@@ -598,7 +599,7 @@
SiteProduct siteProduct = iSiteProductService.getProductSetting();
// 判斷休息日不能買入
- if (siteProduct.getHolidayDisplay()) {
+ if (TradingDayUtil.shouldBlockTradingToday(siteProduct)) {
return ServerResponse.createByErrorMsg("周末或節假日不能交易!");
}
//重復申購限製
@@ -802,7 +803,7 @@
userPosition.setOrderStayDays(1);
- BigDecimal buy_fee_amt = com.nq.utils.TradeFeeUtil.calcBuyFee(buy_amt);
+ BigDecimal buy_fee_amt = com.nq.utils.TradeFeeUtil.calcBuyFee(buy_amt, siteSetting.getBuyFee());
log.info("创建模拟持仓 手续费(配资后总资金 * 百分比) = {}", buy_fee_amt);
userPosition.setOrderFee(buy_fee_amt);
diff --git a/src/main/java/com/nq/service/impl/UserWithdrawServiceImpl.java b/src/main/java/com/nq/service/impl/UserWithdrawServiceImpl.java
index 69c29db..9c86ec8 100644
--- a/src/main/java/com/nq/service/impl/UserWithdrawServiceImpl.java
+++ b/src/main/java/com/nq/service/impl/UserWithdrawServiceImpl.java
@@ -16,6 +16,7 @@
import com.nq.dao.UserWithdrawMapper;
+import com.nq.utils.TradingDayUtil;
import com.nq.utils.stock.WithDrawUtils;
import java.math.BigDecimal;
@@ -159,7 +160,7 @@
// return ServerResponse.createByErrorMsg("提款失败,当前不能出金!");
// }
- if(siteProduct.getHolidayDisplay() && siteProduct.getTranWithdrawDisplay()){
+ if(siteProduct.getTranWithdrawDisplay() && TradingDayUtil.shouldBlockTradingToday(siteProduct)){
return ServerResponse.createByErrorMsg("周末或节假日不能出金!");
}
diff --git a/src/main/java/com/nq/utils/PhoneUtil.java b/src/main/java/com/nq/utils/PhoneUtil.java
new file mode 100644
index 0000000..3f70d0f
--- /dev/null
+++ b/src/main/java/com/nq/utils/PhoneUtil.java
@@ -0,0 +1,77 @@
+package com.nq.utils;
+
+
+
+import org.apache.commons.lang3.StringUtils;
+
+
+
+import java.util.regex.Pattern;
+
+
+
+public final class PhoneUtil {
+
+
+
+ /**
+
+ * 严格大陆手机号(11位):有效运营商号段
+
+ * 13x | 14[5-9] | 15[0-35-9] | 16[2567] | 17[0-8] | 18x | 19[0-35-9]
+
+ */
+
+ private static final Pattern STRICT_MOBILE = Pattern.compile(
+
+ "^1(3\\d|4[5-9]|5[0-35-9]|6[2567]|7[0-8]|8\\d|9[0-35-9])\\d{8}$");
+
+
+
+ private PhoneUtil() {
+
+ }
+
+
+
+ /** 规范为 11 位大陆手机号,非法返回 null */
+
+ public static String normalizeChinaMobile(String phone) {
+
+ if (StringUtils.isBlank(phone)) {
+
+ return null;
+
+ }
+
+ String digits = phone.trim().replaceAll("[^0-9]", "");
+
+ if (digits.startsWith("86") && digits.length() == 13) {
+
+ digits = digits.substring(2);
+
+ }
+
+ if (digits.length() != 11 || !digits.startsWith("1")) {
+
+ return null;
+
+ }
+
+ return digits;
+
+ }
+
+
+
+ public static boolean isValidChinaMobile(String phone) {
+
+ String normalized = normalizeChinaMobile(phone);
+
+ return normalized != null && STRICT_MOBILE.matcher(normalized).matches();
+
+ }
+
+}
+
+
diff --git a/src/main/java/com/nq/utils/RechargeAmtValidator.java b/src/main/java/com/nq/utils/RechargeAmtValidator.java
new file mode 100644
index 0000000..2f80add
--- /dev/null
+++ b/src/main/java/com/nq/utils/RechargeAmtValidator.java
@@ -0,0 +1,34 @@
+package com.nq.utils;
+
+import com.nq.common.ServerResponse;
+import com.nq.pojo.SiteSetting;
+
+import java.math.BigDecimal;
+
+public final class RechargeAmtValidator {
+
+ private RechargeAmtValidator() {
+ }
+
+ public static ServerResponse validate(SiteSetting siteSetting, BigDecimal amt) {
+ if (siteSetting == null) {
+ return ServerResponse.createByErrorMsg("设置set未初始化");
+ }
+ if (amt == null) {
+ return ServerResponse.createByErrorMsg("充值金额不能为空");
+ }
+ if (siteSetting.getChargeMinAmt() != null) {
+ BigDecimal min = new BigDecimal(siteSetting.getChargeMinAmt());
+ if (amt.compareTo(min) < 0) {
+ return ServerResponse.createByErrorMsg("充值金额不得低于" + siteSetting.getChargeMinAmt() + "元");
+ }
+ }
+ if (siteSetting.getChargeMaxAmt() != null && siteSetting.getChargeMaxAmt() > 0) {
+ BigDecimal max = new BigDecimal(siteSetting.getChargeMaxAmt());
+ if (amt.compareTo(max) > 0) {
+ return ServerResponse.createByErrorMsg("超过充值最大金额,请联系客服");
+ }
+ }
+ return ServerResponse.createBySuccess();
+ }
+}
diff --git a/src/main/java/com/nq/utils/StayFeeUtil.java b/src/main/java/com/nq/utils/StayFeeUtil.java
new file mode 100644
index 0000000..f75c9e0
--- /dev/null
+++ b/src/main/java/com/nq/utils/StayFeeUtil.java
@@ -0,0 +1,151 @@
+package com.nq.utils;
+
+import com.nq.utils.stock.GetStayDays;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.ZoneId;
+import java.time.temporal.ChronoUnit;
+import java.util.Date;
+
+public final class StayFeeUtil {
+
+ private static final BigDecimal WAN_UNIT = new BigDecimal("10000");
+
+ public static final LocalTime CHARGE_TIME = LocalTime.of(14, 50);
+
+ private StayFeeUtil() {
+ }
+
+ /**
+ * 递延费计费基数:不足1万按1万;超过1万按每1万向上取整(如5999→10000,35999→40000)。
+ */
+ public static BigDecimal calcBillingMarketValue(BigDecimal marketValue) {
+ BigDecimal mv = marketValue == null ? BigDecimal.ZERO : marketValue;
+ if (mv.compareTo(BigDecimal.ZERO) <= 0) {
+ return WAN_UNIT;
+ }
+ return mv.divide(WAN_UNIT, 0, RoundingMode.CEILING).multiply(WAN_UNIT);
+ }
+
+ public static BigDecimal calcDailyStayFee(BigDecimal marketValue, BigDecimal stayFeeRate) {
+ BigDecimal base = calcBillingMarketValue(marketValue);
+ BigDecimal rate = stayFeeRate == null ? BigDecimal.ZERO : stayFeeRate;
+ return base.multiply(rate).setScale(2, RoundingMode.HALF_UP);
+ }
+
+ public static LocalDate toLocalDate(Date date) {
+ if (date == null) {
+ return LocalDate.now();
+ }
+ return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
+ }
+
+ /** 自然日:买入日不计,自次日起每个自然日计 1 天(含周末、法定假日本身) */
+ public static int getCalendarDaysSinceBuy(Date buyOrderTime) {
+ LocalDate buy = toLocalDate(GetStayDays.getBeginDate(buyOrderTime));
+ LocalDate today = LocalDate.now();
+ if (!buy.isBefore(today)) {
+ return 0;
+ }
+ return (int) ChronoUnit.DAYS.between(buy, today);
+ }
+
+ /** 交易日:买入日不计,仅统计非周末且非法定假日本身的工作日 */
+ public static int getTradingDaysSinceBuy(Date buyOrderTime) {
+ LocalDate buy = toLocalDate(GetStayDays.getBeginDate(buyOrderTime));
+ LocalDate today = LocalDate.now();
+ if (!buy.isBefore(today)) {
+ return 0;
+ }
+ int count = 0;
+ LocalDate d = buy.plusDays(1);
+ while (!d.isAfter(today)) {
+ if (TradingDayUtil.isTradingDay(d)) {
+ count++;
+ }
+ d = d.plusDays(1);
+ }
+ return count;
+ }
+
+ /**
+ * 应计费天数(买入日不计):
+ * 节假日休市开 → 仅交易日;关 → 每个自然日(不管节假日)。
+ */
+ public static int getChargeableDaysSinceBuy(Date buyOrderTime, boolean respectHoliday) {
+ return respectHoliday ? getTradingDaysSinceBuy(buyOrderTime) : getCalendarDaysSinceBuy(buyOrderTime);
+ }
+
+ /**
+ * 本次定时任务应扣天数(最多 1 天,不补扣历史):
+ * 开 → 仅交易日扣 1 天;关 → 每个自然日扣 1 天(节假日也扣,不补扣周末/假日前遗漏的天数)。
+ */
+ public static int getDaysToChargeThisRun(Date buyOrderTime, Integer orderStayDays, boolean respectHoliday) {
+ int chargeableDays = getChargeableDaysSinceBuy(buyOrderTime, respectHoliday);
+ int billed = orderStayDays == null ? 0 : orderStayDays;
+ if (chargeableDays < 1 || billed >= chargeableDays) {
+ return 0;
+ }
+ if (respectHoliday && !TradingDayUtil.isTradingDay(LocalDate.now())) {
+ return 0;
+ }
+ return 1;
+ }
+
+ public static boolean isEligibleForCharge(Date buyOrderTime, Integer orderStayDays, boolean respectHoliday) {
+ return getDaysToChargeThisRun(buyOrderTime, orderStayDays, respectHoliday) > 0;
+ }
+
+ public static long getNextStayFeeTimestamp(Date buyOrderTime, Integer orderStayDays, boolean respectHoliday) {
+ LocalDate buy = toLocalDate(GetStayDays.getBeginDate(buyOrderTime));
+ LocalDate today = LocalDate.now();
+ LocalDateTime now = LocalDateTime.now();
+ int chargeableDays = getChargeableDaysSinceBuy(buyOrderTime, respectHoliday);
+ int billed = orderStayDays == null ? 0 : orderStayDays;
+
+ LocalDate targetDate;
+ if (respectHoliday) {
+ if (chargeableDays < 1) {
+ targetDate = firstTradingDayOnOrAfter(buy.plusDays(1));
+ } else if (billed < chargeableDays
+ && TradingDayUtil.isTradingDay(today)
+ && now.toLocalTime().isBefore(CHARGE_TIME)) {
+ targetDate = today;
+ } else {
+ targetDate = firstTradingDayOnOrAfter(today.plusDays(1));
+ }
+ } else {
+ if (chargeableDays < 1) {
+ targetDate = buy.plusDays(1);
+ } else if (billed < chargeableDays && now.toLocalTime().isBefore(CHARGE_TIME)) {
+ targetDate = today;
+ } else {
+ targetDate = today.plusDays(1);
+ }
+ }
+ LocalDateTime next = LocalDateTime.of(targetDate, CHARGE_TIME);
+ return next.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
+ }
+
+ public static long getStayFeeCountdownSec(long nextStayFeeTimestamp) {
+ long diff = nextStayFeeTimestamp - System.currentTimeMillis();
+ return diff <= 0 ? 0 : diff / 1000;
+ }
+
+ /** 节假日休市开:非交易日不跑递延费任务 */
+ public static boolean shouldSkipStayFeeTaskToday(boolean respectHoliday) {
+ return respectHoliday && !TradingDayUtil.isTradingDay(LocalDate.now());
+ }
+
+ private static LocalDate firstTradingDayOnOrAfter(LocalDate from) {
+ LocalDate d = from;
+ while (!TradingDayUtil.isTradingDay(d)) {
+ d = d.plusDays(1);
+ }
+ return d;
+ }
+}
diff --git a/src/main/java/com/nq/utils/TradeFeeUtil.java b/src/main/java/com/nq/utils/TradeFeeUtil.java
index 195ed57..c97e9de 100644
--- a/src/main/java/com/nq/utils/TradeFeeUtil.java
+++ b/src/main/java/com/nq/utils/TradeFeeUtil.java
@@ -5,16 +5,32 @@
public class TradeFeeUtil {
- public static final BigDecimal BUY_FEE_RATE = new BigDecimal("0.03");
+ /** 默认买入手续费率 0.0035 = 0.35% */
+ public static final BigDecimal BUY_FEE_RATE = new BigDecimal("0.0035");
private TradeFeeUtil() {
}
+ public static BigDecimal resolveBuyFeeRate(BigDecimal configured) {
+ if (configured == null || configured.compareTo(BigDecimal.ZERO) <= 0) {
+ return BUY_FEE_RATE;
+ }
+ return configured;
+ }
+
public static BigDecimal calcBuyFee(BigDecimal buyAmt) {
+ return calcBuyFee(buyAmt, BUY_FEE_RATE);
+ }
+
+ public static BigDecimal calcBuyFee(BigDecimal buyAmt, BigDecimal feeRate) {
if (buyAmt == null) {
return BigDecimal.ZERO;
}
- return buyAmt.multiply(BUY_FEE_RATE).setScale(2, RoundingMode.HALF_UP);
+ return buyAmt.multiply(resolveBuyFeeRate(feeRate)).setScale(2, RoundingMode.HALF_UP);
+ }
+
+ public static BigDecimal calcBuyFeeByNotional(BigDecimal notional, Integer lever, BigDecimal feeRate) {
+ return calcBuyFee(notional, feeRate);
}
public static BigDecimal calcBuyFeeByNotional(BigDecimal notional, Integer lever) {
diff --git a/src/main/java/com/nq/utils/TradingDayUtil.java b/src/main/java/com/nq/utils/TradingDayUtil.java
new file mode 100644
index 0000000..dd2af6a
--- /dev/null
+++ b/src/main/java/com/nq/utils/TradingDayUtil.java
@@ -0,0 +1,72 @@
+package com.nq.utils;
+
+import java.time.DayOfWeek;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 交易日判断:优先走 HolidayUtil 接口,失败时回退 MarketUtils 硬编码列表。
+ */
+public final class TradingDayUtil {
+
+ private static final DateTimeFormatter YMD = DateTimeFormatter.ofPattern("yyyyMMdd");
+ private static final ConcurrentHashMap<String, Boolean> CACHE = new ConcurrentHashMap<>();
+
+ private TradingDayUtil() {
+ }
+
+ /** true=休市(周末或法定节假日) */
+ public static boolean isMarketClosed(LocalDate date) {
+ if (date == null) {
+ return false;
+ }
+ if (date.getDayOfWeek() == DayOfWeek.SATURDAY || date.getDayOfWeek() == DayOfWeek.SUNDAY) {
+ return true;
+ }
+ String key = date.toString();
+ return CACHE.computeIfAbsent(key, TradingDayUtil::resolveClosedOnApi);
+ }
+
+ /** 是否开启「节假日休市」(含落在工作日的法定假日本身) */
+ public static boolean isHolidayRestrictionEnabled(com.nq.pojo.SiteProduct siteProduct) {
+ return siteProduct != null && Boolean.TRUE.equals(siteProduct.getHolidayDisplay());
+ }
+
+ /** 开启节假日休市且今日为周末或法定假日本身时,禁止交易/出金等 */
+ public static boolean shouldBlockTradingToday(com.nq.pojo.SiteProduct siteProduct) {
+ return isHolidayRestrictionEnabled(siteProduct) && isMarketClosed(LocalDate.now());
+ }
+
+ /** 是否为 A 股交易日(非周末且非法定假日本身) */
+ public static boolean isTradingDay(LocalDate date) {
+ return date != null && !isMarketClosed(date);
+ }
+
+ /**
+ * @deprecated 递延费计费请使用 {@link StayFeeUtil}
+ */
+ @Deprecated
+ public static boolean isStayFeeChargeableDay(LocalDate date, boolean respectHoliday) {
+ if (respectHoliday) {
+ return isTradingDay(date);
+ }
+ return true;
+ }
+
+ private static boolean resolveClosedOnApi(String isoDate) {
+ LocalDate date = LocalDate.parse(isoDate);
+ try {
+ String result = HolidayUtil.request(date.format(YMD));
+ if ("1".equals(result) || "2".equals(result)) {
+ return true;
+ }
+ if ("0".equals(result)) {
+ return false;
+ }
+ } catch (Exception ignored) {
+ // 接口异常时回退本地节假日表
+ }
+ return MarketUtils.isMarketClosed(date);
+ }
+}
diff --git a/src/main/java/com/nq/utils/captcha/CaptchaUtil.java b/src/main/java/com/nq/utils/captcha/CaptchaUtil.java
new file mode 100644
index 0000000..e009764
--- /dev/null
+++ b/src/main/java/com/nq/utils/captcha/CaptchaUtil.java
@@ -0,0 +1,45 @@
+package com.nq.utils.captcha;
+
+import com.nq.utils.redis.RedisShardedPoolUtils;
+import org.apache.commons.lang3.StringUtils;
+
+public final class CaptchaUtil {
+
+ public static final String REDIS_PREFIX = "Captcha:";
+ /** 图形验证码有效期(秒) */
+ public static final int CAPTCHA_TTL_SECONDS = 300;
+
+ private CaptchaUtil() {
+ }
+
+ public static void save(String token, String code) {
+ if (StringUtils.isBlank(token) || StringUtils.isBlank(code)) {
+ return;
+ }
+ RedisShardedPoolUtils.setEx(REDIS_PREFIX + token.trim(), code, CAPTCHA_TTL_SECONDS);
+ }
+
+ public static String get(String token) {
+ if (StringUtils.isBlank(token)) {
+ return null;
+ }
+ return RedisShardedPoolUtils.get(REDIS_PREFIX + token.trim());
+ }
+
+ /** 校验失败不删除,避免输错一次就必须刷新;校验成功才删除防重复使用 */
+ public static boolean verifyAndRemove(String token, String inputCode) {
+ if (StringUtils.isBlank(token) || StringUtils.isBlank(inputCode)) {
+ return false;
+ }
+ String key = REDIS_PREFIX + token.trim();
+ String original = RedisShardedPoolUtils.get(key);
+ if (StringUtils.isBlank(original)) {
+ return false;
+ }
+ if (!inputCode.trim().equalsIgnoreCase(original.trim())) {
+ return false;
+ }
+ RedisShardedPoolUtils.del(key);
+ return true;
+ }
+}
diff --git a/src/main/java/com/nq/utils/ip/IpUtils.java b/src/main/java/com/nq/utils/ip/IpUtils.java
index 031206a..900fb71 100644
--- a/src/main/java/com/nq/utils/ip/IpUtils.java
+++ b/src/main/java/com/nq/utils/ip/IpUtils.java
@@ -10,20 +10,33 @@
return "";
}
- String ip = request.getHeader("X-real-ip");
- if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+ String ip = request.getHeader("X-Real-IP");
+ if (isBlankIp(ip)) {
+ ip = request.getHeader("X-real-ip");
+ }
+ if (isBlankIp(ip)) {
ip = request.getHeader("x-forwarded-for");
}
- if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+ if (isBlankIp(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
- if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+ if (isBlankIp(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
- if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+ if (isBlankIp(ip)) {
ip = request.getRemoteAddr();
}
- return ip;
+ if (ip != null && ip.contains(",")) {
+ ip = ip.split(",")[0].trim();
+ }
+ if ("0:0:0:0:0:0:0:1".equals(ip) || "::1".equals(ip)) {
+ ip = "127.0.0.1";
+ }
+ return ip == null ? "" : ip.trim();
+ }
+
+ private static boolean isBlankIp(String ip) {
+ return ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip);
}
}
diff --git a/src/main/java/com/nq/utils/pay/AlipayPayUtil.java b/src/main/java/com/nq/utils/pay/AlipayPayUtil.java
new file mode 100644
index 0000000..f507e8e
--- /dev/null
+++ b/src/main/java/com/nq/utils/pay/AlipayPayUtil.java
@@ -0,0 +1,208 @@
+package com.nq.utils.pay;
+
+import com.alipay.api.AlipayApiException;
+import com.alipay.api.AlipayClient;
+import com.alipay.api.AlipayConfig;
+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.nq.utils.PropertiesUtil;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.http.HttpServletRequest;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyFactory;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 支付宝手机网站支付(alipay.trade.wap.pay),对接官方 SDK 示例写法。
+ */
+public final class AlipayPayUtil {
+
+ private static final Logger log = LoggerFactory.getLogger(AlipayPayUtil.class);
+ /** 手机网站支付产品码 */
+ private static final String PRODUCT_CODE = "QUICK_WAP_WAY";
+ private static final String SIGN_TYPE = "RSA2";
+ private static final String FORMAT = "json";
+ private static final String CHARSET = "UTF-8";
+
+ private AlipayPayUtil() {
+ }
+
+ public static boolean isConfigured() {
+ return getConfigError() == null;
+ }
+
+ /** @return 配置错误说明,null 表示配置有效 */
+ public static String getConfigError() {
+ if (StringUtils.isBlank(PropertiesUtil.getProperty("alipay.app-id"))) {
+ return "请填写 alipay.app-id";
+ }
+ if (StringUtils.isBlank(PropertiesUtil.getProperty("alipay.alipay-public-key"))) {
+ return "请填写 alipay.alipay-public-key(支付宝公钥)";
+ }
+ String path = PropertiesUtil.getProperty("alipay.private-key-path");
+ String inlineKey = PropertiesUtil.getProperty("alipay.private-key");
+ if (StringUtils.isAllBlank(path, inlineKey)) {
+ return "请填写 alipay.private-key(应用私钥 PKCS8)或 alipay.private-key-path(私钥文件路径)";
+ }
+ try {
+ toSdkPrivateKey(loadPrivateKeyMaterial());
+ return null;
+ } catch (IllegalArgumentException e) {
+ return e.getMessage();
+ } catch (Exception e) {
+ return "应用私钥读取失败:" + e.getMessage();
+ }
+ }
+
+ public static String buildWapPayForm(String orderSn, String amount, String subject,
+ String notifyUrl, String returnUrl, String quitUrl)
+ throws AlipayApiException {
+ AlipayClient client = createClient();
+ AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();
+ request.setNotifyUrl(notifyUrl);
+ request.setReturnUrl(returnUrl);
+
+ AlipayTradeWapPayModel model = new AlipayTradeWapPayModel();
+ model.setOutTradeNo(orderSn);
+ model.setTotalAmount(amount);
+ model.setSubject(subject);
+ model.setProductCode(PRODUCT_CODE);
+ if (StringUtils.isNotBlank(quitUrl)) {
+ model.setQuitUrl(quitUrl);
+ }
+ request.setBizModel(model);
+
+ AlipayTradeWapPayResponse response = client.pageExecute(request, "POST");
+ if (response == null || StringUtils.isBlank(response.getBody())) {
+ throw new AlipayApiException("支付宝返回为空");
+ }
+ if (!response.isSuccess()) {
+ log.error("支付宝 WAP 下单失败 code={} msg={} subCode={} subMsg={}",
+ response.getCode(), response.getMsg(), response.getSubCode(), response.getSubMsg());
+ throw new AlipayApiException(StringUtils.defaultIfBlank(response.getSubMsg(), response.getMsg()));
+ }
+ return response.getBody();
+ }
+
+ public static Map<String, String> parseRequestParams(HttpServletRequest request) {
+ Map<String, String> params = new HashMap<>();
+ Map<String, String[]> requestParams = request.getParameterMap();
+ for (Map.Entry<String, String[]> entry : requestParams.entrySet()) {
+ String[] values = entry.getValue();
+ if (values == null || values.length == 0) {
+ continue;
+ }
+ StringBuilder valueStr = new StringBuilder();
+ for (int i = 0; i < values.length; i++) {
+ if (i > 0) {
+ valueStr.append(",");
+ }
+ valueStr.append(values[i]);
+ }
+ params.put(entry.getKey(), valueStr.toString());
+ }
+ return params;
+ }
+
+ public static boolean verifyNotify(Map<String, String> params) {
+ try {
+ String alipayPublicKey = normalizePublicKey(PropertiesUtil.getProperty("alipay.alipay-public-key"));
+ return AlipaySignature.rsaCheckV1(params, alipayPublicKey, CHARSET, SIGN_TYPE);
+ } catch (Exception e) {
+ log.error("支付宝回调验签失败", e);
+ return false;
+ }
+ }
+
+ private static AlipayClient createClient() throws AlipayApiException {
+ try {
+ return new DefaultAlipayClient(buildAlipayConfig());
+ } catch (IllegalArgumentException e) {
+ throw new AlipayApiException(e.getMessage(), e);
+ } catch (Exception e) {
+ throw new AlipayApiException("支付宝私钥配置错误,请检查 alipay.private-key", e);
+ }
+ }
+
+ private static AlipayConfig buildAlipayConfig() throws Exception {
+ AlipayConfig config = new AlipayConfig();
+ config.setServerUrl(StringUtils.defaultIfBlank(
+ PropertiesUtil.getProperty("alipay.gateway-url"),
+ "https://openapi.alipay.com/gateway.do"));
+ config.setAppId(PropertiesUtil.getProperty("alipay.app-id"));
+ config.setPrivateKey(toSdkPrivateKey(loadPrivateKeyMaterial()));
+ config.setFormat(FORMAT);
+ config.setAlipayPublicKey(normalizePublicKey(PropertiesUtil.getProperty("alipay.alipay-public-key")));
+ config.setCharset(CHARSET);
+ config.setSignType(SIGN_TYPE);
+ return config;
+ }
+
+ private static String loadPrivateKeyMaterial() {
+ String path = PropertiesUtil.getProperty("alipay.private-key-path");
+ if (StringUtils.isNotBlank(path)) {
+ try {
+ return new String(Files.readAllBytes(Paths.get(path.trim())), StandardCharsets.UTF_8);
+ } catch (Exception e) {
+ throw new IllegalArgumentException("读取 alipay.private-key-path 失败: " + path, e);
+ }
+ }
+ return PropertiesUtil.getProperty("alipay.private-key");
+ }
+
+ static String toSdkPrivateKey(String raw) throws Exception {
+ if (StringUtils.isBlank(raw)) {
+ throw new IllegalArgumentException("alipay.private-key 不能为空");
+ }
+ String compact = extractKeyBody(raw);
+ if (isLikelyPublicKey(compact)) {
+ throw new IllegalArgumentException(
+ "alipay.private-key 配置错误:当前填入的是公钥。请填写「应用私钥 PKCS8」,不要填应用公钥或支付宝公钥。");
+ }
+ if (compact.length() < 800) {
+ throw new IllegalArgumentException(
+ "alipay.private-key 长度异常(" + compact.length() + "),应用私钥通常远长于公钥,请确认已完整粘贴 PKCS8 私钥。");
+ }
+ try {
+ KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(compact)));
+ return compact;
+ } catch (Exception e) {
+ throw new IllegalArgumentException(
+ "应用私钥格式不正确,Java 需使用 PKCS8 格式。请用支付宝开放平台「密钥生成器」生成并选择 PKCS8(Java适用)。", e);
+ }
+ }
+
+ private static String extractKeyBody(String raw) {
+ String material = raw.trim().replace("\\n", "\n");
+ return material
+ .replace("-----BEGIN RSA PRIVATE KEY-----", "")
+ .replace("-----END RSA PRIVATE KEY-----", "")
+ .replace("-----BEGIN PRIVATE KEY-----", "")
+ .replace("-----END PRIVATE KEY-----", "")
+ .replace("-----BEGIN PUBLIC KEY-----", "")
+ .replace("-----END PUBLIC KEY-----", "")
+ .replaceAll("\\s+", "");
+ }
+
+ private static String normalizePublicKey(String key) {
+ if (StringUtils.isBlank(key)) {
+ return key;
+ }
+ return extractKeyBody(key);
+ }
+
+ private static boolean isLikelyPublicKey(String compactKey) {
+ return compactKey.startsWith("MIIBIjAN") || compactKey.startsWith("MIGfMA0G");
+ }
+}
diff --git a/src/main/java/com/nq/utils/sms/SmsBaoClient.java b/src/main/java/com/nq/utils/sms/SmsBaoClient.java
new file mode 100644
index 0000000..0c33ea6
--- /dev/null
+++ b/src/main/java/com/nq/utils/sms/SmsBaoClient.java
@@ -0,0 +1,93 @@
+package com.nq.utils.sms;
+
+import com.nq.utils.PropertiesUtil;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+
+public final class SmsBaoClient {
+
+ private static final Logger log = LoggerFactory.getLogger(SmsBaoClient.class);
+
+ private static final String API_URL = "https://api.smsbao.com/sms";
+
+ private SmsBaoClient() {
+ }
+
+ /**
+ * @return true 发送成功(返回码 0)
+ */
+ public static boolean send(String phone, String content) {
+ String username = PropertiesUtil.getProperty("smsbao.username");
+ String apiKey = PropertiesUtil.getProperty("smsbao.apikey");
+ if (StringUtils.isBlank(username) || StringUtils.isBlank(apiKey)) {
+ log.error("短信宝配置缺失 smsbao.username / smsbao.apikey");
+ return false;
+ }
+ try {
+ String encodedContent = URLEncoder.encode(content, StandardCharsets.UTF_8.name());
+ String httpArg = "u=" + username
+ + "&p=" + apiKey
+ + "&m=" + phone
+ + "&c=" + encodedContent;
+ String result = request(API_URL, httpArg);
+ if ("0".equals(StringUtils.trimToEmpty(result))) {
+ return true;
+ }
+ log.error("短信宝发送失败 phone={} result={} msg={}", phone, result, mapError(result));
+ return false;
+ } catch (Exception e) {
+ log.error("短信宝发送异常 phone=" + phone, e);
+ return false;
+ }
+ }
+
+ private static String request(String httpUrl, String httpArg) throws Exception {
+ String fullUrl = httpUrl + "?" + httpArg;
+ URL url = new URL(fullUrl);
+ HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+ connection.setRequestMethod("GET");
+ connection.setConnectTimeout(10000);
+ connection.setReadTimeout(10000);
+ connection.connect();
+ try (InputStream is = connection.getInputStream();
+ BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
+ StringBuilder sbf = new StringBuilder();
+ String line;
+ while ((line = reader.readLine()) != null) {
+ sbf.append(line);
+ }
+ return sbf.toString();
+ }
+ }
+
+ private static String mapError(String code) {
+ if (code == null) {
+ return "未知错误";
+ }
+ switch (code.trim()) {
+ case "30":
+ return "错误密码";
+ case "40":
+ return "账号不存在";
+ case "41":
+ return "余额不足";
+ case "43":
+ return "IP地址限制";
+ case "50":
+ return "内容含有敏感词";
+ case "51":
+ return "手机号码不正确";
+ default:
+ return "错误码:" + code;
+ }
+ }
+}
diff --git a/src/main/java/com/nq/utils/sms/SmsConstants.java b/src/main/java/com/nq/utils/sms/SmsConstants.java
new file mode 100644
index 0000000..bb05b31
--- /dev/null
+++ b/src/main/java/com/nq/utils/sms/SmsConstants.java
@@ -0,0 +1,20 @@
+package com.nq.utils.sms;
+
+public final class SmsConstants {
+
+ public static final String SMS_CODE_PREFIX = "SmsCode:";
+ public static final String SMS_SEND_LOCK_PREFIX = "SmsSendLock:";
+ /** 验证码有效期(秒) */
+ public static final int SMS_CODE_TTL_SECONDS = 60;
+ /** 同一手机号发送间隔(秒) */
+ public static final int SMS_SEND_INTERVAL_SECONDS = 60;
+
+ /** 短信验证码校验是否开启,对应 application.properties 中 sms.verify.enabled */
+ public static boolean isVerifyEnabled() {
+ String enabled = com.nq.utils.PropertiesUtil.getProperty("sms.verify.enabled");
+ return !"false".equalsIgnoreCase(org.apache.commons.lang3.StringUtils.trimToEmpty(enabled));
+ }
+
+ private SmsConstants() {
+ }
+}
diff --git a/src/main/java/com/nq/utils/smsUtil/smsUtil.java b/src/main/java/com/nq/utils/smsUtil/smsUtil.java
index 713c252..52d5184 100644
--- a/src/main/java/com/nq/utils/smsUtil/smsUtil.java
+++ b/src/main/java/com/nq/utils/smsUtil/smsUtil.java
@@ -5,8 +5,10 @@
import com.nq.utils.PropertiesUtil;
import com.nq.utils.pay.CmcPayOuterRequestUtil;
import com.nq.utils.redis.RedisShardedPoolUtils;
+import com.nq.utils.sms.SmsBaoClient;
import com.nq.utils.smsUtil.support.TecentYunSMSProvider;
import org.apache.commons.lang3.RandomStringUtils;
+import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -76,33 +78,18 @@
*/
public String sendForgetSms(String telephone) {
String code = RandomStringUtils.randomNumeric(4);
- CmcPayOuterRequestUtil requestUtil = new CmcPayOuterRequestUtil();
-
- String sign = "【老虎证券】";
- String uid = PropertiesUtil.getProperty("dxb.sms.USERNAME");
- String key = PropertiesUtil.getProperty("dxb.sms.PASSWORD");
-// String coding = PropertiesUtil.getProperty("wj.sms.coding");
- String smscontent = sign+"您正在申請找回密码,验证码为:" + code + ",5分钟内有效!";
-
-
-
- try {
- uid = URLEncoder.encode(uid,"UTF-8");
- smscontent = URLEncoder.encode(smscontent,"UTF-8");
- } catch (UnsupportedEncodingException e){
- e.printStackTrace();
+ String signName = PropertiesUtil.getProperty("smsbao.sign.name");
+ if (StringUtils.isBlank(signName)) {
+ signName = "滁州炬亿科技";
}
-// String url = "http://"+ cod+ing +".api.smschinese.cn/?Uid="+ uid +"&Key="+ key +"&smsMob=" + telephone + "&smsText="+sign+smscontent;
- String url ="https://api.smsbao.com/sms?u="+uid+"&p="+key+"&m="+telephone+"&c="+smscontent;
- log.info("smsurl"+url);
- String result = requestUtil.sendGet(url);
- log.info("smsresult="+result+"==code="+code);
- if (Integer.valueOf(result) < 0) {
- return "";
- } else {
+ String content = "【" + signName.trim() + "】您的验证码是" + code + "。如非本人操作,请忽略本短信";
+ log.info("sendForgetSms phone={}", telephone);
+ if (SmsBaoClient.send(telephone, content)) {
String keys = "AliyunSmsCode:" + telephone;
RedisShardedPoolUtils.setEx(keys, code, 5400);
+ log.info("sendForgetSms success phone={} code={}", telephone, code);
return code;
}
+ return "";
}
}
diff --git a/src/main/java/com/nq/utils/task/stock/ClosingStayTask.java b/src/main/java/com/nq/utils/task/stock/ClosingStayTask.java
index e86fec3..d26f103 100644
--- a/src/main/java/com/nq/utils/task/stock/ClosingStayTask.java
+++ b/src/main/java/com/nq/utils/task/stock/ClosingStayTask.java
@@ -1,70 +1,44 @@
-package com.nq.utils.task.stock;//package com.nq.utils.task.stock;
-
+package com.nq.utils.task.stock;
import com.nq.service.IUserPositionService;
-
import com.nq.utils.DateTimeUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
import java.util.Date;
-import org.slf4j.Logger;
-
-import org.slf4j.LoggerFactory;
-
-import org.springframework.beans.factory.annotation.Autowired;
-
-import org.springframework.scheduling.annotation.Scheduled;
-
-import org.springframework.stereotype.Component;
-
-
-//@Component
+@Component
public class ClosingStayTask {
private static final Logger log = LoggerFactory.getLogger(ClosingStayTask.class);
-
@Autowired
IUserPositionService iUserPositionService;
- /*遞延費周一到周五,每天七點定時計算*/
-@Scheduled(cron = "0 0 7 * * ?")
-@Scheduled(cron = "0 15 9 ? * MON-FRI")
+ /** 递延费:T+1,每个交易日 14:50 从余额扣款 */
+ @Scheduled(cron = "0 */1 * * * ?")
public void closingStayV1() {
-
- log.info("=======================收盤收取留倉費任務開始 ===========================");
-
- log.info("收盤收取留倉費任務 開始時間 = {}", DateTimeUtil.dateToStr(new Date()));
-
- log.info("");
-
+ log.info("=======================递延费扣款任务开始 ===========================");
+ log.info("递延费扣款任务 开始时间 = {}", DateTimeUtil.dateToStr(new Date()));
dotask();
-
- log.info("");
-
- log.info("收盤收取留倉費任務 結束時間 = {}", DateTimeUtil.dateToStr(new Date()));
-
- log.info("=======================收盤收取留倉費任務結束 ===========================");
-
+ log.info("递延费扣款任务 结束时间 = {}", DateTimeUtil.dateToStr(new Date()));
+ log.info("=======================递延费扣款任务结束 ===========================");
}
-
public void dotask() {
this.iUserPositionService.doClosingStayTask();
}
- /*留倉到期強制平倉,每天15點執行*/
-@Scheduled(cron = "0 0 15 ? * MON-FRI")
+ /** 递延费达到最大天数后强制平仓,每个交易日 14:55 执行 */
+ @Scheduled(cron = "0 55 14 ? * MON-FRI")
public void expireStayUnwind() {
-
- log.info("=======================留倉到期強制平倉任務開始 ===========================");
- log.info("留倉到期強制平倉 開始時間 = {}", DateTimeUtil.dateToStr(new Date()));
-
+ log.info("=======================递延费到期强制平仓任务开始 ===========================");
+ log.info("递延费到期强制平仓 开始时间 = {}", DateTimeUtil.dateToStr(new Date()));
this.iUserPositionService.expireStayUnwindTask();
-
- log.info("留倉到期強制平倉 結束時間 = {}", DateTimeUtil.dateToStr(new Date()));
- log.info("=======================留倉到期強制平倉任務結束 ===========================");
-
+ log.info("递延费到期强制平仓 结束时间 = {}", DateTimeUtil.dateToStr(new Date()));
+ log.info("=======================递延费到期强制平仓任务结束 ===========================");
}
-
}
diff --git a/src/main/java/com/nq/vo/pay/AlipayPayVO.java b/src/main/java/com/nq/vo/pay/AlipayPayVO.java
new file mode 100644
index 0000000..1094192
--- /dev/null
+++ b/src/main/java/com/nq/vo/pay/AlipayPayVO.java
@@ -0,0 +1,14 @@
+package com.nq.vo.pay;
+
+public class AlipayPayVO {
+ /** 支付宝网页支付表单 HTML,前端写入页面后自动提交 */
+ private String payForm;
+
+ public String getPayForm() {
+ return payForm;
+ }
+
+ public void setPayForm(String payForm) {
+ this.payForm = payForm;
+ }
+}
diff --git a/src/main/java/com/nq/vo/position/UserPositionVO.java b/src/main/java/com/nq/vo/position/UserPositionVO.java
index 7e19069..35b9a5d 100644
--- a/src/main/java/com/nq/vo/position/UserPositionVO.java
+++ b/src/main/java/com/nq/vo/position/UserPositionVO.java
@@ -69,6 +69,12 @@
private Integer status;
private Integer lockDays;
+ /** 下次递延费扣款时间戳(毫秒) */
+ private Long nextStayFeeTime;
+ /** 距下次递延费扣款剩余秒数 */
+ private Long stayFeeCountdownSec;
+ /** 节假日休市开关是否开启(影响递延费倒计时与扣费日) */
+ private Boolean respectHoliday;
public void setPositionType(Integer positionType) {
this.positionType = positionType;
@@ -537,4 +543,28 @@
public void setLockDays(Integer lockDays) {
this.lockDays = lockDays;
}
+
+ public Long getNextStayFeeTime() {
+ return nextStayFeeTime;
+ }
+
+ public void setNextStayFeeTime(Long nextStayFeeTime) {
+ this.nextStayFeeTime = nextStayFeeTime;
+ }
+
+ public Long getStayFeeCountdownSec() {
+ return stayFeeCountdownSec;
+ }
+
+ public void setStayFeeCountdownSec(Long stayFeeCountdownSec) {
+ this.stayFeeCountdownSec = stayFeeCountdownSec;
+ }
+
+ public Boolean getRespectHoliday() {
+ return respectHoliday;
+ }
+
+ public void setRespectHoliday(Boolean respectHoliday) {
+ this.respectHoliday = respectHoliday;
+ }
}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 23de710..8077952 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -214,6 +214,14 @@
#dxb.sms.USERNAME=banbao
#dxb.sms.PASSWORD=a705312041274b72b77d43b0ce136913
+# 短信宝(注册验证码)
+smsbao.username=ql20260401
+smsbao.apikey=aeb266b5e4fe4978990ced3651c48ef0
+smsbao.sign.name=滁州炬亿科技
+
+# 短信验证码开关(false=关闭注册/找回密码短信校验)
+sms.verify.enabled=false
+
#?????
@@ -229,6 +237,18 @@
frontend.domain.url=https://www.zhonghenginvest.com
website.token=0DC8F78384C7AAFF3192A9C60A473FEE7F89C62888689616B98A06910E86B510
+# 支付宝手机网站支付(alipay.trade.wap.pay / QUICK_WAP_WAY)
+# private-key: 应用私钥 PKCS8(MIIEv 开头,不要用公钥 MIIBIjAN...)
+# alipay-public-key: 支付宝公钥(开放平台页面复制)
+alipay.app-id=2021006162616448
+# 应用私钥 PKCS8,以 MIIEv 开头(约1600字符)。下面这行请替换成你的真实私钥,不要填 MIIBIjAN 开头的公钥
+alipay.private-key=MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQCwveCMeuLAYI1eSV+DaC3VyR46VxDrYs0SHiXokPL9C373oe3LxY7t/11AVuehrhL9sxR1SM7eg8+g/dCdgzU6fS4y09QqwTkx7JMyphYqN/c38OCK5mUcv358KYxpjWQ/M4eYOuZz1IQ0RktARfWe97XtnAEUw/LJSNEVd0TQMh//APlIKLUf2sjN6J8gT3SMBywU53YDvrhHaY5evq6xV3ce1KAqY5E6Rw4GwRP9RPmYoehrtmF2Uu1JJ1wL3v8gWV6uspQGK1Nz0XnUQ8MvECFyXgpaRuy9VjiYKb4JbyOSWF4EULeJxMrh8jcJNWlpxs4RZrt7RRkIjHYXkagBAgMBAAECggEBAJId2FerfG05JYauuFUmOsaEyAXJV0aS3yE8ZXC47PpAl85YRAT0Ji8isFBxyHvFMxQlCiPQdp5FcNETJXYjNE1PknMY504VgpiAkk0MPETZEfxgmvKaeTtkIUi6/MeCyj7p9g7nmwBl0Ip7FtljPSYYeoKfN9GYEc9Q8PKDMRPBCjpHwDMS6dWbiuvRGXvILTx8n2c3JK0ea/btjspERhbGqfMoPZH0GculqLeaBU0HlyFmnY+OYewOKS4XIC82J/b1iOlrBj6vHJir1Rekz72xgSwJfCKvwxPWzjpXOP0kWiZqGc3byJty0tJoU6a24nFbRag5qv8P/htmShjXUMECgYEA8kqXesTOdQgzBSZXdcQOYwWi3iHmMEaM4FO6C5Oie227P0Q1BX20AOuKthO7vCEiCTMgFjjU1GcLj1bk+kuxcVkFP4CUX/zBT2WPhr6WDH2USLmMEgzCBK7WkmxvhKJuilEYjztZ9+kQgmshusLM+0hNW5CNC8fsUB/vWykoKAkCgYEAur3ZCsvbDARLrZoLVPxoCN7bWoq4EyEd0uFjZ0uYGryk/5OWnmRjClRSuKmAqxpfmLsOsvFVLmr0qAu5n3hzyecI+kAiZCDTB7zHg6VE0t2SGMZgqA8YHxR8Uox8kncOUrfIHsS0NdMo0Ro+TlzqMu00Qqzta9Qfn0LxSVV+TjkCgYEAhveMOERHaaP+n8DC+YQUK47pMeoaSwwRz6h0O5yq5yyyIMUC8CMc0WJkK5cEfgDK7+r91cMks4kPRHK4LMFbf/OB2+TpQisPGAZQFFQ4S47sujI515gpv+gQ+RHUcUL2ZrBUCP2b2WCH5M5+xIVdICEfjoEQQN7GQb/4WoSrrNkCgYEAk/A41MvA4dk5VvOVjKNVi8l5f1YsE8OFi/3QsRF7E5tlH6/9f8fJinJZAkAOVf7TwvxV3b9Am0BSSBrZ2BMlr8rfGtnaJPa4GAYfDRTN3Dt6NTRbeGhU1LJ29zKisamt6Cv+VxnAJm/9gwJGX/BN/UBbyYKTqHvo/VOcMVuQb0kCgYEA126j0Psc1pw8HshLa9wYmX4jfZyFQZnmdtsev0ANu5XS0KaBLzOyKI3SaFNNsfoflrJYRzKHMNJfmHH31OqPeSSqqJl8T4/3BoMWYHhxeI/VBqGFt2PbAd2fcjsjfB/zIpOGGn+PMzvS1z93Fk/tamtBlbTVUyf6P8u8MGlYhy4=
+alipay.private-key-path=
+alipay.alipay-public-key=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlvQHrsQgc8laFkPZVSFV8xcVCRkKbYMRAQjYZTzV5SNf/ELRpznXQmMjtw4RUtA8FdkDa6BOjBTmesL1XboWJD6zi4+TZSFiBfeRPhqbG/xR7FooCG+dgPLBFWvPBG7yMSPFHlLMJnrQENtF8nk1/l14G6tBmmS1Q8kdCbqX4y25UTEVgRvHKQdvSESq53HbFliNNt3L5VQFXnhsl6FnHSKFnZmzVASKac71raW9VQD3AYtPnS2qTaXvcplAdvJ+uHdm03Z+nyqwULB4eT2Bzq/ezc6DUoTdPLUj483qpBismrD84s1NiFdCv0PsCTwC7kDQLzp4DDFxdPHYbBpdnQIDAQAB
+alipay.gateway-url=https://openapi.alipay.com/gateway.do
+alipay.product-name=
+alipay.frontend-redirect=https://www.zhonghenginvest.com/#/rechargelist
+
#?????
news.main.url=http://eminfo.eastmoney.com
diff --git a/src/main/resources/mapper/SiteSettingMapper.xml b/src/main/resources/mapper/SiteSettingMapper.xml
index a8caae9..93dcd3d 100644
--- a/src/main/resources/mapper/SiteSettingMapper.xml
+++ b/src/main/resources/mapper/SiteSettingMapper.xml
@@ -11,6 +11,7 @@
<arg column="stay_max_days" jdbcType="INTEGER" javaType="java.lang.Integer" />
<arg column="buy_min_amt" jdbcType="INTEGER" javaType="java.lang.Integer" />
<arg column="charge_min_amt" jdbcType="INTEGER" javaType="java.lang.Integer" />
+ <arg column="charge_max_amt" jdbcType="INTEGER" javaType="java.lang.Integer" />
<arg column="buy_min_num" jdbcType="INTEGER" javaType="java.lang.Integer" />
<arg column="force_stop_fee" jdbcType="DECIMAL" javaType="java.math.BigDecimal" />
<arg column="buy_max_amt_percent" jdbcType="DECIMAL" javaType="java.math.BigDecimal" />
@@ -113,7 +114,7 @@
</where>
</sql>
<sql id="Base_Column_List" >
- id, buy_fee, sell_fee, stay_fee, duty_fee, stay_max_days, buy_min_amt, charge_min_amt,
+ id, buy_fee, sell_fee, stay_fee, duty_fee, stay_max_days, buy_min_amt, charge_min_amt, charge_max_amt,
buy_min_num, force_stop_fee, buy_max_amt_percent, force_stop_percent, hight_and_low,
with_min_amt, crease_max_percent, buy_max_num, with_time_begin, with_time_end, trans_am_begin,
trans_am_end, trans_pm_begin, trans_pm_end, trans_am_begin_us, trans_am_end_us, trans_pm_begin_us,
@@ -159,7 +160,7 @@
</selectKey>
insert into site_setting (id, buy_fee, sell_fee,
stay_fee, duty_fee, stay_max_days,
- buy_min_amt, charge_min_amt, buy_min_num,
+ buy_min_amt, charge_min_amt, charge_max_amt, buy_min_num,
force_stop_fee, buy_max_amt_percent, force_stop_percent,
hight_and_low, with_min_amt, crease_max_percent,
buy_max_num, with_time_begin, with_time_end,
@@ -176,7 +177,7 @@
hk_exchange_rate)
values (#{id,jdbcType=INTEGER}, #{buyFee,jdbcType=DECIMAL}, #{sellFee,jdbcType=DECIMAL},
#{stayFee,jdbcType=DECIMAL}, #{dutyFee,jdbcType=DECIMAL}, #{stayMaxDays,jdbcType=INTEGER},
- #{buyMinAmt,jdbcType=INTEGER}, #{chargeMinAmt,jdbcType=INTEGER}, #{buyMinNum,jdbcType=INTEGER},
+ #{buyMinAmt,jdbcType=INTEGER}, #{chargeMinAmt,jdbcType=INTEGER}, #{chargeMaxAmt,jdbcType=INTEGER}, #{buyMinNum,jdbcType=INTEGER},
#{forceStopFee,jdbcType=DECIMAL}, #{buyMaxAmtPercent,jdbcType=DECIMAL}, #{forceStopPercent,jdbcType=DECIMAL},
#{hightAndLow,jdbcType=DECIMAL}, #{withMinAmt,jdbcType=INTEGER}, #{creaseMaxPercent,jdbcType=DECIMAL},
#{buyMaxNum,jdbcType=INTEGER}, #{withTimeBegin,jdbcType=INTEGER}, #{withTimeEnd,jdbcType=INTEGER},
@@ -219,6 +220,9 @@
</if>
<if test="chargeMinAmt != null" >
charge_min_amt,
+ </if>
+ <if test="chargeMaxAmt != null" >
+ charge_max_amt,
</if>
<if test="buyMinNum != null" >
buy_min_num,
@@ -366,6 +370,9 @@
</if>
<if test="chargeMinAmt != null" >
#{chargeMinAmt,jdbcType=INTEGER},
+ </if>
+ <if test="chargeMaxAmt != null" >
+ #{chargeMaxAmt,jdbcType=INTEGER},
</if>
<if test="buyMinNum != null" >
#{buyMinNum,jdbcType=INTEGER},
@@ -525,6 +532,9 @@
<if test="record.chargeMinAmt != null" >
charge_min_amt = #{record.chargeMinAmt,jdbcType=INTEGER},
</if>
+ <if test="record.chargeMaxAmt != null" >
+ charge_max_amt = #{record.chargeMaxAmt,jdbcType=INTEGER},
+ </if>
<if test="record.buyMinNum != null" >
buy_min_num = #{record.buyMinNum,jdbcType=INTEGER},
</if>
@@ -663,6 +673,7 @@
stay_max_days = #{record.stayMaxDays,jdbcType=INTEGER},
buy_min_amt = #{record.buyMinAmt,jdbcType=INTEGER},
charge_min_amt = #{record.chargeMinAmt,jdbcType=INTEGER},
+ charge_max_amt = #{record.chargeMaxAmt,jdbcType=INTEGER},
buy_min_num = #{record.buyMinNum,jdbcType=INTEGER},
force_stop_fee = #{record.forceStopFee,jdbcType=DECIMAL},
buy_max_amt_percent = #{record.buyMaxAmtPercent,jdbcType=DECIMAL},
@@ -731,6 +742,9 @@
</if>
<if test="chargeMinAmt != null" >
charge_min_amt = #{chargeMinAmt,jdbcType=INTEGER},
+ </if>
+ <if test="chargeMaxAmt != null" >
+ charge_max_amt = #{chargeMaxAmt,jdbcType=INTEGER},
</if>
<if test="buyMinNum != null" >
buy_min_num = #{buyMinNum,jdbcType=INTEGER},
@@ -867,6 +881,7 @@
stay_max_days = #{stayMaxDays,jdbcType=INTEGER},
buy_min_amt = #{buyMinAmt,jdbcType=INTEGER},
charge_min_amt = #{chargeMinAmt,jdbcType=INTEGER},
+ charge_max_amt = #{chargeMaxAmt,jdbcType=INTEGER},
buy_min_num = #{buyMinNum,jdbcType=INTEGER},
force_stop_fee = #{forceStopFee,jdbcType=DECIMAL},
buy_max_amt_percent = #{buyMaxAmtPercent,jdbcType=DECIMAL},
--
Gitblit v1.9.3