zhangwl 3 weeks ago
commit
2914373b3a

+ 209 - 0
pom.xml

@@ -0,0 +1,209 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>2.7.18</version> <!-- 或 3.x,但配置略有不同 -->
+        <relativePath/>
+    </parent>
+    <groupId>org.pay</groupId>
+    <artifactId>msNewPayment2.0</artifactId>
+    <version>1.0</version>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <!-- 环境标识 -->
+        <env>dev</env>
+        <commons-lang3.version>3.4</commons-lang3.version>
+        <fastjson.version>2.0.61</fastjson.version>
+        <httpcomponents-httpclient.version>4.5.14</httpcomponents-httpclient.version>
+        <mybatis.version>3.2.0</mybatis.version>
+        <mysql.version>8.0.33</mysql.version>
+    </properties>
+    <dependencies>
+        <!-- Spring Boot Starter -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
+
+
+        <!-- commons -->
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+            <version>${commons-lang3.version}</version>
+        </dependency>
+        <!-- Apache HTTP Client -->
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+            <version>${httpcomponents-httpclient.version}</version>
+        </dependency>
+        <!-- lombok -->
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <!-- json依赖 -->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+            <version>${fastjson.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+
+
+
+        <!--redis-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+            <exclusions>
+                <!-- 过滤lettuce,使用jedis作为redis客户端 -->
+                <exclusion>
+                    <groupId>io.lettuce</groupId>
+                    <artifactId>lettuce-core</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>redis.clients</groupId>
+            <artifactId>jedis</artifactId>
+        </dependency>
+
+        <!-- @RefreshScope 核心支持 -->
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-bootstrap</artifactId>
+        </dependency>
+
+        <!-- spring cloud config -->
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-config</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.mysql</groupId>
+            <artifactId>mysql-connector-j</artifactId>
+            <version>${mysql.version}</version>
+            <scope>runtime</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+            <version>${mybatis.version}</version>
+        </dependency>
+
+
+    </dependencies>
+
+    <!-- BOM 版本管理 -->
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.springframework.cloud</groupId>
+                <artifactId>spring-cloud-dependencies</artifactId>
+                <version>2021.0.9</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <!-- 环境配置 -->
+    <profiles>
+        <!-- 开发环境 -->
+        <!--        <profile>-->
+        <!--            <id>dev</id>-->
+        <!--            <activation>-->
+        <!--                <activeByDefault>true</activeByDefault>-->
+        <!--            </activation>-->
+        <!--            <properties>-->
+        <!--                <env>dev</env>-->
+        <!--                <spring.profiles.active>dev</spring.profiles.active>-->
+        <!--            </properties>-->
+        <!--            <build>-->
+        <!--                <finalName>${project.artifactId}-${project.version}-dev</finalName>-->
+        <!--            </build>-->
+        <!--        </profile>-->
+
+        <!-- 生产环境 -->
+        <profile>
+            <id>pro</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <properties>
+                <env>pro</env>
+                <spring.profiles.active>pro</spring.profiles.active>
+            </properties>
+            <build>
+                <finalName>${project.artifactId}-${project.version}-pro</finalName>
+            </build>
+        </profile>
+    </profiles>
+
+    <build>
+        <!-- 资源过滤配置 -->
+        <resources>
+            <resource>
+                <directory>src/main/resources</directory>
+                <filtering>true</filtering>
+                <includes>
+                    <include>**/*.properties</include>
+                    <include>**/*.yml</include>
+                    <include>**/*.yaml</include>
+                    <include>**/*.xml</include>
+                </includes>
+            </resource>
+            <resource>
+                <directory>src/main/resources</directory>
+                <filtering>false</filtering>
+                <excludes>
+                    <exclude>**/*.properties</exclude>
+                    <exclude>**/*.yml</exclude>
+                    <exclude>**/*.yaml</exclude>
+                    <exclude>**/*.xml</exclude>
+                </excludes>
+            </resource>
+        </resources>
+
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <configuration>
+                    <!-- 激活的Spring Profile -->
+                    <profiles>
+                        <profile>${spring.profiles.active}</profile>
+                    </profiles>
+                </configuration>
+            </plugin>
+
+            <!-- 资源过滤插件 -->
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-resources-plugin</artifactId>
+                <version>3.2.0</version>
+                <configuration>
+                    <encoding>UTF-8</encoding>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>

+ 13 - 0
src/main/java/zs/payment/Starter.java

@@ -0,0 +1,13 @@
+package zs.payment;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.scheduling.annotation.EnableScheduling;
+
+@SpringBootApplication
+@EnableScheduling
+public class Starter {
+    public static void main(String[] args) {
+        SpringApplication.run(Starter.class, args);
+    }
+}

+ 202 - 0
src/main/java/zs/payment/controller/YxSupplyChannelManualController.java

@@ -0,0 +1,202 @@
+package zs.payment.controller;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.alibaba.fastjson.JSONObject;
+import zs.payment.req.OrderCheckReq;
+import zs.payment.req.OrderReq;
+import zs.payment.req.ProductDetailReq;
+import zs.payment.req.ProductPageReq;
+import zs.payment.resp.Result;
+import zs.payment.utils.HttpUtil;
+import zs.payment.utils.RedisUtils;
+
+import javax.validation.Valid;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 芸信 供应链平台
+ */
+
+@RestController
+@RequestMapping("/supply")
+public class YxSupplyChannelManualController {
+
+    @Value("${supply.demain}")
+    private String prefixUrl;
+
+    @Value("${supply.appKey}")
+    private String appKey;
+    @Value("${supply.appSecret}")
+    private String appSecret;
+
+    @Autowired
+    private RedisUtils redisUtils;
+
+    public static final String SUPPLY_TOKEN = "supply:token";
+
+    /**
+     * 获取token
+     */
+    @PostMapping("/getToken")
+    public Result getToken() {
+        return fetchAndCacheToken();
+    }
+
+    /**
+     * 请求上游获取token并缓存到Redis
+     * 成功返回 Result.success(token),失败返回 Result.fail(msg)
+     */
+    private Result fetchAndCacheToken() {
+        Map<String, Object> params = new HashMap<>();
+        params.put("app_key", appKey);
+        params.put("app_secret", appSecret);
+
+        String resp = HttpUtil.restTemplatePost(prefixUrl + "/supplyapi/app/application/getToken", params);
+
+        JSONObject respJson = JSONObject.parseObject(resp);
+        if (respJson.getIntValue("code") != 0) {
+            return Result.fail(respJson.getString("msg"));
+        }
+
+        JSONObject data = respJson.getJSONObject("data");
+        String token = data.getString("token");
+        long expiresAt = data.getLongValue("expiresAt");
+
+        long ttlSeconds = (expiresAt - System.currentTimeMillis()) / 1000;
+        if (ttlSeconds <= 0) ttlSeconds = 60;
+
+        redisUtils.set(SUPPLY_TOKEN, token, (int) ttlSeconds);
+
+        return Result.success(token);
+    }
+
+    /**
+     * 选品列表API
+     */
+    @PostMapping("/cursorList")
+    public Result cursorList(@Valid @RequestBody ProductPageReq req) {
+
+        String token = redisUtils.get(SUPPLY_TOKEN);
+        if (token == null || token.isEmpty()) {
+            Result tokenResult = fetchAndCacheToken();
+            if (!tokenResult.isSuccess()) {
+                return tokenResult;
+            }
+            token = (String) tokenResult.getBody().get("token");
+        }
+
+        Map<String, Object> params = new HashMap<>();
+        params.put("pageSize", req.getPageSize());
+        if (req.getCursor() != null) {
+            params.put("cursor", req.getCursor());
+        }
+
+        Map<String, String> headers = new HashMap<>();
+        headers.put("x-token", token);
+
+        String resp = HttpUtil.restTemplatePost(prefixUrl + "/supplyapi/app/product/storage/cursorList", params, headers);
+
+        JSONObject respJson = JSONObject.parseObject(resp);
+        if (respJson.getIntValue("code") != 0) {
+            return Result.fail(respJson.getString("msg"));
+        }
+
+        return Result.success(respJson.getJSONObject("data"));
+    }
+
+    //支持多个商品详情查看
+    @PostMapping("/detailList")
+    public Result detailList(@Valid @RequestBody ProductDetailReq req) {
+
+        String token = redisUtils.get(SUPPLY_TOKEN);
+        if (token == null || token.isEmpty()) {
+            Result tokenResult = fetchAndCacheToken();
+            if (!tokenResult.isSuccess()) {
+                return tokenResult;
+            }
+            token = (String) tokenResult.getBody().get("token");
+        }
+
+        Map<String, Object> params = new HashMap<>();
+        params.put("ids", req.getIds());
+
+        Map<String, String> headers = new HashMap<>();
+        headers.put("x-token", token);
+
+        String resp = HttpUtil.restTemplatePost(prefixUrl + "/supplyapi/app/product/storage/detailList", params, headers);
+
+        JSONObject respJson = JSONObject.parseObject(resp);
+        if (respJson.getIntValue("code") != 0) {
+            return Result.fail(respJson.getString("msg"));
+        }
+
+        return Result.success(respJson.getJSONObject("data"));
+    }
+
+
+    // 订单验证
+    @PostMapping("/beforeCheck")
+    public Result beforeCheck(@Valid @RequestBody OrderCheckReq req) {
+        String token = redisUtils.get(SUPPLY_TOKEN);
+        if (token == null || token.isEmpty()) {
+            Result tokenResult = fetchAndCacheToken();
+            if (!tokenResult.isSuccess()) {
+                return tokenResult;
+            }
+            token = (String) tokenResult.getBody().get("token");
+        }
+
+        Map<String, Object> params = new HashMap<>();
+        params.put("spu", req.getSpu());
+        params.put("address", req.getAddress());
+
+        Map<String, String> headers = new HashMap<>();
+        headers.put("x-token", token);
+
+        String resp = HttpUtil.restTemplatePost(prefixUrl + "/supplyapi/app/order/beforeCheck", params, headers);
+
+        JSONObject respJson = JSONObject.parseObject(resp);
+        if (respJson.getIntValue("code") != 0) {
+            return Result.fail(respJson.getString("msg"));
+        }
+
+        return Result.success(respJson.getJSONObject("data"));
+    }
+
+    // 下单
+    @PostMapping("/order")
+    public Result order(@Valid @RequestBody OrderReq req) {
+        String token = redisUtils.get(SUPPLY_TOKEN);
+        if (token == null || token.isEmpty()) {
+            Result tokenResult = fetchAndCacheToken();
+            if (!tokenResult.isSuccess()) {
+                return tokenResult;
+            }
+            token = (String) tokenResult.getBody().get("token");
+        }
+
+        Map<String, Object> params = new HashMap<>();
+        params.put("spu", req.getSpu());
+        params.put("address", req.getAddress());
+        params.put("order_sn", req.getOrder_sn());
+
+        Map<String, String> headers = new HashMap<>();
+        headers.put("x-token", token);
+
+        String resp = HttpUtil.restTemplatePost(prefixUrl + "/supplyapi/app/order", params, headers);
+
+        JSONObject respJson = JSONObject.parseObject(resp);
+        if (respJson.getIntValue("code") != 0) {
+            return Result.fail(respJson.getString("msg"));
+        }
+
+        return Result.success(respJson.getJSONObject("data"));
+    }
+
+}

+ 26 - 0
src/main/java/zs/payment/req/OrderCheckReq.java

@@ -0,0 +1,26 @@
+package zs.payment.req;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import zs.payment.req.order.AddressInfo;
+import zs.payment.req.order.SpuItem;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+@Setter
+@Getter
+@NoArgsConstructor
+public class OrderCheckReq {
+
+    @NotEmpty(message = "spu不能为空")
+    @Valid
+    private List<SpuItem> spu;
+
+    @NotNull(message = "address不能为空")
+    @Valid
+    private AddressInfo address;
+}

+ 23 - 0
src/main/java/zs/payment/req/ProductDetailReq.java

@@ -0,0 +1,23 @@
+package zs.payment.req;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 查看商品详情的请求参数
+ */
+@Setter
+@Getter
+@NoArgsConstructor
+public class ProductDetailReq implements Serializable {
+
+    @NotNull(message = "参数不能为空")
+    @Size(min = 1,message = "参数不能为空")
+    private List<Integer> ids;
+}

+ 20 - 0
src/main/java/zs/payment/req/ProductPageReq.java

@@ -0,0 +1,20 @@
+package zs.payment.req;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+
+@Setter
+@Getter
+@NoArgsConstructor
+public class ProductPageReq implements Serializable {
+
+    @NotNull(message = "商品个数不能为空")
+    private Integer pageSize;
+    //商品查询的起始位置,该值为上个接口返回的cursor的值(商品的id)
+    private Integer cursor;
+
+}

+ 32 - 0
src/main/java/zs/payment/req/order/AddressInfo.java

@@ -0,0 +1,32 @@
+package zs.payment.req.order;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import javax.validation.constraints.NotBlank;
+
+@Setter
+@Getter
+@NoArgsConstructor
+public class AddressInfo {
+
+    @NotBlank(message = "收货人不能为空")
+    private String consignee;
+
+    @NotBlank(message = "手机号不能为空")
+    private String phone;
+
+    @NotBlank(message = "省份不能为空")
+    private String province;
+
+    @NotBlank(message = "城市不能为空")
+    private String city;
+
+    @NotBlank(message = "区域不能为空")
+    private String area;
+
+    private String street;
+
+    private String description;
+}

+ 19 - 0
src/main/java/zs/payment/req/order/SpuItem.java

@@ -0,0 +1,19 @@
+package zs.payment.req.order;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import javax.validation.constraints.NotNull;
+
+@Setter
+@Getter
+@NoArgsConstructor
+public class SpuItem {
+
+    @NotNull(message = "sku不能为空")
+    private Integer sku;
+
+    @NotNull(message = "number不能为空")
+    private Integer number;
+}

+ 57 - 0
src/main/java/zs/payment/resp/Result.java

@@ -0,0 +1,57 @@
+package zs.payment.resp;
+
+import com.alibaba.fastjson.annotation.JSONField;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 统一响应封装
+ */
+public class Result {
+
+    @JSONField(name = "body")
+    private Map<String, Object> body;
+
+    private Result(Map<String, Object> body) {
+        this.body = body;
+    }
+
+    public static Result success() {
+        Map<String, Object> body = new HashMap<>();
+        body.put("CODE", 200);
+        body.put("MSG", "SUCCESS");
+        return new Result(body);
+    }
+
+    public static Result success(String token) {
+        Map<String, Object> body = new HashMap<>();
+        body.put("CODE", 200);
+        body.put("MSG", "SUCCESS");
+        body.put("token", token);
+        return new Result(body);
+    }
+
+    public static Result success(Object data) {
+        Map<String, Object> body = new HashMap<>();
+        body.put("CODE", 200);
+        body.put("MSG", "SUCCESS");
+        body.put("data", data);
+        return new Result(body);
+    }
+
+    public static Result fail(String msg) {
+        Map<String, Object> body = new HashMap<>();
+        body.put("CODE", 500);
+        body.put("MSG", msg);
+        return new Result(body);
+    }
+
+    public Map<String, Object> getBody() {
+        return body;
+    }
+
+    public boolean isSuccess() {
+        return Integer.valueOf(200).equals(body.get("CODE"));
+    }
+}

+ 157 - 0
src/main/java/zs/payment/utils/HttpUtil.java

@@ -0,0 +1,157 @@
+package zs.payment.utils;
+
+import com.alibaba.fastjson.JSONObject;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.http.client.HttpClient;
+import org.apache.http.conn.ssl.NoopHostnameVerifier;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.ssl.SSLContexts;
+import org.apache.http.ssl.TrustStrategy;
+import org.springframework.http.*;
+import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
+import org.springframework.http.converter.StringHttpMessageConverter;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import javax.net.ssl.SSLContext;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.Map;
+
+@Slf4j
+public class HttpUtil {
+
+    public static String restTemplateGet(String url, Map<String, Object> params) {
+        try {
+            // 创建忽略SSL验证的RestTemplate
+            TrustStrategy acceptingTrustStrategy = new TrustStrategy() {
+                @Override
+                public boolean isTrusted(java.security.cert.X509Certificate[] chain, String authType) {
+                    return true;
+                }
+            };
+            SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build();
+            SSLConnectionSocketFactory connectionSocketFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
+            HttpClient httpClient = HttpClients.custom().setSSLSocketFactory(connectionSocketFactory).build();
+            HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
+            RestTemplate restTemplate = new RestTemplate(factory);
+
+            // 构建URL
+            UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromHttpUrl(url);
+            if (params != null) {
+                for (Map.Entry<String, Object> entry : params.entrySet()) {
+                    String key = entry.getKey();
+                    Object value = entry.getValue();
+                    if (key != null && value != null) {
+                        uriBuilder.queryParam(key, value.toString());
+                    }
+                }
+            }
+
+            String finalUrl = uriBuilder.toUriString();
+            log.info("GET_URL====>{}", finalUrl);
+
+            HttpHeaders headers = new HttpHeaders();
+            HttpEntity<String> entity = new HttpEntity<>(headers);
+
+            ResponseEntity<String> rsp = restTemplate.exchange(
+                finalUrl,
+                HttpMethod.GET,
+                entity,
+                String.class
+            );
+            return rsp.getBody();
+
+        } catch (Exception e) {
+            log.error("HTTP请求失败: {}", e.getMessage());
+            throw new RuntimeException("网络请求异常", e);
+        }
+    }
+
+
+    /**
+     * post 请求,请求头=application/json
+     * @param url
+     * @param params
+     * @return
+     */
+    public static String restTemplatePost(String url, Map<String, Object> params) {
+        try {
+            // 创建忽略SSL验证的RestTemplate
+            TrustStrategy acceptingTrustStrategy = (chain, authType) -> true;
+            SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build();
+            SSLConnectionSocketFactory connectionSocketFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
+            HttpClient httpClient = HttpClients.custom().setSSLSocketFactory(connectionSocketFactory).build();
+            HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
+            RestTemplate restTemplate = new RestTemplate(factory);
+
+            // 配置UTF-8编码的消息转换器
+            restTemplate.getMessageConverters().removeIf(converter -> converter instanceof StringHttpMessageConverter);
+            restTemplate.getMessageConverters().add(0, new StringHttpMessageConverter(StandardCharsets.UTF_8));
+
+            // 设置请求头
+            HttpHeaders headers = new HttpHeaders();
+            headers.setContentType(MediaType.APPLICATION_JSON);
+            headers.setAcceptCharset(Collections.singletonList(StandardCharsets.UTF_8)); // 添加Accept-Charset,解决中文乱码问题
+
+            // 将参数转换为JSON字符串
+            String jsonBody = new JSONObject(params).toJSONString();
+            log.info("POST_URL====>{}, body====>{}", url, jsonBody);
+
+            HttpEntity<String> request = new HttpEntity<>(jsonBody, headers);
+
+            ResponseEntity<String> rsp = restTemplate.exchange(
+                url,
+                HttpMethod.POST,
+                request,
+                String.class
+            );
+            return rsp.getBody();
+
+        } catch (Exception e) {
+            log.error("HTTP POST请求失败: {}", e.getMessage());
+            throw new RuntimeException("网络请求异常", e);
+        }
+    }
+
+    /**
+     * post 请求,支持自定义额外请求头
+     * @param url
+     * @param params
+     * @param extraHeaders 额外请求头(如 x-token)
+     * @return
+     */
+    public static String restTemplatePost(String url, Map<String, Object> params, Map<String, String> extraHeaders) {
+        try {
+            TrustStrategy acceptingTrustStrategy = (chain, authType) -> true;
+            SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build();
+            SSLConnectionSocketFactory connectionSocketFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
+            HttpClient httpClient = HttpClients.custom().setSSLSocketFactory(connectionSocketFactory).build();
+            HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
+            RestTemplate restTemplate = new RestTemplate(factory);
+
+            restTemplate.getMessageConverters().removeIf(converter -> converter instanceof StringHttpMessageConverter);
+            restTemplate.getMessageConverters().add(0, new StringHttpMessageConverter(StandardCharsets.UTF_8));
+
+            HttpHeaders headers = new HttpHeaders();
+            headers.setContentType(MediaType.APPLICATION_JSON);
+            headers.setAcceptCharset(Collections.singletonList(StandardCharsets.UTF_8));
+            if (extraHeaders != null) {
+                extraHeaders.forEach(headers::set);
+            }
+
+            String jsonBody = new JSONObject(params).toJSONString();
+            log.info("POST_URL====>{}, body====>{}", url, jsonBody);
+
+            HttpEntity<String> request = new HttpEntity<>(jsonBody, headers);
+
+            ResponseEntity<String> rsp = restTemplate.exchange(url, HttpMethod.POST, request, String.class);
+            return rsp.getBody();
+
+        } catch (Exception e) {
+            log.error("HTTP POST请求失败: {}", e.getMessage());
+            throw new RuntimeException("网络请求异常", e);
+        }
+    }
+}

+ 169 - 0
src/main/java/zs/payment/utils/Redis.java

@@ -0,0 +1,169 @@
+package zs.payment.utils;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import redis.clients.jedis.Jedis;
+import redis.clients.jedis.JedisPool;
+import redis.clients.jedis.JedisPoolConfig;
+
+import java.util.Random;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.regex.Pattern;
+
+public class Redis {
+    private static final Logger log = LoggerFactory.getLogger(Redis.class);
+
+    @FunctionalInterface
+    public interface RedisCallback<V> {
+        V execute(Jedis jedis) throws Exception;
+    }
+    /**
+     * 主从模式
+     * @param ipPort Redis server address
+     * @param callback Redis callback
+     * @return callback result
+     */
+    public static <V> V with(String ipPort, RedisCallback<V> callback) {
+        JedisPool pool = pool(ipPort);
+        Jedis jedis = null;
+        V val = null;
+
+        try {
+            for (int i = 1; i <= 2; i++) {
+                if (jedis == null) {
+                    if (i > 1) {
+                        Thread.sleep(200);
+                    }
+                    jedis = pool.getResource();
+                }
+            }
+            if (callback != null) {
+                val = callback.execute(jedis);
+            }
+        } catch (Exception e) {
+            log.error("redis " + ipPort, e);
+            returnResource(pool, jedis);
+        } finally {
+            returnResource(pool, jedis);
+        }
+        return val;
+    }
+    /**
+     * 集群模式
+     * @param ipPorts Redis cluster addresses
+     * @param callback Redis callback
+     * @return callback result
+     */
+    public static <V> V withCluster(String ipPorts, RedisCallback<V> callback) {
+        String[] ipPortArray = ipPorts.split(",");
+        String ipPort = "";
+        if (ipPortArray.length > 0) {
+            Random random = new Random();
+            int index = random.nextInt(ipPortArray.length);
+            ipPort = ipPortArray[index];
+        }
+        JedisPool pool = poolCluster(ipPort);
+        Jedis jedis = null;
+        V val = null;
+
+        try {
+            for (int i = 1; i <= 2; i++) {
+                if (jedis == null) {
+                    if (i > 1) {
+                        Thread.sleep(200);
+                    }
+                    jedis = pool.getResource();
+                }
+            }
+            if (callback != null) {
+                val = callback.execute(jedis);
+            }
+        } catch (Exception e) {
+            log.error("redis " + ipPort, e);
+            returnResource(pool, jedis);
+        } finally {
+            returnResource(pool, jedis);
+        }
+        return val;
+    }
+
+    private static ConcurrentHashMap<String, JedisPool> pools = new ConcurrentHashMap<String, JedisPool>();
+    private static JedisPoolConfig poolConfig = config();
+    private static JedisPoolConfig poolConfigCluster = configCluster();
+
+    private static JedisPoolConfig config() {
+        JedisPoolConfig cfg = new JedisPoolConfig();
+        cfg.setMaxTotal(500);
+        cfg.setMinIdle(50);
+        cfg.setMaxIdle(100);
+        cfg.setMaxWaitMillis(5000);
+        cfg.setTestOnBorrow(true);
+        cfg.setTestOnReturn(true);
+        cfg.setTestWhileIdle(true);
+        cfg.setMinEvictableIdleTimeMillis(300000);
+        cfg.setTimeBetweenEvictionRunsMillis(60000);
+        cfg.setNumTestsPerEvictionRun(10);
+        return cfg;
+    }
+
+    private static JedisPool pool(String ipPort) {
+        JedisPool pool = pools.get(ipPort);
+        if (pool == null) {
+            String host = null;
+            int port = 6379;
+            String[] hp = ipPort.split(":");
+            if (hp.length > 0) {
+                host = hp[0];
+                if (hp.length > 1 && Pattern.matches("[0-9]+", hp[1])) {
+                    port = Integer.parseInt(hp[1]);
+                }
+            }
+            pool = new JedisPool(poolConfig, host, port);
+            pools.put(ipPort, pool);
+        }
+        return pool;
+    }
+    /**
+     * 集群配置
+     * @return
+     */
+
+    private static JedisPoolConfig configCluster() {
+        JedisPoolConfig cfg = new JedisPoolConfig();
+        cfg.setMaxTotal(500);
+        cfg.setMinIdle(50);
+        cfg.setMaxIdle(100);
+        cfg.setMaxWaitMillis(5000);
+        cfg.setTestOnBorrow(true);
+        cfg.setTestOnReturn(true);
+        cfg.setTestWhileIdle(true);
+        cfg.setMinEvictableIdleTimeMillis(300000);
+        cfg.setTimeBetweenEvictionRunsMillis(60000);
+        cfg.setNumTestsPerEvictionRun(10);
+        return cfg;
+    }
+
+    private static JedisPool poolCluster(String ipPort) {
+        JedisPool pool = pools.get(ipPort);
+        if (pool == null) {
+            String host = null;
+            int port = 6379;
+            String[] hp = ipPort.split(":");
+            if (hp.length > 0) {
+                host = hp[0];
+                if (hp.length > 1 && Pattern.matches("[0-9]+", hp[1])) {
+                    port = Integer.parseInt(hp[1]);
+                }
+            }
+            pool = new JedisPool(poolConfigCluster, host, port);
+            pools.put(ipPort, pool);
+        }
+        return pool;
+    }
+
+    public static void returnResource(JedisPool jedisPool, Jedis jedis) {
+        if (jedisPool != null && jedis != null) {
+            jedisPool.returnResource(jedis);
+        }
+    }
+}

+ 501 - 0
src/main/java/zs/payment/utils/RedisUtils.java

@@ -0,0 +1,501 @@
+package zs.payment.utils;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.data.redis.core.ZSetOperations;
+import org.springframework.stereotype.Repository;
+import redis.clients.jedis.GeoCoordinate;
+import redis.clients.jedis.GeoUnit;
+import redis.clients.jedis.Pipeline;
+import redis.clients.jedis.Tuple;
+import redis.clients.jedis.params.SetParams;
+
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+/**
+ * 该redis主要包含支付、订单等数据相关缓存(与资讯池共用)
+ */
+@Repository
+@Slf4j
+public class RedisUtils {
+    @Value("${pay.redis.master}")
+    private String ecoCluster;
+
+    public void set(String key, String cont) {
+        set(key, cont, 0);
+    }
+
+    public void set(String key, String cont, int seconds) {
+        Redis.withCluster(ecoCluster, jedis -> {
+            jedis.set(key, cont);
+            if (seconds > 0) {
+                jedis.expire(key, seconds);
+            }
+            return null;
+        });
+    }
+
+    public String get(String key) {
+        return Redis.withCluster(ecoCluster, jedis -> jedis.get(key));
+    }
+
+    public String getByKey(String key) {
+        return Redis.withCluster(ecoCluster, jedis -> jedis.get(key));
+    }
+
+    public List<String> lRange(String key, long startIndex, long endIndex) {
+        List<String> back = new ArrayList<>();
+        if (key == null || key.isEmpty()) {
+            return back;
+        }
+
+        Redis.withCluster(ecoCluster, jedis ->
+             back.addAll(jedis.lrange(key, startIndex, endIndex))
+        );
+        return back;
+    }
+
+    public List<String> mget(List<String> keys) {
+        List<String> back = new ArrayList<>();
+        if (keys == null || keys.isEmpty()) {
+            return back;
+        }
+
+        Redis.withCluster(ecoCluster, jedis ->
+            back.addAll(jedis.mget(keys.toArray(new String[0])))
+        );
+        return back;
+    }
+
+    public Long del(String key) {
+        return Redis.withCluster(ecoCluster, jedis -> jedis.del(key));
+    }
+
+    public void hset(String key, String field, String cont) {
+        hset(key, field, cont, 0);
+    }
+
+    public void hset(String key, String field, String cont, int seconds) {
+        Redis.withCluster(ecoCluster, jedis -> {
+            jedis.hset(key, field, cont);
+            if (seconds > 0) {
+                jedis.expire(key, seconds);
+            }
+            return  null;
+        });
+    }
+
+    public void hmset(String key, Map<String, String> hash) {
+        hmset(key, hash, 0);
+    }
+
+    public void hmset(String key, Map<String, String> hash, int seconds) {
+        Redis.withCluster(ecoCluster, jedis -> {
+            jedis.hmset(key, hash);
+            if (seconds > 0) {
+                jedis.expire(key, seconds);
+            }
+            return null;
+        });
+    }
+
+    public Long hincrBy(String key, String field, long value) {
+        return Redis.withCluster(ecoCluster, jedis -> jedis.hincrBy(key, field, value));
+    }
+
+    public String hget(String key, String field) {
+        return Redis.withCluster(ecoCluster, jedis -> jedis.hget(key, field));
+    }
+
+    public Map<String, String> hgetAll(String key) {
+        return Redis.withCluster(ecoCluster, jedis -> jedis.hgetAll(key));
+    }
+
+    public void hdel(String key, String field) {
+        Redis.withCluster(ecoCluster, jedis -> {
+            jedis.hdel(key, field);
+            return null;
+        });
+    }
+
+    public long sadd(String key, String... members) {
+        return Redis.withCluster(ecoCluster, jedis -> jedis.sadd(key, members));
+    }
+
+    public Set<String> smembers(String key) {
+        return  Redis.withCluster(ecoCluster, jedis -> jedis.smembers(key));
+    }
+
+    public long zadd(String key, double score, String member) {
+        return Redis.withCluster(ecoCluster, jedis -> jedis.zadd(key, score, member));
+    }
+
+    /**
+     * 向有序集合批量添加元素(使用 Tuple 集合)
+     * @param key 键
+     * @param tuples 元组集合(包含分数和成员)
+     * @return 成功添加的数量
+     */
+    public Long zadd(String key, Set<ZSetOperations.TypedTuple<String>> tuples) {
+        if (tuples == null || tuples.isEmpty()) {
+            return 0L;
+        }
+
+        long result = 0L;
+
+        for (ZSetOperations.TypedTuple<String> tuple : tuples) {
+            result=result+  Redis.withCluster(ecoCluster, jedis -> jedis.zadd(key, tuple.getScore(),tuple.getValue()));
+
+        }
+
+        return result;
+    }
+
+//    /**
+//     * 通过管道模式一次性添加多个值
+//     *
+//     * @param key
+//     * @param list
+//     */
+//    public void zaddWithPipeline(String key, List<?> list) {
+//        Redis.withCluster(ecoCluster, jedis -> {
+//            Pipeline pipeline = jedis.pipelined();
+//            for (Object item : list) {
+//                double score = Double.parseDouble(((Map<?, ?>) item).get("userId").toString());
+//                jedis.zadd(key, score, Strings.toJson(item));
+//            }
+//            pipeline.syncAndReturnAll();
+//        });
+//    }
+
+    public long llen(String key) {
+        return Redis.withCluster(ecoCluster, jedis -> jedis.llen(key));
+    }
+
+    public List<String> lrangeList(String key, int start, int end) {
+        List<String> result = Redis.withCluster(ecoCluster, jedis -> jedis.lrange(key, start, end));
+        return result != null ? result : new ArrayList<>();
+    }
+
+    public void lpush(String key, String... strings) {
+        Redis.withCluster(ecoCluster, jedis -> {
+            jedis.lpush(key, strings);
+            return null;
+        });
+    }
+
+    public long rpush(String key, String value) {
+        return Redis.withCluster(ecoCluster, jedis -> jedis.rpush(key, value));
+    }
+
+    public String rpop(String key) {
+        return Redis.withCluster(ecoCluster, jedis -> jedis.rpop(key));
+    }
+
+    public String lpop(String key) {
+        return Redis.withCluster(ecoCluster, jedis -> jedis.lpop(key));
+    }
+
+    public long zrem(String key, String member) {
+        return Redis.withCluster(ecoCluster, jedis -> jedis.zrem(key, member));
+    }
+
+    /**
+     *
+     * 返回有序集的元素个数(对应 redisTemplate.opsForZSet().size())
+     * @param key
+     * @return
+     */
+    public long zcard(String key) {
+        return Redis.withCluster(ecoCluster, jedis -> jedis.zcard(key));
+    }
+
+    public Double zscore(String key, String member) {
+        return Redis.withCluster(ecoCluster, jedis -> jedis.zscore(key, member));
+    }
+
+    /**
+     * 倒序(从大到小)获取指定区间内的数据
+     *
+     * @param key
+     * @param start
+     * @param end
+     * @return
+     */
+    public Set<String> zrevrange(String key, long start, long end) {
+        return Redis.withCluster(ecoCluster, jedis -> jedis.zrevrange(key, start, end));
+    }
+
+    /**
+     * 正序(从小到大)获取指定区间内的数据
+     *
+     * @param key
+     * @param start
+     * @param end
+     * @return
+     */
+    public Set<String> zrange(String key, long start, long end) {
+        return Redis.withCluster(ecoCluster, jedis -> jedis.zrange(key, start, end));
+    }
+
+    /**
+     * 根据score值,倒序(从大到小)获取指定区间内的数据
+     *
+     * @param key
+     * @param score
+     * @param psize
+     * @return
+     */
+    public Set<String> zrevrangeByScore(String key, String score, Integer psize) {
+        return  Redis.withCluster(ecoCluster, jedis -> jedis.zrevrangeByScore(key, "(" + score, "-inf", 0, psize));
+    }
+
+    /**
+     * 获取score值介于score1和score2之间的数据
+     *
+     * @param key
+     * @param score1
+     * @param score2
+     * @return
+     */
+    public Set<String> zrangeByScore(String key, String score1, String score2) {
+        return  Redis.withCluster(ecoCluster, jedis -> jedis.zrangeByScore(key, score1, score2));
+    }
+
+    /**
+     * 设置自增长
+     *
+     * @param key
+     * @return
+     */
+    public synchronized long setIncyBy(String key) {
+        return setIncyBy(key, 1L, 0);
+    }
+
+    /**
+     * 设置自增长
+     *
+     * @param key
+     * @param num
+     * @return
+     */
+    public synchronized long setIncyBy(String key, long num) {
+        return setIncyBy(key, num, 0);
+    }
+
+    /**
+     * 设置自增长
+     *
+     * @param key
+     * @param num
+     * @param seconds
+     * @return
+     */
+    public synchronized long setIncyBy(String key, long num, int seconds) {
+        return Redis.withCluster(ecoCluster, jedis -> {
+            long result = jedis.incrBy(key, num);
+            if (seconds > 0) {
+                jedis.expire(key, seconds);
+            }
+            return result;
+        });
+    }
+
+    public long setIncy(String key) {
+        return Redis.withCluster(ecoCluster, jedis -> jedis.incr(key));
+    }
+
+    public long setDecy(String key) {
+        return Redis.withCluster(ecoCluster, jedis -> jedis.decr(key));
+    }
+
+    /**
+     * 按分数从小到大删除指定区域的数据 (对应redisTemplate.opsForZSet().removeRange(key,begin,end)))
+     *
+     * @param key
+     * @param start
+     * @param end
+     * @return
+     */
+    public long zremrangeByRank(String key, long start, long end) {
+        return Redis.withCluster(ecoCluster, jedis -> jedis.zremrangeByRank(key, start, end));
+    }
+
+    /**
+     * setnx实现简单的分布式锁
+     *
+     * @param key
+     * @param value
+     * @return
+     */
+    public long setnx(String key, String value) {
+        return setnx(key, value, 0);
+    }
+
+    /**
+     * setnx实现简单的分布式锁
+     *
+     * @param key
+     * @param value
+     * @param seconds
+     * @return
+     */
+    public long setnx(String key, String value, int seconds) {
+        return Redis.withCluster(ecoCluster, jedis -> {
+            long result = jedis.setnx(key, value);
+            if (result == 1 && seconds > 0) {
+                jedis.expire(key, seconds);
+            }
+            return result;
+        });
+    }
+
+    /**
+     * SET NX EX 原子命令:仅当Key不存在时设置值,并设置过期时间
+     * 用于分布式锁的获取
+     *
+     * @param key      锁的键
+     * @param value    锁的值(通常是实例ID或服务ID)
+     * @param seconds  过期时间(秒)
+     * @return true 表示成功获得锁,false 表示锁已被占用
+     */
+    public boolean setIfAbsent(String key, String value, int seconds) {
+        String result = Redis.withCluster(ecoCluster, jedis -> {
+            // 使用 SET key value NX EX seconds 原子命令
+            // NX: Only set the key if it does not already exist
+            // EX: Set the specified expire time, in seconds
+            SetParams params = new SetParams().nx().ex(seconds);
+            return jedis.set(key, value, params);
+        });
+        // 返回 "OK" 表示成功,null 表示键已存在
+        return "OK".equals(result);
+    }
+
+    /**
+     * 根据前缀获取所有的key
+     * 例如:pro_*
+     */
+    public Set<String> getListKey(String prefix) {
+        return Redis.withCluster(ecoCluster, jedis -> jedis.keys(prefix.concat("*")));
+    }
+
+    public long geoAdd(String key, double lng, double lat, String member) {
+        return Redis.withCluster(ecoCluster, jedis -> jedis.geoadd(key, lng, lat, member));
+    }
+
+    public double geoDist(String key, String member1, String member2) {
+        Double result = Redis.withCluster(ecoCluster, jedis -> jedis.geodist(key, member1, member2, GeoUnit.KM));
+        return result != null ? result : 0.0;
+    }
+
+    public List<Map<String, Object>> geoPos(String key, String... members) {
+        List<GeoCoordinate> coordinates = Redis.withCluster(ecoCluster, jedis -> jedis.geopos(key, members));
+
+        List<Map<String, Object>> coordinateList = new ArrayList<>();
+        if (coordinates != null && !coordinates.isEmpty()) {
+            for (GeoCoordinate coord : coordinates) {
+                Map<String, Object> location = new HashMap<>();
+                location.put("lng", coord.getLongitude());
+                location.put("lat", coord.getLatitude());
+                coordinateList.add(location);
+            }
+        }
+        return coordinateList;
+    }
+
+
+    /**
+     * 设置key的过期时间(对应 redisTemplate.expire())
+     * @param key 键
+     * @param timeout 过期时间
+     * @param unit 时间单位
+     * @return 是否设置成功
+     */
+    public Boolean expire(String key, long timeout, TimeUnit unit) {
+        return Redis.withCluster(ecoCluster, jedis -> {
+            // 转换为秒
+            long seconds = unit.toSeconds(timeout);
+            if (seconds > Integer.MAX_VALUE) {
+                log.warn("过期时间超过Integer最大值,将使用最大值: {}", Integer.MAX_VALUE);
+                seconds = Integer.MAX_VALUE;
+            }
+            jedis.expire(key, (int) seconds);
+            return true;
+        });
+    }
+
+    public void set(String key, String cont, int timeout, TimeUnit unit) {
+        Redis.withCluster(ecoCluster, jedis -> {
+            // 转换为秒
+            long seconds = unit.toSeconds(timeout);
+            if (seconds > Integer.MAX_VALUE) {
+                seconds = Integer.MAX_VALUE;
+            }
+
+            jedis.set(key, cont);
+            if (seconds > 0) {
+                jedis.expire(key, seconds);
+            }
+            return null;
+        });
+    }
+
+    /**
+     * 获取有序集合中指定排名范围内的成员及其分数(按分数从高到低排序)
+     * 对应 redisTemplate.opsForZSet().reverseRangeWithScores()
+     *
+     * @param key 键
+     * @param start 起始索引(从0开始,包含)
+     * @param end 结束索引(包含,-1表示最后一个)
+     * @return 包含成员和分数的元组集合
+     */
+    public Set<ZSetOperations.TypedTuple<String>> zReverseRangeWithScores(String key, long start, long end) {
+        Set<Tuple> tuples = Redis.withCluster(ecoCluster, jedis ->
+            jedis.zrevrangeWithScores(key, start, end));
+        return tuples.stream()
+            .map(tuple -> ZSetOperations.TypedTuple.of(
+                tuple.getElement(),  // 获取元素
+                tuple.getScore()      // 获取分数
+            ))
+            .collect(Collectors.toSet());
+    }
+
+
+
+    /**
+     * 使用 Pipeline 批量设置字符串键值对(高性能,适合大量数据)
+     * @param map 包含多个键值对的Map
+     */
+    public void multiSetWithPipeline(Map<String, String> map,int timeout, TimeUnit unit) {
+        if (map == null || map.isEmpty()) {
+            return;
+        }
+
+        Redis.withCluster(ecoCluster, jedis -> {
+            // 创建管道
+            Pipeline pipeline = jedis.pipelined();
+
+            // 将所有set命令添加到管道
+            for (Map.Entry<String, String> entry : map.entrySet()) {
+                pipeline.set(entry.getKey(), entry.getValue());
+                pipeline.expire(entry.getKey(), unit.toSeconds(timeout));
+            }
+
+            // 批量执行
+            pipeline.sync();
+            return null;
+        });
+    }
+
+    public void hmset(String key, Map<String, String> hash, int timeout, TimeUnit unit) {
+        Redis.withCluster(ecoCluster, jedis -> {
+            jedis.hmset(key, hash);
+            if (timeout > 0) {
+                jedis.expire(key, unit.toSeconds(timeout));
+            }
+            return null;
+        });
+    }
+}

+ 56 - 0
src/main/resources/application-pro.yml

@@ -0,0 +1,56 @@
+spring:
+    kafka:
+        # ============== 生产者配置 ==============
+        producer:
+            bootstrap-servers: 172.17.240.140:9092,172.17.240.141:9092  # Kafka 3.1.2 KRaft地址
+            key-serializer: org.apache.kafka.common.serialization.StringSerializer
+            value-serializer: org.apache.kafka.common.serialization.StringSerializer
+            # 可选:提高可靠性
+            acks: all
+            retries: 3
+
+        # ============== 消费者配置 ==============
+        #172.17.240.132:9092,172.17.240.140:9092,172.17.240.141:9092
+        consumer:
+            bootstrap-servers: 172.17.240.140:9092,172.17.240.141:9092  # 同上
+            key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
+            value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
+            #            group-id: store-group  # 消费者组,同一组内负载均衡
+            auto-offset-reset: earliest  # 无偏移量时从最早开始
+            enable-auto-commit: true  # 自动提交偏移量
+
+        # ============== 监听器容器配置 ==============
+        listener:
+            # 当使用批量消费时改为 BATCH
+            type: single
+
+    data:
+        mongodb:
+            uri: mongodb://moblie_cloud:123456@dds-2ze39a292a3addd41.mongodb.rds.aliyuncs.com:3717,dds-2ze39a292a3addd42.mongodb.rds.aliyuncs.com:3717,dds-2ze39a292a3addd43.mongodb.rds.aliyuncs.com:3717/moblie_cloud?replicaSet=mgset-32910145&minPoolSize=2&maxPoolSize=10
+
+    datasource:
+        driver-class-name: com.mysql.cj.jdbc.Driver #数据源配置
+        url: jdbc:mysql://rm-2zelpi3n5442058bq.mysql.rds.aliyuncs.com:3306/moblie_cloud_online?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&failOverReadOnly=false
+        username: mob_on
+        password: Mo11@BL7i!@
+        hikari:
+            maximum-pool-size: 20      # 适合中等并发量的应用,既保证了响应速度,又不会过度占用数据库资源
+            minimum-idle: 10            # 通常设置为maximum-pool-size的一半
+            connection-timeout: 30000   # 30秒等待时间足够
+            idle-timeout: 600000        # 10分钟空闲可接受
+            max-lifetime: 1800000       # 30分钟防止连接泄露
+
+
+kafka:
+    topic:
+        name: batch-store
+
+pay:
+    redis:
+        master: r-2zeitjlg0gdypzb4v6.redis.rds.aliyuncs.com:6379
+
+
+supply:
+    appKey: application519
+    appSecret: 80694edd4a7da03f3c35473f27603967
+    demain: https://yx.gz.cn

+ 15 - 0
src/main/resources/application.yml

@@ -0,0 +1,15 @@
+
+# 应用基础配置
+server:
+    port: 18816 #msPayment
+    servlet:
+        context-path: /pay2
+
+# Spring Profile 配置
+spring:
+    profiles:
+        active: @spring.profiles.active@
+
+
+
+

+ 104 - 0
src/main/resources/logback-spring.xml

@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+    <!-- 日志文件输出目录 -->
+    <!-- 支持系统属性和环境变量覆盖:LOG_FILE_PATH 和 LOG_FILE_NAME -->
+    <property name="LOG_FILE_PATH" value="${LOG_FILE_PATH:-./logs}"/>
+    <property name="LOG_FILE_NAME" value="${LOG_FILE_NAME:-msPayment2.0}"/>
+
+    <!-- Spring Boot 默认配置 -->
+    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
+
+    <!-- 控制台输出 -->
+    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+    </appender>
+
+    <!-- 按日期滚动的文件输出 -->
+    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <!-- 当前日志文件 -->
+        <file>${LOG_FILE_PATH}/${LOG_FILE_NAME}.log</file>
+
+        <!-- 滚动策略 -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+            <!-- 日志文件名模式:按日期格式(yyyy-MM-dd)生成 -->
+            <fileNamePattern>${LOG_FILE_PATH}/${LOG_FILE_NAME}-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
+
+            <!-- 日志文件保留时间:30天 -->
+            <maxHistory>30</maxHistory>
+
+            <!-- 单个日志文件最大大小 -->
+            <maxFileSize>100MB</maxFileSize>
+
+            <!-- 所有日志文件的总大小限制 -->
+            <totalSizeCap>10GB</totalSizeCap>
+        </rollingPolicy>
+
+        <encoder>
+            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+    </appender>
+
+    <!-- 错误日志文件 -->
+    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${LOG_FILE_PATH}/${LOG_FILE_NAME}-error.log</file>
+
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <level>ERROR</level>
+            <onMatch>ACCEPT</onMatch>
+            <onMismatch>DENY</onMismatch>
+        </filter>
+
+        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+            <fileNamePattern>${LOG_FILE_PATH}/${LOG_FILE_NAME}-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
+            <maxHistory>30</maxHistory>
+            <maxFileSize>100MB</maxFileSize>
+            <totalSizeCap>10GB</totalSizeCap>
+        </rollingPolicy>
+
+        <encoder>
+            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+    </appender>
+
+    <!-- Spring 框架日志级别 -->
+    <logger name="org.springframework" level="INFO"/>
+    <logger name="org.springframework.boot" level="INFO"/>
+    <logger name="org.springframework.data" level="INFO"/>
+
+    <!-- Kafka 日志级别 -->
+    <logger name="org.apache.kafka" level="WARN"/>
+
+    <!-- MongoDB 日志级别 -->
+    <logger name="org.springframework.data.mongodb" level="DEBUG"/>
+
+
+
+    <!-- 开发环境配置 -->
+    <springProfile name="dev">
+        <root level="DEBUG">
+            <appender-ref ref="CONSOLE"/>
+            <appender-ref ref="FILE"/>
+            <appender-ref ref="ERROR_FILE"/>
+        </root>
+    </springProfile>
+
+    <!-- 生产环境配置 -->
+    <springProfile name="pro">
+        <root level="INFO">
+            <appender-ref ref="FILE"/>
+            <appender-ref ref="ERROR_FILE"/>
+        </root>
+    </springProfile>
+
+    <!-- 默认配置(如果没有激活 profile) -->
+    <root level="INFO">
+        <appender-ref ref="CONSOLE"/>
+        <appender-ref ref="FILE"/>
+        <appender-ref ref="ERROR_FILE"/>
+    </root>
+</configuration>