{"id":64307,"date":"2026-01-23T10:24:26","date_gmt":"2026-01-23T02:24:26","guid":{"rendered":"https:\/\/www.wsisp.com\/helps\/64307.html"},"modified":"2026-01-23T10:24:26","modified_gmt":"2026-01-23T02:24:26","slug":"spring-cloud%e5%ae%9e%e6%88%98%ef%bc%9a%e7%94%b5%e5%95%86%e5%be%ae%e6%9c%8d%e5%8a%a1%e7%b3%bb%e7%bb%9f%e4%bb%8e0%e5%88%b01%ef%bc%8825000%e5%ad%97%e7%bb%88%e6%9e%81%e5%ae%9e%e6%88%98%e6%8c%87%e5%8d%97","status":"publish","type":"post","link":"https:\/\/www.wsisp.com\/helps\/64307.html","title":{"rendered":"Spring Cloud\u5b9e\u6218\uff1a\u7535\u5546\u5fae\u670d\u52a1\u7cfb\u7edf\u4ece0\u52301\uff0825000\u5b57\u7ec8\u6781\u5b9e\u6218\u6307\u5357\uff09"},"content":{"rendered":"<h3>\u7b2c\u4e00\u7ae0&#xff1a;\u9879\u76ee\u67b6\u6784\u8bbe\u8ba1\u4e0e\u6280\u672f\u9009\u578b<\/h3>\n<p>1.1 \u7535\u5546\u7cfb\u7edf\u6838\u5fc3\u4e1a\u52a1\u5206\u6790 \u4f20\u7edf\u5355\u4f53\u7535\u5546\u7cfb\u7edf\u7684\u75db\u70b9&#xff1a;<\/p>\n<p>java \/\/ \u4f20\u7edf\u5355\u4f53\u7535\u5546\u67b6\u6784\u7684\u95ee\u9898 \u5355\u70b9\u6545\u969c&#xff1a;\u5546\u54c1\u670d\u52a1\u5d29\u6e83 \u2192 \u6574\u4e2a\u7f51\u7ad9\u4e0d\u53ef\u7528 \u6280\u672f\u6808\u56fa\u5316&#xff1a;\u6240\u6709\u6a21\u5757\u5fc5\u987b\u4f7f\u7528\u76f8\u540c\u6280\u672f \u6269\u5c55\u56f0\u96be&#xff1a;\u4fc3\u9500\u65f6\u53ea\u80fd\u6574\u4f53\u6269\u5bb9 \u53d1\u5e03\u98ce\u9669&#xff1a;\u5c0f\u6539\u52a8\u9700\u8981\u5168\u7ad9\u53d1\u5e03 \u56e2\u961f\u534f\u4f5c&#xff1a;\u4ee3\u7801\u51b2\u7a81\u3001\u6c9f\u901a\u6210\u672c\u9ad8<\/p>\n<p>\/\/ \u7535\u5546\u7cfb\u7edf\u6838\u5fc3\u4e1a\u52a1\u6d41\u7a0b&#xff1a; \u7528\u6237\u6d4f\u89c8 \u2192 \u52a0\u5165\u8d2d\u7269\u8f66 \u2192 \u4e0b\u5355 \u2192 \u652f\u4ed8 \u2192 \u53d1\u8d27 \u2192 \u6536\u8d27 \u2192 \u8bc4\u4ef7 \u2193 \u2193 \u2193 \u2193 \u2193 \u2193 \u2193 \u5546\u54c1\u670d\u52a1 \u8d2d\u7269\u8f66\u670d\u52a1 \u8ba2\u5355\u670d\u52a1 \u652f\u4ed8\u670d\u52a1 \u5e93\u5b58\u670d\u52a1 \u7269\u6d41\u670d\u52a1 \u8bc4\u4ef7\u670d\u52a1 \u5fae\u670d\u52a1\u62c6\u5206\u7b56\u7565&#xff1a;<\/p>\n<p>text \u7535\u5546\u5fae\u670d\u52a1\u62c6\u5206\u539f\u5219&#xff1a;<\/p>\n<li>\u6309\u4e1a\u52a1\u9886\u57df\u62c6\u5206&#xff08;DDD\u9886\u57df\u9a71\u52a8\u8bbe\u8ba1&#xff09;<\/li>\n<li>\u6309\u529f\u80fd\u72ec\u7acb\u6027\u62c6\u5206<\/li>\n<li>\u6309\u6570\u636e\u8fb9\u754c\u62c6\u5206<\/li>\n<li>\u6309\u56e2\u961f\u7ed3\u6784\u62c6\u5206<\/li>\n<li>\u6309\u6027\u80fd\u9700\u6c42\u62c6\u5206<\/li>\n<p>\u6838\u5fc3\u670d\u52a1\u5212\u5206&#xff1a; \u7528\u6237\u57df&#xff1a;\u7528\u6237\u670d\u52a1\u3001\u8ba4\u8bc1\u670d\u52a1\u3001\u4f1a\u5458\u670d\u52a1 \u5546\u54c1\u57df&#xff1a;\u5546\u54c1\u670d\u52a1\u3001\u7c7b\u76ee\u670d\u52a1\u3001\u641c\u7d22\u670d\u52a1 \u4ea4\u6613\u57df&#xff1a;\u8d2d\u7269\u8f66\u670d\u52a1\u3001\u8ba2\u5355\u670d\u52a1\u3001\u652f\u4ed8\u670d\u52a1 \u5e93\u5b58\u57df&#xff1a;\u5e93\u5b58\u670d\u52a1\u3001\u4ed3\u5e93\u670d\u52a1 \u7269\u6d41\u57df&#xff1a;\u7269\u6d41\u670d\u52a1\u3001\u5730\u5740\u670d\u52a1 \u8425\u9500\u57df&#xff1a;\u4f18\u60e0\u5238\u670d\u52a1\u3001\u6d3b\u52a8\u670d\u52a1 \u8fd0\u8425\u57df&#xff1a;\u7edf\u8ba1\u670d\u52a1\u3001\u901a\u77e5\u670d\u52a1 1.2 \u6280\u672f\u6808\u5168\u666f\u56fe \u5b8c\u6574\u6280\u672f\u9009\u578b&#xff1a;<\/p>\n<p>xml<\/p>\n<p>\u6838\u5fc3\u6280\u672f&#xff1a;Spring Boot 3.1 &#043; Spring Cloud 2022 &#043; JDK 17 \u6ce8\u518c\u4e2d\u5fc3&#xff1a;Nacos 2.2&#xff08;\u670d\u52a1\u53d1\u73b0&#043;\u914d\u7f6e\u4e2d\u5fc3&#xff09; API\u7f51\u5173&#xff1a;Spring Cloud Gateway &#043; Sentinel \u670d\u52a1\u901a\u4fe1&#xff1a;OpenFeign &#043; LoadBalancer \u7194\u65ad\u964d\u7ea7&#xff1a;Resilience4j &#043; Sentinel \u94fe\u8def\u8ffd\u8e2a&#xff1a;Sleuth &#043; Zipkin &#043; SkyWalking \u914d\u7f6e\u4e2d\u5fc3&#xff1a;Nacos Config \u5206\u5e03\u5f0f\u4e8b\u52a1&#xff1a;Seata \u6d88\u606f\u961f\u5217&#xff1a;RocketMQ 5.0 \u7f13\u5b58&#xff1a;Redis 7 &#043; Redisson \u6570\u636e\u5e93&#xff1a;MySQL 8.0 &#043; MyBatis-Plus \u641c\u7d22\u5f15\u64ce&#xff1a;Elasticsearch 8.0 \u5bf9\u8c61\u5b58\u50a8&#xff1a;MinIO \u76d1\u63a7\u544a\u8b66&#xff1a;Prometheus &#043; Grafana &#043; AlertManager \u5bb9\u5668\u5316&#xff1a;Docker &#043; Kubernetes CI\/CD&#xff1a;Jenkins &#043; GitLab<\/p>\n<h3>\u7b2c\u4e8c\u7ae0&#xff1a;\u9879\u76ee\u521d\u59cb\u5316\u4e0e\u57fa\u7840\u67b6\u6784<\/h3>\n<p>2.1 \u521b\u5efa\u7236\u5de5\u7a0b\u4e0e\u6a21\u5757\u5212\u5206 \u9879\u76ee\u6574\u4f53\u7ed3\u6784&#xff1a;<\/p>\n<p>text e-commerce-microservices\/ \u251c\u2500\u2500 README.md \u251c\u2500\u2500 pom.xml # \u7236\u5de5\u7a0b \u251c\u2500\u2500 docker-compose.yml \u251c\u2500\u2500 sql\/ # \u6570\u636e\u5e93\u811a\u672c \u251c\u2500\u2500 config\/ # \u914d\u7f6e\u6587\u4ef6 \u251c\u2500\u2500 scripts\/ # \u90e8\u7f72\u811a\u672c \u251c\u2500\u2500 docs\/ # \u6587\u6863 \u251c\u2500\u2500 e-commerce-parent\/ # \u7236\u6a21\u5757 \u2502 \u251c\u2500\u2500 e-commerce-common\/ # \u516c\u5171\u6a21\u5757 \u2502 \u2502 \u251c\u2500\u2500 common-core\/ # \u6838\u5fc3\u5de5\u5177 \u2502 \u2502 \u251c\u2500\u2500 common-dto\/ # \u901a\u7528DTO \u2502 \u2502 \u251c\u2500\u2500 common-feign\/ # Feign\u5ba2\u6237\u7aef \u2502 \u2502 \u251c\u2500\u2500 common-redis\/ # Redis\u914d\u7f6e \u2502 \u2502 \u251c\u2500\u2500 common-mybatis\/ # MyBatis\u914d\u7f6e \u2502 \u2502 \u2514\u2500\u2500 common-security\/ # \u5b89\u5168\u914d\u7f6e \u2502 \u2502 \u2502 \u251c\u2500\u2500 e-commerce-gateway\/ # API\u7f51\u5173 \u2502 \u251c\u2500\u2500 e-commerce-auth\/ # \u8ba4\u8bc1\u4e2d\u5fc3 \u2502 \u251c\u2500\u2500 e-commerce-user\/ # \u7528\u6237\u670d\u52a1 \u2502 \u251c\u2500\u2500 e-commerce-product\/ # \u5546\u54c1\u670d\u52a1 \u2502 \u251c\u2500\u2500 e-commerce-cart\/ # \u8d2d\u7269\u8f66\u670d\u52a1 \u2502 \u251c\u2500\u2500 e-commerce-order\/ # \u8ba2\u5355\u670d\u52a1 \u2502 \u251c\u2500\u2500 e-commerce-payment\/ # \u652f\u4ed8\u670d\u52a1 \u2502 \u251c\u2500\u2500 e-commerce-inventory\/ # \u5e93\u5b58\u670d\u52a1 \u2502 \u251c\u2500\u2500 e-commerce-coupon\/ # \u4f18\u60e0\u5238\u670d\u52a1 \u2502 \u251c\u2500\u2500 e-commerce-search\/ # \u641c\u7d22\u670d\u52a1 \u2502 \u251c\u2500\u2500 e-commerce-notification\/ # \u901a\u77e5\u670d\u52a1 \u2502 \u2514\u2500\u2500 e-commerce-monitor\/ # \u76d1\u63a7\u670d\u52a1 \u2514\u2500\u2500 infrastructure\/ # \u57fa\u7840\u8bbe\u65bd \u251c\u2500\u2500 nacos\/ # Nacos\u914d\u7f6e \u251c\u2500\u2500 seata\/ # Seata\u914d\u7f6e \u251c\u2500\u2500 sentinel\/ # Sentinel\u914d\u7f6e \u2514\u2500\u2500 prometheus\/ # Prometheus\u914d\u7f6e \u7236\u5de5\u7a0bpom.xml&#xff1a;<\/p>\n<p>xml<\/p>\n<p> &lt;?xml version&#061;&#034;1.0&#034; encoding&#061;&#034;UTF-8&#034;?&gt; <\/p>\n<p>&lt;modelVersion&gt;4.0.0&lt;\/modelVersion&gt;<\/p>\n<p>&lt;groupId&gt;com.ecommerce&lt;\/groupId&gt;<br \/>\n&lt;artifactId&gt;e-commerce-microservices&lt;\/artifactId&gt;<br \/>\n&lt;version&gt;1.0.0&lt;\/version&gt;<br \/>\n&lt;packaging&gt;pom&lt;\/packaging&gt;<br \/>\n&lt;name&gt;e-commerce-microservices&lt;\/name&gt;<br \/>\n&lt;description&gt;\u7535\u5546\u5fae\u670d\u52a1\u7cfb\u7edf&lt;\/description&gt;<\/p>\n<p>&lt;!&#8211; \u6a21\u5757\u5b9a\u4e49 &#8211;&gt;<br \/>\n&lt;modules&gt;<br \/>\n    &lt;module&gt;e-commerce-parent\/e-commerce-common\/common-core&lt;\/module&gt;<br \/>\n    &lt;module&gt;e-commerce-parent\/e-commerce-common\/common-dto&lt;\/module&gt;<br \/>\n    &lt;module&gt;e-commerce-parent\/e-commerce-common\/common-feign&lt;\/module&gt;<br \/>\n    &lt;module&gt;e-commerce-parent\/e-commerce-common\/common-redis&lt;\/module&gt;<br \/>\n    &lt;module&gt;e-commerce-parent\/e-commerce-common\/common-mybatis&lt;\/module&gt;<br \/>\n    &lt;module&gt;e-commerce-parent\/e-commerce-common\/common-security&lt;\/module&gt;<br \/>\n    &lt;module&gt;e-commerce-parent\/e-commerce-gateway&lt;\/module&gt;<br \/>\n    &lt;module&gt;e-commerce-parent\/e-commerce-auth&lt;\/module&gt;<br \/>\n    &lt;module&gt;e-commerce-parent\/e-commerce-user&lt;\/module&gt;<br \/>\n    &lt;module&gt;e-commerce-parent\/e-commerce-product&lt;\/module&gt;<br \/>\n    &lt;module&gt;e-commerce-parent\/e-commerce-cart&lt;\/module&gt;<br \/>\n    &lt;module&gt;e-commerce-parent\/e-commerce-order&lt;\/module&gt;<br \/>\n    &lt;module&gt;e-commerce-parent\/e-commerce-payment&lt;\/module&gt;<br \/>\n    &lt;module&gt;e-commerce-parent\/e-commerce-inventory&lt;\/module&gt;<br \/>\n    &lt;module&gt;e-commerce-parent\/e-commerce-coupon&lt;\/module&gt;<br \/>\n    &lt;module&gt;e-commerce-parent\/e-commerce-search&lt;\/module&gt;<br \/>\n    &lt;module&gt;e-commerce-parent\/e-commerce-notification&lt;\/module&gt;<br \/>\n    &lt;module&gt;e-commerce-parent\/e-commerce-monitor&lt;\/module&gt;<br \/>\n&lt;\/modules&gt;<\/p>\n<p>&lt;!&#8211; \u7edf\u4e00\u7248\u672c\u7ba1\u7406 &#8211;&gt;<br \/>\n&lt;parent&gt;<br \/>\n    &lt;groupId&gt;org.springframework.boot&lt;\/groupId&gt;<br \/>\n    &lt;artifactId&gt;spring-boot-starter-parent&lt;\/artifactId&gt;<br \/>\n    &lt;version&gt;3.1.0&lt;\/version&gt;<br \/>\n    &lt;relativePath\/&gt;<br \/>\n&lt;\/parent&gt;<\/p>\n<p>&lt;properties&gt;<br \/>\n    &lt;!&#8211; \u57fa\u7840\u7248\u672c &#8211;&gt;<br \/>\n    &lt;java.version&gt;17&lt;\/java.version&gt;<br \/>\n    &lt;project.build.sourceEncoding&gt;UTF-8&lt;\/project.build.sourceEncoding&gt;<br \/>\n    &lt;project.reporting.outputEncoding&gt;UTF-8&lt;\/project.reporting.outputEncoding&gt;<\/p>\n<p>    &lt;!&#8211; Spring Cloud &#8211;&gt;<br \/>\n    &lt;spring-cloud.version&gt;2022.0.3&lt;\/spring-cloud.version&gt;<br \/>\n    &lt;spring-cloud-alibaba.version&gt;2022.0.0.0&lt;\/spring-cloud-alibaba.version&gt;<\/p>\n<p>    &lt;!&#8211; \u6301\u4e45\u5c42 &#8211;&gt;<br \/>\n    &lt;mybatis-plus.version&gt;3.5.3.1&lt;\/mybatis-plus.version&gt;<br \/>\n    &lt;druid.version&gt;1.2.18&lt;\/druid.version&gt;<\/p>\n<p>    &lt;!&#8211; \u5de5\u5177 &#8211;&gt;<br \/>\n    &lt;mapstruct.version&gt;1.5.5.Final&lt;\/mapstruct.version&gt;<br \/>\n    &lt;lombok.version&gt;1.18.28&lt;\/lombok.version&gt;<br \/>\n    &lt;hutool.version&gt;5.8.20&lt;\/hutool.version&gt;<\/p>\n<p>    &lt;!&#8211; \u7f13\u5b58 &#8211;&gt;<br \/>\n    &lt;redisson.version&gt;3.20.0&lt;\/redisson.version&gt;<\/p>\n<p>    &lt;!&#8211; \u5206\u5e03\u5f0f\u4e8b\u52a1 &#8211;&gt;<br \/>\n    &lt;seata.version&gt;1.7.1&lt;\/seata.version&gt;<\/p>\n<p>    &lt;!&#8211; \u6d88\u606f\u961f\u5217 &#8211;&gt;<br \/>\n    &lt;rocketmq.version&gt;2.2.3&lt;\/rocketmq.version&gt;<\/p>\n<p>    &lt;!&#8211; \u670d\u52a1\u7aef\u53e3 &#8211;&gt;<br \/>\n    &lt;gateway.port&gt;8080&lt;\/gateway.port&gt;<br \/>\n    &lt;auth.port&gt;8081&lt;\/auth.port&gt;<br \/>\n    &lt;user.port&gt;8082&lt;\/user.port&gt;<br \/>\n    &lt;product.port&gt;8083&lt;\/product.port&gt;<br \/>\n    &lt;cart.port&gt;8084&lt;\/cart.port&gt;<br \/>\n    &lt;order.port&gt;8085&lt;\/order.port&gt;<br \/>\n    &lt;payment.port&gt;8086&lt;\/payment.port&gt;<br \/>\n    &lt;inventory.port&gt;8087&lt;\/inventory.port&gt;<br \/>\n&lt;\/properties&gt;<\/p>\n<p>&lt;!&#8211; \u4f9d\u8d56\u7ba1\u7406 &#8211;&gt;<br \/>\n&lt;dependencyManagement&gt;<br \/>\n    &lt;dependencies&gt;<br \/>\n        &lt;!&#8211; Spring Cloud &#8211;&gt;<br \/>\n        &lt;dependency&gt;<br \/>\n            &lt;groupId&gt;org.springframework.cloud&lt;\/groupId&gt;<br \/>\n            &lt;artifactId&gt;spring-cloud-dependencies&lt;\/artifactId&gt;<br \/>\n            &lt;version&gt;${spring-cloud.version}&lt;\/version&gt;<br \/>\n            &lt;type&gt;pom&lt;\/type&gt;<br \/>\n            &lt;scope&gt;import&lt;\/scope&gt;<br \/>\n        &lt;\/dependency&gt;<\/p>\n<p>        &lt;!&#8211; Spring Cloud Alibaba &#8211;&gt;<br \/>\n        &lt;dependency&gt;<br \/>\n            &lt;groupId&gt;com.alibaba.cloud&lt;\/groupId&gt;<br \/>\n            &lt;artifactId&gt;spring-cloud-alibaba-dependencies&lt;\/artifactId&gt;<br \/>\n            &lt;version&gt;${spring-cloud-alibaba.version}&lt;\/version&gt;<br \/>\n            &lt;type&gt;pom&lt;\/type&gt;<br \/>\n            &lt;scope&gt;import&lt;\/scope&gt;<br \/>\n        &lt;\/dependency&gt;<\/p>\n<p>        &lt;!&#8211; MyBatis Plus &#8211;&gt;<br \/>\n        &lt;dependency&gt;<br \/>\n            &lt;groupId&gt;com.baomidou&lt;\/groupId&gt;<br \/>\n            &lt;artifactId&gt;mybatis-plus-boot-starter&lt;\/artifactId&gt;<br \/>\n            &lt;version&gt;${mybatis-plus.version}&lt;\/version&gt;<br \/>\n        &lt;\/dependency&gt;<\/p>\n<p>        &lt;!&#8211; Druid &#8211;&gt;<br \/>\n        &lt;dependency&gt;<br \/>\n            &lt;groupId&gt;com.alibaba&lt;\/groupId&gt;<br \/>\n            &lt;artifactId&gt;druid-spring-boot-starter&lt;\/artifactId&gt;<br \/>\n            &lt;version&gt;${druid.version}&lt;\/version&gt;<br \/>\n        &lt;\/dependency&gt;<\/p>\n<p>        &lt;!&#8211; MapStruct &#8211;&gt;<br \/>\n        &lt;dependency&gt;<br \/>\n            &lt;groupId&gt;org.mapstruct&lt;\/groupId&gt;<br \/>\n            &lt;artifactId&gt;mapstruct&lt;\/artifactId&gt;<br \/>\n            &lt;version&gt;${mapstruct.version}&lt;\/version&gt;<br \/>\n        &lt;\/dependency&gt;<\/p>\n<p>        &lt;!&#8211; Redisson &#8211;&gt;<br \/>\n        &lt;dependency&gt;<br \/>\n            &lt;groupId&gt;org.redisson&lt;\/groupId&gt;<br \/>\n            &lt;artifactId&gt;redisson-spring-boot-starter&lt;\/artifactId&gt;<br \/>\n            &lt;version&gt;${redisson.version}&lt;\/version&gt;<br \/>\n        &lt;\/dependency&gt;<\/p>\n<p>        &lt;!&#8211; Hutool &#8211;&gt;<br \/>\n        &lt;dependency&gt;<br \/>\n            &lt;groupId&gt;cn.hutool&lt;\/groupId&gt;<br \/>\n            &lt;artifactId&gt;hutool-all&lt;\/artifactId&gt;<br \/>\n            &lt;version&gt;${hutool.version}&lt;\/version&gt;<br \/>\n        &lt;\/dependency&gt;<\/p>\n<p>        &lt;!&#8211; Seata &#8211;&gt;<br \/>\n        &lt;dependency&gt;<br \/>\n            &lt;groupId&gt;io.seata&lt;\/groupId&gt;<br \/>\n            &lt;artifactId&gt;seata-spring-boot-starter&lt;\/artifactId&gt;<br \/>\n            &lt;version&gt;${seata.version}&lt;\/version&gt;<br \/>\n        &lt;\/dependency&gt;<\/p>\n<p>        &lt;!&#8211; RocketMQ &#8211;&gt;<br \/>\n        &lt;dependency&gt;<br \/>\n            &lt;groupId&gt;org.apache.rocketmq&lt;\/groupId&gt;<br \/>\n            &lt;artifactId&gt;rocketmq-spring-boot-starter&lt;\/artifactId&gt;<br \/>\n            &lt;version&gt;${rocketmq.version}&lt;\/version&gt;<br \/>\n        &lt;\/dependency&gt;<br \/>\n    &lt;\/dependencies&gt;<br \/>\n&lt;\/dependencyManagement&gt;<\/p>\n<p>&lt;!&#8211; \u901a\u7528\u4f9d\u8d56 &#8211;&gt;<br \/>\n&lt;dependencies&gt;<br \/>\n    &lt;!&#8211; Lombok &#8211;&gt;<br \/>\n    &lt;dependency&gt;<br \/>\n        &lt;groupId&gt;org.projectlombok&lt;\/groupId&gt;<br \/>\n        &lt;artifactId&gt;lombok&lt;\/artifactId&gt;<br \/>\n        &lt;optional&gt;true&lt;\/optional&gt;<br \/>\n    &lt;\/dependency&gt;<\/p>\n<p>    &lt;!&#8211; \u6d4b\u8bd5 &#8211;&gt;<br \/>\n    &lt;dependency&gt;<br \/>\n        &lt;groupId&gt;org.springframework.boot&lt;\/groupId&gt;<br \/>\n        &lt;artifactId&gt;spring-boot-starter-test&lt;\/artifactId&gt;<br \/>\n        &lt;scope&gt;test&lt;\/scope&gt;<br \/>\n    &lt;\/dependency&gt;<br \/>\n&lt;\/dependencies&gt;<\/p>\n<p>&lt;!&#8211; \u6784\u5efa\u914d\u7f6e &#8211;&gt;<br \/>\n&lt;build&gt;<br \/>\n    &lt;plugins&gt;<br \/>\n        &lt;!&#8211; \u7f16\u8bd1\u5668\u63d2\u4ef6 &#8211;&gt;<br \/>\n        &lt;plugin&gt;<br \/>\n            &lt;groupId&gt;org.apache.maven.plugins&lt;\/groupId&gt;<br \/>\n            &lt;artifactId&gt;maven-compiler-plugin&lt;\/artifactId&gt;<br \/>\n            &lt;configuration&gt;<br \/>\n                &lt;source&gt;${java.version}&lt;\/source&gt;<br \/>\n                &lt;target&gt;${java.version}&lt;\/target&gt;<br \/>\n                &lt;encoding&gt;${project.build.sourceEncoding}&lt;\/encoding&gt;<br \/>\n                &lt;annotationProcessorPaths&gt;<br \/>\n                    &lt;path&gt;<br \/>\n                        &lt;groupId&gt;org.projectlombok&lt;\/groupId&gt;<br \/>\n                        &lt;artifactId&gt;lombok&lt;\/artifactId&gt;<br \/>\n                        &lt;version&gt;${lombok.version}&lt;\/version&gt;<br \/>\n                    &lt;\/path&gt;<br \/>\n                    &lt;path&gt;<br \/>\n                        &lt;groupId&gt;org.mapstruct&lt;\/groupId&gt;<br \/>\n                        &lt;artifactId&gt;mapstruct-processor&lt;\/artifactId&gt;<br \/>\n                        &lt;version&gt;${mapstruct.version}&lt;\/version&gt;<br \/>\n                    &lt;\/path&gt;<br \/>\n                    &lt;path&gt;<br \/>\n                        &lt;groupId&gt;org.projectlombok&lt;\/groupId&gt;<br \/>\n                        &lt;artifactId&gt;lombok-mapstruct-binding&lt;\/artifactId&gt;<br \/>\n                        &lt;version&gt;0.2.0&lt;\/version&gt;<br \/>\n                    &lt;\/path&gt;<br \/>\n                &lt;\/annotationProcessorPaths&gt;<br \/>\n            &lt;\/configuration&gt;<br \/>\n        &lt;\/plugin&gt;<\/p>\n<p>        &lt;!&#8211; Spring Boot Maven\u63d2\u4ef6 &#8211;&gt;<br \/>\n        &lt;plugin&gt;<br \/>\n            &lt;groupId&gt;org.springframework.boot&lt;\/groupId&gt;<br \/>\n            &lt;artifactId&gt;spring-boot-maven-plugin&lt;\/artifactId&gt;<br \/>\n            &lt;configuration&gt;<br \/>\n                &lt;excludes&gt;<br \/>\n                    &lt;exclude&gt;<br \/>\n                        &lt;groupId&gt;org.projectlombok&lt;\/groupId&gt;<br \/>\n                        &lt;artifactId&gt;lombok&lt;\/artifactId&gt;<br \/>\n                    &lt;\/exclude&gt;<br \/>\n                &lt;\/excludes&gt;<br \/>\n            &lt;\/configuration&gt;<br \/>\n        &lt;\/plugin&gt;<br \/>\n    &lt;\/plugins&gt;<br \/>\n&lt;\/build&gt;<br \/>\n 2.2 \u516c\u5171\u6a21\u5757\u8bbe\u8ba1 common-core \u6838\u5fc3\u5de5\u5177\u6a21\u5757&#xff1a; <\/p>\n<p>java \/\/ 1. \u7edf\u4e00\u54cd\u5e94\u7c7b package com.ecommerce.common.core.response;<\/p>\n<p>import lombok.Data; import lombok.experimental.Accessors;<\/p>\n<p>import java.io.Serializable;<\/p>\n<p>&#064;Data &#064;Accessors(chain &#061; true) public class ApiResponse implements Serializable {<\/p>\n<p>private Integer code;<br \/>\nprivate String message;<br \/>\nprivate T data;<br \/>\nprivate Long timestamp;<\/p>\n<p>public static &lt;T&gt; ApiResponse&lt;T&gt; success() {<br \/>\n    return success(null);<br \/>\n}<\/p>\n<p>public static &lt;T&gt; ApiResponse&lt;T&gt; success(T data) {<br \/>\n    return new ApiResponse&lt;T&gt;()<br \/>\n            .setCode(200)<br \/>\n            .setMessage(&#034;\u6210\u529f&#034;)<br \/>\n            .setData(data)<br \/>\n            .setTimestamp(System.currentTimeMillis());<br \/>\n}<\/p>\n<p>public static &lt;T&gt; ApiResponse&lt;T&gt; success(String message, T data) {<br \/>\n    return new ApiResponse&lt;T&gt;()<br \/>\n            .setCode(200)<br \/>\n            .setMessage(message)<br \/>\n            .setData(data)<br \/>\n            .setTimestamp(System.currentTimeMillis());<br \/>\n}<\/p>\n<p>public static &lt;T&gt; ApiResponse&lt;T&gt; error(Integer code, String message) {<br \/>\n    return new ApiResponse&lt;T&gt;()<br \/>\n            .setCode(code)<br \/>\n            .setMessage(message)<br \/>\n            .setTimestamp(System.currentTimeMillis());<br \/>\n}<\/p>\n<p>public static &lt;T&gt; ApiResponse&lt;T&gt; error(String message) {<br \/>\n    return error(500, message);<br \/>\n}<\/p>\n<p>}<\/p>\n<p>\/\/ 2. \u4e1a\u52a1\u5f02\u5e38\u7c7b package com.ecommerce.common.core.exception;<\/p>\n<p>import lombok.Getter;<\/p>\n<p>&#064;Getter public class BusinessException extends RuntimeException {<\/p>\n<p>private final Integer code;<br \/>\nprivate final String message;<\/p>\n<p>public BusinessException(String message) {<br \/>\n    super(message);<br \/>\n    this.code &#061; 500;<br \/>\n    this.message &#061; message;<br \/>\n}<\/p>\n<p>public BusinessException(Integer code, String message) {<br \/>\n    super(message);<br \/>\n    this.code &#061; code;<br \/>\n    this.message &#061; message;<br \/>\n}<\/p>\n<p>public BusinessException(ErrorCode errorCode) {<br \/>\n    super(errorCode.getMessage());<br \/>\n    this.code &#061; errorCode.getCode();<br \/>\n    this.message &#061; errorCode.getMessage();<br \/>\n}<\/p>\n<p>}<\/p>\n<p>\/\/ 3. \u9519\u8bef\u7801\u679a\u4e3e package com.ecommerce.common.core.exception;<\/p>\n<p>import lombok.Getter;<\/p>\n<p>&#064;Getter public enum ErrorCode {<\/p>\n<p>\/\/ \u901a\u7528\u9519\u8bef\u7801<br \/>\nSUCCESS(200, &#034;\u6210\u529f&#034;),<br \/>\nPARAM_ERROR(400, &#034;\u53c2\u6570\u9519\u8bef&#034;),<br \/>\nUNAUTHORIZED(401, &#034;\u672a\u6388\u6743&#034;),<br \/>\nFORBIDDEN(403, &#034;\u7981\u6b62\u8bbf\u95ee&#034;),<br \/>\nNOT_FOUND(404, &#034;\u8d44\u6e90\u4e0d\u5b58\u5728&#034;),<br \/>\nMETHOD_NOT_ALLOWED(405, &#034;\u65b9\u6cd5\u4e0d\u5141\u8bb8&#034;),<br \/>\nSYSTEM_ERROR(500, &#034;\u7cfb\u7edf\u9519\u8bef&#034;),<br \/>\nSERVICE_UNAVAILABLE(503, &#034;\u670d\u52a1\u4e0d\u53ef\u7528&#034;),<\/p>\n<p>\/\/ \u4e1a\u52a1\u9519\u8bef\u7801 1000-1999<br \/>\nUSER_NOT_EXIST(1001, &#034;\u7528\u6237\u4e0d\u5b58\u5728&#034;),<br \/>\nUSER_DISABLED(1002, &#034;\u7528\u6237\u5df2\u88ab\u7981\u7528&#034;),<br \/>\nUSER_PASSWORD_ERROR(1003, &#034;\u5bc6\u7801\u9519\u8bef&#034;),<br \/>\nUSER_EXISTS(1004, &#034;\u7528\u6237\u5df2\u5b58\u5728&#034;),<\/p>\n<p>\/\/ \u5546\u54c1\u9519\u8bef\u7801 2000-2999<br \/>\nPRODUCT_NOT_EXIST(2001, &#034;\u5546\u54c1\u4e0d\u5b58\u5728&#034;),<br \/>\nPRODUCT_OFF_SHELF(2002, &#034;\u5546\u54c1\u5df2\u4e0b\u67b6&#034;),<br \/>\nPRODUCT_INSUFFICIENT_STOCK(2003, &#034;\u5546\u54c1\u5e93\u5b58\u4e0d\u8db3&#034;),<\/p>\n<p>\/\/ \u8ba2\u5355\u9519\u8bef\u7801 3000-3999<br \/>\nORDER_NOT_EXIST(3001, &#034;\u8ba2\u5355\u4e0d\u5b58\u5728&#034;),<br \/>\nORDER_STATUS_ERROR(3002, &#034;\u8ba2\u5355\u72b6\u6001\u9519\u8bef&#034;),<br \/>\nORDER_CANCEL_FAILED(3003, &#034;\u8ba2\u5355\u53d6\u6d88\u5931\u8d25&#034;),<\/p>\n<p>\/\/ \u652f\u4ed8\u9519\u8bef\u7801 4000-4999<br \/>\nPAYMENT_FAILED(4001, &#034;\u652f\u4ed8\u5931\u8d25&#034;),<br \/>\nPAYMENT_NOT_EXIST(4002, &#034;\u652f\u4ed8\u8bb0\u5f55\u4e0d\u5b58\u5728&#034;),<\/p>\n<p>\/\/ \u5e93\u5b58\u9519\u8bef\u7801 5000-5999<br \/>\nINVENTORY_LOCK_FAILED(5001, &#034;\u5e93\u5b58\u9501\u5b9a\u5931\u8d25&#034;),<br \/>\nINVENTORY_RELEASE_FAILED(5002, &#034;\u5e93\u5b58\u91ca\u653e\u5931\u8d25&#034;),<\/p>\n<p>\/\/ \u4f18\u60e0\u5238\u9519\u8bef\u7801 6000-6999<br \/>\nCOUPON_NOT_EXIST(6001, &#034;\u4f18\u60e0\u5238\u4e0d\u5b58\u5728&#034;),<br \/>\nCOUPON_EXPIRED(6002, &#034;\u4f18\u60e0\u5238\u5df2\u8fc7\u671f&#034;),<br \/>\nCOUPON_USED(6003, &#034;\u4f18\u60e0\u5238\u5df2\u4f7f\u7528&#034;);<\/p>\n<p>private final Integer code;<br \/>\nprivate final String message;<\/p>\n<p>ErrorCode(Integer code, String message) {<br \/>\n    this.code &#061; code;<br \/>\n    this.message &#061; message;<br \/>\n}<\/p>\n<p>}<\/p>\n<p>\/\/ 4. \u7edf\u4e00\u5f02\u5e38\u5904\u7406\u5668 package com.ecommerce.common.core.handler;<\/p>\n<p>import com.ecommerce.common.core.response.ApiResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.validation.BindException; import org.springframework.validation.FieldError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice;<\/p>\n<p>import java.util.HashMap; import java.util.Map;<\/p>\n<p>&#064;Slf4j &#064;RestControllerAdvice public class GlobalExceptionHandler {<\/p>\n<p>&#064;ExceptionHandler(BusinessException.class)<br \/>\npublic ApiResponse&lt;?&gt; handleBusinessException(BusinessException e) {<br \/>\n    log.error(&#034;\u4e1a\u52a1\u5f02\u5e38: code&#061;{}, message&#061;{}&#034;, e.getCode(), e.getMessage());<br \/>\n    return ApiResponse.error(e.getCode(), e.getMessage());<br \/>\n}<\/p>\n<p>&#064;ExceptionHandler(MethodArgumentNotValidException.class)<br \/>\npublic ApiResponse&lt;?&gt; handleValidationException(MethodArgumentNotValidException e) {<br \/>\n    Map&lt;String, String&gt; errors &#061; new HashMap&lt;&gt;();<br \/>\n    e.getBindingResult().getAllErrors().forEach(error -&gt; {<br \/>\n        String fieldName &#061; ((FieldError) error).getField();<br \/>\n        String errorMessage &#061; error.getDefaultMessage();<br \/>\n        errors.put(fieldName, errorMessage);<br \/>\n    });<\/p>\n<p>    log.error(&#034;\u53c2\u6570\u9a8c\u8bc1\u5931\u8d25: {}&#034;, errors);<br \/>\n    return ApiResponse.error(400, &#034;\u53c2\u6570\u9a8c\u8bc1\u5931\u8d25&#034;).setData(errors);<br \/>\n}<\/p>\n<p>&#064;ExceptionHandler(Exception.class)<br \/>\npublic ApiResponse&lt;?&gt; handleGlobalException(Exception e) {<br \/>\n    log.error(&#034;\u7cfb\u7edf\u5f02\u5e38: &#034;, e);<br \/>\n    return ApiResponse.error(500, &#034;\u7cfb\u7edf\u5f02\u5e38&#xff0c;\u8bf7\u7a0d\u540e\u91cd\u8bd5&#034;);<br \/>\n}<\/p>\n<p>} common-dto \u6570\u636e\u4f20\u8f93\u5bf9\u8c61&#xff1a;<\/p>\n<p>java \/\/ 1. \u5206\u9875\u8bf7\u6c42DTO package com.ecommerce.common.dto;<\/p>\n<p>import lombok.Data;<\/p>\n<p>&#064;Data public class PageRequest {<\/p>\n<p>private Integer pageNum &#061; 1;<br \/>\nprivate Integer pageSize &#061; 10;<br \/>\nprivate String orderBy;<br \/>\nprivate Boolean asc &#061; true;<\/p>\n<p>public Integer getOffset() {<br \/>\n    return (pageNum &#8211; 1) * pageSize;<br \/>\n}<\/p>\n<p>}<\/p>\n<p>\/\/ 2. \u5206\u9875\u54cd\u5e94DTO package com.ecommerce.common.dto;<\/p>\n<p>import lombok.Data; import lombok.NoArgsConstructor;<\/p>\n<p>import java.util.List;<\/p>\n<p>&#064;Data &#064;NoArgsConstructor public class PageResult {<\/p>\n<p>private Integer pageNum;<br \/>\nprivate Integer pageSize;<br \/>\nprivate Long total;<br \/>\nprivate Integer pages;<br \/>\nprivate List&lt;T&gt; list;<\/p>\n<p>public PageResult(Integer pageNum, Integer pageSize, Long total, List&lt;T&gt; list) {<br \/>\n    this.pageNum &#061; pageNum;<br \/>\n    this.pageSize &#061; pageSize;<br \/>\n    this.total &#061; total;<br \/>\n    this.pages &#061; (int) Math.ceil((double) total \/ pageSize);<br \/>\n    this.list &#061; list;<br \/>\n}<\/p>\n<p>public static &lt;T&gt; PageResult&lt;T&gt; of(Integer pageNum, Integer pageSize, Long total, List&lt;T&gt; list) {<br \/>\n    return new PageResult&lt;&gt;(pageNum, pageSize, total, list);<br \/>\n}<\/p>\n<p>}<\/p>\n<p>\/\/ 3. \u7528\u6237\u4e0a\u4e0b\u6587DTO package com.ecommerce.common.dto;<\/p>\n<p>import lombok.Data;<\/p>\n<p>&#064;Data public class UserContext {<\/p>\n<p>private Long userId;<br \/>\nprivate String username;<br \/>\nprivate String nickname;<br \/>\nprivate String phone;<br \/>\nprivate String email;<br \/>\nprivate Integer userType;<br \/>\nprivate String token;<\/p>\n<p>public static UserContext empty() {<br \/>\n    return new UserContext();<br \/>\n}<\/p>\n<p>public boolean isLogin() {<br \/>\n    return userId !&#061; null;<br \/>\n}<\/p>\n<p>} common-feign Feign\u5ba2\u6237\u7aef\u914d\u7f6e&#xff1a;<\/p>\n<p>java \/\/ 1. Feign\u914d\u7f6e\u7c7b package com.ecommerce.common.feign.config;<\/p>\n<p>import com.ecommerce.common.feign.interceptor.FeignAuthInterceptor; import feign.Logger; import feign.Request; import feign.Retryer; import feign.codec.ErrorDecoder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;<\/p>\n<p>import java.util.concurrent.TimeUnit;<\/p>\n<p>&#064;Configuration public class FeignConfig {<\/p>\n<p>&#064;Bean<br \/>\npublic Logger.Level feignLoggerLevel() {<br \/>\n    return Logger.Level.FULL;<br \/>\n}<\/p>\n<p>&#064;Bean<br \/>\npublic Request.Options options() {<br \/>\n    return new Request.Options(<br \/>\n        5, TimeUnit.SECONDS,   \/\/ \u8fde\u63a5\u8d85\u65f6<br \/>\n        30, TimeUnit.SECONDS,  \/\/ \u8bfb\u53d6\u8d85\u65f6<br \/>\n        true                   \/\/ \u8ddf\u968f\u91cd\u5b9a\u5411<br \/>\n    );<br \/>\n}<\/p>\n<p>&#064;Bean<br \/>\npublic Retryer feignRetryer() {<br \/>\n    \/\/ \u6700\u5927\u91cd\u8bd53\u6b21&#xff0c;\u521d\u59cb\u95f4\u9694100ms<br \/>\n    return new Retryer.Default(100, TimeUnit.SECONDS.toMillis(1), 3);<br \/>\n}<\/p>\n<p>&#064;Bean<br \/>\npublic FeignAuthInterceptor feignAuthInterceptor() {<br \/>\n    return new FeignAuthInterceptor();<br \/>\n}<\/p>\n<p>&#064;Bean<br \/>\npublic ErrorDecoder errorDecoder() {<br \/>\n    return new FeignErrorDecoder();<br \/>\n}<\/p>\n<p>}<\/p>\n<p>\/\/ 2. Feign\u8ba4\u8bc1\u62e6\u622a\u5668 package com.ecommerce.common.feign.interceptor;<\/p>\n<p>import feign.RequestInterceptor; import feign.RequestTemplate; import lombok.extern.slf4j.Slf4j; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes;<\/p>\n<p>import jakarta.servlet.http.HttpServletRequest; import java.util.Enumeration;<\/p>\n<p>&#064;Slf4j public class FeignAuthInterceptor implements RequestInterceptor {<\/p>\n<p>&#064;Override<br \/>\npublic void apply(RequestTemplate template) {<br \/>\n    \/\/ \u4f20\u9012\u8ba4\u8bc1\u4fe1\u606f<br \/>\n    ServletRequestAttributes attributes &#061;<br \/>\n        (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();<\/p>\n<p>    if (attributes !&#061; null) {<br \/>\n        HttpServletRequest request &#061; attributes.getRequest();<br \/>\n        Enumeration&lt;String&gt; headerNames &#061; request.getHeaderNames();<\/p>\n<p>        while (headerNames.hasMoreElements()) {<br \/>\n            String headerName &#061; headerNames.nextElement();<br \/>\n            if (headerName.startsWith(&#034;x-&#034;) ||<br \/>\n                headerName.equalsIgnoreCase(&#034;authorization&#034;)) {<br \/>\n                String headerValue &#061; request.getHeader(headerName);<br \/>\n                template.header(headerName, headerValue);<br \/>\n            }<br \/>\n        }<br \/>\n    }<\/p>\n<p>    \/\/ \u6dfb\u52a0Feign\u8c03\u7528\u6807\u8bc6<br \/>\n    template.header(&#034;X-Feign-Request&#034;, &#034;true&#034;);<br \/>\n    template.header(&#034;X-Request-ID&#034;, generateRequestId());<\/p>\n<p>    log.debug(&#034;Feign\u8bf7\u6c42: {} {}, Headers: {}&#034;,<br \/>\n             template.method(), template.url(), template.headers());<br \/>\n}<\/p>\n<p>private String generateRequestId() {<br \/>\n    return java.util.UUID.randomUUID().toString();<br \/>\n}<\/p>\n<p>}<\/p>\n<p>\/\/ 3. Feign\u964d\u7ea7\u5de5\u5382 package com.ecommerce.common.feign.fallback;<\/p>\n<p>import feign.hystrix.FallbackFactory; import lombok.extern.slf4j.Slf4j;<\/p>\n<p>&#064;Slf4j public class CommonFallbackFactory implements FallbackFactory {<\/p>\n<p>private final Class&lt;T&gt; targetType;<\/p>\n<p>public CommonFallbackFactory(Class&lt;T&gt; targetType) {<br \/>\n    this.targetType &#061; targetType;<br \/>\n}<\/p>\n<p>&#064;Override<br \/>\npublic T create(Throwable cause) {<br \/>\n    log.error(&#034;Feign\u8c03\u7528\u5931\u8d25&#xff0c;\u670d\u52a1: {}, \u9519\u8bef: {}&#034;,<br \/>\n             targetType.getSimpleName(), cause.getMessage());<\/p>\n<p>    return new FallbackProxy&lt;&gt;(targetType, cause).createProxy();<br \/>\n}<\/p>\n<p>} 2.3 \u6570\u636e\u5e93\u8bbe\u8ba1\u4e0e\u521d\u59cb\u5316 \u6570\u636e\u5e93\u8bbe\u8ba1\u539f\u5219&#xff1a;<\/p>\n<p>sql \u2013 1. \u7528\u6237\u6570\u636e\u5e93 (user_db) CREATE DATABASE IF NOT EXISTS user_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;<\/p>\n<p>\u2013 \u7528\u6237\u8868 CREATE TABLE user_db.tb_user ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT \u2018\u7528\u6237ID\u2019, username VARCHAR(50) NOT NULL COMMENT \u2018\u7528\u6237\u540d\u2019, password VARCHAR(100) NOT NULL COMMENT \u2018\u5bc6\u7801\u2019, nickname VARCHAR(50) COMMENT \u2018\u6635\u79f0\u2019, phone VARCHAR(20) COMMENT \u2018\u624b\u673a\u53f7\u2019, email VARCHAR(100) COMMENT \u2018\u90ae\u7bb1\u2019, avatar VARCHAR(500) COMMENT \u2018\u5934\u50cf\u2019, gender TINYINT DEFAULT 0 COMMENT \u2018\u6027\u522b 0-\u672a\u77e5 1-\u7537 2-\u5973\u2019, birthday DATE COMMENT \u2018\u751f\u65e5\u2019, status TINYINT DEFAULT 1 COMMENT \u2018\u72b6\u6001 0-\u7981\u7528 1-\u6b63\u5e38\u2019, user_type TINYINT DEFAULT 1 COMMENT \u2018\u7528\u6237\u7c7b\u578b 1-\u666e\u901a\u7528\u6237 2-\u4f1a\u5458 3-\u7ba1\u7406\u5458\u2019, last_login_time DATETIME COMMENT \u2018\u6700\u540e\u767b\u5f55\u65f6\u95f4\u2019, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT \u2018\u521b\u5efa\u65f6\u95f4\u2019, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT \u2018\u66f4\u65b0\u65f6\u95f4\u2019, PRIMARY KEY (id), UNIQUE KEY uk_username (username), UNIQUE KEY uk_phone (phone), UNIQUE KEY uk_email (email), KEY idx_status (status), KEY idx_create_time (create_time) ) ENGINE&#061;InnoDB DEFAULT CHARSET&#061;utf8mb4 COMMENT&#061;\u2018\u7528\u6237\u8868\u2019;<\/p>\n<p>\u2013 \u7528\u6237\u5730\u5740\u8868 CREATE TABLE user_db.tb_user_address ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT \u2018\u5730\u5740ID\u2019, user_id BIGINT NOT NULL COMMENT \u2018\u7528\u6237ID\u2019, receiver_name VARCHAR(50) NOT NULL COMMENT \u2018\u6536\u8d27\u4eba\u59d3\u540d\u2019, receiver_phone VARCHAR(20) NOT NULL COMMENT \u2018\u6536\u8d27\u4eba\u7535\u8bdd\u2019, province VARCHAR(50) COMMENT \u2018\u7701\u2019, city VARCHAR(50) COMMENT \u2018\u5e02\u2019, district VARCHAR(50) COMMENT \u2018\u533a\u2019, detail VARCHAR(200) COMMENT \u2018\u8be6\u7ec6\u5730\u5740\u2019, postal_code VARCHAR(10) COMMENT \u2018\u90ae\u653f\u7f16\u7801\u2019, is_default TINYINT DEFAULT 0 COMMENT \u2018\u662f\u5426\u9ed8\u8ba4\u5730\u5740 0-\u5426 1-\u662f\u2019, status TINYINT DEFAULT 1 COMMENT \u2018\u72b6\u6001 0-\u5220\u9664 1-\u6b63\u5e38\u2019, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT \u2018\u521b\u5efa\u65f6\u95f4\u2019, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT \u2018\u66f4\u65b0\u65f6\u95f4\u2019, PRIMARY KEY (id), KEY idx_user_id (user_id), KEY idx_is_default (is_default), CONSTRAINT fk_user_address_user FOREIGN KEY (user_id) REFERENCES tb_user (id) ON DELETE CASCADE ) ENGINE&#061;InnoDB DEFAULT CHARSET&#061;utf8mb4 COMMENT&#061;\u2018\u7528\u6237\u5730\u5740\u8868\u2019;<\/p>\n<p>\u2013 2. \u5546\u54c1\u6570\u636e\u5e93 (product_db) CREATE DATABASE IF NOT EXISTS product_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;<\/p>\n<p>\u2013 \u5546\u54c1\u8868 CREATE TABLE product_db.tb_product ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT \u2018\u5546\u54c1ID\u2019, spu_code VARCHAR(50) NOT NULL COMMENT \u2018SPU\u7f16\u7801\u2019, name VARCHAR(200) NOT NULL COMMENT \u2018\u5546\u54c1\u540d\u79f0\u2019, sub_title VARCHAR(500) COMMENT \u2018\u526f\u6807\u9898\u2019, category_id BIGINT NOT NULL COMMENT \u2018\u5206\u7c7bID\u2019, brand_id BIGINT COMMENT \u2018\u54c1\u724cID\u2019, main_image VARCHAR(500) COMMENT \u2018\u4e3b\u56fe\u2019, images TEXT COMMENT \u2018\u5546\u54c1\u56fe\u7247JSON\u6570\u7ec4\u2019, detail TEXT COMMENT \u2018\u5546\u54c1\u8be6\u60c5\u2019, specifications TEXT COMMENT \u2018\u89c4\u683c\u53c2\u6570JSON\u2019, price DECIMAL(10,2) NOT NULL COMMENT \u2018\u4ef7\u683c\u2019, market_price DECIMAL(10,2) COMMENT \u2018\u5e02\u573a\u4ef7\u683c\u2019, cost_price DECIMAL(10,2) COMMENT \u2018\u6210\u672c\u4ef7\u2019, stock INT NOT NULL DEFAULT 0 COMMENT \u2018\u5e93\u5b58\u2019, sales INT DEFAULT 0 COMMENT \u2018\u9500\u91cf\u2019, status TINYINT DEFAULT 1 COMMENT \u2018\u72b6\u6001 0-\u4e0b\u67b6 1-\u4e0a\u67b6 2-\u5f85\u5ba1\u6838\u2019, weight DECIMAL(10,3) COMMENT \u2018\u91cd\u91cf(kg)\u2019, volume DECIMAL(10,3) COMMENT \u2018\u4f53\u79ef(m\u00b3)\u2019, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT \u2018\u521b\u5efa\u65f6\u95f4\u2019, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT \u2018\u66f4\u65b0\u65f6\u95f4\u2019, PRIMARY KEY (id), UNIQUE KEY uk_spu_code (spu_code), KEY idx_category_id (category_id), KEY idx_brand_id (brand_id), KEY idx_status (status), KEY idx_create_time (create_time) ) ENGINE&#061;InnoDB DEFAULT CHARSET&#061;utf8mb4 COMMENT&#061;\u2018\u5546\u54c1\u8868\u2019;<\/p>\n<p>\u2013 \u5546\u54c1SKU\u8868 CREATE TABLE product_db.tb_product_sku ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT \u2018SKU ID\u2019, product_id BIGINT NOT NULL COMMENT \u2018\u5546\u54c1ID\u2019, sku_code VARCHAR(50) NOT NULL COMMENT \u2018SKU\u7f16\u7801\u2019, specifications VARCHAR(500) COMMENT \u2018\u89c4\u683cJSON\u2019, price DECIMAL(10,2) NOT NULL COMMENT \u2018\u4ef7\u683c\u2019, market_price DECIMAL(10,2) COMMENT \u2018\u5e02\u573a\u4ef7\u683c\u2019, stock INT NOT NULL DEFAULT 0 COMMENT \u2018\u5e93\u5b58\u2019, sales INT DEFAULT 0 COMMENT \u2018\u9500\u91cf\u2019, image VARCHAR(500) COMMENT \u2018SKU\u56fe\u7247\u2019, status TINYINT DEFAULT 1 COMMENT \u2018\u72b6\u6001 0-\u7981\u7528 1-\u6b63\u5e38\u2019, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT \u2018\u521b\u5efa\u65f6\u95f4\u2019, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT \u2018\u66f4\u65b0\u65f6\u95f4\u2019, PRIMARY KEY (id), UNIQUE KEY uk_sku_code (sku_code), KEY idx_product_id (product_id), KEY idx_status (status), CONSTRAINT fk_product_sku_product FOREIGN KEY (product_id) REFERENCES tb_product (id) ON DELETE CASCADE ) ENGINE&#061;InnoDB DEFAULT CHARSET&#061;utf8mb4 COMMENT&#061;\u2018\u5546\u54c1SKU\u8868\u2019;<\/p>\n<p>\u2013 3. \u8ba2\u5355\u6570\u636e\u5e93 (order_db) CREATE DATABASE IF NOT EXISTS order_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;<\/p>\n<p>\u2013 \u8ba2\u5355\u8868 CREATE TABLE order_db.tb_order ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT \u2018\u8ba2\u5355ID\u2019, order_no VARCHAR(32) NOT NULL COMMENT \u2018\u8ba2\u5355\u53f7\u2019, user_id BIGINT NOT NULL COMMENT \u2018\u7528\u6237ID\u2019, total_amount DECIMAL(10,2) NOT NULL COMMENT \u2018\u8ba2\u5355\u603b\u91d1\u989d\u2019, discount_amount DECIMAL(10,2) DEFAULT 0 COMMENT \u2018\u4f18\u60e0\u91d1\u989d\u2019, shipping_amount DECIMAL(10,2) DEFAULT 0 COMMENT \u2018\u8fd0\u8d39\u2019, pay_amount DECIMAL(10,2) NOT NULL COMMENT \u2018\u5b9e\u4ed8\u91d1\u989d\u2019, payment_type TINYINT COMMENT \u2018\u652f\u4ed8\u65b9\u5f0f 1-\u5fae\u4fe1 2-\u652f\u4ed8\u5b9d\u2019, status TINYINT NOT NULL COMMENT \u2018\u8ba2\u5355\u72b6\u6001 1-\u5f85\u4ed8\u6b3e 2-\u5f85\u53d1\u8d27 3-\u5df2\u53d1\u8d27 4-\u5df2\u5b8c\u6210 5-\u5df2\u53d6\u6d88\u2019, payment_time DATETIME COMMENT \u2018\u652f\u4ed8\u65f6\u95f4\u2019, shipping_time DATETIME COMMENT \u2018\u53d1\u8d27\u65f6\u95f4\u2019, receive_time DATETIME COMMENT \u2018\u6536\u8d27\u65f6\u95f4\u2019, cancel_time DATETIME COMMENT \u2018\u53d6\u6d88\u65f6\u95f4\u2019, cancel_reason VARCHAR(200) COMMENT \u2018\u53d6\u6d88\u539f\u56e0\u2019, remark VARCHAR(500) COMMENT \u2018\u8ba2\u5355\u5907\u6ce8\u2019, receiver_name VARCHAR(50) NOT NULL COMMENT \u2018\u6536\u8d27\u4eba\u59d3\u540d\u2019, receiver_phone VARCHAR(20) NOT NULL COMMENT \u2018\u6536\u8d27\u4eba\u7535\u8bdd\u2019, receiver_address VARCHAR(500) NOT NULL COMMENT \u2018\u6536\u8d27\u5730\u5740\u2019, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT \u2018\u521b\u5efa\u65f6\u95f4\u2019, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT \u2018\u66f4\u65b0\u65f6\u95f4\u2019, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_user_id (user_id), KEY idx_status (status), KEY idx_create_time (create_time) ) ENGINE&#061;InnoDB DEFAULT CHARSET&#061;utf8mb4 COMMENT&#061;\u2018\u8ba2\u5355\u8868\u2019;<\/p>\n<p>\u2013 \u8ba2\u5355\u5546\u54c1\u8868 CREATE TABLE order_db.tb_order_item ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT \u2018\u8ba2\u5355\u5546\u54c1ID\u2019, order_id BIGINT NOT NULL COMMENT \u2018\u8ba2\u5355ID\u2019, product_id BIGINT NOT NULL COMMENT \u2018\u5546\u54c1ID\u2019, product_name VARCHAR(200) NOT NULL COMMENT \u2018\u5546\u54c1\u540d\u79f0\u2019, product_image VARCHAR(500) COMMENT \u2018\u5546\u54c1\u56fe\u7247\u2019, sku_id BIGINT COMMENT \u2018SKU ID\u2019, sku_code VARCHAR(50) COMMENT \u2018SKU\u7f16\u7801\u2019, specifications VARCHAR(500) COMMENT \u2018\u89c4\u683c\u2019, price DECIMAL(10,2) NOT NULL COMMENT \u2018\u5355\u4ef7\u2019, quantity INT NOT NULL COMMENT \u2018\u6570\u91cf\u2019, total_amount DECIMAL(10,2) NOT NULL COMMENT \u2018\u603b\u91d1\u989d\u2019, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT \u2018\u521b\u5efa\u65f6\u95f4\u2019, PRIMARY KEY (id), KEY idx_order_id (order_id), KEY idx_product_id (product_id), CONSTRAINT fk_order_item_order FOREIGN KEY (order_id) REFERENCES tb_order (id) ON DELETE CASCADE ) ENGINE&#061;InnoDB DEFAULT CHARSET&#061;utf8mb4 COMMENT&#061;\u2018\u8ba2\u5355\u5546\u54c1\u8868\u2019;<\/p>\n<p>\u2013 4. \u5e93\u5b58\u6570\u636e\u5e93 (inventory_db) CREATE DATABASE IF NOT EXISTS inventory_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;<\/p>\n<p>\u2013 \u5e93\u5b58\u8868 CREATE TABLE inventory_db.tb_inventory ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT \u2018\u5e93\u5b58ID\u2019, product_id BIGINT NOT NULL COMMENT \u2018\u5546\u54c1ID\u2019, sku_id BIGINT COMMENT \u2018SKU ID\u2019, warehouse_id BIGINT NOT NULL COMMENT \u2018\u4ed3\u5e93ID\u2019, total_stock INT NOT NULL DEFAULT 0 COMMENT \u2018\u603b\u5e93\u5b58\u2019, available_stock INT NOT NULL DEFAULT 0 COMMENT \u2018\u53ef\u7528\u5e93\u5b58\u2019, locked_stock INT DEFAULT 0 COMMENT \u2018\u9501\u5b9a\u5e93\u5b58\u2019, occupied_stock INT DEFAULT 0 COMMENT \u2018\u5360\u7528\u5e93\u5b58\u2019, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT \u2018\u521b\u5efa\u65f6\u95f4\u2019, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT \u2018\u66f4\u65b0\u65f6\u95f4\u2019, PRIMARY KEY (id), UNIQUE KEY uk_product_warehouse (product_id, warehouse_id), KEY idx_sku_id (sku_id), KEY idx_warehouse_id (warehouse_id) ) ENGINE&#061;InnoDB DEFAULT CHARSET&#061;utf8mb4 COMMENT&#061;\u2018\u5e93\u5b58\u8868\u2019;<\/p>\n<p>\u2013 \u5e93\u5b58\u64cd\u4f5c\u8bb0\u5f55\u8868 CREATE TABLE inventory_db.tb_inventory_log ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT \u2018\u65e5\u5fd7ID\u2019, product_id BIGINT NOT NULL COMMENT \u2018\u5546\u54c1ID\u2019, sku_id BIGINT COMMENT \u2018SKU ID\u2019, warehouse_id BIGINT NOT NULL COMMENT \u2018\u4ed3\u5e93ID\u2019, order_no VARCHAR(32) COMMENT \u2018\u8ba2\u5355\u53f7\u2019, operation_type TINYINT NOT NULL COMMENT \u2018\u64cd\u4f5c\u7c7b\u578b 1-\u5165\u5e93 2-\u51fa\u5e93 3-\u9501\u5b9a 4-\u89e3\u9501 5-\u5360\u7528 6-\u91ca\u653e\u2019, quantity INT NOT NULL COMMENT \u2018\u64cd\u4f5c\u6570\u91cf\u2019, before_stock INT NOT NULL COMMENT \u2018\u64cd\u4f5c\u524d\u5e93\u5b58\u2019, after_stock INT NOT NULL COMMENT \u2018\u64cd\u4f5c\u540e\u5e93\u5b58\u2019, remark VARCHAR(200) COMMENT \u2018\u5907\u6ce8\u2019, operator VARCHAR(50) COMMENT \u2018\u64cd\u4f5c\u4eba\u2019, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT \u2018\u521b\u5efa\u65f6\u95f4\u2019, PRIMARY KEY (id), KEY idx_product_id (product_id), KEY idx_order_no (order_no), KEY idx_create_time (create_time) ) ENGINE&#061;InnoDB DEFAULT CHARSET&#061;utf8mb4 COMMENT&#061;\u2018\u5e93\u5b58\u64cd\u4f5c\u8bb0\u5f55\u8868\u2019;<\/p>\n<h3>\u7b2c\u4e09\u7ae0&#xff1a;\u7528\u6237\u670d\u52a1\u5b9e\u73b0<\/h3>\n<p>3.1 \u7528\u6237\u670d\u52a1\u6838\u5fc3\u529f\u80fd \u7528\u6237\u670d\u52a1pom.xml&#xff1a;<\/p>\n<p>xml<\/p>\n<p> &lt;?xml version&#061;&#034;1.0&#034; encoding&#061;&#034;UTF-8&#034;?&gt; <\/p>\n<p>&lt;modelVersion&gt;4.0.0&lt;\/modelVersion&gt;<\/p>\n<p>&lt;parent&gt;<br \/>\n    &lt;groupId&gt;com.ecommerce&lt;\/groupId&gt;<br \/>\n    &lt;artifactId&gt;e-commerce-microservices&lt;\/artifactId&gt;<br \/>\n    &lt;version&gt;1.0.0&lt;\/version&gt;<br \/>\n&lt;\/parent&gt;<\/p>\n<p>&lt;artifactId&gt;e-commerce-user&lt;\/artifactId&gt;<br \/>\n&lt;name&gt;e-commerce-user&lt;\/name&gt;<br \/>\n&lt;description&gt;\u7528\u6237\u670d\u52a1&lt;\/description&gt;<\/p>\n<p>&lt;properties&gt;<br \/>\n    &lt;service.port&gt;8082&lt;\/service.port&gt;<br \/>\n&lt;\/properties&gt;<\/p>\n<p>&lt;dependencies&gt;<br \/>\n    &lt;!&#8211; \u516c\u5171\u6a21\u5757 &#8211;&gt;<br \/>\n    &lt;dependency&gt;<br \/>\n        &lt;groupId&gt;com.ecommerce&lt;\/groupId&gt;<br \/>\n        &lt;artifactId&gt;common-core&lt;\/artifactId&gt;<br \/>\n        &lt;version&gt;${project.version}&lt;\/version&gt;<br \/>\n    &lt;\/dependency&gt;<\/p>\n<p>    &lt;dependency&gt;<br \/>\n        &lt;groupId&gt;com.ecommerce&lt;\/groupId&gt;<br \/>\n        &lt;artifactId&gt;common-dto&lt;\/artifactId&gt;<br \/>\n        &lt;version&gt;${project.version}&lt;\/version&gt;<br \/>\n    &lt;\/dependency&gt;<\/p>\n<p>    &lt;!&#8211; Spring Boot &#8211;&gt;<br \/>\n    &lt;dependency&gt;<br \/>\n        &lt;groupId&gt;org.springframework.boot&lt;\/groupId&gt;<br \/>\n        &lt;artifactId&gt;spring-boot-starter-web&lt;\/artifactId&gt;<br \/>\n    &lt;\/dependency&gt;<\/p>\n<p>    &lt;dependency&gt;<br \/>\n        &lt;groupId&gt;org.springframework.boot&lt;\/groupId&gt;<br \/>\n        &lt;artifactId&gt;spring-boot-starter-validation&lt;\/artifactId&gt;<br \/>\n    &lt;\/dependency&gt;<\/p>\n<p>    &lt;!&#8211; Spring Cloud &#8211;&gt;<br \/>\n    &lt;dependency&gt;<br \/>\n        &lt;groupId&gt;org.springframework.cloud&lt;\/groupId&gt;<br \/>\n        &lt;artifactId&gt;spring-cloud-starter-bootstrap&lt;\/artifactId&gt;<br \/>\n    &lt;\/dependency&gt;<\/p>\n<p>    &lt;dependency&gt;<br \/>\n        &lt;groupId&gt;com.alibaba.cloud&lt;\/groupId&gt;<br \/>\n        &lt;artifactId&gt;spring-cloud-starter-alibaba-nacos-discovery&lt;\/artifactId&gt;<br \/>\n    &lt;\/dependency&gt;<\/p>\n<p>    &lt;dependency&gt;<br \/>\n        &lt;groupId&gt;com.alibaba.cloud&lt;\/groupId&gt;<br \/>\n        &lt;artifactId&gt;spring-cloud-starter-alibaba-nacos-config&lt;\/artifactId&gt;<br \/>\n    &lt;\/dependency&gt;<\/p>\n<p>    &lt;!&#8211; \u6301\u4e45\u5c42 &#8211;&gt;<br \/>\n    &lt;dependency&gt;<br \/>\n        &lt;groupId&gt;com.baomidou&lt;\/groupId&gt;<br \/>\n        &lt;artifactId&gt;mybatis-plus-boot-starter&lt;\/artifactId&gt;<br \/>\n    &lt;\/dependency&gt;<\/p>\n<p>    &lt;dependency&gt;<br \/>\n        &lt;groupId&gt;com.alibaba&lt;\/groupId&gt;<br \/>\n        &lt;artifactId&gt;druid-spring-boot-starter&lt;\/artifactId&gt;<br \/>\n    &lt;\/dependency&gt;<\/p>\n<p>    &lt;!&#8211; \u6570\u636e\u5e93\u9a71\u52a8 &#8211;&gt;<br \/>\n    &lt;dependency&gt;<br \/>\n        &lt;groupId&gt;com.mysql&lt;\/groupId&gt;<br \/>\n        &lt;artifactId&gt;mysql-connector-j&lt;\/artifactId&gt;<br \/>\n        &lt;scope&gt;runtime&lt;\/scope&gt;<br \/>\n    &lt;\/dependency&gt;<\/p>\n<p>    &lt;!&#8211; Redis &#8211;&gt;<br \/>\n    &lt;dependency&gt;<br \/>\n        &lt;groupId&gt;org.springframework.boot&lt;\/groupId&gt;<br \/>\n        &lt;artifactId&gt;spring-boot-starter-data-redis&lt;\/artifactId&gt;<br \/>\n    &lt;\/dependency&gt;<\/p>\n<p>    &lt;!&#8211; JWT &#8211;&gt;<br \/>\n    &lt;dependency&gt;<br \/>\n        &lt;groupId&gt;io.jsonwebtoken&lt;\/groupId&gt;<br \/>\n        &lt;artifactId&gt;jjwt-api&lt;\/artifactId&gt;<br \/>\n        &lt;version&gt;0.11.5&lt;\/version&gt;<br \/>\n    &lt;\/dependency&gt;<\/p>\n<p>    &lt;dependency&gt;<br \/>\n        &lt;groupId&gt;io.jsonwebtoken&lt;\/groupId&gt;<br \/>\n        &lt;artifactId&gt;jjwt-impl&lt;\/artifactId&gt;<br \/>\n        &lt;version&gt;0.11.5&lt;\/version&gt;<br \/>\n        &lt;scope&gt;runtime&lt;\/scope&gt;<br \/>\n    &lt;\/dependency&gt;<\/p>\n<p>    &lt;dependency&gt;<br \/>\n        &lt;groupId&gt;io.jsonwebtoken&lt;\/groupId&gt;<br \/>\n        &lt;artifactId&gt;jjwt-jackson&lt;\/artifactId&gt;<br \/>\n        &lt;version&gt;0.11.5&lt;\/version&gt;<br \/>\n        &lt;scope&gt;runtime&lt;\/scope&gt;<br \/>\n    &lt;\/dependency&gt;<\/p>\n<p>    &lt;!&#8211; \u76d1\u63a7 &#8211;&gt;<br \/>\n    &lt;dependency&gt;<br \/>\n        &lt;groupId&gt;org.springframework.boot&lt;\/groupId&gt;<br \/>\n        &lt;artifactId&gt;spring-boot-starter-actuator&lt;\/artifactId&gt;<br \/>\n    &lt;\/dependency&gt;<\/p>\n<p>    &lt;!&#8211; \u6d4b\u8bd5 &#8211;&gt;<br \/>\n    &lt;dependency&gt;<br \/>\n        &lt;groupId&gt;org.springframework.boot&lt;\/groupId&gt;<br \/>\n        &lt;artifactId&gt;spring-boot-starter-test&lt;\/artifactId&gt;<br \/>\n        &lt;scope&gt;test&lt;\/scope&gt;<br \/>\n    &lt;\/dependency&gt;<br \/>\n&lt;\/dependencies&gt;<\/p>\n<p>&lt;build&gt;<br \/>\n    &lt;plugins&gt;<br \/>\n        &lt;plugin&gt;<br \/>\n            &lt;groupId&gt;org.springframework.boot&lt;\/groupId&gt;<br \/>\n            &lt;artifactId&gt;spring-boot-maven-plugin&lt;\/artifactId&gt;<br \/>\n        &lt;\/plugin&gt;<br \/>\n    &lt;\/plugins&gt;<br \/>\n&lt;\/build&gt;<br \/>\n \u7528\u6237\u670d\u52a1\u914d\u7f6e\u6587\u4ef6&#xff1a; <\/p>\n<p>yaml<\/p>\n<h2>bootstrap.yml<\/h2>\n<p>spring: application: name: user-service<\/p>\n<p>profiles: active: dev<\/p>\n<p>cloud: nacos: discovery: server-addr: <span class=\"katex--inline\"><span class=\"katex\"><span class=\"katex-mathml\"><\/p>\n<p>         N<\/p>\n<p>         A<\/p>\n<p>         C<\/p>\n<p>         O<\/p>\n<p>          S<\/p>\n<p>          H<\/p>\n<p>         O<\/p>\n<p>         S<\/p>\n<p>         T<\/p>\n<p>         :<\/p>\n<p>         l<\/p>\n<p>         o<\/p>\n<p>         c<\/p>\n<p>         a<\/p>\n<p>         l<\/p>\n<p>         h<\/p>\n<p>         o<\/p>\n<p>         s<\/p>\n<p>         t<\/p>\n<p>        :<\/p>\n<p>       {NACOS_HOST:localhost}:<\/p>\n<p>    <\/span><span class=\"katex-html\"><span class=\"base\"><span class=\"strut\" style=\"height: 0.8444em;vertical-align: -0.15em\"><\/span><span class=\"mord\"><span class=\"mord mathnormal\" style=\"margin-right: 0.109em\">N<\/span><span class=\"mord mathnormal\">A<\/span><span class=\"mord mathnormal\" style=\"margin-right: 0.0278em\">CO<\/span><span class=\"mord\"><span class=\"mord mathnormal\" style=\"margin-right: 0.0576em\">S<\/span><span class=\"msupsub\"><span class=\"vlist-t vlist-t2\"><span class=\"vlist-r\"><span class=\"vlist\" style=\"height: 0.3283em\"><span class=\"\" style=\"top: -2.55em;margin-left: -0.0576em;margin-right: 0.05em\"><span class=\"pstrut\" style=\"height: 2.7em\"><\/span><span class=\"sizing reset-size6 size3 mtight\"><span class=\"mord mathnormal mtight\" style=\"margin-right: 0.0813em\">H<\/span><\/span><\/span><\/span><span class=\"vlist-s\">\u200b<\/span><\/span><span class=\"vlist-r\"><span class=\"vlist\" style=\"height: 0.15em\"><span class=\"\"><\/span><\/span><\/span><\/span><\/span><\/span><span class=\"mord mathnormal\" style=\"margin-right: 0.1389em\">OST<\/span><span class=\"mspace\" style=\"margin-right: 0.2778em\"><\/span><span class=\"mrel\">:<\/span><span class=\"mspace\" style=\"margin-right: 0.2778em\"><\/span><span class=\"mord mathnormal\" style=\"margin-right: 0.0197em\">l<\/span><span class=\"mord mathnormal\">oc<\/span><span class=\"mord mathnormal\">a<\/span><span class=\"mord mathnormal\" style=\"margin-right: 0.0197em\">l<\/span><span class=\"mord mathnormal\">h<\/span><span class=\"mord mathnormal\">os<\/span><span class=\"mord mathnormal\">t<\/span><\/span><span class=\"mspace\" style=\"margin-right: 0.2778em\"><\/span><span class=\"mrel\">:<\/span><\/span><\/span><\/span><\/span>{NACOS_PORT:8848} namespace: ${NACOS_NAMESPACE:public} group: ${NACOS_GROUP:DEFAULT_GROUP} metadata: version: 1.0.0<\/p>\n<p>  config:<br \/>\n    server-addr: ${spring.cloud.nacos.discovery.server-addr}<br \/>\n    namespace: ${spring.cloud.nacos.discovery.namespace}<br \/>\n    group: ${spring.cloud.nacos.discovery.group}<br \/>\n    file-extension: yaml<br \/>\n    shared-configs:<br \/>\n      &#8211; data-id: common.yaml<br \/>\n        group: ${spring.cloud.nacos.discovery.group}<br \/>\n        refresh: true<br \/>\n      &#8211; data-id: datasource.yaml<br \/>\n        group: ${spring.cloud.nacos.discovery.group}<br \/>\n        refresh: true<br \/>\n      &#8211; data-id: redis.yaml<br \/>\n        group: ${spring.cloud.nacos.discovery.group}<br \/>\n        refresh: true<\/p>\n<h2>application-dev.yml<\/h2>\n<p>server: port: 8082 servlet: context-path: \/user<\/p>\n<p>spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql:\/\/localhost:3306\/user_db?useUnicode&#061;true&amp;characterEncoding&#061;utf8&amp;useSSL&#061;false&amp;serverTimezone&#061;Asia\/Shanghai username: root password: 123456 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<\/p>\n<p>redis: host: localhost port: 6379 password: database: 0 lettuce: pool: max-active: 20 max-wait: -1ms max-idle: 10 min-idle: 0 shutdown-timeout: 100ms<\/p>\n<p>mybatis-plus: mapper-locations: classpath:\/mapper\/*.xml type-aliases-package: com.ecommerce.user.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: id-type: auto logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0<\/p>\n<h2>JWT\u914d\u7f6e<\/h2>\n<p>jwt: secret: ecommerce-user-secret-key-2023-spring-cloud-microservices expiration: 86400000 # 24\u5c0f\u65f6 header: Authorization token-prefix: Bearer<\/p>\n<h2>\u7528\u6237\u670d\u52a1\u914d\u7f6e<\/h2>\n<p>user: password: salt: ecommerce2023 max-retry-count: 5 lock-duration: 30 # \u5206\u949f<\/p>\n<h2>\u65e5\u5fd7\u914d\u7f6e<\/h2>\n<p>logging: level: com.ecommerce.user: DEBUG org.springframework.cloud: INFO pattern: console: \u201c%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} &#8211; %msg%n\u201d file: name: logs\/user-service.log max-size: 10MB max-history: 30<\/p>\n<h2>\u7ba1\u7406\u7aef\u70b9<\/h2>\n<p>management: endpoints: web: exposure: include: \u201chealth,info,metrics,prometheus\u201d endpoint: health: show-details: always probes: enabled: true \u7528\u6237\u670d\u52a1\u6838\u5fc3\u4ee3\u7801&#xff1a;<\/p>\n<li>\u5b9e\u4f53\u7c7b&#xff1a;<\/li>\n<p>java \/\/ \u7528\u6237\u5b9e\u4f53 package com.ecommerce.user.entity;<\/p>\n<p>import com.baomidou.mybatisplus.annotation.*; import lombok.Data;<\/p>\n<p>import java.time.LocalDateTime;<\/p>\n<p>&#064;Data &#064;TableName(\u201ctb_user\u201d) public class User {<\/p>\n<p>&#064;TableId(type &#061; IdType.AUTO)<br \/>\nprivate Long id;<\/p>\n<p>private String username;<br \/>\nprivate String password;<br \/>\nprivate String nickname;<br \/>\nprivate String phone;<br \/>\nprivate String email;<br \/>\nprivate String avatar;<br \/>\nprivate Integer gender;<br \/>\nprivate LocalDateTime birthday;<br \/>\nprivate Integer status;<br \/>\nprivate Integer userType;<br \/>\nprivate LocalDateTime lastLoginTime;<\/p>\n<p>&#064;TableField(fill &#061; FieldFill.INSERT)<br \/>\nprivate LocalDateTime createTime;<\/p>\n<p>&#064;TableField(fill &#061; FieldFill.INSERT_UPDATE)<br \/>\nprivate LocalDateTime updateTime;<\/p>\n<p>&#064;TableLogic<br \/>\nprivate Integer deleted;<\/p>\n<p>}<\/p>\n<p>\/\/ \u7528\u6237\u5730\u5740\u5b9e\u4f53 package com.ecommerce.user.entity;<\/p>\n<p>import com.baomidou.mybatisplus.annotation.*; import lombok.Data;<\/p>\n<p>import java.time.LocalDateTime;<\/p>\n<p>&#064;Data &#064;TableName(\u201ctb_user_address\u201d) public class UserAddress {<\/p>\n<p>&#064;TableId(type &#061; IdType.AUTO)<br \/>\nprivate Long id;<\/p>\n<p>private Long userId;<br \/>\nprivate String receiverName;<br \/>\nprivate String receiverPhone;<br \/>\nprivate String province;<br \/>\nprivate String city;<br \/>\nprivate String district;<br \/>\nprivate String detail;<br \/>\nprivate String postalCode;<br \/>\nprivate Integer isDefault;<br \/>\nprivate Integer status;<\/p>\n<p>&#064;TableField(fill &#061; FieldFill.INSERT)<br \/>\nprivate LocalDateTime createTime;<\/p>\n<p>&#064;TableField(fill &#061; FieldFill.INSERT_UPDATE)<br \/>\nprivate LocalDateTime updateTime;<\/p>\n<p>} 2. Mapper\u63a5\u53e3&#xff1a;<\/p>\n<p>java \/\/ \u7528\u6237Mapper package com.ecommerce.user.mapper;<\/p>\n<p>import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.ecommerce.user.entity.User; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select;<\/p>\n<p>&#064;Mapper public interface UserMapper extends BaseMapper {<\/p>\n<p>&#064;Select(&#034;SELECT * FROM tb_user WHERE username &#061; #{username} AND deleted &#061; 0&#034;)<br \/>\nUser selectByUsername(String username);<\/p>\n<p>&#064;Select(&#034;SELECT * FROM tb_user WHERE phone &#061; #{phone} AND deleted &#061; 0&#034;)<br \/>\nUser selectByPhone(String phone);<\/p>\n<p>&#064;Select(&#034;SELECT * FROM tb_user WHERE email &#061; #{email} AND deleted &#061; 0&#034;)<br \/>\nUser selectByEmail(String email);<\/p>\n<p>}<\/p>\n<p>\/\/ \u7528\u6237\u5730\u5740Mapper package com.ecommerce.user.mapper;<\/p>\n<p>import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.ecommerce.user.entity.UserAddress; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select;<\/p>\n<p>import java.util.List;<\/p>\n<p>&#064;Mapper public interface UserAddressMapper extends BaseMapper {<\/p>\n<p>&#064;Select(&#034;SELECT * FROM tb_user_address WHERE user_id &#061; #{userId} AND status &#061; 1 ORDER BY is_default DESC, create_time DESC&#034;)<br \/>\nList&lt;UserAddress&gt; selectByUserId(Long userId);<\/p>\n<p>&#064;Select(&#034;SELECT * FROM tb_user_address WHERE user_id &#061; #{userId} AND is_default &#061; 1 AND status &#061; 1&#034;)<br \/>\nUserAddress selectDefaultByUserId(Long userId);<\/p>\n<p>} 3. Service\u5c42&#xff1a;<\/p>\n<p>java \/\/ \u7528\u6237\u670d\u52a1\u63a5\u53e3 package com.ecommerce.user.service;<\/p>\n<p>import com.ecommerce.common.dto.PageRequest; import com.ecommerce.common.dto.PageResult; import com.ecommerce.user.dto.*;<\/p>\n<p>public interface UserService {<\/p>\n<p>\/\/ \u7528\u6237\u6ce8\u518c<br \/>\nUserDTO register(UserRegisterDTO registerDTO);<\/p>\n<p>\/\/ \u7528\u6237\u767b\u5f55<br \/>\nUserLoginResult login(UserLoginDTO loginDTO);<\/p>\n<p>\/\/ \u83b7\u53d6\u7528\u6237\u4fe1\u606f<br \/>\nUserDTO getUserInfo(Long userId);<\/p>\n<p>\/\/ \u66f4\u65b0\u7528\u6237\u4fe1\u606f<br \/>\nUserDTO updateUserInfo(Long userId, UserUpdateDTO updateDTO);<\/p>\n<p>\/\/ \u4fee\u6539\u5bc6\u7801<br \/>\nvoid changePassword(Long userId, ChangePasswordDTO changePasswordDTO);<\/p>\n<p>\/\/ \u5206\u9875\u67e5\u8be2\u7528\u6237<br \/>\nPageResult&lt;UserDTO&gt; listUsers(PageRequest pageRequest, UserQueryDTO queryDTO);<\/p>\n<p>\/\/ \u68c0\u67e5\u7528\u6237\u540d\u662f\u5426\u5b58\u5728<br \/>\nboolean checkUsername(String username);<\/p>\n<p>\/\/ \u68c0\u67e5\u624b\u673a\u53f7\u662f\u5426\u5b58\u5728<br \/>\nboolean checkPhone(String phone);<\/p>\n<p>\/\/ \u68c0\u67e5\u90ae\u7bb1\u662f\u5426\u5b58\u5728<br \/>\nboolean checkEmail(String email);<\/p>\n<p>}<\/p>\n<p>\/\/ \u7528\u6237\u670d\u52a1\u5b9e\u73b0 package com.ecommerce.user.service.impl;<\/p>\n<p>import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ecommerce.common.core.exception.BusinessException; import com.ecommerce.common.core.exception.ErrorCode; import com.ecommerce.common.dto.PageRequest; import com.ecommerce.common.dto.PageResult; import com.ecommerce.user.dto.*; import com.ecommerce.user.entity.User; import com.ecommerce.user.mapper.UserMapper; import com.ecommerce.user.service.UserService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional;<\/p>\n<p>import java.time.LocalDateTime; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors;<\/p>\n<p>&#064;Slf4j &#064;Service &#064;RequiredArgsConstructor &#064;Transactional(rollbackFor &#061; Exception.class) public class UserServiceImpl implements UserService {<\/p>\n<p>private final UserMapper userMapper;<br \/>\nprivate final PasswordEncoder passwordEncoder;<br \/>\nprivate final RedisTemplate&lt;String, Object&gt; redisTemplate;<br \/>\nprivate final UserMapper userMapper;<\/p>\n<p>&#064;Override<br \/>\npublic UserDTO register(UserRegisterDTO registerDTO) {<br \/>\n    log.info(&#034;\u7528\u6237\u6ce8\u518c: username&#061;{}, phone&#061;{}&#034;,<br \/>\n            registerDTO.getUsername(), registerDTO.getPhone());<\/p>\n<p>    \/\/ 1. \u9a8c\u8bc1\u53c2\u6570<br \/>\n    validateRegisterDTO(registerDTO);<\/p>\n<p>    \/\/ 2. \u68c0\u67e5\u7528\u6237\u540d\u3001\u624b\u673a\u53f7\u3001\u90ae\u7bb1\u662f\u5426\u5df2\u5b58\u5728<br \/>\n    if (checkUsername(registerDTO.getUsername())) {<br \/>\n        throw new BusinessException(ErrorCode.USER_EXISTS);<br \/>\n    }<\/p>\n<p>    if (StrUtil.isNotBlank(registerDTO.getPhone()) &amp;&amp;<br \/>\n        checkPhone(registerDTO.getPhone())) {<br \/>\n        throw new BusinessException(&#034;\u624b\u673a\u53f7\u5df2\u5b58\u5728&#034;);<br \/>\n    }<\/p>\n<p>    if (StrUtil.isNotBlank(registerDTO.getEmail()) &amp;&amp;<br \/>\n        checkEmail(registerDTO.getEmail())) {<br \/>\n        throw new BusinessException(&#034;\u90ae\u7bb1\u5df2\u5b58\u5728&#034;);<br \/>\n    }<\/p>\n<p>    \/\/ 3. \u521b\u5efa\u7528\u6237<br \/>\n    User user &#061; new User();<br \/>\n    BeanUtil.copyProperties(registerDTO, user);<\/p>\n<p>    \/\/ \u5bc6\u7801\u52a0\u5bc6<br \/>\n    user.setPassword(passwordEncoder.encode(registerDTO.getPassword()));<\/p>\n<p>    \/\/ \u8bbe\u7f6e\u9ed8\u8ba4\u503c<br \/>\n    user.setStatus(1);  \/\/ \u6b63\u5e38\u72b6\u6001<br \/>\n    user.setUserType(1); \/\/ \u666e\u901a\u7528\u6237<br \/>\n    user.setCreateTime(LocalDateTime.now());<br \/>\n    user.setUpdateTime(LocalDateTime.now());<\/p>\n<p>    \/\/ 4. \u4fdd\u5b58\u7528\u6237<br \/>\n    userMapper.insert(user);<br \/>\n    log.info(&#034;\u7528\u6237\u6ce8\u518c\u6210\u529f: userId&#061;{}, username&#061;{}&#034;, user.getId(), user.getUsername());<\/p>\n<p>    \/\/ 5. \u6e05\u9664\u76f8\u5173\u7f13\u5b58<br \/>\n    clearUserCache(user.getId(), user.getUsername());<\/p>\n<p>    return convertToDTO(user);<br \/>\n}<\/p>\n<p>&#064;Override<br \/>\npublic UserLoginResult login(UserLoginDTO loginDTO) {<br \/>\n    log.info(&#034;\u7528\u6237\u767b\u5f55: identifier&#061;{}&#034;, loginDTO.getIdentifier());<\/p>\n<p>    \/\/ 1. \u83b7\u53d6\u7528\u6237<br \/>\n    User user &#061; getUserByIdentifier(loginDTO.getIdentifier());<br \/>\n    if (user &#061;&#061; null) {<br \/>\n        throw new BusinessException(ErrorCode.USER_NOT_EXIST);<br \/>\n    }<\/p>\n<p>    \/\/ 2. \u68c0\u67e5\u7528\u6237\u72b6\u6001<br \/>\n    if (user.getStatus() !&#061; 1) {<br \/>\n        throw new BusinessException(ErrorCode.USER_DISABLED);<br \/>\n    }<\/p>\n<p>    \/\/ 3. \u9a8c\u8bc1\u5bc6\u7801<br \/>\n    if (!passwordEncoder.matches(loginDTO.getPassword(), user.getPassword())) {<br \/>\n        \/\/ \u8bb0\u5f55\u767b\u5f55\u5931\u8d25\u6b21\u6570<br \/>\n        recordLoginFailure(user.getId());<br \/>\n        throw new BusinessException(ErrorCode.USER_PASSWORD_ERROR);<br \/>\n    }<\/p>\n<p>    \/\/ 4. \u6e05\u9664\u767b\u5f55\u5931\u8d25\u8bb0\u5f55<br \/>\n    clearLoginFailure(user.getId());<\/p>\n<p>    \/\/ 5. \u66f4\u65b0\u6700\u540e\u767b\u5f55\u65f6\u95f4<br \/>\n    user.setLastLoginTime(LocalDateTime.now());<br \/>\n    userMapper.updateById(user);<\/p>\n<p>    \/\/ 6. \u751f\u6210token<br \/>\n    String token &#061; generateToken(user);<\/p>\n<p>    \/\/ 7. \u8fd4\u56de\u7ed3\u679c<br \/>\n    UserLoginResult result &#061; new UserLoginResult();<br \/>\n    result.setUser(convertToDTO(user));<br \/>\n    result.setToken(token);<br \/>\n    result.setExpireTime(System.currentTimeMillis() &#043; 86400000); \/\/ 24\u5c0f\u65f6<\/p>\n<p>    log.info(&#034;\u7528\u6237\u767b\u5f55\u6210\u529f: userId&#061;{}, username&#061;{}&#034;, user.getId(), user.getUsername());<br \/>\n    return result;<br \/>\n}<\/p>\n<p>&#064;Override<br \/>\n&#064;Transactional(readOnly &#061; true)<br \/>\npublic UserDTO getUserInfo(Long userId) {<br \/>\n    log.debug(&#034;\u83b7\u53d6\u7528\u6237\u4fe1\u606f: userId&#061;{}&#034;, userId);<\/p>\n<p>    \/\/ \u5148\u4ece\u7f13\u5b58\u83b7\u53d6<br \/>\n    String cacheKey &#061; &#034;user:info:&#034; &#043; userId;<br \/>\n    UserDTO cachedUser &#061; (UserDTO) redisTemplate.opsForValue().get(cacheKey);<br \/>\n    if (cachedUser !&#061; null) {<br \/>\n        return cachedUser;<br \/>\n    }<\/p>\n<p>    \/\/ \u4ece\u6570\u636e\u5e93\u83b7\u53d6<br \/>\n    User user &#061; userMapper.selectById(userId);<br \/>\n    if (user &#061;&#061; null || user.getDeleted() &#061;&#061; 1) {<br \/>\n        throw new BusinessException(ErrorCode.USER_NOT_EXIST);<br \/>\n    }<\/p>\n<p>    UserDTO userDTO &#061; convertToDTO(user);<\/p>\n<p>    \/\/ \u5b58\u5165\u7f13\u5b58<br \/>\n    redisTemplate.opsForValue().set(cacheKey, userDTO, 30, TimeUnit.MINUTES);<\/p>\n<p>    return userDTO;<br \/>\n}<\/p>\n<p>&#064;Override<br \/>\npublic PageResult&lt;UserDTO&gt; listUsers(PageRequest pageRequest, UserQueryDTO queryDTO) {<br \/>\n    log.debug(&#034;\u5206\u9875\u67e5\u8be2\u7528\u6237: pageNum&#061;{}, pageSize&#061;{}&#034;,<br \/>\n             pageRequest.getPageNum(), pageRequest.getPageSize());<\/p>\n<p>    \/\/ \u6784\u5efa\u67e5\u8be2\u6761\u4ef6<br \/>\n    LambdaQueryWrapper&lt;User&gt; wrapper &#061; new LambdaQueryWrapper&lt;&gt;();<br \/>\n    wrapper.eq(User::getDeleted, 0);<\/p>\n<p>    if (StrUtil.isNotBlank(queryDTO.getUsername())) {<br \/>\n        wrapper.like(User::getUsername, queryDTO.getUsername());<br \/>\n    }<\/p>\n<p>    if (StrUtil.isNotBlank(queryDTO.getPhone())) {<br \/>\n        wrapper.like(User::getPhone, queryDTO.getPhone());<br \/>\n    }<\/p>\n<p>    if (queryDTO.getStatus() !&#061; null) {<br \/>\n        wrapper.eq(User::getStatus, queryDTO.getStatus());<br \/>\n    }<\/p>\n<p>    if (queryDTO.getUserType() !&#061; null) {<br \/>\n        wrapper.eq(User::getUserType, queryDTO.getUserType());<br \/>\n    }<\/p>\n<p>    \/\/ \u6392\u5e8f<br \/>\n    if (StrUtil.isNotBlank(pageRequest.getOrderBy())) {<br \/>\n        if (pageRequest.getAsc()) {<br \/>\n            wrapper.orderByAsc(User::getCreateTime);<br \/>\n        } else {<br \/>\n            wrapper.orderByDesc(User::getCreateTime);<br \/>\n        }<br \/>\n    } else {<br \/>\n        wrapper.orderByDesc(User::getCreateTime);<br \/>\n    }<\/p>\n<p>    \/\/ \u5206\u9875\u67e5\u8be2<br \/>\n    Page&lt;User&gt; page &#061; new Page&lt;&gt;(pageRequest.getPageNum(), pageRequest.getPageSize());<br \/>\n    Page&lt;User&gt; userPage &#061; userMapper.selectPage(page, wrapper);<\/p>\n<p>    \/\/ \u8f6c\u6362\u4e3aDTO<br \/>\n    List&lt;UserDTO&gt; userDTOs &#061; userPage.getRecords().stream()<br \/>\n            .map(this::convertToDTO)<br \/>\n            .collect(Collectors.toList());<\/p>\n<p>    return PageResult.of(<br \/>\n            (int) userPage.getCurrent(),<br \/>\n            (int) userPage.getSize(),<br \/>\n            userPage.getTotal(),<br \/>\n            userDTOs<br \/>\n    );<br \/>\n}<\/p>\n<p>private User getUserByIdentifier(String identifier) {<br \/>\n    \/\/ \u6839\u636e\u7528\u6237\u540d\u3001\u624b\u673a\u53f7\u3001\u90ae\u7bb1\u83b7\u53d6\u7528\u6237<br \/>\n    User user &#061; userMapper.selectByUsername(identifier);<br \/>\n    if (user &#061;&#061; null) {<br \/>\n        user &#061; userMapper.selectByPhone(identifier);<br \/>\n    }<br \/>\n    if (user &#061;&#061; null) {<br \/>\n        user &#061; userMapper.selectByEmail(identifier);<br \/>\n    }<br \/>\n    return user;<br \/>\n}<\/p>\n<p>private void validateRegisterDTO(UserRegisterDTO registerDTO) {<br \/>\n    if (StrUtil.isBlank(registerDTO.getUsername())) {<br \/>\n        throw new BusinessException(&#034;\u7528\u6237\u540d\u4e0d\u80fd\u4e3a\u7a7a&#034;);<br \/>\n    }<\/p>\n<p>    if (StrUtil.isBlank(registerDTO.getPassword())) {<br \/>\n        throw new BusinessException(&#034;\u5bc6\u7801\u4e0d\u80fd\u4e3a\u7a7a&#034;);<br \/>\n    }<\/p>\n<p>    if (StrUtil.isBlank(registerDTO.getPhone())) {<br \/>\n        throw new BusinessException(&#034;\u624b\u673a\u53f7\u4e0d\u80fd\u4e3a\u7a7a&#034;);<br \/>\n    }<\/p>\n<p>    \/\/ \u9a8c\u8bc1\u624b\u673a\u53f7\u683c\u5f0f<br \/>\n    if (!registerDTO.getPhone().matches(&#034;^1[3-9]\\\\\\\\d{9}$&#034;)) {<br \/>\n        throw new BusinessException(&#034;\u624b\u673a\u53f7\u683c\u5f0f\u4e0d\u6b63\u786e&#034;);<br \/>\n    }<\/p>\n<p>    \/\/ \u9a8c\u8bc1\u5bc6\u7801\u5f3a\u5ea6<br \/>\n    if (registerDTO.getPassword().length() &lt; 6) {<br \/>\n        throw new BusinessException(&#034;\u5bc6\u7801\u957f\u5ea6\u4e0d\u80fd\u5c11\u4e8e6\u4f4d&#034;);<br \/>\n    }<br \/>\n}<\/p>\n<p>private void recordLoginFailure(Long userId) {<br \/>\n    String key &#061; &#034;user:login:fail:&#034; &#043; userId;<br \/>\n    Long failCount &#061; redisTemplate.opsForValue().increment(key);<br \/>\n    redisTemplate.expire(key, 30, TimeUnit.MINUTES);<\/p>\n<p>    \/\/ \u5982\u679c\u5931\u8d25\u6b21\u6570\u8d85\u8fc75\u6b21&#xff0c;\u9501\u5b9a\u8d26\u623730\u5206\u949f<br \/>\n    if (failCount !&#061; null &amp;&amp; failCount &gt;&#061; 5) {<br \/>\n        String lockKey &#061; &#034;user:lock:&#034; &#043; userId;<br \/>\n        redisTemplate.opsForValue().set(lockKey, &#034;locked&#034;, 30, TimeUnit.MINUTES);<br \/>\n        log.warn(&#034;\u7528\u6237\u767b\u5f55\u5931\u8d25\u6b21\u6570\u8fc7\u591a&#xff0c;\u8d26\u6237\u9501\u5b9a: userId&#061;{}, failCount&#061;{}&#034;, userId, failCount);<br \/>\n    }<br \/>\n}<\/p>\n<p>private void clearLoginFailure(Long userId) {<br \/>\n    String key &#061; &#034;user:login:fail:&#034; &#043; userId;<br \/>\n    redisTemplate.delete(key);<\/p>\n<p>    String lockKey &#061; &#034;user:lock:&#034; &#043; userId;<br \/>\n    redisTemplate.delete(lockKey);<br \/>\n}<\/p>\n<p>private String generateToken(User user) {<br \/>\n    \/\/ \u4f7f\u7528JWT\u751f\u6210token<br \/>\n    Map&lt;String, Object&gt; claims &#061; new HashMap&lt;&gt;();<br \/>\n    claims.put(&#034;userId&#034;, user.getId());<br \/>\n    claims.put(&#034;username&#034;, user.getUsername());<br \/>\n    claims.put(&#034;userType&#034;, user.getUserType());<\/p>\n<p>    return JwtUtil.generateToken(claims, user.getUsername());<br \/>\n}<\/p>\n<p>private UserDTO convertToDTO(User user) {<br \/>\n    UserDTO userDTO &#061; new UserDTO();<br \/>\n    BeanUtil.copyProperties(user, userDTO);<br \/>\n    return userDTO;<br \/>\n}<\/p>\n<p>private void clearUserCache(Long userId, String username) {<br \/>\n    \/\/ \u6e05\u9664\u7528\u6237\u4fe1\u606f\u7f13\u5b58<br \/>\n    redisTemplate.delete(&#034;user:info:&#034; &#043; userId);<br \/>\n    redisTemplate.delete(&#034;user:info:username:&#034; &#043; username);<br \/>\n}<\/p>\n<p>}<\/p>\n<p>\/\/ \u7528\u6237\u5730\u5740\u670d\u52a1 package com.ecommerce.user.service;<\/p>\n<p>import com.ecommerce.user.dto.UserAddressDTO;<\/p>\n<p>import java.util.List;<\/p>\n<p>public interface UserAddressService {<\/p>\n<p>\/\/ \u6dfb\u52a0\u5730\u5740<br \/>\nUserAddressDTO addAddress(Long userId, UserAddressDTO addressDTO);<\/p>\n<p>\/\/ \u66f4\u65b0\u5730\u5740<br \/>\nUserAddressDTO updateAddress(Long userId, Long addressId, UserAddressDTO addressDTO);<\/p>\n<p>\/\/ \u5220\u9664\u5730\u5740<br \/>\nvoid deleteAddress(Long userId, Long addressId);<\/p>\n<p>\/\/ \u83b7\u53d6\u5730\u5740\u5217\u8868<br \/>\nList&lt;UserAddressDTO&gt; listAddresses(Long userId);<\/p>\n<p>\/\/ \u83b7\u53d6\u9ed8\u8ba4\u5730\u5740<br \/>\nUserAddressDTO getDefaultAddress(Long userId);<\/p>\n<p>\/\/ \u8bbe\u7f6e\u9ed8\u8ba4\u5730\u5740<br \/>\nvoid setDefaultAddress(Long userId, Long addressId);<\/p>\n<p>} 4. \u63a7\u5236\u5668\u5c42&#xff1a;<\/p>\n<p>java \/\/ \u7528\u6237\u63a7\u5236\u5668 package com.ecommerce.user.controller;<\/p>\n<p>import com.ecommerce.common.core.response.ApiResponse; import com.ecommerce.common.dto.PageRequest; import com.ecommerce.common.dto.PageResult; import com.ecommerce.user.dto.; import com.ecommerce.user.service.UserService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.;<\/p>\n<p>&#064;Slf4j &#064;RestController &#064;RequestMapping(\u201c\/api\/user\u201d) &#064;RequiredArgsConstructor &#064;Tag(name &#061; \u201c\u7528\u6237\u7ba1\u7406\u201d, description &#061; \u201c\u7528\u6237\u7ba1\u7406\u76f8\u5173\u63a5\u53e3\u201d) public class UserController {<\/p>\n<p>private final UserService userService;<\/p>\n<p>&#064;PostMapping(&#034;\/register&#034;)<br \/>\n&#064;Operation(summary &#061; &#034;\u7528\u6237\u6ce8\u518c&#034;, description &#061; &#034;\u65b0\u7528\u6237\u6ce8\u518c\u63a5\u53e3&#034;)<br \/>\npublic ApiResponse&lt;UserDTO&gt; register(&#064;Valid &#064;RequestBody UserRegisterDTO registerDTO) {<br \/>\n    UserDTO userDTO &#061; userService.register(registerDTO);<br \/>\n    return ApiResponse.success(&#034;\u6ce8\u518c\u6210\u529f&#034;, userDTO);<br \/>\n}<\/p>\n<p>&#064;PostMapping(&#034;\/login&#034;)<br \/>\n&#064;Operation(summary &#061; &#034;\u7528\u6237\u767b\u5f55&#034;, description &#061; &#034;\u7528\u6237\u767b\u5f55\u63a5\u53e3&#034;)<br \/>\npublic ApiResponse&lt;UserLoginResult&gt; login(&#064;Valid &#064;RequestBody UserLoginDTO loginDTO) {<br \/>\n    UserLoginResult result &#061; userService.login(loginDTO);<br \/>\n    return ApiResponse.success(&#034;\u767b\u5f55\u6210\u529f&#034;, result);<br \/>\n}<\/p>\n<p>&#064;GetMapping(&#034;\/info&#034;)<br \/>\n&#064;Operation(summary &#061; &#034;\u83b7\u53d6\u7528\u6237\u4fe1\u606f&#034;, description &#061; &#034;\u83b7\u53d6\u5f53\u524d\u767b\u5f55\u7528\u6237\u4fe1\u606f&#034;)<br \/>\npublic ApiResponse&lt;UserDTO&gt; getUserInfo(&#064;RequestHeader(&#034;X-User-Id&#034;) Long userId) {<br \/>\n    UserDTO userDTO &#061; userService.getUserInfo(userId);<br \/>\n    return ApiResponse.success(userDTO);<br \/>\n}<\/p>\n<p>&#064;PutMapping(&#034;\/info&#034;)<br \/>\n&#064;Operation(summary &#061; &#034;\u66f4\u65b0\u7528\u6237\u4fe1\u606f&#034;, description &#061; &#034;\u66f4\u65b0\u7528\u6237\u57fa\u672c\u4fe1\u606f&#034;)<br \/>\npublic ApiResponse&lt;UserDTO&gt; updateUserInfo(<br \/>\n        &#064;RequestHeader(&#034;X-User-Id&#034;) Long userId,<br \/>\n        &#064;Valid &#064;RequestBody UserUpdateDTO updateDTO) {<br \/>\n    UserDTO userDTO &#061; userService.updateUserInfo(userId, updateDTO);<br \/>\n    return ApiResponse.success(&#034;\u66f4\u65b0\u6210\u529f&#034;, userDTO);<br \/>\n}<\/p>\n<p>&#064;PutMapping(&#034;\/password&#034;)<br \/>\n&#064;Operation(summary &#061; &#034;\u4fee\u6539\u5bc6\u7801&#034;, description &#061; &#034;\u4fee\u6539\u767b\u5f55\u5bc6\u7801&#034;)<br \/>\npublic ApiResponse&lt;Void&gt; changePassword(<br \/>\n        &#064;RequestHeader(&#034;X-User-Id&#034;) Long userId,<br \/>\n        &#064;Valid &#064;RequestBody ChangePasswordDTO changePasswordDTO) {<br \/>\n    userService.changePassword(userId, changePasswordDTO);<br \/>\n    return ApiResponse.success(&#034;\u5bc6\u7801\u4fee\u6539\u6210\u529f&#034;);<br \/>\n}<\/p>\n<p>&#064;GetMapping(&#034;\/list&#034;)<br \/>\n&#064;Operation(summary &#061; &#034;\u5206\u9875\u67e5\u8be2\u7528\u6237&#034;, description &#061; &#034;\u7ba1\u7406\u5458\u5206\u9875\u67e5\u8be2\u7528\u6237\u5217\u8868&#034;)<br \/>\npublic ApiResponse&lt;PageResult&lt;UserDTO&gt;&gt; listUsers(<br \/>\n        &#064;Valid PageRequest pageRequest,<br \/>\n        UserQueryDTO queryDTO) {<br \/>\n    PageResult&lt;UserDTO&gt; result &#061; userService.listUsers(pageRequest, queryDTO);<br \/>\n    return ApiResponse.success(result);<br \/>\n}<\/p>\n<p>&#064;GetMapping(&#034;\/check\/username&#034;)<br \/>\n&#064;Operation(summary &#061; &#034;\u68c0\u67e5\u7528\u6237\u540d&#034;, description &#061; &#034;\u68c0\u67e5\u7528\u6237\u540d\u662f\u5426\u5df2\u5b58\u5728&#034;)<br \/>\npublic ApiResponse&lt;Boolean&gt; checkUsername(&#064;RequestParam String username) {<br \/>\n    boolean exists &#061; userService.checkUsername(username);<br \/>\n    return ApiResponse.success(!exists);<br \/>\n}<\/p>\n<p>&#064;GetMapping(&#034;\/check\/phone&#034;)<br \/>\n&#064;Operation(summary &#061; &#034;\u68c0\u67e5\u624b\u673a\u53f7&#034;, description &#061; &#034;\u68c0\u67e5\u624b\u673a\u53f7\u662f\u5426\u5df2\u5b58\u5728&#034;)<br \/>\npublic ApiResponse&lt;Boolean&gt; checkPhone(&#064;RequestParam String phone) {<br \/>\n    boolean exists &#061; userService.checkPhone(phone);<br \/>\n    return ApiResponse.success(!exists);<br \/>\n}<\/p>\n<p>} 5. JWT\u5de5\u5177\u7c7b&#xff1a;<\/p>\n<p>java package com.ecommerce.user.util;<\/p>\n<p>import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.security.Keys; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component;<\/p>\n<p>import java.security.Key; import java.util.Date; import java.util.Map;<\/p>\n<p>&#064;Slf4j &#064;Component public class JwtUtil {<\/p>\n<p>&#064;Value(&#034;${jwt.secret}&#034;)<br \/>\nprivate String secret;<\/p>\n<p>&#064;Value(&#034;${jwt.expiration}&#034;)<br \/>\nprivate Long expiration;<\/p>\n<p>private Key getSigningKey() {<br \/>\n    return Keys.hmacShaKeyFor(secret.getBytes());<br \/>\n}<\/p>\n<p>\/\/ \u751f\u6210token<br \/>\npublic static String generateToken(Map&lt;String, Object&gt; claims, String subject) {<br \/>\n    return Jwts.builder()<br \/>\n            .setClaims(claims)<br \/>\n            .setSubject(subject)<br \/>\n            .setIssuedAt(new Date())<br \/>\n            .setExpiration(new Date(System.currentTimeMillis() &#043; expiration))<br \/>\n            .signWith(getSigningKey(), SignatureAlgorithm.HS256)<br \/>\n            .compact();<br \/>\n}<\/p>\n<p>\/\/ \u4ecetoken\u4e2d\u83b7\u53d6\u7528\u6237\u540d<br \/>\npublic String getUsernameFromToken(String token) {<br \/>\n    return getClaimFromToken(token, Claims::getSubject);<br \/>\n}<\/p>\n<p>\/\/ \u4ecetoken\u4e2d\u83b7\u53d6\u8fc7\u671f\u65f6\u95f4<br \/>\npublic Date getExpirationDateFromToken(String token) {<br \/>\n    return getClaimFromToken(token, Claims::getExpiration);<br \/>\n}<\/p>\n<p>\/\/ \u4ecetoken\u4e2d\u83b7\u53d6\u6307\u5b9aclaim<br \/>\npublic &lt;T&gt; T getClaimFromToken(String token, java.util.function.Function&lt;Claims, T&gt; claimsResolver) {<br \/>\n    final Claims claims &#061; getAllClaimsFromToken(token);<br \/>\n    return claimsResolver.apply(claims);<br \/>\n}<\/p>\n<p>\/\/ \u4ecetoken\u4e2d\u83b7\u53d6\u6240\u6709claims<br \/>\nprivate Claims getAllClaimsFromToken(String token) {<br \/>\n    return Jwts.parserBuilder()<br \/>\n            .setSigningKey(getSigningKey())<br \/>\n            .build()<br \/>\n            .parseClaimsJws(token)<br \/>\n            .getBody();<br \/>\n}<\/p>\n<p>\/\/ \u9a8c\u8bc1token\u662f\u5426\u8fc7\u671f<br \/>\nprivate Boolean isTokenExpired(String token) {<br \/>\n    final Date expiration &#061; getExpirationDateFromToken(token);<br \/>\n    return expiration.before(new Date());<br \/>\n}<\/p>\n<p>\/\/ \u9a8c\u8bc1token<br \/>\npublic Boolean validateToken(String token, String username) {<br \/>\n    final String usernameFromToken &#061; getUsernameFromToken(token);<br \/>\n    return (username.equals(usernameFromToken) &amp;&amp; !isTokenExpired(token));<br \/>\n}<\/p>\n<p>}<\/p>\n<h3>\u7b2c\u56db\u7ae0&#xff1a;\u5546\u54c1\u670d\u52a1\u5b9e\u73b0<\/h3>\n<p>4.1 \u5546\u54c1\u670d\u52a1\u6838\u5fc3\u529f\u80fd \u5546\u54c1\u670d\u52a1pom.xml&#xff1a;<\/p>\n<p>xml<\/p>\n<p> com.ecommerce common-core ${project.version}<br \/>\n&lt;!&#8211; Elasticsearch &#8211;&gt;<br \/>\n&lt;dependency&gt;<br \/>\n    &lt;groupId&gt;org.springframework.boot&lt;\/groupId&gt;<br \/>\n    &lt;artifactId&gt;spring-boot-starter-data-elasticsearch&lt;\/artifactId&gt;<br \/>\n&lt;\/dependency&gt;<\/p>\n<p>&lt;!&#8211; \u7f13\u5b58 &#8211;&gt;<br \/>\n&lt;dependency&gt;<br \/>\n    &lt;groupId&gt;org.redisson&lt;\/groupId&gt;<br \/>\n    &lt;artifactId&gt;redisson-spring-boot-starter&lt;\/artifactId&gt;<br \/>\n&lt;\/dependency&gt;<\/p>\n<p>&lt;!&#8211; \u6d88\u606f\u961f\u5217 &#8211;&gt;<br \/>\n&lt;dependency&gt;<br \/>\n    &lt;groupId&gt;org.apache.rocketmq&lt;\/groupId&gt;<br \/>\n    &lt;artifactId&gt;rocketmq-spring-boot-starter&lt;\/artifactId&gt;<br \/>\n&lt;\/dependency&gt;<\/p>\n<p>&lt;!&#8211; Seata\u5206\u5e03\u5f0f\u4e8b\u52a1 &#8211;&gt;<br \/>\n&lt;dependency&gt;<br \/>\n    &lt;groupId&gt;io.seata&lt;\/groupId&gt;<br \/>\n    &lt;artifactId&gt;seata-spring-boot-starter&lt;\/artifactId&gt;<br \/>\n&lt;\/dependency&gt;<br \/>\n \u5546\u54c1\u670d\u52a1\u6838\u5fc3\u4ee3\u7801&#xff1a; <\/p>\n<li>\u5546\u54c1\u5b9e\u4f53\u4e0eMapper&#xff1a;<\/li>\n<p>java \/\/ \u5546\u54c1\u5b9e\u4f53 package com.ecommerce.product.entity;<\/p>\n<p>import com.baomidou.mybatisplus.annotation.*; import lombok.Data;<\/p>\n<p>import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.List;<\/p>\n<p>&#064;Data &#064;TableName(\u201ctb_product\u201d) public class Product {<\/p>\n<p>&#064;TableId(type &#061; IdType.AUTO)<br \/>\nprivate Long id;<\/p>\n<p>private String spuCode;<br \/>\nprivate String name;<br \/>\nprivate String subTitle;<br \/>\nprivate Long categoryId;<br \/>\nprivate Long brandId;<br \/>\nprivate String mainImage;<\/p>\n<p>&#064;TableField(typeHandler &#061; JsonTypeHandler.class)<br \/>\nprivate List&lt;String&gt; images;<\/p>\n<p>private String detail;<\/p>\n<p>&#064;TableField(typeHandler &#061; JsonTypeHandler.class)<br \/>\nprivate List&lt;ProductSpec&gt; specifications;<\/p>\n<p>private BigDecimal price;<br \/>\nprivate BigDecimal marketPrice;<br \/>\nprivate BigDecimal costPrice;<br \/>\nprivate Integer stock;<br \/>\nprivate Integer sales;<br \/>\nprivate Integer status;<br \/>\nprivate BigDecimal weight;<br \/>\nprivate BigDecimal volume;<\/p>\n<p>&#064;TableField(fill &#061; FieldFill.INSERT)<br \/>\nprivate LocalDateTime createTime;<\/p>\n<p>&#064;TableField(fill &#061; FieldFill.INSERT_UPDATE)<br \/>\nprivate LocalDateTime updateTime;<\/p>\n<p>&#064;TableLogic<br \/>\nprivate Integer deleted;<\/p>\n<p>}<\/p>\n<p>\/\/ \u5546\u54c1\u89c4\u683c &#064;Data public class ProductSpec { private String name; private String value; private String unit; }<\/p>\n<p>\/\/ JSON\u7c7b\u578b\u5904\u7406\u5668 public class JsonTypeHandler extends BaseTypeHandler {<\/p>\n<p>private final Class&lt;T&gt; type;<br \/>\nprivate final ObjectMapper objectMapper;<\/p>\n<p>public JsonTypeHandler(Class&lt;T&gt; type) {<br \/>\n    this.type &#061; type;<br \/>\n    this.objectMapper &#061; new ObjectMapper();<br \/>\n    this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);<br \/>\n}<\/p>\n<p>&#064;Override<br \/>\npublic void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {<br \/>\n    try {<br \/>\n        String json &#061; objectMapper.writeValueAsString(parameter);<br \/>\n        ps.setString(i, json);<br \/>\n    } catch (JsonProcessingException e) {<br \/>\n        throw new RuntimeException(&#034;JSON\u5e8f\u5217\u5316\u5931\u8d25&#034;, e);<br \/>\n    }<br \/>\n}<\/p>\n<p>&#064;Override<br \/>\npublic T getNullableResult(ResultSet rs, String columnName) throws SQLException {<br \/>\n    String json &#061; rs.getString(columnName);<br \/>\n    return parseJson(json);<br \/>\n}<\/p>\n<p>&#064;Override<br \/>\npublic T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {<br \/>\n    String json &#061; rs.getString(columnIndex);<br \/>\n    return parseJson(json);<br \/>\n}<\/p>\n<p>&#064;Override<br \/>\npublic T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {<br \/>\n    String json &#061; cs.getString(columnIndex);<br \/>\n    return parseJson(json);<br \/>\n}<\/p>\n<p>private T parseJson(String json) {<br \/>\n    if (StringUtils.isBlank(json)) {<br \/>\n        return null;<br \/>\n    }<br \/>\n    try {<br \/>\n        return objectMapper.readValue(json, type);<br \/>\n    } catch (IOException e) {<br \/>\n        throw new RuntimeException(&#034;JSON\u53cd\u5e8f\u5217\u5316\u5931\u8d25&#034;, e);<br \/>\n    }<br \/>\n}<\/p>\n<p>}<\/p>\n<p>\/\/ \u5546\u54c1Mapper package com.ecommerce.product.mapper;<\/p>\n<p>import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.ecommerce.product.entity.Product; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Update;<\/p>\n<p>&#064;Mapper public interface ProductMapper extends BaseMapper {<\/p>\n<p>&#064;Select(&#034;SELECT * FROM tb_product WHERE id &#061; #{id} AND status &#061; 1 AND deleted &#061; 0&#034;)<br \/>\nProduct selectAvailableById(Long id);<\/p>\n<p>&#064;Update(&#034;UPDATE tb_product SET stock &#061; stock &#8211; #{quantity}, sales &#061; sales &#043; #{quantity} WHERE id &#061; #{productId} AND stock &gt;&#061; #{quantity}&#034;)<br \/>\nint deductStock(&#064;Param(&#034;productId&#034;) Long productId, &#064;Param(&#034;quantity&#034;) Integer quantity);<\/p>\n<p>&#064;Update(&#034;UPDATE tb_product SET stock &#061; stock &#043; #{quantity} WHERE id &#061; #{productId}&#034;)<br \/>\nint addStock(&#064;Param(&#034;productId&#034;) Long productId, &#064;Param(&#034;quantity&#034;) Integer quantity);<\/p>\n<p>} 2. \u5546\u54c1\u641c\u7d22&#xff08;Elasticsearch\u96c6\u6210&#xff09;&#xff1a;<\/p>\n<p>java \/\/ Elasticsearch\u5546\u54c1\u6587\u6863 package com.ecommerce.product.document;<\/p>\n<p>import lombok.Data; import org.springframework.data.annotation.Id; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.FieldType;<\/p>\n<p>import java.math.BigDecimal; import java.util.List; import java.util.Map;<\/p>\n<p>&#064;Data &#064;Document(indexName &#061; \u201cproducts\u201d) public class ProductDocument {<\/p>\n<p>&#064;Id<br \/>\nprivate Long id;<\/p>\n<p>&#064;Field(type &#061; FieldType.Text, analyzer &#061; &#034;ik_max_word&#034;)<br \/>\nprivate String name;<\/p>\n<p>&#064;Field(type &#061; FieldType.Text, analyzer &#061; &#034;ik_smart&#034;)<br \/>\nprivate String subTitle;<\/p>\n<p>&#064;Field(type &#061; FieldType.Long)<br \/>\nprivate Long categoryId;<\/p>\n<p>&#064;Field(type &#061; FieldType.Long)<br \/>\nprivate Long brandId;<\/p>\n<p>&#064;Field(type &#061; FieldType.Keyword)<br \/>\nprivate String spuCode;<\/p>\n<p>&#064;Field(type &#061; FieldType.Text)<br \/>\nprivate String mainImage;<\/p>\n<p>&#064;Field(type &#061; FieldType.Double)<br \/>\nprivate BigDecimal price;<\/p>\n<p>&#064;Field(type &#061; FieldType.Integer)<br \/>\nprivate Integer stock;<\/p>\n<p>&#064;Field(type &#061; FieldType.Integer)<br \/>\nprivate Integer sales;<\/p>\n<p>&#064;Field(type &#061; FieldType.Integer)<br \/>\nprivate Integer status;<\/p>\n<p>&#064;Field(type &#061; FieldType.Nested)<br \/>\nprivate List&lt;ProductSpecDocument&gt; specifications;<\/p>\n<p>&#064;Field(type &#061; FieldType.Object)<br \/>\nprivate Map&lt;String, Object&gt; attributes;<\/p>\n<p>&#064;Field(type &#061; FieldType.Date)<br \/>\nprivate Long createTime;<\/p>\n<p>}<\/p>\n<p>\/\/ \u5546\u54c1\u89c4\u683c\u6587\u6863 &#064;Data public class ProductSpecDocument {<\/p>\n<p>&#064;Field(type &#061; FieldType.Keyword)<br \/>\nprivate String name;<\/p>\n<p>&#064;Field(type &#061; FieldType.Text, analyzer &#061; &#034;ik_smart&#034;)<br \/>\nprivate String value;<\/p>\n<p>&#064;Field(type &#061; FieldType.Keyword)<br \/>\nprivate String unit;<\/p>\n<p>}<\/p>\n<p>\/\/ \u5546\u54c1\u641c\u7d22Repository package com.ecommerce.product.repository;<\/p>\n<p>import com.ecommerce.product.document.ProductDocument; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.elasticsearch.annotations.Query; import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;<\/p>\n<p>public interface ProductSearchRepository extends ElasticsearchRepository&lt;ProductDocument, Long&gt; {<\/p>\n<p>\/\/ \u6839\u636e\u540d\u79f0\u641c\u7d22<br \/>\nPage&lt;ProductDocument&gt; findByName(String name, Pageable pageable);<\/p>\n<p>\/\/ \u6839\u636e\u5206\u7c7bID\u641c\u7d22<br \/>\nPage&lt;ProductDocument&gt; findByCategoryId(Long categoryId, Pageable pageable);<\/p>\n<p>\/\/ \u4ef7\u683c\u8303\u56f4\u641c\u7d22<br \/>\nPage&lt;ProductDocument&gt; findByPriceBetween(BigDecimal minPrice, BigDecimal maxPrice, Pageable pageable);<\/p>\n<p>\/\/ \u590d\u5408\u6761\u4ef6\u641c\u7d22<br \/>\n&#064;Query(&#034;{\\\\&#034;bool\\\\&#034;: {\\\\&#034;must\\\\&#034;: [&#034; &#043;<br \/>\n        &#034;{\\\\&#034;match\\\\&#034;: {\\\\&#034;name\\\\&#034;: \\\\&#034;?0\\\\&#034;}},&#034; &#043;<br \/>\n        &#034;{\\\\&#034;range\\\\&#034;: {\\\\&#034;price\\\\&#034;: {\\\\&#034;gte\\\\&#034;: ?1, \\\\&#034;lte\\\\&#034;: ?2}}}&#034; &#043;<br \/>\n        &#034;]}}&#034;)<br \/>\nPage&lt;ProductDocument&gt; searchByNameAndPriceRange(String name, BigDecimal minPrice, BigDecimal maxPrice, Pageable pageable);<\/p>\n<p>}<\/p>\n<p>\/\/ \u5546\u54c1\u641c\u7d22\u670d\u52a1 package com.ecommerce.product.service;<\/p>\n<p>import com.ecommerce.common.dto.PageRequest; import com.ecommerce.common.dto.PageResult; import com.ecommerce.product.dto.ProductSearchDTO; import com.ecommerce.product.document.ProductDocument;<\/p>\n<p>public interface ProductSearchService {<\/p>\n<p>\/\/ \u521b\u5efa\u7d22\u5f15<br \/>\nboolean createIndex();<\/p>\n<p>\/\/ \u6dfb\u52a0\u5546\u54c1\u5230\u7d22\u5f15<br \/>\nvoid addProductToIndex(ProductDocument product);<\/p>\n<p>\/\/ \u4ece\u7d22\u5f15\u4e2d\u5220\u9664\u5546\u54c1<br \/>\nvoid removeProductFromIndex(Long productId);<\/p>\n<p>\/\/ \u641c\u7d22\u5546\u54c1<br \/>\nPageResult&lt;ProductDocument&gt; searchProducts(ProductSearchDTO searchDTO, PageRequest pageRequest);<\/p>\n<p>\/\/ \u6839\u636eID\u6279\u91cf\u67e5\u8be2<br \/>\nList&lt;ProductDocument&gt; findByIds(List&lt;Long&gt; ids);<\/p>\n<p>\/\/ \u66f4\u65b0\u5546\u54c1\u7d22\u5f15<br \/>\nvoid updateProductIndex(ProductDocument product);<\/p>\n<p>} 3. \u5546\u54c1\u7f13\u5b58\u7b56\u7565&#xff08;Redisson\u5206\u5e03\u5f0f\u9501&#xff09;&#xff1a;<\/p>\n<p>java \/\/ \u5546\u54c1\u7f13\u5b58\u670d\u52a1 package com.ecommerce.product.service;<\/p>\n<p>import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service;<\/p>\n<p>import java.util.concurrent.TimeUnit;<\/p>\n<p>&#064;Slf4j &#064;Service &#064;RequiredArgsConstructor public class ProductCacheService {<\/p>\n<p>private final RedisTemplate&lt;String, Object&gt; redisTemplate;<br \/>\nprivate final RedissonClient redissonClient;<\/p>\n<p>\/\/ \u5546\u54c1\u7f13\u5b58key<br \/>\nprivate static final String PRODUCT_KEY_PREFIX &#061; &#034;product:&#034;;<br \/>\nprivate static final String PRODUCT_STOCK_KEY_PREFIX &#061; &#034;product:stock:&#034;;<br \/>\nprivate static final String PRODUCT_LOCK_KEY_PREFIX &#061; &#034;product:lock:&#034;;<\/p>\n<p>\/\/ \u83b7\u53d6\u5546\u54c1\u4fe1\u606f&#xff08;\u5e26\u7f13\u5b58&#xff09;<br \/>\npublic ProductDTO getProductWithCache(Long productId) {<br \/>\n    String cacheKey &#061; PRODUCT_KEY_PREFIX &#043; productId;<\/p>\n<p>    \/\/ 1. \u4ece\u7f13\u5b58\u83b7\u53d6<br \/>\n    ProductDTO product &#061; (ProductDTO) redisTemplate.opsForValue().get(cacheKey);<br \/>\n    if (product !&#061; null) {<br \/>\n        return product;<br \/>\n    }<\/p>\n<p>    \/\/ 2. \u83b7\u53d6\u5206\u5e03\u5f0f\u9501<br \/>\n    String lockKey &#061; PRODUCT_LOCK_KEY_PREFIX &#043; productId;<br \/>\n    RLock lock &#061; redissonClient.getLock(lockKey);<\/p>\n<p>    try {<br \/>\n        \/\/ \u5c1d\u8bd5\u52a0\u9501&#xff0c;\u6700\u591a\u7b49\u5f855\u79d2&#xff0c;\u9501\u8d85\u65f6\u65f6\u95f410\u79d2<br \/>\n        boolean locked &#061; lock.tryLock(5, 10, TimeUnit.SECONDS);<br \/>\n        if (!locked) {<br \/>\n            throw new RuntimeException(&#034;\u83b7\u53d6\u5546\u54c1\u4fe1\u606f\u8d85\u65f6&#034;);<br \/>\n        }<\/p>\n<p>        \/\/ 3. \u53cc\u91cd\u68c0\u67e5\u7f13\u5b58<br \/>\n        product &#061; (ProductDTO) redisTemplate.opsForValue().get(cacheKey);<br \/>\n        if (product !&#061; null) {<br \/>\n            return product;<br \/>\n        }<\/p>\n<p>        \/\/ 4. \u4ece\u6570\u636e\u5e93\u83b7\u53d6<br \/>\n        product &#061; productService.getProductById(productId);<br \/>\n        if (product &#061;&#061; null) {<br \/>\n            \/\/ \u7f13\u5b58\u7a7a\u503c&#xff0c;\u9632\u6b62\u7f13\u5b58\u7a7f\u900f<br \/>\n            redisTemplate.opsForValue().set(cacheKey, &#034;&#034;, 5, TimeUnit.MINUTES);<br \/>\n            return null;<br \/>\n        }<\/p>\n<p>        \/\/ 5. \u653e\u5165\u7f13\u5b58<br \/>\n        redisTemplate.opsForValue().set(cacheKey, product, 30, TimeUnit.MINUTES);<\/p>\n<p>        \/\/ 6. \u7f13\u5b58\u5e93\u5b58\u5230Redis&#xff08;\u7528\u4e8e\u79d2\u6740\u7b49\u573a\u666f&#xff09;<br \/>\n        cacheStock(productId, product.getStock());<\/p>\n<p>        return product;<\/p>\n<p>    } catch (InterruptedException e) {<br \/>\n        Thread.currentThread().interrupt();<br \/>\n        throw new RuntimeException(&#034;\u83b7\u53d6\u5546\u54c1\u4fe1\u606f\u4e2d\u65ad&#034;, e);<br \/>\n    } finally {<br \/>\n        \/\/ \u91ca\u653e\u9501<br \/>\n        if (lock.isHeldByCurrentThread()) {<br \/>\n            lock.unlock();<br \/>\n        }<br \/>\n    }<br \/>\n}<\/p>\n<p>\/\/ \u7f13\u5b58\u5e93\u5b58<br \/>\nprivate void cacheStock(Long productId, Integer stock) {<br \/>\n    String stockKey &#061; PRODUCT_STOCK_KEY_PREFIX &#043; productId;<br \/>\n    redisTemplate.opsForValue().set(stockKey, stock, 1, TimeUnit.HOURS);<br \/>\n}<\/p>\n<p>\/\/ \u6263\u51cf\u5e93\u5b58&#xff08;Redis\u9884\u51cf\u5e93\u5b58&#xff09;<br \/>\npublic boolean deductStock(Long productId, Integer quantity) {<br \/>\n    String stockKey &#061; PRODUCT_STOCK_KEY_PREFIX &#043; productId;<br \/>\n    String lockKey &#061; PRODUCT_LOCK_KEY_PREFIX &#043; productId &#043; &#034;:stock&#034;;<\/p>\n<p>    RLock lock &#061; redissonClient.getLock(lockKey);<\/p>\n<p>    try {<br \/>\n        \/\/ \u52a0\u9501<br \/>\n        boolean locked &#061; lock.tryLock(3, 5, TimeUnit.SECONDS);<br \/>\n        if (!locked) {<br \/>\n            return false;<br \/>\n        }<\/p>\n<p>        \/\/ \u83b7\u53d6\u5f53\u524d\u5e93\u5b58<br \/>\n        Object stockObj &#061; redisTemplate.opsForValue().get(stockKey);<br \/>\n        if (stockObj &#061;&#061; null) {<br \/>\n            \/\/ \u5e93\u5b58\u672a\u7f13\u5b58&#xff0c;\u4ece\u6570\u636e\u5e93\u83b7\u53d6<br \/>\n            Integer dbStock &#061; productService.getProductStock(productId);<br \/>\n            redisTemplate.opsForValue().set(stockKey, dbStock, 1, TimeUnit.HOURS);<br \/>\n            stockObj &#061; dbStock;<br \/>\n        }<\/p>\n<p>        Integer currentStock &#061; (Integer) stockObj;<br \/>\n        if (currentStock &lt; quantity) {<br \/>\n            return false; \/\/ \u5e93\u5b58\u4e0d\u8db3<br \/>\n        }<\/p>\n<p>        \/\/ \u9884\u51cf\u5e93\u5b58<br \/>\n        Long newStock &#061; redisTemplate.opsForValue().decrement(stockKey, quantity);<\/p>\n<p>        \/\/ \u5f02\u6b65\u66f4\u65b0\u6570\u636e\u5e93<br \/>\n        asyncUpdateDbStock(productId, quantity);<\/p>\n<p>        return newStock !&#061; null &amp;&amp; newStock &gt;&#061; 0;<\/p>\n<p>    } catch (InterruptedException e) {<br \/>\n        Thread.currentThread().interrupt();<br \/>\n        return false;<br \/>\n    } finally {<br \/>\n        if (lock.isHeldByCurrentThread()) {<br \/>\n            lock.unlock();<br \/>\n        }<br \/>\n    }<br \/>\n}<\/p>\n<p>\/\/ \u5f02\u6b65\u66f4\u65b0\u6570\u636e\u5e93\u5e93\u5b58<br \/>\nprivate void asyncUpdateDbStock(Long productId, Integer quantity) {<br \/>\n    CompletableFuture.runAsync(() -&gt; {<br \/>\n        try {<br \/>\n            productMapper.deductStock(productId, quantity);<br \/>\n        } catch (Exception e) {<br \/>\n            log.error(&#034;\u5f02\u6b65\u66f4\u65b0\u5e93\u5b58\u5931\u8d25: productId&#061;{}, quantity&#061;{}&#034;, productId, quantity, e);<br \/>\n            \/\/ \u5931\u8d25\u65f6\u56de\u6edaRedis\u5e93\u5b58<br \/>\n            rollbackStock(productId, quantity);<br \/>\n        }<br \/>\n    });<br \/>\n}<\/p>\n<p>\/\/ \u56de\u6eda\u5e93\u5b58<br \/>\nprivate void rollbackStock(Long productId, Integer quantity) {<br \/>\n    String stockKey &#061; PRODUCT_STOCK_KEY_PREFIX &#043; productId;<br \/>\n    redisTemplate.opsForValue().increment(stockKey, quantity);<br \/>\n}<\/p>\n<p>}<\/p>\n<h3>\u7b2c\u4e94\u7ae0&#xff1a;\u8ba2\u5355\u670d\u52a1\u4e0e\u5206\u5e03\u5f0f\u4e8b\u52a1<\/h3>\n<p>5.1 \u8ba2\u5355\u670d\u52a1\u6838\u5fc3\u529f\u80fd \u8ba2\u5355\u72b6\u6001\u673a\u8bbe\u8ba1&#xff1a;<\/p>\n<p>java \/\/ \u8ba2\u5355\u72b6\u6001\u679a\u4e3e package com.ecommerce.order.enums;<\/p>\n<p>import lombok.Getter;<\/p>\n<p>&#064;Getter public enum OrderStatus {<\/p>\n<p>PENDING_PAYMENT(1, &#034;\u5f85\u4ed8\u6b3e&#034;) {<br \/>\n    &#064;Override<br \/>\n    public boolean canChangeTo(OrderStatus targetStatus) {<br \/>\n        return targetStatus &#061;&#061; PAID || targetStatus &#061;&#061; CANCELLED;<br \/>\n    }<br \/>\n},<\/p>\n<p>PAID(2, &#034;\u5df2\u4ed8\u6b3e&#034;) {<br \/>\n    &#064;Override<br \/>\n    public boolean canChangeTo(OrderStatus targetStatus) {<br \/>\n        return targetStatus &#061;&#061; SHIPPED || targetStatus &#061;&#061; REFUNDING;<br \/>\n    }<br \/>\n},<\/p>\n<p>SHIPPED(3, &#034;\u5df2\u53d1\u8d27&#034;) {<br \/>\n    &#064;Override<br \/>\n    public boolean canChangeTo(OrderStatus targetStatus) {<br \/>\n        return targetStatus &#061;&#061; RECEIVED || targetStatus &#061;&#061; REFUNDING;<br \/>\n    }<br \/>\n},<\/p>\n<p>RECEIVED(4, &#034;\u5df2\u5b8c\u6210&#034;) {<br \/>\n    &#064;Override<br \/>\n    public boolean canChangeTo(OrderStatus targetStatus) {<br \/>\n        return targetStatus &#061;&#061; REFUNDED;<br \/>\n    }<br \/>\n},<\/p>\n<p>CANCELLED(5, &#034;\u5df2\u53d6\u6d88&#034;) {<br \/>\n    &#064;Override<br \/>\n    public boolean canChangeTo(OrderStatus targetStatus) {<br \/>\n        return false;<br \/>\n    }<br \/>\n},<\/p>\n<p>REFUNDING(6, &#034;\u9000\u6b3e\u4e2d&#034;) {<br \/>\n    &#064;Override<br \/>\n    public boolean canChangeTo(OrderStatus targetStatus) {<br \/>\n        return targetStatus &#061;&#061; REFUNDED || targetStatus &#061;&#061; PAID;<br \/>\n    }<br \/>\n},<\/p>\n<p>REFUNDED(7, &#034;\u5df2\u9000\u6b3e&#034;) {<br \/>\n    &#064;Override<br \/>\n    public boolean canChangeTo(OrderStatus targetStatus) {<br \/>\n        return false;<br \/>\n    }<br \/>\n};<\/p>\n<p>private final Integer code;<br \/>\nprivate final String description;<\/p>\n<p>OrderStatus(Integer code, String description) {<br \/>\n    this.code &#061; code;<br \/>\n    this.description &#061; description;<br \/>\n}<\/p>\n<p>\/\/ \u68c0\u67e5\u662f\u5426\u53ef\u4ee5\u8f6c\u6362\u5230\u76ee\u6807\u72b6\u6001<br \/>\npublic abstract boolean canChangeTo(OrderStatus targetStatus);<\/p>\n<p>\/\/ \u6839\u636ecode\u83b7\u53d6\u679a\u4e3e<br \/>\npublic static OrderStatus getByCode(Integer code) {<br \/>\n    for (OrderStatus status : values()) {<br \/>\n        if (status.getCode().equals(code)) {<br \/>\n            return status;<br \/>\n        }<br \/>\n    }<br \/>\n    throw new IllegalArgumentException(&#034;\u65e0\u6548\u7684\u8ba2\u5355\u72b6\u6001\u7801: &#034; &#043; code);<br \/>\n}<\/p>\n<p>}<\/p>\n<p>\/\/ \u8ba2\u5355\u72b6\u6001\u673a package com.ecommerce.order.service.state;<\/p>\n<p>import com.ecommerce.order.enums.OrderStatus; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component;<\/p>\n<p>&#064;Slf4j &#064;Component public class OrderStateMachine {<\/p>\n<p>\/**<br \/>\n * \u68c0\u67e5\u8ba2\u5355\u72b6\u6001\u662f\u5426\u53ef\u4ee5\u8f6c\u6362<br \/>\n *\/<br \/>\npublic boolean canChangeStatus(OrderStatus currentStatus, OrderStatus targetStatus) {<br \/>\n    if (currentStatus &#061;&#061; null || targetStatus &#061;&#061; null) {<br \/>\n        return false;<br \/>\n    }<\/p>\n<p>    return currentStatus.canChangeTo(targetStatus);<br \/>\n}<\/p>\n<p>\/**<br \/>\n * \u6267\u884c\u72b6\u6001\u8f6c\u6362<br \/>\n *\/<br \/>\npublic void changeStatus(Order order, OrderStatus targetStatus, String operator, String remark) {<br \/>\n    OrderStatus currentStatus &#061; OrderStatus.getByCode(order.getStatus());<\/p>\n<p>    if (!canChangeStatus(currentStatus, targetStatus)) {<br \/>\n        throw new BusinessException(<br \/>\n                String.format(&#034;\u8ba2\u5355\u72b6\u6001\u4e0d\u80fd\u4ece[%s]\u8f6c\u6362\u5230[%s]&#034;,<br \/>\n                currentStatus.getDescription(),<br \/>\n                targetStatus.getDescription()));<br \/>\n    }<\/p>\n<p>    \/\/ \u66f4\u65b0\u8ba2\u5355\u72b6\u6001<br \/>\n    order.setStatus(targetStatus.getCode());<\/p>\n<p>    \/\/ \u8bb0\u5f55\u72b6\u6001\u53d8\u66f4\u65e5\u5fd7<br \/>\n    OrderStatusLog statusLog &#061; new OrderStatusLog();<br \/>\n    statusLog.setOrderId(order.getId());<br \/>\n    statusLog.setFromStatus(currentStatus.getCode());<br \/>\n    statusLog.setToStatus(targetStatus.getCode());<br \/>\n    statusLog.setOperator(operator);<br \/>\n    statusLog.setRemark(remark);<br \/>\n    statusLog.setCreateTime(LocalDateTime.now());<\/p>\n<p>    orderStatusLogMapper.insert(statusLog);<\/p>\n<p>    log.info(&#034;\u8ba2\u5355\u72b6\u6001\u53d8\u66f4: orderId&#061;{}, from&#061;{}, to&#061;{}, operator&#061;{}&#034;,<br \/>\n            order.getId(),<br \/>\n            currentStatus.getDescription(),<br \/>\n            targetStatus.getDescription(),<br \/>\n            operator);<\/p>\n<p>    \/\/ \u53d1\u5e03\u72b6\u6001\u53d8\u66f4\u4e8b\u4ef6<br \/>\n    publishStatusChangeEvent(order, currentStatus, targetStatus);<br \/>\n}<\/p>\n<p>\/**<br \/>\n * \u53d1\u5e03\u72b6\u6001\u53d8\u66f4\u4e8b\u4ef6<br \/>\n *\/<br \/>\nprivate void publishStatusChangeEvent(Order order, OrderStatus fromStatus, OrderStatus toStatus) {<br \/>\n    OrderStatusChangeEvent event &#061; new OrderStatusChangeEvent();<br \/>\n    event.setOrderId(order.getId());<br \/>\n    event.setOrderNo(order.getOrderNo());<br \/>\n    event.setUserId(order.getUserId());<br \/>\n    event.setFromStatus(fromStatus);<br \/>\n    event.setToStatus(toStatus);<br \/>\n    event.setChangeTime(LocalDateTime.now());<\/p>\n<p>    applicationEventPublisher.publishEvent(event);<br \/>\n}<\/p>\n<p>} 5.2 Seata\u5206\u5e03\u5f0f\u4e8b\u52a1\u96c6\u6210 Seata\u914d\u7f6e&#xff1a;<\/p>\n<p>yaml<\/p>\n<h2>seata\u914d\u7f6e<\/h2>\n<p>seata: enabled: true application-id: ${spring.application.name} tx-service-group: default_tx_group enable-auto-data-source-proxy: true config: type: nacos nacos: server-addr: ${spring.cloud.nacos.discovery.server-addr} namespace: ${spring.cloud.nacos.discovery.namespace} group: SEATA_GROUP username: nacos password: nacos registry: type: nacos nacos: server-addr: ${spring.cloud.nacos.discovery.server-addr} namespace: ${spring.cloud.nacos.discovery.namespace} group: SEATA_GROUP username: nacos password: nacos service: vgroup-mapping: default_tx_group: default disable-global-transaction: false \u521b\u5efa\u8ba2\u5355&#xff08;\u5206\u5e03\u5f0f\u4e8b\u52a1&#xff09;&#xff1a;<\/p>\n<p>java \/\/ \u8ba2\u5355\u670d\u52a1 package com.ecommerce.order.service;<\/p>\n<p>import com.ecommerce.order.dto.CreateOrderDTO; import io.seata.spring.annotation.GlobalTransactional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service;<\/p>\n<p>&#064;Slf4j &#064;Service &#064;RequiredArgsConstructor public class OrderService {<\/p>\n<p>private final OrderMapper orderMapper;<br \/>\nprivate final OrderItemMapper orderItemMapper;<br \/>\nprivate final InventoryService inventoryService;<br \/>\nprivate final CouponService couponService;<br \/>\nprivate final ProductService productService;<\/p>\n<p>\/**<br \/>\n * \u521b\u5efa\u8ba2\u5355&#xff08;\u5206\u5e03\u5f0f\u4e8b\u52a1&#xff09;<br \/>\n *\/<br \/>\n&#064;GlobalTransactional(name &#061; &#034;createOrder&#034;, rollbackFor &#061; Exception.class)<br \/>\npublic OrderDTO createOrder(CreateOrderDTO createOrderDTO) {<br \/>\n    log.info(&#034;\u5f00\u59cb\u521b\u5efa\u8ba2\u5355: userId&#061;{}, items&#061;{}&#034;,<br \/>\n            createOrderDTO.getUserId(),<br \/>\n            createOrderDTO.getItems().size());<\/p>\n<p>    \/\/ 1. \u9a8c\u8bc1\u5546\u54c1\u4fe1\u606f<br \/>\n    validateProducts(createOrderDTO.getItems());<\/p>\n<p>    \/\/ 2. \u751f\u6210\u8ba2\u5355\u53f7<br \/>\n    String orderNo &#061; generateOrderNo();<\/p>\n<p>    \/\/ 3. \u8ba1\u7b97\u8ba2\u5355\u91d1\u989d<br \/>\n    OrderAmount orderAmount &#061; calculateOrderAmount(createOrderDTO);<\/p>\n<p>    \/\/ 4. \u521b\u5efa\u8ba2\u5355<br \/>\n    Order order &#061; createOrderEntity(createOrderDTO, orderNo, orderAmount);<br \/>\n    orderMapper.insert(order);<\/p>\n<p>    \/\/ 5. \u521b\u5efa\u8ba2\u5355\u5546\u54c1<br \/>\n    List&lt;OrderItem&gt; orderItems &#061; createOrderItems(order.getId(), createOrderDTO.getItems());<br \/>\n    orderItemMapper.insertBatch(orderItems);<\/p>\n<p>    \/\/ 6. \u6263\u51cf\u5e93\u5b58&#xff08;\u8c03\u7528\u5e93\u5b58\u670d\u52a1&#xff09;<br \/>\n    inventoryService.deductStock(createOrderDTO.getItems());<\/p>\n<p>    \/\/ 7. \u4f7f\u7528\u4f18\u60e0\u5238&#xff08;\u8c03\u7528\u4f18\u60e0\u5238\u670d\u52a1&#xff09;<br \/>\n    if (createOrderDTO.getCouponId() !&#061; null) {<br \/>\n        couponService.useCoupon(createOrderDTO.getUserId(), createOrderDTO.getCouponId());<br \/>\n    }<\/p>\n<p>    \/\/ 8. \u53d1\u9001\u521b\u5efa\u8ba2\u5355\u6d88\u606f<br \/>\n    sendOrderCreatedMessage(order);<\/p>\n<p>    log.info(&#034;\u8ba2\u5355\u521b\u5efa\u6210\u529f: orderId&#061;{}, orderNo&#061;{}&#034;, order.getId(), order.getOrderNo());<\/p>\n<p>    return convertToDTO(order);<br \/>\n}<\/p>\n<p>\/**<br \/>\n * \u9a8c\u8bc1\u5546\u54c1\u4fe1\u606f<br \/>\n *\/<br \/>\nprivate void validateProducts(List&lt;CreateOrderItemDTO&gt; items) {<br \/>\n    for (CreateOrderItemDTO item : items) {<br \/>\n        ProductDTO product &#061; productService.getProductById(item.getProductId());<br \/>\n        if (product &#061;&#061; null) {<br \/>\n            throw new BusinessException(&#034;\u5546\u54c1\u4e0d\u5b58\u5728: &#034; &#043; item.getProductId());<br \/>\n        }<br \/>\n        if (product.getStatus() !&#061; 1) {<br \/>\n            throw new BusinessException(&#034;\u5546\u54c1\u5df2\u4e0b\u67b6: &#034; &#043; product.getName());<br \/>\n        }<br \/>\n        if (product.getStock() &lt; item.getQuantity()) {<br \/>\n            throw new BusinessException(&#034;\u5546\u54c1\u5e93\u5b58\u4e0d\u8db3: &#034; &#043; product.getName());<br \/>\n        }<br \/>\n    }<br \/>\n}<\/p>\n<p>\/**<br \/>\n * \u751f\u6210\u8ba2\u5355\u53f7<br \/>\n *\/<br \/>\nprivate String generateOrderNo() {<br \/>\n    \/\/ \u65f6\u95f4\u6233 &#043; \u968f\u673a\u6570<br \/>\n    return &#034;O&#034; &#043; System.currentTimeMillis() &#043;<br \/>\n           RandomUtil.randomNumbers(6);<br \/>\n}<\/p>\n<p>\/**<br \/>\n * \u8ba1\u7b97\u8ba2\u5355\u91d1\u989d<br \/>\n *\/<br \/>\nprivate OrderAmount calculateOrderAmount(CreateOrderDTO createOrderDTO) {<br \/>\n    BigDecimal totalAmount &#061; BigDecimal.ZERO;<\/p>\n<p>    for (CreateOrderItemDTO item : createOrderDTO.getItems()) {<br \/>\n        ProductDTO product &#061; productService.getProductById(item.getProductId());<br \/>\n        BigDecimal itemAmount &#061; product.getPrice().multiply(<br \/>\n            BigDecimal.valueOf(item.getQuantity()));<br \/>\n        totalAmount &#061; totalAmount.add(itemAmount);<br \/>\n    }<\/p>\n<p>    \/\/ \u8ba1\u7b97\u8fd0\u8d39<br \/>\n    BigDecimal shippingAmount &#061; calculateShipping(createOrderDTO);<\/p>\n<p>    \/\/ \u8ba1\u7b97\u4f18\u60e0\u91d1\u989d<br \/>\n    BigDecimal discountAmount &#061; calculateDiscount(createOrderDTO);<\/p>\n<p>    \/\/ \u5b9e\u4ed8\u91d1\u989d &#061; \u5546\u54c1\u603b\u989d &#043; \u8fd0\u8d39 &#8211; \u4f18\u60e0<br \/>\n    BigDecimal payAmount &#061; totalAmount.add(shippingAmount).subtract(discountAmount);<\/p>\n<p>    OrderAmount orderAmount &#061; new OrderAmount();<br \/>\n    orderAmount.setTotalAmount(totalAmount);<br \/>\n    orderAmount.setShippingAmount(shippingAmount);<br \/>\n    orderAmount.setDiscountAmount(discountAmount);<br \/>\n    orderAmount.setPayAmount(payAmount);<\/p>\n<p>    return orderAmount;<br \/>\n}<\/p>\n<p>}<\/p>\n<p>\/\/ \u5e93\u5b58\u670d\u52a1&#xff08;\u6263\u51cf\u5e93\u5b58&#xff09; package com.ecommerce.inventory.service;<\/p>\n<p>import io.seata.rm.tcc.api.BusinessActionContext; import io.seata.rm.tcc.api.BusinessActionContextParameter; import io.seata.rm.tcc.api.LocalTCC; import io.seata.rm.tcc.api.TwoPhaseBusinessAction; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service;<\/p>\n<p>&#064;Slf4j &#064;Service &#064;LocalTCC public class InventoryService {<\/p>\n<p>\/**<br \/>\n * TCC\u6a21\u5f0f\u6263\u51cf\u5e93\u5b58<br \/>\n *\/<br \/>\n&#064;TwoPhaseBusinessAction(name &#061; &#034;deductStock&#034;, commitMethod &#061; &#034;commitDeductStock&#034;,<br \/>\n                      rollbackMethod &#061; &#034;rollbackDeductStock&#034;)<br \/>\npublic boolean deductStockPrepare(BusinessActionContext actionContext,<br \/>\n                                 &#064;BusinessActionContextParameter(paramName &#061; &#034;items&#034;)<br \/>\n                                 List&lt;CreateOrderItemDTO&gt; items) {<br \/>\n    log.info(&#034;TCC\u7b2c\u4e00\u9636\u6bb5&#xff1a;\u9884\u6263\u5e93\u5b58, xid&#061;{}&#034;, actionContext.getXid());<\/p>\n<p>    Map&lt;Long, Integer&gt; lockMap &#061; new HashMap&lt;&gt;();<\/p>\n<p>    try {<br \/>\n        for (CreateOrderItemDTO item : items) {<br \/>\n            \/\/ \u83b7\u53d6\u5206\u5e03\u5f0f\u9501<br \/>\n            String lockKey &#061; &#034;inventory:lock:&#034; &#043; item.getProductId();<br \/>\n            RLock lock &#061; redissonClient.getLock(lockKey);<br \/>\n            boolean locked &#061; lock.tryLock(5, 10, TimeUnit.SECONDS);<\/p>\n<p>            if (!locked) {<br \/>\n                throw new BusinessException(&#034;\u9501\u5b9a\u5e93\u5b58\u5931\u8d25&#034;);<br \/>\n            }<\/p>\n<p>            \/\/ \u9884\u6263\u5e93\u5b58<br \/>\n            int affected &#061; inventoryMapper.lockStock(<br \/>\n                item.getProductId(),<br \/>\n                item.getQuantity(),<br \/>\n                actionContext.getXid());<\/p>\n<p>            if (affected &#061;&#061; 0) {<br \/>\n                throw new BusinessException(&#034;\u5e93\u5b58\u4e0d\u8db3&#034;);<br \/>\n            }<\/p>\n<p>            lockMap.put(item.getProductId(), item.getQuantity());<\/p>\n<p>            \/\/ \u8bb0\u5f55\u5e93\u5b58\u9501\u5b9a\u65e5\u5fd7<br \/>\n            InventoryLockLog lockLog &#061; new InventoryLockLog();<br \/>\n            lockLog.setProductId(item.getProductId());<br \/>\n            lockLog.setOrderXid(actionContext.getXid());<br \/>\n            lockLog.setQuantity(item.getQuantity());<br \/>\n            lockLog.setStatus(0); \/\/ \u9501\u5b9a\u72b6\u6001<br \/>\n            lockLog.setCreateTime(LocalDateTime.now());<br \/>\n            inventoryLockLogMapper.insert(lockLog);<br \/>\n        }<\/p>\n<p>        \/\/ \u5c06\u9501\u5b9a\u4fe1\u606f\u4fdd\u5b58\u5230\u4e0a\u4e0b\u6587<br \/>\n        actionContext.setActionContext(&#034;lockMap&#034;, lockMap);<\/p>\n<p>        return true;<\/p>\n<p>    } catch (InterruptedException e) {<br \/>\n        Thread.currentThread().interrupt();<br \/>\n        throw new BusinessException(&#034;\u9501\u5b9a\u5e93\u5b58\u4e2d\u65ad&#034;, e);<br \/>\n    }<br \/>\n}<\/p>\n<p>\/**<br \/>\n * TCC\u7b2c\u4e8c\u9636\u6bb5\u63d0\u4ea4<br \/>\n *\/<br \/>\npublic boolean commitDeductStock(BusinessActionContext actionContext) {<br \/>\n    log.info(&#034;TCC\u7b2c\u4e8c\u9636\u6bb5\u63d0\u4ea4&#xff1a;\u786e\u8ba4\u6263\u51cf\u5e93\u5b58, xid&#061;{}&#034;, actionContext.getXid());<\/p>\n<p>    \/\/ \u66f4\u65b0\u5e93\u5b58\u9501\u5b9a\u72b6\u6001\u4e3a\u5df2\u786e\u8ba4<br \/>\n    inventoryLockLogMapper.updateStatusByXid(actionContext.getXid(), 1);<\/p>\n<p>    \/\/ \u5b9e\u9645\u6263\u51cf\u5e93\u5b58<br \/>\n    Map&lt;Long, Integer&gt; lockMap &#061; (Map&lt;Long, Integer&gt;)<br \/>\n        actionContext.getActionContext(&#034;lockMap&#034;);<\/p>\n<p>    for (Map.Entry&lt;Long, Integer&gt; entry : lockMap.entrySet()) {<br \/>\n        inventoryMapper.confirmDeductStock(entry.getKey(), entry.getValue());<br \/>\n    }<\/p>\n<p>    return true;<br \/>\n}<\/p>\n<p>\/**<br \/>\n * TCC\u7b2c\u4e8c\u9636\u6bb5\u56de\u6eda<br \/>\n *\/<br \/>\npublic boolean rollbackDeductStock(BusinessActionContext actionContext) {<br \/>\n    log.info(&#034;TCC\u7b2c\u4e8c\u9636\u6bb5\u56de\u6eda&#xff1a;\u91ca\u653e\u5e93\u5b58, xid&#061;{}&#034;, actionContext.getXid());<\/p>\n<p>    \/\/ \u66f4\u65b0\u5e93\u5b58\u9501\u5b9a\u72b6\u6001\u4e3a\u5df2\u56de\u6eda<br \/>\n    inventoryLockLogMapper.updateStatusByXid(actionContext.getXid(), 2);<\/p>\n<p>    \/\/ \u91ca\u653e\u9501\u5b9a\u7684\u5e93\u5b58<br \/>\n    Map&lt;Long, Integer&gt; lockMap &#061; (Map&lt;Long, Integer&gt;)<br \/>\n        actionContext.getActionContext(&#034;lockMap&#034;);<\/p>\n<p>    for (Map.Entry&lt;Long, Integer&gt; entry : lockMap.entrySet()) {<br \/>\n        inventoryMapper.releaseLockedStock(entry.getKey(), entry.getValue());<br \/>\n    }<\/p>\n<p>    return true;<br \/>\n}<\/p>\n<p>}<\/p>\n<p>\/\/ \u5e93\u5b58Mapper &#064;Mapper public interface InventoryMapper {<\/p>\n<p>\/\/ \u9501\u5b9a\u5e93\u5b58<br \/>\n&#064;Update(&#034;UPDATE tb_inventory SET locked_stock &#061; locked_stock &#043; #{quantity}, &#034; &#043;<br \/>\n        &#034;available_stock &#061; available_stock &#8211; #{quantity} &#034; &#043;<br \/>\n        &#034;WHERE product_id &#061; #{productId} AND available_stock &gt;&#061; #{quantity}&#034;)<br \/>\nint lockStock(&#064;Param(&#034;productId&#034;) Long productId,<br \/>\n              &#064;Param(&#034;quantity&#034;) Integer quantity,<br \/>\n              &#064;Param(&#034;xid&#034;) String xid);<\/p>\n<p>\/\/ \u786e\u8ba4\u6263\u51cf\u5e93\u5b58<br \/>\n&#064;Update(&#034;UPDATE tb_inventory SET locked_stock &#061; locked_stock &#8211; #{quantity}, &#034; &#043;<br \/>\n        &#034;total_stock &#061; total_stock &#8211; #{quantity} &#034; &#043;<br \/>\n        &#034;WHERE product_id &#061; #{productId} AND locked_stock &gt;&#061; #{quantity}&#034;)<br \/>\nint confirmDeductStock(&#064;Param(&#034;productId&#034;) Long productId,<br \/>\n                      &#064;Param(&#034;quantity&#034;) Integer quantity);<\/p>\n<p>\/\/ \u91ca\u653e\u9501\u5b9a\u7684\u5e93\u5b58<br \/>\n&#064;Update(&#034;UPDATE tb_inventory SET locked_stock &#061; locked_stock &#8211; #{quantity}, &#034; &#043;<br \/>\n        &#034;available_stock &#061; available_stock &#043; #{quantity} &#034; &#043;<br \/>\n        &#034;WHERE product_id &#061; #{productId} AND locked_stock &gt;&#061; #{quantity}&#034;)<br \/>\nint releaseLockedStock(&#064;Param(&#034;productId&#034;) Long productId,<br \/>\n                      &#064;Param(&#034;quantity&#034;) Integer quantity);<\/p>\n<p>} 5.3 \u8ba2\u5355\u8d85\u65f6\u53d6\u6d88&#xff08;RocketMQ\u5ef6\u8fdf\u6d88\u606f&#xff09; \u8ba2\u5355\u8d85\u65f6\u53d6\u6d88\u5b9e\u73b0&#xff1a;<\/p>\n<p>java \/\/ \u8ba2\u5355\u8d85\u65f6\u670d\u52a1 package com.ecommerce.order.service;<\/p>\n<p>import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.rocketmq.spring.core.RocketMQTemplate; import org.springframework.stereotype.Service;<\/p>\n<p>import java.time.LocalDateTime; import java.util.concurrent.DelayQueue; import java.util.concurrent.Delayed; import java.util.concurrent.TimeUnit;<\/p>\n<p>&#064;Slf4j &#064;Service &#064;RequiredArgsConstructor public class OrderTimeoutService {<\/p>\n<p>private final RocketMQTemplate rocketMQTemplate;<br \/>\nprivate final OrderMapper orderMapper;<br \/>\nprivate final DelayQueue&lt;OrderTimeoutTask&gt; timeoutQueue &#061; new DelayQueue&lt;&gt;();<\/p>\n<p>\/**<br \/>\n * \u542f\u52a8\u8ba2\u5355\u8d85\u65f6\u68c0\u67e5<br \/>\n *\/<br \/>\n&#064;PostConstruct<br \/>\npublic void init() {<br \/>\n    \/\/ \u542f\u52a8\u540e\u53f0\u7ebf\u7a0b\u5904\u7406\u8d85\u65f6\u8ba2\u5355<br \/>\n    new Thread(this::processTimeoutOrders).start();<\/p>\n<p>    \/\/ \u4ece\u6570\u636e\u5e93\u52a0\u8f7d\u5f85\u652f\u4ed8\u7684\u8d85\u65f6\u8ba2\u5355<br \/>\n    loadPendingOrders();<br \/>\n}<\/p>\n<p>\/**<br \/>\n * \u6dfb\u52a0\u8ba2\u5355\u8d85\u65f6\u4efb\u52a1<br \/>\n *\/<br \/>\npublic void addOrderTimeoutTask(Long orderId, LocalDateTime createTime) {<br \/>\n    \/\/ \u8ba1\u7b97\u5ef6\u8fdf\u65f6\u95f4&#xff08;30\u5206\u949f&#xff09;<br \/>\n    long delay &#061; TimeUnit.MINUTES.toMillis(30) &#8211;<br \/>\n                System.currentTimeMillis() &#8211;<br \/>\n                createTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();<\/p>\n<p>    if (delay &gt; 0) {<br \/>\n        OrderTimeoutTask task &#061; new OrderTimeoutTask(orderId, delay);<br \/>\n        timeoutQueue.put(task);<br \/>\n        log.info(&#034;\u6dfb\u52a0\u8ba2\u5355\u8d85\u65f6\u4efb\u52a1: orderId&#061;{}, delay&#061;{}ms&#034;, orderId, delay);<br \/>\n    }<br \/>\n}<\/p>\n<p>\/**<br \/>\n * \u5904\u7406\u8d85\u65f6\u8ba2\u5355<br \/>\n *\/<br \/>\nprivate void processTimeoutOrders() {<br \/>\n    while (!Thread.currentThread().isInterrupted()) {<br \/>\n        try {<br \/>\n            OrderTimeoutTask task &#061; timeoutQueue.take();<br \/>\n            cancelTimeoutOrder(task.getOrderId());<br \/>\n        } catch (InterruptedException e) {<br \/>\n            Thread.currentThread().interrupt();<br \/>\n            log.error(&#034;\u8ba2\u5355\u8d85\u65f6\u5904\u7406\u7ebf\u7a0b\u4e2d\u65ad&#034;, e);<br \/>\n        }<br \/>\n    }<br \/>\n}<\/p>\n<p>\/**<br \/>\n * \u53d6\u6d88\u8d85\u65f6\u8ba2\u5355<br \/>\n *\/<br \/>\nprivate void cancelTimeoutOrder(Long orderId) {<br \/>\n    try {<br \/>\n        Order order &#061; orderMapper.selectById(orderId);<br \/>\n        if (order &#061;&#061; null || order.getStatus() !&#061; OrderStatus.PENDING_PAYMENT.getCode()) {<br \/>\n            return;<br \/>\n        }<\/p>\n<p>        \/\/ \u68c0\u67e5\u662f\u5426\u771f\u7684\u8d85\u65f6&#xff08;\u9632\u6b62\u91cd\u590d\u5904\u7406&#xff09;<br \/>\n        LocalDateTime now &#061; LocalDateTime.now();<br \/>\n        LocalDateTime createTime &#061; order.getCreateTime();<br \/>\n        long minutes &#061; Duration.between(createTime, now).toMinutes();<\/p>\n<p>        if (minutes &gt;&#061; 30) {<br \/>\n            log.info(&#034;\u53d6\u6d88\u8d85\u65f6\u8ba2\u5355: orderId&#061;{}, createTime&#061;{}&#034;, orderId, createTime);<\/p>\n<p>            \/\/ \u66f4\u65b0\u8ba2\u5355\u72b6\u6001\u4e3a\u5df2\u53d6\u6d88<br \/>\n            order.setStatus(OrderStatus.CANCELLED.getCode());<br \/>\n            order.setCancelTime(now);<br \/>\n            order.setCancelReason(&#034;\u8d85\u65f6\u672a\u652f\u4ed8&#034;);<br \/>\n            orderMapper.updateById(order);<\/p>\n<p>            \/\/ \u91ca\u653e\u5e93\u5b58<br \/>\n            releaseOrderStock(orderId);<\/p>\n<p>            \/\/ \u53d1\u9001\u8ba2\u5355\u53d6\u6d88\u6d88\u606f<br \/>\n            sendOrderCanceledMessage(order);<br \/>\n        }<\/p>\n<p>    } catch (Exception e) {<br \/>\n        log.error(&#034;\u53d6\u6d88\u8d85\u65f6\u8ba2\u5355\u5931\u8d25: orderId&#061;{}&#034;, orderId, e);<br \/>\n    }<br \/>\n}<\/p>\n<p>\/**<br \/>\n * \u53d1\u9001\u5ef6\u8fdf\u6d88\u606f&#xff08;RocketMQ\u65b9\u5f0f&#xff09;<br \/>\n *\/<br \/>\npublic void sendOrderTimeoutMessage(Long orderId) {<br \/>\n    \/\/ \u53d1\u900130\u5206\u949f\u540e\u7684\u5ef6\u8fdf\u6d88\u606f<br \/>\n    rocketMQTemplate.syncSend(&#034;ORDER_TIMEOUT_TOPIC&#034;,<br \/>\n                             MessageBuilder.withPayload(orderId).build(),<br \/>\n                             TimeUnit.MINUTES.toMillis(30),<br \/>\n                             TimeUnit.MILLISECONDS);<\/p>\n<p>    log.info(&#034;\u53d1\u9001\u8ba2\u5355\u8d85\u65f6\u5ef6\u8fdf\u6d88\u606f: orderId&#061;{}&#034;, orderId);<br \/>\n}<\/p>\n<p>\/**<br \/>\n * \u76d1\u542c\u8ba2\u5355\u8d85\u65f6\u6d88\u606f<br \/>\n *\/<br \/>\n&#064;RocketMQMessageListener(<br \/>\n    topic &#061; &#034;ORDER_TIMEOUT_TOPIC&#034;,<br \/>\n    consumerGroup &#061; &#034;order-timeout-consumer&#034;<br \/>\n)<br \/>\n&#064;Slf4j<br \/>\n&#064;Component<br \/>\npublic class OrderTimeoutConsumer implements RocketMQListener&lt;Long&gt; {<\/p>\n<p>    &#064;Override<br \/>\n    public void onMessage(Long orderId) {<br \/>\n        log.info(&#034;\u6536\u5230\u8ba2\u5355\u8d85\u65f6\u6d88\u606f: orderId&#061;{}&#034;, orderId);<br \/>\n        cancelTimeoutOrder(orderId);<br \/>\n    }<br \/>\n}<\/p>\n<p>}<\/p>\n<p>\/\/ \u8ba2\u5355\u8d85\u65f6\u4efb\u52a1 &#064;Data class OrderTimeoutTask implements Delayed {<\/p>\n<p>private final Long orderId;<br \/>\nprivate final long expireTime;<\/p>\n<p>public OrderTimeoutTask(Long orderId, long delay) {<br \/>\n    this.orderId &#061; orderId;<br \/>\n    this.expireTime &#061; System.currentTimeMillis() &#043; delay;<br \/>\n}<\/p>\n<p>&#064;Override<br \/>\npublic long getDelay(TimeUnit unit) {<br \/>\n    return unit.convert(expireTime &#8211; System.currentTimeMillis(), TimeUnit.MILLISECONDS);<br \/>\n}<\/p>\n<p>&#064;Override<br \/>\npublic int compareTo(Delayed o) {<br \/>\n    return Long.compare(this.expireTime, ((OrderTimeoutTask) o).expireTime);<br \/>\n}<\/p>\n<p>}<\/p>\n<h3>\u7b2c\u516d\u7ae0&#xff1a;\u652f\u4ed8\u670d\u52a1\u4e0e\u6d88\u606f\u961f\u5217<\/h3>\n<p>6.1 \u652f\u4ed8\u670d\u52a1\u96c6\u6210 \u652f\u4ed8\u670d\u52a1\u914d\u7f6e&#xff1a;<\/p>\n<p>java \/\/ \u652f\u4ed8\u670d\u52a1\u63a5\u53e3 package com.ecommerce.payment.service;<\/p>\n<p>import com.ecommerce.payment.dto.*;<\/p>\n<p>public interface PaymentService {<\/p>\n<p>\/\/ \u521b\u5efa\u652f\u4ed8<br \/>\nPaymentDTO createPayment(CreatePaymentDTO createPaymentDTO);<\/p>\n<p>\/\/ \u67e5\u8be2\u652f\u4ed8\u72b6\u6001<br \/>\nPaymentDTO queryPayment(String paymentNo);<\/p>\n<p>\/\/ \u652f\u4ed8\u56de\u8c03\u5904\u7406<br \/>\nPaymentCallbackResult handlePaymentCallback(PaymentCallbackDTO callbackDTO);<\/p>\n<p>\/\/ \u9000\u6b3e<br \/>\nRefundDTO refund(RefundRequest refundRequest);<\/p>\n<p>\/\/ \u67e5\u8be2\u9000\u6b3e\u72b6\u6001<br \/>\nRefundDTO queryRefund(String refundNo);<\/p>\n<p>}<\/p>\n<p>\/\/ \u652f\u4ed8\u7b56\u7565\u63a5\u53e3 package com.ecommerce.payment.strategy;<\/p>\n<p>public interface PaymentStrategy {<\/p>\n<p>\/\/ \u652f\u4ed8\u7c7b\u578b<br \/>\nPaymentType getPaymentType();<\/p>\n<p>\/\/ \u521b\u5efa\u652f\u4ed8<br \/>\nPaymentDTO createPayment(CreatePaymentDTO createPaymentDTO);<\/p>\n<p>\/\/ \u67e5\u8be2\u652f\u4ed8<br \/>\nPaymentDTO queryPayment(String paymentNo);<\/p>\n<p>\/\/ \u5904\u7406\u56de\u8c03<br \/>\nPaymentCallbackResult handleCallback(PaymentCallbackDTO callbackDTO);<\/p>\n<p>\/\/ \u9000\u6b3e<br \/>\nRefundDTO refund(RefundRequest refundRequest);<\/p>\n<p>}<\/p>\n<p>\/\/ \u5fae\u4fe1\u652f\u4ed8\u5b9e\u73b0 &#064;Service &#064;Slf4j public class WechatPaymentStrategy implements PaymentStrategy {<\/p>\n<p>&#064;Override<br \/>\npublic PaymentType getPaymentType() {<br \/>\n    return PaymentType.WECHAT;<br \/>\n}<\/p>\n<p>&#064;Override<br \/>\npublic PaymentDTO createPayment(CreatePaymentDTO createPaymentDTO) {<br \/>\n    log.info(&#034;\u5fae\u4fe1\u652f\u4ed8\u521b\u5efa: orderNo&#061;{}, amount&#061;{}&#034;,<br \/>\n            createPaymentDTO.getOrderNo(),<br \/>\n            createPaymentDTO.getAmount());<\/p>\n<p>    \/\/ \u8c03\u7528\u5fae\u4fe1\u652f\u4ed8API<br \/>\n    WechatPaymentRequest request &#061; buildWechatRequest(createPaymentDTO);<br \/>\n    WechatPaymentResponse response &#061; wechatClient.createPayment(request);<\/p>\n<p>    \/\/ \u4fdd\u5b58\u652f\u4ed8\u8bb0\u5f55<br \/>\n    Payment payment &#061; savePaymentRecord(createPaymentDTO, response);<\/p>\n<p>    PaymentDTO paymentDTO &#061; convertToDTO(payment);<br \/>\n    paymentDTO.setPayData(response.getPayData()); \/\/ \u652f\u4ed8\u53c2\u6570&#xff08;\u7528\u4e8e\u524d\u7aef\u8c03\u8d77\u652f\u4ed8&#xff09;<\/p>\n<p>    return paymentDTO;<br \/>\n}<\/p>\n<p>&#064;Override<br \/>\npublic PaymentCallbackResult handleCallback(PaymentCallbackDTO callbackDTO) {<br \/>\n    log.info(&#034;\u5904\u7406\u5fae\u4fe1\u652f\u4ed8\u56de\u8c03: {}&#034;, callbackDTO);<\/p>\n<p>    \/\/ \u9a8c\u8bc1\u7b7e\u540d<br \/>\n    if (!verifySignature(callbackDTO)) {<br \/>\n        throw new BusinessException(&#034;\u7b7e\u540d\u9a8c\u8bc1\u5931\u8d25&#034;);<br \/>\n    }<\/p>\n<p>    \/\/ \u89e3\u6790\u56de\u8c03\u6570\u636e<br \/>\n    WechatCallbackData callbackData &#061; parseCallbackData(callbackDTO);<\/p>\n<p>    \/\/ \u66f4\u65b0\u652f\u4ed8\u72b6\u6001<br \/>\n    Payment payment &#061; updatePaymentStatus(callbackData);<\/p>\n<p>    \/\/ \u53d1\u9001\u652f\u4ed8\u6210\u529f\u6d88\u606f<br \/>\n    if (payment.getStatus() &#061;&#061; PaymentStatus.SUCCESS.getCode()) {<br \/>\n        sendPaymentSuccessMessage(payment);<br \/>\n    }<\/p>\n<p>    return buildCallbackResult(callbackData);<br \/>\n}<\/p>\n<p>private WechatPaymentRequest buildWechatRequest(CreatePaymentDTO createPaymentDTO) {<br \/>\n    WechatPaymentRequest request &#061; new WechatPaymentRequest();<br \/>\n    request.setAppId(wechatConfig.getAppId());<br \/>\n    request.setMchId(wechatConfig.getMchId());<br \/>\n    request.setNonceStr(RandomUtil.randomString(32));<br \/>\n    request.setBody(&#034;\u5546\u54c1\u8d2d\u4e70-&#034; &#043; createPaymentDTO.getOrderNo());<br \/>\n    request.setOutTradeNo(createPaymentDTO.getPaymentNo());<br \/>\n    request.setTotalFee(createPaymentDTO.getAmount().multiply(new BigDecimal(100)).intValue());<br \/>\n    request.setSpbillCreateIp(createPaymentDTO.getClientIp());<br \/>\n    request.setNotifyUrl(wechatConfig.getNotifyUrl());<br \/>\n    request.setTradeType(&#034;JSAPI&#034;);<br \/>\n    request.setOpenid(createPaymentDTO.getOpenId());<\/p>\n<p>    \/\/ \u751f\u6210\u7b7e\u540d<br \/>\n    String sign &#061; generateSignature(request);<br \/>\n    request.setSign(sign);<\/p>\n<p>    return request;<br \/>\n}<\/p>\n<p>} \u652f\u4ed8\u72b6\u6001\u540c\u6b65&#xff08;\u6d88\u606f\u961f\u5217&#xff09;&#xff1a;<\/p>\n<p>java \/\/ \u652f\u4ed8\u6210\u529f\u6d88\u606f\u751f\u4ea7\u8005 &#064;Service &#064;Slf4j &#064;RequiredArgsConstructor public class PaymentMessageProducer {<\/p>\n<p>private final RocketMQTemplate rocketMQTemplate;<\/p>\n<p>\/**<br \/>\n * \u53d1\u9001\u652f\u4ed8\u6210\u529f\u6d88\u606f<br \/>\n *\/<br \/>\npublic void sendPaymentSuccessMessage(Payment payment) {<br \/>\n    PaymentSuccessMessage message &#061; new PaymentSuccessMessage();<br \/>\n    message.setPaymentId(payment.getId());<br \/>\n    message.setOrderNo(payment.getOrderNo());<br \/>\n    message.setPaymentNo(payment.getPaymentNo());<br \/>\n    message.setAmount(payment.getAmount());<br \/>\n    message.setPaymentTime(payment.getPaymentTime());<br \/>\n    message.setPaymentType(payment.getPaymentType());<\/p>\n<p>    \/\/ \u53d1\u9001\u987a\u5e8f\u6d88\u606f&#xff0c;\u786e\u4fdd\u540c\u4e00\u4e2a\u8ba2\u5355\u7684\u6d88\u606f\u6709\u5e8f\u5904\u7406<br \/>\n    rocketMQTemplate.syncSendOrderly(<br \/>\n        &#034;PAYMENT_SUCCESS_TOPIC&#034;,<br \/>\n        message,<br \/>\n        payment.getOrderNo()<br \/>\n    );<\/p>\n<p>    log.info(&#034;\u53d1\u9001\u652f\u4ed8\u6210\u529f\u6d88\u606f: paymentId&#061;{}, orderNo&#061;{}&#034;,<br \/>\n            payment.getId(), payment.getOrderNo());<br \/>\n}<\/p>\n<p>\/**<br \/>\n * \u53d1\u9001\u652f\u4ed8\u5931\u8d25\u6d88\u606f<br \/>\n *\/<br \/>\npublic void sendPaymentFailedMessage(Payment payment, String failReason) {<br \/>\n    PaymentFailedMessage message &#061; new PaymentFailedMessage();<br \/>\n    message.setPaymentId(payment.getId());<br \/>\n    message.setOrderNo(payment.getOrderNo());<br \/>\n    message.setPaymentNo(payment.getPaymentNo());<br \/>\n    message.setAmount(payment.getAmount());<br \/>\n    message.setFailReason(failReason);<br \/>\n    message.setFailTime(LocalDateTime.now());<\/p>\n<p>    rocketMQTemplate.syncSend(&#034;PAYMENT_FAILED_TOPIC&#034;, message);<\/p>\n<p>    log.info(&#034;\u53d1\u9001\u652f\u4ed8\u5931\u8d25\u6d88\u606f: paymentId&#061;{}, orderNo&#061;{}, reason&#061;{}&#034;,<br \/>\n            payment.getId(), payment.getOrderNo(), failReason);<br \/>\n}<\/p>\n<p>}<\/p>\n<p>\/\/ \u8ba2\u5355\u670d\u52a1\u6d88\u8d39\u8005 &#064;Slf4j &#064;Component &#064;RocketMQMessageListener( topic &#061; \u201cPAYMENT_SUCCESS_TOPIC\u201d, consumerGroup &#061; \u201corder-payment-consumer\u201d, consumeMode &#061; ConsumeMode.ORDERLY, \/\/ \u987a\u5e8f\u6d88\u8d39 messageModel &#061; MessageModel.CLUSTERING ) public class PaymentSuccessConsumer implements RocketMQListener {<\/p>\n<p>private final OrderService orderService;<\/p>\n<p>&#064;Override<br \/>\npublic void onMessage(PaymentSuccessMessage message) {<br \/>\n    log.info(&#034;\u6536\u5230\u652f\u4ed8\u6210\u529f\u6d88\u606f: orderNo&#061;{}, paymentNo&#061;{}&#034;,<br \/>\n            message.getOrderNo(), message.getPaymentNo());<\/p>\n<p>    try {<br \/>\n        \/\/ \u66f4\u65b0\u8ba2\u5355\u72b6\u6001\u4e3a\u5df2\u652f\u4ed8<br \/>\n        orderService.updateOrderStatus(<br \/>\n            message.getOrderNo(),<br \/>\n            OrderStatus.PAID,<br \/>\n            &#034;\u7cfb\u7edf&#034;,<br \/>\n            &#034;\u652f\u4ed8\u6210\u529f&#034;<br \/>\n        );<\/p>\n<p>        \/\/ \u53d1\u9001\u8ba2\u5355\u652f\u4ed8\u6210\u529f\u901a\u77e5<br \/>\n        sendOrderPaidNotification(message);<\/p>\n<p>    } catch (Exception e) {<br \/>\n        log.error(&#034;\u5904\u7406\u652f\u4ed8\u6210\u529f\u6d88\u606f\u5931\u8d25: orderNo&#061;{}&#034;, message.getOrderNo(), e);<br \/>\n        \/\/ \u8bb0\u5f55\u5931\u8d25&#xff0c;\u4eba\u5de5\u5e72\u9884\u6216\u91cd\u8bd5<br \/>\n    }<br \/>\n}<\/p>\n<p>}<\/p>\n<p>\/\/ \u5e93\u5b58\u670d\u52a1\u6d88\u8d39\u8005 &#064;Slf4j &#064;Component &#064;RocketMQMessageListener( topic &#061; \u201cPAYMENT_SUCCESS_TOPIC\u201d, consumerGroup &#061; \u201cinventory-payment-consumer\u201d, consumeMode &#061; ConsumeMode.ORDERLY, messageModel &#061; MessageModel.CLUSTERING ) public class InventoryPaymentConsumer implements RocketMQListener {<\/p>\n<p>private final InventoryService inventoryService;<\/p>\n<p>&#064;Override<br \/>\npublic void onMessage(PaymentSuccessMessage message) {<br \/>\n    log.info(&#034;\u5e93\u5b58\u670d\u52a1\u6536\u5230\u652f\u4ed8\u6210\u529f\u6d88\u606f: orderNo&#061;{}&#034;, message.getOrderNo());<\/p>\n<p>    try {<br \/>\n        \/\/ \u786e\u8ba4\u6263\u51cf\u5e93\u5b58&#xff08;\u4ece\u9501\u5b9a\u72b6\u6001\u8f6c\u4e3a\u5df2\u552e\u51fa&#xff09;<br \/>\n        inventoryService.confirmStock(message.getOrderNo());<\/p>\n<p>    } catch (Exception e) {<br \/>\n        log.error(&#034;\u5e93\u5b58\u670d\u52a1\u5904\u7406\u652f\u4ed8\u6d88\u606f\u5931\u8d25: orderNo&#061;{}&#034;, message.getOrderNo(), e);<br \/>\n        \/\/ \u53d1\u9001\u5e93\u5b58\u5904\u7406\u5931\u8d25\u6d88\u606f&#xff0c;\u89e6\u53d1\u8865\u507f\u673a\u5236<br \/>\n        sendInventoryProcessFailedMessage(message, e.getMessage());<br \/>\n    }<br \/>\n}<\/p>\n<p>}<\/p>\n<h3>\u7b2c\u4e03\u7ae0&#xff1a;\u7f51\u5173\u4e0e\u5b89\u5168\u8ba4\u8bc1<\/h3>\n<p>7.1 Spring Cloud Gateway\u7f51\u5173\u914d\u7f6e \u7f51\u5173\u8def\u7531\u914d\u7f6e&#xff1a;<\/p>\n<p>yaml spring: cloud: gateway: discovery: locator: enabled: true lower-case-service-id: true<\/p>\n<p>  routes:<br \/>\n    # \u7528\u6237\u670d\u52a1<br \/>\n    &#8211; id: user-service<br \/>\n      uri: lb:\/\/user-service<br \/>\n      predicates:<br \/>\n        &#8211; Path&#061;\/api\/user\/**<br \/>\n      filters:<br \/>\n        &#8211; StripPrefix&#061;1<br \/>\n        &#8211; name: RequestRateLimiter<br \/>\n          args:<br \/>\n            redis-rate-limiter.replenishRate: 10<br \/>\n            redis-rate-limiter.burstCapacity: 20<br \/>\n            key-resolver: &#034;#{&#064;userKeyResolver}&#034;<\/p>\n<p>    # \u5546\u54c1\u670d\u52a1<br \/>\n    &#8211; id: product-service<br \/>\n      uri: lb:\/\/product-service<br \/>\n      predicates:<br \/>\n        &#8211; Path&#061;\/api\/product\/**<br \/>\n      filters:<br \/>\n        &#8211; StripPrefix&#061;1<br \/>\n        &#8211; name: CircuitBreaker<br \/>\n          args:<br \/>\n            name: productService<br \/>\n            fallbackUri: forward:\/fallback\/product<\/p>\n<p>    # \u8ba2\u5355\u670d\u52a1<br \/>\n    &#8211; id: order-service<br \/>\n      uri: lb:\/\/order-service<br \/>\n      predicates:<br \/>\n        &#8211; Path&#061;\/api\/order\/**<br \/>\n        &#8211; Method&#061;POST,PUT,GET<br \/>\n      filters:<br \/>\n        &#8211; StripPrefix&#061;1<br \/>\n        &#8211; name: AuthFilter<\/p>\n<p>    # \u652f\u4ed8\u670d\u52a1<br \/>\n    &#8211; id: payment-service<br \/>\n      uri: lb:\/\/payment-service<br \/>\n      predicates:<br \/>\n        &#8211; Path&#061;\/api\/payment\/**<br \/>\n      filters:<br \/>\n        &#8211; StripPrefix&#061;1<br \/>\n        &#8211; name: AuthFilter<\/p>\n<p>    # \u8ba4\u8bc1\u670d\u52a1<br \/>\n    &#8211; id: auth-service<br \/>\n      uri: lb:\/\/auth-service<br \/>\n      predicates:<br \/>\n        &#8211; Path&#061;\/api\/auth\/**<br \/>\n      filters:<br \/>\n        &#8211; StripPrefix&#061;1<\/p>\n<p>    # \u9759\u6001\u8d44\u6e90<br \/>\n    &#8211; id: static-resource<br \/>\n      uri: lb:\/\/user-service<br \/>\n      predicates:<br \/>\n        &#8211; Path&#061;\/static\/**<\/p>\n<p>    # \u7ba1\u7406\u7aef\u70b9<br \/>\n    &#8211; id: actuator<br \/>\n      uri: lb:\/\/${spring.application.name}<br \/>\n      predicates:<br \/>\n        &#8211; Path&#061;\/actuator\/**<br \/>\n      filters:<br \/>\n        &#8211; name: AuthFilter<br \/>\n          args:<br \/>\n            require-role: ADMIN<\/p>\n<h2>\u7f51\u5173\u9650\u6d41\u914d\u7f6e<\/h2>\n<p>redis: rate-limiter: enabled: true replenish-rate: 10 # \u6bcf\u79d2\u4ee4\u724c\u6570 burst-capacity: 20 # \u4ee4\u724c\u6876\u5bb9\u91cf<\/p>\n<h2>\u7f51\u5173\u65ad\u8def\u5668\u914d\u7f6e<\/h2>\n<p>resilience4j: circuitbreaker: instances: productService: sliding-window-size: 10 minimum-number-of-calls: 5 permitted-number-of-calls-in-half-open-state: 3 wait-duration-in-open-state: 5s failure-rate-threshold: 50 \u7f51\u5173\u8fc7\u6ee4\u5668&#xff1a;<\/p>\n<p>java \/\/ \u8ba4\u8bc1\u8fc7\u6ee4\u5668 &#064;Component &#064;Slf4j public class AuthFilter implements GlobalFilter, Ordered {<\/p>\n<p>private final JwtUtil jwtUtil;<br \/>\nprivate final RedisTemplate&lt;String, Object&gt; redisTemplate;<\/p>\n<p>\/\/ \u767d\u540d\u5355\u8def\u5f84<br \/>\nprivate static final List&lt;String&gt; WHITE_LIST &#061; Arrays.asList(<br \/>\n    &#034;\/api\/auth\/login&#034;,<br \/>\n    &#034;\/api\/auth\/register&#034;,<br \/>\n    &#034;\/api\/product\/list&#034;,<br \/>\n    &#034;\/api\/product\/detail&#034;,<br \/>\n    &#034;\/static\/&#034;,<br \/>\n    &#034;\/actuator\/health&#034;<br \/>\n);<\/p>\n<p>&#064;Override<br \/>\npublic Mono&lt;Void&gt; filter(ServerWebExchange exchange, GatewayFilterChain chain) {<br \/>\n    ServerHttpRequest request &#061; exchange.getRequest();<br \/>\n    String path &#061; request.getPath().value();<br \/>\n    String method &#061; request.getMethod().name();<\/p>\n<p>    \/\/ \u68c0\u67e5\u662f\u5426\u5728\u767d\u540d\u5355<br \/>\n    if (isWhiteList(path, method)) {<br \/>\n        return chain.filter(exchange);<br \/>\n    }<\/p>\n<p>    \/\/ \u83b7\u53d6token<br \/>\n    String token &#061; getToken(request);<br \/>\n    if (StringUtils.isBlank(token)) {<br \/>\n        return unauthorized(exchange, &#034;\u7f3a\u5c11\u8ba4\u8bc1\u4ee4\u724c&#034;);<br \/>\n    }<\/p>\n<p>    \/\/ \u9a8c\u8bc1token<br \/>\n    if (!jwtUtil.validateToken(token)) {<br \/>\n        return unauthorized(exchange, &#034;\u4ee4\u724c\u65e0\u6548\u6216\u5df2\u8fc7\u671f&#034;);<br \/>\n    }<\/p>\n<p>    \/\/ \u68c0\u67e5token\u662f\u5426\u5728\u9ed1\u540d\u5355\u4e2d<br \/>\n    if (isTokenBlacklisted(token)) {<br \/>\n        return unauthorized(exchange, &#034;\u4ee4\u724c\u5df2\u5931\u6548&#034;);<br \/>\n    }<\/p>\n<p>    \/\/ \u83b7\u53d6\u7528\u6237\u4fe1\u606f<br \/>\n    UserInfo userInfo &#061; jwtUtil.getUserInfo(token);<\/p>\n<p>    \/\/ \u68c0\u67e5\u6743\u9650<br \/>\n    if (!hasPermission(userInfo, path, method)) {<br \/>\n        return forbidden(exchange, &#034;\u6ca1\u6709\u8bbf\u95ee\u6743\u9650&#034;);<br \/>\n    }<\/p>\n<p>    \/\/ \u5c06\u7528\u6237\u4fe1\u606f\u6dfb\u52a0\u5230\u8bf7\u6c42\u5934<br \/>\n    ServerHttpRequest mutatedRequest &#061; request.mutate()<br \/>\n        .header(&#034;X-User-Id&#034;, String.valueOf(userInfo.getUserId()))<br \/>\n        .header(&#034;X-User-Name&#034;, userInfo.getUsername())<br \/>\n        .header(&#034;X-User-Roles&#034;, String.join(&#034;,&#034;, userInfo.getRoles()))<br \/>\n        .build();<\/p>\n<p>    \/\/ \u8bb0\u5f55\u8bbf\u95ee\u65e5\u5fd7<br \/>\n    logAccess(request, userInfo);<\/p>\n<p>    return chain.filter(exchange.mutate().request(mutatedRequest).build());<br \/>\n}<\/p>\n<p>private boolean isWhiteList(String path, String method) {<br \/>\n    return WHITE_LIST.stream().anyMatch(path::startsWith) ||<br \/>\n           (path.startsWith(&#034;\/api\/product\/&#034;) &amp;&amp; &#034;GET&#034;.equals(method));<br \/>\n}<\/p>\n<p>private String getToken(ServerHttpRequest request) {<br \/>\n    String bearerToken &#061; request.getHeaders().getFirst(&#034;Authorization&#034;);<br \/>\n    if (StringUtils.hasText(bearerToken) &amp;&amp; bearerToken.startsWith(&#034;Bearer &#034;)) {<br \/>\n        return bearerToken.substring(7);<br \/>\n    }<br \/>\n    return request.getQueryParams().getFirst(&#034;token&#034;);<br \/>\n}<\/p>\n<p>private boolean isTokenBlacklisted(String token) {<br \/>\n    String key &#061; &#034;blacklist:token:&#034; &#043; DigestUtils.md5Hex(token);<br \/>\n    return Boolean.TRUE.equals(redisTemplate.hasKey(key));<br \/>\n}<\/p>\n<p>private boolean hasPermission(UserInfo userInfo, String path, String method) {<br \/>\n    \/\/ \u7b80\u5355\u7684\u6743\u9650\u68c0\u67e5&#xff0c;\u5b9e\u9645\u9879\u76ee\u4e2d\u53ef\u4ee5\u4f7f\u7528RBAC\u6a21\u578b<br \/>\n    if (path.startsWith(&#034;\/api\/admin\/&#034;) &amp;&amp; !userInfo.getRoles().contains(&#034;ADMIN&#034;)) {<br \/>\n        return false;<br \/>\n    }<\/p>\n<p>    if (path.startsWith(&#034;\/api\/order\/&#034;) &amp;&amp; method.equals(&#034;POST&#034;) &amp;&amp;<br \/>\n        !userInfo.getRoles().contains(&#034;USER&#034;)) {<br \/>\n        return false;<br \/>\n    }<\/p>\n<p>    return true;<br \/>\n}<\/p>\n<p>private void logAccess(ServerHttpRequest request, UserInfo userInfo) {<br \/>\n    String logMsg &#061; String.format(&#034;\u7f51\u5173\u8bbf\u95ee: user&#061;%s, method&#061;%s, path&#061;%s, ip&#061;%s&#034;,<br \/>\n            userInfo.getUsername(),<br \/>\n            request.getMethod(),<br \/>\n            request.getPath(),<br \/>\n            request.getRemoteAddress());<\/p>\n<p>    if (log.isDebugEnabled()) {<br \/>\n        log.debug(logMsg);<br \/>\n    }<br \/>\n}<\/p>\n<p>private Mono&lt;Void&gt; unauthorized(ServerWebExchange exchange, String message) {<br \/>\n    ServerHttpResponse response &#061; exchange.getResponse();<br \/>\n    response.setStatusCode(HttpStatus.UNAUTHORIZED);<br \/>\n    response.getHeaders().setContentType(MediaType.APPLICATION_JSON);<\/p>\n<p>    ApiResponse&lt;?&gt; apiResponse &#061; ApiResponse.error(401, message);<br \/>\n    byte[] bytes &#061; JsonUtil.toJson(apiResponse).getBytes(StandardCharsets.UTF_8);<\/p>\n<p>    DataBuffer buffer &#061; response.bufferFactory().wrap(bytes);<br \/>\n    return response.writeWith(Mono.just(buffer));<br \/>\n}<\/p>\n<p>private Mono&lt;Void&gt; forbidden(ServerWebExchange exchange, String message) {<br \/>\n    ServerHttpResponse response &#061; exchange.getResponse();<br \/>\n    response.setStatusCode(HttpStatus.FORBIDDEN);<br \/>\n    response.getHeaders().setContentType(MediaType.APPLICATION_JSON);<\/p>\n<p>    ApiResponse&lt;?&gt; apiResponse &#061; ApiResponse.error(403, message);<br \/>\n    byte[] bytes &#061; JsonUtil.toJson(apiResponse).getBytes(StandardCharsets.UTF_8);<\/p>\n<p>    DataBuffer buffer &#061; response.bufferFactory().wrap(bytes);<br \/>\n    return response.writeWith(Mono.just(buffer));<br \/>\n}<\/p>\n<p>&#064;Override<br \/>\npublic int getOrder() {<br \/>\n    return -100;<br \/>\n}<\/p>\n<p>}<\/p>\n<p>\/\/ \u9650\u6d41Key\u89e3\u6790\u5668 &#064;Component public class UserKeyResolver implements KeyResolver {<\/p>\n<p>&#064;Override<br \/>\npublic Mono&lt;String&gt; resolve(ServerWebExchange exchange) {<br \/>\n    \/\/ \u6839\u636e\u7528\u6237ID\u9650\u6d41<br \/>\n    String userId &#061; exchange.getRequest().getHeaders().getFirst(&#034;X-User-Id&#034;);<br \/>\n    if (StringUtils.isNotBlank(userId)) {<br \/>\n        return Mono.just(userId);<br \/>\n    }<\/p>\n<p>    \/\/ \u6839\u636eIP\u9650\u6d41<br \/>\n    String ip &#061; Objects.requireNonNull(exchange.getRequest().getRemoteAddress())<br \/>\n            .getAddress().getHostAddress();<br \/>\n    return Mono.just(ip);<br \/>\n}<\/p>\n<p>}<\/p>\n<h3>\u7b2c\u516b\u7ae0&#xff1a;\u76d1\u63a7\u4e0e\u90e8\u7f72<\/h3>\n<p>8.1 \u5b8c\u6574\u7684\u76d1\u63a7\u4f53\u7cfb Prometheus\u914d\u7f6e&#xff1a;<\/p>\n<p>yaml<\/p>\n<h2>prometheus.yml<\/h2>\n<p>global: scrape_interval: 15s evaluation_interval: 15s<\/p>\n<p>alerting: alertmanagers: &#8211; static_configs: &#8211; targets: [\u2018alertmanager:9093\u2019]<\/p>\n<p>rule_files:<\/p>\n<ul>\n<li>\u201calert_rules.yml\u201d<\/li>\n<\/ul>\n<p>scrape_configs:<\/p>\n<ul>\n<li>\n<p>job_name: \u2018spring-boot-apps\u2019 metrics_path: \u2018\/actuator\/prometheus\u2019 scrape_interval: 10s static_configs:<\/p>\n<ul>\n<li>targets:\n<ul>\n<li>\u2018gateway-service:8080\u2019<\/li>\n<li>\u2018user-service:8082\u2019<\/li>\n<li>\u2018product-service:8083\u2019<\/li>\n<li>\u2018order-service:8085\u2019<\/li>\n<li>\u2018payment-service:8086\u2019 labels: group: \u2018e-commerce-services\u2019<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<\/li>\n<li>\n<p>job_name: \u2018nacos\u2019 static_configs:<\/p>\n<ul>\n<li>targets: [\u2018nacos:8848\u2019]<\/li>\n<\/ul>\n<\/li>\n<li>\n<p>job_name: \u2018redis\u2019 static_configs:<\/p>\n<ul>\n<li>targets: [\u2018redis-exporter:9121\u2019]<\/li>\n<\/ul>\n<\/li>\n<li>\n<p>job_name: \u2018mysql\u2019 static_configs:<\/p>\n<ul>\n<li>targets: [\u2018mysqld-exporter:9104\u2019]<\/li>\n<\/ul>\n<\/li>\n<li>\n<p>job_name: \u2018rocketmq\u2019 static_configs:<\/p>\n<ul>\n<li>targets: [\u2018rocketmq-exporter:5557\u2019]<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<h2>\u544a\u8b66\u89c4\u5219<\/h2>\n<h2>alert_rules.yml<\/h2>\n<p>groups:<\/p>\n<ul>\n<li>name: e-commerce-alerts rules:\n<ul>\n<li>\n<p>alert: HighErrorRate expr: rate(http_server_requests_seconds_count{status!~\u201c2\u2026\u201d}[5m]) &gt; 0.1 for: 1m labels: severity: warning annotations: summary: \u201c\u9ad8\u9519\u8bef\u7387\u62a5\u8b66\u201d description: \u201c{{ $labels.instance }} \u9519\u8bef\u7387\u8d85\u8fc710%\u201d<\/p>\n<\/li>\n<li>\n<p>alert: HighLatency expr: histogram_quantile(0.95, rate(http_server_requests_seconds_bucket[5m])) &gt; 1 for: 2m labels: severity: warning annotations: summary: \u201c\u9ad8\u5ef6\u8fdf\u62a5\u8b66\u201d description: \u201c{{ $labels.instance }} 95\u5206\u4f4d\u5ef6\u8fdf\u8d85\u8fc71\u79d2\u201d<\/p>\n<\/li>\n<li>\n<p>alert: ServiceDown expr: up &#061;&#061; 0 for: 1m labels: severity: critical annotations: summary: \u201c\u670d\u52a1\u4e0b\u7ebf\u62a5\u8b66\u201d description: \u201c{{ $labels.instance }} \u670d\u52a1\u5df2\u4e0b\u7ebf\u201d<\/p>\n<\/li>\n<li>\n<p>alert: HighMemoryUsage expr: (sum(jvm_memory_used_bytes) by (instance) \/ sum(jvm_memory_max_bytes) by (instance)) * 100 &gt; 80 for: 5m labels: severity: warning annotations: summary: \u201c\u9ad8\u5185\u5b58\u4f7f\u7528\u7387\u201d description: \u201c{{ $labels.instance }} \u5185\u5b58\u4f7f\u7528\u7387\u8d85\u8fc780%\u201d<\/p>\n<\/li>\n<li>\n<p>alert: HighCPUUsage expr: process_cpu_usage * 100 &gt; 80 for: 5m labels: severity: warning annotations: summary: \u201c\u9ad8CPU\u4f7f\u7528\u7387\u201d description: \u201c{{ $labels.instance }} CPU\u4f7f\u7528\u7387\u8d85\u8fc780%\u201d Spring Boot Actuator\u6307\u6807&#xff1a;<\/p>\n<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>java \/\/ \u81ea\u5b9a\u4e49\u4e1a\u52a1\u6307\u6807 &#064;Component public class BusinessMetrics {<\/p>\n<p>private final MeterRegistry meterRegistry;<\/p>\n<p>\/\/ \u8ba2\u5355\u76f8\u5173\u6307\u6807<br \/>\nprivate final Counter orderCreatedCounter;<br \/>\nprivate final Counter orderPaidCounter;<br \/>\nprivate final Counter orderCanceledCounter;<br \/>\nprivate final Timer orderProcessTimer;<\/p>\n<p>\/\/ \u652f\u4ed8\u76f8\u5173\u6307\u6807<br \/>\nprivate final Counter paymentSuccessCounter;<br \/>\nprivate final Counter paymentFailedCounter;<br \/>\nprivate final DistributionSummary paymentAmountSummary;<\/p>\n<p>\/\/ \u7528\u6237\u76f8\u5173\u6307\u6807<br \/>\nprivate final Gauge activeUsersGauge;<br \/>\nprivate final AtomicInteger activeUsers &#061; new AtomicInteger(0);<\/p>\n<p>public BusinessMetrics(MeterRegistry meterRegistry) {<br \/>\n    this.meterRegistry &#061; meterRegistry;<\/p>\n<p>    \/\/ \u521d\u59cb\u5316\u8ba1\u6570\u5668<br \/>\n    this.orderCreatedCounter &#061; Counter.builder(&#034;ecommerce.orders.created&#034;)<br \/>\n        .description(&#034;\u521b\u5efa\u8ba2\u5355\u603b\u6570&#034;)<br \/>\n        .tag(&#034;application&#034;, &#034;order-service&#034;)<br \/>\n        .register(meterRegistry);<\/p>\n<p>    this.orderPaidCounter &#061; Counter.builder(&#034;ecommerce.orders.paid&#034;)<br \/>\n        .description(&#034;\u652f\u4ed8\u6210\u529f\u8ba2\u5355\u6570&#034;)<br \/>\n        .tag(&#034;application&#034;, &#034;order-service&#034;)<br \/>\n        .register(meterRegistry);<\/p>\n<p>    this.orderCanceledCounter &#061; Counter.builder(&#034;ecommerce.orders.canceled&#034;)<br \/>\n        .description(&#034;\u53d6\u6d88\u8ba2\u5355\u6570&#034;)<br \/>\n        .tag(&#034;application&#034;, &#034;order-service&#034;)<br \/>\n        .register(meterRegistry);<\/p>\n<p>    \/\/ \u521d\u59cb\u5316\u8ba1\u65f6\u5668<br \/>\n    this.orderProcessTimer &#061; Timer.builder(&#034;ecommerce.orders.process.time&#034;)<br \/>\n        .description(&#034;\u8ba2\u5355\u5904\u7406\u65f6\u95f4&#034;)<br \/>\n        .publishPercentiles(0.5, 0.95, 0.99)<br \/>\n        .register(meterRegistry);<\/p>\n<p>    \/\/ \u521d\u59cb\u5316\u652f\u4ed8\u6307\u6807<br \/>\n    this.paymentSuccessCounter &#061; Counter.builder(&#034;ecommerce.payments.success&#034;)<br \/>\n        .description(&#034;\u652f\u4ed8\u6210\u529f\u6570&#034;)<br \/>\n        .tag(&#034;application&#034;, &#034;payment-service&#034;)<br \/>\n        .register(meterRegistry);<\/p>\n<p>    this.paymentFailedCounter &#061; Counter.builder(&#034;ecommerce.payments.failed&#034;)<br \/>\n        .description(&#034;\u652f\u4ed8\u5931\u8d25\u6570&#034;)<br \/>\n        .tag(&#034;application&#034;, &#034;payment-service&#034;)<br \/>\n        .register(meterRegistry);<\/p>\n<p>    this.paymentAmountSummary &#061; DistributionSummary.builder(&#034;ecommerce.payments.amount&#034;)<br \/>\n        .description(&#034;\u652f\u4ed8\u91d1\u989d\u5206\u5e03&#034;)<br \/>\n        .baseUnit(&#034;CNY&#034;)<br \/>\n        .register(meterRegistry);<\/p>\n<p>    \/\/ \u521d\u59cb\u5316\u8ba1\u91cf\u5668<br \/>\n    this.activeUsersGauge &#061; Gauge.builder(&#034;ecommerce.users.active&#034;, activeUsers, AtomicInteger::get)<br \/>\n        .description(&#034;\u6d3b\u8dc3\u7528\u6237\u6570&#034;)<br \/>\n        .register(meterRegistry);<br \/>\n}<\/p>\n<p>public void recordOrderCreated(OrderDTO order) {<br \/>\n    orderCreatedCounter.increment();<\/p>\n<p>    \/\/ \u8bb0\u5f55\u8ba2\u5355\u91d1\u989d<br \/>\n    meterRegistry.summary(&#034;ecommerce.orders.amount&#034;)<br \/>\n        .tag(&#034;status&#034;, &#034;created&#034;)<br \/>\n        .record(order.getPayAmount().doubleValue());<br \/>\n}<\/p>\n<p>public void recordOrderPaid(OrderDTO order, long processTime) {<br \/>\n    orderPaidCounter.increment();<br \/>\n    orderProcessTimer.record(processTime, TimeUnit.MILLISECONDS);<\/p>\n<p>    meterRegistry.summary(&#034;ecommerce.orders.amount&#034;)<br \/>\n        .tag(&#034;status&#034;, &#034;paid&#034;)<br \/>\n        .record(order.getPayAmount().doubleValue());<br \/>\n}<\/p>\n<p>public void recordPaymentSuccess(PaymentDTO payment) {<br \/>\n    paymentSuccessCounter.increment();<br \/>\n    paymentAmountSummary.record(payment.getAmount().doubleValue());<br \/>\n}<\/p>\n<p>public void recordPaymentFailed(PaymentDTO payment, String reason) {<br \/>\n    paymentFailedCounter.increment();<\/p>\n<p>    meterRegistry.counter(&#034;ecommerce.payments.failed.reason&#034;,<br \/>\n        &#034;reason&#034;, reason).increment();<br \/>\n}<\/p>\n<p>public void incrementActiveUsers() {<br \/>\n    activeUsers.incrementAndGet();<br \/>\n}<\/p>\n<p>public void decrementActiveUsers() {<br \/>\n    activeUsers.decrementAndGet();<br \/>\n}<\/p>\n<p>}<\/p>\n<p>\/\/ \u5728\u4e1a\u52a1\u4ee3\u7801\u4e2d\u4f7f\u7528 &#064;Service &#064;RequiredArgsConstructor public class OrderService {<\/p>\n<p>private final BusinessMetrics businessMetrics;<\/p>\n<p>public OrderDTO createOrder(CreateOrderDTO createOrderDTO) {<br \/>\n    long startTime &#061; System.currentTimeMillis();<\/p>\n<p>    try {<br \/>\n        \/\/ \u521b\u5efa\u8ba2\u5355\u903b\u8f91&#8230;<br \/>\n        OrderDTO order &#061; createOrderInternal(createOrderDTO);<\/p>\n<p>        \/\/ \u8bb0\u5f55\u6307\u6807<br \/>\n        businessMetrics.recordOrderCreated(order);<\/p>\n<p>        return order;<br \/>\n    } finally {<br \/>\n        long processTime &#061; System.currentTimeMillis() &#8211; startTime;<br \/>\n        businessMetrics.recordOrderProcessTime(processTime);<br \/>\n    }<br \/>\n}<\/p>\n<p>} 8.2 Docker\u4e0eKubernetes\u90e8\u7f72 Docker Compose\u5b8c\u6574\u90e8\u7f72&#xff1a;<\/p>\n<p>yaml version: \u20183.8\u2019<\/p>\n<p>services:<\/p>\n<h2>\u57fa\u7840\u670d\u52a1<\/h2>\n<p>mysql: image: mysql:8.0 container_name: ecommerce-mysql environment: MYSQL_ROOT_PASSWORD: ecommerce123 MYSQL_DATABASE: ecommerce ports: &#8211; \u201c3306:3306\u201d volumes: &#8211; mysql_data:\/var\/lib\/mysql &#8211; .\/sql\/init.sql:\/docker-entrypoint-initdb.d\/init.sql networks: &#8211; ecommerce-network healthcheck: test: [\u201cCMD\u201d, \u201cmysqladmin\u201d, \u201cping\u201d, \u201c-h\u201d, \u201clocalhost\u201d] timeout: 20s retries: 10<\/p>\n<p>redis: image: redis:7-alpine container_name: ecommerce-redis ports: &#8211; \u201c6379:6379\u201d volumes: &#8211; redis_data:\/data command: redis-server &#8211;appendonly yes networks: &#8211; ecommerce-network healthcheck: test: [\u201cCMD\u201d, \u201credis-cli\u201d, \u201cping\u201d] interval: 10s timeout: 5s retries: 5<\/p>\n<p>nacos: image: nacos\/nacos-server:v2.2.0 container_name: ecommerce-nacos environment: &#8211; MODE&#061;standalone &#8211; SPRING_DATASOURCE_PLATFORM&#061;mysql &#8211; MYSQL_SERVICE_HOST&#061;mysql &#8211; MYSQL_SERVICE_DB_NAME&#061;nacos &#8211; MYSQL_SERVICE_PORT&#061;3306 &#8211; MYSQL_SERVICE_USER&#061;root &#8211; MYSQL_SERVICE_PASSWORD&#061;ecommerce123 &#8211; NACOS_AUTH_ENABLE&#061;true ports: &#8211; \u201c8848:8848\u201d &#8211; \u201c9848:9848\u201d &#8211; \u201c9849:9849\u201d volumes: &#8211; nacos_logs:\/home\/nacos\/logs depends_on: mysql: condition: service_healthy networks: &#8211; ecommerce-network<\/p>\n<p>seata: image: seataio\/seata-server:1.7.1 container_name: ecommerce-seata environment: &#8211; SEATA_PORT&#061;8091 &#8211; STORE_MODE&#061;db &#8211; SEATA_IP&#061;seata ports: &#8211; \u201c8091:8091\u201d volumes: &#8211; .\/config\/seata\/registry.conf:\/seata-server\/resources\/registry.conf depends_on: &#8211; mysql &#8211; nacos networks: &#8211; ecommerce-network<\/p>\n<p>rocketmq: image: apache\/rocketmq:5.1.0 container_name: ecommerce-rocketmq ports: &#8211; \u201c9876:9876\u201d &#8211; \u201c10909:10909\u201d &#8211; \u201c10911:10911\u201d volumes: &#8211; rocketmq_logs:\/home\/rocketmq\/logs &#8211; rocketmq_store:\/home\/rocketmq\/store networks: &#8211; ecommerce-network<\/p>\n<h2>\u76d1\u63a7\u670d\u52a1<\/h2>\n<p>prometheus: image: prom\/prometheus:v2.44.0 container_name: ecommerce-prometheus ports: &#8211; \u201c9090:9090\u201d volumes: &#8211; .\/config\/prometheus\/prometheus.yml:\/etc\/prometheus\/prometheus.yml &#8211; prometheus_data:\/prometheus command: &#8211; \u2018\u2013config.file&#061;\/etc\/prometheus\/prometheus.yml\u2019 &#8211; \u2018\u2013storage.tsdb.path&#061;\/prometheus\u2019 &#8211; \u2018\u2013web.console.libraries&#061;\/etc\/prometheus\/console_libraries\u2019 &#8211; \u2018\u2013web.console.templates&#061;\/etc\/prometheus\/consoles\u2019 &#8211; \u2018\u2013storage.tsdb.retention.time&#061;200h\u2019 &#8211; \u2018\u2013web.enable-lifecycle\u2019 networks: &#8211; ecommerce-network<\/p>\n<p>grafana: image: grafana\/grafana:9.5.2 container_name: ecommerce-grafana ports: &#8211; \u201c3000:3000\u201d environment: &#8211; GF_SECURITY_ADMIN_PASSWORD&#061;admin123 volumes: &#8211; grafana_data:\/var\/lib\/grafana &#8211; .\/config\/grafana\/dashboards:\/etc\/grafana\/provisioning\/dashboards &#8211; .\/config\/grafana\/datasources:\/etc\/grafana\/provisioning\/datasources depends_on: &#8211; prometheus networks: &#8211; ecommerce-network<\/p>\n<h2>\u5e94\u7528\u670d\u52a1<\/h2>\n<p>gateway: build: context: .\/e-commerce-parent\/e-commerce-gateway dockerfile: Dockerfile container_name: ecommerce-gateway ports: &#8211; \u201c8080:8080\u201d environment: &#8211; SPRING_PROFILES_ACTIVE&#061;docker &#8211; SPRING_CLOUD_NACOS_SERVER_ADDR&#061;nacos:8848 &#8211; SPRING_CLOUD_NACOS_USERNAME&#061;nacos &#8211; SPRING_CLOUD_NACOS_PASSWORD&#061;nacos &#8211; SPRING_REDIS_HOST&#061;redis &#8211; SPRING_REDIS_PORT&#061;6379 depends_on: nacos: condition: service_healthy redis: condition: service_healthy networks: &#8211; ecommerce-network deploy: replicas: 2<\/p>\n<p>user-service: build: context: .\/e-commerce-parent\/e-commerce-user dockerfile: Dockerfile container_name: ecommerce-user ports: &#8211; \u201c8082:8082\u201d environment: &#8211; SPRING_PROFILES_ACTIVE&#061;docker &#8211; SPRING_CLOUD_NACOS_SERVER_ADDR&#061;nacos:8848 &#8211; SPRING_DATASOURCE_URL&#061;jdbc:mysql:\/\/mysql:3306\/user_db &#8211; SPRING_DATASOURCE_USERNAME&#061;root &#8211; SPRING_DATASOURCE_PASSWORD&#061;ecommerce123 &#8211; SPRING_REDIS_HOST&#061;redis &#8211; SPRING_REDIS_PORT&#061;6379 depends_on: &#8211; nacos &#8211; mysql &#8211; redis networks: &#8211; ecommerce-network deploy: replicas: 3<\/p>\n<p>product-service: build: context: .\/e-commerce-parent\/e-commerce-product dockerfile: Dockerfile container_name: ecommerce-product ports: &#8211; \u201c8083:8083\u201d environment: &#8211; SPRING_PROFILES_ACTIVE&#061;docker &#8211; SPRING_CLOUD_NACOS_SERVER_ADDR&#061;nacos:8848 &#8211; SPRING_DATASOURCE_URL&#061;jdbc:mysql:\/\/mysql:3306\/product_db &#8211; SPRING_DATASOURCE_USERNAME&#061;root &#8211; SPRING_DATASOURCE_PASSWORD&#061;ecommerce123 &#8211; SPRING_REDIS_HOST&#061;redis &#8211; SPRING_REDIS_PORT&#061;6379 &#8211; SPRING_ELASTICSEARCH_HOST&#061;elasticsearch &#8211; SPRING_ELASTICSEARCH_PORT&#061;9200 depends_on: &#8211; nacos &#8211; mysql &#8211; redis networks: &#8211; ecommerce-network deploy: replicas: 3<\/p>\n<p>order-service: build: context: .\/e-commerce-parent\/e-commerce-order dockerfile: Dockerfile container_name: ecommerce-order ports: &#8211; \u201c8085:8085\u201d environment: &#8211; SPRING_PROFILES_ACTIVE&#061;docker &#8211; SPRING_CLOUD_NACOS_SERVER_ADDR&#061;nacos:8848 &#8211; SPRING_DATASOURCE_URL&#061;jdbc:mysql:\/\/mysql:3306\/order_db &#8211; SPRING_DATASOURCE_USERNAME&#061;root &#8211; SPRING_DATASOURCE_PASSWORD&#061;ecommerce123 &#8211; SPRING_REDIS_HOST&#061;redis &#8211; SPRING_REDIS_PORT&#061;6379 &#8211; SEATA_SERVER_HOST&#061;seata &#8211; ROCKETMQ_NAME_SERVER&#061;rocketmq:9876 depends_on: &#8211; nacos &#8211; mysql &#8211; redis &#8211; seata &#8211; rocketmq networks: &#8211; ecommerce-network deploy: replicas: 3<\/p>\n<p>networks: ecommerce-network: driver: bridge<\/p>\n<p>volumes: mysql_data: redis_data: nacos_logs: rocketmq_logs: rocketmq_store: prometheus_data: grafana_data: Kubernetes\u90e8\u7f72\u6587\u4ef6&#xff1a;<\/p>\n<p>yaml<\/p>\n<h2>namespace.yaml<\/h2>\n<h3>apiVersion: v1 kind: Namespace metadata: name: ecommerce<\/h3>\n<h2>configmap.yaml<\/h2>\n<h3>apiVersion: v1 kind: ConfigMap metadata: name: ecommerce-config namespace: ecommerce data: application.yml: | spring: cloud: nacos: discovery: server-addr: nacos:8848 config: server-addr: nacos:8848 redis: host: redis port: 6379<\/h3>\n<h2>secret.yaml<\/h2>\n<h3>apiVersion: v1 kind: Secret metadata: name: ecommerce-secrets namespace: ecommerce type: Opaque data: mysql-password: ZWNvbW1lcmNlMTIz # ecommerce123 base64\u7f16\u7801 nacos-password: bmFjb3M&#061; # nacos base64\u7f16\u7801<\/h3>\n<h2>deployment.yaml<\/h2>\n<h3>apiVersion: apps\/v1 kind: Deployment metadata: name: user-service namespace: ecommerce labels: app: user-service spec: replicas: 3 selector: matchLabels: app: user-service template: metadata: labels: app: user-service spec: containers: &#8211; name: user-service image: ecommerce\/user-service:1.0.0 ports: &#8211; containerPort: 8082 env: &#8211; name: SPRING_PROFILES_ACTIVE value: \u201ck8s\u201d &#8211; name: SPRING_DATASOURCE_URL value: \u201cjdbc:mysql:\/\/mysql:3306\/user_db\u201d &#8211; name: SPRING_DATASOURCE_USERNAME value: \u201croot\u201d &#8211; name: SPRING_DATASOURCE_PASSWORD valueFrom: secretKeyRef: name: ecommerce-secrets key: mysql-password resources: requests: memory: \u201c512Mi\u201d cpu: \u201c250m\u201d limits: memory: \u201c1Gi\u201d cpu: \u201c500m\u201d livenessProbe: httpGet: path: \/actuator\/health\/liveness port: 8082 initialDelaySeconds: 60 periodSeconds: 10 readinessProbe: httpGet: path: \/actuator\/health\/readiness port: 8082 initialDelaySeconds: 30 periodSeconds: 5<\/h3>\n<h2>service.yaml<\/h2>\n<p>apiVersion: v1 kind: Service metadata: name: user-service namespace: ecommerce spec: selector: app: user-service ports:<\/p>\n<ul>\n<li>port: 8082 targetPort: 8082 type: ClusterIP<\/li>\n<\/ul>\n<hr \/>\n<h2>ingress.yaml<\/h2>\n<p>apiVersion: networking.k8s.io\/v1 kind: Ingress metadata: name: ecommerce-ingress namespace: ecommerce annotations: nginx.ingress.kubernetes.io\/rewrite-target: \/ nginx.ingress.kubernetes.io\/ssl-redirect: \u201cfalse\u201d spec: rules:<\/p>\n<ul>\n<li>host: api.ecommerce.com http: paths:\n<ul>\n<li>path: \/user pathType: Prefix backend: service: name: user-service port: number: 8082<\/li>\n<li>path: \/product pathType: Prefix backend: service: name: product-service port: number: 8083<\/li>\n<li>path: \/order pathType: Prefix backend: service: name: order-service port: number: 8085<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<hr \/>\n<h2>hpa.yaml<\/h2>\n<p>apiVersion: autoscaling\/v2 kind: HorizontalPodAutoscaler metadata: name: user-service-hpa namespace: ecommerce spec: scaleTargetRef: apiVersion: apps\/v1 kind: Deployment name: user-service minReplicas: 2 maxReplicas: 10 metrics:<\/p>\n<ul>\n<li>type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70<\/li>\n<li>type: Resource resource: name: memory target: type: Utilization averageUtilization: 80<\/li>\n<\/ul>\n<h3>\u7b2c\u4e5d\u7ae0&#xff1a;\u9879\u76ee\u603b\u7ed3\u4e0e\u6269\u5c55<\/h3>\n<p>9.1 \u9879\u76ee\u67b6\u6784\u603b\u7ed3 \u5df2\u5b9e\u73b0\u7684\u6838\u5fc3\u529f\u80fd&#xff1a;<\/p>\n<p>text \u2705 \u7528\u6237\u670d\u52a1&#xff1a;\u6ce8\u518c\u3001\u767b\u5f55\u3001\u7528\u6237\u7ba1\u7406 \u2705 \u5546\u54c1\u670d\u52a1&#xff1a;\u5546\u54c1\u7ba1\u7406\u3001\u641c\u7d22\u3001\u7f13\u5b58 \u2705 \u8ba2\u5355\u670d\u52a1&#xff1a;\u521b\u5efa\u8ba2\u5355\u3001\u72b6\u6001\u7ba1\u7406\u3001\u5206\u5e03\u5f0f\u4e8b\u52a1 \u2705 \u652f\u4ed8\u670d\u52a1&#xff1a;\u652f\u4ed8\u96c6\u6210\u3001\u56de\u8c03\u5904\u7406 \u2705 \u5e93\u5b58\u670d\u52a1&#xff1a;\u5e93\u5b58\u7ba1\u7406\u3001\u5206\u5e03\u5f0f\u9501 \u2705 \u4f18\u60e0\u5238\u670d\u52a1&#xff1a;\u4f18\u60e0\u5238\u7ba1\u7406\u3001\u4f7f\u7528 \u2705 \u7f51\u5173\u670d\u52a1&#xff1a;\u8def\u7531\u3001\u8ba4\u8bc1\u3001\u9650\u6d41\u3001\u7194\u65ad \u2705 \u914d\u7f6e\u4e2d\u5fc3&#xff1a;\u7edf\u4e00\u914d\u7f6e\u7ba1\u7406 \u2705 \u670d\u52a1\u6ce8\u518c\u53d1\u73b0&#xff1a;Nacos\u670d\u52a1\u6cbb\u7406 \u2705 \u76d1\u63a7\u544a\u8b66&#xff1a;Prometheus &#043; Grafana \u2705 \u94fe\u8def\u8ffd\u8e2a&#xff1a;Sleuth &#043; Zipkin \u2705 \u6d88\u606f\u961f\u5217&#xff1a;RocketMQ\u5f02\u6b65\u5904\u7406 \u2705 \u5206\u5e03\u5f0f\u4e8b\u52a1&#xff1a;Seata TCC\u6a21\u5f0f \u2705 \u5bb9\u5668\u5316\u90e8\u7f72&#xff1a;Docker &#043; Kubernetes \u6027\u80fd\u4f18\u5316\u70b9&#xff1a;<\/p>\n<p>text<\/p>\n<li>\u7f13\u5b58\u7b56\u7565&#xff1a;\u591a\u7ea7\u7f13\u5b58&#xff08;Redis &#043; \u672c\u5730\u7f13\u5b58&#xff09;<\/li>\n<li>\u6570\u636e\u5e93\u4f18\u5316&#xff1a;\u8bfb\u5199\u5206\u79bb\u3001\u5206\u5e93\u5206\u8868<\/li>\n<li>\u5f02\u6b65\u5904\u7406&#xff1a;\u6d88\u606f\u961f\u5217\u89e3\u8026<\/li>\n<li>\u9650\u6d41\u7194\u65ad&#xff1a;\u4fdd\u62a4\u6838\u5fc3\u670d\u52a1<\/li>\n<li>\u5206\u5e03\u5f0f\u9501&#xff1a;\u4fdd\u8bc1\u6570\u636e\u4e00\u81f4\u6027<\/li>\n<li>\u8fde\u63a5\u6c60\u4f18\u5316&#xff1a;\u6570\u636e\u5e93\u3001Redis\u8fde\u63a5\u6c60<\/li>\n<li>JVM\u4f18\u5316&#xff1a;GC\u8c03\u4f18\u3001\u5185\u5b58\u914d\u7f6e<\/li>\n<li>\u9759\u6001\u8d44\u6e90&#xff1a;CDN\u52a0\u901f 9.2 \u6269\u5c55\u529f\u80fd\u5efa\u8bae<\/li>\n<li>\u79d2\u6740\u7cfb\u7edf&#xff1a;<\/li>\n<p>java \/\/ \u79d2\u6740\u670d\u52a1\u8bbe\u8ba1 &#064;Service public class SeckillService {<\/p>\n<p>\/\/ 1. \u5e93\u5b58\u9884\u70ed\u5230Redis<br \/>\npublic void preheatStock(Long productId, Integer stock) {<br \/>\n    redisTemplate.opsForValue().set(&#034;seckill:stock:&#034; &#043; productId, stock);<br \/>\n}<\/p>\n<p>\/\/ 2. \u7528\u6237\u8bf7\u6c42\u6392\u961f<br \/>\npublic SeckillResult seckill(Long userId, Long productId) {<br \/>\n    \/\/ \u9a8c\u8bc1\u7528\u6237\u8d44\u683c<br \/>\n    if (!checkUserQualification(userId)) {<br \/>\n        return SeckillResult.fail(&#034;\u7528\u6237\u8d44\u683c\u4e0d\u7b26&#034;);<br \/>\n    }<\/p>\n<p>    \/\/ \u9a8c\u8bc1\u79d2\u6740\u65f6\u95f4<br \/>\n    if (!checkSeckillTime(productId)) {<br \/>\n        return SeckillResult.fail(&#034;\u79d2\u6740\u672a\u5f00\u59cb\u6216\u5df2\u7ed3\u675f&#034;);<br \/>\n    }<\/p>\n<p>    \/\/ \u83b7\u53d6Redis\u5206\u5e03\u5f0f\u9501<br \/>\n    RLock lock &#061; redissonClient.getLock(&#034;seckill:lock:&#034; &#043; productId);<\/p>\n<p>    try {<br \/>\n        boolean acquired &#061; lock.tryLock(100, 10, TimeUnit.MILLISECONDS);<br \/>\n        if (!acquired) {<br \/>\n            return SeckillResult.fail(&#034;\u7cfb\u7edf\u7e41\u5fd9&#034;);<br \/>\n        }<\/p>\n<p>        \/\/ \u9884\u51cf\u5e93\u5b58<br \/>\n        Long stock &#061; redisTemplate.opsForValue().decrement(&#034;seckill:stock:&#034; &#043; productId);<br \/>\n        if (stock &#061;&#061; null || stock &lt; 0) {<br \/>\n            \/\/ \u5e93\u5b58\u4e0d\u8db3&#xff0c;\u6062\u590d\u5e93\u5b58<br \/>\n            redisTemplate.opsForValue().increment(&#034;seckill:stock:&#034; &#043; productId);<br \/>\n            return SeckillResult.fail(&#034;\u5e93\u5b58\u4e0d\u8db3&#034;);<br \/>\n        }<\/p>\n<p>        \/\/ \u751f\u6210\u79d2\u6740\u8ba2\u5355\u53f7<br \/>\n        String seckillOrderNo &#061; generateSeckillOrderNo();<\/p>\n<p>        \/\/ \u53d1\u9001\u6d88\u606f\u5230MQ&#xff0c;\u5f02\u6b65\u521b\u5efa\u8ba2\u5355<br \/>\n        sendSeckillOrderMessage(userId, productId, seckillOrderNo);<\/p>\n<p>        return SeckillResult.success(seckillOrderNo);<\/p>\n<p>    } catch (InterruptedException e) {<br \/>\n        Thread.currentThread().interrupt();<br \/>\n        return SeckillResult.fail(&#034;\u7cfb\u7edf\u5f02\u5e38&#034;);<br \/>\n    } finally {<br \/>\n        if (lock.isHeldByCurrentThread()) {<br \/>\n            lock.unlock();<br \/>\n        }<br \/>\n    }<br \/>\n}<\/p>\n<p>} 2. \u63a8\u8350\u7cfb\u7edf&#xff1a;<\/p>\n<p>java \/\/ \u57fa\u4e8e\u7528\u6237\u884c\u4e3a\u7684\u63a8\u8350 &#064;Service public class RecommendationService {<\/p>\n<p>\/\/ \u534f\u540c\u8fc7\u6ee4\u63a8\u8350<br \/>\npublic List&lt;ProductDTO&gt; recommendByCF(Long userId) {<br \/>\n    \/\/ 1. \u83b7\u53d6\u7528\u6237\u5386\u53f2\u884c\u4e3a<br \/>\n    List&lt;UserBehavior&gt; behaviors &#061; getUserBehaviors(userId);<\/p>\n<p>    \/\/ 2. \u8ba1\u7b97\u7528\u6237\u76f8\u4f3c\u5ea6<br \/>\n    Map&lt;Long, Double&gt; userSimilarities &#061; calculateUserSimilarities(userId);<\/p>\n<p>    \/\/ 3. \u83b7\u53d6\u76f8\u4f3c\u7528\u6237\u559c\u6b22\u7684\u5546\u54c1<br \/>\n    Set&lt;Long&gt; recommendedProductIds &#061; getProductsFromSimilarUsers(userSimilarities);<\/p>\n<p>    \/\/ 4. \u8fc7\u6ee4\u5df2\u8d2d\u4e70\u5546\u54c1<br \/>\n    recommendedProductIds.removeAll(getPurchasedProducts(userId));<\/p>\n<p>    \/\/ 5. \u83b7\u53d6\u5546\u54c1\u8be6\u60c5<br \/>\n    return productService.getProductsByIds(new ArrayList&lt;&gt;(recommendedProductIds));<br \/>\n}<\/p>\n<p>\/\/ \u5b9e\u65f6\u63a8\u8350&#xff08;\u57fa\u4e8eRedis&#xff09;<br \/>\npublic List&lt;ProductDTO&gt; getRealTimeRecommendations(Long userId) {<br \/>\n    String key &#061; &#034;recommend:realtime:&#034; &#043; userId;<\/p>\n<p>    \/\/ \u4eceRedis\u83b7\u53d6\u5b9e\u65f6\u63a8\u8350<br \/>\n    List&lt;Long&gt; productIds &#061; (List&lt;Long&gt;) redisTemplate.opsForValue().get(key);<\/p>\n<p>    if (productIds &#061;&#061; null || productIds.isEmpty()) {<br \/>\n        \/\/ \u91cd\u65b0\u8ba1\u7b97\u63a8\u8350<br \/>\n        productIds &#061; calculateRealTimeRecommendations(userId);<br \/>\n        redisTemplate.opsForValue().set(key, productIds, 10, TimeUnit.MINUTES);<br \/>\n    }<\/p>\n<p>    return productService.getProductsByIds(productIds);<br \/>\n}<\/p>\n<p>} 3. \u6570\u636e\u7edf\u8ba1\u5206\u6790&#xff1a;<\/p>\n<p>java \/\/ \u5b9e\u65f6\u6570\u636e\u7edf\u8ba1 &#064;Service public class DataAnalysisService {<\/p>\n<p>\/\/ \u4f7f\u7528Flink\u8fdb\u884c\u5b9e\u65f6\u6570\u636e\u5206\u6790<br \/>\npublic void realTimeAnalysis() {<br \/>\n    StreamExecutionEnvironment env &#061; StreamExecutionEnvironment.getExecutionEnvironment();<\/p>\n<p>    \/\/ \u4eceKafka\u8bfb\u53d6\u8ba2\u5355\u6570\u636e<br \/>\n    DataStream&lt;OrderEvent&gt; orderStream &#061; env<br \/>\n        .addSource(new FlinkKafkaConsumer&lt;&gt;(<br \/>\n            &#034;order-topic&#034;,<br \/>\n            new OrderEventSchema(),<br \/>\n            properties))<br \/>\n        .name(&#034;\u8ba2\u5355\u6570\u636e\u6e90&#034;);<\/p>\n<p>    \/\/ \u5b9e\u65f6\u7edf\u8ba1<br \/>\n    orderStream<br \/>\n        .keyBy(OrderEvent::getProductId)<br \/>\n        .timeWindow(Time.minutes(5))<br \/>\n        .aggregate(new ProductSalesAggregator())<br \/>\n        .addSink(new RedisSink&lt;&gt;());<\/p>\n<p>    env.execute(&#034;\u5b9e\u65f6\u6570\u636e\u5206\u6790&#034;);<br \/>\n}<\/p>\n<p>\/\/ \u5546\u54c1\u9500\u552e\u805a\u5408\u5668<br \/>\npublic static class ProductSalesAggregator<br \/>\n        implements AggregateFunction&lt;OrderEvent, ProductSales, ProductSales&gt; {<\/p>\n<p>    &#064;Override<br \/>\n    public ProductSales createAccumulator() {<br \/>\n        return new ProductSales();<br \/>\n    }<\/p>\n<p>    &#064;Override<br \/>\n    public ProductSales add(OrderEvent event, ProductSales accumulator) {<br \/>\n        accumulator.setProductId(event.getProductId());<br \/>\n        accumulator.setSalesCount(accumulator.getSalesCount() &#043; event.getQuantity());<br \/>\n        accumulator.setSalesAmount(accumulator.getSalesAmount() &#043;<br \/>\n            event.getPrice() * event.getQuantity());<br \/>\n        return accumulator;<br \/>\n    }<\/p>\n<p>    &#064;Override<br \/>\n    public ProductSales getResult(ProductSales accumulator) {<br \/>\n        return accumulator;<br \/>\n    }<\/p>\n<p>    &#064;Override<br \/>\n    public ProductSales merge(ProductSales a, ProductSales b) {<br \/>\n        a.setSalesCount(a.getSalesCount() &#043; b.getSalesCount());<br \/>\n        a.setSalesAmount(a.getSalesAmount() &#043; b.getSalesAmount());<br \/>\n        return a;<br \/>\n    }<br \/>\n}<\/p>\n<p>} 9.3 \u5b66\u4e60\u6536\u83b7\u4e0e\u804c\u4e1a\u53d1\u5c55 \u901a\u8fc7\u672c\u9879\u76ee\u4f60\u5b66\u4f1a\u4e86&#xff1a;<\/p>\n<p>\u2705 \u5fae\u670d\u52a1\u67b6\u6784\u8bbe\u8ba1\u4e0e\u62c6\u5206<\/p>\n<p>\u2705 Spring Cloud\u5168\u5bb6\u6876\u5b9e\u6218<\/p>\n<p>\u2705 \u5206\u5e03\u5f0f\u7cfb\u7edf\u6838\u5fc3\u95ee\u9898\u89e3\u51b3\u65b9\u6848<\/p>\n<p>\u2705 \u9ad8\u5e76\u53d1\u573a\u666f\u4e0b\u7684\u6027\u80fd\u4f18\u5316<\/p>\n<p>\u2705 \u5bb9\u5668\u5316\u4e0e\u4e91\u539f\u751f\u90e8\u7f72<\/p>\n<p>\u2705 \u5168\u94fe\u8def\u76d1\u63a7\u4e0e\u6545\u969c\u6392\u67e5<\/p>\n<p>\u2705 \u56e2\u961f\u534f\u4f5c\u4e0e\u9879\u76ee\u7ba1\u7406<\/p>\n<p>\u9762\u8bd5\u52a0\u5206\u9879&#xff1a;<\/p>\n<p>text<\/p>\n<li>\u9879\u76ee\u7ecf\u9a8c&#xff1a;\u5b8c\u6574\u7535\u5546\u5fae\u670d\u52a1\u9879\u76ee<\/li>\n<li>\u6280\u672f\u6df1\u5ea6&#xff1a;Spring Cloud\u6e90\u7801\u7406\u89e3<\/li>\n<li>\u67b6\u6784\u80fd\u529b&#xff1a;\u7cfb\u7edf\u8bbe\u8ba1\u3001\u6027\u80fd\u4f18\u5316<\/li>\n<li>\u89e3\u51b3\u95ee\u9898&#xff1a;\u5206\u5e03\u5f0f\u4e8b\u52a1\u3001\u7f13\u5b58\u4e00\u81f4\u6027\u95ee\u9898<\/li>\n<li>\u8fd0\u7ef4\u80fd\u529b&#xff1a;Docker\u3001Kubernetes\u3001\u76d1\u63a7 \u804c\u4e1a\u53d1\u5c55\u5efa\u8bae&#xff1a;<\/li>\n<p>text \u521d\u7ea7 \u2192 \u4e2d\u7ea7 \u2192 \u9ad8\u7ea7 \u2192 \u67b6\u6784\u5e08 \u2192 \u6280\u672f\u4e13\u5bb6<\/p>\n<p>\u5b66\u4e60\u8def\u7ebf&#xff1a;<\/p>\n<li>\u592f\u5b9e\u57fa\u7840&#xff1a;Java\u3001Spring\u3001\u6570\u636e\u5e93\u3001\u7f51\u7edc<\/li>\n<li>\u638c\u63e1\u6846\u67b6&#xff1a;Spring Cloud\u3001MyBatis\u3001Redis<\/li>\n<li>\u5b66\u4e60\u67b6\u6784&#xff1a;\u5fae\u670d\u52a1\u3001DDD\u3001\u4e91\u539f\u751f<\/li>\n<li>\u6df1\u5165\u539f\u7406&#xff1a;JVM\u3001\u64cd\u4f5c\u7cfb\u7edf\u3001\u7f51\u7edc\u539f\u7406<\/li>\n<li>\u62d3\u5c55\u89c6\u91ce&#xff1a;\u5927\u6570\u636e\u3001AI\u3001\u533a\u5757\u94fe \u7b2c\u5341\u7ae0&#xff1a;\u8d44\u6e90\u4e0e\u540e\u7eed\u5b66\u4e60 10.1 \u9879\u76ee\u8d44\u6e90\u4e0b\u8f7d GitHub\u4ed3\u5e93\u5730\u5740&#xff1a;<\/li>\n<p>text https:\/\/github.com\/yourusername\/e-commerce-microservices<\/p>\n<p>\u5305\u542b\u5185\u5bb9&#xff1a; \u251c\u2500\u2500 \u5b8c\u6574\u6e90\u4ee3\u7801 \u251c\u2500\u2500 \u6570\u636e\u5e93\u811a\u672c \u251c\u2500\u2500 Docker\u914d\u7f6e\u6587\u4ef6 \u251c\u2500\u2500 Kubernetes\u90e8\u7f72\u6587\u4ef6 \u251c\u2500\u2500 \u63a5\u53e3\u6587\u6863 \u251c\u2500\u2500 \u67b6\u6784\u8bbe\u8ba1\u6587\u6863 \u2514\u2500\u2500 \u5b66\u4e60\u8def\u7ebf\u56fe \u5feb\u901f\u542f\u52a8\u547d\u4ee4&#xff1a;<\/p>\n<p>bash<\/p>\n<h2>1. \u514b\u9686\u9879\u76ee<\/h2>\n<p>git clone https:\/\/github.com\/yourusername\/e-commerce-microservices.git<\/p>\n<h2>2. \u542f\u52a8\u57fa\u7840\u8bbe\u65bd<\/h2>\n<p>cd e-commerce-microservices docker-compose up -d mysql redis nacos seata rocketmq<\/p>\n<h2>3. \u5bfc\u5165\u6570\u636e\u5e93<\/h2>\n<p>mysql -uroot -p &lt; sql\/init.sql<\/p>\n<h2>4. \u542f\u52a8\u670d\u52a1<\/h2>\n<h2>\u65b9\u5f0f1&#xff1a;IDEA\u4e2d\u9010\u4e2a\u542f\u52a8<\/h2>\n<h2>\u65b9\u5f0f2&#xff1a;\u4f7f\u7528Maven<\/h2>\n<p>mvn spring-boot:run -pl e-commerce-parent\/e-commerce-user mvn spring-boot:run -pl e-commerce-parent\/e-commerce-product<\/p>\n<h2>\u2026<\/h2>\n<h2>5. \u8bbf\u95ee\u7cfb\u7edf<\/h2>\n<h2>\u7f51\u5173&#xff1a;http:\/\/localhost:8080<\/h2>\n<h2>Nacos&#xff1a;http:\/\/localhost:8848\/nacos (nacos\/nacos)<\/h2>\n<h2>Grafana&#xff1a;http:\/\/localhost:3000 (admin\/admin123)<\/h2>\n<p>10.2 \u5b66\u4e60\u8def\u7ebf\u56fe 30\u5929\u5b66\u4e60\u8ba1\u5212&#xff1a;<\/p>\n<p>text \u7b2c1\u5468&#xff1a;Spring Cloud\u57fa\u7840 Day1-2&#xff1a;Nacos\u670d\u52a1\u6ce8\u518c\u4e0e\u53d1\u73b0 Day3-4&#xff1a;OpenFeign\u670d\u52a1\u8c03\u7528 Day5-6&#xff1a;Spring Cloud Gateway Day7&#xff1a;\u9879\u76ee\u642d\u5efa\u4e0e\u8c03\u8bd5<\/p>\n<p>\u7b2c2\u5468&#xff1a;\u6838\u5fc3\u4e1a\u52a1\u5f00\u53d1 Day8-9&#xff1a;\u7528\u6237\u670d\u52a1\u5b9e\u73b0 Day10-11&#xff1a;\u5546\u54c1\u670d\u52a1\u5b9e\u73b0 Day12-13&#xff1a;\u8ba2\u5355\u670d\u52a1\u5b9e\u73b0 Day14&#xff1a;\u652f\u4ed8\u670d\u52a1\u5b9e\u73b0<\/p>\n<p>\u7b2c3\u5468&#xff1a;\u9ad8\u7ea7\u7279\u6027 Day15&#xff1a;\u5206\u5e03\u5f0f\u4e8b\u52a1Seata Day16&#xff1a;\u6d88\u606f\u961f\u5217RocketMQ Day17&#xff1a;\u7f13\u5b58\u4e0e\u5206\u5e03\u5f0f\u9501 Day18&#xff1a;\u76d1\u63a7\u4e0e\u94fe\u8def\u8ffd\u8e2a Day19&#xff1a;\u5b89\u5168\u4e0e\u8ba4\u8bc1 Day20&#xff1a;\u6027\u80fd\u4f18\u5316<\/p>\n<p>\u7b2c4\u5468&#xff1a;\u90e8\u7f72\u4e0e\u8fd0\u7ef4 Day21-22&#xff1a;Docker\u5bb9\u5668\u5316 Day23-24&#xff1a;Kubernetes\u90e8\u7f72 Day25-26&#xff1a;CI\/CD\u6d41\u6c34\u7ebf Day27-28&#xff1a;\u538b\u529b\u6d4b\u8bd5\u4e0e\u8c03\u4f18 Day29-30&#xff1a;\u9879\u76ee\u603b\u7ed3\u4e0e\u9762\u8bd5\u51c6\u5907 10.3 \u5e38\u89c1\u95ee\u9898\u89e3\u7b54 Q1&#xff1a;\u9879\u76ee\u542f\u52a8\u62a5\u9519\u600e\u4e48\u529e&#xff1f;<\/p>\n<p>text A&#xff1a;\u5e38\u89c1\u95ee\u9898\u6392\u67e5&#xff1a;<\/p>\n<li>\u68c0\u67e5Java\u7248\u672c&#xff08;\u9700\u8981Java 17&#043;&#xff09;<\/li>\n<li>\u68c0\u67e5MySQL\u3001Redis\u3001Nacos\u662f\u5426\u542f\u52a8<\/li>\n<li>\u68c0\u67e5\u6570\u636e\u5e93\u8fde\u63a5\u914d\u7f6e<\/li>\n<li>\u68c0\u67e5\u4f9d\u8d56\u662f\u5426\u4e0b\u8f7d\u5b8c\u6574<\/li>\n<li>\u67e5\u770b\u65e5\u5fd7\u6587\u4ef6\u5b9a\u4f4d\u95ee\u9898 Q2&#xff1a;\u5982\u4f55\u4fee\u6539\u914d\u7f6e&#xff1f;<\/li>\n<p>text A&#xff1a;\u914d\u7f6e\u4fee\u6539\u4f4d\u7f6e&#xff1a;<\/p>\n<li>\u516c\u5171\u914d\u7f6e&#xff1a;config\/\u76ee\u5f55\u4e0b<\/li>\n<li>\u670d\u52a1\u914d\u7f6e&#xff1a;bootstrap.yml &#043; application.yml<\/li>\n<li>\u73af\u5883\u914d\u7f6e&#xff1a;\u901a\u8fc7profile\u5207\u6362<\/li>\n<li>\u8fd0\u884c\u65f6\u914d\u7f6e&#xff1a;\u901a\u8fc7Nacos\u52a8\u6001\u914d\u7f6e Q3&#xff1a;\u5982\u4f55\u6269\u5c55\u65b0\u529f\u80fd&#xff1f;<\/li>\n<p>text A&#xff1a;\u6269\u5c55\u6b65\u9aa4&#xff1a;<\/p>\n<li>\u5728\u7236\u5de5\u7a0b\u4e2d\u521b\u5efa\u65b0\u6a21\u5757<\/li>\n<li>\u7ee7\u627f\u516c\u5171\u6a21\u5757\u4f9d\u8d56<\/li>\n<li>\u5b9e\u73b0\u4e1a\u52a1\u903b\u8f91<\/li>\n<li>\u6ce8\u518c\u5230Nacos<\/li>\n<li>\u5728\u7f51\u5173\u914d\u7f6e\u8def\u7531<\/li>\n<li>\u66f4\u65b0\u90e8\u7f72\u914d\u7f6e \u7ed3\u8bed&#xff1a;\u4ece\u5b66\u4e60\u8005\u5230\u67b6\u6784\u5e08\u7684\u8715\u53d8 \u606d\u559c\u4f60\u5b8c\u6210\u4e86\u8fd9\u4e2a\u5b8c\u6574\u7684\u7535\u5546\u5fae\u670d\u52a1\u7cfb\u7edf\u9879\u76ee&#xff01;\u8fd9\u4e0d\u4ec5\u4ec5\u662f\u4e00\u4e2a\u6280\u672f\u9879\u76ee&#xff0c;\u66f4\u662f\u4f60\u6280\u672f\u6210\u957f\u7684\u91cd\u8981\u91cc\u7a0b\u7891\u3002<\/li>\n<p>\u5173\u952e\u6536\u83b7&#xff1a;<\/p>\n<p>\u2705 \u638c\u63e1\u4e86\u4f01\u4e1a\u7ea7\u5fae\u670d\u52a1\u67b6\u6784\u8bbe\u8ba1<\/p>\n<p>\u2705 \u7406\u89e3\u4e86\u5206\u5e03\u5f0f\u7cfb\u7edf\u6838\u5fc3\u6982\u5ff5<\/p>\n<p>\u2705 \u79ef\u7d2f\u4e86\u5b8c\u6574\u7684\u9879\u76ee\u5f00\u53d1\u7ecf\u9a8c<\/p>\n<p>\u2705 \u5177\u5907\u4e86\u89e3\u51b3\u590d\u6742\u95ee\u9898\u7684\u80fd\u529b<\/p>\n<p>\u2705 \u5efa\u7acb\u4e86\u6280\u672f\u81ea\u4fe1\u548c\u5b66\u4e60\u65b9\u6cd5<\/p>\n<p>\u8bb0\u4f4f&#xff1a;<\/p>\n<p>\u6280\u672f\u7684\u5b66\u4e60\u6c38\u65e0\u6b62\u5883&#xff0c;\u4f46\u6bcf\u4e2a\u5b8c\u6574\u7684\u9879\u76ee\u90fd\u662f\u4f60\u804c\u4e1a\u751f\u6daf\u7684\u575a\u5b9e\u57fa\u77f3\u3002 \u4ece\u4eca\u5929\u8d77&#xff0c;\u4f60\u4e0d\u518d\u53ea\u662f\u6280\u672f\u7684\u4f7f\u7528\u8005&#xff0c;\u800c\u662f\u67b6\u6784\u7684\u8bbe\u8ba1\u8005\u3002<\/p>\n<p>\u4e0b\u4e00\u6b65\u884c\u52a8&#xff1a;<\/p>\n<p>\u8fd0\u884c\u9879\u76ee&#xff1a;\u786e\u4fdd\u6240\u6709\u670d\u52a1\u6b63\u5e38\u8fd0\u884c<\/p>\n<p>\u6df1\u5165\u7814\u7a76&#xff1a;\u9009\u62e9\u611f\u5174\u8da3\u7684\u90e8\u5206\u6df1\u5165\u6e90\u7801<\/p>\n<p>\u5206\u4eab\u7ecf\u9a8c&#xff1a;\u5199\u535a\u5ba2\u3001\u505a\u5206\u4eab\u3001\u5e2e\u52a9\u4ed6\u4eba<\/p>\n<p>\u53c2\u4e0e\u5f00\u6e90&#xff1a;\u8d21\u732e\u4ee3\u7801&#xff0c;\u52a0\u5165\u6280\u672f\u793e\u533a<\/p>\n<p>\u6301\u7eed\u5b66\u4e60&#xff1a;\u5173\u6ce8\u65b0\u6280\u672f&#xff0c;\u4e0d\u65ad\u66f4\u65b0\u77e5\u8bc6\u4f53\u7cfb<\/p>\n<p>\u7279\u522b\u798f\u5229&#xff1a; \u5173\u6ce8\u6211\u5e76\u79c1\u4fe1&#034;\u7535\u5546\u5fae\u670d\u52a1&#034;&#xff0c;\u83b7\u53d6&#xff1a;<\/p>\n<p>\u672c\u6587\u5b8c\u6574Markdown\u7248\u672c<\/p>\n<p>\u9879\u76eePPT\u6f14\u793a\u6587\u7a3f<\/p>\n<p>\u9762\u8bd5\u5e38\u89c1\u95ee\u9898\u53ca\u7b54\u6848<\/p>\n<p>\u6269\u5c55\u529f\u80fd\u5b9e\u73b0\u4ee3\u7801<\/p>\n<p>\u5b66\u4e60\u793e\u7fa4\u9080\u8bf7<\/p>\n<p>\u4eca\u65e5\u6253\u5361\u4efb\u52a1&#xff1a;<\/p>\n<p>&#x1f44d; \u70b9\u8d5e\u672c\u6587&#xff0c;\u652f\u6301\u539f\u521b<\/p>\n<p>&#x1f4be; \u6536\u85cf\u672c\u6587&#xff0c;\u65b9\u4fbf\u67e5\u9605<\/p>\n<p>&#x1f680; \u8fd0\u884c\u9879\u76ee&#xff0c;\u5728\u8bc4\u8bba\u533a\u5206\u4eab\u622a\u56fe<\/p>\n<p>&#x1f468;\u200d&#x1f4bb; \u5173\u6ce8\u6211&#xff0c;\u83b7\u53d6\u66f4\u591a\u5b9e\u6218\u6559\u7a0b<\/p>\n<p>\u5728\u8bc4\u8bba\u533a\u544a\u8bc9\u6211&#xff1a;<\/p>\n<p>\u4f60\u5728\u5b9e\u73b0\u8fc7\u7a0b\u4e2d\u6700\u5927\u7684\u6536\u83b7\u662f\u4ec0\u4e48&#xff1f;<\/p>\n<p>\u8fd8\u5e0c\u671b\u5b66\u4e60\u4ec0\u4e48\u7c7b\u578b\u7684\u9879\u76ee&#xff1f;<\/p>\n<p>\u5bf9\u7535\u5546\u7cfb\u7edf\u6709\u4ec0\u4e48\u6539\u8fdb\u5efa\u8bae&#xff1f;<\/p>\n<p>\u8ba9\u6211\u4eec\u4e00\u8d77\u5728\u6280\u672f\u7684\u9053\u8def\u4e0a\u4e0d\u65ad\u524d\u884c&#xff01; &#x1f680;&#x1f525;<\/p>\n<p>\u4e0b\u671f\u9884\u544a&#xff1a; \u300a\u4ebf\u7ea7\u6d41\u91cf\u7535\u5546\u7cfb\u7edf\u67b6\u6784\u8bbe\u8ba1\u4e0e\u5b9e\u8df5\u300b<\/p>\n<p>\u9ad8\u5e76\u53d1\u573a\u666f\u4e0b\u7684\u67b6\u6784\u4f18\u5316<\/p>\n<p>\u6570\u636e\u5e93\u5206\u5e93\u5206\u8868\u5b9e\u6218<\/p>\n<p>\u7f13\u5b58\u7a7f\u900f\u3001\u96ea\u5d29\u3001\u51fb\u7a7f\u89e3\u51b3\u65b9\u6848<\/p>\n<p>\u5168\u94fe\u8def\u538b\u6d4b\u4e0e\u6027\u80fd\u4f18\u5316<\/p>\n<p>\u5bb9\u707e\u4e0e\u591a\u6d3b\u67b6\u6784\u8bbe\u8ba1<\/p>\n<p>\u5173\u6ce8\u6211&#xff0c;\u4e0d\u9519\u8fc7\u6bcf\u4e00\u7bc7\u7cbe\u54c1\u6559\u7a0b&#xff01; &#x1f4aa;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u7b2c\u4e00\u7ae0&#xff1a;\u9879\u76ee\u67b6\u6784\u8bbe\u8ba1\u4e0e\u6280\u672f\u9009\u578b<br \/>\n1.1 \u7535\u5546\u7cfb\u7edf\u6838\u5fc3\u4e1a\u52a1\u5206\u6790 \u4f20\u7edf\u5355\u4f53\u7535\u5546\u7cfb\u7edf\u7684\u75db\u70b9&#xff1a;<br \/>\njava \/\/ \u4f20\u7edf\u5355\u4f53\u7535\u5546\u67b6\u6784\u7684\u95ee\u9898 \u5355\u70b9\u6545\u969c&#xff1a;\u5546\u54c1\u670d\u52a1\u5d29\u6e83 \u2192 \u6574\u4e2a\u7f51\u7ad9\u4e0d\u53ef\u7528 \u6280\u672f\u6808\u56fa\u5316&#xff1a;\u6240\u6709\u6a21\u5757\u5fc5\u987b\u4f7f\u7528\u76f8\u540c\u6280\u672f \u6269\u5c55\u56f0\u96be&#xff1a;\u4fc3\u9500\u65f6\u53ea\u80fd\u6574\u4f53\u6269\u5bb9 \u53d1\u5e03\u98ce\u9669&#xff1a;\u5c0f\u6539\u52a8\u9700\u8981\u5168\u7ad9\u53d1\u5e03 \u56e2\u961f\u534f\u4f5c&#xff1a;\u4ee3\u7801\u51b2\u7a81\u3001\u6c9f\u901a\u6210\u672c\u9ad8<br \/>\n\/\/ \u7535\u5546\u7cfb\u7edf\u6838\u5fc3\u4e1a\u52a1\u6d41\u7a0b&#xff1a; \u7528\u6237\u6d4f\u89c8 \u2192 \u52a0\u5165\u8d2d\u7269\u8f66 \u2192 <\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[101,963,1348],"topic":[],"class_list":["post-64307","post","type-post","status-publish","format-standard","hentry","category-server","tag-spring","tag-spring-cloud","tag-1348"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v20.3 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Spring Cloud\u5b9e\u6218\uff1a\u7535\u5546\u5fae\u670d\u52a1\u7cfb\u7edf\u4ece0\u52301\uff0825000\u5b57\u7ec8\u6781\u5b9e\u6218\u6307\u5357\uff09 - \u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.wsisp.com\/helps\/64307.html\" \/>\n<meta property=\"og:locale\" content=\"zh_CN\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Spring Cloud\u5b9e\u6218\uff1a\u7535\u5546\u5fae\u670d\u52a1\u7cfb\u7edf\u4ece0\u52301\uff0825000\u5b57\u7ec8\u6781\u5b9e\u6218\u6307\u5357\uff09 - \u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3\" \/>\n<meta property=\"og:description\" content=\"\u7b2c\u4e00\u7ae0&#xff1a;\u9879\u76ee\u67b6\u6784\u8bbe\u8ba1\u4e0e\u6280\u672f\u9009\u578b 1.1 \u7535\u5546\u7cfb\u7edf\u6838\u5fc3\u4e1a\u52a1\u5206\u6790 \u4f20\u7edf\u5355\u4f53\u7535\u5546\u7cfb\u7edf\u7684\u75db\u70b9&#xff1a; java \/\/ \u4f20\u7edf\u5355\u4f53\u7535\u5546\u67b6\u6784\u7684\u95ee\u9898 \u5355\u70b9\u6545\u969c&#xff1a;\u5546\u54c1\u670d\u52a1\u5d29\u6e83 \u2192 \u6574\u4e2a\u7f51\u7ad9\u4e0d\u53ef\u7528 \u6280\u672f\u6808\u56fa\u5316&#xff1a;\u6240\u6709\u6a21\u5757\u5fc5\u987b\u4f7f\u7528\u76f8\u540c\u6280\u672f \u6269\u5c55\u56f0\u96be&#xff1a;\u4fc3\u9500\u65f6\u53ea\u80fd\u6574\u4f53\u6269\u5bb9 \u53d1\u5e03\u98ce\u9669&#xff1a;\u5c0f\u6539\u52a8\u9700\u8981\u5168\u7ad9\u53d1\u5e03 \u56e2\u961f\u534f\u4f5c&#xff1a;\u4ee3\u7801\u51b2\u7a81\u3001\u6c9f\u901a\u6210\u672c\u9ad8 \/\/ \u7535\u5546\u7cfb\u7edf\u6838\u5fc3\u4e1a\u52a1\u6d41\u7a0b&#xff1a; \u7528\u6237\u6d4f\u89c8 \u2192 \u52a0\u5165\u8d2d\u7269\u8f66 \u2192\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.wsisp.com\/helps\/64307.html\" \/>\n<meta property=\"og:site_name\" content=\"\u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3\" \/>\n<meta property=\"article:published_time\" content=\"2026-01-23T02:24:26+00:00\" \/>\n<meta name=\"author\" content=\"admin\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"\u4f5c\u8005\" \/>\n\t<meta name=\"twitter:data1\" content=\"admin\" \/>\n\t<meta name=\"twitter:label2\" content=\"\u9884\u8ba1\u9605\u8bfb\u65f6\u95f4\" \/>\n\t<meta name=\"twitter:data2\" content=\"59 \u5206\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.wsisp.com\/helps\/64307.html\",\"url\":\"https:\/\/www.wsisp.com\/helps\/64307.html\",\"name\":\"Spring Cloud\u5b9e\u6218\uff1a\u7535\u5546\u5fae\u670d\u52a1\u7cfb\u7edf\u4ece0\u52301\uff0825000\u5b57\u7ec8\u6781\u5b9e\u6218\u6307\u5357\uff09 - \u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3\",\"isPartOf\":{\"@id\":\"https:\/\/www.wsisp.com\/helps\/#website\"},\"datePublished\":\"2026-01-23T02:24:26+00:00\",\"dateModified\":\"2026-01-23T02:24:26+00:00\",\"author\":{\"@id\":\"https:\/\/www.wsisp.com\/helps\/#\/schema\/person\/358e386c577a3ab51c4493330a20ad41\"},\"breadcrumb\":{\"@id\":\"https:\/\/www.wsisp.com\/helps\/64307.html#breadcrumb\"},\"inLanguage\":\"zh-Hans\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.wsisp.com\/helps\/64307.html\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.wsisp.com\/helps\/64307.html#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"\u9996\u9875\",\"item\":\"https:\/\/www.wsisp.com\/helps\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Spring Cloud\u5b9e\u6218\uff1a\u7535\u5546\u5fae\u670d\u52a1\u7cfb\u7edf\u4ece0\u52301\uff0825000\u5b57\u7ec8\u6781\u5b9e\u6218\u6307\u5357\uff09\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.wsisp.com\/helps\/#website\",\"url\":\"https:\/\/www.wsisp.com\/helps\/\",\"name\":\"\u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3\",\"description\":\"\u9999\u6e2f\u670d\u52a1\u5668_\u9999\u6e2f\u4e91\u670d\u52a1\u5668\u8d44\u8baf_\u670d\u52a1\u5668\u5e2e\u52a9\u6587\u6863_\u670d\u52a1\u5668\u6559\u7a0b\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.wsisp.com\/helps\/?s={search_term_string}\"},\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"zh-Hans\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.wsisp.com\/helps\/#\/schema\/person\/358e386c577a3ab51c4493330a20ad41\",\"name\":\"admin\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"zh-Hans\",\"@id\":\"https:\/\/www.wsisp.com\/helps\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/gravatar.wp-china-yes.net\/avatar\/?s=96&d=mystery\",\"contentUrl\":\"https:\/\/gravatar.wp-china-yes.net\/avatar\/?s=96&d=mystery\",\"caption\":\"admin\"},\"sameAs\":[\"http:\/\/wp.wsisp.com\"],\"url\":\"https:\/\/www.wsisp.com\/helps\/author\/admin\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Spring Cloud\u5b9e\u6218\uff1a\u7535\u5546\u5fae\u670d\u52a1\u7cfb\u7edf\u4ece0\u52301\uff0825000\u5b57\u7ec8\u6781\u5b9e\u6218\u6307\u5357\uff09 - \u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.wsisp.com\/helps\/64307.html","og_locale":"zh_CN","og_type":"article","og_title":"Spring Cloud\u5b9e\u6218\uff1a\u7535\u5546\u5fae\u670d\u52a1\u7cfb\u7edf\u4ece0\u52301\uff0825000\u5b57\u7ec8\u6781\u5b9e\u6218\u6307\u5357\uff09 - \u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3","og_description":"\u7b2c\u4e00\u7ae0&#xff1a;\u9879\u76ee\u67b6\u6784\u8bbe\u8ba1\u4e0e\u6280\u672f\u9009\u578b 1.1 \u7535\u5546\u7cfb\u7edf\u6838\u5fc3\u4e1a\u52a1\u5206\u6790 \u4f20\u7edf\u5355\u4f53\u7535\u5546\u7cfb\u7edf\u7684\u75db\u70b9&#xff1a; java \/\/ \u4f20\u7edf\u5355\u4f53\u7535\u5546\u67b6\u6784\u7684\u95ee\u9898 \u5355\u70b9\u6545\u969c&#xff1a;\u5546\u54c1\u670d\u52a1\u5d29\u6e83 \u2192 \u6574\u4e2a\u7f51\u7ad9\u4e0d\u53ef\u7528 \u6280\u672f\u6808\u56fa\u5316&#xff1a;\u6240\u6709\u6a21\u5757\u5fc5\u987b\u4f7f\u7528\u76f8\u540c\u6280\u672f \u6269\u5c55\u56f0\u96be&#xff1a;\u4fc3\u9500\u65f6\u53ea\u80fd\u6574\u4f53\u6269\u5bb9 \u53d1\u5e03\u98ce\u9669&#xff1a;\u5c0f\u6539\u52a8\u9700\u8981\u5168\u7ad9\u53d1\u5e03 \u56e2\u961f\u534f\u4f5c&#xff1a;\u4ee3\u7801\u51b2\u7a81\u3001\u6c9f\u901a\u6210\u672c\u9ad8 \/\/ \u7535\u5546\u7cfb\u7edf\u6838\u5fc3\u4e1a\u52a1\u6d41\u7a0b&#xff1a; \u7528\u6237\u6d4f\u89c8 \u2192 \u52a0\u5165\u8d2d\u7269\u8f66 \u2192","og_url":"https:\/\/www.wsisp.com\/helps\/64307.html","og_site_name":"\u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3","article_published_time":"2026-01-23T02:24:26+00:00","author":"admin","twitter_card":"summary_large_image","twitter_misc":{"\u4f5c\u8005":"admin","\u9884\u8ba1\u9605\u8bfb\u65f6\u95f4":"59 \u5206"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/www.wsisp.com\/helps\/64307.html","url":"https:\/\/www.wsisp.com\/helps\/64307.html","name":"Spring Cloud\u5b9e\u6218\uff1a\u7535\u5546\u5fae\u670d\u52a1\u7cfb\u7edf\u4ece0\u52301\uff0825000\u5b57\u7ec8\u6781\u5b9e\u6218\u6307\u5357\uff09 - \u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3","isPartOf":{"@id":"https:\/\/www.wsisp.com\/helps\/#website"},"datePublished":"2026-01-23T02:24:26+00:00","dateModified":"2026-01-23T02:24:26+00:00","author":{"@id":"https:\/\/www.wsisp.com\/helps\/#\/schema\/person\/358e386c577a3ab51c4493330a20ad41"},"breadcrumb":{"@id":"https:\/\/www.wsisp.com\/helps\/64307.html#breadcrumb"},"inLanguage":"zh-Hans","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.wsisp.com\/helps\/64307.html"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/www.wsisp.com\/helps\/64307.html#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"\u9996\u9875","item":"https:\/\/www.wsisp.com\/helps"},{"@type":"ListItem","position":2,"name":"Spring Cloud\u5b9e\u6218\uff1a\u7535\u5546\u5fae\u670d\u52a1\u7cfb\u7edf\u4ece0\u52301\uff0825000\u5b57\u7ec8\u6781\u5b9e\u6218\u6307\u5357\uff09"}]},{"@type":"WebSite","@id":"https:\/\/www.wsisp.com\/helps\/#website","url":"https:\/\/www.wsisp.com\/helps\/","name":"\u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3","description":"\u9999\u6e2f\u670d\u52a1\u5668_\u9999\u6e2f\u4e91\u670d\u52a1\u5668\u8d44\u8baf_\u670d\u52a1\u5668\u5e2e\u52a9\u6587\u6863_\u670d\u52a1\u5668\u6559\u7a0b","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.wsisp.com\/helps\/?s={search_term_string}"},"query-input":"required name=search_term_string"}],"inLanguage":"zh-Hans"},{"@type":"Person","@id":"https:\/\/www.wsisp.com\/helps\/#\/schema\/person\/358e386c577a3ab51c4493330a20ad41","name":"admin","image":{"@type":"ImageObject","inLanguage":"zh-Hans","@id":"https:\/\/www.wsisp.com\/helps\/#\/schema\/person\/image\/","url":"https:\/\/gravatar.wp-china-yes.net\/avatar\/?s=96&d=mystery","contentUrl":"https:\/\/gravatar.wp-china-yes.net\/avatar\/?s=96&d=mystery","caption":"admin"},"sameAs":["http:\/\/wp.wsisp.com"],"url":"https:\/\/www.wsisp.com\/helps\/author\/admin"}]}},"_links":{"self":[{"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/posts\/64307","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/comments?post=64307"}],"version-history":[{"count":0,"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/posts\/64307\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/media?parent=64307"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/categories?post=64307"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/tags?post=64307"},{"taxonomy":"topic","embeddable":true,"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/topic?post=64307"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}