diff --git a/gym-manage-api/KeyAnalyzer.java b/gym-manage-api/KeyAnalyzer.java
deleted file mode 100644
index e460fe7..0000000
--- a/gym-manage-api/KeyAnalyzer.java
+++ /dev/null
@@ -1,202 +0,0 @@
-import java.util.Base64;
-import java.math.BigInteger;
-import java.io.ByteArrayInputStream;
-
-public class KeyAnalyzer {
- public static void main(String[] args) {
- String privateKeyBase64 = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCcxAwrhZF+izz1gxQKOr4jnC05obIBHbl0DzHOcd4CaaDV7Kv1NRJKi33JhdDsW4JGgu16e5Rtzq1VzU5VWz5EKgGL46maOFwCkngJUTP/LC3JVf/wtJmYCm8xNE8C7GNNIqKzYEvUOEfXqagpVvVrGsQ9FpzFp2rP9hBmHY3yizVyPX/uT9S+af5TRxiaItj3SSJGgloaEMrnKOpb/EH7JwPSS0liAyT/NxPfOyyZHc22AvaAIOE5y+0PMUIKPuIdfpOrej3LVpO1Arc2hSgmdB+YIPSiBVYPXa6AuAmil9mpbtSikQJ7Uu7lX4JyTW4QxQ06rPFKnFWVKkzivAElAgMBAAECggEAJd0AJ37iTlMpDQ90xqe7hvRQxAu256gbQ9nrqLY97g0/KIw6WEZSPakFX6gvdvb/NzKmUyAIEKGLoh6tXdZk6qfOqc/6BeK47nIcBfwT9/zerjNUVvn34w4aHyNINieMMHQ+Id8PUZmqWH+Euz9ilVTosuyEPwUZulLvUQqwXzU5VnwVghURbUhDd+ecBJACWgemRun6d5241PQXNYAdH1k7cETd8GfIi3qclhhJrxi7tu5tq4YGCXQIoz7HCLim7GIvT0M+FRgSw2EOrHnAQNFeQ/vQbP71ttLoTxehL6Se9dfWrV5OI+Y/T7vR2F84Qt0iNbaxyJGir7siKDFIwQKBgQDXDzinx3/TasplM78pR/0CtuuKr1Ch02LOPrTosJ1qf1OohxQThowhOTxMsBlgYSKu9s1QRffUUXEqYXxd2B6lzDKfggwO6U2XxIcxWeNow0xoFfqcXYSg7Ga2sCr9uhdwxIdFQNF7SNBpT8ht4fJrRX6mWY1nHybpyTDQ4xoQNQKBgQC6m+yiOoi5JD3zVSSJq/iq5DJPA5B4aoP+t5u9lp2Q7iVO0QI5ilBlEKGE4VOU0glnXlDTfuqEYooMY85ekl4WGb3AOT0PhLL2i+gO2nlWBzf4HPzB/hibjfyPyniRM03cHkG3HXucL7Sne6FwERcfjEqjUd2cdP1l89PNrq4rMQKBgQCMmjABSWYh8/y1I5rEQ4OAJdVjC3GdC1Xa35ZpVybjvLEWSpHunhW5lvD8dllw8LC7UTI0XDpGPqTM/4VO2YBYB2PFc0Gs8g0/v0ZgFpOeJ6kpl80MM/wFNemFYTIKRoMSv/psZY9PmfBgGcBBTuquBXZjDcNr+yr2yAm5V/DvTQKBgHqRi94KoF8q5N39IKCkqhJlDH5FkxDktYoKw2rFkPzuzuZz9gghRyj6wXxsG9/2DWMt2dzw0czehFoa/CO188KEadPmRKr6uCmkP2nyKhxNZX+8WnB5G2Sg4DD6BjMpBYz8+qDx5ozx8LDJTYI0V4HLPgMD9JGdbgsXGhlREOkhAoGALr6IQOXnviWNAhCdc7rrsaMLMPbLZ1wqzWtQUG1JxDobbpzEP4CW/mvW5pMn58mSBg5qbXhyDI4fFP0CPb98QIz2tGnIzYyFzdKmF5Z1N7X1OF9O+tsSqASoBZzTqB4fr/o4mz0s9JCeriBR2LWjsbsDU13DTLsfQpWbtOnIy70=";
-
- System.out.println("========== 密钥分析开始 ==========");
- System.out.println("密钥长度: " + privateKeyBase64.length());
-
- byte[] keyBytes = Base64.getDecoder().decode(privateKeyBase64);
- System.out.println("解码后长度: " + keyBytes.length + " bytes");
-
- // 打印前30字节
- System.out.print("前30字节(hex): ");
- for (int i = 0; i < 30 && i < keyBytes.length; i++) {
- System.out.printf("%02X ", keyBytes[i]);
- }
- System.out.println();
-
- // 解析ASN.1结构
- try {
- parseASN1(keyBytes);
- } catch (Exception e) {
- System.out.println("解析失败: " + e.getMessage());
- e.printStackTrace();
- }
- }
-
- static void parseASN1(byte[] keyBytes) throws Exception {
- ByteArrayInputStream bis = new ByteArrayInputStream(keyBytes);
-
- // 读取SEQUENCE
- int tag = bis.read();
- System.out.println("第一个tag: 0x" + Integer.toHexString(tag));
-
- if (tag == 0x30) {
- System.out.println("这是SEQUENCE");
-
- // 读取长度
- int len = readASN1Length(bis);
- System.out.println("SEQUENCE长度: " + len);
-
- // 检查下一个tag
- int nextTag = bis.read();
- System.out.println("下一个tag: 0x" + Integer.toHexString(nextTag));
-
- if (nextTag == 0x02) {
- // INTEGER
- int intLen = readASN1Length(bis);
- byte[] versionBytes = new byte[intLen];
- bis.read(versionBytes);
- int version = new BigInteger(versionBytes).intValue();
- System.out.println("版本INTEGER值: " + version);
-
- // 检查这是PKCS#8还是PKCS#1
- // PKCS#8: version=0后是SEQUENCE(算法标识符)
- // PKCS#1: version=0后是INTEGER(modulus)
- int afterVersionTag = bis.read();
- System.out.println("版本后的tag: 0x" + Integer.toHexString(afterVersionTag));
-
- if (afterVersionTag == 0x30) {
- System.out.println(">>> 这是PKCS#8格式 (version后是SEQUENCE)");
-
- // 解析PKCS#8
- // AlgorithmIdentifier: SEQUENCE { OID, NULL }
- int algLen = readASN1Length(bis);
- System.out.println("AlgorithmIdentifier长度: " + algLen);
-
- // 跳过AlgorithmIdentifier内容
- byte[] algBytes = new byte[algLen];
- bis.read(algBytes);
-
- // 打印OID
- System.out.print("OID bytes: ");
- for (int i = 0; i < algBytes.length; i++) {
- System.out.printf("%02X ", algBytes[i]);
- }
- System.out.println();
-
- // 下一个应该是OCTET STRING (包含私钥)
- int octetTag = bis.read();
- System.out.println("下一个tag: 0x" + Integer.toHexString(octetTag));
-
- if (octetTag == 0x04) {
- System.out.println("这是OCTET STRING (包含私钥数据)");
- int octetLen = readASN1Length(bis);
- System.out.println("OCTET STRING长度: " + octetLen);
-
- // OCTET STRING内容是PKCS#1私钥
- byte[] pkcs1Bytes = new byte[octetLen];
- bis.read(pkcs1Bytes);
-
- System.out.print("PKCS#1私钥前20字节: ");
- for (int i = 0; i < 20; i++) {
- System.out.printf("%02X ", pkcs1Bytes[i]);
- }
- System.out.println();
-
- // 解析PKCS#1私钥
- ByteArrayInputStream pkcs1Stream = new ByteArrayInputStream(pkcs1Bytes);
- int pkcs1Tag = pkcs1Stream.read();
- System.out.println("PKCS#1第一个tag: 0x" + Integer.toHexString(pkcs1Tag));
-
- if (pkcs1Tag == 0x30) {
- int pkcs1Len = readASN1Length(pkcs1Stream);
- System.out.println("PKCS#1 SEQUENCE长度: " + pkcs1Len);
-
- // version
- int vTag = pkcs1Stream.read();
- int vLen = readASN1Length(pkcs1Stream);
- byte[] vBytes = new byte[vLen];
- pkcs1Stream.read(vBytes);
- System.out.println("PKCS#1版本: " + new BigInteger(vBytes));
-
- // 解析私钥参数
- parsePKCS1(pkcs1Stream);
- }
- }
- } else if (afterVersionTag == 0x02) {
- System.out.println(">>> 这是PKCS#1格式 (version后是INTEGER)");
-
- // 继续解析PKCS#1
- parsePKCS1(bis);
- } else {
- System.out.println(">>> 未知格式 (tag: 0x" + Integer.toHexString(afterVersionTag) + ")");
- }
- }
- }
- }
-
- static void parsePKCS1(ByteArrayInputStream bis) throws Exception {
- System.out.println("\n========== 解析PKCS#1私钥参数 ==========");
-
- // modulus
- BigInteger modulus = readASN1Integer(bis);
- System.out.println("modulus长度: " + modulus.bitLength() + " bits");
-
- // publicExponent
- BigInteger publicExponent = readASN1Integer(bis);
- System.out.println("publicExponent: " + publicExponent);
-
- // privateExponent
- BigInteger privateExponent = readASN1Integer(bis);
- System.out.println("privateExponent长度: " + privateExponent.bitLength() + " bits");
-
- // prime1
- BigInteger prime1 = readASN1Integer(bis);
- System.out.println("prime1长度: " + prime1.bitLength() + " bits");
-
- // prime2
- BigInteger prime2 = readASN1Integer(bis);
- System.out.println("prime2长度: " + prime2.bitLength() + " bits");
-
- // 验证 prime1 * prime2 == modulus
- BigInteger calculatedModulus = prime1.multiply(prime2);
- boolean valid = calculatedModulus.equals(modulus);
- System.out.println("\n验证 prime1 * prime2 == modulus: " + valid);
-
- if (!valid) {
- System.out.println(">>> 密钥数学关系不正确!这是无效的RSA私钥");
- System.out.println("计算得到的modulus长度: " + calculatedModulus.bitLength() + " bits");
- }
-
- // exponent1
- BigInteger exponent1 = readASN1Integer(bis);
- System.out.println("exponent1长度: " + exponent1.bitLength() + " bits");
-
- // exponent2
- BigInteger exponent2 = readASN1Integer(bis);
- System.out.println("exponent2长度: " + exponent2.bitLength() + " bits");
-
- // coefficient
- BigInteger coefficient = readASN1Integer(bis);
- System.out.println("coefficient长度: " + coefficient.bitLength() + " bits");
-
- System.out.println("\n========== 解析完成 ==========");
- }
-
- static int readASN1Length(ByteArrayInputStream bis) throws Exception {
- int firstByte = bis.read();
- if ((firstByte & 0x80) == 0) {
- return firstByte;
- }
- int numBytes = firstByte & 0x7F;
- int length = 0;
- for (int i = 0; i < numBytes; i++) {
- length = (length << 8) | (bis.read() & 0xFF);
- }
- return length;
- }
-
- static BigInteger readASN1Integer(ByteArrayInputStream bis) throws Exception {
- int tag = bis.read();
- if (tag != 0x02) throw new Exception("期望INTEGER tag: 0x02, 实际: 0x" + Integer.toHexString(tag));
- int len = readASN1Length(bis);
- byte[] data = new byte[len];
- bis.read(data);
- return new BigInteger(1, data);
- }
-}
\ No newline at end of file
diff --git a/gym-manage-api/META-INF/MANIFEST.MF b/gym-manage-api/META-INF/MANIFEST.MF
deleted file mode 100644
index 19b83f2..0000000
--- a/gym-manage-api/META-INF/MANIFEST.MF
+++ /dev/null
@@ -1,6 +0,0 @@
-Manifest-Version: 1.0
-Created-By: Maven JAR Plugin 3.4.2
-Build-Jdk-Spec: 21
-Implementation-Title: Gym Payment
-Implementation-Version: 1.0.0
-
diff --git a/gym-manage-api/META-INF/maven/cn.novalon.gym.manage/gym-payment/pom.properties b/gym-manage-api/META-INF/maven/cn.novalon.gym.manage/gym-payment/pom.properties
deleted file mode 100644
index e4cde16..0000000
--- a/gym-manage-api/META-INF/maven/cn.novalon.gym.manage/gym-payment/pom.properties
+++ /dev/null
@@ -1,3 +0,0 @@
-artifactId=gym-payment
-groupId=cn.novalon.gym.manage
-version=1.0.0
diff --git a/gym-manage-api/META-INF/maven/cn.novalon.gym.manage/gym-payment/pom.xml b/gym-manage-api/META-INF/maven/cn.novalon.gym.manage/gym-payment/pom.xml
deleted file mode 100644
index 39709f1..0000000
--- a/gym-manage-api/META-INF/maven/cn.novalon.gym.manage/gym-payment/pom.xml
+++ /dev/null
@@ -1,85 +0,0 @@
-
-
-
- 4.0.0
-
-
- cn.novalon.gym.manage
- gym-manage-api
- 1.0.0
-
-
- gym-payment
- jar
-
- Gym Payment
- Payment Module - Integrates Huifu Payment Gateway
-
-
-
- cn.novalon.gym.manage
- manage-common
- ${project.version}
-
-
- org.springframework.boot
- spring-boot-starter-webflux
-
-
- org.springdoc
- springdoc-openapi-starter-webflux-ui
-
-
- org.springframework.boot
- spring-boot-starter-test
- test
-
-
- io.projectreactor
- reactor-test
- test
-
-
- cn.hutool
- hutool-all
- 5.8.25
-
-
- com.squareup.okhttp3
- okhttp
- 4.12.0
-
-
- org.springframework.boot
- spring-boot-starter-validation
-
-
-
-
-
-
- org.apache.maven.plugins
- maven-jar-plugin
- 3.4.2
-
-
- org.apache.maven.plugins
- maven-compiler-plugin
- 3.11.0
-
- 21
- 21
-
-
- org.projectlombok
- lombok
- ${lombok.version}
-
-
-
-
-
-
-
diff --git a/gym-manage-api/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/gym-manage-api/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
deleted file mode 100644
index 691774a..0000000
--- a/gym-manage-api/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
+++ /dev/null
@@ -1 +0,0 @@
-cn.novalon.gym.manage.payment.config.HuifuPayConfig
diff --git a/gym-manage-api/db/migration/V1__Create_all_tables.sql b/gym-manage-api/db/migration/V1__Create_all_tables.sql
deleted file mode 100644
index ab538c2..0000000
--- a/gym-manage-api/db/migration/V1__Create_all_tables.sql
+++ /dev/null
@@ -1,407 +0,0 @@
--- Novalon管理系统数据库初始化脚本
--- 版本: V1
--- 描述: 创建所有核心表结构(合并版)
-
--- ============================================
--- 用户与角色相关表
--- ============================================
-
--- 用户表
-CREATE TABLE IF NOT EXISTS sys_user (
- id BIGSERIAL PRIMARY KEY,
- username VARCHAR(50) NOT NULL UNIQUE,
- password VARCHAR(255) NOT NULL,
- email VARCHAR(100),
- phone VARCHAR(20),
- nickname VARCHAR(100),
- status INTEGER DEFAULT 1,
- role_id BIGINT,
- create_by VARCHAR(50),
- update_by VARCHAR(50),
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- deleted_at TIMESTAMP
-);
-
--- 角色表
-CREATE TABLE IF NOT EXISTS sys_role (
- id BIGSERIAL PRIMARY KEY,
- role_name VARCHAR(100) NOT NULL,
- role_key VARCHAR(100) NOT NULL UNIQUE,
- role_sort INTEGER DEFAULT 0,
- status INTEGER DEFAULT 1,
- create_by VARCHAR(50),
- update_by VARCHAR(50),
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- deleted_at TIMESTAMP
-);
-
--- 用户角色关联表(支持多对多关系)
-CREATE TABLE IF NOT EXISTS user_role (
- id BIGSERIAL PRIMARY KEY,
- user_id BIGINT NOT NULL,
- role_id BIGINT NOT NULL,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- created_by VARCHAR(50),
- CONSTRAINT fk_user_role_user FOREIGN KEY (user_id) REFERENCES sys_user(id) ON DELETE CASCADE,
- CONSTRAINT fk_user_role_role FOREIGN KEY (role_id) REFERENCES sys_role(id) ON DELETE CASCADE,
- CONSTRAINT uk_user_role UNIQUE (user_id, role_id)
-);
-
--- ============================================
--- 权限相关表
--- ============================================
-
--- 权限表
-CREATE TABLE IF NOT EXISTS sys_permission (
- id BIGSERIAL PRIMARY KEY,
- permission_name VARCHAR(100) NOT NULL,
- permission_code VARCHAR(100) NOT NULL UNIQUE,
- resource VARCHAR(200) NOT NULL,
- action VARCHAR(50) NOT NULL,
- description VARCHAR(500),
- status INTEGER DEFAULT 1,
- create_by VARCHAR(50),
- update_by VARCHAR(50),
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- deleted_at TIMESTAMP
-);
-
--- 角色权限关联表
-CREATE TABLE IF NOT EXISTS sys_role_permission (
- id BIGSERIAL PRIMARY KEY,
- role_id BIGINT NOT NULL,
- permission_id BIGINT NOT NULL,
- create_by VARCHAR(50),
- update_by VARCHAR(50),
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- FOREIGN KEY (role_id) REFERENCES sys_role(id) ON DELETE CASCADE,
- FOREIGN KEY (permission_id) REFERENCES sys_permission(id) ON DELETE CASCADE,
- UNIQUE (role_id, permission_id)
-);
-
--- ============================================
--- 菜单相关表
--- ============================================
-
--- 菜单表
-CREATE TABLE IF NOT EXISTS sys_menu (
- id BIGSERIAL PRIMARY KEY,
- menu_name VARCHAR(50) NOT NULL,
- parent_id BIGINT DEFAULT 0,
- order_num INTEGER DEFAULT 0,
- menu_type VARCHAR(1) DEFAULT 'C',
- perms VARCHAR(100),
- component VARCHAR(200),
- status INTEGER DEFAULT 1,
- create_by VARCHAR(50),
- update_by VARCHAR(50),
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- deleted_at TIMESTAMP
-);
-
--- ============================================
--- 字典相关表
--- ============================================
-
--- 字典类型表
-CREATE TABLE IF NOT EXISTS sys_dict_type (
- id BIGSERIAL PRIMARY KEY,
- dict_name VARCHAR(100) NOT NULL,
- dict_type VARCHAR(100) NOT NULL UNIQUE,
- status VARCHAR(1) DEFAULT '0',
- remark VARCHAR(500),
- create_by VARCHAR(50),
- update_by VARCHAR(50),
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- deleted_at TIMESTAMP
-);
-
--- 字典数据表
-CREATE TABLE IF NOT EXISTS sys_dict_data (
- id BIGSERIAL PRIMARY KEY,
- dict_sort INTEGER DEFAULT 0,
- dict_label VARCHAR(100) NOT NULL,
- dict_value VARCHAR(100) NOT NULL,
- dict_type VARCHAR(100) NOT NULL,
- css_class VARCHAR(100),
- list_class VARCHAR(100),
- is_default VARCHAR(1) DEFAULT 'N',
- status VARCHAR(1) DEFAULT '0',
- create_by VARCHAR(50),
- update_by VARCHAR(50),
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- deleted_at TIMESTAMP
-);
-
--- 字典表(通用字典)
-CREATE TABLE IF NOT EXISTS sys_dictionary (
- id BIGSERIAL PRIMARY KEY,
- type VARCHAR(100) NOT NULL,
- code VARCHAR(100) NOT NULL,
- name VARCHAR(100) NOT NULL,
- value VARCHAR(500),
- remark VARCHAR(500),
- sort INTEGER DEFAULT 0,
- create_by VARCHAR(50),
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- deleted_at TIMESTAMP
-);
-
--- ============================================
--- 系统配置表
--- ============================================
-
--- 系统配置表
-CREATE TABLE IF NOT EXISTS sys_config (
- id BIGSERIAL PRIMARY KEY,
- config_name VARCHAR(100) NOT NULL,
- config_key VARCHAR(100) NOT NULL UNIQUE,
- config_value VARCHAR(500) NOT NULL,
- config_type VARCHAR(1) DEFAULT 'N',
- create_by VARCHAR(50),
- update_by VARCHAR(50),
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- deleted_at TIMESTAMP
-);
-
--- ============================================
--- 日志相关表
--- ============================================
-
--- 登录日志表
-CREATE TABLE IF NOT EXISTS sys_login_log (
- id BIGSERIAL PRIMARY KEY,
- username VARCHAR(50),
- ip VARCHAR(50),
- location VARCHAR(255),
- browser VARCHAR(50),
- os VARCHAR(50),
- status VARCHAR(1),
- message VARCHAR(255),
- login_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
-);
-
--- 异常日志表
-CREATE TABLE IF NOT EXISTS sys_exception_log (
- id BIGSERIAL PRIMARY KEY,
- username VARCHAR(50),
- title VARCHAR(100),
- exception_name VARCHAR(100),
- method_name VARCHAR(255),
- method_params TEXT,
- exception_msg TEXT,
- exception_stack TEXT,
- ip VARCHAR(50),
- create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
-);
-
--- 操作日志表
-CREATE TABLE IF NOT EXISTS operation_log (
- id BIGSERIAL PRIMARY KEY,
- username VARCHAR(50),
- operation VARCHAR(100),
- method VARCHAR(200),
- params TEXT,
- result TEXT,
- ip VARCHAR(50),
- duration BIGINT,
- status VARCHAR(1) DEFAULT '0',
- error_msg TEXT,
- create_by VARCHAR(50),
- update_by VARCHAR(50),
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- deleted_at TIMESTAMP
-);
-
--- 审计日志表
-CREATE TABLE IF NOT EXISTS audit_log (
- id BIGSERIAL PRIMARY KEY,
- entity_type VARCHAR(100) NOT NULL,
- entity_id BIGINT,
- operation_type VARCHAR(20) NOT NULL,
- operator VARCHAR(100),
- operation_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- before_data JSONB,
- after_data JSONB,
- changed_fields TEXT[],
- ip_address VARCHAR(50),
- user_agent TEXT,
- description TEXT,
- create_by VARCHAR(50),
- update_by VARCHAR(50),
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- deleted_at TIMESTAMP
-);
-
--- 审计日志归档表
-CREATE TABLE IF NOT EXISTS audit_log_archive (
- id BIGSERIAL PRIMARY KEY,
- entity_type VARCHAR(100) NOT NULL,
- entity_id BIGINT,
- operation_type VARCHAR(20) NOT NULL,
- operator VARCHAR(100),
- operation_time TIMESTAMP,
- before_data JSONB,
- after_data JSONB,
- changed_fields TEXT[],
- ip_address VARCHAR(50),
- user_agent TEXT,
- description TEXT,
- created_at TIMESTAMP,
- archived_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
-);
-
--- ============================================
--- 通知与消息表
--- ============================================
-
--- 系统公告表
-CREATE TABLE IF NOT EXISTS sys_notice (
- id BIGSERIAL PRIMARY KEY,
- notice_title VARCHAR(50) NOT NULL,
- notice_type VARCHAR(1) NOT NULL,
- notice_content TEXT,
- status VARCHAR(1) DEFAULT '0',
- create_by VARCHAR(50),
- update_by VARCHAR(50),
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- deleted_at TIMESTAMP
-);
-
--- 用户消息表
-CREATE TABLE IF NOT EXISTS sys_user_message (
- id BIGSERIAL PRIMARY KEY,
- user_id BIGINT NOT NULL,
- notice_id BIGINT,
- message_title VARCHAR(255),
- message_content TEXT,
- is_read VARCHAR(1) DEFAULT '0',
- read_time TIMESTAMP,
- create_by VARCHAR(50),
- update_by VARCHAR(50),
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- deleted_at TIMESTAMP
-);
-
--- ============================================
--- 文件管理表
--- ============================================
-
--- 文件管理表
-CREATE TABLE IF NOT EXISTS sys_file (
- id BIGSERIAL PRIMARY KEY,
- file_name VARCHAR(255) NOT NULL,
- file_path VARCHAR(500) NOT NULL,
- file_size BIGINT,
- file_type VARCHAR(100),
- file_extension VARCHAR(10),
- storage_type VARCHAR(50),
- create_by VARCHAR(50),
- update_by VARCHAR(50),
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- deleted_at TIMESTAMP
-);
-
--- ============================================
--- OAuth2相关表
--- ============================================
-
--- OAuth2客户端表
-CREATE TABLE IF NOT EXISTS oauth2_client (
- id BIGSERIAL PRIMARY KEY,
- client_id VARCHAR(100) NOT NULL UNIQUE,
- client_secret VARCHAR(255) NOT NULL,
- client_name VARCHAR(100),
- web_server_redirect_uri VARCHAR(500),
- scope VARCHAR(500),
- authorized_grant_types VARCHAR(500),
- access_token_validity_seconds INTEGER,
- refresh_token_validity_seconds INTEGER,
- auto_approve VARCHAR(1) DEFAULT 'false',
- enabled VARCHAR(1) DEFAULT 'true',
- create_by VARCHAR(50),
- update_by VARCHAR(50),
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- deleted_at TIMESTAMP
-);
-
--- ============================================
--- 表注释
--- ============================================
-
-COMMENT ON TABLE sys_user IS '系统用户表';
-COMMENT ON TABLE sys_role IS '系统角色表';
-COMMENT ON TABLE user_role IS '用户角色关联表';
-COMMENT ON TABLE sys_permission IS '系统权限表';
-COMMENT ON TABLE sys_role_permission IS '角色权限关联表';
-COMMENT ON TABLE sys_menu IS '系统菜单表';
-COMMENT ON TABLE sys_dict_type IS '字典类型表';
-COMMENT ON TABLE sys_dict_data IS '字典数据表';
-COMMENT ON TABLE sys_dictionary IS '通用字典表';
-COMMENT ON TABLE sys_config IS '系统配置表';
-COMMENT ON TABLE sys_login_log IS '登录日志表';
-COMMENT ON TABLE sys_exception_log IS '异常日志表';
-COMMENT ON TABLE operation_log IS '操作日志表';
-COMMENT ON TABLE audit_log IS '审计日志表';
-COMMENT ON TABLE audit_log_archive IS '审计日志归档表';
-COMMENT ON TABLE sys_notice IS '系统公告表';
-COMMENT ON TABLE sys_user_message IS '用户消息表';
-COMMENT ON TABLE sys_file IS '文件管理表';
-COMMENT ON TABLE oauth2_client IS 'OAuth2客户端表';
-
-COMMENT ON TABLE sys_exception_log IS '异常日志表';
-COMMENT ON COLUMN sys_exception_log.id IS '主键ID';
-COMMENT ON COLUMN sys_exception_log.username IS '操作用户';
-COMMENT ON COLUMN sys_exception_log.title IS '异常标题';
-COMMENT ON COLUMN sys_exception_log.exception_name IS '异常名称';
-COMMENT ON COLUMN sys_exception_log.method_name IS '方法名称';
-COMMENT ON COLUMN sys_exception_log.method_params IS '方法参数';
-COMMENT ON COLUMN sys_exception_log.exception_msg IS '异常消息';
-COMMENT ON COLUMN sys_exception_log.exception_stack IS '异常堆栈';
-COMMENT ON COLUMN sys_exception_log.ip IS 'IP地址';
-COMMENT ON COLUMN sys_exception_log.create_time IS '创建时间';
-
-COMMENT ON TABLE audit_log IS '审计日志表';
-COMMENT ON COLUMN audit_log.id IS '主键ID';
-COMMENT ON COLUMN audit_log.entity_type IS '实体类型(如User, Role等)';
-COMMENT ON COLUMN audit_log.entity_id IS '实体ID';
-COMMENT ON COLUMN audit_log.operation_type IS '操作类型(CREATE, UPDATE, DELETE)';
-COMMENT ON COLUMN audit_log.operator IS '操作人';
-COMMENT ON COLUMN audit_log.operation_time IS '操作时间';
-COMMENT ON COLUMN audit_log.before_data IS '变更前数据(JSON格式)';
-COMMENT ON COLUMN audit_log.after_data IS '变更后数据(JSON格式)';
-COMMENT ON COLUMN audit_log.changed_fields IS '变更字段列表';
-COMMENT ON COLUMN audit_log.ip_address IS 'IP地址';
-COMMENT ON COLUMN audit_log.description IS '操作描述';
-COMMENT ON COLUMN audit_log.created_at IS '记录创建时间';
-
-COMMENT ON TABLE audit_log_archive IS '审计日志归档表';
-COMMENT ON COLUMN audit_log_archive.id IS '主键ID';
-COMMENT ON COLUMN audit_log_archive.entity_type IS '实体类型(如User, Role等)';
-COMMENT ON COLUMN audit_log_archive.entity_id IS '实体ID';
-COMMENT ON COLUMN audit_log_archive.operation_type IS '操作类型(CREATE, UPDATE, DELETE)';
-COMMENT ON COLUMN audit_log_archive.operator IS '操作人';
-COMMENT ON COLUMN audit_log_archive.operation_time IS '操作时间';
-COMMENT ON COLUMN audit_log_archive.before_data IS '变更前数据(JSON格式)';
-COMMENT ON COLUMN audit_log_archive.after_data IS '变更后数据(JSON格式)';
-COMMENT ON COLUMN audit_log_archive.changed_fields IS '变更字段列表';
-COMMENT ON COLUMN audit_log_archive.ip_address IS 'IP地址';
-COMMENT ON COLUMN audit_log_archive.user_agent IS '用户代理';
-COMMENT ON COLUMN audit_log_archive.description IS '操作描述';
-COMMENT ON COLUMN audit_log_archive.created_at IS '记录创建时间';
-COMMENT ON COLUMN audit_log_archive.archived_at IS '归档时间';
diff --git a/gym-manage-api/gym-auth/pom.xml b/gym-manage-api/gym-auth/pom.xml
index 42aa5be..23e8943 100644
--- a/gym-manage-api/gym-auth/pom.xml
+++ b/gym-manage-api/gym-auth/pom.xml
@@ -10,11 +10,12 @@
1.0.0
+ cn.novalon.gym.manage
gym-auth
jar
Gym Auth
- Phone Authentication Module - Phone Number Login Services
+ Authentication module for Gym Management System
@@ -29,93 +30,46 @@
cn.novalon.gym.manage
- manage-sys
+ gym-member
${project.version}
cn.novalon.gym.manage
- gym-member
+ manage-sys
${project.version}
+
org.springframework.boot
spring-boot-starter-webflux
-
- 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
-
-
- io.projectreactor
- reactor-test
- test
-
-
- cn.hutool
- hutool-all
- 5.8.25
-
-
-
- com.aliyun
- aliyun-java-sdk-dypnsapi
- 1.2.12
+ spring-boot-starter-validation
+
com.aliyun
aliyun-java-sdk-core
- 4.6.0
+ 4.6.3
+
- com.aliyun
- dysmsapi20170525
- 2.0.0
+ org.projectlombok
+ lombok
+ true
-
- 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.projectlombok
- lombok
- ${lombok.version}
-
-
+ ${java.version}
+ ${java.version}
diff --git a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/config/AuthConfig.java b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/config/AuthConfig.java
deleted file mode 100644
index d8d842d..0000000
--- a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/config/AuthConfig.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package cn.novalon.gym.manage.auth.config;
-
-import org.springframework.context.annotation.Configuration;
-
-@Configuration
-public class AuthConfig {
-}
\ No newline at end of file
diff --git a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/config/SmsProperties.java b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/config/SmsProperties.java
index fe9e35c..31551f3 100644
--- a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/config/SmsProperties.java
+++ b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/config/SmsProperties.java
@@ -4,44 +4,20 @@ import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
-/**
- * 阿里云短信配置属性类
- *
- * @author auto-generated
- * @date 2026-06-20
- */
@Data
@Configuration
@ConfigurationProperties(prefix = "alibaba.cloud.sms")
public class SmsProperties {
- /**
- * 访问密钥ID
- */
private String accessKeyId;
- /**
- * 访问密钥密钥
- */
private String accessKeySecret;
- /**
- * 短信签名名称
- */
private String signName;
- /**
- * 短信模板CODE
- */
private String templateCode;
- /**
- * 短信验证码长度(默认6位)
- */
- private int codeLength = 6;
+ private int codeLength = 4;
- /**
- * 短信验证码有效期(秒,默认300秒=5分钟)
- */
private long codeExpireSeconds = 300;
-}
+}
\ No newline at end of file
diff --git a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/dto/PhoneCodeLoginDto.java b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/dto/PhoneCodeLoginDto.java
index 4d21283..033da34 100644
--- a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/dto/PhoneCodeLoginDto.java
+++ b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/dto/PhoneCodeLoginDto.java
@@ -7,9 +7,6 @@ import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
-/**
- * 手机号验证码登录请求DTO
- */
@Data
@Builder
@NoArgsConstructor
diff --git a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/dto/PhoneLoginDto.java b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/dto/PhoneLoginDto.java
index 1666761..2cc0743 100644
--- a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/dto/PhoneLoginDto.java
+++ b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/dto/PhoneLoginDto.java
@@ -7,10 +7,6 @@ import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
-/**
- * 手机号一键登录请求DTO
- * uniapp官方一键登录流程:前端通过云函数获取手机号,直接传给后端
- */
@Data
@Builder
@NoArgsConstructor
diff --git a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/dto/SendCodeRequest.java b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/dto/SendCodeRequest.java
deleted file mode 100644
index c16602d..0000000
--- a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/dto/SendCodeRequest.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package cn.novalon.gym.manage.auth.dto;
-
-import jakarta.validation.constraints.NotBlank;
-import jakarta.validation.constraints.Pattern;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-/**
- * 发送短信验证码请求DTO
- */
-@Data
-@Builder
-@NoArgsConstructor
-@AllArgsConstructor
-public class SendCodeRequest {
-
- @NotBlank(message = "手机号不能为空")
- @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
- private String phone;
-}
diff --git a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/dto/SmsLoginDto.java b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/dto/SmsLoginDto.java
deleted file mode 100644
index d97cff5..0000000
--- a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/dto/SmsLoginDto.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package cn.novalon.gym.manage.auth.dto;
-
-import jakarta.validation.constraints.NotBlank;
-import jakarta.validation.constraints.Pattern;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-/**
- * 短信验证码登录请求DTO
- */
-@Data
-@Builder
-@NoArgsConstructor
-@AllArgsConstructor
-public class SmsLoginDto {
-
- @NotBlank(message = "手机号不能为空")
- @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
- private String phone;
-
- @NotBlank(message = "验证码不能为空")
- private String code;
-}
diff --git a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/service/PhoneAuthService.java b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/service/PhoneAuthService.java
index 2407a9b..cd2848e 100644
--- a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/service/PhoneAuthService.java
+++ b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/service/PhoneAuthService.java
@@ -1,37 +1,15 @@
package cn.novalon.gym.manage.auth.service;
-import cn.novalon.gym.manage.auth.dto.PhoneLoginDto;
import cn.novalon.gym.manage.auth.dto.PhoneCodeLoginDto;
+import cn.novalon.gym.manage.auth.dto.PhoneLoginDto;
import cn.novalon.gym.manage.auth.vo.PhoneLoginVO;
import reactor.core.publisher.Mono;
-/**
- * 手机号认证服务接口
- */
public interface PhoneAuthService {
- /**
- * 手机号一键登录(uniapp官方运营商认证)
- * 已注册则直接登录,未注册则自动注册后登录
- *
- * @param request 登录请求
- * @return 登录响应
- */
Mono oneClickLogin(PhoneLoginDto request);
- /**
- * 发送短信验证码(阿里云)
- *
- * @param phone 手机号
- * @return 是否发送成功
- */
Mono sendSmsCode(String phone);
- /**
- * 手机号验证码登录(阿里云)
- *
- * @param request 登录请求
- * @return 登录响应
- */
Mono codeLogin(PhoneCodeLoginDto request);
}
\ No newline at end of file
diff --git a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/service/SmsService.java b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/service/SmsService.java
index 914f4ff..585074e 100644
--- a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/service/SmsService.java
+++ b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/service/SmsService.java
@@ -1,35 +1,12 @@
package cn.novalon.gym.manage.auth.service;
-/**
- * 短信服务接口
- *
- * @author auto-generated
- * @date 2026-06-20
- */
+import reactor.core.publisher.Mono;
+
public interface SmsService {
- /**
- * 发送短信验证码
- *
- * @param phone 手机号
- * @return 发送结果
- */
- boolean sendVerificationCode(String phone);
+ Mono sendVerificationCode(String phone);
- /**
- * 验证短信验证码
- *
- * @param phone 手机号
- * @param code 验证码
- * @return 验证结果
- */
- boolean verifyCode(String phone, String code);
+ Mono verifyCode(String phone, String code);
- /**
- * 获取验证码(用于测试或特殊场景)
- *
- * @param phone 手机号
- * @return 验证码
- */
- String getVerificationCode(String phone);
-}
+ Mono getVerificationCode(String phone);
+}
\ No newline at end of file
diff --git a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/service/impl/PhoneAuthServiceImpl.java b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/service/impl/PhoneAuthServiceImpl.java
index 2ad6588..fe96c7f 100644
--- a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/service/impl/PhoneAuthServiceImpl.java
+++ b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/service/impl/PhoneAuthServiceImpl.java
@@ -63,14 +63,14 @@ public class PhoneAuthServiceImpl implements PhoneAuthService {
@Override
public Mono sendSmsCode(String phone) {
log.info("发送短信验证码, phone: {}", phone);
- return Mono.fromCallable(() -> smsService.sendVerificationCode(phone));
+ return smsService.sendVerificationCode(phone);
}
@Override
public Mono codeLogin(PhoneCodeLoginDto request) {
log.info("手机号验证码登录, phone: {}", request.getPhone());
- return Mono.fromCallable(() -> smsService.verifyCode(request.getPhone(), request.getCode()))
+ return smsService.verifyCode(request.getPhone(), request.getCode())
.flatMap(verified -> {
if (!verified) {
log.warn("验证码验证失败, phone: {}", request.getPhone());
diff --git a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/service/impl/SmsServiceImpl.java b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/service/impl/SmsServiceImpl.java
index 2162e13..2a8b630 100644
--- a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/service/impl/SmsServiceImpl.java
+++ b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/service/impl/SmsServiceImpl.java
@@ -1,148 +1,198 @@
package cn.novalon.gym.manage.auth.service.impl;
-import cn.hutool.core.util.RandomUtil;
import cn.novalon.gym.manage.auth.config.SmsProperties;
import cn.novalon.gym.manage.auth.service.SmsService;
import cn.novalon.gym.manage.common.constant.RedisKeyConstants;
import cn.novalon.gym.manage.common.util.RedisUtil;
-import com.aliyun.dysmsapi20170525.Client;
-import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
-import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
-import com.aliyun.teaopenapi.models.Config;
+import com.aliyuncs.CommonRequest;
+import com.aliyuncs.CommonResponse;
+import com.aliyuncs.DefaultAcsClient;
+import com.aliyuncs.IAcsClient;
+import com.aliyuncs.exceptions.ClientException;
+import com.aliyuncs.http.MethodType;
+import com.aliyuncs.profile.DefaultProfile;
+import com.aliyuncs.profile.IClientProfile;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
-import java.time.Duration;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
-/**
- * 短信服务实现类
- *
- * @author auto-generated
- * @date 2026-06-20
- */
@Slf4j
@Service
@RequiredArgsConstructor
public class SmsServiceImpl implements SmsService {
+ private static final long SEND_INTERVAL_SECONDS = 60;
+ private static final long CODE_EXPIRE_SECONDS = 300;
+
private final SmsProperties smsProperties;
private final RedisUtil redisUtil;
+ private final ObjectMapper objectMapper;
@Override
- public boolean sendVerificationCode(String phone) {
+ public Mono sendVerificationCode(String phone) {
log.info("发送短信验证码, phone: {}", phone);
- try {
- // 生成验证码
- String code = generateCode();
+ String rateLimitKey = RedisKeyConstants.SMS_CODE + phone + ":rate_limit";
- // 发送短信
- boolean sent = sendSms(phone, code);
+ return redisUtil.get(rateLimitKey, Long.class)
+ .defaultIfEmpty(0L)
+ .flatMap(lastSendTime -> {
+ long currentTime = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC);
- if (sent) {
- // 将验证码存入Redis,5分钟过期
- String cacheKey = RedisKeyConstants.SMS_CODE + phone;
- redisUtil.setWithExpire(cacheKey, code, smsProperties.getCodeExpireSeconds())
- .doOnSuccess(result -> log.info("验证码已缓存, phone: {}, code: {}, expire: {}s",
- phone, code, smsProperties.getCodeExpireSeconds()))
- .block();
+ if (currentTime - lastSendTime < SEND_INTERVAL_SECONDS) {
+ long remainingSeconds = SEND_INTERVAL_SECONDS - (currentTime - lastSendTime);
+ log.warn("发送频率限制, phone: {}, 剩余时间: {}秒", phone, remainingSeconds);
+ return Mono.just(false);
+ }
- log.info("短信验证码发送成功, phone: {}", phone);
- return true;
- }
+ return Mono.fromCallable(() -> {
+ try {
+ IAcsClient client = createClient();
- log.warn("短信验证码发送失败, phone: {}", phone);
- return false;
+ CommonRequest request = new CommonRequest();
+ request.setSysMethod(MethodType.POST);
+ request.setSysDomain("dypnsapi.aliyuncs.com");
+ request.setSysVersion("2017-05-25");
+ request.setSysAction("SendSmsVerifyCode");
+ request.putQueryParameter("PhoneNumber", phone);
+ request.putQueryParameter("SignName", smsProperties.getSignName());
+ request.putQueryParameter("TemplateCode", smsProperties.getTemplateCode());
+ request.putQueryParameter("TemplateParam", "{\"code\":\"##code##\",\"min\":\"5\"}");
+ request.putQueryParameter("ReturnVerifyCode", "true");
- } catch (Exception e) {
- log.error("发送短信验证码异常, phone: {}", phone, e);
- return false;
- }
+ log.info("阿里云号码认证请求参数 - signName: {}, templateCode: {}, templateParam: {}",
+ smsProperties.getSignName(),
+ smsProperties.getTemplateCode(),
+ "{\"code\":\"##code##\",\"min\":\"5\"}");
+
+ CommonResponse response = client.getCommonResponse(request);
+ String responseData = response.getData();
+ log.info("阿里云号码认证原始响应: {}", responseData);
+
+ JsonNode jsonNode = objectMapper.readTree(responseData);
+
+ JsonNode codeNode = jsonNode.get("Code");
+ if (codeNode == null) {
+ log.error("阿里云响应中找不到Code字段, 原始响应: {}", responseData);
+ return false;
+ }
+
+ String code = codeNode.asText();
+
+ if ("OK".equals(code)) {
+ JsonNode requestIdNode = jsonNode.get("RequestId");
+ String requestId = requestIdNode != null ? requestIdNode.asText() : "unknown";
+ log.info("短信验证码发送成功, phone: {}, requestId: {}", phone, requestId);
+
+ // 提取验证码并存入Redis
+ JsonNode modelNode = jsonNode.get("Model");
+ if (modelNode != null) {
+ JsonNode verifyCodeNode = modelNode.get("VerifyCode");
+ if (verifyCodeNode != null) {
+ String verifyCode = verifyCodeNode.asText();
+ String smsCodeKey = RedisKeyConstants.SMS_CODE + phone;
+ redisUtil.setWithExpire(smsCodeKey, verifyCode, CODE_EXPIRE_SECONDS).subscribe();
+ log.info("验证码已存入Redis, phone: {}, key: {}, expire: {}秒", phone, smsCodeKey, CODE_EXPIRE_SECONDS);
+ } else {
+ log.warn("响应中未找到Model.VerifyCode字段, 原始响应: {}", responseData);
+ }
+ } else {
+ log.warn("响应中未找到Model字段, 原始响应: {}", responseData);
+ }
+
+ redisUtil.setWithExpire(rateLimitKey, currentTime, SEND_INTERVAL_SECONDS).subscribe();
+ return true;
+ }
+
+ JsonNode messageNode = jsonNode.get("Message");
+ String message = messageNode != null ? messageNode.asText() : "unknown";
+ log.error("短信验证码发送失败, phone: {}, code: {}, message: {}", phone, code, message);
+ return false;
+
+ } catch (Exception e) {
+ log.error("发送短信验证码异常, phone: {}, 异常信息: {}", phone, e.getMessage(), e);
+ return false;
+ }
+ });
+ });
}
@Override
- public boolean verifyCode(String phone, String code) {
+ public Mono verifyCode(String phone, String code) {
log.info("验证短信验证码, phone: {}", phone);
- try {
- String cacheKey = RedisKeyConstants.SMS_CODE + phone;
- String cachedCode = redisUtil.get(cacheKey, String.class).block();
+ return Mono.fromCallable(() -> {
+ try {
+ IAcsClient client = createClient();
- if (cachedCode == null) {
- log.warn("验证码已过期或不存在, phone: {}", phone);
+ CommonRequest request = new CommonRequest();
+ request.setSysMethod(MethodType.POST);
+ request.setSysDomain("dypnsapi.aliyuncs.com");
+ request.setSysVersion("2017-05-25");
+ request.setSysAction("CheckSmsVerifyCode");
+ request.putQueryParameter("PhoneNumber", phone);
+ request.putQueryParameter("VerifyCode", code);
+
+ log.info("阿里云号码认证核验参数 - phone: {}, code: {}", phone, code);
+
+ CommonResponse response = client.getCommonResponse(request);
+ String responseData = response.getData();
+ log.info("阿里云号码认证核验原始响应: {}", responseData);
+
+ JsonNode jsonNode = objectMapper.readTree(responseData);
+
+ JsonNode codeNode = jsonNode.get("Code");
+ if (codeNode == null) {
+ log.error("阿里云核验响应中找不到Code字段, 原始响应: {}", responseData);
+ return false;
+ }
+
+ String responseCode = codeNode.asText();
+ JsonNode modelNode = jsonNode.get("Model");
+ JsonNode verifyResultNode = modelNode != null ? modelNode.get("VerifyResult") : null;
+ boolean verifyResult = verifyResultNode != null && "PASS".equals(verifyResultNode.asText());
+
+ if ("OK".equals(responseCode) && verifyResult) {
+ log.info("验证码验证成功, phone: {}", phone);
+ return true;
+ }
+
+ JsonNode messageNode = jsonNode.get("Message");
+ String message = messageNode != null ? messageNode.asText() : "unknown";
+ log.warn("验证码验证失败, phone: {}, code: {}, message: {}, result: {}",
+ phone, responseCode, message, verifyResult);
+ return false;
+
+ } catch (Exception e) {
+ log.error("验证短信验证码异常, phone: {}, 异常信息: {}", phone, e.getMessage(), e);
return false;
}
-
- if (cachedCode.equals(code)) {
- // 验证成功后删除验证码
- redisUtil.delete(cacheKey).block();
- log.info("验证码验证成功, phone: {}", phone);
- return true;
- }
-
- log.warn("验证码不匹配, phone: {}, input: {}, cached: {}", phone, code, cachedCode);
- return false;
-
- } catch (Exception e) {
- log.error("验证短信验证码异常, phone: {}", phone, e);
- return false;
- }
+ });
}
@Override
- public String getVerificationCode(String phone) {
+ public Mono getVerificationCode(String phone) {
try {
String cacheKey = RedisKeyConstants.SMS_CODE + phone;
- return redisUtil.get(cacheKey, String.class).block();
+ return redisUtil.get(cacheKey, String.class);
} catch (Exception e) {
log.error("获取验证码异常, phone: {}", phone, e);
- return null;
+ return Mono.empty();
}
}
- /**
- * 生成验证码
- */
- private String generateCode() {
- return RandomUtil.randomNumbers(smsProperties.getCodeLength());
+ private IAcsClient createClient() throws ClientException {
+ IClientProfile profile = DefaultProfile.getProfile(
+ "cn-hangzhou",
+ smsProperties.getAccessKeyId(),
+ smsProperties.getAccessKeySecret());
+ DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", "Dypnsapi", "dypnsapi.aliyuncs.com");
+ return new DefaultAcsClient(profile);
}
-
- /**
- * 发送短信
- */
- private boolean sendSms(String phone, String code) throws Exception {
- log.info("调用阿里云短信API发送短信, phone: {}, code: {}", phone, code);
-
- // 创建阿里云短信客户端
- Config config = new Config()
- .setAccessKeyId(smsProperties.getAccessKeyId())
- .setAccessKeySecret(smsProperties.getAccessKeySecret())
- .setEndpoint("dysmsapi.aliyuncs.com");
-
- Client client = new Client(config);
-
- // 构建发送短信请求
- SendSmsRequest request = new SendSmsRequest()
- .setPhoneNumbers(phone)
- .setSignName(smsProperties.getSignName())
- .setTemplateCode(smsProperties.getTemplateCode())
- .setTemplateParam("{\"code\":\"" + code + "\"}");
-
- // 发送短信
- SendSmsResponse response = client.sendSms(request);
-
- log.info("阿里云短信API响应, phone: {}, code: {}, message: {}",
- phone, response.getBody().getCode(), response.getBody().getMessage());
-
- // 判断发送结果
- if ("OK".equals(response.getBody().getCode())) {
- return true;
- }
-
- log.error("阿里云短信发送失败, phone: {}, code: {}, message: {}",
- phone, response.getBody().getCode(), response.getBody().getMessage());
- return false;
- }
-}
+}
\ No newline at end of file
diff --git a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/vo/PhoneLoginVO.java b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/vo/PhoneLoginVO.java
index f5f4651..795c103 100644
--- a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/vo/PhoneLoginVO.java
+++ b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/vo/PhoneLoginVO.java
@@ -5,9 +5,6 @@ import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
-/**
- * 手机号一键登录响应VO
- */
@Data
@Builder
@NoArgsConstructor
diff --git a/gym-manage-api/gym-auth/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/gym-manage-api/gym-auth/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
index 216a5f9..beb4996 100644
--- a/gym-manage-api/gym-auth/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
+++ b/gym-manage-api/gym-auth/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -1,5 +1 @@
-cn.novalon.gym.manage.auth.handler.PhoneAuthHandler
-cn.novalon.gym.manage.auth.service.impl.PhoneAuthServiceImpl
-cn.novalon.gym.manage.auth.service.impl.SmsServiceImpl
-cn.novalon.gym.manage.auth.config.AuthConfig
cn.novalon.gym.manage.auth.config.SmsProperties
\ No newline at end of file
diff --git a/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/config/WebSocketConfig.java b/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/config/WebSocketConfig.java
new file mode 100644
index 0000000..e4d6ea1
--- /dev/null
+++ b/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/config/WebSocketConfig.java
@@ -0,0 +1,43 @@
+package cn.novalon.gym.manage.checkIn.config;
+
+import cn.novalon.gym.manage.checkIn.websocket.MyWebSocketHandler;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.reactive.HandlerMapping;
+import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping;
+import org.springframework.web.reactive.socket.WebSocketHandler;
+import org.springframework.web.reactive.socket.server.support.WebSocketHandlerAdapter;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Configuration
+public class WebSocketConfig {
+
+ @Autowired
+ private MyWebSocketHandler myWebSocketHandler;
+
+ /**
+ * 注册 WebSocket 路由映射
+ * 路径对应前端连接的 ws://xxx/webSocket/checkIn
+ */
+ @Bean
+ public HandlerMapping webSocketMapping() {
+ Map map = new HashMap<>();
+ map.put("/webSocket/checkIn", myWebSocketHandler);
+
+ SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
+ mapping.setUrlMap(map);
+ mapping.setOrder(10); // 设置优先级
+ return mapping;
+ }
+
+ /**
+ * 注册 WebSocket 处理器适配器(必须)
+ */
+ @Bean
+ public WebSocketHandlerAdapter handlerAdapter() {
+ return new WebSocketHandlerAdapter();
+ }
+}
\ No newline at end of file
diff --git a/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/entity/SignInRecord.java b/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/entity/SignInRecord.java
index 4946012..63cf034 100644
--- a/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/entity/SignInRecord.java
+++ b/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/entity/SignInRecord.java
@@ -206,4 +206,4 @@ public class SignInRecord {
public void restore() {
this.isDelete = false;
}
-}
\ No newline at end of file
+}
diff --git a/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/handler/CheckInHandler.java b/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/handler/CheckInHandler.java
index ceae33f..16716f9 100644
--- a/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/handler/CheckInHandler.java
+++ b/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/handler/CheckInHandler.java
@@ -2,6 +2,8 @@ package cn.novalon.gym.manage.checkIn.handler;
import cn.novalon.gym.manage.checkIn.service.impl.CheckServiceImpl;
import cn.novalon.gym.manage.checkIn.websocket.MyWebSocketHandler;
+import cn.novalon.gym.manage.checkIn.vo.SignInRecordVO;
+import cn.novalon.gym.manage.checkIn.vo.SignInStatsVO;
import cn.novalon.gym.manage.sys.util.AuthUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -12,6 +14,7 @@ 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 cn.novalon.gym.manage.checkIn.websocket.MyWebSocketHandler;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
@@ -36,6 +39,7 @@ public class CheckInHandler {
public Mono checkIn(ServerRequest request) {
Long memberId = authUtil.getMemberIdOrThrow(request);
+
return request.bodyToMono(Map.class)
.flatMap(body -> {
String qrContent = (String) body.get("qrContent");
@@ -190,4 +194,4 @@ public class CheckInHandler {
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(Map.of("code", 200, "message", "success", "data", stats)));
}
-}
\ No newline at end of file
+}
diff --git a/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/service/ICheckInService.java b/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/service/ICheckInService.java
index 5b4894f..e162423 100644
--- a/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/service/ICheckInService.java
+++ b/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/service/ICheckInService.java
@@ -78,4 +78,4 @@ public interface ICheckInService {
* @return 签到统计VO
*/
Mono getDailySignInStats(LocalDate date);
-}
\ No newline at end of file
+}
diff --git a/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/service/impl/CheckServiceImpl.java b/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/service/impl/CheckServiceImpl.java
index ad41210..e041842 100644
--- a/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/service/impl/CheckServiceImpl.java
+++ b/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/service/impl/CheckServiceImpl.java
@@ -2,6 +2,8 @@ package cn.novalon.gym.manage.checkIn.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.json.JSONUtil;
+import cn.hutool.extra.qrcode.QrCodeUtil;
+import cn.hutool.extra.qrcode.QrConfig;
import cn.novalon.gym.manage.checkIn.config.QRCodeConfig;
import cn.novalon.gym.manage.checkIn.constant.QRRedisKey;
import cn.novalon.gym.manage.checkIn.entity.SignInRecord;
@@ -19,8 +21,6 @@ import cn.novalon.gym.manage.member.entity.MemberCardRecord;
import cn.novalon.gym.manage.member.enums.MemberCardType;
import cn.novalon.gym.manage.member.repository.MemberCardRecordRepository;
import cn.novalon.gym.manage.member.repository.MemberCardRepository;
-import cn.hutool.extra.qrcode.QrCodeUtil;
-import cn.hutool.extra.qrcode.QrConfig;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
diff --git a/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/websocket/MyWebSocketHandler.java b/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/websocket/MyWebSocketHandler.java
index 6b94625..f3bd900 100644
--- a/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/websocket/MyWebSocketHandler.java
+++ b/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/websocket/MyWebSocketHandler.java
@@ -199,4 +199,4 @@ public class MyWebSocketHandler implements WebSocketHandler {
cleanupTimeoutConnections();
return qrContentToSink.size();
}
-}
\ No newline at end of file
+}
diff --git a/gym-manage-api/gym-groupCourse/pom.xml b/gym-manage-api/gym-groupCourse/pom.xml
index 2ef9fd2..bfafbbb 100644
--- a/gym-manage-api/gym-groupCourse/pom.xml
+++ b/gym-manage-api/gym-groupCourse/pom.xml
@@ -35,6 +35,11 @@
manage-common
${project.version}
+
+ cn.novalon.gym.manage
+ manage-sys
+ ${project.version}
+
cn.novalon.gym.manage
manage-db
@@ -81,8 +86,8 @@
gym-member
${project.version}
-
-
+
+
com.google.zxing
core
@@ -93,14 +98,20 @@
javase
3.5.3
+
+
+
+ com.aliyun.oss
+ aliyun-sdk-oss
+ 3.17.1
+
- org.apache.maven.plugins
- maven-jar-plugin
- 3.4.2
+ org.springframework.boot
+ spring-boot-maven-plugin
diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/util/OSSUtil.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/util/OSSUtil.java
index c3cd21b..1318db2 100644
--- a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/util/OSSUtil.java
+++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/util/OSSUtil.java
@@ -1,8 +1,8 @@
package cn.novalon.gym.manage.groupcourse.util;
-//import com.aliyun.oss.OSS;
-//import com.aliyun.oss.OSSClientBuilder;
-//import com.aliyun.oss.model.PutObjectRequest;
+import com.aliyun.oss.OSS;
+import com.aliyun.oss.OSSClientBuilder;
+import com.aliyun.oss.model.PutObjectRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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
index 8983e20..5c96f8f 100644
--- 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
@@ -71,6 +71,22 @@ public class Member extends BaseEntity {
@Column("official_open_id")
private String officialOpenId;
+ // 阿里云号码认证OpenID
+ @Column("dypns_open_id")
+ private String dypnsOpenId;
+
+ // 身份证号码(AES加密存储)
+ @Column("id_card")
+ private String idCard;
+
+ // 真实姓名(AES加密存储)
+ @Column("real_name")
+ private String realName;
+
+ // 注册渠道:SMS-短信验证码,ONE_CLICK-一键登录,WECHAT-微信授权
+ @Column("register_channel")
+ private String registerChannel;
+
// 软删除
@Column("is_deleted")
private Boolean isDeleted;
diff --git a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/MemberCardServiceImpl.java b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/MemberCardServiceImpl.java
index 070a938..3d87017 100644
--- a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/MemberCardServiceImpl.java
+++ b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/MemberCardServiceImpl.java
@@ -1,5 +1,6 @@
package cn.novalon.gym.manage.member.service.impl;
+import cn.novalon.gym.manage.common.util.RedisUtil;
import cn.novalon.gym.manage.member.entity.MemberCard;
import cn.novalon.gym.manage.member.entity.MemberCardRecord;
import cn.novalon.gym.manage.member.entity.MemberCardTransaction;
@@ -15,7 +16,6 @@ import cn.novalon.gym.manage.member.repository.MemberCardRecordRepository;
import cn.novalon.gym.manage.member.repository.MemberCardRepository;
import cn.novalon.gym.manage.member.service.IMemberCardService;
import cn.novalon.gym.manage.member.service.IMemberCardTransactionService;
-import cn.novalon.gym.manage.common.util.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
diff --git a/gym-manage-api/gym-payment/pom.xml b/gym-manage-api/gym-payment/pom.xml
deleted file mode 100644
index b8d2fcd..0000000
--- a/gym-manage-api/gym-payment/pom.xml
+++ /dev/null
@@ -1,95 +0,0 @@
-
-
-
- 4.0.0
-
-
- cn.novalon.gym.manage
- gym-manage-api
- 1.0.0
-
-
- gym-payment
- jar
-
- Gym Payment
- Payment Module - Integrates Huifu Payment Gateway
-
-
-
- cn.novalon.gym.manage
- manage-common
- ${project.version}
-
-
- org.springframework.boot
- spring-boot-starter-webflux
-
-
- org.springdoc
- springdoc-openapi-starter-webflux-ui
-
-
- org.springframework.boot
- spring-boot-starter-test
- test
-
-
- io.projectreactor
- reactor-test
- test
-
-
- cn.hutool
- hutool-all
- 5.8.25
-
-
- com.squareup.okhttp3
- okhttp
- 4.12.0
-
-
- org.bouncycastle
- bcprov-jdk18on
- 1.78.1
-
-
- org.bouncycastle
- bcpkix-jdk18on
- 1.78.1
-
-
- org.springframework.boot
- spring-boot-starter-validation
-
-
-
-
-
-
- org.apache.maven.plugins
- maven-jar-plugin
- 3.4.2
-
-
- org.apache.maven.plugins
- maven-compiler-plugin
- 3.11.0
-
- 21
- 21
-
-
- org.projectlombok
- lombok
- ${lombok.version}
-
-
-
-
-
-
-
diff --git a/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/config/CorsConfig.java b/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/config/CorsConfig.java
deleted file mode 100644
index 5669d26..0000000
--- a/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/config/CorsConfig.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package cn.novalon.gym.manage.payment.config;
-
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.web.cors.CorsConfiguration;
-import org.springframework.web.cors.reactive.CorsWebFilter;
-import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
-
-@Configuration
-public class CorsConfig {
- @Bean
- public CorsWebFilter corsWebFilter() {
- CorsConfiguration config = new CorsConfiguration();
- config.addAllowedOrigin("*");
- config.addAllowedMethod("*");
- config.addAllowedHeader("*");
- config.setAllowCredentials(false);
-
- UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
- source.registerCorsConfiguration("/**", config);
- return new CorsWebFilter(source);
- }
-}
\ No newline at end of file
diff --git a/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/config/HuifuPayConfig.java b/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/config/HuifuPayConfig.java
deleted file mode 100644
index c9596cc..0000000
--- a/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/config/HuifuPayConfig.java
+++ /dev/null
@@ -1,34 +0,0 @@
-
-package cn.novalon.gym.manage.payment.config;
-
-import lombok.Data;
-import org.springframework.boot.context.properties.ConfigurationProperties;
-import org.springframework.context.annotation.Configuration;
-
-@Data
-@Configuration
-@ConfigurationProperties(prefix = "payment.huifu")
-public class HuifuPayConfig {
-
- private String sysId;
-
- private String productId;
-
- private String huifuId;
-
- private String acctId;
-
- private String privateKey;
-
- private String publicKey;
-
- private String createUrl;
-
- private String queryUrl;
-
- private String refundUrl;
-
- private String notifyUrl;
-
- private String version = "1.0";
-}
diff --git a/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/dto/request/PaymentCreateRequest.java b/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/dto/request/PaymentCreateRequest.java
deleted file mode 100644
index b37b8a8..0000000
--- a/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/dto/request/PaymentCreateRequest.java
+++ /dev/null
@@ -1,58 +0,0 @@
-
-package cn.novalon.gym.manage.payment.dto.request;
-
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-import jakarta.validation.constraints.NotBlank;
-import jakarta.validation.constraints.NotNull;
-import jakarta.validation.constraints.Size;
-
-@Data
-@Builder
-@NoArgsConstructor
-@AllArgsConstructor
-public class PaymentCreateRequest {
-
- @NotNull(message = "会员ID不能为空")
- private Long memberId;
-
- @NotBlank(message = "订单类型不能为空")
- @Size(max = 50, message = "订单类型长度不能超过50")
- private String orderType;
-
- @NotBlank(message = "商品描述不能为空")
- @Size(max = 128, message = "商品描述长度不能超过128")
- private String goodsDesc;
-
- @NotBlank(message = "交易金额不能为空")
- private String transAmt;
-
- @NotBlank(message = "交易类型不能为空")
- @Size(max = 16, message = "交易类型长度不能超过16")
- private String tradeType;
-
- @Size(max = 255, message = "备注长度不能超过255")
- private String remark;
-
- @Size(max = 9, message = "账户号长度不能超过9")
- private String acctId;
-
- private String timeExpire;
-
- private String delayAcctFlag;
-
- private Integer feeFlag;
-
- @Size(max = 128, message = "禁用支付方式长度不能超过128")
- private String limitPayType;
-
- @Size(max = 32, message = "渠道号长度不能超过32")
- private String channelNo;
-
- private String payScene;
-
- private String notifyUrl;
-}
diff --git a/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/dto/response/PaymentCreateResponse.java b/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/dto/response/PaymentCreateResponse.java
deleted file mode 100644
index dc89b50..0000000
--- a/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/dto/response/PaymentCreateResponse.java
+++ /dev/null
@@ -1,28 +0,0 @@
-
-package cn.novalon.gym.manage.payment.dto.response;
-
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-@Data
-@Builder
-@NoArgsConstructor
-@AllArgsConstructor
-public class PaymentCreateResponse {
-
- private String orderId;
-
- private String tradeType;
-
- private String qrCode;
-
- private String payInfo;
-
- private String transAmt;
-
- private String payStatus;
-
- private String message;
-}
diff --git a/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/dto/response/PaymentQueryResponse.java b/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/dto/response/PaymentQueryResponse.java
deleted file mode 100644
index b7b0b71..0000000
--- a/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/dto/response/PaymentQueryResponse.java
+++ /dev/null
@@ -1,30 +0,0 @@
-
-package cn.novalon.gym.manage.payment.dto.response;
-
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-@Data
-@Builder
-@NoArgsConstructor
-@AllArgsConstructor
-public class PaymentQueryResponse {
-
- private String orderId;
-
- private String tradeType;
-
- private String transAmt;
-
- private String payStatus;
-
- private String outTransId;
-
- private String qrCode;
-
- private String payTime;
-
- private String message;
-}
diff --git a/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/dto/response/PaymentRefundResponse.java b/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/dto/response/PaymentRefundResponse.java
deleted file mode 100644
index 92b3c41..0000000
--- a/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/dto/response/PaymentRefundResponse.java
+++ /dev/null
@@ -1,22 +0,0 @@
-
-package cn.novalon.gym.manage.payment.dto.response;
-
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-@Data
-@Builder
-@NoArgsConstructor
-@AllArgsConstructor
-public class PaymentRefundResponse {
-
- private String orderId;
-
- private String refundAmt;
-
- private String refundStatus;
-
- private String message;
-}
diff --git a/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/handler/PaymentHandler.java b/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/handler/PaymentHandler.java
deleted file mode 100644
index eab9746..0000000
--- a/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/handler/PaymentHandler.java
+++ /dev/null
@@ -1,78 +0,0 @@
-
-package cn.novalon.gym.manage.payment.handler;
-
-import cn.novalon.gym.manage.payment.dto.request.PaymentCreateRequest;
-import cn.novalon.gym.manage.payment.service.IPaymentService;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import lombok.extern.slf4j.Slf4j;
-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.util.HashMap;
-import java.util.Map;
-
-@Slf4j
-@Component
-@Tag(name = "支付管理", description = "斗拱聚合支付相关操作")
-public class PaymentHandler {
-
- private final IPaymentService paymentService;
-
- public PaymentHandler(IPaymentService paymentService) {
- this.paymentService = paymentService;
- }
-
- @Operation(summary = "创建支付", description = "创建支付订单,支持微信、支付宝等多种支付方式")
- public Mono createPayment(ServerRequest request) {
- log.info("========== PaymentHandler.createPayment 被调用 ==========");
- return request.bodyToMono(PaymentCreateRequest.class)
- .flatMap(paymentService::createPayment)
- .flatMap(response -> ServerResponse.ok().bodyValue(response))
- .onErrorResume(e -> ServerResponse.badRequest().bodyValue(buildErrorResponse("创建支付失败: " + e.getMessage())));
- }
-
- @Operation(summary = "查询支付状态", description = "根据订单号查询支付状态")
- public Mono queryPayment(ServerRequest request) {
- String orderId = request.pathVariable("orderId");
- return paymentService.queryPayment(orderId)
- .flatMap(response -> ServerResponse.ok().bodyValue(response))
- .onErrorResume(e -> ServerResponse.badRequest().bodyValue(buildErrorResponse(e.getMessage())));
- }
-
- @Operation(summary = "退款", description = "根据订单号发起退款")
- public Mono refundPayment(ServerRequest request) {
- String orderId = request.pathVariable("orderId");
- return request.bodyToMono(Map.class)
- .flatMap(body -> {
- String refundAmt = String.valueOf(body.get("refundAmt"));
- return paymentService.refundPayment(orderId, refundAmt);
- })
- .flatMap(response -> ServerResponse.ok().bodyValue(response))
- .onErrorResume(e -> ServerResponse.badRequest().bodyValue(buildErrorResponse("退款失败: " + e.getMessage())));
- }
-
- @Operation(summary = "支付回调", description = "支付成功回调通知")
- public Mono handleNotify(ServerRequest request) {
- return request.bodyToMono(String.class)
- .flatMap(paymentService::handleNotify)
- .then(ServerResponse.ok().bodyValue(buildSuccessResponse("处理成功")))
- .onErrorResume(e -> ServerResponse.badRequest().bodyValue(buildErrorResponse("处理失败: " + e.getMessage())));
- }
-
- private Map buildErrorResponse(String message) {
- Map response = new HashMap<>();
- response.put("success", false);
- response.put("message", message);
- return response;
- }
-
- private Map buildSuccessResponse(String message) {
- Map response = new HashMap<>();
- response.put("success", true);
- response.put("message", message);
- return response;
- }
-}
diff --git a/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/service/IPaymentService.java b/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/service/IPaymentService.java
deleted file mode 100644
index d8f70a5..0000000
--- a/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/service/IPaymentService.java
+++ /dev/null
@@ -1,19 +0,0 @@
-
-package cn.novalon.gym.manage.payment.service;
-
-import cn.novalon.gym.manage.payment.dto.request.PaymentCreateRequest;
-import cn.novalon.gym.manage.payment.dto.response.PaymentCreateResponse;
-import cn.novalon.gym.manage.payment.dto.response.PaymentQueryResponse;
-import cn.novalon.gym.manage.payment.dto.response.PaymentRefundResponse;
-import reactor.core.publisher.Mono;
-
-public interface IPaymentService {
-
- Mono createPayment(PaymentCreateRequest request);
-
- Mono queryPayment(String orderId);
-
- Mono refundPayment(String orderId, String refundAmt);
-
- Mono handleNotify(String notifyBody);
-}
diff --git a/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/service/impl/PaymentServiceImpl.java b/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/service/impl/PaymentServiceImpl.java
deleted file mode 100644
index 8cbecce..0000000
--- a/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/service/impl/PaymentServiceImpl.java
+++ /dev/null
@@ -1,723 +0,0 @@
-package cn.novalon.gym.manage.payment.service.impl;
-
-import cn.hutool.json.JSONObject;
-import cn.hutool.json.JSONUtil;
-import cn.novalon.gym.manage.payment.dto.request.PaymentCreateRequest;
-import cn.novalon.gym.manage.payment.dto.response.PaymentCreateResponse;
-import cn.novalon.gym.manage.payment.dto.response.PaymentQueryResponse;
-import cn.novalon.gym.manage.payment.dto.response.PaymentRefundResponse;
-import cn.novalon.gym.manage.payment.service.IPaymentService;
-import lombok.extern.slf4j.Slf4j;
-import okhttp3.*;
-import org.springframework.stereotype.Service;
-import reactor.core.publisher.Mono;
-
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.security.KeyFactory;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.Signature;
-import java.security.spec.PKCS8EncodedKeySpec;
-import java.security.spec.X509EncodedKeySpec;
-import java.time.LocalDateTime;
-import java.time.format.DateTimeFormatter;
-import java.util.Base64;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.UUID;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.TimeUnit;
-
-@Slf4j
-@Service
-public class PaymentServiceImpl implements IPaymentService {
-
- private final OkHttpClient okHttpClient;
- private final Map paymentCache = new ConcurrentHashMap<>();
-
- private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
-
- // ==================== 配置 ====================
- private static final String SYS_ID = "6666000207573586"; // 代理商
- private static final String PRODUCT_ID = "XLSISV";
- private static final String HUIFU_ID = "6666000207581039"; // 商户号
- private static final String ACCT_ID = "F28308086";
- private static final String ALIPAY_CHANNEL = "hlm001";
- private static final String NOTIFY_URL = "http://localhost:8084/api/payment/notify";
-
- // ===== v4 接口 =====
- private static final String CREATE_URL = "https://api.huifu.com/v4/trade/payment/create";
- private static final String QUERY_URL = "https://api.huifu.com/v4/trade/payment/query";
- private static final String REFUND_URL = "https://api.huifu.com/v4/trade/payment/refund";
- private static final String CLOSE_URL = "https://api.huifu.com/v4/trade/payment/close";
-
- // ==================== 密钥 ====================
- // 商户私钥(用于签名请求)
- private static final String MERCHANT_PRIVATE_KEY =
- "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCcxAwrhZF+izz1gxQKOr4jnC05obIBHbl0DzHOcd4CaaDV7Kv1NRJKi33JhdDsW4JGgu16e5Rtzq1VzU5VWz5EKgGL46maOFwCkngJUTP/LC3JVf/wtJmYCm8xNE8C7GNNIqKzYEvUOEfXqagpVvVrGsQ9FpzFp2rP9hBmHY3yizVyPX/uT9S+af5TRxiaItj3SSJGgloaEMrnKOpb/EH7JwPSS0liAyT/NxPfOyyZHc22AvaAIOE5y+0PMUIKPuIdfpOrej3LVpO1Arc2hSgmdB+YIPSiBVYPXa6AuAmil9mpbtSikQJ7Uu7lX4JyTW4QxQ06rPFKnFWVKkzivAElAgMBAAECggEAJd0AJ37iTlMpDQ90xqe7hvRQxAu256gbQ9nrqLY97g0/KIw6WEZSPakFX6gvdvb/NzKmUyAIEKGLoh6tXdZk6qfOqc/6BeK47nIcBfwT9/zerjNUVvn34w4aHyNINieMMHQ+Id8PUZmqWH+Euz9ilVTosuyEPwUZulLvUQqwXzU5VnwVghURbUhDd+ecBJACWgemRun6d5241PQXNYAdH1k7cETd8GfIi3qclhhJrxi7tu5tq4YGCXQIoz7HCLim7GIvT0M+FRgSw2EOrHnAQNFeQ/vQbP71ttLoTxehL6Se9dfWrV5OI+Y/T7vR2F84Qt0iNbaxyJGir7siKDFIwQKBgQDXDzinx3/TasplM78pR/0CtuuKr1Ch02LOPrTosJ1qf1OohxQThowhOTxMsBlgYSKu9s1QRffUUXEqYXxd2B6lzDKfggwO6U2XxIcxWeNow0xoFfqcXYSg7Ga2sCr9uhdwxIdFQNF7SNBpT8ht4fJrRX6mWY1nHybpyTDQ4xoQNQKBgQC6m+yiOoi5JD3zVSSJq/iq5DJPA5B4aoP+t5u9lp2Q7iVO0QI5ilBlEKGE4VOU0glnXlDTfuqEYooMY85ekl4WGb3AOT0PhLL2i+gO2nlWBzf4HPzB/hibjfyPyniRM03cHkG3HXucL7Sne6FwERcfjEqjUd2cdP1l89PNrq4rMQKBgQCMmjABSWYh8/y1I5rEQ4OAJdVjC3GdC1Xa35ZpVybjvLEWSpHunhW5lvD8dllw8LC7UTI0XDpGPqTM/4VO2YBYB2PFc0Gs8g0/v0ZgFpOeJ6kpl80MM/wFNemFYTIKRoMSv/psZY9PmfBgGcBBTuquBXZjDcNr+yr2yAm5V/DvTQKBgHqRi94KoF8q5N39IKCkqhJlDH5FkxDktYoKw2rFkPzuzuZz9gghRyj6wXxsG9/2DWMt2dzw0czehFoa/CO188KEadPmRKr6uCmkP2nyKhxNZX+8WnB5G2Sg4DD6BjMpBYz8+qDx5ozx8LDJTYI0V4HLPgMD9JGdbgsXGhlREOkhAoGALr6IQOXnviWNAhCdc7rrsaMLMPbLZ1wqzWtQUG1JxDobbpzEP4CW/mvW5pMn58mSBg5qbXhyDI4fFP0CPb98QIz2tGnIzYyFzdKmF5Z1N7X1OF9O+tsSqASoBZzTqB4fr/o4mz0s9JCeriBR2LWjsbsDU13DTLsfQpWbtOnIy70=";
-
- // 汇付公钥(用于验证回调签名)
- private static final String HUIFU_PUBLIC_KEY =
- "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuqolFneAH66z3/3gaIYIaRZOIk/UYzdsXIm0RyawYBYAOu/NJ7ul8CRIrRlt5vd98HodW2yPrXA4+VHF3AS9UE4WTDpo9qV5brhqQSr/lAuZtEwMZwUWwgdnGFMkUFd9RvyGXAqY0bsQrcQgQ6zGjZHzlMljogDR3iblG0ak5ssD2TSC2W+1cxu+id+FP6onZXlXizuClTyIRh17m7CbS6rl0P3M96MlTdCzTeBw/Y54CiegBJI2wOrm2Qa6Dg6KRc+YkaJWjuRJVJkwjk8JhSyALno9oEzuDKaAXlsQlxeIhmAy4esRrZGrMV8SG0gwUZIP8lduPjQE95lCqqJ0gQIDAQAB";
-
- public PaymentServiceImpl() {
- this.okHttpClient = new OkHttpClient.Builder()
- .connectTimeout(30, TimeUnit.SECONDS)
- .readTimeout(30, TimeUnit.SECONDS)
- .writeTimeout(30, TimeUnit.SECONDS)
- .retryOnConnectionFailure(false)
- .build();
-
- // 验证私钥是否有效
- validatePrivateKey();
- }
-
- private void validatePrivateKey() {
- try {
- log.info("========== 验证私钥 ==========");
- String privateKeyBase64 = MERCHANT_PRIVATE_KEY.replaceAll("\\s", "");
- log.info("私钥长度(去空格后): {}", privateKeyBase64.length());
-
- byte[] privateKeyBytes = Base64.getDecoder().decode(privateKeyBase64);
- log.info("私钥解码后长度: {} bytes", privateKeyBytes.length);
-
- PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(privateKeyBytes);
- KeyFactory keyFactory = KeyFactory.getInstance("RSA");
- PrivateKey privateKey = keyFactory.generatePrivate(spec);
- log.info("私钥加载成功,算法: {}", privateKey.getAlgorithm());
-
- // 测试签名
- Signature signature = Signature.getInstance("SHA256withRSA");
- signature.initSign(privateKey);
- signature.update("test".getBytes(StandardCharsets.UTF_8));
- signature.sign();
- log.info("私钥签名测试成功!");
- log.info("========== 私钥验证完成 ==========");
- } catch (Exception e) {
- log.error("========== 私钥验证失败 ==========", e);
- }
- }
-
- // ==================== 1. 创建支付订单 ====================
- @Override
- public Mono createPayment(PaymentCreateRequest request) {
- log.info("========== createPayment 方法被调用 ==========");
- String orderId = UUID.randomUUID().toString().replace("-", "");
- String reqDate = LocalDateTime.now().format(DATE_FORMATTER);
- String reqSeqId = "RQ" + System.currentTimeMillis();
-
- // ===== 构建 data 参数 =====
- Map data = new HashMap<>();
- data.put("req_date", reqDate);
- data.put("req_seq_id", reqSeqId);
- data.put("huifu_id", HUIFU_ID);
- data.put("trade_type", "ALIPAY");
- data.put("pay_type", "APP"); // APP 支付,返回 alipay_scheme
- data.put("trans_amt", request.getTransAmt() != null ? request.getTransAmt() : "1");
- data.put("goods_desc", request.getGoodsDesc() != null ? request.getGoodsDesc() : "会员卡");
- data.put("acct_id", ACCT_ID);
- data.put("notify_url", NOTIFY_URL);
- data.put("remark", request.getRemark() != null ? request.getRemark() : "");
-
- // ===== 支付宝参数 =====
- data.put("alipay_channel", ALIPAY_CHANNEL);
-
- String dataJson = JSONUtil.toJsonStr(data);
- String sign = generateSign(dataJson);
-
- Map requestBody = new HashMap<>();
- requestBody.put("sys_id", SYS_ID);
- requestBody.put("product_id", PRODUCT_ID);
- requestBody.put("data", dataJson);
- requestBody.put("sign", sign);
-
- return Mono.fromCallable(() -> {
- String jsonBody = JSONUtil.toJsonStr(requestBody);
- log.info("========== 发起支付宝支付请求 (APP) ==========");
- log.info("请求URL: {}", CREATE_URL);
- log.info("请求Body: {}", jsonBody);
- log.info("==================================");
-
- Request httpRequest = new Request.Builder()
- .url(CREATE_URL)
- .post(RequestBody.create(jsonBody, MediaType.parse("application/json; charset=utf-8")))
- .addHeader("Content-Type", "application/json; charset=UTF-8")
- .addHeader("Accept", "application/json")
- .build();
-
- try (Response response = okHttpClient.newCall(httpRequest).execute()) {
- int httpCode = response.code();
- String responseBody = response.body() != null ? response.body().string() : "";
-
- log.info("========== 收到响应 ==========");
- log.info("HTTP状态码: {}", httpCode);
- log.info("响应Body: {}", responseBody);
- log.info("===============================");
-
- if (httpCode >= 400) {
- if (responseBody != null && !responseBody.isEmpty()) {
- try {
- JSONObject errorJson = JSONUtil.parseObj(responseBody);
- String errorMsg = errorJson.getStr("error_msg");
- String errorCode = errorJson.getStr("error_code");
- if (errorMsg != null) {
- throw new RuntimeException("支付失败: " + errorMsg + " (code: " + errorCode + ")");
- }
- } catch (Exception e) {
- // 忽略
- }
- }
- throw new RuntimeException("支付请求失败: HTTP " + httpCode);
- }
-
- if (responseBody == null || responseBody.isEmpty()) {
- throw new RuntimeException("响应体为空");
- }
-
- return parseCreateResponse(responseBody, orderId, request);
- } catch (IOException e) {
- log.error("网络请求异常", e);
- throw new RuntimeException("网络请求异常: " + e.getMessage(), e);
- }
- });
- }
-
- // ==================== 2. 查询支付状态 ====================
- @Override
- public Mono queryPayment(String orderId) {
- PaymentInfo paymentInfo = paymentCache.get(orderId);
- if (paymentInfo == null) {
- return Mono.error(new RuntimeException("支付记录不存在"));
- }
-
- String reqDate = LocalDateTime.now().format(DATE_FORMATTER);
- String reqSeqId = "RQ" + System.currentTimeMillis();
-
- Map data = new HashMap<>();
- data.put("req_date", reqDate);
- data.put("req_seq_id", reqSeqId);
- data.put("huifu_id", HUIFU_ID);
- data.put("out_trans_id", paymentInfo.outTransId);
-
- String dataJson = JSONUtil.toJsonStr(data);
- String sign = generateSign(dataJson);
-
- Map requestBody = new HashMap<>();
- requestBody.put("sys_id", SYS_ID);
- requestBody.put("product_id", PRODUCT_ID);
- requestBody.put("data", dataJson);
- requestBody.put("sign", sign);
-
- return Mono.fromCallable(() -> {
- String jsonBody = JSONUtil.toJsonStr(requestBody);
- log.info("查询支付状态, orderId={}", orderId);
-
- Request httpRequest = new Request.Builder()
- .url(QUERY_URL)
- .post(RequestBody.create(jsonBody, MediaType.parse("application/json; charset=utf-8")))
- .addHeader("Content-Type", "application/json; charset=UTF-8")
- .addHeader("Accept", "application/json")
- .build();
-
- try (Response response = okHttpClient.newCall(httpRequest).execute()) {
- int httpCode = response.code();
- String responseBody = response.body() != null ? response.body().string() : "";
-
- log.info("查询响应: code={}, body={}", httpCode, responseBody);
-
- if (httpCode >= 400) {
- throw new RuntimeException("查询失败: HTTP " + httpCode);
- }
-
- if (responseBody == null || responseBody.isEmpty()) {
- throw new RuntimeException("查询响应为空");
- }
-
- return parseQueryResponse(responseBody, orderId);
- }
- });
- }
-
- // ==================== 3. 申请退款 ====================
- @Override
- public Mono refundPayment(String orderId, String refundAmt) {
- PaymentInfo paymentInfo = paymentCache.get(orderId);
- if (paymentInfo == null) {
- return Mono.error(new RuntimeException("支付记录不存在"));
- }
-
- String reqDate = LocalDateTime.now().format(DATE_FORMATTER);
- String reqSeqId = "RQ" + System.currentTimeMillis();
-
- Map data = new HashMap<>();
- data.put("req_date", reqDate);
- data.put("req_seq_id", reqSeqId);
- data.put("huifu_id", HUIFU_ID);
- data.put("out_trans_id", paymentInfo.outTransId);
- data.put("trans_amt", refundAmt);
-
- String dataJson = JSONUtil.toJsonStr(data);
- String sign = generateSign(dataJson);
-
- Map requestBody = new HashMap<>();
- requestBody.put("sys_id", SYS_ID);
- requestBody.put("product_id", PRODUCT_ID);
- requestBody.put("data", dataJson);
- requestBody.put("sign", sign);
-
- return Mono.fromCallable(() -> {
- String jsonBody = JSONUtil.toJsonStr(requestBody);
- log.info("发起退款请求, orderId={}", orderId);
-
- Request httpRequest = new Request.Builder()
- .url(REFUND_URL)
- .post(RequestBody.create(jsonBody, MediaType.parse("application/json; charset=utf-8")))
- .addHeader("Content-Type", "application/json; charset=UTF-8")
- .addHeader("Accept", "application/json")
- .build();
-
- try (Response response = okHttpClient.newCall(httpRequest).execute()) {
- int httpCode = response.code();
- String responseBody = response.body() != null ? response.body().string() : "";
-
- log.info("退款响应: code={}, body={}", httpCode, responseBody);
-
- if (httpCode >= 400) {
- throw new RuntimeException("退款失败: HTTP " + httpCode);
- }
-
- return parseRefundResponse(responseBody, orderId, refundAmt);
- }
- });
- }
-
- // ==================== 4. 关闭订单 ====================
- public Mono closePayment(String orderId) {
- PaymentInfo paymentInfo = paymentCache.get(orderId);
- if (paymentInfo == null) {
- return Mono.error(new RuntimeException("支付记录不存在"));
- }
-
- String reqDate = LocalDateTime.now().format(DATE_FORMATTER);
- String reqSeqId = "RQ" + System.currentTimeMillis();
-
- Map data = new HashMap<>();
- data.put("req_date", reqDate);
- data.put("req_seq_id", reqSeqId);
- data.put("huifu_id", HUIFU_ID);
- data.put("out_trans_id", paymentInfo.outTransId);
-
- String dataJson = JSONUtil.toJsonStr(data);
- String sign = generateSign(dataJson);
-
- Map requestBody = new HashMap<>();
- requestBody.put("sys_id", SYS_ID);
- requestBody.put("product_id", PRODUCT_ID);
- requestBody.put("data", dataJson);
- requestBody.put("sign", sign);
-
- return Mono.fromRunnable(() -> {
- try {
- String jsonBody = JSONUtil.toJsonStr(requestBody);
- log.info("关闭订单请求, orderId={}", orderId);
-
- Request httpRequest = new Request.Builder()
- .url(CLOSE_URL)
- .post(RequestBody.create(jsonBody, MediaType.parse("application/json; charset=utf-8")))
- .addHeader("Content-Type", "application/json; charset=UTF-8")
- .addHeader("Accept", "application/json")
- .build();
-
- try (Response response = okHttpClient.newCall(httpRequest).execute()) {
- String responseBody = response.body() != null ? response.body().string() : "";
- log.info("关闭订单响应, code={}, body={}", response.code(), responseBody);
- }
- } catch (Exception e) {
- log.error("关闭订单异常", e);
- }
- });
- }
-
- // ==================== 5. 处理异步通知 ====================
- @Override
- public Mono handleNotify(String notifyBody) {
- return Mono.fromRunnable(() -> {
- log.info("========== 收到支付回调通知 ==========");
- log.info("回调内容: {}", notifyBody);
-
- try {
- JSONObject notifyJson = JSONUtil.parseObj(notifyBody);
- String sign = notifyJson.getStr("sign");
- String data = notifyJson.getStr("data");
-
- // 直接打印回调信息,不验证签名
- log.info("签名: {}", sign);
- log.info("数据: {}", data);
-
- if (data != null && !data.isEmpty()) {
- JSONObject dataJson = JSONUtil.parseObj(data);
- String respCode = dataJson.getStr("resp_code");
- String respDesc = dataJson.getStr("resp_desc");
- String outTransId = dataJson.getStr("out_trans_id");
- String transStatus = dataJson.getStr("trans_status");
- String transAmt = dataJson.getStr("trans_amt");
- String finishDate = dataJson.getStr("finish_date");
- String finishTime = dataJson.getStr("finish_time");
-
- log.info("========== 回调数据解析 ==========");
- log.info("响应码: {}", respCode);
- log.info("响应描述: {}", respDesc);
- log.info("商户订单号: {}", outTransId);
- log.info("交易状态: {}", transStatus);
- log.info("交易金额: {}", transAmt);
- log.info("交易完成日期: {}", finishDate);
- log.info("交易完成时间: {}", finishTime);
- log.info("===================================");
-
- // 更新本地支付状态
- if ("S".equals(transStatus) || "TRADE_SUCCESS".equals(transStatus)) {
- paymentCache.values().stream()
- .filter(info -> outTransId != null && outTransId.equals(info.outTransId))
- .findFirst()
- .ifPresent(info -> {
- info.payStatus = transStatus;
- info.payTime = LocalDateTime.now();
- log.info("支付状态更新成功, orderId={}, status={}", info.orderId, transStatus);
- });
- }
- }
- } catch (Exception e) {
- log.error("处理回调异常", e);
- }
- });
- }
-
- // ==================== 签名方法 ====================
- private String generateSign(String dataJson) {
- Exception originalException = null;
- System.err.println(">>>>>>>>> [PaymentServiceImpl] generateSign 开始执行 <<<<<<<<<<");
- System.err.flush();
- try {
- System.err.println(">>>>>>>>> 开始签名方法 <<<<<<<<<<");
- System.err.flush();
- log.error("========== 开始签名 ==========");
-
- // 打印密钥的前几个字节来检查格式
- String privateKeyBase64 = MERCHANT_PRIVATE_KEY.replaceAll("\\s", "");
- byte[] keyBytes = Base64.getDecoder().decode(privateKeyBase64);
- System.err.println(">>>>>>>>> 密钥前20字节(hex): " + bytesToHex(keyBytes, 20) + " <<<<<<<<<<");
- System.err.println(">>>>>>>>> 密钥长度: " + keyBytes.length + " bytes <<<<<<<<<<");
- System.err.flush();
-
- // 检查是否是PKCS#8格式 (应该以30 82开头)
- if (keyBytes.length > 2) {
- System.err.println(">>>>>>>>> 密钥前2字节(hex): " + bytesToHex(keyBytes, 2) + " <<<<<<<<<<");
- boolean startsWith30 = (keyBytes[0] & 0xFF) == 0x30;
- System.err.println(">>>>>>>>> 密钥是否以0x30开头: " + startsWith30 + " <<<<<<<<<<");
- System.err.flush();
-
- // 打印更多头部字节来识别密钥类型
- if (keyBytes.length > 4) {
- System.err.println(">>>>>>>>> 密钥前5字节(hex): " + bytesToHex(keyBytes, 5) + " <<<<<<<<<<");
- System.err.flush();
- // RSA PKCS#8 私钥通常以 30 82 04** 开头
- // EC PKCS#8 私钥通常以 30 82 04** 开头 (和RSA一样,需要看后面的内容区分)
- // 如果是 30 82,且第4个字节是 0x00 或 0x01,可能是RSA私钥
- // 如果是 30 82,且第4个字节是 0x02 或 0x03,可能是EC私钥
- }
- }
-
- log.info("私钥长度(去空格后): {}", privateKeyBase64.length());
-
- // 检查私钥格式
- if (privateKeyBase64.length() < 100) {
- throw new RuntimeException("私钥长度异常: " + privateKeyBase64.length());
- }
-
- log.info("私钥前50字符: {}", privateKeyBase64.substring(0, 50));
- log.info("私钥后50字符: {}", privateKeyBase64.substring(privateKeyBase64.length() - 50));
-
- log.info("开始解码私钥...");
- byte[] privateKeyBytes = Base64.getDecoder().decode(privateKeyBase64);
- log.info("私钥解码后长度: {} bytes", privateKeyBytes.length);
-
- // 直接尝试使用PKCS8EncodedKeySpec加载
- log.info("开始生成 PrivateKey 对象...");
- PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(privateKeyBytes);
- KeyFactory keyFactory = KeyFactory.getInstance("RSA");
- PrivateKey privateKey = keyFactory.generatePrivate(spec);
- log.info("私钥生成成功,算法: {}", privateKey.getAlgorithm());
-
- log.info("开始签名...");
- Signature signature = Signature.getInstance("SHA256withRSA");
- log.info("调用 initSign...");
- signature.initSign(privateKey);
- log.info("initSign 完成");
- log.info("开始 update...");
- signature.update(dataJson.getBytes(StandardCharsets.UTF_8));
- log.info("update 完成,开始 sign...");
- String sign = Base64.getEncoder().encodeToString(signature.sign());
- log.info("签名生成成功, 签名长度: {}", sign.length());
- log.info("========== 签名完成 ==========");
- return sign;
- } catch (RuntimeException e) {
- log.error("签名生成失败(RuntimeException): {} - {}", e.getClass().getName(), e.getMessage(), e);
- throw e;
- } catch (Exception e) {
- log.error("签名生成失败(Exception): {} - {}", e.getClass().getName(), e.getMessage(), e);
- throw new RuntimeException("签名生成失败: " + e.getMessage(), e);
- }
- }
-
- private boolean verifySign(String sign, String dataJson) {
- try {
- String publicKeyBase64 = HUIFU_PUBLIC_KEY.replaceAll("\\s", "");
- byte[] publicKeyBytes = Base64.getDecoder().decode(publicKeyBase64);
- X509EncodedKeySpec spec = new X509EncodedKeySpec(publicKeyBytes);
- KeyFactory keyFactory = KeyFactory.getInstance("RSA");
- PublicKey publicKey = keyFactory.generatePublic(spec);
- Signature signature = Signature.getInstance("SHA256withRSA");
- signature.initVerify(publicKey);
- signature.update(dataJson.getBytes(StandardCharsets.UTF_8));
- return signature.verify(Base64.getDecoder().decode(sign));
- } catch (Exception e) {
- log.error("验签失败", e);
- return false;
- }
- }
-
- // ==================== 响应解析方法 ====================
-
- private PaymentCreateResponse parseCreateResponse(String responseBody, String orderId, PaymentCreateRequest request) {
- JSONObject responseJson = JSONUtil.parseObj(responseBody);
- String dataStr = responseJson.getStr("data");
- JSONObject data = JSONUtil.parseObj(dataStr);
-
- String respCode = data.getStr("resp_code");
- if (!"000000".equals(respCode)) {
- throw new RuntimeException("支付创建失败: " + data.getStr("resp_desc") + " (code: " + respCode + ")");
- }
-
- // APP 支付优先取 alipay_scheme
- String payUrl = data.getStr("alipay_scheme");
- if (payUrl == null) {
- payUrl = data.getStr("pay_url");
- }
- if (payUrl == null) {
- payUrl = data.getStr("pay_info");
- }
-
- PaymentInfo paymentInfo = new PaymentInfo();
- paymentInfo.orderId = orderId;
- paymentInfo.memberId = request.getMemberId();
- paymentInfo.orderType = request.getOrderType();
- paymentInfo.transAmt = request.getTransAmt();
- paymentInfo.goodsDesc = request.getGoodsDesc();
- paymentInfo.payStatus = "PENDING";
- paymentInfo.outTransId = data.getStr("out_trans_id");
- paymentInfo.payUrl = payUrl;
- paymentInfo.remark = request.getRemark();
- paymentCache.put(orderId, paymentInfo);
-
- log.info("支付创建成功, orderId={}, outTransId={}, payUrl={}", orderId, paymentInfo.outTransId, payUrl);
-
- return PaymentCreateResponse.builder()
- .orderId(orderId)
- .payInfo(payUrl)
- .transAmt(request.getTransAmt())
- .payStatus("PENDING")
- .message("支付创建成功")
- .build();
- }
-
- private PaymentQueryResponse parseQueryResponse(String responseBody, String orderId) {
- JSONObject responseJson = JSONUtil.parseObj(responseBody);
- String dataStr = responseJson.getStr("data");
- JSONObject data = JSONUtil.parseObj(dataStr);
-
- String respCode = data.getStr("resp_code");
- if (!"000000".equals(respCode)) {
- throw new RuntimeException("查询失败: " + data.getStr("resp_desc"));
- }
-
- PaymentInfo paymentInfo = paymentCache.get(orderId);
- if (paymentInfo != null) {
- paymentInfo.payStatus = data.getStr("trans_status");
- }
-
- return PaymentQueryResponse.builder()
- .orderId(orderId)
- .tradeType(data.getStr("trade_type"))
- .transAmt(data.getStr("trans_amt"))
- .payStatus(data.getStr("trans_status"))
- .outTransId(data.getStr("out_trans_id"))
- .payTime(data.getStr("end_time"))
- .message("查询成功")
- .build();
- }
-
- private PaymentRefundResponse parseRefundResponse(String responseBody, String orderId, String refundAmt) {
- JSONObject responseJson = JSONUtil.parseObj(responseBody);
- String dataStr = responseJson.getStr("data");
- JSONObject data = JSONUtil.parseObj(dataStr);
-
- String respCode = data.getStr("resp_code");
- if (!"000000".equals(respCode)) {
- throw new RuntimeException("退款失败: " + data.getStr("resp_desc"));
- }
-
- PaymentInfo paymentInfo = paymentCache.get(orderId);
- if (paymentInfo != null) {
- paymentInfo.payStatus = "REFUNDED";
- }
-
- return PaymentRefundResponse.builder()
- .orderId(orderId)
- .refundAmt(refundAmt)
- .refundStatus(data.getStr("trans_status"))
- .message("退款成功")
- .build();
- }
-
- // ==================== 工具方法 ====================
- private static String bytesToHex(byte[] bytes, int length) {
- int len = Math.min(bytes.length, length);
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < len; i++) {
- sb.append(String.format("%02X ", bytes[i] & 0xFF));
- }
- return sb.toString();
- }
-
- /**
- * 将PKCS#1格式的RSA私钥转换为PKCS#8格式
- * PKCS#1: SEQUENCE { version, modulus, publicExponent, privateExponent, prime1, prime2, ... }
- * PKCS#8: SEQUENCE { version, algorithmIdentifier, OCTET STRING (containing PKCS#1) }
- */
- private byte[] convertPKCS1ToPKCS8(byte[] pkcs1Key) {
- try {
- log.info("检测到PKCS#1格式私钥,开始转换为PKCS#8格式...");
- log.info("PKCS#1密钥长度: {} bytes", pkcs1Key.length);
-
- // RSA算法OID: 1.2.840.113549.1.1.1
- byte[] rsaOid = new byte[] {
- 0x06, 0x09, 0x2A, (byte)0x86, 0x48, (byte)0x86, (byte)0xF7, 0x0D, 0x01, 0x01, 0x01
- };
-
- // 构建AlgorithmIdentifier: SEQUENCE { OID, NULL }
- // SEQUENCE (2 bytes: tag + length) + OID + NULL = 15 bytes total
- byte[] algorithmIdentifier = new byte[15];
- int idx = 0;
- algorithmIdentifier[idx++] = 0x30; // SEQUENCE tag
- algorithmIdentifier[idx++] = 0x0D; // length = 13 bytes for OID + NULL
- System.arraycopy(rsaOid, 0, algorithmIdentifier, idx, rsaOid.length);
- idx += rsaOid.length;
- algorithmIdentifier[idx++] = 0x05; // NULL tag
- algorithmIdentifier[idx++] = 0x00; // NULL value
-
- log.info("AlgorithmIdentifier长度: {} bytes", algorithmIdentifier.length);
-
- // 计算 OCTET STRING 长度编码需要的字节数
- int octetStringLengthBytes = 1; // tag
- if (pkcs1Key.length > 127) {
- if (pkcs1Key.length > 255) {
- octetStringLengthBytes += 3; // 0x82 + 2 length bytes
- } else {
- octetStringLengthBytes += 2; // 0x81 + 1 length byte
- }
- } else {
- octetStringLengthBytes += 1; // 1 length byte
- }
-
- // 计算 version INTEGER 编码 (3 bytes: tag + length + value)
- int versionBytes = 3;
-
- // 计算 total length of inner content (version + algorithmId + octetString)
- int innerContentLength = versionBytes + algorithmIdentifier.length + octetStringLengthBytes + pkcs1Key.length;
-
- // 计算 outer SEQUENCE 长度编码需要的字节数
- int sequenceLengthBytes = 1; // tag
- if (innerContentLength > 127) {
- if (innerContentLength > 255) {
- sequenceLengthBytes += 3; // 0x82 + 2 length bytes
- } else {
- sequenceLengthBytes += 2; // 0x81 + 1 length byte
- }
- } else {
- sequenceLengthBytes += 1;
- }
-
- // 分配最终数组
- int totalLen = sequenceLengthBytes + innerContentLength;
- byte[] pkcs8Key = new byte[totalLen];
- log.info("PKCS#8密钥分配: {} bytes", totalLen);
-
- int offset = 0;
-
- // outer SEQUENCE
- pkcs8Key[offset++] = 0x30;
- if (innerContentLength > 127) {
- if (innerContentLength > 255) {
- pkcs8Key[offset++] = (byte)0x82;
- pkcs8Key[offset++] = (byte)((innerContentLength >> 8) & 0xFF);
- pkcs8Key[offset++] = (byte)(innerContentLength & 0xFF);
- } else {
- pkcs8Key[offset++] = (byte)0x81;
- pkcs8Key[offset++] = (byte)innerContentLength;
- }
- } else {
- pkcs8Key[offset++] = (byte)innerContentLength;
- }
-
- // version = 0
- pkcs8Key[offset++] = 0x02;
- pkcs8Key[offset++] = 0x01;
- pkcs8Key[offset++] = 0x00;
-
- // algorithmIdentifier
- System.arraycopy(algorithmIdentifier, 0, pkcs8Key, offset, algorithmIdentifier.length);
- offset += algorithmIdentifier.length;
-
- // privateKey as OCTET STRING
- pkcs8Key[offset++] = 0x04;
- if (pkcs1Key.length > 127) {
- if (pkcs1Key.length > 255) {
- pkcs8Key[offset++] = (byte)0x82;
- pkcs8Key[offset++] = (byte)((pkcs1Key.length >> 8) & 0xFF);
- pkcs8Key[offset++] = (byte)(pkcs1Key.length & 0xFF);
- } else {
- pkcs8Key[offset++] = (byte)0x81;
- pkcs8Key[offset++] = (byte)pkcs1Key.length;
- }
- } else {
- pkcs8Key[offset++] = (byte)pkcs1Key.length;
- }
-
- // 复制 PKCS#1 私钥
- System.arraycopy(pkcs1Key, 0, pkcs8Key, offset, pkcs1Key.length);
-
- log.info("PKCS#1到PKCS#8转换完成,最终长度: {} bytes", pkcs8Key.length);
- return pkcs8Key;
- } catch (Exception e) {
- log.error("PKCS#1到PKCS#8转换失败", e);
- throw new RuntimeException("密钥格式转换失败: " + e.getMessage(), e);
- }
- }
-
- // ==================== 内部缓存类 ====================
- private static class PaymentInfo {
- String orderId;
- Long memberId;
- String orderType;
- String tradeType;
- String transAmt;
- String goodsDesc;
- String payStatus;
- String outTransId;
- String payUrl;
- String hfSeqId;
- LocalDateTime payTime;
- String remark;
- }
-}
\ No newline at end of file
diff --git a/gym-manage-api/gym-payment/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/gym-manage-api/gym-payment/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
deleted file mode 100644
index 691774a..0000000
--- a/gym-manage-api/gym-payment/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
+++ /dev/null
@@ -1 +0,0 @@
-cn.novalon.gym.manage.payment.config.HuifuPayConfig
diff --git a/gym-manage-api/manage-app/pom.xml b/gym-manage-api/manage-app/pom.xml
index 6e95ea5..7a15e65 100644
--- a/gym-manage-api/manage-app/pom.xml
+++ b/gym-manage-api/manage-app/pom.xml
@@ -58,7 +58,7 @@
gym-auth
${project.version}
-
+
org.springframework.boot
spring-boot-starter-webflux
diff --git a/gym-manage-api/manage-app/src/main/java/cn/novalon/gym/manage/app/config/SystemRouter.java b/gym-manage-api/manage-app/src/main/java/cn/novalon/gym/manage/app/config/SystemRouter.java
index 482d931..2a4da75 100644
--- a/gym-manage-api/manage-app/src/main/java/cn/novalon/gym/manage/app/config/SystemRouter.java
+++ b/gym-manage-api/manage-app/src/main/java/cn/novalon/gym/manage/app/config/SystemRouter.java
@@ -1,10 +1,11 @@
package cn.novalon.gym.manage.app.config;
+import cn.novalon.gym.manage.auth.handler.PhoneAuthHandler;
import cn.novalon.gym.manage.checkIn.handler.CheckInHandler;
import cn.novalon.gym.manage.datacount.handler.DataStatisticsHandler;
import cn.novalon.gym.manage.file.handler.SysFileHandler;
-import cn.novalon.gym.manage.auth.handler.PhoneAuthHandler;
+
import cn.novalon.gym.manage.groupcourse.handler.GroupCourseBookingHandler;
import cn.novalon.gym.manage.groupcourse.handler.GroupCourseHandler;
import cn.novalon.gym.manage.groupcourse.handler.GroupCourseRecommendHandler;
diff --git a/gym-manage-api/manage-app/src/main/resources/application-dev.yml b/gym-manage-api/manage-app/src/main/resources/application-dev.yml
index 46b6a66..d14149a 100644
--- a/gym-manage-api/manage-app/src/main/resources/application-dev.yml
+++ b/gym-manage-api/manage-app/src/main/resources/application-dev.yml
@@ -36,3 +36,11 @@ logging:
cn.novalon.manage: DEBUG
org.springframework.r2dbc: DEBUG
org.springframework.web: TRACE
+
+alibaba:
+ cloud:
+ sms:
+ access-key-id: LTAI5t8GhorWLu5WkEx8MDZz
+ access-key-secret: jNDwb9IHvTIESUezLYHZRT5c5NEaCz
+ sign-name: 云渚科技验证平台
+ template-code: 100001
diff --git a/gym-manage-api/manage-app/src/main/resources/member-config.yml b/gym-manage-api/manage-app/src/main/resources/member-config.yml
index 05ce7cb..5338737 100644
--- a/gym-manage-api/manage-app/src/main/resources/member-config.yml
+++ b/gym-manage-api/manage-app/src/main/resources/member-config.yml
@@ -16,8 +16,8 @@ wechat:
# 手机号加密配置
phone-encryption:
- secret-key: ${PHONE_ENCRYPTION_SECRET_KEY}
- iv: ${PHONE_ENCRYPTION_IV}
+ secret-key: ${PHONE_ENCRYPTION_SECRET_KEY:P8539ANjWJWsRbVHZKhM8Q==}
+ iv: ${PHONE_ENCRYPTION_IV:3tHp07uMRYh1xKsIXvYJMA==}
spring:
elasticsearch:
diff --git a/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/config/AuthConfig.java b/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/config/AuthConfig.java
new file mode 100644
index 0000000..d4995e8
--- /dev/null
+++ b/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/config/AuthConfig.java
@@ -0,0 +1,19 @@
+package cn.novalon.gym.manage.common.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+@Data
+@Component
+@ConfigurationProperties(prefix = "auth")
+public class AuthConfig {
+
+ private String accessKeyId;
+
+ private String accessKeySecret;
+
+ private Integer tokenExpireSeconds = 86400;
+
+ private Integer refreshTokenExpireSeconds = 604800;
+}
\ No newline at end of file
diff --git a/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/response/Result.java b/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/response/Result.java
new file mode 100644
index 0000000..7b0efbc
--- /dev/null
+++ b/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/response/Result.java
@@ -0,0 +1,54 @@
+package cn.novalon.gym.manage.common.response;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class Result {
+
+ private Integer code;
+
+ private String message;
+
+ private T data;
+
+ private Long timestamp;
+
+ public static Result success(T data) {
+ return Result.builder()
+ .code(200)
+ .message("success")
+ .data(data)
+ .timestamp(System.currentTimeMillis())
+ .build();
+ }
+
+ public static Result success(String message) {
+ return Result.builder()
+ .code(200)
+ .message(message)
+ .timestamp(System.currentTimeMillis())
+ .build();
+ }
+
+ public static Result fail(Integer code, String message) {
+ return Result.builder()
+ .code(code)
+ .message(message)
+ .timestamp(System.currentTimeMillis())
+ .build();
+ }
+
+ public static Result fail(String message) {
+ return Result.builder()
+ .code(500)
+ .message(message)
+ .timestamp(System.currentTimeMillis())
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/util/JwtUtil.java b/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/util/JwtUtil.java
new file mode 100644
index 0000000..bae13cc
--- /dev/null
+++ b/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/util/JwtUtil.java
@@ -0,0 +1,86 @@
+package cn.novalon.gym.manage.common.util;
+
+import cn.novalon.gym.manage.common.config.JwtProperties;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.security.Keys;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import javax.crypto.SecretKey;
+import java.nio.charset.StandardCharsets;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class JwtUtil {
+
+ private final JwtProperties jwtProperties;
+
+ private SecretKey getSigningKey() {
+ String secret = jwtProperties.getSecret();
+ if (secret.length() < 32) {
+ StringBuilder sb = new StringBuilder(secret);
+ while (sb.length() < 32) {
+ sb.append(secret);
+ }
+ secret = sb.substring(0, 32);
+ }
+ return Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
+ }
+
+ public String generateToken(String userId, String phone) {
+ Date now = new Date();
+ Date expiryDate = new Date(now.getTime() + jwtProperties.getExpiration());
+
+ Map claims = new HashMap<>();
+ claims.put("userId", userId);
+ claims.put("phone", phone);
+
+ return Jwts.builder()
+ .setClaims(claims)
+ .setSubject(userId)
+ .setIssuedAt(now)
+ .setExpiration(expiryDate)
+ .signWith(getSigningKey())
+ .compact();
+ }
+
+ public String generateRefreshToken(String userId) {
+ Date now = new Date();
+ Date expiryDate = new Date(now.getTime() + jwtProperties.getExpiration() * 7);
+
+ return Jwts.builder()
+ .setSubject(userId)
+ .setIssuedAt(now)
+ .setExpiration(expiryDate)
+ .signWith(getSigningKey())
+ .compact();
+ }
+
+ public Claims parseToken(String token) {
+ return Jwts.parserBuilder()
+ .setSigningKey(getSigningKey())
+ .build()
+ .parseClaimsJws(token)
+ .getBody();
+ }
+
+ public String getUserIdFromToken(String token) {
+ return parseToken(token).getSubject();
+ }
+
+ public boolean validateToken(String token) {
+ try {
+ parseToken(token);
+ return true;
+ } catch (Exception e) {
+ log.warn("JWT token validation failed: {}", e.getMessage());
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V13__Create_SignInRecord_table.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V13__Create_SignInRecord_table.sql
new file mode 100644
index 0000000..ca01bc4
--- /dev/null
+++ b/gym-manage-api/manage-db/src/main/resources/db/migration/V13__Create_SignInRecord_table.sql
@@ -0,0 +1,99 @@
+-- ============================================
+-- 签到记录表(sign_in_record)
+-- ============================================
+
+-- Step 1: 创建 sign_in_record 表
+CREATE TABLE IF NOT EXISTS sign_in_record (
+ -- ========== 主键和基础字段 ==========
+ id BIGSERIAL PRIMARY KEY, -- 自增主键
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, -- 记录创建时间
+ updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, -- 记录更新时间
+
+ -- ========== 会员相关字段 ==========
+ member_id BIGINT NOT NULL, -- 会员ID,关联member_user表
+ member_card_id BIGINT, -- 签到时使用的会员卡ID
+
+ -- ========== 签到核心字段 ==========
+ sign_in_time TIMESTAMP NOT NULL, -- 签到入场时间
+ sign_in_type VARCHAR(20) NOT NULL, -- 签到方式:QR_CODE-扫码签到,MANUAL-手动签到,FACE-人脸识别
+ sign_in_status VARCHAR(20) NOT NULL DEFAULT 'SUCCESS', -- 签到状态:SUCCESS-成功,FAILED-失败
+
+ -- ========== 验证和错误信息 ==========
+ verification_details TEXT, -- JSONB格式,存储会员卡验证时的快照数据
+ fail_reason VARCHAR(500), -- 失败时的具体原因文案
+
+ -- ========== 操作人信息 ==========
+ operator_id BIGINT, -- 操作人ID(前台人员),自助签到时为NULL
+ operator_name VARCHAR(100), -- 操作人姓名冗余
+
+ -- ========== 设备和环境信息 ==========
+ device_info VARCHAR(200), -- 签到设备标识或型号
+ ip_address VARCHAR(50), -- 客户端IP地址
+ source VARCHAR(20) NOT NULL, -- 签到来源:MINI_PROGRAM-小程序扫码,PC_BACKEND-后台管理端
+
+ -- ========== 软删除字段 ==========
+ is_delete BOOLEAN DEFAULT FALSE -- 软删除标识:false-未删除,true-已删除
+);
+
+-- Step 2: 创建索引
+-- 会员ID索引(加速按会员查询签到记录)
+CREATE INDEX IF NOT EXISTS idx_sign_in_record_member_id ON sign_in_record(member_id);
+
+-- 签到时间索引(加速按时间范围查询)
+CREATE INDEX IF NOT EXISTS idx_sign_in_record_sign_in_time ON sign_in_record(sign_in_time);
+
+-- 签到状态索引(加速按状态筛选)
+CREATE INDEX IF NOT EXISTS idx_sign_in_record_sign_in_status ON sign_in_record(sign_in_status);
+
+-- 会员卡ID索引(加速按会员卡查询)
+CREATE INDEX IF NOT EXISTS idx_sign_in_record_member_card_id ON sign_in_record(member_card_id);
+
+-- 操作人ID索引(加速按操作人查询)
+CREATE INDEX IF NOT EXISTS idx_sign_in_record_operator_id ON sign_in_record(operator_id);
+
+-- 签到来源索引(加速按来源统计)
+CREATE INDEX IF NOT EXISTS idx_sign_in_record_source ON sign_in_record(source);
+
+-- 软删除索引(加速查询未删除的记录)
+CREATE INDEX IF NOT EXISTS idx_sign_in_record_is_delete ON sign_in_record(is_delete);
+
+-- 复合索引:会员ID + 签到时间(加速会员签到历史查询)
+CREATE INDEX IF NOT EXISTS idx_sign_in_record_member_time ON sign_in_record(member_id, sign_in_time);
+
+-- 复合索引:签到状态 + 签到时间(加速统计数据查询)
+CREATE INDEX IF NOT EXISTS idx_sign_in_record_status_time ON sign_in_record(sign_in_status, sign_in_time);
+
+-- 复合索引:签到日期(用于每日统计,使用表达式索引)
+CREATE INDEX IF NOT EXISTS idx_sign_in_record_sign_in_date ON sign_in_record(DATE(sign_in_time));
+
+-- Step 3: 添加外键约束(可选,根据业务需求决定)
+-- 注意:如果member_user表使用了软删除,外键约束可能需要谨慎使用
+-- ALTER TABLE sign_in_record
+-- ADD CONSTRAINT fk_sign_in_record_member
+-- FOREIGN KEY (member_id) REFERENCES member_user(id) ON DELETE SET NULL;
+
+-- Step 4: 添加注释
+COMMENT ON TABLE sign_in_record IS '会员到店签到记录表';
+
+COMMENT ON COLUMN sign_in_record.id IS '自增主键';
+COMMENT ON COLUMN sign_in_record.created_at IS '记录创建时间';
+COMMENT ON COLUMN sign_in_record.updated_at IS '记录更新时间';
+
+COMMENT ON COLUMN sign_in_record.member_id IS '会员ID,关联member_user表';
+COMMENT ON COLUMN sign_in_record.member_card_id IS '签到时使用的会员卡ID';
+
+COMMENT ON COLUMN sign_in_record.sign_in_time IS '签到入场时间';
+COMMENT ON COLUMN sign_in_record.sign_in_type IS '签到方式:QR_CODE-扫码签到,MANUAL-手动签到,FACE-人脸识别';
+COMMENT ON COLUMN sign_in_record.sign_in_status IS '签到状态:SUCCESS-成功,FAILED-失败';
+
+COMMENT ON COLUMN sign_in_record.verification_details IS 'JSON格式,存储会员卡验证时的快照数据(包含卡类型、剩余次数/金额、有效期等)';
+COMMENT ON COLUMN sign_in_record.fail_reason IS '失败时的具体原因文案';
+
+COMMENT ON COLUMN sign_in_record.operator_id IS '操作人ID(前台人员),自助签到时为NULL';
+COMMENT ON COLUMN sign_in_record.operator_name IS '操作人姓名冗余(避免关联查询)';
+
+COMMENT ON COLUMN sign_in_record.device_info IS '签到设备标识或型号';
+COMMENT ON COLUMN sign_in_record.ip_address IS '客户端IP地址';
+COMMENT ON COLUMN sign_in_record.source IS '签到来源:MINI_PROGRAM-小程序扫码,PC_BACKEND-后台管理端';
+
+COMMENT ON COLUMN sign_in_record.is_delete IS '软删除标识:false-未删除,true-已删除';
diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V20__Update_Member_User_For_One_Click_Login.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V20__Update_Member_User_For_One_Click_Login.sql
new file mode 100644
index 0000000..05765d1
--- /dev/null
+++ b/gym-manage-api/manage-db/src/main/resources/db/migration/V20__Update_Member_User_For_One_Click_Login.sql
@@ -0,0 +1,21 @@
+-- ============================================
+-- V20: 更新 member_user 表 - 添加阿里云号码认证字段
+-- 支持一键登录功能
+-- ============================================
+
+-- 添加阿里云号码认证相关字段
+ALTER TABLE IF EXISTS member_user
+ ADD COLUMN IF NOT EXISTS dypns_open_id VARCHAR(100),
+ ADD COLUMN IF NOT EXISTS id_card VARCHAR(50),
+ ADD COLUMN IF NOT EXISTS real_name VARCHAR(50),
+ ADD COLUMN IF NOT EXISTS register_channel VARCHAR(50) DEFAULT 'SMS';
+
+-- 创建索引
+CREATE INDEX IF NOT EXISTS idx_member_user_dypns_open_id ON member_user(dypns_open_id);
+CREATE INDEX IF NOT EXISTS idx_member_user_register_channel ON member_user(register_channel);
+
+-- 添加字段注释
+COMMENT ON COLUMN member_user.dypns_open_id IS '阿里云号码认证OpenID(一键登录用户唯一标识)';
+COMMENT ON COLUMN member_user.id_card IS '身份证号码(AES加密存储)';
+COMMENT ON COLUMN member_user.real_name IS '真实姓名(AES加密存储)';
+COMMENT ON COLUMN member_user.register_channel IS '注册渠道:SMS-短信验证码,ONE_CLICK-一键登录,WECHAT-微信授权';
diff --git a/gym-manage-api/manage-gateway/src/main/java/cn/novalon/gym/manage/gateway/filter/JwtAuthenticationFilter.java b/gym-manage-api/manage-gateway/src/main/java/cn/novalon/gym/manage/gateway/filter/JwtAuthenticationFilter.java
index b2d1a4a..b743860 100644
--- a/gym-manage-api/manage-gateway/src/main/java/cn/novalon/gym/manage/gateway/filter/JwtAuthenticationFilter.java
+++ b/gym-manage-api/manage-gateway/src/main/java/cn/novalon/gym/manage/gateway/filter/JwtAuthenticationFilter.java
@@ -63,7 +63,7 @@ public class JwtAuthenticationFilter extends AbstractGatewayFilterFactory "dev".equals(profile) || "test".equals(profile) || "h2-test".equals(profile));
-
- logger.info("SecurityConfig初始化: 当前环境={}, Swagger启用状态={}",
+
+ logger.info("SecurityConfig初始化: 当前环境={}, Swagger启用状态={}",
activeProfiles.length > 0 ? String.join(",", activeProfiles) : "default", isDevOrTest);
http
@@ -66,7 +66,6 @@ public class SecurityConfig {
.pathMatchers("/api/payment/notify").permitAll()
.pathMatchers("/api/payment/refund").permitAll();
-
if (isDevOrTest) {
spec.pathMatchers("/swagger-ui.html").permitAll()
.pathMatchers("/swagger-ui/**").permitAll()
diff --git a/gym-manage-api/pom.xml b/gym-manage-api/pom.xml
index 06ebb61..8746a4a 100644
--- a/gym-manage-api/pom.xml
+++ b/gym-manage-api/pom.xml
@@ -46,7 +46,6 @@
gym-groupCourse
gym-checkIn
gym-dataCount
- gym-payment
gym-auth
diff --git a/gym-manage-api/test-jar/META-INF/MANIFEST.MF b/gym-manage-api/test-jar/META-INF/MANIFEST.MF
deleted file mode 100644
index 19b83f2..0000000
--- a/gym-manage-api/test-jar/META-INF/MANIFEST.MF
+++ /dev/null
@@ -1,6 +0,0 @@
-Manifest-Version: 1.0
-Created-By: Maven JAR Plugin 3.4.2
-Build-Jdk-Spec: 21
-Implementation-Title: Gym Payment
-Implementation-Version: 1.0.0
-
diff --git a/gym-manage-api/test-jar/META-INF/maven/cn.novalon.gym.manage/gym-payment/pom.properties b/gym-manage-api/test-jar/META-INF/maven/cn.novalon.gym.manage/gym-payment/pom.properties
deleted file mode 100644
index e4cde16..0000000
--- a/gym-manage-api/test-jar/META-INF/maven/cn.novalon.gym.manage/gym-payment/pom.properties
+++ /dev/null
@@ -1,3 +0,0 @@
-artifactId=gym-payment
-groupId=cn.novalon.gym.manage
-version=1.0.0
diff --git a/gym-manage-api/test-jar/META-INF/maven/cn.novalon.gym.manage/gym-payment/pom.xml b/gym-manage-api/test-jar/META-INF/maven/cn.novalon.gym.manage/gym-payment/pom.xml
deleted file mode 100644
index 39709f1..0000000
--- a/gym-manage-api/test-jar/META-INF/maven/cn.novalon.gym.manage/gym-payment/pom.xml
+++ /dev/null
@@ -1,85 +0,0 @@
-
-
-
- 4.0.0
-
-
- cn.novalon.gym.manage
- gym-manage-api
- 1.0.0
-
-
- gym-payment
- jar
-
- Gym Payment
- Payment Module - Integrates Huifu Payment Gateway
-
-
-
- cn.novalon.gym.manage
- manage-common
- ${project.version}
-
-
- org.springframework.boot
- spring-boot-starter-webflux
-
-
- org.springdoc
- springdoc-openapi-starter-webflux-ui
-
-
- org.springframework.boot
- spring-boot-starter-test
- test
-
-
- io.projectreactor
- reactor-test
- test
-
-
- cn.hutool
- hutool-all
- 5.8.25
-
-
- com.squareup.okhttp3
- okhttp
- 4.12.0
-
-
- org.springframework.boot
- spring-boot-starter-validation
-
-
-
-
-
-
- org.apache.maven.plugins
- maven-jar-plugin
- 3.4.2
-
-
- org.apache.maven.plugins
- maven-compiler-plugin
- 3.11.0
-
- 21
- 21
-
-
- org.projectlombok
- lombok
- ${lombok.version}
-
-
-
-
-
-
-
diff --git a/gym-manage-api/test-jar/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/gym-manage-api/test-jar/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
deleted file mode 100644
index 691774a..0000000
--- a/gym-manage-api/test-jar/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
+++ /dev/null
@@ -1 +0,0 @@
-cn.novalon.gym.manage.payment.config.HuifuPayConfig