增加拉卡拉电子合同html页面
This commit is contained in:
parent
5da423e50b
commit
b8627633fb
@ -6,11 +6,18 @@ import com.suisung.mall.common.api.ResultCode;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.ConstraintViolationException;
|
||||
import java.sql.SQLException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 全局异常处理器
|
||||
@ -25,22 +32,62 @@ import java.sql.SQLException;
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
|
||||
private static final String UNKNOWN_LOCATION = "未知位置";
|
||||
private static final String DB_ERROR_MSG = "数据库操作失败,请稍后重试";
|
||||
private static final String LOG_FORMAT = "业务异常 || URI={} || Method={} || Error={} || Location={}";
|
||||
|
||||
/**
|
||||
* 获取异常发生位置信息
|
||||
*
|
||||
* @param stackTrace 异常堆栈
|
||||
* @return 格式化的位置信息(类名.方法名 : 行号)
|
||||
* 处理参数校验异常
|
||||
*/
|
||||
private String getExceptionLocation(StackTraceElement[] stackTrace) {
|
||||
if (stackTrace == null || stackTrace.length == 0) {
|
||||
return UNKNOWN_LOCATION;
|
||||
@ExceptionHandler({MethodArgumentNotValidException.class, BindException.class})
|
||||
public CommonResult handleValidationException(Exception e, HttpServletRequest req) {
|
||||
String errorMessage;
|
||||
|
||||
if (e instanceof MethodArgumentNotValidException) {
|
||||
errorMessage = ((MethodArgumentNotValidException) e).getBindingResult().getFieldErrors().stream()
|
||||
.map(FieldError::getDefaultMessage)
|
||||
.collect(Collectors.joining("; "));
|
||||
} else {
|
||||
errorMessage = ((BindException) e).getBindingResult().getFieldErrors().stream()
|
||||
.map(FieldError::getDefaultMessage)
|
||||
.collect(Collectors.joining("; "));
|
||||
}
|
||||
StackTraceElement first = stackTrace[0];
|
||||
return String.format("%s.%s:%d", first.getClassName(), first.getMethodName(), first.getLineNumber());
|
||||
|
||||
logError(req, "参数校验失败", e);
|
||||
return CommonResult.validateFailed(errorMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理约束违反异常
|
||||
*/
|
||||
@ExceptionHandler(ConstraintViolationException.class)
|
||||
public CommonResult handleConstraintViolation(ConstraintViolationException e, HttpServletRequest req) {
|
||||
String errorMessage = e.getConstraintViolations().stream()
|
||||
.map(ConstraintViolation::getMessage)
|
||||
.collect(Collectors.joining("; "));
|
||||
|
||||
logError(req, "约束违反异常", e);
|
||||
return CommonResult.validateFailed(errorMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理参数类型不匹配异常
|
||||
*/
|
||||
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
|
||||
public CommonResult handleMethodArgumentTypeMismatch(MethodArgumentTypeMismatchException e, HttpServletRequest req) {
|
||||
String errorMessage = String.format("参数%s类型不匹配,需要%s类型,但接收到%s",
|
||||
e.getName(), e.getRequiredType().getSimpleName(), e.getValue());
|
||||
|
||||
logError(req, "参数类型不匹配", e);
|
||||
return CommonResult.validateFailed(errorMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理数字格式异常和其他参数异常
|
||||
*/
|
||||
@ExceptionHandler({NumberFormatException.class, IllegalArgumentException.class})
|
||||
public CommonResult handleParameterException(Exception e, HttpServletRequest req) {
|
||||
String errorMessage = e.getMessage();
|
||||
logError(req, "参数异常", e);
|
||||
return CommonResult.validateFailed(errorMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -48,8 +95,7 @@ public class GlobalExceptionHandler {
|
||||
*/
|
||||
@ExceptionHandler({SQLException.class, DataAccessException.class, Exception.class})
|
||||
public CommonResult handleSystemException(HttpServletRequest req, Exception e) {
|
||||
String location = getExceptionLocation(e.getStackTrace());
|
||||
logger.error(LOG_FORMAT, req.getRequestURI(), req.getMethod(), e.getMessage(), location, e);
|
||||
logError(req, e.getMessage(), e);
|
||||
|
||||
if (e instanceof SQLException || e instanceof DataAccessException) {
|
||||
return CommonResult.failed(DB_ERROR_MSG);
|
||||
@ -62,12 +108,22 @@ public class GlobalExceptionHandler {
|
||||
*/
|
||||
@ExceptionHandler(ApiException.class)
|
||||
public CommonResult handleApiException(HttpServletRequest req, ApiException e) {
|
||||
String location = getExceptionLocation(e.getStackTrace());
|
||||
logger.error(LOG_FORMAT,
|
||||
req.getRequestURI(), req.getMethod(), e.getErrorCode(), location, e);
|
||||
|
||||
logError(req, e.getErrorCode() != null ? e.getErrorCode().getMessage() : e.getMessage(), e);
|
||||
return StrUtil.isNotBlank(e.getMessage())
|
||||
? CommonResult.failed(e.getMessage())
|
||||
: CommonResult.failed(ResultCode.FAILED);
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录错误日志
|
||||
*/
|
||||
private void logError(HttpServletRequest req, String message, Exception e) {
|
||||
StackTraceElement[] stackTrace = e.getStackTrace();
|
||||
String location = (stackTrace == null || stackTrace.length == 0)
|
||||
? "未知位置"
|
||||
: String.format("%s.%s:%d", stackTrace[0].getClassName(), stackTrace[0].getMethodName(), stackTrace[0].getLineNumber());
|
||||
|
||||
logger.error("业务异常 || URI={} || Method={} || Error={} || Location={}",
|
||||
req.getRequestURI(), req.getMethod(), message, location, e);
|
||||
}
|
||||
}
|
||||
@ -90,6 +90,7 @@ secure:
|
||||
- "/esProduct/**"
|
||||
- "/admin/oss/upload/**"
|
||||
- "/admin/shop/wxqrcode/common/wxurlscheme"
|
||||
- "/mobile/shop/lakala/sign/ec/**"
|
||||
- "/mobile/**/**/test/case"
|
||||
- "/**/**/testcase"
|
||||
universal:
|
||||
|
||||
@ -273,6 +273,11 @@
|
||||
<version>4.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
|
||||
<dependency>
|
||||
|
||||
@ -13,6 +13,7 @@ import cn.hutool.json.JSONUtil;
|
||||
import com.suisung.mall.common.api.CommonResult;
|
||||
import com.suisung.mall.common.service.impl.BaseControllerImpl;
|
||||
import com.suisung.mall.shop.lakala.service.LakalaApiService;
|
||||
import com.suisung.mall.shop.lakala.service.LklLedgerEcService;
|
||||
import com.suisung.mall.shop.library.service.LibraryProductService;
|
||||
import com.suisung.mall.shop.message.service.MqMessageService;
|
||||
import com.suisung.mall.shop.message.service.PushMessageService;
|
||||
@ -28,6 +29,7 @@ import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.util.Base64Utils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
@ -71,6 +73,9 @@ public class LakalaController extends BaseControllerImpl {
|
||||
@Resource
|
||||
private MqMessageService mqMessageService;
|
||||
|
||||
@Resource
|
||||
private LklLedgerEcService lklLedgerEcService;
|
||||
|
||||
@ApiOperation(value = "测试案例", notes = "测试案例")
|
||||
@RequestMapping(value = "/testcase", method = RequestMethod.POST)
|
||||
public Object testcase(@RequestBody JSONObject paramsJSON) {
|
||||
@ -154,6 +159,26 @@ public class LakalaController extends BaseControllerImpl {
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(resp);
|
||||
}
|
||||
|
||||
@ApiOperation(value = "跳转到拉卡拉签署合同链接", notes = "跳转到拉卡拉签署合同链接")
|
||||
@RequestMapping(value = "/sign/ec/{code}", method = RequestMethod.GET)
|
||||
public ModelAndView jumpToSignLklEcLink(@PathVariable Long code) {
|
||||
// // 根据 code 值决定重定向到哪个 URL
|
||||
// String resultUrl = lklLedgerEcService.getLklEcResultUrl(code);
|
||||
// if (StrUtil.isBlank(resultUrl)) {
|
||||
// return new RedirectView("https://mall.gpxscs.cn/mchapp");
|
||||
// }
|
||||
// return new RedirectView(resultUrl);
|
||||
|
||||
// 根据 code 值获取结果 URL
|
||||
String resultUrl = lklLedgerEcService.getLklEcResultUrl(code);
|
||||
|
||||
ModelAndView modelAndView = new ModelAndView("sign_lkl_ec");
|
||||
modelAndView.addObject("resultUrl", resultUrl);
|
||||
|
||||
// 返回模板名称,渲染 sign_lkl_ec.html 页面
|
||||
return modelAndView;
|
||||
}
|
||||
|
||||
@ApiOperation(value = "商户分账业务开通申请", notes = "商户分账业务开通申请")
|
||||
@RequestMapping(value = "/ledger/applyLedgerMer", method = RequestMethod.POST)
|
||||
public CommonResult ledgerApplyLedgerMer(@RequestBody JSONObject paramsJSON) {
|
||||
|
||||
@ -50,4 +50,12 @@ public interface LklLedgerEcService extends IBaseService<LklLedgerEc> {
|
||||
LklLedgerEc getByMchId(Long mchId, String ecStatus, Integer status);
|
||||
|
||||
|
||||
/**
|
||||
* 获取拉卡拉商户签署合同页面URL
|
||||
*
|
||||
* @param mchId
|
||||
* @return
|
||||
*/
|
||||
String getLklEcResultUrl(Long mchId);
|
||||
|
||||
}
|
||||
|
||||
@ -135,4 +135,19 @@ public class LklLedgerEcServiceImpl extends BaseServiceImpl<LklLedgerEcMapper, L
|
||||
return findOne(queryWrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取拉卡拉商户签署合同页面URL
|
||||
*
|
||||
* @param mchId
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public String getLklEcResultUrl(Long mchId) {
|
||||
LklLedgerEc record = getByMchId(mchId, "", null);
|
||||
if (record != null) {
|
||||
return record.getResult_url();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
368
mall-shop/src/main/resources/templates/sign_lkl_ec.html
Normal file
368
mall-shop/src/main/resources/templates/sign_lkl_ec.html
Normal file
@ -0,0 +1,368 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
|
||||
<title>小发同城电子合同签署</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
background-color: #f5f5f5;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 100%;
|
||||
margin: 0 auto;
|
||||
background-color: #fff;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: linear-gradient(135deg, #4CAF50, #2E7D32);
|
||||
color: white;
|
||||
padding: 30px 20px 20px;
|
||||
text-align: center;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
margin: 0;
|
||||
font-size: 22px;
|
||||
font-weight: 500;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
padding: 30px 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.success-icon {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
background-color: #e8f5e9;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.success-icon span {
|
||||
font-size: 40px;
|
||||
color: #4CAF50;
|
||||
}
|
||||
|
||||
.message {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.message p {
|
||||
margin-bottom: 20px;
|
||||
font-size: 16px;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.message p:first-child {
|
||||
font-size: 18px;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.link-container {
|
||||
background-color: #f0f9ff;
|
||||
border: 1px dashed #2196F3;
|
||||
border-radius: 12px;
|
||||
padding: 20px 15px;
|
||||
margin: 25px 0;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.link-title {
|
||||
font-weight: 500;
|
||||
color: #1976D2;
|
||||
margin-bottom: 12px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.link {
|
||||
color: #1976D2;
|
||||
word-break: break-all;
|
||||
font-size: 15px;
|
||||
font-family: monospace;
|
||||
padding: 10px;
|
||||
background-color: #e3f2fd;
|
||||
border-radius: 8px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.app-option {
|
||||
background-color: #fff3e0;
|
||||
border: 1px solid #ffcc80;
|
||||
border-radius: 12px;
|
||||
padding: 20px 15px;
|
||||
margin: 20px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.app-option p {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
color: #e65100;
|
||||
}
|
||||
|
||||
.app-icon {
|
||||
font-size: 36px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.warning {
|
||||
background-color: #fff8e1;
|
||||
border-left: 4px solid #ffc107;
|
||||
padding: 15px;
|
||||
margin: 25px 0;
|
||||
text-align: left;
|
||||
border-radius: 0 6px 6px 0;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.warning strong {
|
||||
color: #e65100;
|
||||
}
|
||||
|
||||
.footer {
|
||||
background-color: #fafafa;
|
||||
padding: 20px 15px;
|
||||
text-align: center;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
|
||||
.contact {
|
||||
color: #1976D2;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.contact:hover, .contact:active {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 16px;
|
||||
background: linear-gradient(135deg, #2196F3, #1976D2);
|
||||
color: white;
|
||||
text-align: center;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
font-size: 17px;
|
||||
font-weight: 500;
|
||||
margin: 20px 0;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 4px 6px rgba(33, 150, 243, 0.2);
|
||||
}
|
||||
|
||||
.btn:active {
|
||||
transform: translateY(1px);
|
||||
box-shadow: 0 2px 3px rgba(33, 150, 243, 0.2);
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
background: #e3f2fd;
|
||||
color: #1976D2;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
/* 移动端优化 */
|
||||
@media (max-width: 480px) {
|
||||
.header {
|
||||
padding: 25px 15px 15px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 20px 15px;
|
||||
}
|
||||
|
||||
.success-icon {
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
}
|
||||
|
||||
.success-icon span {
|
||||
font-size: 35px;
|
||||
}
|
||||
|
||||
.message p {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.message p:first-child {
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.link-container, .app-option {
|
||||
padding: 15px 12px;
|
||||
}
|
||||
|
||||
.link {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 超小屏幕优化 */
|
||||
@media (max-width: 360px) {
|
||||
body {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 14px;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>小发同城电子合同签署</h1>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<div class="success-icon">
|
||||
<span>✓</span>
|
||||
</div>
|
||||
|
||||
<div class="message">
|
||||
<p>恭喜!您的开店入驻申请已审核通过</p>
|
||||
<p>请在24小时内完成电子合同签署</p>
|
||||
|
||||
<div class="link-container">
|
||||
<div class="link-title">合作方签署链接(24小时内有效)</div>
|
||||
<div class="link" id="signLink" th:text="${resultUrl}">${resultUrl}</div>
|
||||
<button class="btn copy-btn" onclick="copyLink()">复制链接</button>
|
||||
</div>
|
||||
|
||||
<!-- <div class="app-option">-->
|
||||
<!-- <div class="app-icon">📱</div>-->
|
||||
<!-- <p>或前往<strong>小发商家版APP</strong>完成签署</p>-->
|
||||
<!-- </div>-->
|
||||
|
||||
<button class="btn" onclick="openLink()">立即签署合同</button>
|
||||
|
||||
<div class="warning">
|
||||
<strong>注意:</strong>链接有效期为24小时,逾期需重新提交申请。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p>如有疑问请联系客服:<a class="contact" href="tel:17777525395">17777525395</a></p>
|
||||
<p>感谢您对小发同城的支持!</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script th:inline="javascript">
|
||||
// 获取后端传递的resultUrl变量
|
||||
var resultUrl = '${resultUrl}';
|
||||
|
||||
// 复制链接功能
|
||||
function copyLink() {
|
||||
var linkText = document.getElementById('signLink').textContent;
|
||||
if (navigator.clipboard) {
|
||||
navigator.clipboard.writeText(linkText).then(() => {
|
||||
alert('链接已复制到剪贴板');
|
||||
}).catch(err => {
|
||||
console.error('复制失败:', err);
|
||||
fallbackCopyTextToClipboard(linkText);
|
||||
});
|
||||
} else {
|
||||
fallbackCopyTextToClipboard(linkText);
|
||||
}
|
||||
}
|
||||
|
||||
// 兼容性复制方法
|
||||
function fallbackCopyTextToClipboard(text) {
|
||||
var textArea = document.createElement("textarea");
|
||||
textArea.value = text;
|
||||
textArea.style.position = "fixed";
|
||||
document.body.appendChild(textArea);
|
||||
textArea.focus();
|
||||
textArea.select();
|
||||
|
||||
try {
|
||||
var successful = document.execCommand('copy');
|
||||
if (successful) {
|
||||
alert('链接已复制到剪贴板');
|
||||
} else {
|
||||
prompt("请手动复制以下链接:", text);
|
||||
}
|
||||
} catch (err) {
|
||||
prompt("请手动复制以下链接:", text);
|
||||
}
|
||||
|
||||
document.body.removeChild(textArea);
|
||||
}
|
||||
|
||||
// 打开链接功能
|
||||
function openLink() {
|
||||
if (resultUrl) {
|
||||
// 如果是完整URL则直接跳转
|
||||
if (resultUrl.startsWith('http') || resultUrl.startsWith('https')) {
|
||||
window.location.href = resultUrl;
|
||||
} else {
|
||||
// 否则添加协议前缀
|
||||
window.location.href = 'https://' + resultUrl;
|
||||
}
|
||||
} else {
|
||||
alert('链接无效,请联系客服');
|
||||
}
|
||||
}
|
||||
|
||||
// 检测是否在微信中打开
|
||||
var ua = navigator.userAgent.toLowerCase();
|
||||
if (ua.includes('micromessenger')) {
|
||||
// 在微信中显示提示
|
||||
var btn = document.querySelector('.btn');
|
||||
if (btn) {
|
||||
btn.textContent = '点击右上角菜单选择在浏览器中打开';
|
||||
}
|
||||
}
|
||||
|
||||
// 防止页面被嵌入到iframe中
|
||||
if (window.self !== window.top) {
|
||||
window.top.location = window.location;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in New Issue
Block a user