From 6ea23b5a015fdca075ae40a0bf84ebef07e7784e Mon Sep 17 00:00:00 2001
From: future <1360317836@qq.com>
Date: Sun, 10 May 2026 17:10:28 +0800
Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E4=BC=9A=E5=91=98=E6=B3=A8?=
=?UTF-8?q?=E5=86=8C=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
gym-manage-api/gym-member/.gitignore | 47 +++
.../gym-member/MEMBER_USER_TABLE_SIMPLE.sql | 58 +++
gym-manage-api/gym-member/merge_comments.py | 102 +++++
gym-manage-api/gym-member/pom.xml | 246 ++++++++++++
.../gym-member/spotbugs-exclude.xml | 18 +
.../member/config/HttpClientConfig.java | 22 ++
.../member/config/WechatProperties.java | 66 ++++
.../member/dto/AdminUpdatePhoneDto.java | 17 +
.../gym/manage/member/dto/WechatLoginDto.java | 29 ++
.../member/dto/WechatOfficialEventDto.java | 35 ++
.../gym/manage/member/entity/BaseEntity.java | 41 ++
.../gym/manage/member/entity/Member.java | 81 ++++
.../gym/manage/member/entity/WechatUser.java | 55 +++
.../manage/member/handler/MemberHandler.java | 140 +++++++
.../member/handler/WechatAuthHandler.java | 68 ++++
.../handler/WechatOfficialEventHandler.java | 218 +++++++++++
.../member/repository/MemberRepository.java | 28 ++
.../repository/WechatUserRepository.java | 29 ++
.../manage/member/service/MemberService.java | 30 ++
.../member/service/WechatApiService.java | 48 +++
.../member/service/WechatAuthService.java | 31 ++
.../member/service/WechatOfficialService.java | 54 +++
.../service/impl/MemberServiceImpl.java | 117 ++++++
.../service/impl/WechatApiServiceImpl.java | 260 ++++++++++++
.../service/impl/WechatAuthServiceImpl.java | 369 ++++++++++++++++++
.../impl/WechatOfficialServiceImpl.java | 313 +++++++++++++++
.../gym/manage/member/util/AesUtil.java | 77 ++++
.../manage/member/util/MemberNoGenerator.java | 53 +++
.../manage/member/util/WechatPhoneUtil.java | 65 +++
.../gym/manage/member/vo/MemberInfoVO.java | 46 +++
.../gym/manage/member/vo/WechatLoginVO.java | 38 ++
.../manage/member/vo/WechatUserInfoVO.java | 72 ++++
...ot.autoconfigure.AutoConfiguration.imports | 1 +
.../src/main/resources/application.yml | 18 +
.../src/main/resources/db/schema.sql | 138 +++++++
.../gym/manage/member/MemberModuleTest.java | 16 +
.../gym-member/test-member-apis.bat | 56 +++
.../gym-member/test-member-apis.ps1 | 140 +++++++
38 files changed, 3242 insertions(+)
create mode 100644 gym-manage-api/gym-member/.gitignore
create mode 100644 gym-manage-api/gym-member/MEMBER_USER_TABLE_SIMPLE.sql
create mode 100644 gym-manage-api/gym-member/merge_comments.py
create mode 100644 gym-manage-api/gym-member/pom.xml
create mode 100644 gym-manage-api/gym-member/spotbugs-exclude.xml
create mode 100644 gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/config/HttpClientConfig.java
create mode 100644 gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/config/WechatProperties.java
create mode 100644 gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/dto/AdminUpdatePhoneDto.java
create mode 100644 gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/dto/WechatLoginDto.java
create mode 100644 gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/dto/WechatOfficialEventDto.java
create mode 100644 gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/entity/BaseEntity.java
create mode 100644 gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/entity/Member.java
create mode 100644 gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/entity/WechatUser.java
create mode 100644 gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/handler/MemberHandler.java
create mode 100644 gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/handler/WechatAuthHandler.java
create mode 100644 gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/handler/WechatOfficialEventHandler.java
create mode 100644 gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/repository/MemberRepository.java
create mode 100644 gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/repository/WechatUserRepository.java
create mode 100644 gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/MemberService.java
create mode 100644 gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/WechatApiService.java
create mode 100644 gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/WechatAuthService.java
create mode 100644 gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/WechatOfficialService.java
create mode 100644 gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/MemberServiceImpl.java
create mode 100644 gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/WechatApiServiceImpl.java
create mode 100644 gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/WechatAuthServiceImpl.java
create mode 100644 gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/WechatOfficialServiceImpl.java
create mode 100644 gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/util/AesUtil.java
create mode 100644 gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/util/MemberNoGenerator.java
create mode 100644 gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/util/WechatPhoneUtil.java
create mode 100644 gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/vo/MemberInfoVO.java
create mode 100644 gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/vo/WechatLoginVO.java
create mode 100644 gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/vo/WechatUserInfoVO.java
create mode 100644 gym-manage-api/gym-member/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
create mode 100644 gym-manage-api/gym-member/src/main/resources/application.yml
create mode 100644 gym-manage-api/gym-member/src/main/resources/db/schema.sql
create mode 100644 gym-manage-api/gym-member/src/test/java/cn/novalon/gym/manage/member/MemberModuleTest.java
create mode 100644 gym-manage-api/gym-member/test-member-apis.bat
create mode 100644 gym-manage-api/gym-member/test-member-apis.ps1
diff --git a/gym-manage-api/gym-member/.gitignore b/gym-manage-api/gym-member/.gitignore
new file mode 100644
index 0000000..9d5d968
--- /dev/null
+++ b/gym-manage-api/gym-member/.gitignore
@@ -0,0 +1,47 @@
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+
+### Maven ###
+pom.xml.tag
+pom.xml.releaseBackup
+pom.xml.versionsBackup
+pom.xml.next
+release.properties
+dependency-reduced-pom.xml
+buildNumber.properties
+.mvn/timing.properties
+.mvn/wrapper/maven-wrapper.jar
+
+### System Files ###
+.DS_Store
+Thumbs.db
diff --git a/gym-manage-api/gym-member/MEMBER_USER_TABLE_SIMPLE.sql b/gym-manage-api/gym-member/MEMBER_USER_TABLE_SIMPLE.sql
new file mode 100644
index 0000000..aef3540
--- /dev/null
+++ b/gym-manage-api/gym-member/MEMBER_USER_TABLE_SIMPLE.sql
@@ -0,0 +1,58 @@
+-- ============================================
+-- member_user 表 - 简洁版建表语句
+-- ============================================
+-- 用途:直接复制执行,快速创建会员表
+-- ============================================
+
+CREATE TABLE IF NOT EXISTS member_user (
+ -- 主键和基础字段
+ id BIGSERIAL PRIMARY KEY,
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+
+ -- 会员核心字段
+ member_no VARCHAR(50) NOT NULL UNIQUE,
+ nickname VARCHAR(100),
+ phone VARCHAR(255),
+ gender INTEGER DEFAULT 0,
+ birthday TIMESTAMP,
+ address VARCHAR(500),
+ avatar VARCHAR(500),
+ subscribed BOOLEAN DEFAULT FALSE,
+ last_login_at TIMESTAMP,
+
+ -- 微信相关字段
+ union_id VARCHAR(100),
+ miniapp_open_id VARCHAR(100),
+ official_open_id VARCHAR(100),
+
+ -- 软删除字段
+ is_deleted BOOLEAN DEFAULT FALSE
+);
+
+-- 创建索引
+CREATE UNIQUE INDEX IF NOT EXISTS idx_member_user_member_no ON member_user(member_no);
+CREATE INDEX IF NOT EXISTS idx_member_user_union_id ON member_user(union_id);
+CREATE INDEX IF NOT EXISTS idx_member_user_miniapp_openid ON member_user(miniapp_open_id);
+CREATE INDEX IF NOT EXISTS idx_member_user_official_openid ON member_user(official_open_id);
+CREATE INDEX IF NOT EXISTS idx_member_user_phone ON member_user(phone);
+CREATE INDEX IF NOT EXISTS idx_member_user_is_deleted ON member_user(is_deleted);
+
+-- 添加注释
+COMMENT ON TABLE member_user IS '会员表';
+COMMENT ON COLUMN member_user.id IS '主键ID';
+COMMENT ON COLUMN member_user.created_at IS '创建时间';
+COMMENT ON COLUMN member_user.updated_at IS '更新时间';
+COMMENT ON COLUMN member_user.member_no IS '会员编号(唯一)';
+COMMENT ON COLUMN member_user.nickname IS '昵称';
+COMMENT ON COLUMN member_user.phone IS '手机号(AES加密存储)';
+COMMENT ON COLUMN member_user.gender IS '性别:0-未知,1-男,2-女';
+COMMENT ON COLUMN member_user.birthday IS '生日';
+COMMENT ON COLUMN member_user.address IS '地址';
+COMMENT ON COLUMN member_user.avatar IS '头像URL';
+COMMENT ON COLUMN member_user.subscribed IS '是否关注服务号';
+COMMENT ON COLUMN member_user.last_login_at IS '最后登录时间';
+COMMENT ON COLUMN member_user.union_id IS '微信UnionID(跨应用唯一标识)';
+COMMENT ON COLUMN member_user.miniapp_open_id IS '小程序OpenID';
+COMMENT ON COLUMN member_user.official_open_id IS '服务号OpenID';
+COMMENT ON COLUMN member_user.is_deleted IS '是否删除(软删除标记)';
diff --git a/gym-manage-api/gym-member/merge_comments.py b/gym-manage-api/gym-member/merge_comments.py
new file mode 100644
index 0000000..3c1000c
--- /dev/null
+++ b/gym-manage-api/gym-member/merge_comments.py
@@ -0,0 +1,102 @@
+# -*- coding: utf-8 -*-
+import os
+import re
+
+base_path = r"c:\Users\13603\Desktop\健身房项目\gym-manage\gym-manage-api\gym-member\src\main\java\cn\novalon\gym\manage\member"
+
+count = 0
+for root, dirs, files in os.walk(base_path):
+ for file in files:
+ if file.endswith('.java'):
+ filepath = os.path.join(root, file)
+ with open(filepath, 'r', encoding='utf-8') as f:
+ content = f.read()
+
+ # 匹配模式:两个连续的注释块(类文档 + 作者注释)
+ pattern = r'(/\*\*[\s\S]*?\*/)\s*\n(/\*\*\s*\n\s*\*\s*@author\s+.+\s*\n\s*\*\s*@date\s+\d{4}-\d{2}-\d{2}\s*\n\s*\*/)'
+
+ match = re.search(pattern, content)
+ if match:
+ class_doc = match.group(1)
+ author_doc = match.group(2)
+
+ # 提取作者信息
+ author_match = re.search(r'@author\s+(.+)', author_doc)
+ date_match = re.search(r'@date\s+(\d{4}-\d{2}-\d{2})', author_doc)
+
+ if author_match and date_match:
+ author = author_match.group(1).strip()
+ date = date_match.group(1).strip()
+
+ # 提取类文档的内容(去掉 /** 和 */)
+ class_doc_content = re.sub(r'/\*\*\s*\n|\s*\*/', '', class_doc).strip()
+ # 清理每行开头的多余星号和空格
+ class_doc_lines = class_doc_content.split('\n')
+ cleaned_lines = []
+ for line in class_doc_lines:
+ cleaned_line = re.sub(r'^\s*\*\s?', '', line).strip()
+ if cleaned_line:
+ cleaned_lines.append(cleaned_line)
+ class_doc_content = '\n * '.join(cleaned_lines)
+
+ # 构建合并后的注释
+ merged_doc = f"""/**
+ * {class_doc_content}
+ *
+ * @author {author}
+ * @date {date}
+ */"""
+
+ # 替换原文
+ new_content = content[:match.start()] + merged_doc + content[match.end():]
+
+ with open(filepath, 'w', encoding='utf-8') as f:
+ f.write(new_content)
+
+ count += 1
+ print(f"已合并: {file}")
+ else:
+ # 检查是否有单独的作者注释在类文档之前
+ pattern2 = r'/\*\*\s*\n\s*\*\s*@author\s+.+\s*\n\s*\*\s*@date\s+\d{4}-\d{2}-\d{2}\s*\n\s*\*/\s*\n(/\*\*[\s\S]*?\*/)'
+ match2 = re.search(pattern2, content)
+ if match2:
+ author_doc = match2.group(0).split('\n/**')[0] + '\n/**'
+ class_doc = '/**' + match2.group(1)
+
+ # 提取作者信息
+ author_match = re.search(r'@author\s+(.+)', author_doc)
+ date_match = re.search(r'@date\s+(\d{4}-\d{2}-\d{2})', author_doc)
+
+ if author_match and date_match:
+ author = author_match.group(1).strip()
+ date = date_match.group(1).strip()
+
+ # 提取类文档的内容
+ class_doc_content = re.sub(r'/\*\*\s*\n|\s*\*/', '', class_doc).strip()
+ # 清理每行开头的多余星号和空格
+ class_doc_lines = class_doc_content.split('\n')
+ cleaned_lines = []
+ for line in class_doc_lines:
+ cleaned_line = re.sub(r'^\s*\*\s?', '', line).strip()
+ if cleaned_line:
+ cleaned_lines.append(cleaned_line)
+ class_doc_content = '\n * '.join(cleaned_lines)
+
+ # 构建合并后的注释
+ merged_doc = f"""/**
+ * {class_doc_content}
+ *
+ * @author {author}
+ * @date {date}
+ */"""
+
+ # 替换原文
+ new_content = content[:match2.start()] + merged_doc + content[match2.end():]
+
+ with open(filepath, 'w', encoding='utf-8') as f:
+ f.write(new_content)
+
+ count += 1
+ print(f"已合并: {file}")
+
+print(f"\n完成!共合并 {count} 个文件")
diff --git a/gym-manage-api/gym-member/pom.xml b/gym-manage-api/gym-member/pom.xml
new file mode 100644
index 0000000..00d2673
--- /dev/null
+++ b/gym-manage-api/gym-member/pom.xml
@@ -0,0 +1,246 @@
+
+
+ 4.0.0
+
+
+ cn.novalon.gym.manage
+ gym-manage-api
+ 1.0.0
+
+
+ gym-member
+ jar
+
+ Gym Member
+ Member Management Module - Frontend User Services
+
+
+
+ cn.novalon.gym.manage
+ manage-common
+ ${project.version}
+
+
+ cn.novalon.gym.manage
+ manage-db
+ ${project.version}
+
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+
+
+ org.springframework.boot
+ spring-boot-starter-aop
+
+
+ org.springdoc
+ springdoc-openapi-starter-webflux-ui
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+ org.springframework.data
+ spring-data-commons
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.springframework.security
+ spring-security-test
+ test
+
+
+ io.projectreactor
+ reactor-test
+ test
+
+
+ io.github.resilience4j
+ resilience4j-spring-boot3
+
+
+ io.github.resilience4j
+ resilience4j-reactor
+
+
+ org.testcontainers
+ testcontainers
+ 1.21.4
+ test
+
+
+ org.testcontainers
+ postgresql
+ 1.21.4
+ test
+
+
+ org.testcontainers
+ junit-jupiter
+ 1.21.4
+ test
+
+
+ com.h2database
+ h2
+ test
+
+
+ io.r2dbc
+ r2dbc-h2
+ test
+
+
+ org.postgresql
+ r2dbc-postgresql
+ test
+
+
+
+ com.github.binarywang
+ weixin-java-miniapp
+ 4.6.0
+
+
+ com.github.binarywang
+ weixin-java-mp
+ 4.6.0
+
+
+
+ io.jsonwebtoken
+ jjwt-api
+
+
+ io.jsonwebtoken
+ jjwt-impl
+ runtime
+
+
+ io.jsonwebtoken
+ jjwt-jackson
+ runtime
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 3.4.2
+
+
+ default-jar
+ package
+
+ jar
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.11.0
+
+ 21
+ 21
+
+
+ org.mapstruct
+ mapstruct-processor
+ 1.5.5.Final
+
+
+ org.projectlombok
+ lombok
+ ${lombok.version}
+
+
+ org.projectlombok
+ lombok-mapstruct-binding
+ 0.2.0
+
+
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ 0.8.12
+
+
+ prepare-agent
+
+ prepare-agent
+
+
+
+ report
+ verify
+
+ report
+
+
+
+ check
+ verify
+
+ check
+
+
+
+
+ BUNDLE
+
+
+ INSTRUCTION
+ COVEREDRATIO
+ 0.60
+
+
+
+
+
+
+
+
+
+ com.github.spotbugs
+ spotbugs-maven-plugin
+ 4.8.6.0
+
+
+ com.github.spotbugs
+ spotbugs
+ 4.8.6
+
+
+
+
+ spotbugs-check
+ verify
+
+ check
+
+
+
+
+ Max
+ High
+ true
+ spotbugs-exclude.xml
+
+
+
+
+
diff --git a/gym-manage-api/gym-member/spotbugs-exclude.xml b/gym-manage-api/gym-member/spotbugs-exclude.xml
new file mode 100644
index 0000000..fd9cd91
--- /dev/null
+++ b/gym-manage-api/gym-member/spotbugs-exclude.xml
@@ -0,0 +1,18 @@
+
diff --git a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/config/HttpClientConfig.java b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/config/HttpClientConfig.java
new file mode 100644
index 0000000..a3be578
--- /dev/null
+++ b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/config/HttpClientConfig.java
@@ -0,0 +1,22 @@
+package cn.novalon.gym.manage.member.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.reactive.function.client.WebClient;
+
+/**
+ * HTTP 客户端配置
+ *
+ * @author 付嘉
+ * @date 2026-05-01
+ */
+
+@Configuration
+public class HttpClientConfig {
+
+ // WebClient Bean,用于调用微信 API
+ @Bean
+ public WebClient webClient() {
+ return WebClient.builder().build();
+ }
+}
diff --git a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/config/WechatProperties.java b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/config/WechatProperties.java
new file mode 100644
index 0000000..9527e6b
--- /dev/null
+++ b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/config/WechatProperties.java
@@ -0,0 +1,66 @@
+package cn.novalon.gym.manage.member.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.PropertySource;
+import org.springframework.stereotype.Component;
+
+/**
+ * 微信配置
+ *
+ * @author 付嘉
+ * @date 2026-05-01
+ */
+
+@Data
+@Component
+@ConfigurationProperties(prefix = "wechat")
+// 指定属性文件位置(当前在gym-member模块的application.yml)
+@PropertySource(value = "classpath:application.yml", ignoreResourceNotFound = true)
+public class WechatProperties {
+
+ // 小程序配置
+ private MiniApp miniapp = new MiniApp();
+
+ // 服务号配置
+ private Mp mp = new Mp();
+
+ // 手机号加密配置
+ private PhoneEncryption phoneEncryption = new PhoneEncryption();
+
+ @Data
+ public static class MiniApp {
+ // 小程序 AppID
+ private String appId;
+
+ // 小程序 AppSecret
+ private String appSecret;
+ }
+
+ @Data
+ public static class Mp {
+ // 服务号 AppID
+ private String appId;
+
+ // 服务号 AppSecret
+ private String appSecret;
+
+ // Token 验证信息
+ private String token;
+
+ // EncodingAESKey
+ private String aesKey;
+
+ // 回调地址(微信服务号事件推送 URL)
+ private String callbackUrl;
+ }
+
+ @Data
+ public static class PhoneEncryption {
+ // 手机号加密密钥
+ private String secretKey;
+
+ // 初始化向量 IV
+ private String iv;
+ }
+}
diff --git a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/dto/AdminUpdatePhoneDto.java b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/dto/AdminUpdatePhoneDto.java
new file mode 100644
index 0000000..0854b49
--- /dev/null
+++ b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/dto/AdminUpdatePhoneDto.java
@@ -0,0 +1,17 @@
+package cn.novalon.gym.manage.member.dto;
+
+import lombok.Data;
+
+/**
+ * 更新手机号Dto
+ *
+ * @author 付嘉
+ * @date 2026-05-01
+ */
+
+@Data
+public class AdminUpdatePhoneDto {
+
+ // 手机号
+ private String phone;
+}
diff --git a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/dto/WechatLoginDto.java b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/dto/WechatLoginDto.java
new file mode 100644
index 0000000..b8f706d
--- /dev/null
+++ b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/dto/WechatLoginDto.java
@@ -0,0 +1,29 @@
+package cn.novalon.gym.manage.member.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import jakarta.validation.constraints.NotBlank;
+
+/**
+ * 微信小程序登录 DTO
+ *
+ * @author 付嘉
+ * @date 2026-05-01
+ */
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class WechatLoginDto {
+
+ // 微信小程序登录 code
+ @NotBlank(message = "登录code不能为空")
+ private String code;
+
+ // 手机号code
+ private String phoneCode;
+}
diff --git a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/dto/WechatOfficialEventDto.java b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/dto/WechatOfficialEventDto.java
new file mode 100644
index 0000000..602f3d3
--- /dev/null
+++ b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/dto/WechatOfficialEventDto.java
@@ -0,0 +1,35 @@
+package cn.novalon.gym.manage.member.dto;
+
+import lombok.Data;
+
+/**
+ * 微信服务号事件 DTO
+ *
+ * @author 付嘉
+ * @date 2026-05-01
+ */
+
+@Data
+public class WechatOfficialEventDto {
+
+ // 微信号
+ private String toUserName;
+
+ // 发送方帐号(一个 OpenID)
+ private String fromUserName;
+
+ // 消息创建时间(整型)
+ private Long createTime;
+
+ // 消息类型,event
+ private String msgType;
+
+ // 事件类型:subscribe(关注)/ unsubscribe(取消关注)
+ private String event;
+
+ // 事件 KEY
+ private String eventKey;
+
+ // 二维码 ticket(获取二维码图片)
+ private String ticket;
+}
diff --git a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/entity/BaseEntity.java b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/entity/BaseEntity.java
new file mode 100644
index 0000000..1ad7597
--- /dev/null
+++ b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/entity/BaseEntity.java
@@ -0,0 +1,41 @@
+package cn.novalon.gym.manage.member.entity;
+
+import org.springframework.data.annotation.CreatedDate;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.annotation.LastModifiedDate;
+import org.springframework.data.domain.Persistable;
+import org.springframework.data.relational.core.mapping.Column;
+
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 会员模块实体基类
+ *
+ * @author 付嘉
+ * @date 2026-05-08
+ */
+@Data
+public abstract class BaseEntity implements Persistable {
+
+ // ID
+ @Id
+ private Long id;
+
+ // 创建时间
+ @CreatedDate
+ @Column("created_at")
+ private LocalDateTime createdAt;
+
+ // 更新时间
+ @LastModifiedDate
+ @Column("updated_at")
+ private LocalDateTime updatedAt;
+
+ // 判断当前实体是否是新建的
+ @Override
+ public boolean isNew() {
+ return createdAt == null;
+ }
+}
diff --git a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/entity/Member.java b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/entity/Member.java
new file mode 100644
index 0000000..36c7ba4
--- /dev/null
+++ b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/entity/Member.java
@@ -0,0 +1,81 @@
+package cn.novalon.gym.manage.member.entity;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import org.springframework.data.relational.core.mapping.Column;
+import org.springframework.data.relational.core.mapping.Table;
+
+import java.time.LocalDateTime;
+import java.util.Date;
+
+/**
+ * 会员实体类 - 对应 member_user 表
+ *
+ * @author 付嘉
+ * @date 2026-05-01
+ */
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+@Table("member_user")
+public class Member extends BaseEntity {
+
+ //会员号
+ @Column("member_no")
+ private String memberNo;
+
+ //昵称
+ @Column("nickname")
+ private String nickname;
+
+ //手机号(AES 加密存储
+ @Column("phone")
+ private String phone;
+
+ //性别
+ @Column("gender")
+ private Integer gender;
+
+ //生日
+ @Column("birthday")
+ private Date birthday;
+
+ //地址
+ @Column("address")
+ private String address;
+
+ //是否关注服务号
+ @Column("subscribed")
+ private Boolean subscribed;
+
+ // 最后登录时间
+ @Column("last_login_at")
+ private LocalDateTime lastLoginAt;
+
+ // 头像
+ @Column("avatar")
+ private String avatar;
+
+ // 微信UnionID
+ @Column("union_id")
+ private String unionId;
+
+ // 微信OpenID小程序
+ @Column("miniapp_open_id")
+ private String miniappOpenId;
+
+ // 服务号openid
+ @Column("official_open_id")
+ private String officialOpenId;
+
+ // 软删除
+ @Column("is_deleted")
+ private Boolean isDeleted;
+
+}
diff --git a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/entity/WechatUser.java b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/entity/WechatUser.java
new file mode 100644
index 0000000..2b7a8e0
--- /dev/null
+++ b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/entity/WechatUser.java
@@ -0,0 +1,55 @@
+package cn.novalon.gym.manage.member.entity;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import org.springframework.data.relational.core.mapping.Column;
+import org.springframework.data.relational.core.mapping.Table;
+
+import java.time.LocalDateTime;
+
+/**
+ * 微信用户信息
+ *
+ * @author 付嘉
+ * @date 2026-05-01
+ */
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+@Table("wechat_user")
+public class WechatUser extends BaseEntity {
+
+ // 会员 ID
+ @Column("member_id")
+ private Long memberId;
+
+ // 微信 UnionID
+ @Column("union_id")
+ private String unionId;
+
+ // 小程序 OpenID
+ @Column("miniapp_openid")
+ private String miniappOpenid;
+
+ // 服务号 OpenID
+ @Column("mp_openid")
+ private String mpOpenid;
+
+ // 是否关注服务号
+ @Column("is_subscribed")
+ private Boolean isSubscribed;
+
+ // 首次关注时间公众号的时间
+ @Column("subscribe_time")
+ private LocalDateTime subscribeTime;
+
+ // 最后一次取消关注的时间
+ @Column("unsubscribe_time")
+ private LocalDateTime unsubscribeTime;
+}
diff --git a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/handler/MemberHandler.java b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/handler/MemberHandler.java
new file mode 100644
index 0000000..271d499
--- /dev/null
+++ b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/handler/MemberHandler.java
@@ -0,0 +1,140 @@
+package cn.novalon.gym.manage.member.handler;
+
+import cn.novalon.gym.manage.member.dto.AdminUpdatePhoneDto;
+import cn.novalon.gym.manage.member.service.MemberService;
+import cn.novalon.gym.manage.member.service.WechatAuthService;
+import cn.novalon.gym.manage.member.service.WechatOfficialService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.math.NumberUtils;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Component;
+import org.springframework.web.reactive.function.server.ServerRequest;
+import org.springframework.web.reactive.function.server.ServerResponse;
+import reactor.core.publisher.Mono;
+
+/**
+ * 会员信息处理器
+ *
+ * @author 付嘉
+ * @date 2026-05-01
+ */
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class MemberHandler {
+
+ private final MemberService memberService;
+ private final WechatAuthService wechatAuthService;
+ private final WechatOfficialService wechatOfficialService;
+
+ /**
+ * 获取会员信息
+ *
+ * GET /api/member/info
+ * Header: X-Member-Id: 123
+ */
+ public Mono getMemberInfo(ServerRequest request) {
+
+ String memberIdStr = request.headers().firstHeader("X-Member-Id");
+ long memberId = NumberUtils.toLong(memberIdStr,0L);
+
+ if (memberId <= 0) throw new IllegalArgumentException("获取会员信息失败: memberId 无效");
+
+ log.info("获取会员信息, memberId: {}", memberId);
+
+ return memberService.getMemberInfo(memberId)
+ .flatMap(info -> {
+ return ServerResponse.ok()
+ .contentType(MediaType.APPLICATION_JSON)
+ .bodyValue(info);
+ });
+ }
+
+ /**
+ * 绑定手机号(微信小程序)
+ *
+ * POST /api/member/phone/bind?code=PHONE_CODE
+ * Header: X-Member-Id: 123
+ */
+ public Mono bindPhone(ServerRequest request) {
+
+ String memberIdStr = request.headers().firstHeader("X-Member-Id");
+ Long memberId = NumberUtils.toLong(memberIdStr, 0L);
+
+ if (memberId <= 0) throw new IllegalArgumentException("绑定手机号失败: memberId 无效");
+
+ String phoneCode = request.queryParam("phoneCode").orElse("");
+
+ if (phoneCode == null || phoneCode.trim().isEmpty()) throw new IllegalArgumentException("手机号code不能为空");
+
+ log.info("收到绑定手机号请求, memberId: {}, phoneCode: {}", memberId, phoneCode);
+
+ return wechatAuthService.bindPhone(memberId, phoneCode)
+ .flatMap(success -> ServerResponse.ok()
+ .contentType(MediaType.APPLICATION_JSON)
+ .bodyValue(success));
+ }
+
+ /**
+ * 查询服务号关注状态
+ *
+ * GET /api/member/subscribe/status
+ * Header: X-Member-Id: 123
+ *
+ */
+ public Mono checkSubscribeStatus(ServerRequest request) {
+
+ String memberIdStr = request.headers().firstHeader("X-Member-Id");
+ long memberId = NumberUtils.toLong(memberIdStr,0L);
+
+ if (memberId <= 0) throw new IllegalArgumentException("查询服务号关注状态失败: memberId 无效");
+
+ log.info("查询服务号关注状态, memberId: {}", memberId);
+
+ return wechatOfficialService.checkSubscribeStatus(memberId)
+ .flatMap(subscribed -> {
+ return ServerResponse.ok()
+ .contentType(MediaType.APPLICATION_JSON)
+ .bodyValue(subscribed);
+ });
+ }
+
+ /**
+ * 管理员更新手机号
+ *
+ * POST /api/admin/members/123/phone
+ * Body: { "phone": "13800138000" }
+ *
+ */
+ public Mono adminUpdatePhone(ServerRequest request) {
+
+ String memberIdStr = request.pathVariable("id");
+ long memberId = NumberUtils.toLong(memberIdStr, 0L);
+
+ if (memberId <= 0) throw new IllegalArgumentException("更新手机号失败: memberId 无效");
+
+ log.info("收到更新手机号请求, memberId: {}", memberId);
+
+ return request.bodyToMono(AdminUpdatePhoneDto.class)
+ .flatMap(body -> {
+ String phone = body.getPhone();
+
+ if (phone == null || phone.isEmpty()) return Mono.error(new IllegalArgumentException("手机号不能为空"));
+
+ if (!phone.matches("^1[3-9]\\d{9}$")) return Mono.error(new IllegalArgumentException("手机号格式不正确"));
+
+ log.info("开始更新手机号, memberId: {}, phone: {}", memberId, phone);
+
+ return memberService.adminUpdatePhone(memberId, phone);
+ })
+ .flatMap(success -> {
+ log.info("手机号更新成功, memberId: {}", memberId);
+ return ServerResponse.ok()
+ .contentType(MediaType.APPLICATION_JSON)
+ .bodyValue(success);
+ });
+ }
+
+}
diff --git a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/handler/WechatAuthHandler.java b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/handler/WechatAuthHandler.java
new file mode 100644
index 0000000..7ba7fa8
--- /dev/null
+++ b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/handler/WechatAuthHandler.java
@@ -0,0 +1,68 @@
+package cn.novalon.gym.manage.member.handler;
+
+import cn.novalon.gym.manage.member.dto.WechatLoginDto;
+import cn.novalon.gym.manage.member.service.WechatAuthService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Component;
+import org.springframework.web.reactive.function.server.ServerRequest;
+import org.springframework.web.reactive.function.server.ServerResponse;
+import reactor.core.publisher.Mono;
+
+/**
+ * 微信֤
+ *
+ * @author 付嘉
+ * @date 2026-05-01
+ */
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class WechatAuthHandler {
+
+ private final WechatAuthService wechatAuthService;
+ private final WechatOfficialEventHandler wechatOfficialEventHandler;
+
+ /**
+ * 小程序更新
+ *
+ * POST /api/member/auth/miniapp/login
+ * Body: {"code": "wx_login_code"}
+ *
+ * @param request ServerRequest
+ * @return Mono 登录响应
+ */
+ public Mono miniappLogin(ServerRequest request) {
+ log.info("收到小程序登录请求");
+
+ return request.bodyToMono(WechatLoginDto.class)
+ .flatMap(loginRequest -> {
+ log.info("开始微信AuthService, code: {}", loginRequest.getCode());
+ return wechatAuthService.miniappLogin(loginRequest);
+ })
+ .flatMap(response -> {
+ log.info("更新成功, memberId: {}", response.getMemberId());
+ return ServerResponse.ok()
+ .contentType(MediaType.APPLICATION_JSON)
+ .bodyValue(response);
+ });
+ }
+
+ /**
+ * 更新ص
+ *
+ * POST /api/member/auth/mp/callback
+ * Body: subscribeopenid
+ *
+ */
+ public Mono mpCallback(ServerRequest request) {
+ return wechatOfficialEventHandler.handleEvent(request);
+ }
+
+ // ֤微信ŷǩ
+ public Mono verifyMpSignature(ServerRequest request) {
+ return wechatOfficialEventHandler.verifySignature(request);
+ }
+}
diff --git a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/handler/WechatOfficialEventHandler.java b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/handler/WechatOfficialEventHandler.java
new file mode 100644
index 0000000..d35ef87
--- /dev/null
+++ b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/handler/WechatOfficialEventHandler.java
@@ -0,0 +1,218 @@
+package cn.novalon.gym.manage.member.handler;
+
+import cn.novalon.gym.manage.member.config.WechatProperties;
+import cn.novalon.gym.manage.member.service.WechatOfficialService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Component;
+import org.springframework.web.reactive.function.server.ServerRequest;
+import org.springframework.web.reactive.function.server.ServerResponse;
+import reactor.core.publisher.Mono;
+
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.util.Arrays;
+
+/**
+ * 微信ŷ更新
+ *
+ * @author 付嘉
+ * @date 2026-05-01
+ */
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class WechatOfficialEventHandler {
+
+ private final WechatOfficialService wechatOfficialService;
+ private final WechatProperties wechatProperties;
+
+ /**
+ * 微信ŷ͵更新
+ *
+ * ʽXML
+ * Ӧʽsuccess 头像地址
+ */
+ public Mono handleEvent(ServerRequest request) {
+ return request.bodyToMono(String.class)
+ .flatMap(xmlBody -> {
+ log.info("收到微信ŷ {}", xmlBody);
+
+ // TODO: XML为WechatOfficialEventDto
+ // Ŀǰֱ获取openidevent
+
+ String openId = extractOpenId(xmlBody);
+ String event = extractEvent(xmlBody);
+
+ if (openId == null || event == null) {
+ log.error("微信更新");
+ return ServerResponse.badRequest().bodyValue("error");
+ }
+
+ log.info(" openId={}, event={}", openId, event);
+
+ // 更新ʹ
+ if ("subscribe".equals(event)) {
+ return wechatOfficialService.handleSubscribeEvent(openId)
+ .then(ServerResponse.ok()
+ .contentType(MediaType.TEXT_PLAIN)
+ .bodyValue("success"));
+ } else if ("unsubscribe".equals(event)) {
+ return wechatOfficialService.handleUnsubscribeEvent(openId)
+ .then(ServerResponse.ok()
+ .contentType(MediaType.TEXT_PLAIN)
+ .bodyValue("success"));
+ } else {
+ log.warn("δ֪更新: {}", event);
+ return ServerResponse.ok()
+ .contentType(MediaType.TEXT_PLAIN)
+ .bodyValue("success");
+ }
+ })
+ .onErrorResume(e -> {
+ log.error("微信更新失败", e);
+ return ServerResponse.ok()
+ .contentType(MediaType.TEXT_PLAIN)
+ .bodyValue("success"); // ʹ失败Ҳsuccess微信
+ });
+ }
+
+ /**
+ * ֤微信ŷǩ
+ *
+ * GET֤头像地址
+ */
+ public Mono verifySignature(ServerRequest request) {
+ String signature = request.queryParam("signature").orElse("");
+ String timestamp = request.queryParam("timestamp").orElse("");
+ String nonce = request.queryParam("nonce").orElse("");
+ String echostr = request.queryParam("echostr").orElse("");
+
+ log.info("========== 微信ǩ֤==========");
+ log.info("收到IJ:");
+ log.info(" signature: {}", signature);
+ log.info(" timestamp: {}", timestamp);
+ log.info(" nonce: {}", nonce);
+ log.info(" echostr: {}", echostr);
+
+ // 获取õToken
+ String token = wechatProperties.getMp().getToken();
+ log.info("õToken: {}", token);
+
+ // ֤ǩ
+ if (checkSignature(signature, timestamp, nonce, token)) {
+ log.info("ǩ֤成功echostr: {}", echostr);
+ log.info("========== 微信ǩ֤ ==========");
+ return ServerResponse.ok()
+ .contentType(MediaType.TEXT_PLAIN)
+ .bodyValue(echostr);
+ } else {
+ log.warn("ǩ֤失败");
+ log.info("========== 微信ǩ֤ ==========");
+ return ServerResponse.badRequest()
+ .contentType(MediaType.TEXT_PLAIN)
+ .bodyValue("error");
+ }
+ }
+
+ /**
+ * ֤ǩ
+ *
+ * @param signature 微信żǩ
+ * @param timestamp 创建时间
+ * @param nonce
+ * @param token Token
+ * @return Ƿ֤通过
+ */
+ private boolean checkSignature(String signature, String timestamp, String nonce, String token) {
+ // 1. tokentimestampnonceֵ
+ String[] arr = new String[]{token, timestamp, nonce};
+ Arrays.sort(arr);
+
+ // 2. 头像地址ƴӳһ头像地址
+ StringBuilder sb = new StringBuilder();
+ for (String str : arr) {
+ sb.append(str);
+ }
+
+ // 3. ƴӺ头像地址sha1
+ String encrypted = sha1(sb.toString());
+ log.debug("õǩ {}", encrypted);
+
+ // 4. ܺ头像地址signature会员
+ return encrypted != null && encrypted.equalsIgnoreCase(signature);
+ }
+
+ /**
+ * SHA1
+ *
+ * @param str 头像地址
+ * @return ܺ头像地址
+ */
+ private String sha1(String str) {
+ try {
+ MessageDigest md = MessageDigest.getInstance("SHA-1");
+ byte[] digest = md.digest(str.getBytes(StandardCharsets.UTF_8));
+ StringBuilder hexString = new StringBuilder();
+ for (byte b : digest) {
+ String hex = Integer.toHexString(0xff & b);
+ if (hex.length() == 1) {
+ hexString.append('0');
+ }
+ hexString.append(hex);
+ }
+ return hexString.toString();
+ } catch (Exception e) {
+ log.error("SHA1失败", e);
+ return null;
+ }
+ }
+
+ /**
+ * XML OpenID
+ */
+ private String extractOpenId(String xml) {
+ int start = xml.indexOf("");
+ int end = xml.indexOf("");
+ if (start != -1 && end != -1) {
+ String value = xml.substring(start + 14, end);
+ // ȥ CDATA
+ return cleanCdata(value);
+ }
+ return null;
+ }
+
+ /**
+ * XML 获取更新
+ */
+ private String extractEvent(String xml) {
+ int start = xml.indexOf("");
+ int end = xml.indexOf("");
+ if (start != -1 && end != -1) {
+ String value = xml.substring(start + 7, end);
+ // ȥ CDATA
+ return cleanCdata(value);
+ }
+ return null;
+ }
+
+ /**
+ * CDATA
+ * : -> subscribe
+ */
+ private String cleanCdata(String value) {
+ if (value == null) {
+ return null;
+ }
+ // ȥǰհ
+ value = value.trim();
+ // CDATA获取м
+ // ʽ:
+ if (value.startsWith("")) {
+ return value.substring(9, value.length() - 3);
+ }
+ return value;
+ }
+}
diff --git a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/repository/MemberRepository.java b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/repository/MemberRepository.java
new file mode 100644
index 0000000..1e248cf
--- /dev/null
+++ b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/repository/MemberRepository.java
@@ -0,0 +1,28 @@
+package cn.novalon.gym.manage.member.repository;
+
+import cn.novalon.gym.manage.member.entity.Member;
+import org.springframework.data.r2dbc.repository.R2dbcRepository;
+import org.springframework.stereotype.Repository;
+import reactor.core.publisher.Mono;
+
+/**
+ * 会员Repository
+ * @author 付嘉
+ * @date 2026-05-01
+ */
+
+@Repository
+public interface MemberRepository extends R2dbcRepository {
+
+ // UnionID查询会员
+ Mono findByUnionId(String unionId);
+
+ // 小程序OpenID查询会员
+ Mono findByMiniappOpenId(String miniappOpenId);
+
+ // 服务号OpenID查询会员
+ Mono findByOfficialOpenId(String officialOpenId);
+
+ // 手机号查询
+ Mono findByPhone(String phone);
+}
diff --git a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/repository/WechatUserRepository.java b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/repository/WechatUserRepository.java
new file mode 100644
index 0000000..a0afe0b
--- /dev/null
+++ b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/repository/WechatUserRepository.java
@@ -0,0 +1,29 @@
+package cn.novalon.gym.manage.member.repository;
+
+import cn.novalon.gym.manage.member.entity.WechatUser;
+import org.springframework.data.r2dbc.repository.R2dbcRepository;
+import org.springframework.stereotype.Repository;
+import reactor.core.publisher.Mono;
+
+/**
+ * 微信用户Repository
+ *
+ * @author 付嘉
+ * @date 2026-05-01
+ */
+
+@Repository
+public interface WechatUserRepository extends R2dbcRepository {
+
+ // 通过UnionID查询微信用户
+ Mono findByUnionId(String unionId);
+
+ // 通过小程序OpenID查询微信用户
+ Mono findByMiniappOpenid(String miniappOpenid);
+
+ // 通过服务号OpenID查询微信用户
+ Mono findByMpOpenid(String mpOpenid);
+
+ // 通过会员ID查询微信用户
+ Mono findByMemberId(Long memberId);
+}
diff --git a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/MemberService.java b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/MemberService.java
new file mode 100644
index 0000000..23a2ea8
--- /dev/null
+++ b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/MemberService.java
@@ -0,0 +1,30 @@
+package cn.novalon.gym.manage.member.service;
+
+import cn.novalon.gym.manage.member.vo.MemberInfoVO;
+import reactor.core.publisher.Mono;
+
+/**
+ * 会员服务接口
+ *
+ * @author 付嘉
+ * @date 2026-05-01
+ */
+public interface MemberService {
+
+ /**
+ * 获取会员信息
+ *
+ * @param memberId 会员ID
+ * @return 会员信息
+ */
+ Mono getMemberInfo(Long memberId);
+
+ /**
+ * 管理端更新会员手机号
+ *
+ * @param memberId 会员ID
+ * @param phone 明文手机号
+ * @return 是否成功
+ */
+ Mono adminUpdatePhone(Long memberId, String phone);
+}
diff --git a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/WechatApiService.java b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/WechatApiService.java
new file mode 100644
index 0000000..3258cb9
--- /dev/null
+++ b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/WechatApiService.java
@@ -0,0 +1,48 @@
+package cn.novalon.gym.manage.member.service;
+
+import reactor.core.publisher.Mono;
+
+import java.util.Map;
+
+/**
+ * 微信API服务接口
+ *
+ * @author 付嘉
+ * @date 2026-05-01
+ */
+public interface WechatApiService {
+
+ /**
+ * 小程序- 通过code获取session_key/openid
+ *
+ * @param code 小程序登录的code
+ * @return Mono