基于Spring Boot的HTTP请求签名验证实现解析
概述
在分布式系统交互中,API接口的安全性至关重要。本文将深入解析基于Spring Boot实现的HTTP请求签名验证机制,该方案支持GET/POST等多种请求方式,提供时效性验证和数据完整性保障。以下是核心实现的技术要点解析。
功能特性
- 多协议支持:完整覆盖GET、POST(JSON/Form-Data)等常见请求类型
- 时效控制:5分钟有效期的请求时间窗口
- 多重验证:公钥校验 + 签名验证 + 时间戳的三重防护机制
- 安全过滤:自动排除签名参数参与验签计算
- 编码兼容:自动处理URL特殊字符编码问题
核心实现解析
1. 主校验流程
public boolean verifySignature(HttpServletRequest request,
HttpServletResponse response,
GridAccountUser accountUser) {
// 获取公钥配置
StateGridAccount gridAccount = stateGridService
.getStateGridAccountById(accountUser.getAccountId());
// 请求类型路由
if(HttpMethod.POST.matches(request.getMethod())) {
// 处理JSON/Form-Data类型
} else if(HttpMethod.GET.matches(request.getMethod())) {
// 处理GET参数
}
// 统一返回校验结果
}
2. 关键技术点
2.1 时间戳验证
private Boolean verifyTimestamp(String timestampStr) {
long timestamp = Long.parseLong(timestampStr);
long currentTime = System.currentTimeMillis();
return Math.abs(currentTime - timestamp) <= 300_000; // 5分钟有效期
}
- 防止重放攻击
- 要求客户端服务端时间同步
- 误差窗口可配置化建议
2.2 请求体处理
JSON请求处理:
String requestStr = getRequestBody(request);
JSONObject jsonObject = JSON.parseObject(requestStr);
map.remove("sign"); // 过滤签名参数
Form-Data处理:
Map<String, Object> formData = WebUtils.getParametersStartingWith(request, "");
formData.remove("sign");
GET请求处理:
Map<String, String[]> queryParams = request.getParameterMap();
signature = signature.replaceAll(" ", "+"); // 处理URL编码
2.3 签名验证
SignUtil.verifySignature(
uri, // 请求路径
data.toString(), // 过滤后的参数
Long.parseLong(timestamp),
signature,
publicKey
);
优化建议
- 参数序列化优化
- 当前同时使用Fastjson和Hutool的JSONUtil,建议统一JSON处理库
- 推荐使用Jackson进行标准化处理
- 异常处理增强
try {
// 验签逻辑
} catch (NumberFormatException e) {
log.error("时间戳格式异常: {}", timestampStr);
throw new InvalidTimestampException();
} catch (SignatureException e) {
log.warn("签名验证失败: {}", e.getMessage());
}
- 性能优化
- 添加公钥缓存机制(RedisCache)
- 采用连接池管理数据库查询
- 安全增强
- 添加重放攻击计数器
- 支持动态时间窗口配置
- 增加黑名单IP机制
注意事项
- 时间同步:确保NTP服务的时间同步
- 密钥管理:建议采用密钥轮换机制
- 空参数处理:需要明确空字符串和null的处理策略
- 编码一致性:统一使用UTF-8字符集
- 日志脱敏:敏感参数需要做日志过滤
总结
该实现方案为API接口安全提供了基础保障,在实际生产环境中可根据业务需求扩展以下功能:
- 增加流量限频控制
- 实现双向证书验证
- 支持多种哈希算法
- 添加OpenAPI规范支持
- 集成API管理平台
通过持续优化验签流程和完善监控机制,可以有效构建安全可靠的API网关体系。
附完整代码
生产秘钥私钥工具类
package com.aspire.datasynchron.common.utils;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.Map;
public class KeyGenExample {
private static final String ALGORITHM = "RSA";
public static Map<String, String> generateKey() throws NoSuchAlgorithmException {
// 1. 选择算法(RSA/EC/DSA)
KeyPairGenerator keyGen = KeyPairGenerator.getInstance(ALGORITHM);
keyGen.initialize(2048); // 密钥长度
// 2. 生成密钥对
KeyPair keyPair = keyGen.generateKeyPair();
// 3. 获取公私钥(Base64 编码打印)
byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
String privateKey = Base64.getEncoder().encodeToString(privateKeyBytes);
String publicKey = Base64.getEncoder().encodeToString(publicKeyBytes);
Map<String, String> map = ApiKeyGenerator.generateApiCredentials();
map.put("private_key", privateKey);
map.put("public_key", publicKey);
return map;
}
public static void main(String[] args) {
try {
Map<String, String> stringStringMap = generateKey();
System.out.println(stringStringMap);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
}
生成签名和验签方法
package com.aspire.datasynchron.common.utils;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.json.JSONUtil;
import org.apache.commons.lang3.ArrayUtils;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
/**
* <b>System:</b>NCC<br/>
* <b>Title:</b>SignUtil.java<br/>
* <b>Description:</b>添加描述信息<br/>
* <b>@author: </b>zhouxiaomin_a<br/>
* <b>@date:</b>2018/6/21 17:00<br/>
* <b>@version:</b> 1.0.0.0<br/>
* <b>Copyright (c) 2017 ASPire Tech.</b>
*/
public class SignUtil {
private static final String CHARSET = "UTF-8";
private static final String SIGNATURE_ALGORITHM = "SHA256withRSA";
private static final String KEY_ALGORITHM = "RSA";
// 通过传入私钥、数据和时间戳生成签名的方法
public static String generateSignature(String url, String data, long timestamp, String privateKeyStr) throws Exception {
// 将数据和时间戳合并成一个字符串
String dataWithTimestamp = url + "|" + data + "|" + timestamp;
// 通过传入的私钥字符串生成私钥
PrivateKey privateKey = getPrivateKeyFromString(privateKeyStr);
// 创建签名对象,使用 SHA256withRSA 算法
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initSign(privateKey);
// 更新数据
signature.update(dataWithTimestamp.getBytes(StandardCharsets.UTF_8));
// 生成签名
byte[] signedData = signature.sign();
// 将签名转换为 Base64 编码的字符串
return Base64.getEncoder().encodeToString(signedData);
}
// 通过传入私钥字符串,生成私钥对象的方法
public static PrivateKey getPrivateKeyFromString(String privateKeyStr) throws Exception {
// 将 Base64 编码的私钥字符串解码为字节数组
byte[] decodedKey = Base64.getDecoder().decode(privateKeyStr);
// 使用 KeyFactory 生成私钥对象
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKey);
return keyFactory.generatePrivate(keySpec);
}
// 验证签名的方法
public static boolean verifySignature(String url, String data, Long timestamp, String signatureStr, String publicKeyStr) throws Exception {
if (StringUtils.isEmpty(url) || StringUtils.isEmpty(data) || null == timestamp
|| StringUtils.isEmpty(signatureStr) || StringUtils.isEmpty(publicKeyStr)) {
return false;
}
// 将数据和时间戳合并成一个字符串
String dataWithTimestamp = url + "|" + data + "|" + timestamp;
// 通过传入的公钥字符串生成公钥
PublicKey publicKey = getPublicKeyFromString(publicKeyStr);
// 创建签名对象,使用 SHA256withRSA 算法
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initVerify(publicKey);
// 更新数据
signature.update(dataWithTimestamp.getBytes(StandardCharsets.UTF_8));
// 将 Base64 编码的签名字符串转换为字节数组
byte[] signatureBytes = Base64.getDecoder().decode(signatureStr);
// 验证签名
return signature.verify(signatureBytes);
}
// 通过传入公钥字符串,生成公钥对象的方法
public static PublicKey getPublicKeyFromString(String publicKeyStr) throws Exception {
// 将 Base64 编码的公钥字符串解码为字节数组
byte[] decodedKey = Base64.getDecoder().decode(publicKeyStr);
// 使用 KeyFactory 生成公钥对象
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decodedKey);
return keyFactory.generatePublic(keySpec);
}
// 测试代码
public static void main(String[] args) throws Exception {
// 获取公钥和私钥的 Base64 编码字符串
String privateKeyStr = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC+3ZTH/VCeWaesY4muy7fOvqLLTX07QpfJVeG1KZKSCjeciih3VRQfMn1PsxL0elMUFIfRKGOtjS7o0+V3UqAwaJCozyxEMPw3OGKQ0bb2dCId/TCnjWkFpSOj1UKct/LdOT/CZ07Bu+8DCynjzHSxwATe2DYWZp8fHf9FAuYjPuXhkeFEkf+/TG5ObyDE4VqzHiTb4XmjkJCuhk98H5CLOCrWb+NiyHpbp/fPF865QzEG1n9/pyFKyMDsBgSxmx0iXDheR6ue/wotxnDvJgGqaG00kD88r/oU2M3xo581a9sF+v6fzNCldtF+MHJJVd4OhFcscahBySEhIUY9TZo/AgMBAAECggEAIh6TP7MBa+VEC5WZocUqHQvIJ0a5adQMNUIkgJGncXLhIRszg62SVMdeTlaJP2n0mwTWiKXLN9Wiup1SimObXjv7DCpI1AHbvHVYbWIH7oOxK6I8xd8KFKfCOMHhUAm0ISbgRnzYP9q8LdObj+zXOYVFeZ62AIgkzte6b9hGUqtWv4yGL3VTiki5TvzHpRPJoeEYHEk+zGhdCn9dr7GY+3mm3EfnNL69s78jWAdkib8fHQsgunOB4NiA4Gnn58Aphzw/SYio6mS5ieRXz4h9ng77UI4agkuU6QhYMdt+s+7YTs4Z6+iXnE3f0zHZJT/UA1GItcxnYuznl2X+vKWS3QKBgQD3L/lUfh5R2N40rB16cE4W75betT59TIjEW+/g5VY4WhZZXEmIi7HWcxoAYXeTp1/Ls2KrKWyAs5ys89lTmasJh2M8afS04mUTCtOO2/bsnUXc0g7P8M913X2fWME+uIVLXBbG2+0xfXGgLCtsgZro5IkDqrHO09SX8s02jOAbJQKBgQDFq5HkGsxJZzS7anFoaBnXxUK590bkIt4eIyOs5Z7AldppWIaT5uCR9TAq3N29rvxBPBwiRKIsghleTHtkvccImDECu6ztVubn1oaudvNnHPbUyi5rfxg+dAKUoLxX3nzniSYupyN94QDq+q/OPiTBopyAXxRmNfn8x3YGvOi0kwKBgQCv3D/E7x1fGa2tR66JR5EnHDn4JHZa6rJ7EPWuyTr4SI+R7+iY7toNOkKLdsx+DhxHbk6Ke6QoRKD5I1vA8JkQ5HOjrbZdYpyKWa99+dzJJnNn0UKcijTvJC+VyK1jlB+xJ8lEnX85MIhAbmxOfD7b5ovcQfrSrT6ZBDMf1kYyyQKBgHQq32NRyGr/B0N5S8rTGxTubceCphvezfCiMA4lKAYAS0qL5xM2pRXCJZubD4mxM7hWziXpdfF4R9ZeVkofKcBISM1VZExbPPpU3fPcHjGkGP93Do7IM4RIg1e7mtR9AaTEujbCrR4GRJbT2sv3Q3y0xwq+Veu3nwHKaveMv6mXAoGBAM0trI9b/k7oHS8z9FfETTeiXxac3puumRcGt3HmCISPmXxc4RWMVbMPUWSzm2KFdWtKEYMMxvSZcEG90rxM/Mh3Uhdj8Pdz2iwAnCghpwzAtp60N2yKxJyq80gRFywTlNp70VxDxYCFZ9Dugjzg7NT2JbhOdSjoZDP9FXg+konq";
String publicKeyStr = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvt2Ux/1QnlmnrGOJrsu3zr6iy019O0KXyVXhtSmSkgo3nIood1UUHzJ9T7MS9HpTFBSH0ShjrY0u6NPld1KgMGiQqM8sRDD8NzhikNG29nQiHf0wp41pBaUjo9VCnLfy3Tk/wmdOwbvvAwsp48x0scAE3tg2FmafHx3/RQLmIz7l4ZHhRJH/v0xuTm8gxOFasx4k2+F5o5CQroZPfB+Qizgq1m/jYsh6W6f3zxfOuUMxBtZ/f6chSsjA7AYEsZsdIlw4Xkernv8KLcZw7yYBqmhtNJA/PK/6FNjN8aOfNWvbBfr+n8zQpXbRfjBySVXeDoRXLHGoQckhISFGPU2aPwIDAQAB";
String url = "/api/rest/demo/queryAlarmInfo";
// 要签名的数据和时间戳
String json = "{\"SPECIALTY\":8,\"NETWORKTYPE\":801,\"VENDORNAME\":\"华为\",\"NETYPE\":\"801\",\"ALARMTITLE\":\"\",\"ORG_EVENT_ID\":\"\"}";
Map map = JSONUtil.toBean(json, Map.class);
String data = map.toString();
System.out.println(data);
long timestamp = System.currentTimeMillis(); // 当前时间戳
System.out.println(timestamp);
// 生成签名
String signature = generateSignature(url, data, timestamp, privateKeyStr);
System.out.println("Generated Signature: " + signature);
// 验证签名
boolean isVerified = SignUtil.verifySignature(url, data, timestamp, signature, publicKeyStr);
System.out.println("Signature Verified: " + isVerified);
System.out.println("---------------------------------------------------------");
String url2 = "/api/rest/demo/getUser";
Map<String, Object> params = new HashMap<>();
params.put("idcard", "123456");
long timestamp1 = System.currentTimeMillis(); // 当前时间戳
System.out.println(timestamp1);
String data2 = params.toString();
System.out.println(data2);
// 生成签名
String signature1 = generateSignature(url2, data2, timestamp1, privateKeyStr);
System.out.println("Generated Signature1: " + signature1);
// 验证签名
boolean isVerified1 = SignUtil.verifySignature(url2, data2, timestamp1, signature1, publicKeyStr);
System.out.println("Signature isVerified1: " + isVerified1);
System.out.println("---------------------------------------------------------");
String url3 = "/api/rest/demo/getIdCard";
Map<String, Object> params1 = new HashMap<>();
params1.put("name", "李四");
params1.put("age", "27");
String data3 = params1.toString();
System.out.println(data3);
long timestamp2 = System.currentTimeMillis(); // 当前时间戳
System.out.println(timestamp2);
// 生成签名
String signature2 = generateSignature(url3, data3, timestamp2, privateKeyStr);
System.out.println("Generated signature2: " + signature2);
// 验证签名
boolean isVerified2 = SignUtil.verifySignature(url3, data3, timestamp2, signature2, publicKeyStr);
System.out.println("Signature isVerified2: " + isVerified2);
}
}
自定义拦截器
package com.aspire.datasynchron.framework.interceptor;
import com.aspire.datasynchron.common.core.domain.model.GridAccountUser;
import com.aspire.datasynchron.common.utils.StringUtils;
import com.aspire.datasynchron.framework.web.service.TokenService;
import com.aspire.datasynchron.framework.web.service.VerifySignatureService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.nio.charset.StandardCharsets;
@Slf4j
@Component
public class SignatureInterceptor implements HandlerInterceptor {
private final TokenService tokenService;
private final VerifySignatureService verifySignatureService;
@Autowired
public SignatureInterceptor(TokenService tokenService, VerifySignatureService verifySignatureService) {
this.tokenService = tokenService;
this.verifySignatureService = verifySignatureService;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String clientType = request.getHeader("clientType");
if (StringUtils.isNotEmpty(clientType)) {
GridAccountUser accountUser = tokenService.getAccountUser(request);
// 自定义验签逻辑
boolean verified = verifySignatureService.verifySignature(request, response, accountUser);
if (!verified) {
log.error("验签失败");
response.setStatus(HttpStatus.FORBIDDEN.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
// 构建错误响应
String errorMessage = "{\"error\": \"签名不合法\", \"message\": \"Signature is invalid.\"}";
response.getWriter().write(errorMessage);
return false;
}
}
return true;
}
}
验签实现类
package com.aspire.datasynchron.framework.web.service;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.aspire.datasynchron.common.core.domain.model.GridAccountUser;
import com.aspire.datasynchron.common.core.redis.RedisCache;
import com.aspire.datasynchron.common.utils.SignUtil;
import com.aspire.datasynchron.stateGrid.domain.StateGridAccount;
import com.aspire.datasynchron.stateGrid.domain.vo.StateGridAccountDetailsVO;
import com.aspire.datasynchron.stateGrid.service.IStateGridAccountService;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;
import org.springframework.web.util.WebUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.*;
@Slf4j
@Service
public class VerifySignatureService {
@Autowired
private IStateGridAccountService stateGridService;
public boolean verifySignature(HttpServletRequest request, HttpServletResponse response, GridAccountUser accountUser) {
//查询公钥
StateGridAccount gridAccount = stateGridService.getStateGridAccountById(accountUser.getAccountId());
if (gridAccount == null || StringUtils.isEmpty(gridAccount.getPublicKey())) {
log.error("账号[{}]公钥未配置", accountUser.getAccountId());
return false;
}
String publicKey = gridAccount.getPublicKey();
String uri = request.getRequestURI();
// 判断当前请求方式
if (HttpMethod.POST.name().equals(request.getMethod())) {
// 判断是否是 JSON 格式的请求
if (MediaType.APPLICATION_JSON_VALUE.equals(request.getContentType())) {
// 获取请求体的内容
String requestStr = getRequestBody(request);
log.info("请求体内容:{}", requestStr);
if (StringUtils.isEmpty(requestStr)) {
return false;
}
JSONObject jsonObject = JSON.parseObject(requestStr);
Map map = JSONUtil.toBean(requestStr, Map.class);
map.remove("sign");
map.remove("timestamp");
String timestamp = jsonObject.getString("timestamp");
String signature = jsonObject.getString("sign");
// 验证时间戳
Boolean verifed = verifTimestamp(timestamp);
if (!verifed) {
return false;
}
try {
boolean isValid = SignUtil.verifySignature(uri, map.toString(), Long.parseLong(timestamp), signature, publicKey);
return isValid;
} catch (Exception e) {
log.error(e.getMessage());
return false;
}
} else if (request.getContentType() != null && request.getContentType().startsWith(MediaType.MULTIPART_FORM_DATA_VALUE)) {
// 处理 form 表单数据格式
Map<String, Object> formData = WebUtils.getParametersStartingWith(request, "");
log.info("表单数据:{}", formData);
String timestamp = (String) formData.get("timestamp");
String signature = (String) formData.get("sign");
formData.remove("sign");
formData.remove("timestamp");
// 验证时间戳
Boolean verifed = verifTimestamp(timestamp);
if (!verifed) {
return false;
}
try {
boolean isValid = SignUtil.verifySignature(uri, formData.toString(), Long.parseLong(timestamp), signature, publicKey);
return isValid;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
} else if (HttpMethod.GET.name().equals(request.getMethod())) {
// GET 请求的处理,获取查询参数进行验签
Map<String, String[]> queryParams = request.getParameterMap();
if (queryParams == null) {
return false;
}
Map<String, String> filteredParams = new HashMap<>();
queryParams.forEach((key, values) -> {
// 过滤掉 "sign" 和 "timestamp" 参数
if (!"sign".equals(key) && !"timestamp".equals(key)) {
filteredParams.put(key, values.length > 0 ? values[0] : "");
}
});
String timestamp = request.getParameter("timestamp");
String signature = request.getParameter("sign");
if (timestamp == null || signature == null) {
return false;
}
// 验证时间戳
Boolean verifed = verifTimestamp(timestamp);
if (!verifed) {
return false;
}
//+ 符号通常会被转换为一个空格 (%20),这是因为在 URL 编码中,+ 符号表示空格
signature = signature.replaceAll(" ", "+");
boolean isValid = false;
try {
isValid = SignUtil.verifySignature(uri, filteredParams.toString(), Long.parseLong(timestamp), signature, publicKey);
if (!isValid) {
return false; // 返回 false 表示验签失败
}
return true; // 返回 true 表示验签成功
} catch (Exception e) {
log.error(e.getMessage());
return false;
}
}
return false; // 默认返回 false,如果请求方法不支持
}
/**
* @throws
* @MethodName verifTimestamp
* @author zhouzihao
* @param: timestampStr
* @DateTime 2025年3月25日, 0025 下午 06:13
* @return: java.lang.Boolean
* @description:检查时间戳:确保客户端和服务端时间同步,误差在5分钟内。
*/
private Boolean verifTimestamp(String timestampStr) {
long timestamp = Long.parseLong(timestampStr);
long currentTime = System.currentTimeMillis();
if (Math.abs(currentTime - timestamp) > 300000) { // 5分钟
log.error("验签失败:时间戳已过期");
return false;
}
return true;
}
/**
* 读取请求体的内容
*/
private static String getRequestBody(HttpServletRequest request) {
InputStream in = null;
StringBuffer sb = null;
try {
in = request.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(in,
Charset.forName("UTF-8")));
sb = new StringBuffer("");
String temp;
while ((temp = br.readLine()) != null) {
sb.append(temp);
}
if (in != null) {
in.close();
}
if (br != null) {
br.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
return sb.toString();
}
}
基于Spring Boot的HTTP请求签名验证实现解析的更多相关文章
- step6----->往工程中添加spring boot项目------->修改pom.xml使得我的project是基于spring boot的,而非直接基于spring framework
文章内容概述: spring项目组其实有多个projects,如spring IO platform用于管理external dependencies的版本,通过定义BOM(bill of mater ...
- Https系列之三:让服务器同时支持http、https,基于spring boot
Https系列会在下面几篇文章中分别作介绍: 一:https的简单介绍及SSL证书的生成二:https的SSL证书在服务器端的部署,基于tomcat,spring boot三:让服务器同时支持http ...
- 基于Spring Boot、Spring Cloud、Docker的微服务系统架构实践
由于最近公司业务需要,需要搭建基于Spring Cloud的微服务系统.遍访各大搜索引擎,发现国内资料少之又少,也难怪,国内Dubbo正统治着天下.但是,一个技术总有它的瓶颈,Dubbo也有它捉襟见肘 ...
- 实战基于Spring Boot 2的WebFlux和mLab搭建反应式Web
Spring Framework 5带来了新的Reactive Stack非阻塞式Web框架:Spring WebFlux.作为与Spring MVC并行使用的Web框架,Spring WebFlux ...
- 基于Spring Boot的统一异常处理设计
基于Spring Boot的统一异常处理设计 作者: Grey 原文地址:https://www.cnblogs.com/greyzeng/p/11733327.html Spring Boot中,支 ...
- 基于Spring Boot的注解驱动式公众号极速开发框架FastBootWeixin
本框架基于Spring Boot实现,使用注解完成快速开发,可以快速的完成一个微信公众号,重新定义公众号开发. 在使用本框架前建议对微信公众号开发文档有所了解,不过在不了解公众号文档的情况下使用本框架 ...
- 如何优雅的关闭基于Spring Boot 内嵌 Tomcat 的 Web 应用
背景 最近在搞云化项目的启动脚本,觉得以往kill方式关闭服务项目太粗暴了,这种kill关闭应用的方式会让当前应用将所有处理中的请求丢弃,响应失败.这种形式的响应失败在处理重要业务逻辑中是要极力避免的 ...
- 快速搭建基于Spring Boot + Spring Security 环境
个人博客网:https://wushaopei.github.io/ (你想要这里多有) 1.Spring Security 权限管理框架介绍 简介: Spring Security 提供了基于 ...
- 基于Spring Boot/Spring Session/Redis的分布式Session共享解决方案
分布式Web网站一般都会碰到集群session共享问题,之前也做过一些Spring3的项目,当时解决这个问题做过两种方案,一是利用nginx,session交给nginx控制,但是这个需要额外工作较多 ...
- 基于Spring Boot的图片上传
package com.clou.inteface.domain.web.user; import java.io.File; import java.io.IOException; import j ...
随机推荐
- Java中的基本数据类型默认值扩展
因为在很多情况下,如果要转换的数据为null,调用者期望的是返回默认值. 系统自动提供的默认值不能满足我们的需求,例如int的默认值为0,但是在sql查询中,如果查询失败,我们期望的是小于0的值,例如 ...
- 独立开发经验谈:如何通过 Docker 让潜在客户快速体验你的系统
我在业余时间开发了一款自己的独立产品:升讯威在线客服与营销系统.陆陆续续开发了几年,从一开始的偶有用户尝试,到如今线上环境和私有化部署均有了越来越多的稳定用户,在这个过程中,我也积累了不少如何开发运营 ...
- 一种基于alpine、支持ARM架构64位的镜像构建方法及其构建系统
本文分享自天翼云开发者社区<一种基于alpine.支持ARM架构64位的镜像构建方法及其构建系统>,作者:郑****团 一种基于alpine.支持ARM架构64位的镜像构建方法及其构建系统 ...
- 天翼云云电脑:IAAS基础设施带来的计算革新
本文分享自天翼云开发者社区<天翼云云电脑:IAAS基础设施带来的计算革新>,作者:不知不觉 在当今这个数字化快速发展的时代,云计算作为一种新兴的信息技术,已经逐渐成为企业和个人日常运营的重 ...
- 10GSFP+系列光模块
10GSFP+双纤系列光模块包括SR.LRM.LR.ER.ZR模块,它们的接口类型都是LC双工,且符合IEEE802.3ae.SFF-8472和SFF-8431标准,以下是这几种光模块的具体详情. 1 ...
- 记一次腾讯云轻量级服务器安装mysql配置完成后,外网无法访问问题
一.配置信息正常 1.防火墙配置通过 2.mysql端口正常启动netstat -antlp | grep 3306 3.配置都正常,但是telnet访问不通超时Operation timed out ...
- 我的世界服务端插件安装 Vault经济前置插件安装(商店,圈地等需要该前置插件)
Minecraft服务端插件安装-Vault用户登录插件安装 需要准备Vault插件 Vault.jar经济前置插件 Minecraft Vault插件是一款用于Minecraft服务器的多功能经济. ...
- Esp32s3(立创实战派)移植LVGL
Esp32s3(立创实战派)移植LVGL 移植: 使用软件EEZ studio 创建工程选择带EEZ Flow的,可以使用该软件更便捷的功能 根据屏幕像素调整画布为320*240 复制ui文件至工程 ...
- android短信数据库监听回调多次问题
在监听android短信数据库变化时.由于只能注册content://sms/ 的observer.所以,在数据库每次状态变化的时候,都会多次回调 onChange 方式.目前还未找到很好的方式,解决 ...
- QT5笔记:36. QGraphicsView 综合示例 (完结撒花!)
通过此示例可以比较熟悉QGraphincsView的流程以及操作 坐标关系以及获取: View坐标:左上角为原点,通过鼠标移动事件获取 Scene坐标:中心为原点,横竖为X,Y轴.通过View.map ...