Initial commit
This commit is contained in:
+29
@@ -0,0 +1,29 @@
|
||||
package com.gis.basic_template_not_login_back;
|
||||
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||
|
||||
/**
|
||||
* 后台启动入口
|
||||
* 启动时过滤DataSourceAutoConfiguration,避免数据源自动配置
|
||||
*/
|
||||
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
|
||||
@MapperScan("com.gis.basic_template_not_login_back.mapper") // 扫描MyBatis的Mapper接口
|
||||
public class BasicTemplateNotLoginBackApplication {
|
||||
|
||||
// 使用非静态变量
|
||||
@Value("${server.port}")
|
||||
private Integer port;
|
||||
|
||||
public static void main(String[] args) {
|
||||
// 启动Spring Boot应用并获取应用上下文
|
||||
var context = SpringApplication.run(BasicTemplateNotLoginBackApplication.class, args);
|
||||
|
||||
// 从上下文中获取当前实例
|
||||
BasicTemplateNotLoginBackApplication app = context.getBean(BasicTemplateNotLoginBackApplication.class);
|
||||
System.out.println("后端服务启动成功!访问地址: http://localhost:" + app.port);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.gis.basic_template_not_login_back.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 加解密配置属性类
|
||||
* 从application.yml中读取safety.crypto配置
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "safety.crypto")
|
||||
public class CryptoProperties {
|
||||
|
||||
/**
|
||||
* 响应无需加密的路径列表
|
||||
*/
|
||||
private List<String> noEncryptPaths = Collections.emptyList();
|
||||
|
||||
/**
|
||||
* 请求无需解密的路径列表
|
||||
*/
|
||||
private List<String> noDecryptPaths = Collections.emptyList();
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.gis.basic_template_not_login_back.config;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 数据源切换注解
|
||||
* 可用于类或方法上,指定使用的数据源
|
||||
*/
|
||||
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface DataSource {
|
||||
|
||||
/**
|
||||
* 数据源名称
|
||||
* 可选值:master(主库)、slave等
|
||||
* @return 数据源名称
|
||||
*/
|
||||
String value() default "master";
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.gis.basic_template_not_login_back.config;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Aspect
|
||||
@Component
|
||||
@Order(1)
|
||||
@Slf4j
|
||||
public class DataSourceAspect {
|
||||
|
||||
@Around("@annotation(dataSource) || @within(dataSource)")
|
||||
public Object around(ProceedingJoinPoint point, DataSource dataSource) throws Throwable {
|
||||
try {
|
||||
String dsName = dataSource.value();
|
||||
log.debug("切换数据源: {}", dsName);
|
||||
DataSourceContextHolder.setDataSource(dsName);
|
||||
return point.proceed();
|
||||
} finally {
|
||||
DataSourceContextHolder.clearDataSource();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.gis.basic_template_not_login_back.config;
|
||||
|
||||
import com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceBuilder;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Configuration
|
||||
public class DataSourceConfig {
|
||||
|
||||
@Bean
|
||||
@ConfigurationProperties("spring.datasource.master")
|
||||
public DataSource master() {
|
||||
return DruidDataSourceBuilder.create().build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConfigurationProperties("spring.datasource.slave")
|
||||
public DataSource slave() {
|
||||
return DruidDataSourceBuilder.create().build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
public DataSource dataSource() {
|
||||
DynamicDataSource ds = new DynamicDataSource();
|
||||
Map<Object, Object> map = new HashMap<>();
|
||||
map.put("master", master());
|
||||
map.put("slave", slave());
|
||||
ds.setTargetDataSources(map);
|
||||
ds.setDefaultTargetDataSource(master());
|
||||
return ds;
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package com.gis.basic_template_not_login_back.config;
|
||||
|
||||
/**
|
||||
* 数据源上下文持有者
|
||||
* 使用ThreadLocal存储当前线程使用的数据源名称
|
||||
*/
|
||||
public class DataSourceContextHolder {
|
||||
|
||||
/**
|
||||
* 默认数据源名称
|
||||
*/
|
||||
private static final String DEFAULT_DATASOURCE = "master";
|
||||
|
||||
/**
|
||||
* ThreadLocal存储数据源名称
|
||||
*/
|
||||
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
|
||||
|
||||
/**
|
||||
* 设置数据源名称
|
||||
* @param dataSourceName 数据源名称
|
||||
*/
|
||||
public static void setDataSource(String dataSourceName) {
|
||||
CONTEXT_HOLDER.set(dataSourceName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据源名称
|
||||
* @return 数据源名称
|
||||
*/
|
||||
public static String getDataSource() {
|
||||
String dataSource = CONTEXT_HOLDER.get();
|
||||
return dataSource == null ? DEFAULT_DATASOURCE : dataSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除数据源名称
|
||||
*/
|
||||
public static void clearDataSource() {
|
||||
CONTEXT_HOLDER.remove();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.gis.basic_template_not_login_back.config;
|
||||
|
||||
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
|
||||
|
||||
/**
|
||||
* 动态数据源路由
|
||||
* 根据DataSourceContextHolder中设置的数据源名称,动态选择数据源
|
||||
*/
|
||||
public class DynamicDataSource extends AbstractRoutingDataSource {
|
||||
|
||||
@Override
|
||||
protected Object determineCurrentLookupKey() {
|
||||
return DataSourceContextHolder.getDataSource();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.gis.basic_template_not_login_back.config;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
|
||||
@Configuration
|
||||
public class RedisConfig {
|
||||
|
||||
@Bean
|
||||
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
|
||||
RedisTemplate<String, Object> template = new RedisTemplate<>();
|
||||
template.setConnectionFactory(factory);
|
||||
|
||||
// 字符串序列化器(key 用字符串存储)
|
||||
StringRedisSerializer stringSerializer = new StringRedisSerializer();
|
||||
template.setKeySerializer(stringSerializer);
|
||||
template.setHashKeySerializer(stringSerializer);
|
||||
|
||||
// JSON 序列化器(value 用 JSON 存储,支持对象)
|
||||
Jackson2JsonRedisSerializer<Object> jsonSerializer = new Jackson2JsonRedisSerializer<>(new ObjectMapper(), Object.class);
|
||||
template.setValueSerializer(jsonSerializer);
|
||||
template.setHashValueSerializer(jsonSerializer);
|
||||
|
||||
template.afterPropertiesSet();
|
||||
return template;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.gis.basic_template_not_login_back.controller;
|
||||
|
||||
import com.gis.basic_template_not_login_back.domain.ApiResponse;
|
||||
import com.gis.basic_template_not_login_back.service.ex.ServiceException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
|
||||
/**
|
||||
* 基础controller类,所有controller层对象必须继承这个类
|
||||
*/
|
||||
public class BaseController{
|
||||
|
||||
// 用于统一处理应用层抛出的异常
|
||||
@ExceptionHandler(ServiceException.class)
|
||||
public ApiResponse<Void> handleServiceException(Throwable e) {
|
||||
return ApiResponse.error(e.getMessage());
|
||||
}
|
||||
|
||||
// 用于统一处理其余抛出的异常
|
||||
@ExceptionHandler(RuntimeException.class)
|
||||
public ApiResponse<Void> handleException(Throwable e) {
|
||||
return ApiResponse.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.gis.basic_template_not_login_back.controller;
|
||||
|
||||
import com.gis.basic_template_not_login_back.domain.ApiResponse;
|
||||
import com.gis.basic_template_not_login_back.utils.safety.SM2Utils;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/crypto")
|
||||
public class CryptoController extends BaseController {
|
||||
|
||||
@Resource
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
@Value("${safety.sm2.global}")
|
||||
private String sm2KeyPairRedisKey;
|
||||
|
||||
/**
|
||||
* 初始化SM2密钥对
|
||||
*/
|
||||
@PostConstruct
|
||||
public void initSm2KeyPair() {
|
||||
Object sm2KeyPairObj = redisTemplate.opsForValue().get(sm2KeyPairRedisKey);
|
||||
if (sm2KeyPairObj == null) {
|
||||
Map<String, String> sm2KeyPair = SM2Utils.generateKeyPair();
|
||||
redisTemplate.opsForValue().set(sm2KeyPairRedisKey, sm2KeyPair);
|
||||
System.out.println("SM2密钥对已生成并存储到Redis");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取SM2公钥
|
||||
*/
|
||||
@GetMapping("/sm2/public-key")
|
||||
@SuppressWarnings("unchecked")
|
||||
public ApiResponse<Map<String, String>> getSm2PublicKey() {
|
||||
Map<String, String> sm2KeyPair = (Map<String, String>) redisTemplate.opsForValue().get(sm2KeyPairRedisKey);
|
||||
if (sm2KeyPair == null) {
|
||||
throw new RuntimeException("SM2密钥对未初始化");
|
||||
}
|
||||
|
||||
Map<String, String> result = new HashMap<>();
|
||||
result.put("publicKey", sm2KeyPair.get("publicKey"));
|
||||
return ApiResponse.ok(result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.gis.basic_template_not_login_back.domain;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
public class ApiResponse<T> implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = -7318963194081396360L;
|
||||
|
||||
private int code;
|
||||
private String message;
|
||||
private T data;
|
||||
|
||||
public ApiResponse() {}
|
||||
|
||||
public ApiResponse(int code, String message, T data) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public ApiResponse(int code, String message) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
// 成功响应 - 无数据
|
||||
public static ApiResponse<Void> ok() {
|
||||
return new ApiResponse<>(200, "success");
|
||||
}
|
||||
|
||||
// 成功响应 - 带数据
|
||||
public static <T> ApiResponse<T> ok(T data) {
|
||||
return new ApiResponse<>(200, "success", data);
|
||||
}
|
||||
|
||||
// 成功响应 - 自定义消息和数据
|
||||
public static <T> ApiResponse<T> ok(String message, T data) {
|
||||
return new ApiResponse<>(200, message, data);
|
||||
}
|
||||
|
||||
// 错误响应 - 默认错误
|
||||
public static ApiResponse<Void> error() {
|
||||
return new ApiResponse<>(500, "error");
|
||||
}
|
||||
|
||||
// 错误响应 - 自定义错误消息
|
||||
public static ApiResponse<Void> error(String message) {
|
||||
return new ApiResponse<>(500, message);
|
||||
}
|
||||
|
||||
// 错误响应 - 自定义状态码和错误消息
|
||||
public static ApiResponse<Void> error(Integer code, String message) {
|
||||
return new ApiResponse<>(code, message);
|
||||
}
|
||||
|
||||
// 错误相应,自定义所有信息
|
||||
public static <T> ApiResponse<T> error(Integer code, String message, T data) {
|
||||
return new ApiResponse<>(code, message, data);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,292 @@
|
||||
package com.gis.basic_template_not_login_back.filter;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.TypeReference;
|
||||
import com.gis.basic_template_not_login_back.config.CryptoProperties;
|
||||
import com.gis.basic_template_not_login_back.utils.safety.SM2Utils;
|
||||
import com.gis.basic_template_not_login_back.utils.safety.SM4Utils;
|
||||
import com.gis.basic_template_not_login_back.wrapper.Sm4KeyHolder;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.*;
|
||||
import jakarta.servlet.annotation.WebFilter;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.Setter;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 请求解密过滤器
|
||||
*/
|
||||
@WebFilter(urlPatterns = "/*")
|
||||
@Order(1)
|
||||
@Component
|
||||
public class DecryptFilter implements Filter {
|
||||
|
||||
@Resource
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
@Value("${safety.sm2.global}")
|
||||
private String sm2KeyPairRedisKey;
|
||||
|
||||
@Resource
|
||||
private CryptoProperties cryptoProperties;
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
|
||||
throws IOException, ServletException {
|
||||
HttpServletRequest request = (HttpServletRequest) servletRequest;
|
||||
String requestUri = request.getRequestURI();
|
||||
|
||||
// 检查是否为无需解密的接口
|
||||
if (isNoDecryptPath(requestUri)) {
|
||||
chain.doFilter(request, servletResponse);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 提取加密参数
|
||||
String[] encryptedParams = extractEncryptedParams(request);
|
||||
String encryptedData = encryptedParams[0];
|
||||
String sm4KeyEncrypted = encryptedParams[1];
|
||||
|
||||
// 校验加密参数(仅校验sm4KeyEncrypted,确保必传)
|
||||
if (!StringUtils.hasText(sm4KeyEncrypted)) {
|
||||
throw new IllegalArgumentException("加密参数缺失(sm4KeyEncrypted)");
|
||||
}
|
||||
|
||||
// 解密SM4密钥
|
||||
String sm2PrivateKey = getSm2PrivateKey();
|
||||
String sm4Key = SM2Utils.decrypt(sm2PrivateKey, sm4KeyEncrypted);
|
||||
|
||||
// 存储SM4密钥
|
||||
Sm4KeyHolder.setSm4Key(sm4Key);
|
||||
|
||||
// 仅当encryptedData有实际内容时才解密(排除null和空字符串)
|
||||
String decryptedData = null;
|
||||
if (StringUtils.hasText(encryptedData)) {
|
||||
decryptedData = SM4Utils.decrypt(sm4Key, encryptedData);
|
||||
}
|
||||
|
||||
// 包装解密后的请求(兼容空数据场景)
|
||||
DecryptRequestWrapper wrappedRequest = wrapDecryptedRequest(request, decryptedData);
|
||||
chain.doFilter(wrappedRequest, servletResponse);
|
||||
|
||||
} catch (Exception e) {
|
||||
handleDecryptError(servletResponse, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为无需解密的路径
|
||||
*/
|
||||
private boolean isNoDecryptPath(String requestUri) {
|
||||
for (String path : cryptoProperties.getNoDecryptPaths()) {
|
||||
if (requestUri.contains(path)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取加密参数
|
||||
*/
|
||||
private String[] extractEncryptedParams(HttpServletRequest request) throws IOException {
|
||||
String encryptedData = null;
|
||||
String sm4KeyEncrypted = null;
|
||||
String method = request.getMethod();
|
||||
String contentType = request.getContentType();
|
||||
|
||||
if ("GET".equalsIgnoreCase(method)) {
|
||||
// GET请求从Query参数提取(允许参数为空字符串)
|
||||
encryptedData = request.getParameter("encryptedData");
|
||||
sm4KeyEncrypted = request.getParameter("sm4KeyEncrypted");
|
||||
} else {
|
||||
// POST/PUT请求从Body提取
|
||||
if (contentType == null) {
|
||||
throw new IllegalArgumentException("请求Content-Type不能为空");
|
||||
}
|
||||
|
||||
if (contentType.contains("application/json")) {
|
||||
String jsonBody = readRequestBody(request);
|
||||
// 空JSON体处理(避免解析空字符串报错)
|
||||
if (StringUtils.hasText(jsonBody)) {
|
||||
Map<String, Object> bodyMap = JSON.parseObject(jsonBody, new TypeReference<Map<String, Object>>() {});
|
||||
encryptedData = (String) bodyMap.get("encryptedData");
|
||||
sm4KeyEncrypted = (String) bodyMap.get("sm4KeyEncrypted");
|
||||
}
|
||||
} else if (contentType.contains("multipart/form-data")) {
|
||||
// FormData从表单参数提取
|
||||
encryptedData = request.getParameter("encryptedData");
|
||||
sm4KeyEncrypted = request.getParameter("sm4KeyEncrypted");
|
||||
} else if (contentType.contains("application/x-www-form-urlencoded")) {
|
||||
encryptedData = request.getParameter("encryptedData");
|
||||
sm4KeyEncrypted = request.getParameter("sm4KeyEncrypted");
|
||||
} else {
|
||||
throw new IllegalArgumentException("不支持的Content-Type: " + contentType);
|
||||
}
|
||||
}
|
||||
|
||||
return new String[]{encryptedData, sm4KeyEncrypted};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取SM2私钥
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private String getSm2PrivateKey() {
|
||||
Map<String, String> sm2KeyPair = (Map<String, String>) redisTemplate.opsForValue().get(sm2KeyPairRedisKey);
|
||||
if (sm2KeyPair == null || !sm2KeyPair.containsKey("privateKey")) {
|
||||
throw new RuntimeException("Redis中未找到SM2私钥,请先初始化密钥对");
|
||||
}
|
||||
return sm2KeyPair.get("privateKey");
|
||||
}
|
||||
|
||||
/**
|
||||
* 包装解密后的请求(兼容空数据)
|
||||
*/
|
||||
private DecryptRequestWrapper wrapDecryptedRequest(HttpServletRequest request, String decryptedData) {
|
||||
if ("GET".equalsIgnoreCase(request.getMethod())) {
|
||||
// GET请求:解析为参数Map(空数据返回空Map)
|
||||
Map<String, String[]> originalParams = parseGetParams(decryptedData);
|
||||
DecryptRequestWrapper wrapper = new DecryptRequestWrapper(request, new byte[0]);
|
||||
wrapper.setDecryptedParams(originalParams);
|
||||
return wrapper;
|
||||
} else {
|
||||
// POST请求:空数据转换为空字节数组(避免null)
|
||||
byte[] decryptedBody = StringUtils.hasText(decryptedData)
|
||||
? decryptedData.getBytes(StandardCharsets.UTF_8)
|
||||
: new byte[0];
|
||||
return new DecryptRequestWrapper(request, decryptedBody);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析GET参数(兼容空数据)
|
||||
*/
|
||||
private Map<String, String[]> parseGetParams(String decryptedData) {
|
||||
// 空数据直接返回空Map,避免JSON解析报错
|
||||
if (!StringUtils.hasText(decryptedData)) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
Map<String, Object> paramMap = JSON.parseObject(decryptedData);
|
||||
Map<String, String[]> result = new HashMap<>(paramMap.size());
|
||||
|
||||
for (Map.Entry<String, Object> entry : paramMap.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
Object value = entry.getValue();
|
||||
|
||||
if (value == null) {
|
||||
result.put(key, new String[0]);
|
||||
} else if (value instanceof String) {
|
||||
result.put(key, new String[]{(String) value});
|
||||
} else {
|
||||
result.put(key, new String[]{value.toString()});
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取请求体
|
||||
*/
|
||||
private String readRequestBody(HttpServletRequest request) throws IOException {
|
||||
try (ServletInputStream is = request.getInputStream();
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
sb.append(line);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理解密错误
|
||||
*/
|
||||
private void handleDecryptError(ServletResponse servletResponse, Exception e) throws IOException {
|
||||
servletResponse.setContentType("application/json;charset=UTF-8");
|
||||
servletResponse.setCharacterEncoding("UTF-8");
|
||||
|
||||
Map<String, Object> errorResult = new HashMap<>();
|
||||
errorResult.put("code", 500);
|
||||
errorResult.put("msg", "请求解密失败: " + e.getMessage());
|
||||
errorResult.put("success", false);
|
||||
|
||||
PrintWriter writer = servletResponse.getWriter();
|
||||
writer.write(JSON.toJSONString(errorResult));
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义请求包装类
|
||||
*/
|
||||
public static class DecryptRequestWrapper extends jakarta.servlet.http.HttpServletRequestWrapper {
|
||||
private final byte[] decryptedBody;
|
||||
@Setter
|
||||
private Map<String, String[]> decryptedParams;
|
||||
|
||||
public DecryptRequestWrapper(HttpServletRequest request, byte[] decryptedBody) {
|
||||
super(request);
|
||||
this.decryptedBody = decryptedBody;
|
||||
this.decryptedParams = new HashMap<>(request.getParameterMap());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServletInputStream getInputStream() {
|
||||
ByteArrayInputStream bis = new ByteArrayInputStream(decryptedBody);
|
||||
return new ServletInputStream() {
|
||||
@Override
|
||||
public boolean isFinished() {
|
||||
return bis.available() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReady() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReadListener(ReadListener listener) {
|
||||
// 无需实现
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() {
|
||||
return bis.read();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public BufferedReader getReader() {
|
||||
return new BufferedReader(new InputStreamReader(getInputStream(), StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getParameter(String name) {
|
||||
String[] values = decryptedParams.get(name);
|
||||
return values != null && values.length > 0 ? values[0] : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String[]> getParameterMap() {
|
||||
return decryptedParams;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getParameterValues(String name) {
|
||||
return decryptedParams.get(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.gis.basic_template_not_login_back.service.ex;
|
||||
|
||||
/**
|
||||
* 业务层所有自定义异常父类
|
||||
*/
|
||||
public class ServiceException extends RuntimeException {
|
||||
public ServiceException() {
|
||||
}
|
||||
|
||||
public ServiceException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ServiceException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public ServiceException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public ServiceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package com.gis.basic_template_not_login_back.utils.safety;
|
||||
|
||||
import org.bouncycastle.asn1.gm.GMNamedCurves;
|
||||
import org.bouncycastle.asn1.x9.X9ECParameters;
|
||||
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
|
||||
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
|
||||
import org.bouncycastle.crypto.params.*;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.Security;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* SM2工具类(与前端gm-crypto兼容,加解密使用Hex编码)
|
||||
*/
|
||||
public class SM2Utils {
|
||||
|
||||
static {
|
||||
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
}
|
||||
}
|
||||
|
||||
private static final X9ECParameters x9ECParameters = GMNamedCurves.getByName("sm2p256v1");
|
||||
private static final ECDomainParameters ecDomainParameters = new ECDomainParameters(x9ECParameters.getCurve(), x9ECParameters.getG(), x9ECParameters.getN());
|
||||
|
||||
/**
|
||||
* 生成SM2密钥对
|
||||
*/
|
||||
public static Map<String, String> generateKeyPair() {
|
||||
try {
|
||||
ECKeyPairGenerator generator = new ECKeyPairGenerator();
|
||||
ECKeyGenerationParameters genParams = new ECKeyGenerationParameters(ecDomainParameters, new SecureRandom());
|
||||
generator.init(genParams);
|
||||
AsymmetricCipherKeyPair keyPair = generator.generateKeyPair();
|
||||
|
||||
// 公钥:非压缩格式(64字节,包含0x04前缀共65字节)
|
||||
ECPublicKeyParameters publicKey = (ECPublicKeyParameters) keyPair.getPublic();
|
||||
String publicKeyHex = Hex.toHexString(publicKey.getQ().getEncoded(false));
|
||||
|
||||
// 私钥:32字节Hex字符串
|
||||
ECPrivateKeyParameters privateKey = (ECPrivateKeyParameters) keyPair.getPrivate();
|
||||
String privateKeyHex = privateKey.getD().toString(16);
|
||||
// 补零到64位
|
||||
privateKeyHex = String.format("%64s", privateKeyHex).replace(' ', '0');
|
||||
|
||||
Map<String, String> keyMap = new HashMap<>(2);
|
||||
keyMap.put("publicKey", publicKeyHex);
|
||||
keyMap.put("privateKey", privateKeyHex);
|
||||
return keyMap;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("生成SM2密钥对失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SM2公钥加密(返回Hex字符串)
|
||||
*/
|
||||
public static String encrypt(String publicKeyHex, String plaintext) {
|
||||
try {
|
||||
// 解析带0x04前缀的公钥(65字节)
|
||||
byte[] publicKeyBytes = Hex.decode(publicKeyHex);
|
||||
|
||||
ECPublicKeyParameters publicKey = new ECPublicKeyParameters(x9ECParameters.getCurve().decodePoint(publicKeyBytes), ecDomainParameters);
|
||||
|
||||
// 初始化加密器(C1C3C2模式)
|
||||
org.bouncycastle.crypto.engines.SM2Engine engine = new org.bouncycastle.crypto.engines.SM2Engine((org.bouncycastle.crypto.engines.SM2Engine.Mode.C1C3C2));
|
||||
engine.init(true, new ParametersWithRandom(publicKey, new SecureRandom()));
|
||||
|
||||
byte[] plaintextBytes = plaintext.getBytes(StandardCharsets.UTF_8);
|
||||
byte[] encryptedBytes = engine.processBlock(plaintextBytes, 0, plaintextBytes.length);
|
||||
// 加密结果转为Hex字符串
|
||||
return Hex.toHexString(encryptedBytes);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("SM2加密失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SM2私钥解密(接收Hex字符串密文)
|
||||
*/
|
||||
public static String decrypt(String privateKeyHex, String ciphertextHex) {
|
||||
try {
|
||||
// 解析私钥
|
||||
byte[] privateKeyBytes = Hex.decode(privateKeyHex);
|
||||
ECPrivateKeyParameters privateKey = new ECPrivateKeyParameters(new BigInteger(1, privateKeyBytes), ecDomainParameters);
|
||||
|
||||
// 解码Hex格式密文
|
||||
byte[] ciphertextBytes = Hex.decode(ciphertextHex);
|
||||
|
||||
// 初始化解密器
|
||||
org.bouncycastle.crypto.engines.SM2Engine engine = new org.bouncycastle.crypto.engines.SM2Engine(org.bouncycastle.crypto.engines.SM2Engine.Mode.C1C3C2);
|
||||
engine.init(false, privateKey);
|
||||
|
||||
byte[] decryptedBytes = engine.processBlock(ciphertextBytes, 0, ciphertextBytes.length);
|
||||
return new String(decryptedBytes, StandardCharsets.UTF_8);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("SM2解密失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.gis.basic_template_not_login_back.utils.safety;
|
||||
|
||||
import org.bouncycastle.crypto.digests.SM3Digest;
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
|
||||
/**
|
||||
* SM3算法工具类(纯算法实现,不依赖业务逻辑)
|
||||
* 仅负责:计算字节数组的SM3哈希值、字节数组与十六进制转换
|
||||
*/
|
||||
public class SM3Utils {
|
||||
/**
|
||||
* 计算字节数组的SM3哈希值
|
||||
* @param input 输入字节数组(可任意数据,如盐值+密码的组合)
|
||||
* @return SM3哈希值(32字节)
|
||||
*/
|
||||
public static byte[] hash(byte[] input) {
|
||||
if (input == null || input.length == 0) {
|
||||
throw new IllegalArgumentException("输入字节数组不能为空");
|
||||
}
|
||||
SM3Digest digest = new SM3Digest();
|
||||
digest.update(input, 0, input.length);
|
||||
byte[] hashBytes = new byte[digest.getDigestSize()];
|
||||
digest.doFinal(hashBytes, 0);
|
||||
return hashBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 字节数组转16进制字符串(用于哈希值可视化)
|
||||
*/
|
||||
public static String toHex(byte[] bytes) {
|
||||
if (bytes == null) {
|
||||
return null;
|
||||
}
|
||||
return Hex.toHexString(bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 16进制字符串转字节数组(反向操作)
|
||||
*/
|
||||
public static byte[] fromHex(String hexStr) {
|
||||
if (hexStr == null || hexStr.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return Hex.decode(hexStr);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package com.gis.basic_template_not_login_back.utils.safety;
|
||||
|
||||
import org.bouncycastle.crypto.CipherParameters;
|
||||
import org.bouncycastle.crypto.engines.SM4Engine;
|
||||
import org.bouncycastle.crypto.paddings.PKCS7Padding;
|
||||
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
|
||||
import org.bouncycastle.crypto.params.KeyParameter;
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* SM4工具类(ECB模式,与前端兼容,加解密使用Hex编码)
|
||||
*/
|
||||
public class SM4Utils {
|
||||
|
||||
/**
|
||||
* SM4加密(ECB模式,返回Hex字符串)
|
||||
*/
|
||||
public static String encrypt(String keyHex, String plaintext) {
|
||||
try {
|
||||
// 解析密钥(16字节=32位Hex字符串)
|
||||
byte[] key = Hex.decode(keyHex);
|
||||
if (key.length != 16) {
|
||||
throw new IllegalArgumentException("SM4密钥必须是16字节(32位Hex字符串)");
|
||||
}
|
||||
|
||||
// 初始化加密器(ECB模式 + PKCS7填充)
|
||||
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(
|
||||
new SM4Engine(),
|
||||
new PKCS7Padding()
|
||||
);
|
||||
CipherParameters params = new KeyParameter(key);
|
||||
cipher.init(true, params);
|
||||
|
||||
// 处理明文
|
||||
byte[] plaintextBytes = plaintext.getBytes(StandardCharsets.UTF_8);
|
||||
byte[] output = new byte[cipher.getOutputSize(plaintextBytes.length)];
|
||||
int length = cipher.processBytes(plaintextBytes, 0, plaintextBytes.length, output, 0);
|
||||
length += cipher.doFinal(output, length);
|
||||
|
||||
// 加密结果转为Hex字符串
|
||||
return Hex.toHexString(output, 0, length);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("SM4加密失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SM4解密(ECB模式,接收Hex字符串密文)
|
||||
*/
|
||||
public static String decrypt(String keyHex, String ciphertextHex) {
|
||||
try {
|
||||
// 解析密钥(16字节=32位Hex字符串)
|
||||
byte[] key = Hex.decode(keyHex);
|
||||
if (key.length != 16) {
|
||||
throw new IllegalArgumentException("SM4密钥必须是16字节(32位Hex字符串)");
|
||||
}
|
||||
|
||||
// 解码Hex格式密文
|
||||
byte[] ciphertextBytes = Hex.decode(ciphertextHex);
|
||||
|
||||
// 初始化解密器
|
||||
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(
|
||||
new SM4Engine(),
|
||||
new PKCS7Padding()
|
||||
);
|
||||
CipherParameters params = new KeyParameter(key);
|
||||
cipher.init(false, params);
|
||||
|
||||
// 处理密文
|
||||
byte[] output = new byte[cipher.getOutputSize(ciphertextBytes.length)];
|
||||
int length = cipher.processBytes(ciphertextBytes, 0, ciphertextBytes.length, output, 0);
|
||||
length += cipher.doFinal(output, length);
|
||||
|
||||
// 解密结果转为字符串
|
||||
return new String(output, 0, length, StandardCharsets.UTF_8);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("SM4解密失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
package com.gis.basic_template_not_login_back.wrapper;
|
||||
|
||||
import jakarta.servlet.ReadListener;
|
||||
import jakarta.servlet.ServletInputStream;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequestWrapper;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 解密拦截器
|
||||
*/
|
||||
public class DecryptRequestWrapper extends HttpServletRequestWrapper {
|
||||
// 缓存解密后的请求体(用于POST等带Body的请求)
|
||||
private final byte[] decryptedBody;
|
||||
// 用于更新解密后的参数(GET场景)
|
||||
// 缓存解密后的参数(用于GET等带Query参数的请求)
|
||||
@Setter
|
||||
private Map<String, String[]> decryptedParams;
|
||||
|
||||
public DecryptRequestWrapper(HttpServletRequest request, byte[] decryptedBody) {
|
||||
super(request);
|
||||
this.decryptedBody = decryptedBody;
|
||||
this.decryptedParams = new HashMap<>(request.getParameterMap());
|
||||
}
|
||||
|
||||
// 重写输入流,返回解密后的Body
|
||||
@Override
|
||||
public ServletInputStream getInputStream() throws IOException {
|
||||
ByteArrayInputStream bis = new ByteArrayInputStream(decryptedBody);
|
||||
return new ServletInputStream() {
|
||||
@Override
|
||||
public boolean isFinished() {
|
||||
return bis.available() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReady() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReadListener(ReadListener listener) {}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
return bis.read();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 重写参数获取方法,返回解密后的参数(用于GET)
|
||||
@Override
|
||||
public String getParameter(String name) {
|
||||
String[] values = decryptedParams.get(name);
|
||||
return values != null && values.length > 0 ? values[0] : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String[]> getParameterMap() {
|
||||
return decryptedParams;
|
||||
}
|
||||
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
package com.gis.basic_template_not_login_back.wrapper;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.gis.basic_template_not_login_back.config.CryptoProperties;
|
||||
import com.gis.basic_template_not_login_back.utils.safety.SM4Utils;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.server.ServerHttpRequest;
|
||||
import org.springframework.http.server.ServerHttpResponse;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* 响应数据加密拦截器
|
||||
*/
|
||||
@ControllerAdvice
|
||||
public class EncryptResponseAdvice implements ResponseBodyAdvice<Object> {
|
||||
|
||||
@Resource
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@Resource
|
||||
private CryptoProperties cryptoProperties;
|
||||
|
||||
/**
|
||||
* 判断是否需要加密:排除特定路径,其余全部加密
|
||||
*/
|
||||
@Override
|
||||
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
|
||||
// 获取当前请求的URI
|
||||
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
if (attributes == null) {
|
||||
return false; // 非Web请求场景,不加密
|
||||
}
|
||||
HttpServletRequest request = attributes.getRequest();
|
||||
String requestUri = request.getRequestURI();
|
||||
|
||||
// 检查是否为无需加密的路径
|
||||
for (String path : cryptoProperties.getNoEncryptPaths()) {
|
||||
if (requestUri.contains(path)) {
|
||||
return false; // 排除路径,不加密
|
||||
}
|
||||
}
|
||||
|
||||
// 其余路径均需要加密
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 响应体加密逻辑(保持不变)
|
||||
*/
|
||||
@Override
|
||||
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
|
||||
Class<? extends HttpMessageConverter<?>> selectedConverterType,
|
||||
ServerHttpRequest request, ServerHttpResponse response) {
|
||||
try {
|
||||
String sm4Key = Sm4KeyHolder.getSm4Key();
|
||||
if (sm4Key == null || sm4Key.length() != 32) {
|
||||
throw new RuntimeException("SM4密钥不存在或格式错误,无法加密响应");
|
||||
}
|
||||
|
||||
String plaintext = objectMapper.writeValueAsString(body);
|
||||
String encryptedText = SM4Utils.encrypt(sm4Key, plaintext);
|
||||
return encryptedText;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("响应数据加密失败: " + e.getMessage(), e);
|
||||
} finally {
|
||||
Sm4KeyHolder.clear(); // 清除线程本地存储,避免内存泄漏
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.gis.basic_template_not_login_back.wrapper;
|
||||
|
||||
/**
|
||||
* SM4密钥存储
|
||||
*/
|
||||
public class Sm4KeyHolder {
|
||||
// 线程本地存储SM4密钥(Hex格式,32位字符串)
|
||||
private static final ThreadLocal<String> SM4_KEY_HOLDER = new ThreadLocal<>();
|
||||
|
||||
// 设置当前线程的SM4密钥
|
||||
public static void setSm4Key(String sm4Key) {
|
||||
SM4_KEY_HOLDER.set(sm4Key);
|
||||
}
|
||||
|
||||
// 获取当前线程的SM4密钥
|
||||
public static String getSm4Key() {
|
||||
return SM4_KEY_HOLDER.get();
|
||||
}
|
||||
|
||||
// 清除线程本地存储(避免内存泄漏)
|
||||
public static void clear() {
|
||||
SM4_KEY_HOLDER.remove();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
spring:
|
||||
datasource:
|
||||
# Druid 公共配置
|
||||
druid:
|
||||
initial-size: 5
|
||||
min-idle: 5
|
||||
max-active: 20
|
||||
max-wait: 60000
|
||||
time-between-eviction-runs-millis: 60000
|
||||
min-evictable-idle-time-millis: 300000
|
||||
validation-query: SELECT 1
|
||||
test-while-idle: true
|
||||
test-on-borrow: false
|
||||
test-on-return: false
|
||||
pool-prepared-statements: true
|
||||
max-pool-prepared-statement-per-connection-size: 20
|
||||
filters: stat,wall
|
||||
web-stat-filter:
|
||||
enabled: true
|
||||
stat-view-servlet:
|
||||
enabled: true
|
||||
url-pattern: /druid/*
|
||||
login-username: admin
|
||||
login-password: admin
|
||||
|
||||
# 主库
|
||||
master:
|
||||
url: jdbc:postgresql://47.92.216.173:7654/xian?characterEncoding=utf8&TimeZone=Asia/Shanghai
|
||||
username: postgres
|
||||
password: zhangsan
|
||||
driver-class-name: org.postgresql.Driver
|
||||
|
||||
# 从库
|
||||
slave:
|
||||
url: jdbc:postgresql://47.92.216.173:7654/assess_disaster?characterEncoding=utf8&TimeZone=Asia/Shanghai
|
||||
username: postgres
|
||||
password: zhangsan
|
||||
driver-class-name: org.postgresql.Driver
|
||||
@@ -0,0 +1,35 @@
|
||||
spring:
|
||||
datasource:
|
||||
# Druid 公共配置
|
||||
druid:
|
||||
initial-size: 10
|
||||
min-idle: 10
|
||||
max-active: 50
|
||||
max-wait: 60000
|
||||
time-between-eviction-runs-millis: 60000
|
||||
min-evictable-idle-time-millis: 300000
|
||||
validation-query: SELECT 1
|
||||
test-while-idle: true
|
||||
test-on-borrow: false
|
||||
test-on-return: false
|
||||
pool-prepared-statements: true
|
||||
max-pool-prepared-statement-per-connection-size: 20
|
||||
filters: stat,wall
|
||||
web-stat-filter:
|
||||
enabled: true
|
||||
stat-view-servlet:
|
||||
enabled: false
|
||||
|
||||
# 主库
|
||||
master:
|
||||
url: jdbc:postgresql://10.22.245.138:54321/xianDC?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=Asia/Shanghai
|
||||
username: zaihailian
|
||||
password: XAYJ@gis2603
|
||||
driver-class-name: org.postgresql.Driver
|
||||
|
||||
# 从库
|
||||
slave:
|
||||
url: jdbc:postgresql://10.22.245.138:54321/xianDCAccess?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=Asia/Shanghai
|
||||
username: zaihailian
|
||||
password: XAYJ@gis2603
|
||||
driver-class-name: org.postgresql.Driver
|
||||
@@ -0,0 +1,42 @@
|
||||
# 开发环境配置
|
||||
spring:
|
||||
config:
|
||||
import: classpath:application-database-dev.yml
|
||||
# redis
|
||||
data:
|
||||
redis:
|
||||
host: 47.92.216.173
|
||||
port: 7655
|
||||
password: zhangsan
|
||||
database: 0
|
||||
connect-timeout: 3000ms
|
||||
|
||||
# 日志配置
|
||||
logging:
|
||||
level:
|
||||
root: INFO
|
||||
pattern:
|
||||
console: '%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n'
|
||||
file: '%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n'
|
||||
file:
|
||||
name: ./logs/springboot-app.log
|
||||
logback:
|
||||
rollingpolicy:
|
||||
file-name-pattern: ./logs/springboot-app-%d{yyyy-MM-dd}.%i.log
|
||||
max-file-size: 100MB
|
||||
max-history: 7
|
||||
total-size-cap: 1GB
|
||||
clean-history-on-start: true
|
||||
|
||||
# 安全配置
|
||||
safety:
|
||||
# 加解密过滤路径配置
|
||||
crypto:
|
||||
# 响应无需加密的路径
|
||||
no-encrypt-paths:
|
||||
- /crypto/sm2/public-key
|
||||
- /druid
|
||||
# 请求无需解密的路径
|
||||
no-decrypt-paths:
|
||||
- /crypto/sm2/public-key
|
||||
- /druid
|
||||
@@ -0,0 +1,41 @@
|
||||
# 生产环境配置
|
||||
spring:
|
||||
config:
|
||||
import: classpath:application-database-prod.yml
|
||||
# redis
|
||||
data:
|
||||
redis:
|
||||
host: 10.22.245.246
|
||||
port: 6379
|
||||
password: XAYJ@gis2603
|
||||
database: 0
|
||||
connect-timeout: 3000ms
|
||||
|
||||
# 日志配置
|
||||
logging:
|
||||
level:
|
||||
root: WARN
|
||||
pattern:
|
||||
console: '%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n'
|
||||
file: '%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n'
|
||||
file:
|
||||
name: ./logs/springboot-app.log
|
||||
logback:
|
||||
rollingpolicy:
|
||||
file-name-pattern: ./logs/springboot-app-%d{yyyy-MM-dd}.%i.log
|
||||
max-file-size: 100MB
|
||||
max-history: 7
|
||||
total-size-cap: 1GB
|
||||
clean-history-on-start: true
|
||||
|
||||
|
||||
# 安全配置
|
||||
safety:
|
||||
# 加解密过滤路径配置
|
||||
crypto:
|
||||
# 响应无需加密的路径
|
||||
no-encrypt-paths:
|
||||
- /crypto/sm2/public-key
|
||||
# 请求无需解密的路径
|
||||
no-decrypt-paths:
|
||||
- /crypto/sm2/public-key
|
||||
@@ -0,0 +1,21 @@
|
||||
# 端口
|
||||
server:
|
||||
port: 8080
|
||||
|
||||
# 激活的配置文件(默认dev,打包时会被Maven profile替换)
|
||||
spring:
|
||||
profiles:
|
||||
active: @spring.profiles.active@
|
||||
|
||||
# MyBatis 配置
|
||||
mybatis:
|
||||
mapper-locations: classpath:mapper/*.xml
|
||||
type-aliases-package: com.gis.basic_template_not_login_back.entity
|
||||
configuration:
|
||||
map-underscore-to-camel-case: true
|
||||
|
||||
# 安全配置
|
||||
safety:
|
||||
sm2:
|
||||
# sm2公钥在redis存储名
|
||||
global: 'sm2:keypair:global'
|
||||
Reference in New Issue
Block a user