目录
1. 项目概述与技术架构
1.1 整体架构设计
1.2 技术栈详解
1.3 项目目录结构详解
2. 环境准备与工具配置
2.1 开发环境详细配置
2.1.1 Java环境配置
2.1.2 Maven环境配置
2.1.3 数据库环境配置
2.2 微信开发者平台配置详解
2.2.1 小程序配置步骤
2.2.2 公众号配置步骤
2.3 IDE配置与插件推荐
2.3.1 IntelliJ IDEA配置
2.3.2 开发工具推荐
3. Spring Boot项目详细搭建
3.1 项目创建与初始化
3.1.1 使用Spring Initializr创建项目
3.1.2 完整的pom.xml配置
3.2 配置文件详细配置
3.2.1 主配置文件 application.yml
3.2.2 开发环境配置 application-dev.yml
3.2.3 生产环境配置 application-prod.yml
3.3 基础类与工具类
3.3.1 基础实体类
3.3.2 统一响应结果类
3.3.3 全局异常处理器
3.3.4 业务异常类
3.3.5 微信工具类
4. 微信小程序后端详细开发
4.1 小程序登录授权深度解析
4.1.1 登录流程详解
4.1.2 小程序配置类详解
4.1.3 用户实体类详细设计
4.1.4 数据访问层
4.1.5 登录请求和响应DTO
4.1.6 小程序认证服务
4.1.7 小程序认证控制器
4.2 用户信息管理完整实现
4.2.1 用户信息请求DTO
4.2.2 用户地址实体
4.2.3 用户积分记录实体
4.2.4 用户服务实现
4.2.5 用户控制器实现
4.3 微信支付完整集成
4.3.1 支付配置类
4.3.2 订单实体类
4.3.3 支付请求和响应DTO
4.3.4 支付服务实现
4.3.5 支付控制器
4.4 小程序消息推送
4.4.1 消息模板配置
4.4.2 消息服务实现
5. 微信公众号深度开发
5.1 公众号接入与验证详解
5.1.1 公众号配置类
5.1.2 公众号入口控制器
5.2 消息处理完整方案
5.2.1 消息处理服务
5.3 菜单管理高级功能
5.3.1 菜单管理服务
5.4 网页授权完整流程
5.4.1 OAuth2授权控制器
5.4.2 OAuth2授权服务
5.5 素材管理与群发消息
5.5.1 素材管理服务
5.5.2 群发消息服务
6. 数据库设计与优化
6.1 完整数据库设计
6.2 数据库优化策略
7. 安全配置与权限管理
7.1 Spring Security配置
7.2 JWT认证过滤器
7.3 认证入口点
8. 性能优化与监控
8.1 缓存配置
8.2 性能监控配置
8.3 接口限流配置
9. 测试与调试指南
9.1 单元测试示例
9.2 集成测试示例
10. 部署与运维指南
10.1 Docker部署
10.2 Nginx配置
11. 常见问题与解决方案
11.1 微信接口调用频率限制
11.2 小程序登录session_key过期
11.3 支付回调重复通知
12. 最佳实践与开发规范
12.1 代码规范
12.2 安全规范
12.3 性能优化建议
总结
附录:相关资源
1. 项目概述与技术架构
1.1 整体架构设计
本项目采用前后端分离的架构设计,后端使用Spring Boot提供RESTful API服务,支持微信小程序和公众号的各种功能需求。
┌─────────────────────────────────────────────────────────┐
│ 前端应用层 │
├─────────────────┬─────────────────┬─────────────────────┤
│ 微信小程序 │ 微信公众号 │ 管理后台 │
├─────────────────┴─────────────────┴─────────────────────┤
│ API网关层 │
├─────────────────────────────────────────────────────────┤
│ Spring Boot应用层 │
├──────────┬──────────┬──────────┬──────────┬─────────────┤
│ 用户服务 │ 支付服务 │ 消息服务 │ 菜单服务 │ 其他服务 │
├─────────────────────────────────────────────────────────┤
│ 数据持久层 │
├──────────┬──────────┬──────────┬──────────┬─────────────┤
│ MySQL │ Redis │ MongoDB │ ES │ 文件存储 │
└─────────────────────────────────────────────────────────┘
1.2 技术栈详解
后端技术栈:
Spring Boot 2.7.x: 主框架,提供自动配置和快速开发能力
Spring Security: 安全框架,处理认证和授权
Spring Data JPA: 数据访问层,简化数据库操作
MySQL 8.0: 主数据库,存储业务数据
Redis 6.x: 缓存和会话存储
WxJava SDK: 微信开发工具包
FastJSON: JSON处理库
Swagger: API文档生成
部署与运维:
Docker: 容器化部署
Nginx: 反向代理和负载均衡
Jenkins: CI/CD持续集成
ELK Stack: 日志收集和分析
Prometheus: 监控和告警
1.3 项目目录结构详解
wechat-platform/
├── src/main/java/com/yourcompany/wechat/
│ ├── WechatApplication.java # 应用启动类
│ ├── config/ # 配置类目录
│ │ ├── WechatMiniAppConfig.java # 小程序配置
│ │ ├── WechatMpConfig.java # 公众号配置
│ │ ├── WechatPayConfig.java # 支付配置
│ │ ├── RedisConfig.java # Redis配置
│ │ ├── SecurityConfig.java # 安全配置
│ │ ├── SwaggerConfig.java # API文档配置
│ │ └── WebMvcConfig.java # Web配置
│ ├── controller/ # 控制器层
│ │ ├── base/ # 基础控制器
│ │ │ └── BaseController.java
│ │ ├── miniapp/ # 小程序控制器
│ │ │ ├── MiniAppAuthController.java # 小程序认证
│ │ │ ├── MiniAppUserController.java # 小程序用户
│ │ │ └── MiniAppPayController.java # 小程序支付
│ │ ├── mp/ # 公众号控制器
│ │ │ ├── MpPortalController.java # 公众号入口
│ │ │ ├── MpMenuController.java # 菜单管理
│ │ │ └── MpOAuthController.java # 网页授权
│ │ └── admin/ # 管理后台控制器
│ │ ├── AdminUserController.java # 用户管理
│ │ └── AdminSystemController.java # 系统管理
│ ├── service/ # 服务层
│ │ ├── base/ # 基础服务
│ │ │ └── BaseService.java
│ │ ├── miniapp/ # 小程序服务
│ │ │ ├── MiniAppAuthService.java # 认证服务
│ │ │ ├── MiniAppUserService.java # 用户服务
│ │ │ └── MiniAppPayService.java # 支付服务
│ │ ├── mp/ # 公众号服务
│ │ │ ├── MpMessageService.java # 消息服务
│ │ │ ├── MpMenuService.java # 菜单服务
│ │ │ └── MpOAuthService.java # 授权服务
│ │ └── common/ # 通用服务
│ │ ├── UserService.java # 用户通用服务
│ │ ├── NotificationService.java # 通知服务
│ │ └── FileService.java # 文件服务
│ ├── repository/ # 数据访问层
│ │ ├── UserRepository.java # 用户数据访问
│ │ ├── OrderRepository.java # 订单数据访问
│ │ └── MessageRepository.java # 消息数据访问
│ ├── entity/ # 实体类
│ │ ├── base/ # 基础实体
│ │ │ └── BaseEntity.java
│ │ ├── User.java # 用户实体
│ │ ├── Order.java # 订单实体
│ │ ├── WechatUser.java # 微信用户实体
│ │ └── Message.java # 消息实体
│ ├── dto/ # 数据传输对象
│ │ ├── request/ # 请求DTO
│ │ │ ├── LoginRequest.java
│ │ │ ├── PayRequest.java
│ │ │ └── UserInfoRequest.java
│ │ └── response/ # 响应DTO
│ │ ├── ApiResponse.java
│ │ ├── UserResponse.java
│ │ └── PayResponse.java
│ ├── enums/ # 枚举类
│ │ ├── UserStatus.java # 用户状态
│ │ ├── OrderStatus.java # 订单状态
│ │ └── MessageType.java # 消息类型
│ ├── exception/ # 异常处理
│ │ ├── GlobalExceptionHandler.java # 全局异常处理
│ │ ├── BusinessException.java # 业务异常
│ │ └── WechatException.java # 微信异常
│ ├── interceptor/ # 拦截器
│ │ ├── AuthInterceptor.java # 认证拦截器
│ │ └── LogInterceptor.java # 日志拦截器
│ ├── utils/ # 工具类
│ │ ├── WechatUtils.java # 微信工具类
│ │ ├── SecurityUtils.java # 安全工具类
│ │ ├── DateUtils.java # 日期工具类
│ │ └── JsonUtils.java # JSON工具类
│ └── constant/ # 常量类
│ ├── WechatConstants.java # 微信常量
│ ├── ApiConstants.java # API常量
│ └── SystemConstants.java # 系统常量
├── src/main/resources/
│ ├── application.yml # 主配置文件
│ ├── application-dev.yml # 开发环境配置
│ ├── application-prod.yml # 生产环境配置
│ ├── logback-spring.xml # 日志配置
│ ├── db/ # 数据库脚本
│ │ ├── migration/ # 数据库迁移脚本
│ │ └── data/ # 初始化数据
│ └── static/ # 静态资源
└── src/test/ # 测试代码
├── java/ # 单元测试
└── resources/ # 测试资源
2. 环境准备与工具配置
2.1 开发环境详细配置
2.1.1 Java环境配置
# 安装JDK 8或以上版本
# Windows环境
# 1. 下载JDK安装包
# 2. 设置JAVA_HOME环境变量
# 3. 配置PATH环境变量
# Linux/Mac环境
# 使用SDKMAN管理Java版本
curl -s "https://get.sdkman.io" | bash
sdk install java 8.0.362-amzn
sdk use java 8.0.362-amzn
# 验证安装
java -version
javac -version
2.1.2 Maven环境配置
<!-- settings.xml配置 -->
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0">
<localRepository>/path/to/local/repo</localRepository>
<mirrors>
<mirror>
<id>aliyun-maven</id>
<mirrorOf>*</mirrorOf>
<name>Aliyun Maven</name>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
</mirrors>
<profiles>
<profile>
<id>jdk-1.8</id>
<activation>
<activeByDefault>true</activeByDefault>
<jdk>1.8</jdk>
</activation>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
</properties>
</profile>
</profiles>
</settings>
2.1.3 数据库环境配置
-- MySQL配置建议
-- 创建数据库
CREATE DATABASE wechat_platform
DEFAULT CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
-- 创建用户
CREATE USER 'wechat_user'@'%' IDENTIFIED BY 'your_strong_password';
GRANT ALL PRIVILEGES ON wechat_platform.* TO 'wechat_user'@'%';
FLUSH PRIVILEGES;
-- MySQL配置文件优化 (my.cnf)
[mysqld]
# 基础配置
port = 3306
bind-address = 0.0.0.0
max_connections = 200
max_connect_errors = 6000
# InnoDB配置
innodb_buffer_pool_size = 128M
innodb_log_file_size = 32M
innodb_file_per_table = 1
innodb_flush_log_at_trx_commit = 1
# 字符集配置
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
2.2 微信开发者平台配置详解
2.2.1 小程序配置步骤
注册小程序账号
访问 https://mp.weixin.qq.com/
选择”小程序”类型注册
完成邮箱验证和信息登记
获取开发者信息
小程序管理后台 → 开发 → 开发管理 → 开发设置
- AppID(小程序ID): wxxxxxxxxxxxxxxxxxxx
- AppSecret(小程序密钥): xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
配置服务器域名
开发 → 开发管理 → 开发设置 → 服务器域名
- request合法域名: https://your-api-domain.com
- socket合法域名: wss://your-websocket-domain.com
- uploadFile合法域名: https://your-upload-domain.com
- downloadFile合法域名: https://your-download-domain.com
配置业务域名
设置 → 基本设置 → 业务域名
- 下载校验文件并上传至服务器根目录
- 添加业务域名: https://your-business-domain.com
2.2.2 公众号配置步骤
注册公众号账号
访问 https://mp.weixin.qq.com/
选择”公众号”类型注册
根据需要选择订阅号或服务号
获取开发者信息
公众号管理后台 → 开发 → 基本配置
- AppID: wxxxxxxxxxxxxxxxxxxx
- AppSecret: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
- 服务器地址URL: https://your-api-domain.com/wechat/mp/portal
- Token: 自定义Token字符串
- EncodingAESKey: 随机生成或自定义43位字符串
配置服务器
开发 → 基本配置 → 服务器配置
- 提交配置前确保服务器能正确响应微信验证请求
- 消息加解密方式建议选择"安全模式"
配置IP白名单
开发 → 基本配置 → IP白名单
- 添加服务器公网IP地址
- 多服务器部署需添加所有服务器IP
2.3 IDE配置与插件推荐
2.3.1 IntelliJ IDEA配置
必装插件:
- Lombok Plugin (简化Java代码)
- MyBatis Log Plugin (SQL日志美化)
- RestfulTool (API测试工具)
- Alibaba Java Coding Guidelines (代码规范检查)
- Maven Helper (Maven依赖管理)
- Rainbow Brackets (括号高亮)
- Git Flow Integration (Git工作流)
代码风格配置:
File → Settings → Editor → Code Style → Java
- 导入Google Java Style或Alibaba Java Style
- 设置缩进为4个空格
- 启用自动格式化
2.3.2 开发工具推荐
API测试工具:
- Postman (REST API测试)
- Apifox (国产API管理工具)
数据库工具:
- DataGrip (JetBrains数据库工具)
- Navicat (可视化数据库管理)
- DBeaver (免费数据库工具)
版本控制:
- Git (版本控制系统)
- SourceTree (Git可视化工具)
- GitKraken (现代化Git客户端)
其他工具:
- Redis Desktop Manager (Redis可视化)
- Docker Desktop (容器管理)
- Charles/Fiddler (网络抓包工具)
3. Spring Boot项目详细搭建
3.1 项目创建与初始化
3.1.1 使用Spring Initializr创建项目
# 使用命令行创建项目
curl https://start.spring.io/starter.tgz
-d dependencies=web,data-jpa,mysql,data-redis,validation,actuator
-d type=maven-project
-d language=java
-d bootVersion=2.7.14
-d baseDir=wechat-platform
-d groupId=com.yourcompany
-d artifactId=wechat-platform
-d name="Wechat Platform"
-d description="Spring Boot Wechat Platform"
-d packageName=com.yourcompany.wechat
-d packaging=jar
-d javaVersion=1.8 | tar -xzvf -
3.1.2 完整的pom.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.14</version>
<relativePath/>
</parent>
<groupId>com.yourcompany</groupId>
<artifactId>wechat-platform</artifactId>
<version>1.0.0</version>
<name>Wechat Platform</name>
<description>Spring Boot Wechat Platform</description>
<properties>
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<!-- 版本管理 -->
<weixin-java.version>4.4.0</weixin-java.version>
<fastjson.version>1.2.83</fastjson.version>
<lombok.version>1.18.28</lombok.version>
<swagger.version>3.0.0</swagger.version>
<hutool.version>5.8.21</hutool.version>
<mybatis-plus.version>3.5.3.1</mybatis-plus.version>
</properties>
<dependencies>
<!-- Spring Boot Starters -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- 数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- MyBatis Plus (可选,增强MyBatis) -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- 微信开发SDK -->
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-miniapp</artifactId>
<version>${weixin-java.version}</version>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-mp</artifactId>
<version>${weixin-java.version}</version>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-pay</artifactId>
<version>${weixin-java.version}</version>
</dependency>
<!-- JSON处理 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<!-- 工具类库 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<!-- API文档 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>${swagger.version}</version>
</dependency>
<!-- 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 内存数据库(测试用) -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
<!-- Maven编译插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<!-- Maven资源插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<!-- Docker构建插件 -->
<plugin>
<groupId>com.spotify</groupId>
<artifactId>dockerfile-maven-plugin</artifactId>
<version>1.4.13</version>
<configuration>
<repository>${docker.registry}/${project.artifactId}</repository>
<tag>${project.version}</tag>
<buildArgs>
<JAR_FILE>target/${project.build.finalName}.jar</JAR_FILE>
</buildArgs>
</configuration>
</plugin>
</plugins>
</build>
<!-- 多环境配置 -->
<profiles>
<profile>
<id>dev</id>
<properties>
<spring.profiles.active>dev</spring.profiles.active>
</properties>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<profile>
<id>test</id>
<properties>
<spring.profiles.active>test</spring.profiles.active>
</properties>
</profile>
<profile>
<id>prod</id>
<properties>
<spring.profiles.active>prod</spring.profiles.active>
</properties>
</profile>
</profiles>
</project>
3.2 配置文件详细配置
3.2.1 主配置文件 application.yml
# 通用配置
spring:
profiles:
active: @spring.profiles.active@
# 应用名称
application:
name: wechat-platform
# 数据源配置
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.zaxxer.hikari.HikariDataSource
hikari:
# 连接池配置
minimum-idle: 5
maximum-pool-size: 20
auto-commit: true
idle-timeout: 30000
pool-name: HikariCP
max-lifetime: 1800000
connection-timeout: 30000
connection-test-query: SELECT 1
validation-timeout: 3000
login-timeout: 5
leak-detection-threshold: 60000
# JPA配置
jpa:
hibernate:
ddl-auto: update
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
show-sql: false
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL8Dialect
format_sql: true
use_sql_comments: true
jdbc:
batch_size: 20
order_inserts: true
order_updates: true
generate_statistics: false
# Redis配置
redis:
timeout: 3000ms
lettuce:
pool:
max-active: 8
max-wait: -1ms
max-idle: 8
min-idle: 0
shutdown-timeout: 100ms
# Jackson配置
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
default-property-inclusion: non_null
serialization:
write-dates-as-timestamps: false
fail-on-empty-beans: false
deserialization:
fail-on-unknown-properties: false
# 文件上传配置
servlet:
multipart:
enabled: true
max-file-size: 10MB
max-request-size: 100MB
file-size-threshold: 2KB
location: /tmp
# 缓存配置
cache:
type: redis
redis:
time-to-live: 600000
cache-null-values: false
# 服务器配置
server:
port: 8080
servlet:
context-path: /api
encoding:
charset: UTF-8
force: true
tomcat:
uri-encoding: UTF-8
max-connections: 10000
max-threads: 200
min-spare-threads: 10
accept-count: 100
connection-timeout: 20000
compression:
enabled: true
mime-types: application/json,application/xml,text/html,text/xml,text/plain
min-response-size: 1024
# 日志配置
logging:
config: classpath:logback-spring.xml
level:
root: INFO
com.yourcompany.wechat: DEBUG
org.springframework.web: INFO
org.hibernate.SQL: DEBUG
org.hibernate.type.descriptor.sql.BasicBinder: TRACE
# 监控配置
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
base-path: /actuator
endpoint:
health:
show-details: when_authorized
metrics:
export:
prometheus:
enabled: true
# Swagger配置
springfox:
documentation:
swagger-ui:
enabled: true
swagger:
v2:
path: /v2/api-docs
# 自定义配置
app:
# JWT配置
jwt:
secret: yourJwtSecretKeyMustBeLongEnoughForHS256Algorithm
expiration: 604800000 # 7天
header: Authorization
prefix: Bearer
# 文件存储配置
file:
upload-path: /data/uploads/
max-size: 10485760 # 10MB
allowed-types: jpg,jpeg,png,gif,pdf,doc,docx,xls,xlsx
# 短信配置
sms:
enabled: true
provider: aliyun
access-key-id: your-access-key-id
access-key-secret: your-access-key-secret
sign-name: 您的短信签名
template-code: SMS_123456789
3.2.2 开发环境配置 application-dev.yml
spring:
# 数据源配置
datasource:
url: jdbc:mysql://localhost:3306/wechat_platform_dev?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: wechat_user
password: dev_password
# Redis配置
redis:
host: localhost
port: 6379
password: dev_redis_password
database: 0
# JPA配置
jpa:
show-sql: true
hibernate:
ddl-auto: update
# 微信配置
wechat:
# 小程序配置
miniapp:
appid: wx1234567890abcdef # 开发环境小程序AppID
secret: abcdef1234567890abcdef1234567890ab # 开发环境小程序Secret
token: dev_miniapp_token
aes-key: dev_miniapp_aes_key_must_be_43_characters_long
msg-data-format: JSON
# 公众号配置
mp:
appid: wx0987654321fedcba # 开发环境公众号AppID
secret: fedcba0987654321fedcba0987654321fe # 开发环境公众号Secret
token: dev_mp_token
aes-key: dev_mp_aes_key_must_be_43_characters_long_too
oauth2-redirect-uri: http://dev.yourcompany.com/api/wechat/mp/oauth/callback
# 微信支付配置
pay:
appid: ${wechat.miniapp.appid}
mch-id: 1234567890 # 开发环境商户号
mch-key: dev_mch_key_must_be_32_characters # 开发环境商户密钥
key-path: classpath:cert/dev/apiclient_cert.p12 # 开发环境证书路径
notify-url: http://dev.yourcompany.com/api/wechat/pay/notify
trade-type: JSAPI
# 日志级别
logging:
level:
com.yourcompany.wechat: DEBUG
org.springframework.web: DEBUG
com.github.binarywang.wxpay: DEBUG
me.chanjar.weixin: DEBUG
# 开发工具配置
spring:
devtools:
restart:
enabled: true
additional-paths: src/main/java
livereload:
enabled: true
# H2控制台(用于快速测试)
h2:
console:
enabled: false
3.2.3 生产环境配置 application-prod.yml
spring:
# 数据源配置
datasource:
url: jdbc:mysql://prod-mysql-host:3306/wechat_platform?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=Asia/Shanghai
username: ${DB_USERNAME:wechat_user}
password: ${DB_PASSWORD}
hikari:
maximum-pool-size: 50
minimum-idle: 10
# Redis配置
redis:
host: ${REDIS_HOST:prod-redis-host}
port: ${REDIS_PORT:6379}
password: ${REDIS_PASSWORD}
database: 0
ssl: true
# JPA配置
jpa:
show-sql: false
hibernate:
ddl-auto: validate # 生产环境不自动更新表结构
# 微信配置(使用环境变量)
wechat:
miniapp:
appid: ${WECHAT_MINIAPP_APPID}
secret: ${WECHAT_MINIAPP_SECRET}
token: ${WECHAT_MINIAPP_TOKEN}
aes-key: ${WECHAT_MINIAPP_AES_KEY}
mp:
appid: ${WECHAT_MP_APPID}
secret: ${WECHAT_MP_SECRET}
token: ${WECHAT_MP_TOKEN}
aes-key: ${WECHAT_MP_AES_KEY}
oauth2-redirect-uri: https://api.yourcompany.com/api/wechat/mp/oauth/callback
pay:
appid: ${WECHAT_PAY_APPID}
mch-id: ${WECHAT_PAY_MCH_ID}
mch-key: ${WECHAT_PAY_MCH_KEY}
key-path: ${WECHAT_PAY_KEY_PATH:/etc/wechat/cert/apiclient_cert.p12}
notify-url: https://api.yourcompany.com/api/wechat/pay/notify
# 服务器配置
server:
port: ${SERVER_PORT:8080}
# SSL配置
ssl:
enabled: true
key-store: ${SSL_KEY_STORE:/etc/ssl/keystore.p12}
key-store-password: ${SSL_KEY_STORE_PASSWORD}
key-store-type: PKCS12
key-alias: ${SSL_KEY_ALIAS:tomcat}
# 日志配置
logging:
level:
root: WARN
com.yourcompany.wechat: INFO
file:
name: /var/log/wechat-platform/application.log
max-size: 100MB
max-history: 30
# 监控配置
management:
endpoints:
web:
exposure:
include: health,info,metrics
endpoint:
health:
show-details: never
3.3 基础类与工具类
3.3.1 基础实体类
package com.yourcompany.wechat.entity.base;
import lombok.Data;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import javax.persistence.*;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 基础实体类
* 包含通用字段:ID、创建时间、更新时间
*/
@Data
@MappedSuperclass
public abstract class BaseEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
/**
* 创建时间
*/
@CreationTimestamp
@Column(name = "create_time", nullable = false, updatable = false)
private LocalDateTime createTime;
/**
* 更新时间
*/
@UpdateTimestamp
@Column(name = "update_time", nullable = false)
private LocalDateTime updateTime;
/**
* 逻辑删除标记
* 0: 正常, 1: 已删除
*/
@Column(name = "deleted", nullable = false, columnDefinition = "tinyint(1) default 0")
private Boolean deleted = false;
/**
* 版本号(乐观锁)
*/
@Version
@Column(name = "version", nullable = false, columnDefinition = "int default 0")
private Integer version = 0;
/**
* 创建者ID
*/
@Column(name = "create_by")
private Long createBy;
/**
* 更新者ID
*/
@Column(name = "update_by")
private Long updateBy;
/**
* 备注
*/
@Column(name = "remark", length = 500)
private String remark;
}
3.3.2 统一响应结果类
package com.yourcompany.wechat.dto.response;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 统一API响应结果
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ApiResponse<T> implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 响应码
* 200: 成功
* 400: 客户端错误
* 500: 服务器错误
*/
private Integer code;
/**
* 响应消息
*/
private String message;
/**
* 响应数据
*/
private T data;
/**
* 时间戳
*/
private Long timestamp;
/**
* 请求ID(用于追踪)
*/
private String traceId;
/**
* 构造函数
*/
public ApiResponse(Integer code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
this.timestamp = System.currentTimeMillis();
}
/**
* 成功响应
*/
public static <T> ApiResponse<T> success() {
return new ApiResponse<>(200, "操作成功", null);
}
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(200, "操作成功", data);
}
public static <T> ApiResponse<T> success(String message, T data) {
return new ApiResponse<>(200, message, data);
}
/**
* 失败响应
*/
public static <T> ApiResponse<T> error() {
return new ApiResponse<>(500, "操作失败", null);
}
public static <T> ApiResponse<T> error(String message) {
return new ApiResponse<>(500, message, null);
}
public static <T> ApiResponse<T> error(Integer code, String message) {
return new ApiResponse<>(code, message, null);
}
/**
* 客户端错误
*/
public static <T> ApiResponse<T> badRequest(String message) {
return new ApiResponse<>(400, message, null);
}
/**
* 未授权
*/
public static <T> ApiResponse<T> unauthorized() {
return new ApiResponse<>(401, "未授权访问", null);
}
/**
* 禁止访问
*/
public static <T> ApiResponse<T> forbidden() {
return new ApiResponse<>(403, "禁止访问", null);
}
/**
* 资源不存在
*/
public static <T> ApiResponse<T> notFound() {
return new ApiResponse<>(404, "资源不存在", null);
}
}
3.3.3 全局异常处理器
package com.yourcompany.wechat.exception;
import com.yourcompany.wechat.dto.response.ApiResponse;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 全局异常处理器
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 业务异常
*/
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ApiResponse<Void>> handleBusinessException(BusinessException e, HttpServletRequest request) {
log.warn("业务异常: {} - {}", request.getRequestURI(), e.getMessage());
ApiResponse<Void> response = ApiResponse.error(e.getCode(), e.getMessage());
response.setTraceId(getTraceId(request));
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
}
/**
* 微信异常
*/
@ExceptionHandler(WxErrorException.class)
public ResponseEntity<ApiResponse<Void>> handleWxErrorException(WxErrorException e, HttpServletRequest request) {
log.error("微信API异常: {} - 错误码: {}, 错误信息: {}",
request.getRequestURI(), e.getError().getErrorCode(), e.getError().getErrorMsg());
String message = String.format("微信服务异常: %s (错误码: %s)",
e.getError().getErrorMsg(), e.getError().getErrorCode());
ApiResponse<Void> response = ApiResponse.error(500, message);
response.setTraceId(getTraceId(request));
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
/**
* 参数验证异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ApiResponse<Void>> handleValidationException(MethodArgumentNotValidException e, HttpServletRequest request) {
log.warn("参数验证异常: {}", request.getRequestURI());
String message = e.getBindingResult().getFieldErrors().stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining(", "));
ApiResponse<Void> response = ApiResponse.badRequest("参数验证失败: " + message);
response.setTraceId(getTraceId(request));
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
}
/**
* 绑定异常
*/
@ExceptionHandler(BindException.class)
public ResponseEntity<ApiResponse<Void>> handleBindException(BindException e, HttpServletRequest request) {
log.warn("参数绑定异常: {}", request.getRequestURI());
String message = e.getFieldErrors().stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining(", "));
ApiResponse<Void> response = ApiResponse.badRequest("参数绑定失败: " + message);
response.setTraceId(getTraceId(request));
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
}
/**
* 约束违反异常
*/
@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<ApiResponse<Void>> handleConstraintViolationException(ConstraintViolationException e, HttpServletRequest request) {
log.warn("约束违反异常: {}", request.getRequestURI());
Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
String message = violations.stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.joining(", "));
ApiResponse<Void> response = ApiResponse.badRequest("约束验证失败: " + message);
response.setTraceId(getTraceId(request));
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
}
/**
* 参数类型不匹配异常
*/
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public ResponseEntity<ApiResponse<Void>> handleTypeMismatchException(MethodArgumentTypeMismatchException e, HttpServletRequest request) {
log.warn("参数类型不匹配异常: {} - 参数: {}, 期望类型: {}",
request.getRequestURI(), e.getName(), e.getRequiredType());
String message = String.format("参数 '%s' 类型不正确,期望类型: %s",
e.getName(), e.getRequiredType().getSimpleName());
ApiResponse<Void> response = ApiResponse.badRequest(message);
response.setTraceId(getTraceId(request));
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
}
/**
* 系统异常
*/
@ExceptionHandler(Exception.class)
public ResponseEntity<ApiResponse<Void>> handleException(Exception e, HttpServletRequest request) {
log.error("系统异常: {} - {}", request.getRequestURI(), e.getMessage(), e);
ApiResponse<Void> response = ApiResponse.error("系统内部错误,请稍后重试");
response.setTraceId(getTraceId(request));
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
/**
* 获取请求追踪ID
*/
private String getTraceId(HttpServletRequest request) {
String traceId = request.getHeader("X-Trace-Id");
return traceId != null ? traceId : String.valueOf(System.currentTimeMillis());
}
}
3.3.4 业务异常类
package com.yourcompany.wechat.exception;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 业务异常类
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class BusinessException extends RuntimeException {
private static final long serialVersionUID = 1L;
/**
* 错误码
*/
private Integer code;
/**
* 错误消息
*/
private String message;
public BusinessException(String message) {
super(message);
this.code = 500;
this.message = message;
}
public BusinessException(Integer code, String message) {
super(message);
this.code = code;
this.message = message;
}
public BusinessException(Integer code, String message, Throwable cause) {
super(message, cause);
this.code = code;
this.message = message;
}
/**
* 常用业务异常
*/
public static class User {
public static final BusinessException NOT_FOUND = new BusinessException(1001, "用户不存在");
public static final BusinessException ALREADY_EXISTS = new BusinessException(1002, "用户已存在");
public static final BusinessException LOGIN_FAILED = new BusinessException(1003, "登录失败");
public static final BusinessException TOKEN_EXPIRED = new BusinessException(1004, "登录已过期");
public static final BusinessException ACCESS_DENIED = new BusinessException(1005, "访问被拒绝");
}
public static class Order {
public static final BusinessException NOT_FOUND = new BusinessException(2001, "订单不存在");
public static final BusinessException STATUS_ERROR = new BusinessException(2002, "订单状态错误");
public static final BusinessException AMOUNT_ERROR = new BusinessException(2003, "订单金额错误");
public static final BusinessException PAY_FAILED = new BusinessException(2004, "支付失败");
}
public static class Wechat {
public static final BusinessException CONFIG_ERROR = new BusinessException(3001, "微信配置错误");
public static final BusinessException API_ERROR = new BusinessException(3002, "微信API调用失败");
public static final BusinessException SIGNATURE_ERROR = new BusinessException(3003, "微信签名验证失败");
public static final BusinessException DECRYPT_ERROR = new BusinessException(3004, "微信数据解密失败");
}
}
3.3.5 微信工具类
package com.yourcompany.wechat.utils;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.Map;
import java.util.TreeMap;
/**
* 微信工具类
*/
@Slf4j
public class WechatUtils {
/**
* 验证微信签名
*
* @param signature 微信签名
* @param timestamp 时间戳
* @param nonce 随机数
* @param token 配置的Token
* @return 验证结果
*/
public static boolean checkSignature(String signature, String timestamp, String nonce, String token) {
if (StrUtil.hasBlank(signature, timestamp, nonce, token)) {
return false;
}
try {
// 1. 将token、timestamp、nonce三个参数进行字典序排序
String[] array = {token, timestamp, nonce};
Arrays.sort(array);
// 2. 将三个参数字符串拼接成一个字符串进行sha1加密
String str = String.join("", array);
String sha1Str = DigestUtil.sha1Hex(str);
// 3. 获得加密后的字符串与signature对比
return sha1Str.equalsIgnoreCase(signature);
} catch (Exception e) {
log.error("微信签名验证失败", e);
return false;
}
}
/**
* 生成微信支付签名
*
* @param params 参数Map
* @param key 商户密钥
* @return 签名字符串
*/
public static String generatePaySign(Map<String, String> params, String key) {
// 1. 过滤空值并排序
TreeMap<String, String> sortedParams = new TreeMap<>();
params.forEach((k, v) -> {
if (StringUtils.hasText(v) && !"sign".equals(k)) {
sortedParams.put(k, v);
}
});
// 2. 拼接参数字符串
StringBuilder sb = new StringBuilder();
sortedParams.forEach((k, v) -> {
if (sb.length() > 0) {
sb.append("&");
}
sb.append(k).append("=").append(v);
});
// 3. 添加商户密钥
sb.append("&key=").append(key);
// 4. MD5加密并转大写
return DigestUtil.md5Hex(sb.toString()).toUpperCase();
}
/**
* 验证微信支付签名
*
* @param params 参数Map
* @param key 商户密钥
* @return 验证结果
*/
public static boolean verifyPaySign(Map<String, String> params, String key) {
String sign = params.get("sign");
if (!StringUtils.hasText(sign)) {
return false;
}
String generatedSign = generatePaySign(params, key);
return sign.equals(generatedSign);
}
/**
* 获取客户端IP地址
*
* @param request HTTP请求
* @return IP地址
*/
public static String getClientIp(HttpServletRequest request) {
String[] headerNames = {
"X-Forwarded-For",
"X-Real-IP",
"Proxy-Client-IP",
"WL-Proxy-Client-IP",
"HTTP_CLIENT_IP",
"HTTP_X_FORWARDED_FOR"
};
for (String headerName : headerNames) {
String ip = request.getHeader(headerName);
if (StringUtils.hasText(ip) && !"unknown".equalsIgnoreCase(ip)) {
// 多级反向代理时,第一个IP为客户端的真实IP
int index = ip.indexOf(',');
if (index != -1) {
ip = ip.substring(0, index);
}
return ip.trim();
}
}
return request.getRemoteAddr();
}
/**
* 生成随机字符串
*
* @param length 长度
* @return 随机字符串
*/
public static String generateNonceStr(int length) {
String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
int index = (int) (Math.random() * chars.length());
sb.append(chars.charAt(index));
}
return sb.toString();
}
/**
* 生成订单号
*
* @param prefix 前缀
* @return 订单号
*/
public static String generateOrderNo(String prefix) {
String timestamp = String.valueOf(System.currentTimeMillis());
String random = generateNonceStr(6);
return (prefix != null ? prefix : "") + timestamp + random;
}
/**
* XML转义
*
* @param str 原字符串
* @return 转义后的字符串
*/
public static String escapeXml(String str) {
if (!StringUtils.hasText(str)) {
return str;
}
return str.replace("&", "&")
.replace("<", "<")
.replace(">", ">")
.replace(""", """)
.replace("'", "'");
}
/**
* 格式化金额(元转分)
*
* @param yuan 金额(元)
* @return 金额(分)
*/
public static Integer yuanToFen(Double yuan) {
if (yuan == null) {
return 0;
}
return (int) Math.round(yuan * 100);
}
/**
* 格式化金额(分转元)
*
* @param fen 金额(分)
* @return 金额(元)
*/
public static Double fenToYuan(Integer fen) {
if (fen == null) {
return 0.0;
}
return fen / 100.0;
}
}
4. 微信小程序后端详细开发
4.1 小程序登录授权深度解析
微信小程序的登录流程是一个涉及小程序前端、开发者服务器和微信服务器的三方交互过程。
4.1.1 登录流程详解
┌─────────────┐ 1.wx.login() ┌─────────────┐
│ │ ──────────────────> │ │
│ 小程序前端 │ │ 微信服务器 │
│ │ <────────────────── │ │
└─────────────┘ 2.返回code └─────────────┘
│ ^
│ 3.发送code到开发者服务器 │
v │ 4.code2Session
┌─────────────┐ 5.返回token ┌─────────────┐
│ │ <────────────────── │ │
│ 开发者服务器 │ │ 业务服务器 │
│ │ │ │
└─────────────┘ └─────────────┘
4.1.2 小程序配置类详解
package com.yourcompany.wechat.config;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;
import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.NotBlank;
/**
* 微信小程序配置
*/
@Slf4j
@Data
@Validated
@Configuration
@ConfigurationProperties(prefix = "wechat.miniapp")
public class WechatMiniAppConfig {
/**
* 小程序AppID
*/
@NotBlank(message = "小程序AppID不能为空")
private String appid;
/**
* 小程序AppSecret
*/
@NotBlank(message = "小程序Secret不能为空")
private String secret;
/**
* 消息格式,XML或者JSON
*/
private String msgDataFormat = "JSON";
/**
* 消息加解密密钥
*/
private String aesKey;
/**
* 消息校验Token
*/
private String token;
/**
* HTTP超时时间(毫秒)
*/
private Integer httpTimeout = 10000;
/**
* HTTP代理主机
*/
private St
暂无评论内容