From 524182c478dae691b971619c82e0d78ff4163199 Mon Sep 17 00:00:00 2001 From: future <1360317836@qq.com> Date: Tue, 23 Jun 2026 23:40:49 +0800 Subject: [PATCH 1/6] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E6=9E=84=E5=BB=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gym-manage-api/META-INF/MANIFEST.MF | 6 - .../gym-payment/pom.properties | 3 - .../cn.novalon.gym.manage/gym-payment/pom.xml | 85 -- ...ot.autoconfigure.AutoConfiguration.imports | 1 - .../db/migration/V1__Create_all_tables.sql | 407 ---------- gym-manage-api/gym-payment/pom.xml | 95 --- .../gym/manage/payment/config/CorsConfig.java | 23 - .../manage/payment/config/HuifuPayConfig.java | 34 - .../dto/request/PaymentCreateRequest.java | 58 -- .../dto/response/PaymentCreateResponse.java | 28 - .../dto/response/PaymentQueryResponse.java | 30 - .../dto/response/PaymentRefundResponse.java | 22 - .../payment/handler/PaymentHandler.java | 78 -- .../payment/service/IPaymentService.java | 19 - .../service/impl/PaymentServiceImpl.java | 723 ------------------ ...ot.autoconfigure.AutoConfiguration.imports | 1 - .../gym/manage/sys/config/SecurityConfig.java | 2 +- gym-manage-api/pom.xml | 1 - gym-manage-api/test-jar/META-INF/MANIFEST.MF | 6 - .../gym-payment/pom.properties | 3 - .../cn.novalon.gym.manage/gym-payment/pom.xml | 85 -- ...ot.autoconfigure.AutoConfiguration.imports | 1 - 22 files changed, 1 insertion(+), 1710 deletions(-) delete mode 100644 gym-manage-api/META-INF/MANIFEST.MF delete mode 100644 gym-manage-api/META-INF/maven/cn.novalon.gym.manage/gym-payment/pom.properties delete mode 100644 gym-manage-api/META-INF/maven/cn.novalon.gym.manage/gym-payment/pom.xml delete mode 100644 gym-manage-api/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports delete mode 100644 gym-manage-api/db/migration/V1__Create_all_tables.sql delete mode 100644 gym-manage-api/gym-payment/pom.xml delete mode 100644 gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/config/CorsConfig.java delete mode 100644 gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/config/HuifuPayConfig.java delete mode 100644 gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/dto/request/PaymentCreateRequest.java delete mode 100644 gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/dto/response/PaymentCreateResponse.java delete mode 100644 gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/dto/response/PaymentQueryResponse.java delete mode 100644 gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/dto/response/PaymentRefundResponse.java delete mode 100644 gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/handler/PaymentHandler.java delete mode 100644 gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/service/IPaymentService.java delete mode 100644 gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/service/impl/PaymentServiceImpl.java delete mode 100644 gym-manage-api/gym-payment/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports delete mode 100644 gym-manage-api/test-jar/META-INF/MANIFEST.MF delete mode 100644 gym-manage-api/test-jar/META-INF/maven/cn.novalon.gym.manage/gym-payment/pom.properties delete mode 100644 gym-manage-api/test-jar/META-INF/maven/cn.novalon.gym.manage/gym-payment/pom.xml delete mode 100644 gym-manage-api/test-jar/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 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-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-sys/src/main/java/cn/novalon/gym/manage/sys/config/SecurityConfig.java b/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/config/SecurityConfig.java index 5b289e1..e66d484 100644 --- a/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/config/SecurityConfig.java +++ b/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/config/SecurityConfig.java @@ -1,4 +1,4 @@ -package cn.novalon.gym.manage.sys.config; +package cn.novalon.gym.manage.sys.config; import cn.novalon.gym.manage.sys.audit.OperationLogWebFilter; import cn.novalon.gym.manage.sys.security.JwtAuthenticationFilter; 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 From a7f31083cfb61f95fff0c3cfcf36425350a6141f Mon Sep 17 00:00:00 2001 From: future <1360317836@qq.com> Date: Tue, 2 Jun 2026 09:56:37 +0800 Subject: [PATCH 2/6] =?UTF-8?q?=E7=AD=BE=E5=88=B0=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../checkIn/config/WebSocketConfig.java | 43 +++++++++++++++++++ .../manage/checkIn/entity/SignInRecord.java | 2 +- .../checkIn/handler/CheckInHandler.java | 3 +- .../checkIn/service/ICheckInService.java | 2 +- .../service/impl/CheckServiceImpl.java | 39 ++--------------- .../checkIn/websocket/MyWebSocketHandler.java | 5 +-- .../gym/manage/checkin/CheckInModuleTest.java | 1 - .../service/impl/MemberCardServiceImpl.java | 2 +- .../common/constant/RedisKeyConstants.java | 3 ++ .../filter/JwtAuthenticationFilter.java | 2 +- .../gym/manage/sys/config/SecurityConfig.java | 1 - 11 files changed, 57 insertions(+), 46 deletions(-) create mode 100644 gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/config/WebSocketConfig.java 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..a8a8748 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 @@ -36,6 +36,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 +191,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..4d9285f 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; @@ -84,7 +84,6 @@ public class CheckServiceImpl implements ICheckInService { public Mono checkIn(Long memberId, String qrContent) { String key = RedisKeyConstants.QRCODE_USER_DAILY + memberId + LocalDate.now(); - // 先检查当天是否已经签到(从数据库查询,作为重复签到的额外保障) return checkTodayAlreadySignedIn(memberId) .flatMap(existingRecord -> { String checkInTime = existingRecord.getSignInTime().format(DATE_FORMATTER); @@ -126,11 +125,6 @@ public class CheckServiceImpl implements ICheckInService { }); } - /** - * 检查会员当天是否已经签到 - * @param memberId 会员ID - * @return 如果已签到返回签到记录,否则返回空 - */ private Mono checkTodayAlreadySignedIn(Long memberId) { LocalDateTime startOfDay = LocalDate.now().atStartOfDay(); LocalDateTime endOfDay = LocalDate.now().atTime(LocalTime.MAX); @@ -138,13 +132,9 @@ public class CheckServiceImpl implements ICheckInService { return signInRecordRepository.findByMemberIdAndDate(memberId, startOfDay, endOfDay); } - /** - * 处理签到逻辑 - */ private Mono processCheckIn(Long memberId, Long memberCardRecordId, Map redisMap, String qrContent) { LocalDateTime now = LocalDateTime.now(); - // 发送实时进度通知 MyWebSocketHandler.sendProgress(qrContent, "VALIDATE_CARD", "正在验证会员卡..."); return memberCardRecordRepository.findById(memberCardRecordId) @@ -163,10 +153,8 @@ public class CheckServiceImpl implements ICheckInService { return Mono.error(new RuntimeException("会员卡已过期")); } - // 发送实时进度通知 MyWebSocketHandler.sendProgress(qrContent, "VALIDATE_BOOKING", "会员卡验证通过,正在检查预约信息..."); - // 检查是否有需要签到的团课预约 return validateBooking(memberId, now) .then(memberCardRepository.findByMemberCardIdAndDeletedAtIsNull(cardRecord.getMemberCardId()) .switchIfEmpty(Mono.defer(() -> { @@ -174,7 +162,6 @@ public class CheckServiceImpl implements ICheckInService { return Mono.error(new RuntimeException("会员卡类型不存在")); })) .flatMap(card -> { - // 发送实时进度通知 MyWebSocketHandler.sendProgress(qrContent, "DEDUCT_USAGE", "正在扣减会员卡次数..."); return deductCardUsage(cardRecord, card) @@ -195,9 +182,6 @@ public class CheckServiceImpl implements ICheckInService { }); } - /** - * 验证预约信息 - */ private Mono validateBooking(Long memberId, LocalDateTime now) { return groupCourseBookingService.getBookingsByMemberId(memberId) .filter(booking -> { @@ -229,9 +213,6 @@ public class CheckServiceImpl implements ICheckInService { }); } - /** - * 扣减会员卡使用次数/金额 - */ private Mono deductCardUsage(MemberCardRecord record, MemberCard card) { MemberCardType cardType = MemberCardType.valueOf(card.getMemberCardType()); LocalDateTime now = LocalDateTime.now(); @@ -267,9 +248,6 @@ public class CheckServiceImpl implements ICheckInService { } } - /** - * 保存签到记录 - */ private Mono saveSignInRecord(Long memberId, Long memberCardRecordId, Long memberCardId) { SignInRecord record = SignInRecord.builder() .memberId(memberId) @@ -284,9 +262,6 @@ public class CheckServiceImpl implements ICheckInService { return signInRecordRepository.save(record).then(); } - /** - * 构建成功响应 - */ private String buildSuccessResponse(LocalDateTime dateTime) { Map res = new HashMap<>(); res.put("message", "签到成功"); @@ -294,9 +269,6 @@ public class CheckServiceImpl implements ICheckInService { return JSONUtil.toJsonStr(res); } - /** - * 查找会员的有效会员卡(优先选择有效期最早到期的) - */ private Mono findValidMemberCard(Long memberId) { return memberCardRecordRepository.findActiveCardsByMemberId(memberId) .filter(record -> { @@ -314,8 +286,6 @@ public class CheckServiceImpl implements ICheckInService { .next(); } - // ==================== 签到记录管理功能 ==================== - @Override public Flux getSignInRecords(Long memberId, LocalDate startTime, LocalDate endTime) { LocalDateTime start = startTime.atStartOfDay(); @@ -413,9 +383,6 @@ public class CheckServiceImpl implements ICheckInService { ); } - /** - * 转换实体到VO - */ private SignInRecordVO convertToVO(SignInRecord record) { SignInRecordVO vo = new SignInRecordVO(); vo.setId(record.getId()); @@ -436,4 +403,4 @@ public class CheckServiceImpl implements ICheckInService { if (now.isAfter(endOfDay)) return 1; return ChronoUnit.SECONDS.between(now, endOfDay); } -} \ No newline at end of file +} 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..7f705c8 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 @@ -77,7 +77,7 @@ public class MyWebSocketHandler implements WebSocketHandler { .doOnError(e -> { log.error("接收消息出错,sessionId={}", sessionId, e); }) - .subscribe(); // 必须订阅,否则不会执行 + .subscribe(); // 发送流给客户端 return session.send(sink.asFlux().map(session::textMessage)) @@ -100,7 +100,6 @@ public class MyWebSocketHandler implements WebSocketHandler { * @return 是否发送成功 */ public static boolean sendMessageToClient(String qrContent, String message) { - // 先清理超时连接 cleanupTimeoutConnections(); Sinks.Many sink = qrContentToSink.get(qrContent); @@ -199,4 +198,4 @@ public class MyWebSocketHandler implements WebSocketHandler { cleanupTimeoutConnections(); return qrContentToSink.size(); } -} \ No newline at end of file +} diff --git a/gym-manage-api/gym-checkIn/src/test/java/cn/novalon/gym/manage/checkin/CheckInModuleTest.java b/gym-manage-api/gym-checkIn/src/test/java/cn/novalon/gym/manage/checkin/CheckInModuleTest.java index 27fb944..d73ab47 100644 --- a/gym-manage-api/gym-checkIn/src/test/java/cn/novalon/gym/manage/checkin/CheckInModuleTest.java +++ b/gym-manage-api/gym-checkIn/src/test/java/cn/novalon/gym/manage/checkin/CheckInModuleTest.java @@ -25,7 +25,6 @@ import reactor.test.StepVerifier; import java.time.LocalDate; import java.time.LocalDateTime; -import java.time.LocalTime; import java.util.HashMap; import java.util.Map; 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/manage-common/src/main/java/cn/novalon/gym/manage/common/constant/RedisKeyConstants.java b/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/constant/RedisKeyConstants.java index 46d7c61..b32601e 100644 --- a/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/constant/RedisKeyConstants.java +++ b/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/constant/RedisKeyConstants.java @@ -61,6 +61,7 @@ public final class RedisKeyConstants { * appType: miniapp(小程序), mp(公众号) */ public static final String WECHAT_ACCESS_TOKEN = "wechat:access_token:"; +<<<<<<< HEAD // ==================== 认证模块 ==================== @@ -70,4 +71,6 @@ public final class RedisKeyConstants { * 用途:存储登录/注册等场景的短信验证码 */ public static final String SMS_CODE = "sms:code:"; +======= +>>>>>>> 08cf82a (签到模块) } \ No newline at end of file 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 Date: Tue, 9 Jun 2026 09:09:12 +0800 Subject: [PATCH 3/6] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E7=AD=BE?= =?UTF-8?q?=E5=88=B0=E6=A8=A1=E5=9D=97=E5=92=8C=E6=95=B0=E6=8D=AE=E5=BA=93?= =?UTF-8?q?=E8=A1=A8=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增gym-checkIn签到模块 - 创建sign_in_record签到记录表(V13迁移脚本) - 添加签到相关路由配置 - 更新应用配置以支持签到模块 - 添加Redis密钥常量 --- .../manage/checkIn/entity/SignInRecord.java | 4 + .../checkIn/handler/CheckInHandler.java | 25 +++++ .../checkIn/service/ICheckInService.java | 4 + .../service/impl/CheckServiceImpl.java | 84 ++++++++++++++- .../checkIn/websocket/MyWebSocketHandler.java | 79 ++++++++++++++ .../gym/manage/checkin/CheckInModuleTest.java | 4 + .../V13__Create_SignInRecord_table.sql | 99 ++++++++++++++++++ .../src/main/resources/application.yml | Bin 4207 -> 8754 bytes 8 files changed, 298 insertions(+), 1 deletion(-) create mode 100644 gym-manage-api/manage-db/src/main/resources/db/migration/V13__Create_SignInRecord_table.sql 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 63cf034..59a9e0e 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,8 @@ public class SignInRecord { public void restore() { this.isDelete = false; } +<<<<<<< HEAD } +======= +} +>>>>>>> f52451d (feat: 添加签到模块和数据库表结构) 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 a8a8748..76b7427 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 @@ -1,7 +1,12 @@ package cn.novalon.gym.manage.checkIn.handler; import cn.novalon.gym.manage.checkIn.service.impl.CheckServiceImpl; +<<<<<<< HEAD import cn.novalon.gym.manage.checkIn.websocket.MyWebSocketHandler; +======= +import cn.novalon.gym.manage.checkIn.vo.SignInRecordVO; +import cn.novalon.gym.manage.checkIn.vo.SignInStatsVO; +>>>>>>> f52451d (feat: 添加签到模块和数据库表结构) import cn.novalon.gym.manage.sys.util.AuthUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -12,6 +17,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; @@ -82,7 +88,12 @@ public class CheckInHandler { * @return */ public Mono getSignInRecords(ServerRequest request) { +<<<<<<< HEAD Long memberId = authUtil.getMemberIdOrThrow(request); +======= + Long memberId = 1L; +// authUtil.getMemberIdOrThrow(request); +>>>>>>> f52451d (feat: 添加签到模块和数据库表结构) String startDateStr = request.queryParam("startDate").orElse(null); String endDateStr = request.queryParam("endDate").orElse(null); @@ -128,7 +139,12 @@ public class CheckInHandler { * @return */ public Mono getSignInStatistics(ServerRequest request) { +<<<<<<< HEAD Long memberId = authUtil.getMemberIdOrThrow(request); +======= + Long memberId = 1L; +// authUtil.getMemberIdOrThrow(request); +>>>>>>> f52451d (feat: 添加签到模块和数据库表结构) String startDateStr = request.queryParam("startDate").orElse(null); String endDateStr = request.queryParam("endDate").orElse(null); @@ -153,7 +169,12 @@ public class CheckInHandler { * @return */ public Mono exportSignInRecords(ServerRequest request) { +<<<<<<< HEAD Long memberId = authUtil.getMemberIdOrThrow(request); +======= + Long memberId = 1L; +// authUtil.getMemberIdOrThrow(request); +>>>>>>> f52451d (feat: 添加签到模块和数据库表结构) String startDateStr = request.queryParam("startDate").orElse(null); String endDateStr = request.queryParam("endDate").orElse(null); @@ -191,4 +212,8 @@ public class CheckInHandler { .contentType(MediaType.APPLICATION_JSON) .bodyValue(Map.of("code", 200, "message", "success", "data", stats))); } +<<<<<<< HEAD } +======= +} +>>>>>>> f52451d (feat: 添加签到模块和数据库表结构) 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 e162423..e78174d 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,8 @@ public interface ICheckInService { * @return 签到统计VO */ Mono getDailySignInStats(LocalDate date); +<<<<<<< HEAD } +======= +} +>>>>>>> f52451d (feat: 添加签到模块和数据库表结构) 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 4d9285f..a0279ea 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,8 +2,11 @@ package cn.novalon.gym.manage.checkIn.service.impl; import cn.hutool.core.bean.BeanUtil; import cn.hutool.json.JSONUtil; +<<<<<<< HEAD import cn.hutool.extra.qrcode.QrCodeUtil; import cn.hutool.extra.qrcode.QrConfig; +======= +>>>>>>> f52451d (feat: 添加签到模块和数据库表结构) import cn.novalon.gym.manage.checkIn.config.QRCodeConfig; import cn.novalon.gym.manage.checkIn.constant.QRRedisKey; import cn.novalon.gym.manage.checkIn.entity.SignInRecord; @@ -21,6 +24,11 @@ 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; +<<<<<<< HEAD +======= +import cn.hutool.extra.qrcode.QrCodeUtil; +import cn.hutool.extra.qrcode.QrConfig; +>>>>>>> f52451d (feat: 添加签到模块和数据库表结构) import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -84,6 +92,10 @@ public class CheckServiceImpl implements ICheckInService { public Mono checkIn(Long memberId, String qrContent) { String key = RedisKeyConstants.QRCODE_USER_DAILY + memberId + LocalDate.now(); +<<<<<<< HEAD +======= + // 先检查当天是否已经签到(从数据库查询,作为重复签到的额外保障) +>>>>>>> f52451d (feat: 添加签到模块和数据库表结构) return checkTodayAlreadySignedIn(memberId) .flatMap(existingRecord -> { String checkInTime = existingRecord.getSignInTime().format(DATE_FORMATTER); @@ -125,6 +137,14 @@ public class CheckServiceImpl implements ICheckInService { }); } +<<<<<<< HEAD +======= + /** + * 检查会员当天是否已经签到 + * @param memberId 会员ID + * @return 如果已签到返回签到记录,否则返回空 + */ +>>>>>>> f52451d (feat: 添加签到模块和数据库表结构) private Mono checkTodayAlreadySignedIn(Long memberId) { LocalDateTime startOfDay = LocalDate.now().atStartOfDay(); LocalDateTime endOfDay = LocalDate.now().atTime(LocalTime.MAX); @@ -132,9 +152,19 @@ public class CheckServiceImpl implements ICheckInService { return signInRecordRepository.findByMemberIdAndDate(memberId, startOfDay, endOfDay); } +<<<<<<< HEAD private Mono processCheckIn(Long memberId, Long memberCardRecordId, Map redisMap, String qrContent) { LocalDateTime now = LocalDateTime.now(); +======= + /** + * 处理签到逻辑 + */ + private Mono processCheckIn(Long memberId, Long memberCardRecordId, Map redisMap, String qrContent) { + LocalDateTime now = LocalDateTime.now(); + + // 发送实时进度通知 +>>>>>>> f52451d (feat: 添加签到模块和数据库表结构) MyWebSocketHandler.sendProgress(qrContent, "VALIDATE_CARD", "正在验证会员卡..."); return memberCardRecordRepository.findById(memberCardRecordId) @@ -153,8 +183,15 @@ public class CheckServiceImpl implements ICheckInService { return Mono.error(new RuntimeException("会员卡已过期")); } +<<<<<<< HEAD MyWebSocketHandler.sendProgress(qrContent, "VALIDATE_BOOKING", "会员卡验证通过,正在检查预约信息..."); +======= + // 发送实时进度通知 + MyWebSocketHandler.sendProgress(qrContent, "VALIDATE_BOOKING", "会员卡验证通过,正在检查预约信息..."); + + // 检查是否有需要签到的团课预约 +>>>>>>> f52451d (feat: 添加签到模块和数据库表结构) return validateBooking(memberId, now) .then(memberCardRepository.findByMemberCardIdAndDeletedAtIsNull(cardRecord.getMemberCardId()) .switchIfEmpty(Mono.defer(() -> { @@ -162,6 +199,10 @@ public class CheckServiceImpl implements ICheckInService { return Mono.error(new RuntimeException("会员卡类型不存在")); })) .flatMap(card -> { +<<<<<<< HEAD +======= + // 发送实时进度通知 +>>>>>>> f52451d (feat: 添加签到模块和数据库表结构) MyWebSocketHandler.sendProgress(qrContent, "DEDUCT_USAGE", "正在扣减会员卡次数..."); return deductCardUsage(cardRecord, card) @@ -182,6 +223,12 @@ public class CheckServiceImpl implements ICheckInService { }); } +<<<<<<< HEAD +======= + /** + * 验证预约信息 + */ +>>>>>>> f52451d (feat: 添加签到模块和数据库表结构) private Mono validateBooking(Long memberId, LocalDateTime now) { return groupCourseBookingService.getBookingsByMemberId(memberId) .filter(booking -> { @@ -213,6 +260,12 @@ public class CheckServiceImpl implements ICheckInService { }); } +<<<<<<< HEAD +======= + /** + * 扣减会员卡使用次数/金额 + */ +>>>>>>> f52451d (feat: 添加签到模块和数据库表结构) private Mono deductCardUsage(MemberCardRecord record, MemberCard card) { MemberCardType cardType = MemberCardType.valueOf(card.getMemberCardType()); LocalDateTime now = LocalDateTime.now(); @@ -248,6 +301,12 @@ public class CheckServiceImpl implements ICheckInService { } } +<<<<<<< HEAD +======= + /** + * 保存签到记录 + */ +>>>>>>> f52451d (feat: 添加签到模块和数据库表结构) private Mono saveSignInRecord(Long memberId, Long memberCardRecordId, Long memberCardId) { SignInRecord record = SignInRecord.builder() .memberId(memberId) @@ -262,6 +321,12 @@ public class CheckServiceImpl implements ICheckInService { return signInRecordRepository.save(record).then(); } +<<<<<<< HEAD +======= + /** + * 构建成功响应 + */ +>>>>>>> f52451d (feat: 添加签到模块和数据库表结构) private String buildSuccessResponse(LocalDateTime dateTime) { Map res = new HashMap<>(); res.put("message", "签到成功"); @@ -269,6 +334,12 @@ public class CheckServiceImpl implements ICheckInService { return JSONUtil.toJsonStr(res); } +<<<<<<< HEAD +======= + /** + * 查找会员的有效会员卡(优先选择有效期最早到期的) + */ +>>>>>>> f52451d (feat: 添加签到模块和数据库表结构) private Mono findValidMemberCard(Long memberId) { return memberCardRecordRepository.findActiveCardsByMemberId(memberId) .filter(record -> { @@ -286,6 +357,11 @@ public class CheckServiceImpl implements ICheckInService { .next(); } +<<<<<<< HEAD +======= + // ==================== 签到记录管理功能 ==================== + +>>>>>>> f52451d (feat: 添加签到模块和数据库表结构) @Override public Flux getSignInRecords(Long memberId, LocalDate startTime, LocalDate endTime) { LocalDateTime start = startTime.atStartOfDay(); @@ -383,6 +459,12 @@ public class CheckServiceImpl implements ICheckInService { ); } +<<<<<<< HEAD +======= + /** + * 转换实体到VO + */ +>>>>>>> f52451d (feat: 添加签到模块和数据库表结构) private SignInRecordVO convertToVO(SignInRecord record) { SignInRecordVO vo = new SignInRecordVO(); vo.setId(record.getId()); @@ -403,4 +485,4 @@ public class CheckServiceImpl implements ICheckInService { if (now.isAfter(endOfDay)) return 1; return ChronoUnit.SECONDS.between(now, endOfDay); } -} +} \ No newline at end of file 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 7f705c8..d3a6a64 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 @@ -77,7 +77,11 @@ public class MyWebSocketHandler implements WebSocketHandler { .doOnError(e -> { log.error("接收消息出错,sessionId={}", sessionId, e); }) +<<<<<<< HEAD .subscribe(); +======= + .subscribe(); // 必须订阅,否则不会执行 +>>>>>>> f52451d (feat: 添加签到模块和数据库表结构) // 发送流给客户端 return session.send(sink.asFlux().map(session::textMessage)) @@ -100,6 +104,10 @@ public class MyWebSocketHandler implements WebSocketHandler { * @return 是否发送成功 */ public static boolean sendMessageToClient(String qrContent, String message) { +<<<<<<< HEAD +======= + // 先清理超时连接 +>>>>>>> f52451d (feat: 添加签到模块和数据库表结构) cleanupTimeoutConnections(); Sinks.Many sink = qrContentToSink.get(qrContent); @@ -129,6 +137,77 @@ public class MyWebSocketHandler implements WebSocketHandler { String progressMessage = buildMessage("PROGRESS", message); sendMessageToClient(qrContent, progressMessage); log.debug("发送进度消息: qrContent={}, step={}, message={}", qrContent, step, message); +<<<<<<< HEAD +======= + } + + /** + * 发送签到成功消息 + * + * @param qrContent 二维码内容 + * @param memberId 会员ID + * @param signInTime 签到时间 + */ + public static void sendSuccess(String qrContent, Long memberId, String signInTime) { + String successMessage = buildMessage("SUCCESS", "签到成功!欢迎光临\n会员ID: " + memberId + "\n签到时间: " + signInTime); + sendMessageToClient(qrContent, successMessage); + log.info("发送成功消息: qrContent={}, memberId={}", qrContent, memberId); + } + + /** + * 发送签到失败消息 + * + * @param qrContent 二维码内容 + * @param reason 失败原因 + */ + public static void sendFailure(String qrContent, String reason) { + String failureMessage = buildMessage("FAILURE", "签到失败:" + reason); + sendMessageToClient(qrContent, failureMessage); + log.warn("发送失败消息: qrContent={}, reason={}", qrContent, reason); + } + + /** + * 构建标准消息格式 + * + * @param type 消息类型 + * @param content 消息内容 + * @return 格式化后的消息字符串 + */ + private static String buildMessage(String type, String content) { + return JSONUtil.toJsonStr(Map.of( + "type", type, + "content", content, + "timestamp", System.currentTimeMillis() + )); + } + + /** + * 清理超时连接 + */ + private static void cleanupTimeoutConnections() { + LocalDateTime now = LocalDateTime.now(); + qrContentToCreateTime.entrySet().removeIf(entry -> { + LocalDateTime createTime = entry.getValue(); + long secondsDiff = java.time.Duration.between(createTime, now).getSeconds(); + if (secondsDiff > TIMEOUT_SECONDS) { + String qrContent = entry.getKey(); + qrContentToSink.remove(qrContent); + log.debug("清理超时连接: qrContent={}, 超时时间={}秒", qrContent, secondsDiff); + return true; + } + return false; + }); + } + + /** + * 获取当前连接数 + * + * @return 连接数 + */ + public static int getConnectionCount() { + cleanupTimeoutConnections(); + return qrContentToSink.size(); +>>>>>>> f52451d (feat: 添加签到模块和数据库表结构) } /** diff --git a/gym-manage-api/gym-checkIn/src/test/java/cn/novalon/gym/manage/checkin/CheckInModuleTest.java b/gym-manage-api/gym-checkIn/src/test/java/cn/novalon/gym/manage/checkin/CheckInModuleTest.java index d73ab47..8f35681 100644 --- a/gym-manage-api/gym-checkIn/src/test/java/cn/novalon/gym/manage/checkin/CheckInModuleTest.java +++ b/gym-manage-api/gym-checkIn/src/test/java/cn/novalon/gym/manage/checkin/CheckInModuleTest.java @@ -25,6 +25,10 @@ import reactor.test.StepVerifier; import java.time.LocalDate; import java.time.LocalDateTime; +<<<<<<< HEAD +======= +import java.time.LocalTime; +>>>>>>> f52451d (feat: 添加签到模块和数据库表结构) import java.util.HashMap; import java.util.Map; 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-gateway/src/main/resources/application.yml b/gym-manage-api/manage-gateway/src/main/resources/application.yml index 240c1f027f78b6e2dc98954b50f4e5feab146a9f..1d99616c7e76cc9fc3d862e5db3217c3c195c543 100644 GIT binary patch literal 8754 zcmcJVNmCm~7>4_ts{98IIVA{VW4ut60|*&ovqcDF`$B;bf*2%%BnV06uP1q*{@irW zCdr~)Q^Sm=yT5(y=D&XjuH$apt^d2?esn*$IbGMTudhS5qW4#B(cg@54>Wq?E?m!b zH6B*9wc=Dz+Q~N{UAbfTr>pt-HT~-72KK$a4fORdeF4i;VcB+T5AcqK<=S0pMq3bu zdiI4e;sbV1BS2(*R}ilR6MTH{>U&LpS=(`Udfq?4ZVPVTjYMx2UUqhN4170!74QbR zm5kPOUwD|tb)>?036f%NxJF)*(0?vEhvIm}J<$by@O`Q8ePKfrribSbTm%?A(MU(Q zgS5&iWF}PZd8wScUxf+UF7*6aSDr%jd8Qd7(J|D@GtJ};i>Z=8s*`t;`_Qdw{ZM># zq!;v<>NAfK>QV?zcOv}l2N+lmHWMvkGK7w>VsH7GNaI)|-BMnHbdf9TFZ6`xIk!{_ ziTUUW%0_w%v)4rXx;t_kdbV7{9qReX&AWZQH{A!f>FRnratCflBOi2cx-I=()89rV zCafNr;&VpThGw?}chBt#cB^uw$@7a=p6M#uc;K5S`bN`3QG*uZD)ZMXjg?6?z%bu0 zu3eU|Pg;xJwS^gtBlTxvF~JowKNa^q$pEe0ih8_!AgSU>BYguBkT3o0JnebwTlWx0 z@{o>ng+_^sJ-_y?ejf`TzL19l|C22%wez}%i)Y~@HR-Y^?$NHr*)j}ZK{q$T#Y!R| z{58ELU`IQ$t2cT!A1nemzk9EYnn!MJFENYvKe@l%JK1SVtFhAhSBNW~I^z3*wDCr` zPi55~WECG3y?>FsKf7hw_G|Z>wEJFBe@ii|Cn}Z%eb2oVm4{mSyL;=NOA8zBg&?eI z&Or9{R5UcS{$BGJ<>4Q->JLGGDQdSh|DOjbcLbT3WqgOcFy~Ub8(P2TImhcZWfeQ3 z`;{<07xZvNm|w&j(o5xxE|AN;u$AW@G;l5H4|Ls0TQ}kd4WyL37gwJo$%ZIElAlCZ zpy){NtCI1)Kr{00k;`k^kk z(v8KY5-FEP({oOg-I6VA>&CXHlR#G-_k9*B<2=$qcEkc|vIt@V`ilA}r(i?$?8^3y z`$*f0uZFPFqBc^DM7cBJA~Kyn0`s2N0$ze8JQcJeUOa%TLqsJ)o$6WUfzxn`6>baX zt|CcG81sByNQPOxVqq!g-!AJ-`C6*~AmbvZDW2ISZ1}#vWDU@y!>zn5eq&hIbXh^tD%$df*%>*FxBA2t)^Tpp43cUo6 zt-(7qeb!s>`fO~y69#fg$Hy4!wo*SK$5U;`>{iC(p>UB|8a~TVgIj)Y={r2qYf;gT zJT!8}6VDkr73lM&GmtRR&PJ_Qqa$O5^@&}th4OK1k#Tq;E2Xon7eDdep)J5PtzPI0 z9e5Rv>eZ~V-u3cp_x2@&w<|ixBG=LmPdbc_*3f~P6(D=dYV@0QUSM{wiA#2Sd}d@i&%-5<^Eibxh^`I;_|@T#gQ^S^Hx*e-FZ@a2$u=FTPA`N?K zMG3Mu1qEHRYHvcLyg)WIR>*=%1w`#Jel6y-8XO{mnT2SazGF6ZRx6-V|-wEM->vV2{aqoT4RT z8>uZ5KWu+&_Be+uTC&*C=T7b`VIrJFPfL=7WvP=wY`%-$R5MJc$gPUvPS15u2U$ zSVJ;y^&*tn&UITb=k=>69nEWPUpavKpUP|gLE^TPhz94i?p&j`!-+NFpUmXTwJ_V4 zlq+ZpyRhtmSjCLqi*;{UmzPwOA<$C3vLlSL<6)Hvav*zBBUuDN8g}AS+@I#8g#8@T zYM=y&;ex8{j#}6o%g>ILY>_^oQM+=o*7Mk)Ee|2!ENYcXpInW;vQYLtx7cMFw4gOs z!{e-2i*U;EX05Eq;dIhu)@_M=rdyr0=viq9kth1WERd?LHm}FD=F4kxI=+iKdOXZ* zFO=239j$^qED%<1CIiP=20g-7NUi^T&{v(lhM^!6-X>UXYF2d2`J3 z_%Bs8a%a!?M3HgaSpi%VVeKT8J$w2Bb{~L1T&BCbD)9s`1q)r!Y|FU(UC`apSqZ!M zK(QED6|*c?=4mME;al-7ijHMPE1s{Dx7V2{Rh9cve#&Xd@>o6xnr$D0Qxn^-uAa&4 z>8zKT=~X4eInRBy6!7v8t1P-l>aYcJMgEkbEnC6z(mlL|Oa+1sud4!HQ`fqYkv4lw eU9C>7oAUy*Vmq<=qVf3u5DS`bo@)8C2>X9J1O>DJ literal 4207 zcmb7HTX)(x5Ps)Z_@Pf-OvtTGea=1@aEKc)tZhQl7mlz6sPV0m45dB$-*+VW2IR8M zLr<~h^37=Gn-Rlwi>cAlv@E6E&~Can-Bycblq75MHAq7oi2rC>jDG4Q(PJE^bgwh= z3me*bcie+Rlw}bK5GQG(WD^v_>3SdQF-p)H>ubdE4()}-D9uA9ReV%GG>xV?$E^IN zYa}$lsZ<9C5`(~zQ(|Zv&NJieEJ_0uZBoV!0De`8WE6)2Pi;-pbZv_G=I=9yDc_-{6E7(G_aCrq10Z3WPvP$uAlG@j2#H#H{`hT~{8& z7Ye5YAZnMDh^8@)c+SMvo;g?yP2ZZEPi@a~AMC!hm^tQyX^+g_$ZF`s7$Ah8GPL~m zbmIAqGeG#9u2yAM6_OR9jQ5HYxWt$>G~`WJhu6o4D-`gQ8rsEy$`UagPLe6bTaxDN z0Yy0!zzRhS7jk9J1~|+!>|&Os3Bz|7g_u^Rj#H%4+)on@cc5Ddlp_>oB`(rM;hNWb zLvtjC+W;lQ>8`_88U+4b9v-%)q;3P0+7q=`NK0H63|F!*g{q*IZ z`2OeUCUGw5^fRAde80O|4L^nE4eOl;*7e@${(8Lp@$h*!zWNu;;-47-zoOg=)7InE zc1_=&IL6JVtFHJHg3oxbG7moKKC2r9`VBF*ODWX20(WmcEi9++K23#{#nkk?`NSO< z!i?w*sEew-06~J55k63MC%yy#Rz!Ua3{G>bv^9-D2I&?>bsdCg4;54UuGDSEhiWrN zi-Gy%8J7@8OXOH+MkFROuLl4iZfh~J$F~21>vfbaYq!#XzDlW{VMpt)+2&2%spZ;}fpN~t$T^8I5Q`q>^^Kjn^zE@VnfZpiHAq)hCh8r; z_OwP@di)b3k|qJx0~Bmlp+-OcZ8-x}GT4?XSQdBzzKXpVqG&o&rEeABK!kYts@Pnb{$rV32A zHAx!3DPDskOw}hC#7j(-B*aNzMm9ALj}CsdOD317t{#)y~Qnz zI#C>=U5)FaHCdGI;GUxBU3G6XnZuopM#=#2iR#eWLdgklfvBwvW&lJm{6#@7z+u3L z9EW<6i@VU%l|;tGp0q*HN>4MK=whV9F#PblnWWz0&K%t?CR59i7(FPd5*`slPT!Fv zgi!fDdqLS7*#p}dF6Oo~fGGW^sb%yhGY8Ucde-1YOKN31hH+%6tZrpN)K#3v$MKy8 z1HE(<#tuQbR0)QJyp@Z&2?bN-_q;0;EcT;nd{*PGkwh5gt{9rf<4$%z+)JEs*^4JC zk^y|*8c%(ga4OUTyhi~lM~5J=%93-dHPIazFK)4V$#V#QUuYfL=nj+u8pe1u&w_0imk6{H9k~j&o z6!wW?m)+r|(wA#d$_kbj^F^#3L6nD3zXjDvf`fIHl7O{wvTXy!tzq$Q3$(Dx1-LEv z0EJ;5XKhf*#D65SYMR(FbwRL(R#9dHk3yjwm56{?c6x|85DruVir@)SuDTgbm(BM< zb#r2EDm<>?Nm6YBVhh>g1ha!BzKSq|o^6*Bo@n{Ph*wkt?Q2F-)p_zF-j2DA%dR;94 z6gosbYCDau9r3(S-5 Date: Wed, 24 Jun 2026 00:43:53 +0800 Subject: [PATCH 4/6] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=B8=A2=E5=A4=B1?= =?UTF-8?q?=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gym/manage/auth/dto/PhoneLoginDto.java | 13 +-- .../manage/auth/handler/PhoneAuthHandler.java | 5 +- .../service/impl/PhoneAuthServiceImpl.java | 78 +++++++++++++++--- .../manage/checkIn/entity/SignInRecord.java | 4 - .../checkIn/handler/CheckInHandler.java | 22 ----- .../checkIn/service/ICheckInService.java | 4 - .../service/impl/CheckServiceImpl.java | 49 ----------- .../checkIn/websocket/MyWebSocketHandler.java | 78 ------------------ .../gym/manage/checkin/CheckInModuleTest.java | 3 - gym-manage-api/gym-groupCourse/pom.xml | 6 ++ .../gym/manage/groupcourse/util/OSSUtil.java | 6 +- .../common/constant/RedisKeyConstants.java | 3 - .../src/main/resources/application.yml | Bin 8754 -> 4227 bytes .../gym/manage/sys/config/SecurityConfig.java | 10 +-- 14 files changed, 89 insertions(+), 192 deletions(-) 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..c3d0758 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 @@ -1,7 +1,6 @@ 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; @@ -9,7 +8,7 @@ import lombok.NoArgsConstructor; /** * 手机号一键登录请求DTO - * uniapp官方一键登录流程:前端通过云函数获取手机号,直接传给后端 + * uniapp官方一键登录流程:前端通过云函数获取access_token和openid传给后端换取手机号 */ @Data @Builder @@ -17,11 +16,13 @@ import lombok.NoArgsConstructor; @AllArgsConstructor public class PhoneLoginDto { - @NotBlank(message = "手机号不能为空") - @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确") - private String phone; + @NotBlank(message = "access_token不能为空") + private String accessToken; + + @NotBlank(message = "openid不能为空") + private String openid; private String nickname; private String avatar; -} \ No newline at end of file +} diff --git a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/handler/PhoneAuthHandler.java b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/handler/PhoneAuthHandler.java index af46968..562920a 100644 --- a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/handler/PhoneAuthHandler.java +++ b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/handler/PhoneAuthHandler.java @@ -2,6 +2,7 @@ package cn.novalon.gym.manage.auth.handler; import cn.novalon.gym.manage.auth.dto.PhoneCodeLoginDto; import cn.novalon.gym.manage.auth.dto.PhoneLoginDto; +import cn.novalon.gym.manage.auth.dto.SendCodeRequest; import cn.novalon.gym.manage.auth.service.PhoneAuthService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -38,8 +39,8 @@ public class PhoneAuthHandler { public Mono sendSmsCode(ServerRequest request) { log.info("收到发送短信验证码请求"); - return request.bodyToMono(PhoneLoginDto.class) - .flatMap(dto -> phoneAuthService.sendSmsCode(dto.getPhone())) + return request.bodyToMono(SendCodeRequest.class) + .flatMap(req -> phoneAuthService.sendSmsCode(req.getPhone())) .flatMap(success -> ServerResponse.ok() .contentType(MediaType.APPLICATION_JSON) .bodyValue(Map.of("success", success, "message", success ? "验证码发送成功" : "验证码发送失败"))); 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..468f5a1 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 @@ -1,5 +1,6 @@ package cn.novalon.gym.manage.auth.service.impl; +import cn.novalon.gym.manage.auth.config.SmsProperties; import cn.novalon.gym.manage.auth.dto.PhoneCodeLoginDto; import cn.novalon.gym.manage.auth.dto.PhoneLoginDto; import cn.novalon.gym.manage.auth.service.PhoneAuthService; @@ -15,6 +16,13 @@ import cn.novalon.gym.manage.member.util.AesUtil; import cn.novalon.gym.manage.member.util.EsSyncUtils; import cn.novalon.gym.manage.member.util.MemberNoGenerator; import cn.novalon.gym.manage.sys.security.JwtTokenProvider; +import com.aliyuncs.DefaultAcsClient; +import com.aliyuncs.IAcsClient; +import com.aliyuncs.dypnsapi.model.v20170525.GetMobileRequest; +import com.aliyuncs.dypnsapi.model.v20170525.GetMobileResponse; +import com.aliyuncs.exceptions.ClientException; +import com.aliyuncs.exceptions.ServerException; +import com.aliyuncs.profile.DefaultProfile; import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -35,6 +43,7 @@ public class PhoneAuthServiceImpl implements PhoneAuthService { private final EsSyncUtils esSyncUtils; private final JwtTokenProvider jwtTokenProvider; private final SmsService smsService; + private final SmsProperties smsProperties; private EsSyncUtils.EntitySyncer memberSyncer; @@ -45,19 +54,63 @@ public class PhoneAuthServiceImpl implements PhoneAuthService { @Override public Mono oneClickLogin(PhoneLoginDto request) { - log.info("手机号一键登录, phone: {}", request.getPhone()); + log.info("手机号一键登录请求, accessToken: {}, openid: {}", request.getAccessToken(), request.getOpenid()); - String encryptedPhone = encryptPhone(request.getPhone()); + return Mono.fromCallable(() -> getPhoneByToken(request.getAccessToken(), request.getOpenid())) + .flatMap(phone -> { + log.info("通过access_token获取手机号成功: {}", maskPhone(phone)); + String encryptedPhone = encryptPhone(phone); - return memberRepository.findByPhone(encryptedPhone) - .flatMap(existingMember -> { - log.info("手机号已注册,直接登录, memberId: {}", existingMember.getId()); - return doLogin(existingMember, false, request); + return memberRepository.findByPhone(encryptedPhone) + .flatMap(existingMember -> { + log.info("手机号已注册,直接登录, memberId: {}", existingMember.getId()); + return doLogin(existingMember, false, request); + }) + .switchIfEmpty(Mono.defer(() -> { + log.info("手机号未注册,创建新会员"); + return createNewMemberAndLogin(request, encryptedPhone); + })); }) - .switchIfEmpty(Mono.defer(() -> { - log.info("手机号未注册,创建新会员"); - return createNewMemberAndLogin(request, encryptedPhone); - })); + .onErrorResume(e -> { + log.error("一键登录失败", e); + return Mono.error(new SystemException(ErrorCode.AUTH_PHONE_ERROR, "手机号获取失败: " + e.getMessage())); + }); + } + + /** + * 通过access_token和openid获取手机号 + * 使用阿里云DYPNS API + */ + private String getPhoneByToken(String accessToken, String openid) throws Exception { + DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", smsProperties.getAccessKeyId(), smsProperties.getAccessKeySecret()); + IAcsClient client = new DefaultAcsClient(profile); + GetMobileRequest request = new GetMobileRequest(); + request.setAccessToken(accessToken); + + try { + GetMobileResponse response = client.getAcsResponse(request); + if ("OK".equals(response.getCode())) { + return response.getGetMobileResultDTO().getMobile(); + } + log.warn("DYPNS API返回错误: code={}, message={}", response.getCode(), response.getMessage()); + throw new SystemException(ErrorCode.AUTH_PHONE_ERROR, "手机号获取失败: " + response.getMessage()); + } catch (ServerException e) { + log.error("阿里云DYPNS API服务端异常", e); + throw new SystemException(ErrorCode.AUTH_PHONE_ERROR, "手机号获取失败"); + } catch (ClientException e) { + log.error("阿里云DYPNS API客户端异常", e); + throw new SystemException(ErrorCode.AUTH_PHONE_ERROR, "手机号获取失败"); + } + } + + /** + * 手机号脱敏显示 + */ + private String maskPhone(String phone) { + if (phone == null || phone.length() < 11) { + return phone; + } + return phone.substring(0, 3) + "****" + phone.substring(7); } @Override @@ -87,7 +140,6 @@ public class PhoneAuthServiceImpl implements PhoneAuthService { .switchIfEmpty(Mono.defer(() -> { log.info("手机号未注册,创建新会员"); PhoneLoginDto registerRequest = new PhoneLoginDto(); - registerRequest.setPhone(request.getPhone()); return createNewMemberAndLogin(registerRequest, encryptedPhone); })); }); @@ -153,7 +205,7 @@ public class PhoneAuthServiceImpl implements PhoneAuthService { vo.setNeedCompleteInfo(needCompleteInfo); vo.setNickname(member.getNickname()); vo.setAvatar(member.getAvatar()); - vo.setPhone(decryptPhone(member.getPhone())); + vo.setPhone(member.getPhone() != null ? decryptPhone(member.getPhone()) : null); return vo; } @@ -174,4 +226,4 @@ public class PhoneAuthServiceImpl implements PhoneAuthService { return null; } } -} \ 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 59a9e0e..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,8 +206,4 @@ public class SignInRecord { public void restore() { this.isDelete = false; } -<<<<<<< HEAD } -======= -} ->>>>>>> f52451d (feat: 添加签到模块和数据库表结构) 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 76b7427..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 @@ -1,12 +1,9 @@ package cn.novalon.gym.manage.checkIn.handler; import cn.novalon.gym.manage.checkIn.service.impl.CheckServiceImpl; -<<<<<<< HEAD import cn.novalon.gym.manage.checkIn.websocket.MyWebSocketHandler; -======= import cn.novalon.gym.manage.checkIn.vo.SignInRecordVO; import cn.novalon.gym.manage.checkIn.vo.SignInStatsVO; ->>>>>>> f52451d (feat: 添加签到模块和数据库表结构) import cn.novalon.gym.manage.sys.util.AuthUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -88,12 +85,7 @@ public class CheckInHandler { * @return */ public Mono getSignInRecords(ServerRequest request) { -<<<<<<< HEAD Long memberId = authUtil.getMemberIdOrThrow(request); -======= - Long memberId = 1L; -// authUtil.getMemberIdOrThrow(request); ->>>>>>> f52451d (feat: 添加签到模块和数据库表结构) String startDateStr = request.queryParam("startDate").orElse(null); String endDateStr = request.queryParam("endDate").orElse(null); @@ -139,12 +131,7 @@ public class CheckInHandler { * @return */ public Mono getSignInStatistics(ServerRequest request) { -<<<<<<< HEAD Long memberId = authUtil.getMemberIdOrThrow(request); -======= - Long memberId = 1L; -// authUtil.getMemberIdOrThrow(request); ->>>>>>> f52451d (feat: 添加签到模块和数据库表结构) String startDateStr = request.queryParam("startDate").orElse(null); String endDateStr = request.queryParam("endDate").orElse(null); @@ -169,12 +156,7 @@ public class CheckInHandler { * @return */ public Mono exportSignInRecords(ServerRequest request) { -<<<<<<< HEAD Long memberId = authUtil.getMemberIdOrThrow(request); -======= - Long memberId = 1L; -// authUtil.getMemberIdOrThrow(request); ->>>>>>> f52451d (feat: 添加签到模块和数据库表结构) String startDateStr = request.queryParam("startDate").orElse(null); String endDateStr = request.queryParam("endDate").orElse(null); @@ -212,8 +194,4 @@ public class CheckInHandler { .contentType(MediaType.APPLICATION_JSON) .bodyValue(Map.of("code", 200, "message", "success", "data", stats))); } -<<<<<<< HEAD } -======= -} ->>>>>>> f52451d (feat: 添加签到模块和数据库表结构) 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 e78174d..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,8 +78,4 @@ public interface ICheckInService { * @return 签到统计VO */ Mono getDailySignInStats(LocalDate date); -<<<<<<< HEAD } -======= -} ->>>>>>> f52451d (feat: 添加签到模块和数据库表结构) 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 a0279ea..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,11 +2,8 @@ package cn.novalon.gym.manage.checkIn.service.impl; import cn.hutool.core.bean.BeanUtil; import cn.hutool.json.JSONUtil; -<<<<<<< HEAD import cn.hutool.extra.qrcode.QrCodeUtil; import cn.hutool.extra.qrcode.QrConfig; -======= ->>>>>>> f52451d (feat: 添加签到模块和数据库表结构) import cn.novalon.gym.manage.checkIn.config.QRCodeConfig; import cn.novalon.gym.manage.checkIn.constant.QRRedisKey; import cn.novalon.gym.manage.checkIn.entity.SignInRecord; @@ -24,11 +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; -<<<<<<< HEAD -======= -import cn.hutool.extra.qrcode.QrCodeUtil; -import cn.hutool.extra.qrcode.QrConfig; ->>>>>>> f52451d (feat: 添加签到模块和数据库表结构) import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -92,10 +84,7 @@ public class CheckServiceImpl implements ICheckInService { public Mono checkIn(Long memberId, String qrContent) { String key = RedisKeyConstants.QRCODE_USER_DAILY + memberId + LocalDate.now(); -<<<<<<< HEAD -======= // 先检查当天是否已经签到(从数据库查询,作为重复签到的额外保障) ->>>>>>> f52451d (feat: 添加签到模块和数据库表结构) return checkTodayAlreadySignedIn(memberId) .flatMap(existingRecord -> { String checkInTime = existingRecord.getSignInTime().format(DATE_FORMATTER); @@ -137,14 +126,11 @@ public class CheckServiceImpl implements ICheckInService { }); } -<<<<<<< HEAD -======= /** * 检查会员当天是否已经签到 * @param memberId 会员ID * @return 如果已签到返回签到记录,否则返回空 */ ->>>>>>> f52451d (feat: 添加签到模块和数据库表结构) private Mono checkTodayAlreadySignedIn(Long memberId) { LocalDateTime startOfDay = LocalDate.now().atStartOfDay(); LocalDateTime endOfDay = LocalDate.now().atTime(LocalTime.MAX); @@ -152,11 +138,6 @@ public class CheckServiceImpl implements ICheckInService { return signInRecordRepository.findByMemberIdAndDate(memberId, startOfDay, endOfDay); } -<<<<<<< HEAD - private Mono processCheckIn(Long memberId, Long memberCardRecordId, Map redisMap, String qrContent) { - LocalDateTime now = LocalDateTime.now(); - -======= /** * 处理签到逻辑 */ @@ -164,7 +145,6 @@ public class CheckServiceImpl implements ICheckInService { LocalDateTime now = LocalDateTime.now(); // 发送实时进度通知 ->>>>>>> f52451d (feat: 添加签到模块和数据库表结构) MyWebSocketHandler.sendProgress(qrContent, "VALIDATE_CARD", "正在验证会员卡..."); return memberCardRecordRepository.findById(memberCardRecordId) @@ -183,15 +163,10 @@ public class CheckServiceImpl implements ICheckInService { return Mono.error(new RuntimeException("会员卡已过期")); } -<<<<<<< HEAD - MyWebSocketHandler.sendProgress(qrContent, "VALIDATE_BOOKING", "会员卡验证通过,正在检查预约信息..."); - -======= // 发送实时进度通知 MyWebSocketHandler.sendProgress(qrContent, "VALIDATE_BOOKING", "会员卡验证通过,正在检查预约信息..."); // 检查是否有需要签到的团课预约 ->>>>>>> f52451d (feat: 添加签到模块和数据库表结构) return validateBooking(memberId, now) .then(memberCardRepository.findByMemberCardIdAndDeletedAtIsNull(cardRecord.getMemberCardId()) .switchIfEmpty(Mono.defer(() -> { @@ -199,10 +174,7 @@ public class CheckServiceImpl implements ICheckInService { return Mono.error(new RuntimeException("会员卡类型不存在")); })) .flatMap(card -> { -<<<<<<< HEAD -======= // 发送实时进度通知 ->>>>>>> f52451d (feat: 添加签到模块和数据库表结构) MyWebSocketHandler.sendProgress(qrContent, "DEDUCT_USAGE", "正在扣减会员卡次数..."); return deductCardUsage(cardRecord, card) @@ -223,12 +195,9 @@ public class CheckServiceImpl implements ICheckInService { }); } -<<<<<<< HEAD -======= /** * 验证预约信息 */ ->>>>>>> f52451d (feat: 添加签到模块和数据库表结构) private Mono validateBooking(Long memberId, LocalDateTime now) { return groupCourseBookingService.getBookingsByMemberId(memberId) .filter(booking -> { @@ -260,12 +229,9 @@ public class CheckServiceImpl implements ICheckInService { }); } -<<<<<<< HEAD -======= /** * 扣减会员卡使用次数/金额 */ ->>>>>>> f52451d (feat: 添加签到模块和数据库表结构) private Mono deductCardUsage(MemberCardRecord record, MemberCard card) { MemberCardType cardType = MemberCardType.valueOf(card.getMemberCardType()); LocalDateTime now = LocalDateTime.now(); @@ -301,12 +267,9 @@ public class CheckServiceImpl implements ICheckInService { } } -<<<<<<< HEAD -======= /** * 保存签到记录 */ ->>>>>>> f52451d (feat: 添加签到模块和数据库表结构) private Mono saveSignInRecord(Long memberId, Long memberCardRecordId, Long memberCardId) { SignInRecord record = SignInRecord.builder() .memberId(memberId) @@ -321,12 +284,9 @@ public class CheckServiceImpl implements ICheckInService { return signInRecordRepository.save(record).then(); } -<<<<<<< HEAD -======= /** * 构建成功响应 */ ->>>>>>> f52451d (feat: 添加签到模块和数据库表结构) private String buildSuccessResponse(LocalDateTime dateTime) { Map res = new HashMap<>(); res.put("message", "签到成功"); @@ -334,12 +294,9 @@ public class CheckServiceImpl implements ICheckInService { return JSONUtil.toJsonStr(res); } -<<<<<<< HEAD -======= /** * 查找会员的有效会员卡(优先选择有效期最早到期的) */ ->>>>>>> f52451d (feat: 添加签到模块和数据库表结构) private Mono findValidMemberCard(Long memberId) { return memberCardRecordRepository.findActiveCardsByMemberId(memberId) .filter(record -> { @@ -357,11 +314,8 @@ public class CheckServiceImpl implements ICheckInService { .next(); } -<<<<<<< HEAD -======= // ==================== 签到记录管理功能 ==================== ->>>>>>> f52451d (feat: 添加签到模块和数据库表结构) @Override public Flux getSignInRecords(Long memberId, LocalDate startTime, LocalDate endTime) { LocalDateTime start = startTime.atStartOfDay(); @@ -459,12 +413,9 @@ public class CheckServiceImpl implements ICheckInService { ); } -<<<<<<< HEAD -======= /** * 转换实体到VO */ ->>>>>>> f52451d (feat: 添加签到模块和数据库表结构) private SignInRecordVO convertToVO(SignInRecord record) { SignInRecordVO vo = new SignInRecordVO(); vo.setId(record.getId()); 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 d3a6a64..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 @@ -77,11 +77,7 @@ public class MyWebSocketHandler implements WebSocketHandler { .doOnError(e -> { log.error("接收消息出错,sessionId={}", sessionId, e); }) -<<<<<<< HEAD - .subscribe(); -======= .subscribe(); // 必须订阅,否则不会执行 ->>>>>>> f52451d (feat: 添加签到模块和数据库表结构) // 发送流给客户端 return session.send(sink.asFlux().map(session::textMessage)) @@ -104,10 +100,7 @@ public class MyWebSocketHandler implements WebSocketHandler { * @return 是否发送成功 */ public static boolean sendMessageToClient(String qrContent, String message) { -<<<<<<< HEAD -======= // 先清理超时连接 ->>>>>>> f52451d (feat: 添加签到模块和数据库表结构) cleanupTimeoutConnections(); Sinks.Many sink = qrContentToSink.get(qrContent); @@ -137,77 +130,6 @@ public class MyWebSocketHandler implements WebSocketHandler { String progressMessage = buildMessage("PROGRESS", message); sendMessageToClient(qrContent, progressMessage); log.debug("发送进度消息: qrContent={}, step={}, message={}", qrContent, step, message); -<<<<<<< HEAD -======= - } - - /** - * 发送签到成功消息 - * - * @param qrContent 二维码内容 - * @param memberId 会员ID - * @param signInTime 签到时间 - */ - public static void sendSuccess(String qrContent, Long memberId, String signInTime) { - String successMessage = buildMessage("SUCCESS", "签到成功!欢迎光临\n会员ID: " + memberId + "\n签到时间: " + signInTime); - sendMessageToClient(qrContent, successMessage); - log.info("发送成功消息: qrContent={}, memberId={}", qrContent, memberId); - } - - /** - * 发送签到失败消息 - * - * @param qrContent 二维码内容 - * @param reason 失败原因 - */ - public static void sendFailure(String qrContent, String reason) { - String failureMessage = buildMessage("FAILURE", "签到失败:" + reason); - sendMessageToClient(qrContent, failureMessage); - log.warn("发送失败消息: qrContent={}, reason={}", qrContent, reason); - } - - /** - * 构建标准消息格式 - * - * @param type 消息类型 - * @param content 消息内容 - * @return 格式化后的消息字符串 - */ - private static String buildMessage(String type, String content) { - return JSONUtil.toJsonStr(Map.of( - "type", type, - "content", content, - "timestamp", System.currentTimeMillis() - )); - } - - /** - * 清理超时连接 - */ - private static void cleanupTimeoutConnections() { - LocalDateTime now = LocalDateTime.now(); - qrContentToCreateTime.entrySet().removeIf(entry -> { - LocalDateTime createTime = entry.getValue(); - long secondsDiff = java.time.Duration.between(createTime, now).getSeconds(); - if (secondsDiff > TIMEOUT_SECONDS) { - String qrContent = entry.getKey(); - qrContentToSink.remove(qrContent); - log.debug("清理超时连接: qrContent={}, 超时时间={}秒", qrContent, secondsDiff); - return true; - } - return false; - }); - } - - /** - * 获取当前连接数 - * - * @return 连接数 - */ - public static int getConnectionCount() { - cleanupTimeoutConnections(); - return qrContentToSink.size(); ->>>>>>> f52451d (feat: 添加签到模块和数据库表结构) } /** diff --git a/gym-manage-api/gym-checkIn/src/test/java/cn/novalon/gym/manage/checkin/CheckInModuleTest.java b/gym-manage-api/gym-checkIn/src/test/java/cn/novalon/gym/manage/checkin/CheckInModuleTest.java index 8f35681..27fb944 100644 --- a/gym-manage-api/gym-checkIn/src/test/java/cn/novalon/gym/manage/checkin/CheckInModuleTest.java +++ b/gym-manage-api/gym-checkIn/src/test/java/cn/novalon/gym/manage/checkin/CheckInModuleTest.java @@ -25,10 +25,7 @@ import reactor.test.StepVerifier; import java.time.LocalDate; import java.time.LocalDateTime; -<<<<<<< HEAD -======= import java.time.LocalTime; ->>>>>>> f52451d (feat: 添加签到模块和数据库表结构) import java.util.HashMap; import java.util.Map; diff --git a/gym-manage-api/gym-groupCourse/pom.xml b/gym-manage-api/gym-groupCourse/pom.xml index 2ef9fd2..12e0171 100644 --- a/gym-manage-api/gym-groupCourse/pom.xml +++ b/gym-manage-api/gym-groupCourse/pom.xml @@ -93,6 +93,12 @@ javase 3.5.3 + + + com.aliyun.oss + aliyun-sdk-oss + 3.17.4 + 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/manage-common/src/main/java/cn/novalon/gym/manage/common/constant/RedisKeyConstants.java b/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/constant/RedisKeyConstants.java index b32601e..46d7c61 100644 --- a/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/constant/RedisKeyConstants.java +++ b/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/constant/RedisKeyConstants.java @@ -61,7 +61,6 @@ public final class RedisKeyConstants { * appType: miniapp(小程序), mp(公众号) */ public static final String WECHAT_ACCESS_TOKEN = "wechat:access_token:"; -<<<<<<< HEAD // ==================== 认证模块 ==================== @@ -71,6 +70,4 @@ public final class RedisKeyConstants { * 用途:存储登录/注册等场景的短信验证码 */ public static final String SMS_CODE = "sms:code:"; -======= ->>>>>>> 08cf82a (签到模块) } \ No newline at end of file diff --git a/gym-manage-api/manage-gateway/src/main/resources/application.yml b/gym-manage-api/manage-gateway/src/main/resources/application.yml index 1d99616c7e76cc9fc3d862e5db3217c3c195c543..dfe6e2a4f20c406d9074e054a46f281150f04731 100644 GIT binary patch literal 4227 zcmb7ITX)(x5Ps)Z_@Pf-OvtTGea=1@aEKdji%m%S!V$IrHMZqSGL-i0f8UYh8<5L3 z4?T%Bmv2Th-;ByJ+hS%l3?rpXSjJ8Jrrl`pl#ygDzXmkKf&9fVV)WA_i5cUVvc1X4 zFKij-?O_KFQJO|1K!VUj%O)s>)Ac?!W0asZHrGht9okEa5zRs^m4DQK7zU%6z`Xd& zG)QQHQ>6|L6b6YSW5hBxLZsH&SwsUAZ73HO0KY0lQiel`r?O@krZGWc^Y2txFH` z3xiVv5RJ=H#4xx(BIELF$L=lqw(rdCr>5sjAKb39n2qcQ+a1`Qfm73oF+d1GrSJI7 z$=LI2XMpgTu2w}>HIfx!Ty*jixWt&(G}KL(hu6o4D-;OIEaT!pWr;Wr$H|1@Euk5I zKv4z-utE{Xxm=O69uBhiww<~V>XTgHdqU+4b9bGjg9;UutT7q>j@KRiF4|F!*g{q*IZ z`2OeMCK+9_$!9UY_VFFD8{Rn&oa_Cl{p<1a$HV8{@akVMi+`pB{K|6AO*@Yh zcWV3Ycx2stx@yZGDfo={I`iO@?bE73px+SZJIbKOHMo1{Y2l2z)2E5FvY6POHy=-X zmNX-K1L~q|FF=r>WrPour(<6M04uUSdX``r*4laVYDOd`Dz66sAZ}+daEGq{g6nlemz7&(z+6#grkD|`@YmG)gK-BO zD_hoio3ZVl3v7KwTX zu{){I79RfxSWx>@prCRD5@aR*2DZV;IssZ^ zs)%Z(>$&}r?a!vpN#RtDTgl|@JEBm>G#RSN;O)7hWu$Fr`bMnaPSn*jJ5bkq8RJlvHDi zjqGz8X?~~>bUTY%7=N;0WD8r@dAst6?%?jC=v{ShFrLFb4hGr)aE$KS%0j^tZ-J<+ z^kx7=vi?O*IKXSbkphQilF9oq=}IBva(CLGXl2qAC#D?pFd#qtt|zIpxU&bhi}A!6 zDU2SJSpkp8!DsGB5<;kApT3~%4cwkP>M!Q*s0UH{PZP)Lj%OoCyX`r>7cJ>E?ij|A zrJ~}s1zBrx79YoV8VvN;Q5ZV}Nvh^UM{~l0nN?q0bfM^ahwaJf282iA*QGZ;}t%Xz+EC=$$}%S9*8H?V5K zrq_*zUd&@y%cLYu!j!^}k?*%Vywv(?RiZp+emQgGDiTCl2=$xOpCUb2VHpW{6DQjy z(B2xB@uoxz>s^4Gk`GWAW^vjCrBwc+*w!%Q-f2qGwTzNRYj`vY?WjNm#IiF(EP!yJ z5>N!sl4AW$8C}-j4CT$qRVw$WsI#yoH{Mv+p2wS$tQ>I{t$*OBg?G5cSENH@;Mk`Zf@Y5t{IApY#HiV z+LEn^S&QKx8Rn90YSmNIe^0C3x^A{xU(`QeRC;S|rF~Q;a%a^Cc$8;?N*=0ztL^PI zP`WSc(2PUp4Ouz){|H2Qi}PnhkhBuLjI}f!SVqt3%=!u}W$Tts?+Q|c;i>c7Qg8oO J@$Aoq{{z#g{1*TK literal 8754 zcmcJVNmCm~7>4_ts{98IIVA{VW4ut60|*&ovqcDF`$B;bf*2%%BnV06uP1q*{@irW zCdr~)Q^Sm=yT5(y=D&XjuH$apt^d2?esn*$IbGMTudhS5qW4#B(cg@54>Wq?E?m!b zH6B*9wc=Dz+Q~N{UAbfTr>pt-HT~-72KK$a4fORdeF4i;VcB+T5AcqK<=S0pMq3bu zdiI4e;sbV1BS2(*R}ilR6MTH{>U&LpS=(`Udfq?4ZVPVTjYMx2UUqhN4170!74QbR zm5kPOUwD|tb)>?036f%NxJF)*(0?vEhvIm}J<$by@O`Q8ePKfrribSbTm%?A(MU(Q zgS5&iWF}PZd8wScUxf+UF7*6aSDr%jd8Qd7(J|D@GtJ};i>Z=8s*`t;`_Qdw{ZM># zq!;v<>NAfK>QV?zcOv}l2N+lmHWMvkGK7w>VsH7GNaI)|-BMnHbdf9TFZ6`xIk!{_ ziTUUW%0_w%v)4rXx;t_kdbV7{9qReX&AWZQH{A!f>FRnratCflBOi2cx-I=()89rV zCafNr;&VpThGw?}chBt#cB^uw$@7a=p6M#uc;K5S`bN`3QG*uZD)ZMXjg?6?z%bu0 zu3eU|Pg;xJwS^gtBlTxvF~JowKNa^q$pEe0ih8_!AgSU>BYguBkT3o0JnebwTlWx0 z@{o>ng+_^sJ-_y?ejf`TzL19l|C22%wez}%i)Y~@HR-Y^?$NHr*)j}ZK{q$T#Y!R| z{58ELU`IQ$t2cT!A1nemzk9EYnn!MJFENYvKe@l%JK1SVtFhAhSBNW~I^z3*wDCr` zPi55~WECG3y?>FsKf7hw_G|Z>wEJFBe@ii|Cn}Z%eb2oVm4{mSyL;=NOA8zBg&?eI z&Or9{R5UcS{$BGJ<>4Q->JLGGDQdSh|DOjbcLbT3WqgOcFy~Ub8(P2TImhcZWfeQ3 z`;{<07xZvNm|w&j(o5xxE|AN;u$AW@G;l5H4|Ls0TQ}kd4WyL37gwJo$%ZIElAlCZ zpy){NtCI1)Kr{00k;`k^kk z(v8KY5-FEP({oOg-I6VA>&CXHlR#G-_k9*B<2=$qcEkc|vIt@V`ilA}r(i?$?8^3y z`$*f0uZFPFqBc^DM7cBJA~Kyn0`s2N0$ze8JQcJeUOa%TLqsJ)o$6WUfzxn`6>baX zt|CcG81sByNQPOxVqq!g-!AJ-`C6*~AmbvZDW2ISZ1}#vWDU@y!>zn5eq&hIbXh^tD%$df*%>*FxBA2t)^Tpp43cUo6 zt-(7qeb!s>`fO~y69#fg$Hy4!wo*SK$5U;`>{iC(p>UB|8a~TVgIj)Y={r2qYf;gT zJT!8}6VDkr73lM&GmtRR&PJ_Qqa$O5^@&}th4OK1k#Tq;E2Xon7eDdep)J5PtzPI0 z9e5Rv>eZ~V-u3cp_x2@&w<|ixBG=LmPdbc_*3f~P6(D=dYV@0QUSM{wiA#2Sd}d@i&%-5<^Eibxh^`I;_|@T#gQ^S^Hx*e-FZ@a2$u=FTPA`N?K zMG3Mu1qEHRYHvcLyg)WIR>*=%1w`#Jel6y-8XO{mnT2SazGF6ZRx6-V|-wEM->vV2{aqoT4RT z8>uZ5KWu+&_Be+uTC&*C=T7b`VIrJFPfL=7WvP=wY`%-$R5MJc$gPUvPS15u2U$ zSVJ;y^&*tn&UITb=k=>69nEWPUpavKpUP|gLE^TPhz94i?p&j`!-+NFpUmXTwJ_V4 zlq+ZpyRhtmSjCLqi*;{UmzPwOA<$C3vLlSL<6)Hvav*zBBUuDN8g}AS+@I#8g#8@T zYM=y&;ex8{j#}6o%g>ILY>_^oQM+=o*7Mk)Ee|2!ENYcXpInW;vQYLtx7cMFw4gOs z!{e-2i*U;EX05Eq;dIhu)@_M=rdyr0=viq9kth1WERd?LHm}FD=F4kxI=+iKdOXZ* zFO=239j$^qED%<1CIiP=20g-7NUi^T&{v(lhM^!6-X>UXYF2d2`J3 z_%Bs8a%a!?M3HgaSpi%VVeKT8J$w2Bb{~L1T&BCbD)9s`1q)r!Y|FU(UC`apSqZ!M zK(QED6|*c?=4mME;al-7ijHMPE1s{Dx7V2{Rh9cve#&Xd@>o6xnr$D0Qxn^-uAa&4 z>8zKT=~X4eInRBy6!7v8t1P-l>aYcJMgEkbEnC6z(mlL|Oa+1sud4!HQ`fqYkv4lw eU9C>7oAUy*Vmq<=qVf3u5DS`bo@)8C2>X9J1O>DJ diff --git a/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/config/SecurityConfig.java b/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/config/SecurityConfig.java index 2a39369..7cec5cf 100644 --- a/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/config/SecurityConfig.java +++ b/gym-manage-api/manage-sys/src/main/java/cn/novalon/gym/manage/sys/config/SecurityConfig.java @@ -1,4 +1,4 @@ -package cn.novalon.gym.manage.sys.config; +package cn.novalon.gym.manage.sys.config; import cn.novalon.gym.manage.sys.audit.OperationLogWebFilter; import cn.novalon.gym.manage.sys.security.JwtAuthenticationFilter; @@ -21,7 +21,7 @@ public class SecurityConfig { private final OperationLogWebFilter operationLogWebFilter; private final Environment environment; - public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter, + public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter, OperationLogWebFilter operationLogWebFilter, Environment environment) { this.jwtAuthenticationFilter = jwtAuthenticationFilter; @@ -33,11 +33,11 @@ public class SecurityConfig { public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { String[] activeProfiles = environment.getActiveProfiles(); final boolean isDevOrTest; - + isDevOrTest = java.util.Arrays.stream(activeProfiles) .anyMatch(profile -> "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 From 2251f31524bb86a49889e52012c68804c92aff38 Mon Sep 17 00:00:00 2001 From: liwentao Date: Wed, 24 Jun 2026 04:59:32 +0800 Subject: [PATCH 5/6] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=94=AF=E4=BB=98?= =?UTF-8?q?=E5=AE=9D=E8=BF=9E=E6=8E=A5=E6=B2=99=E7=AE=B1=E6=A8=A1=E6=8B=9F?= =?UTF-8?q?=E6=94=AF=E4=BB=98=EF=BC=88=E6=9A=82=E4=B8=8D=E5=8F=AF=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GymGroupCourseApplicationTests.java | 13 - .../gym/manage/member/MemberModuleTest.java | 16 - gym-manage-api/gym-payment/pom.xml | 62 + .../payment/config/AlipayClientConfig.java | 32 + .../payment/config/AlipayProperties.java | 57 + .../gym/manage/payment/dto/ApiResponse.java | 52 + .../payment/dto/CreatePaymentRequest.java | 54 + .../manage/payment/dto/PaymentResponse.java | 54 + .../payment/handler/PaymentHandler.java | 140 ++ .../payment/service/PaymentService.java | 59 + .../service/impl/PaymentServiceImpl.java | 445 ++++++ gym-manage-api/info/6666000208197943.txt | 5 + gym-manage-api/log.text | 1333 ++++++++++++----- gym-manage-api/manage-app/pom.xml | 5 + .../gym/manage/app/config/SystemRouter.java | 17 +- .../src/main/resources/application-dev.yml | 16 +- .../src/main/resources/application-local.yml | 4 +- .../src/main/resources/application.yml | 16 +- .../src/main/resources/keys.properties | 6 +- .../src/main/resources/member-config.yml | 11 + .../src/test/resources/application-test.yml | 5 +- ...=> V10__Create_GroupCourse_Type_table.sql} | 0 .../V10__Insert_Booking_Test_Data.sql | 86 -- ...ql => V11__Create_Course_Label_tables.sql} | 0 .../V11__Insert_Cancel_Booking_Test_Data.sql | 116 -- ...2__Create_GroupCourse_Recommend_table.sql} | 0 .../V12__Insert_Time_Conflict_Test_Data.sql | 58 - .../migration/V13__Consolidated_Test_Data.sql | 342 +++++ .../V13__Create_SignInRecord_table.sql | 99 -- .../V14__Insert_DataCount_Test_Data.sql | 120 -- ...Insert_GroupCourse_Recommend_test_data.sql | 43 - .../V20__Add_deleted_at_to_member_user.sql | 9 + ...updated_at_to_member_card_transactions.sql | 9 + .../V22__Add_Business_Permissions.sql | 100 ++ .../db/migration/V23__Add_Coach_Test_Data.sql | 93 ++ .../V24__Add_Second_Coach_Test_Data.sql | 50 + .../V6__Create_GroupCourse_table.sql | 6 +- ...l => V7__Create_Member_And_MemberCard.sql} | 0 .../V7__Insert_GroupCourse_test_data.sql | 28 - ...d_GroupCourse_Booking_Snapshot_Fields.sql} | 0 ...ql => V9__Create_sign_in_record_table.sql} | 0 gym-manage-api/pom.xml | 1 + 42 files changed, 2571 insertions(+), 991 deletions(-) delete mode 100644 gym-manage-api/gym-groupCourse/src/test/java/cn/novalon/gym/manage/groupcourse/GymGroupCourseApplicationTests.java delete mode 100644 gym-manage-api/gym-member/src/test/java/cn/novalon/gym/manage/member/MemberModuleTest.java create mode 100644 gym-manage-api/gym-payment/pom.xml create mode 100644 gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/config/AlipayClientConfig.java create mode 100644 gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/config/AlipayProperties.java create mode 100644 gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/dto/ApiResponse.java create mode 100644 gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/dto/CreatePaymentRequest.java create mode 100644 gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/dto/PaymentResponse.java create mode 100644 gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/handler/PaymentHandler.java create mode 100644 gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/service/PaymentService.java create mode 100644 gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/service/impl/PaymentServiceImpl.java create mode 100644 gym-manage-api/info/6666000208197943.txt rename gym-manage-api/manage-db/src/main/resources/db/migration/{V15__Create_GroupCourse_Type_table.sql => V10__Create_GroupCourse_Type_table.sql} (100%) delete mode 100644 gym-manage-api/manage-db/src/main/resources/db/migration/V10__Insert_Booking_Test_Data.sql rename gym-manage-api/manage-db/src/main/resources/db/migration/{V16__Create_Course_Label_tables.sql => V11__Create_Course_Label_tables.sql} (100%) delete mode 100644 gym-manage-api/manage-db/src/main/resources/db/migration/V11__Insert_Cancel_Booking_Test_Data.sql rename gym-manage-api/manage-db/src/main/resources/db/migration/{V17__Create_GroupCourse_Recommend_table.sql => V12__Create_GroupCourse_Recommend_table.sql} (100%) delete mode 100644 gym-manage-api/manage-db/src/main/resources/db/migration/V12__Insert_Time_Conflict_Test_Data.sql create mode 100644 gym-manage-api/manage-db/src/main/resources/db/migration/V13__Consolidated_Test_Data.sql delete mode 100644 gym-manage-api/manage-db/src/main/resources/db/migration/V13__Create_SignInRecord_table.sql delete mode 100644 gym-manage-api/manage-db/src/main/resources/db/migration/V14__Insert_DataCount_Test_Data.sql delete mode 100644 gym-manage-api/manage-db/src/main/resources/db/migration/V18__Insert_GroupCourse_Recommend_test_data.sql create mode 100644 gym-manage-api/manage-db/src/main/resources/db/migration/V20__Add_deleted_at_to_member_user.sql create mode 100644 gym-manage-api/manage-db/src/main/resources/db/migration/V21__Add_updated_at_to_member_card_transactions.sql create mode 100644 gym-manage-api/manage-db/src/main/resources/db/migration/V22__Add_Business_Permissions.sql create mode 100644 gym-manage-api/manage-db/src/main/resources/db/migration/V23__Add_Coach_Test_Data.sql create mode 100644 gym-manage-api/manage-db/src/main/resources/db/migration/V24__Add_Second_Coach_Test_Data.sql rename gym-manage-api/manage-db/src/main/resources/db/migration/{V8__Create_Member_And_MemberCard.sql => V7__Create_Member_And_MemberCard.sql} (100%) delete mode 100644 gym-manage-api/manage-db/src/main/resources/db/migration/V7__Insert_GroupCourse_test_data.sql rename gym-manage-api/manage-db/src/main/resources/db/migration/{V9__Add_GroupCourse_Booking_Snapshot_Fields.sql => V8__Add_GroupCourse_Booking_Snapshot_Fields.sql} (100%) rename gym-manage-api/manage-db/src/main/resources/db/migration/{V13__Create_sign_in_record_table.sql => V9__Create_sign_in_record_table.sql} (100%) diff --git a/gym-manage-api/gym-groupCourse/src/test/java/cn/novalon/gym/manage/groupcourse/GymGroupCourseApplicationTests.java b/gym-manage-api/gym-groupCourse/src/test/java/cn/novalon/gym/manage/groupcourse/GymGroupCourseApplicationTests.java deleted file mode 100644 index b0d48f8..0000000 --- a/gym-manage-api/gym-groupCourse/src/test/java/cn/novalon/gym/manage/groupcourse/GymGroupCourseApplicationTests.java +++ /dev/null @@ -1,13 +0,0 @@ -package cn.novalon.gym.manage.groupcourse; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class GymGroupCourseApplicationTests { - - @Test - void contextLoads() { - } - -} diff --git a/gym-manage-api/gym-member/src/test/java/cn/novalon/gym/manage/member/MemberModuleTest.java b/gym-manage-api/gym-member/src/test/java/cn/novalon/gym/manage/member/MemberModuleTest.java deleted file mode 100644 index 799f865..0000000 --- a/gym-manage-api/gym-member/src/test/java/cn/novalon/gym/manage/member/MemberModuleTest.java +++ /dev/null @@ -1,16 +0,0 @@ -package cn.novalon.gym.manage.member; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -/** - * 会员模块测试类 - */ -@SpringBootTest -public class MemberModuleTest { - - @Test - void contextLoads() { - // 测试Spring上下文是否能正常加载 - } -} diff --git a/gym-manage-api/gym-payment/pom.xml b/gym-manage-api/gym-payment/pom.xml new file mode 100644 index 0000000..fc273a9 --- /dev/null +++ b/gym-manage-api/gym-payment/pom.xml @@ -0,0 +1,62 @@ + + + 4.0.0 + + + cn.novalon.gym.manage + gym-manage-api + 1.0.0 + + + cn.novalon.gym.manage + gym-payment + jar + + Gym Payment + 支付模块 - 支付宝App支付 + + + + + org.springframework.boot + spring-boot-starter-webflux + + + + + cn.novalon.gym.manage + manage-common + ${project.version} + + + + + com.alipay.sdk + alipay-sdk-java + 4.35.120.ALL + + + + + com.google.code.gson + gson + 2.10.1 + + + + + org.projectlombok + lombok + true + + + + + io.swagger.core.v3 + swagger-annotations + 2.2.19 + + + diff --git a/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/config/AlipayClientConfig.java b/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/config/AlipayClientConfig.java new file mode 100644 index 0000000..e0b747e --- /dev/null +++ b/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/config/AlipayClientConfig.java @@ -0,0 +1,32 @@ +package cn.novalon.gym.manage.payment.config; + +import com.alipay.api.AlipayClient; +import com.alipay.api.DefaultAlipayClient; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 支付宝客户端配置 + */ +@Configuration +public class AlipayClientConfig { + + private final AlipayProperties alipayProperties; + + public AlipayClientConfig(AlipayProperties alipayProperties) { + this.alipayProperties = alipayProperties; + } + + @Bean + public AlipayClient alipayClient() { + return new DefaultAlipayClient( + alipayProperties.getGateway(), + alipayProperties.getAppId(), + alipayProperties.getAppPrivateKey(), + "json", + alipayProperties.getCharset(), + alipayProperties.getAlipayPublicKey(), + alipayProperties.getSignType() + ); + } +} diff --git a/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/config/AlipayProperties.java b/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/config/AlipayProperties.java new file mode 100644 index 0000000..f78fd71 --- /dev/null +++ b/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/config/AlipayProperties.java @@ -0,0 +1,57 @@ +package cn.novalon.gym.manage.payment.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * 支付宝配置属性 + * 沙箱环境配置 + */ +@Data +@Component +@ConfigurationProperties(prefix = "alipay") +public class AlipayProperties { + + /** + * 应用ID + */ + private String appId; + + /** + * 应用私钥 (PKCS#8格式) + */ + private String appPrivateKey; + + /** + * 支付宝公钥 + */ + private String alipayPublicKey; + + /** + * 签名方式 + */ + private String signType = "RSA2"; + + /** + * 编码格式 + */ + private String charset = "UTF-8"; + + /** + * 网关地址 + * 沙箱环境: https://openapi-sandbox.dl.alipaydev.com/gateway.do + * 正式环境: https://openapi.alipay.com/gateway.do + */ + private String gateway; + + /** + * 异步通知地址 + */ + private String notifyUrl; + + /** + * 是否沙箱环境 + */ + private boolean sandbox = true; +} diff --git a/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/dto/ApiResponse.java b/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/dto/ApiResponse.java new file mode 100644 index 0000000..b01b20a --- /dev/null +++ b/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/dto/ApiResponse.java @@ -0,0 +1,52 @@ +package cn.novalon.gym.manage.payment.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 统一API响应 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ApiResponse { + + private int code; + private String message; + private T data; + + public static ApiResponse success(T data) { + return ApiResponse.builder() + .code(200) + .message("success") + .data(data) + .build(); + } + + public static ApiResponse success(String message, T data) { + return ApiResponse.builder() + .code(200) + .message(message) + .data(data) + .build(); + } + + public static ApiResponse error(int code, String message) { + return ApiResponse.builder() + .code(code) + .message(message) + .data(null) + .build(); + } + + public static ApiResponse error(String message) { + return ApiResponse.builder() + .code(500) + .message(message) + .data(null) + .build(); + } +} diff --git a/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/dto/CreatePaymentRequest.java b/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/dto/CreatePaymentRequest.java new file mode 100644 index 0000000..5b1347e --- /dev/null +++ b/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/dto/CreatePaymentRequest.java @@ -0,0 +1,54 @@ +package cn.novalon.gym.manage.payment.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 创建支付请求DTO + */ +@Data +@Schema(description = "创建支付请求") +public class CreatePaymentRequest { + + @Schema(description = "会员ID") + private Long memberId; + + @Schema(description = "订单类型: MEMBER_CARD-会员卡, GROUP_COURSE-团课, GOODS-商品") + private String orderType; + + @Schema(description = "商品描述") + private String goodsDesc; + + @Schema(description = "交易金额(单位:分)") + private String transAmt; + + @Schema(description = "交易类型: ALIPAY-支付宝, WECHAT-微信") + private String tradeType; + + @Schema(description = "备注") + private String remark; + + @Schema(description = "账户号") + private String acctId; + + @Schema(description = "过期时间(yyyyMMddHHmmss)") + private String timeExpire; + + @Schema(description = "延迟入账标识") + private String delayAcctFlag; + + @Schema(description = "手续费标识") + private Integer feeFlag; + + @Schema(description = "禁用支付方式") + private String limitPayType; + + @Schema(description = "渠道号") + private String channelNo; + + @Schema(description = "支付场景") + private String payScene; + + @Schema(description = "异步通知URL") + private String notifyUrl; +} diff --git a/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/dto/PaymentResponse.java b/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/dto/PaymentResponse.java new file mode 100644 index 0000000..50fd4cb --- /dev/null +++ b/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/dto/PaymentResponse.java @@ -0,0 +1,54 @@ +package cn.novalon.gym.manage.payment.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 支付响应DTO + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Schema(description = "支付响应") +public class PaymentResponse { + + @Schema(description = "订单ID") + private String orderId; + + @Schema(description = "支付状态: PENDING-待支付, SUCCESS-成功, FAIL-失败, CLOSED-关闭") + private String status; + + @Schema(description = "支付链接(App支付返回 scheme)") + private String payUrl; + + @Schema(description = "二维码链接 (扫码支付)") + private String qrCode; + + @Schema(description = "完整支付信息") + private String payInfo; + + @Schema(description = "H5支付链接") + private String h5PayUrl; + + @Schema(description = "错误码") + private String errorCode; + + @Schema(description = "错误消息") + private String errorMsg; + + @Schema(description = "交易金额(分)") + private String transAmt; + + @Schema(description = "商品描述") + private String goodsDesc; + + @Schema(description = "交易类型") + private String tradeType; + + @Schema(description = "支付成功时间") + private String payTime; +} 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 new file mode 100644 index 0000000..e402f0c --- /dev/null +++ b/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/handler/PaymentHandler.java @@ -0,0 +1,140 @@ +package cn.novalon.gym.manage.payment.handler; + +import cn.novalon.gym.manage.payment.dto.ApiResponse; +import cn.novalon.gym.manage.payment.dto.CreatePaymentRequest; +import cn.novalon.gym.manage.payment.dto.PaymentResponse; +import cn.novalon.gym.manage.payment.service.PaymentService; +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 reactor.core.publisher.MonoSink; + +import java.util.HashMap; +import java.util.Map; + +/** + * 支付处理器 + * 提供支付宝App支付相关接口 + */ +@Slf4j +@Component +@Tag(name = "支付管理", description = "支付宝App支付接口") +public class PaymentHandler { + + private final PaymentService paymentService; + + public PaymentHandler(PaymentService paymentService) { + this.paymentService = paymentService; + } + + @Operation(summary = "创建支付订单", description = "创建支付宝App支付订单") + public Mono createPayment(ServerRequest request) { + return request.bodyToMono(CreatePaymentRequest.class) + .flatMap(createReq -> { + log.info("[Payment] 创建支付订单: memberId={}, orderType={}, goodsDesc={}, transAmt={}", + createReq.getMemberId(), createReq.getOrderType(), createReq.getGoodsDesc(), createReq.getTransAmt()); + + PaymentResponse result = paymentService.alipayAppPay(createReq); + + if ("FAIL".equals(result.getStatus())) { + return ServerResponse.ok() + .bodyValue(ApiResponse.error(result.getErrorMsg())); + } + + return ServerResponse.ok() + .bodyValue(ApiResponse.success(result)); + }) + .onErrorResume(e -> { + log.error("[Payment] 创建支付订单异常", e); + return ServerResponse.ok() + .bodyValue(ApiResponse.error("创建支付订单失败: " + e.getMessage())); + }); + } + + @Operation(summary = "创建扫码支付订单", description = "创建支付宝扫码支付订单,返回二维码链接") + public Mono createQrCodePayment(ServerRequest request) { + return request.bodyToMono(CreatePaymentRequest.class) + .flatMap(createReq -> { + log.info("[Payment] 创建扫码支付订单: memberId={}, orderType={}, goodsDesc={}, transAmt={}", + createReq.getMemberId(), createReq.getOrderType(), createReq.getGoodsDesc(), createReq.getTransAmt()); + + PaymentResponse result = paymentService.alipayQrCodePay(createReq); + + if ("FAIL".equals(result.getStatus())) { + return ServerResponse.ok() + .bodyValue(ApiResponse.error(result.getErrorMsg())); + } + + return ServerResponse.ok() + .bodyValue(ApiResponse.success(result)); + }) + .onErrorResume(e -> { + log.error("[Payment] 创建扫码支付订单异常", e); + return ServerResponse.ok() + .bodyValue(ApiResponse.error("创建扫码支付订单失败: " + e.getMessage())); + }); + } + + @Operation(summary = "查询支付状态", description = "查询支付订单状态") + public Mono getPaymentStatus(ServerRequest request) { + String orderId = request.pathVariable("orderId"); + log.info("[Payment] 查询支付状态: orderId={}", orderId); + + PaymentResponse result = paymentService.getPaymentStatus(orderId); + return ServerResponse.ok() + .bodyValue(ApiResponse.success(result)); + } + + @Operation(summary = "支付宝异步通知", description = "接收支付宝异步回调通知") + public Mono alipayNotify(ServerRequest request) { + return request.bodyToMono(Map.class) + .flatMap(params -> { + log.info("[Payment] 收到支付宝异步通知: params={}", params); + + // 转换Map类型 + Map notifyParams = new HashMap<>(); + if (params instanceof Map) { + ((Map) params).forEach((key, value) -> + notifyParams.put(String.valueOf(key), String.valueOf(value))); + } + + String result = paymentService.handleAlipayNotify(notifyParams); + return ServerResponse.ok() + .bodyValue(result); + }) + .onErrorResume(e -> { + log.error("[Payment] 异步通知处理异常", e); + return ServerResponse.ok() + .bodyValue("fail"); + }); + } + + @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")); + log.info("[Payment] 申请退款: orderId={}, refundAmt={}", orderId, refundAmt); + + boolean success = paymentService.refund(orderId, refundAmt); + if (success) { + return ServerResponse.ok() + .bodyValue(ApiResponse.success("退款申请成功")); + } else { + return ServerResponse.ok() + .bodyValue(ApiResponse.error("退款申请失败")); + } + }) + .onErrorResume(e -> { + log.error("[Payment] 退款异常: orderId={}", orderId, e); + return ServerResponse.ok() + .bodyValue(ApiResponse.error("退款失败: " + e.getMessage())); + }); + } +} diff --git a/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/service/PaymentService.java b/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/service/PaymentService.java new file mode 100644 index 0000000..7170ce5 --- /dev/null +++ b/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/service/PaymentService.java @@ -0,0 +1,59 @@ +package cn.novalon.gym.manage.payment.service; + +import cn.novalon.gym.manage.payment.dto.CreatePaymentRequest; +import cn.novalon.gym.manage.payment.dto.PaymentResponse; + +/** + * 支付服务接口 + */ +public interface PaymentService { + + /** + * 创建支付订单 + * + * @param request 创建支付请求 + * @return 支付响应 + */ + PaymentResponse createPayment(CreatePaymentRequest request); + + /** + * 查询支付状态 + * + * @param orderId 订单ID + * @return 支付响应 + */ + PaymentResponse getPaymentStatus(String orderId); + + /** + * 支付宝App支付 + * + * @param request 创建支付请求 + * @return 支付响应 + */ + PaymentResponse alipayAppPay(CreatePaymentRequest request); + + /** + * 支付宝扫码支付(二维码支付) + * + * @param request 创建支付请求 + * @return 支付响应(包含二维码链接) + */ + PaymentResponse alipayQrCodePay(CreatePaymentRequest request); + + /** + * 处理支付宝异步通知 + * + * @param params 通知参数 + * @return 处理结果 + */ + String handleAlipayNotify(java.util.Map params); + + /** + * 申请退款 + * + * @param orderId 订单ID + * @param refundAmt 退款金额(分) + * @return 退款结果 + */ + boolean refund(String orderId, String refundAmt); +} 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 new file mode 100644 index 0000000..2731d27 --- /dev/null +++ b/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/service/impl/PaymentServiceImpl.java @@ -0,0 +1,445 @@ +package cn.novalon.gym.manage.payment.service.impl; + +import cn.novalon.gym.manage.common.util.RedisUtil; +import cn.novalon.gym.manage.payment.config.AlipayProperties; +import cn.novalon.gym.manage.payment.dto.CreatePaymentRequest; +import cn.novalon.gym.manage.payment.dto.PaymentResponse; +import cn.novalon.gym.manage.payment.service.PaymentService; +import com.alipay.api.AlipayApiException; +import com.alipay.api.AlipayClient; +import com.alipay.api.internal.util.AlipaySignature; +import com.alipay.api.request.AlipayTradeAppPayRequest; +import com.alipay.api.request.AlipayTradePrecreateRequest; +import com.alipay.api.request.AlipayTradeQueryRequest; +import com.alipay.api.request.AlipayTradeRefundRequest; +import com.alipay.api.response.AlipayTradeAppPayResponse; +import com.alipay.api.response.AlipayTradePrecreateResponse; +import com.alipay.api.response.AlipayTradeQueryResponse; +import com.alipay.api.response.AlipayTradeRefundResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 支付服务实现 + * App支付 (alipay.trade.app.pay) + */ +@Slf4j +@Service +public class PaymentServiceImpl implements PaymentService { + + private static final String PROCESSED_TRADE_PREFIX = "alipay:processed:trade:"; + private static final String REFUND_ORDER_PREFIX = "alipay:refund:order:"; + private static final String ORDER_CACHE_PREFIX = "alipay:order:"; + private static final long IDEMPOTENT_EXPIRE_SECONDS = 86400; // 24小时过期 + + private final AlipayClient alipayClient; + private final AlipayProperties alipayProperties; + private final RedisUtil redisUtil; + + /** + * 订单本地缓存 (作为Redis的二级缓存) + */ + private final Map localOrderCache = new ConcurrentHashMap<>(); + + public PaymentServiceImpl(AlipayClient alipayClient, AlipayProperties alipayProperties, RedisUtil redisUtil) { + this.alipayClient = alipayClient; + this.alipayProperties = alipayProperties; + this.redisUtil = redisUtil; + } + + @Override + public PaymentResponse createPayment(CreatePaymentRequest request) { + // App支付使用 alipay.trade.app.pay + return alipayAppPay(request); + } + + @Override + public PaymentResponse getPaymentStatus(String orderId) { + // 先查本地缓存 + PaymentResponse cached = localOrderCache.get(orderId); + if (cached != null && "SUCCESS".equals(cached.getStatus())) { + return cached; + } + + // 查Redis缓存 + PaymentResponse redisCached = getOrderFromRedis(orderId); + if (redisCached != null && "SUCCESS".equals(redisCached.getStatus())) { + localOrderCache.put(orderId, redisCached); + return redisCached; + } + + try { + AlipayTradeQueryRequest queryRequest = new AlipayTradeQueryRequest(); + queryRequest.setBizContent("{\"out_trade_no\":\"" + orderId + "\"}"); + + AlipayTradeQueryResponse response = alipayClient.execute(queryRequest); + if (response.isSuccess()) { + Date sendPayDate = response.getSendPayDate(); + String payTimeStr = sendPayDate != null ? new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(sendPayDate) : null; + + PaymentResponse result = PaymentResponse.builder() + .orderId(orderId) + .status(response.getTradeStatus()) + .payTime(payTimeStr) + .transAmt(response.getTotalAmount()) + .build(); + + if ("TRADE_SUCCESS".equals(response.getTradeStatus())) { + result.setStatus("SUCCESS"); + // 更新缓存 + saveOrderToRedis(orderId, result); + localOrderCache.put(orderId, result); + } + return result; + } + } catch (AlipayApiException e) { + log.error("查询支付状态失败: orderId={}", orderId, e); + } + + return PaymentResponse.builder() + .orderId(orderId) + .status(cached != null ? cached.getStatus() : (redisCached != null ? redisCached.getStatus() : "PENDING")) + .build(); + } + + @Override + public PaymentResponse alipayAppPay(CreatePaymentRequest request) { + // 生成订单号 + String orderId = generateOrderId(); + + try { + AlipayTradeAppPayRequest payRequest = new AlipayTradeAppPayRequest(); + payRequest.setNotifyUrl(alipayProperties.getNotifyUrl()); + + // 构建业务参数 (测试金额:固定1分钱) + String bizContent = "{\"out_trade_no\":\"" + orderId + "\"," + + "\"total_amount\":\"0.01\"," + + "\"subject\":\"" + request.getGoodsDesc() + "\"," + + "\"product_code\":\"QUICK_MSECURITY_PAY\"," + + "\"timeout_express\":\"30m\"}"; + + log.info("[Alipay] App支付请求: orderId={}, bizContent={}", orderId, bizContent); + + payRequest.setBizContent(bizContent); + + AlipayTradeAppPayResponse response = alipayClient.sdkExecute(payRequest); + + if (response.isSuccess()) { + String payUrl = response.getBody(); + + // App唤起支付宝的scheme格式: alipay://... + // 实际使用时,前端使用 plus.runtime.openURL(payUrl) 唤起支付宝 + log.info("[Alipay] App支付创建成功: orderId={}, payUrl={}", orderId, payUrl); + + PaymentResponse result = PaymentResponse.builder() + .orderId(orderId) + .status("PENDING") + .payUrl(payUrl) + .payInfo(response.getBody()) + .goodsDesc(request.getGoodsDesc()) + .transAmt(request.getTransAmt()) + .tradeType(request.getTradeType()) + .build(); + + // 缓存订单到Redis和本地 + saveOrderToRedis(orderId, result); + localOrderCache.put(orderId, result); + + return result; + } else { + log.error("[Alipay] App支付创建失败: orderId={}, error={}", orderId, response.getMsg()); + return PaymentResponse.builder() + .orderId(orderId) + .status("FAIL") + .errorCode(response.getCode()) + .errorMsg(response.getMsg()) + .build(); + } + } catch (AlipayApiException e) { + log.error("[Alipay] App支付异常: orderId={}", orderId, e); + String errCode = e.getErrCode() != null ? e.getErrCode() : "NETWORK_ERROR"; + String errMsg = e.getErrMsg() != null ? e.getErrMsg() : extractErrorMessage(e); + return PaymentResponse.builder() + .orderId(orderId) + .status("FAIL") + .errorCode(errCode) + .errorMsg(errMsg) + .build(); + } + } + + /** + * 支付宝扫码支付 (alipay.trade.precreate) + * 生成付款二维码,用户使用支付宝扫码完成支付 + */ + @Override + public PaymentResponse alipayQrCodePay(CreatePaymentRequest request) { + String orderId = generateOrderId(); + + try { + AlipayTradePrecreateRequest precreateRequest = new AlipayTradePrecreateRequest(); + precreateRequest.setNotifyUrl(alipayProperties.getNotifyUrl()); + + // 构建业务参数 + String bizContent = "{\"out_trade_no\":\"" + orderId + "\"," + + "\"total_amount\":\"0.01\"," + + "\"subject\":\"" + request.getGoodsDesc() + "\"," + + "\"timeout_express\":\"30m\"}"; + + log.info("[Alipay] 扫码支付请求: orderId={}, bizContent={}", orderId, bizContent); + + precreateRequest.setBizContent(bizContent); + AlipayTradePrecreateResponse response = alipayClient.execute(precreateRequest); + + if (response.isSuccess()) { + String qrCode = response.getQrCode(); // 二维码链接 + + log.info("[Alipay] 扫码支付创建成功: orderId={}, qrCode={}", orderId, qrCode); + + PaymentResponse result = PaymentResponse.builder() + .orderId(orderId) + .status("PENDING") + .qrCode(qrCode) + .payInfo(response.getBody()) + .goodsDesc(request.getGoodsDesc()) + .transAmt(request.getTransAmt()) + .tradeType("QRCODE") + .build(); + + // 缓存订单到Redis和本地 + saveOrderToRedis(orderId, result); + localOrderCache.put(orderId, result); + + return result; + } else { + log.error("[Alipay] 扫码支付创建失败: orderId={}, error={}", orderId, response.getMsg()); + return PaymentResponse.builder() + .orderId(orderId) + .status("FAIL") + .errorCode(response.getCode()) + .errorMsg(response.getMsg()) + .build(); + } + } catch (AlipayApiException e) { + log.error("[Alipay] 扫码支付异常: orderId={}", orderId, e); + String errCode = e.getErrCode() != null ? e.getErrCode() : "NETWORK_ERROR"; + String errMsg = e.getErrMsg() != null ? e.getErrMsg() : extractErrorMessage(e); + return PaymentResponse.builder() + .orderId(orderId) + .status("FAIL") + .errorCode(errCode) + .errorMsg(errMsg) + .build(); + } + } + + @Override + public String handleAlipayNotify(Map params) { + String outTradeNo = params.get("out_trade_no"); + + try { + // 1. 幂等处理:检查是否已处理过该通知(使用Redis) + if (isTradeProcessed(outTradeNo)) { + log.info("[Alipay] 幂等处理:通知已处理,跳过, outTradeNo={}", outTradeNo); + return "success"; + } + + // 2. 验签 + boolean signVerified = AlipaySignature.rsaCheckV1( + params, + alipayProperties.getAlipayPublicKey(), + alipayProperties.getCharset(), + alipayProperties.getSignType() + ); + + if (!signVerified) { + log.warn("[Alipay] 异步通知验签失败: outTradeNo={}", outTradeNo); + return "fail"; + } + + // 3. 关键信息校验 + // 校验 app_id 是否匹配 + String notifyAppId = params.get("app_id"); + if (!alipayProperties.getAppId().equals(notifyAppId)) { + log.warn("[Alipay] app_id 不匹配: expected={}, actual={}", alipayProperties.getAppId(), notifyAppId); + return "fail"; + } + + // 校验卖家ID (seller_id) + String sellerId = params.get("seller_id"); + if (sellerId != null && !sellerId.isEmpty()) { + // 商家ID校验逻辑根据实际情况添加 + log.debug("[Alipay] 卖家ID校验: seller_id={}", sellerId); + } + + // 校验交易金额 + String notifyTotalAmount = params.get("total_amount"); + PaymentResponse cachedOrder = getOrderFromRedis(outTradeNo); + if (cachedOrder != null && notifyTotalAmount != null) { + // 金额校验:确保通知金额与订单金额一致(防止金额篡改) + log.debug("[Alipay] 交易金额校验: orderId={}, amount={}", outTradeNo, notifyTotalAmount); + } + + String tradeStatus = params.get("trade_status"); + log.info("[Alipay] 异步通知验签成功: outTradeNo={}, tradeStatus={}", outTradeNo, tradeStatus); + + // 4. 判断交易状态 + if ("TRADE_SUCCESS".equals(tradeStatus) || "TRADE_FINISHED".equals(tradeStatus)) { + // 更新订单状态 + if (cachedOrder != null) { + cachedOrder.setStatus("SUCCESS"); + cachedOrder.setPayTime(params.get("gmt_payment")); + saveOrderToRedis(outTradeNo, cachedOrder); + localOrderCache.put(outTradeNo, cachedOrder); + } + + // 5. 标记为已处理(幂等,使用Redis) + markTradeProcessed(outTradeNo); + + return "success"; + } + + return "fail"; + } catch (AlipayApiException e) { + log.error("[Alipay] 异步通知处理异常: outTradeNo={}", outTradeNo, e); + return "fail"; + } + } + + @Override + public boolean refund(String orderId, String refundAmt) { + // 1. 退款幂等检查:防止重复退款(使用Redis) + if (isOrderRefunded(orderId)) { + log.info("[Alipay] 退款幂等:订单已退款过,跳过, orderId={}", orderId); + return true; + } + + try { + AlipayTradeRefundRequest refundRequest = new AlipayTradeRefundRequest(); + refundRequest.setBizContent("{\"out_trade_no\":\"" + orderId + "\",\"refund_amount\":\"" + refundAmt + "\"}"); + + AlipayTradeRefundResponse response = alipayClient.execute(refundRequest); + if (response.isSuccess()) { + // 2. 校验退款是否真正成功:必须判断 fund_change=Y + String fundChange = response.getFundChange(); + if ("Y".equals(fundChange)) { + log.info("[Alipay] 退款成功: orderId={}, refundAmt={}, fundChange={}", orderId, refundAmt, fundChange); + // 标记为已退款(幂等,使用Redis) + markOrderRefunded(orderId); + return true; + } else { + log.warn("[Alipay] 退款返回Y但fund_change!=Y: orderId={}, fundChange={}", orderId, fundChange); + return false; + } + } else { + log.error("[Alipay] 退款失败: orderId={}, error={}", orderId, response.getMsg()); + } + } catch (AlipayApiException e) { + log.error("[Alipay] 退款异常: orderId={}", orderId, e); + } + return false; + } + + // ==================== Redis操作方法 ==================== + + /** + * 检查交易是否已处理(幂等) + */ + private boolean isTradeProcessed(String outTradeNo) { + try { + return Boolean.TRUE.equals(redisUtil.hasKey(PROCESSED_TRADE_PREFIX + outTradeNo).block()); + } catch (Exception e) { + log.error("[Alipay] 检查交易处理状态异常: outTradeNo={}", outTradeNo, e); + return false; + } + } + + /** + * 标记交易已处理(幂等) + */ + private void markTradeProcessed(String outTradeNo) { + try { + redisUtil.setWithExpire(PROCESSED_TRADE_PREFIX + outTradeNo, "1", IDEMPOTENT_EXPIRE_SECONDS).block(); + } catch (Exception e) { + log.error("[Alipay] 标记交易处理状态异常: outTradeNo={}", outTradeNo, e); + } + } + + /** + * 检查订单是否已退款(幂等) + */ + private boolean isOrderRefunded(String orderId) { + try { + return Boolean.TRUE.equals(redisUtil.hasKey(REFUND_ORDER_PREFIX + orderId).block()); + } catch (Exception e) { + log.error("[Alipay] 检查订单退款状态异常: orderId={}", orderId, e); + return false; + } + } + + /** + * 标记订单已退款(幂等) + */ + private void markOrderRefunded(String orderId) { + try { + redisUtil.setWithExpire(REFUND_ORDER_PREFIX + orderId, "1", IDEMPOTENT_EXPIRE_SECONDS).block(); + } catch (Exception e) { + log.error("[Alipay] 标记订单退款状态异常: orderId={}", orderId, e); + } + } + + /** + * 从Redis获取订单 + */ + private PaymentResponse getOrderFromRedis(String orderId) { + try { + return redisUtil.get(ORDER_CACHE_PREFIX + orderId, PaymentResponse.class).block(); + } catch (Exception e) { + log.error("[Alipay] 获取Redis订单异常: orderId={}", orderId, e); + return null; + } + } + + /** + * 保存订单到Redis + */ + private void saveOrderToRedis(String orderId, PaymentResponse response) { + try { + redisUtil.setWithExpire(ORDER_CACHE_PREFIX + orderId, response, IDEMPOTENT_EXPIRE_SECONDS).block(); + } catch (Exception e) { + log.error("[Alipay] 保存订单到Redis异常: orderId={}", orderId, e); + } + } + + /** + * 生成订单号 + */ + private String generateOrderId() { + String timestamp = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()); + String uuid = UUID.randomUUID().toString().replace("-", "").substring(0, 8); + return timestamp + uuid; + } + + /** + * 提取异常中的错误信息(兜底) + */ + private String extractErrorMessage(AlipayApiException e) { + Throwable cause = e.getCause(); + if (cause != null && cause.getMessage() != null && cause.getMessage().contains("504")) { + return "支付宝沙箱网关连接超时(504),请检查服务器网络能否访问 openapi-sandbox.dl.alipaydev.com"; + } + if (cause != null) { + return "网络异常: " + cause.getClass().getSimpleName() + " - " + (cause.getMessage() != null ? cause.getMessage().substring(0, Math.min(cause.getMessage().length(), 200)) : ""); + } + if (e.getMessage() != null) { + return "支付宝接口异常: " + (e.getMessage().length() > 200 ? e.getMessage().substring(0, 200) : e.getMessage()); + } + return "支付宝接口调用失败,请稍后重试"; + } +} diff --git a/gym-manage-api/info/6666000208197943.txt b/gym-manage-api/info/6666000208197943.txt new file mode 100644 index 0000000..abc05d8 --- /dev/null +++ b/gym-manage-api/info/6666000208197943.txt @@ -0,0 +1,5 @@ +公钥:MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1US58xqznTU9rem0wLmHRWssd7pLlirWE+cgk1IzrE4ejP2hZOwtHISQ/KLSXh+nXMoRuswfun3n68NHrBy8q86JW0AAfCQdVF1+vqBqGdU89iq6v+hfIYmzl6FwlRTNlGf4NjkPCX+SofxMD9pgi2NSnAubweo03jpg2BYGZpbB9wA9de2a6g0iC78H/VLsbjXm9WbbM2+u1E1HGtJPxnZ/arPSbFRNnplYsbyCAnAFT+JfZm6cuLzIvG9nNIaJ0WqYVwbKI6pdXjTpDmfmUcn1pRe+coEU+CgpQBU8uf7chP1u580KrnUFVkgJrY+SuAPH6eTr8D/ved3GAjestQIDAQAB +私钥:MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDVRLnzGrOdNT2t6bTAuYdFayx3ukuWKtYT5yCTUjOsTh6M/aFk7C0chJD8otJeH6dcyhG6zB+6fefrw0esHLyrzolbQAB8JB1UXX6+oGoZ1Tz2Krq/6F8hibOXoXCVFM2UZ/g2OQ8Jf5Kh/EwP2mCLY1KcC5vB6jTeOmDYFgZmlsH3AD117ZrqDSILvwf9UuxuNeb1Ztszb67UTUca0k/Gdn9qs9JsVE2emVixvIICcAVP4l9mbpy4vMi8b2c0honRaphXBsojql1eNOkOZ+ZRyfWlF75ygRT4KClAFTy5/tyE/W7nzQqudQVWSAmtj5K4A8fp5OvwP+953cYCN6y1AgMBAAECggEADiwB+1Cj7Odz0NG97Cyn+4nyq4YarcDs9ued94w32NRcHVxhVDaJjOKWS+N5/T03PuhCo1obJaZmOfmBEsPaGcSgxgqLvbQcqtWHrZ01T8Amy+js/gGwCKeA4qucsptuSdVa3ieOg0AR+2ermkYVsk2IWMxArnMkzjus4c8c38Y3xJu+Nhd7HfKokH6TUluh2cwHqZLUJuDNkKLCuEgDJqNMyFGC99tLqGldMLuM2g7RVSNDsmzxXZC2vliLfsrwRF8TAZK0Fx5rgLj5WRnGFt3BfcVznDimHOOoBGyvl+1rFkpeb6iKcfKV9IcZN7iZN2/yunKtouqip4FVht8OAQKBgQD3YjR2rU7aWbLu+54c6YTYI1P+h6K8OLqWm0YgXZPFawxK46rEYWEk8noCI6SpQcNKxmqQ61ysv61yTVBL7bwHSF2mSSkfmzPjBS8aO4NVNCTaxU/S84ky/ROXJwXVXHGaVerXEnZGbVlJfHKM7ui80R9hPdGjn0cqEUgdAxiCNQKBgQDcslV096ok0o4iLuwzYmaM1Mtd9ZWy1fh+IG//0oocKNXA3W3Z0mKUyqEnheD3wRsKmiCVEWY+mD1YuRzDupFtlyNv7eet1dmbumXp+icZGiCHPTDEMHpNdpjWhD0x1juMnsrVyf7DKuL0gtT5I+MIdAVAQ3n4sGtdS6n/RyfQgQKBgQCarTOidJBWJDmmBXuCFlxyi/xLrGELEOlRm7qIKBpqGJmyBZHTghOsd1PkHIcrMSw7h74/6HmkpaB8VGz9XKLxZsTvYNUupusVajteSpG+Z4EXrMFRY+aIFAb4vnR+SHPbjRbt7OQ4+swWD5LMHeE1lgfp1C5NAR0M54tQYIwldQKBgBawtxyJghK7GyXkkSBPU5/TGP2WDEgQu7Wr/BffO5fdkux7V5n3iW8mzh7UIucWEYOriQIKgeqZmGUQ0yZEfkM4MMqHOTSkMfVS2ruNnot0JjgBUIw7N7fTI6+adPg0wHaatNv6IplKRX9CHEdpKyRpWUjcJyQsbz1uVBIM7MkBAoGBAJYDO/uvsYLjlRGJSOiY0u0Nc6aubtdwIx5MB9toLCSj0ATHqB8SYf1KnnGOMxUgjpLZsMnkbEyC8+oHslFNkb/uUdoitpxKgQ/sPwh+GkO+Z38obSXJ8FiRoMA6I7zCH3qgsMN3kmF3Y4njhYqGR0g8o95yNqV3yuVTOcLSzpyx +汇付公钥:MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxPEF/yDN4/kavunWrR6hl5wFq9vVyCsNsQMYQg1r607YfVmB6oqgkM/ytH8N85LU5bF45CQ12TyMJWdb/ltpy1yZN26olcknVP1IznvPZlXJUCGJndNVJnHXr1HgSphwVvPyOcrn9B1/phdnbO2+96ChFKRjz5fjnatMaRteapMFBvw4qcoumLtWdTLH85scng4SWMBfuj0Q9LdmB377KKkJl3x3fSgjUw4SquS2fUv6tqGQwyzCKRg2k5KRIb/HgaqdyMt7/SjMX5L1QpFNKbYlg7vTpf3jdh4JefvAk+/kiQO34GeWOzJpSNUWs/GxJUg9eWcCqCsM7aHRXhBTWQIDAQAB +sys_id:6666000208197943 +product_id:XLSISV diff --git a/gym-manage-api/log.text b/gym-manage-api/log.text index f336e34..41fd29c 100644 --- a/gym-manage-api/log.text +++ b/gym-manage-api/log.text @@ -1,387 +1,946 @@ -[ - { - "id": "3", - "createBy": "admin", - "updateBy": null, - "createdAt": "2026-06-15T10:00:00", - "updatedAt": "2026-06-15T10:00:00", - "deletedAt": null, - "courseId": "1", - "recommendTitle": "本周热门推荐", - "recommendContent": "极速燃脂单车课程,跟随音乐节奏变换阻力和速度,体验爬坡与冲刺的快感,一节课消耗800大卡!", - "recommendReason": "教练专业,课程内容丰富,深受学员喜爱,燃脂效果显著", - "priority": 20, - "isActive": true, - "groupCourse": { - "id": "1", - "createBy": "system", - "updateBy": "system", - "createdAt": "2026-06-11T13:50:25.118925", - "updatedAt": "2026-06-11T13:50:25.118925", - "deletedAt": null, - "courseName": "动感单车升级版aaaaa", - "coachId": "2", - "courseType": "2", - "startTime": "2026-06-02T16:45:00", - "endTime": "2026-06-15T20:20:00", - "maxMembers": 30, - "currentMembers": 0, - "status": "0", - "location": "单车房", - "coverImage": "/images/spinning.jpg", - "description": "升级版高强度有氧运动课程", - "pointCardAmount": 2, - "storedValueAmount": 80 - } - }, - { - "id": "7", - "createBy": "coach_li", - "updateBy": null, - "createdAt": "2026-05-25T09:15:00", - "updatedAt": "2026-05-25T09:15:00", - "deletedAt": null, - "courseId": "6", - "recommendTitle": "塑形热门课程", - "recommendContent": "蜜桃臀塑造课程,针对性训练臀部肌肉群,打造完美曲线。", - "recommendReason": "专业私教指导,动作标准,效果显著,深受女性学员喜爱", - "priority": 18, - "isActive": true, - "groupCourse": { - "id": "6", - "createBy": "coach_li", - "updateBy": null, - "createdAt": "2026-05-20T09:15:00", - "updatedAt": "2026-05-20T09:15:00", - "deletedAt": null, - "courseName": "蜜桃臀塑造", - "coachId": "103", - "courseType": "3", - "startTime": "2026-05-30T19:00:00", - "endTime": "2026-05-30T20:00:00", - "maxMembers": 10, - "currentMembers": 8, - "status": "2", - "location": "私教专区", - "coverImage": "/images/glute.jpg", - "description": "针对性训练臀部肌肉群,课程已于5月30日结束,无法预约。", - "pointCardAmount": 1, - "storedValueAmount": 0 - } - }, - { - "id": "9", - "createBy": "coach_zhang", - "updateBy": null, - "createdAt": "2026-06-15T14:00:00", - "updatedAt": "2026-06-15T14:00:00", - "deletedAt": null, - "courseId": "1", - "recommendTitle": "减脂首选课程", - "recommendContent": "想要快速减脂?极速燃脂单车是你的最佳选择!专业教练带领,科学训练计划。", - "recommendReason": "减脂效果最佳,课程强度适中,适合想要快速瘦身的学员", - "priority": 16, - "isActive": true, - "groupCourse": { - "id": "1", - "createBy": "system", - "updateBy": "system", - "createdAt": "2026-06-11T13:50:25.118925", - "updatedAt": "2026-06-11T13:50:25.118925", - "deletedAt": null, - "courseName": "动感单车升级版aaaaa", - "coachId": "2", - "courseType": "2", - "startTime": "2026-06-02T16:45:00", - "endTime": "2026-06-15T20:20:00", - "maxMembers": 30, - "currentMembers": 0, - "status": "0", - "location": "单车房", - "coverImage": "/images/spinning.jpg", - "description": "升级版高强度有氧运动课程", - "pointCardAmount": 2, - "storedValueAmount": 80 - } - }, - { - "id": "4", - "createBy": "admin", - "updateBy": null, - "createdAt": "2026-06-15T11:00:00", - "updatedAt": "2026-06-15T11:00:00", - "deletedAt": null, - "courseId": "2", - "recommendTitle": "新手友好推荐", - "recommendContent": "清晨流瑜伽课程,适合有一定基础的学员,通过流畅的体式连接呼吸,唤醒身体能量。", - "recommendReason": "适合新手入门,教练耐心指导,课程节奏适中", - "priority": 15, - "isActive": true, - "groupCourse": { - "id": "2", - "createBy": "admin", - "updateBy": null, - "createdAt": "2026-06-01T10:00:00", - "updatedAt": "2026-06-01T10:00:00", - "deletedAt": null, - "courseName": "清晨流瑜伽", - "coachId": "101", - "courseType": "1", - "startTime": "2026-06-12T09:00:00", - "endTime": "2026-06-12T10:30:00", - "maxMembers": 15, - "currentMembers": 5, - "status": "0", - "location": "A座3楼瑜伽教室", - "coverImage": "/images/yoga_flow.jpg", - "description": "适合有一定基础的学员,通过流畅的体式连接呼吸,唤醒身体能量。", - "pointCardAmount": 1, - "storedValueAmount": 0 - } - }, - { - "id": "10", - "createBy": "coach_wang", - "updateBy": null, - "createdAt": "2026-06-15T15:00:00", - "updatedAt": "2026-06-15T15:00:00", - "deletedAt": null, - "courseId": "2", - "recommendTitle": "晨练优选", - "recommendContent": "清晨流瑜伽,唤醒身体能量,开启活力一天!适合晨练爱好者。", - "recommendReason": "晨练最佳选择,提升身体活力,改善精神状态", - "priority": 14, - "isActive": true, - "groupCourse": { - "id": "2", - "createBy": "admin", - "updateBy": null, - "createdAt": "2026-06-01T10:00:00", - "updatedAt": "2026-06-01T10:00:00", - "deletedAt": null, - "courseName": "清晨流瑜伽", - "coachId": "101", - "courseType": "1", - "startTime": "2026-06-12T09:00:00", - "endTime": "2026-06-12T10:30:00", - "maxMembers": 15, - "currentMembers": 5, - "status": "0", - "location": "A座3楼瑜伽教室", - "coverImage": "/images/yoga_flow.jpg", - "description": "适合有一定基础的学员,通过流畅的体式连接呼吸,唤醒身体能量。", - "pointCardAmount": 1, - "storedValueAmount": 0 - } - }, - { - "id": "6", - "createBy": "coach_li", - "updateBy": null, - "createdAt": "2026-06-15T13:00:00", - "updatedAt": "2026-06-15T13:00:00", - "deletedAt": null, - "courseId": "4", - "recommendTitle": "基础瑜伽推荐", - "recommendContent": "基础哈他瑜伽课程,适合所有级别学员,通过基础体式练习提升身体柔韧性和平衡能力。", - "recommendReason": "零基础友好,适合所有健身水平,放松身心", - "priority": 12, - "isActive": true, - "groupCourse": { - "id": "4", - "createBy": "coach_li", - "updateBy": null, - "createdAt": "2026-06-01T08:00:00", - "updatedAt": "2026-06-01T08:00:00", - "deletedAt": null, - "courseName": "哈他瑜伽", - "coachId": "101", - "courseType": "1", - "startTime": "2026-06-01T15:20:00", - "endTime": "2026-06-01T16:50:00", - "maxMembers": 12, - "currentMembers": 3, - "status": "0", - "location": "瑜伽教室B", - "coverImage": "/images/hatha_yoga.jpg", - "description": "基础哈他瑜伽,适合所有级别。距开始不足30分钟,已停止预约。", - "pointCardAmount": 1, - "storedValueAmount": 0 - } - }, - { - "id": "11", - "createBy": "coach_li", - "updateBy": null, - "createdAt": "2026-06-15T16:00:00", - "updatedAt": "2026-06-15T16:00:00", - "deletedAt": null, - "courseId": "4", - "recommendTitle": "身心平衡推荐", - "recommendContent": "哈他瑜伽课程,通过体式练习和呼吸控制,达到身心平衡,提升整体健康水平。", - "recommendReason": "改善身体柔韧性,增强核心力量,提升身体协调性", - "priority": 11, - "isActive": true, - "groupCourse": { - "id": "4", - "createBy": "coach_li", - "updateBy": null, - "createdAt": "2026-06-01T08:00:00", - "updatedAt": "2026-06-01T08:00:00", - "deletedAt": null, - "courseName": "哈他瑜伽", - "coachId": "101", - "courseType": "1", - "startTime": "2026-06-01T15:20:00", - "endTime": "2026-06-01T16:50:00", - "maxMembers": 12, - "currentMembers": 3, - "status": "0", - "location": "瑜伽教室B", - "coverImage": "/images/hatha_yoga.jpg", - "description": "基础哈他瑜伽,适合所有级别。距开始不足30分钟,已停止预约。", - "pointCardAmount": 1, - "storedValueAmount": 0 - } - }, - { - "id": "1", - "createBy": "system", - "updateBy": "system", - "createdAt": "2026-06-15T16:21:20.865146", - "updatedAt": "2026-06-15T16:23:05.180219", - "deletedAt": null, - "courseId": "1", - "recommendTitle": "本周热门课程", - "recommendContent": "这是一门非常棒的课程,快来参加吧!", - "recommendReason": "教练专业,课程内容丰富", - "priority": 10, - "isActive": false, - "groupCourse": { - "id": "1", - "createBy": "system", - "updateBy": "system", - "createdAt": "2026-06-11T13:50:25.118925", - "updatedAt": "2026-06-11T13:50:25.118925", - "deletedAt": null, - "courseName": "动感单车升级版aaaaa", - "coachId": "2", - "courseType": "2", - "startTime": "2026-06-02T16:45:00", - "endTime": "2026-06-15T20:20:00", - "maxMembers": 30, - "currentMembers": 0, - "status": "0", - "location": "单车房", - "coverImage": "/images/spinning.jpg", - "description": "升级版高强度有氧运动课程", - "pointCardAmount": 2, - "storedValueAmount": 80 - } - }, - { - "id": "5", - "createBy": "admin", - "updateBy": null, - "createdAt": "2026-06-15T12:00:00", - "updatedAt": "2026-06-15T12:00:00", - "deletedAt": null, - "courseId": "3", - "recommendTitle": "高强度燃脂", - "recommendContent": "燃脂搏击课程,高强度间歇训练,配合音乐快速燃脂,释放压力。", - "recommendReason": "高强度训练,适合进阶学员,快速燃脂塑形", - "priority": 10, - "isActive": false, - "groupCourse": { - "id": "3", - "createBy": "coach_zhang", - "updateBy": null, - "createdAt": "2026-06-01T14:30:00", - "updatedAt": "2026-06-01T14:30:00", - "deletedAt": null, - "courseName": "燃脂搏击", - "coachId": "102", - "courseType": "2", - "startTime": "2026-06-10T18:30:00", - "endTime": "2026-06-10T19:30:00", - "maxMembers": 20, - "currentMembers": 20, - "status": "0", - "location": "综合训练区", - "coverImage": "/images/kickboxing.jpg", - "description": "高强度间歇训练,配合音乐快速燃脂,释放压力。名额已满,无法预约。", - "pointCardAmount": 1, - "storedValueAmount": 0 - } - }, - { - "id": "12", - "createBy": "admin", - "updateBy": null, - "createdAt": "2026-05-25T10:00:00", - "updatedAt": "2026-05-25T10:00:00", - "deletedAt": null, - "courseId": "7", - "recommendTitle": "职场减压课程", - "recommendContent": "午间冥想放松,专为职场人士设计,快速缓解工作压力,提升工作状态。", - "recommendReason": "职场减压首选,课程时间短,效果显著", - "priority": 9, - "isActive": false, - "groupCourse": { - "id": "7", - "createBy": "admin", - "updateBy": null, - "createdAt": "2026-05-25T09:00:00", - "updatedAt": "2026-05-25T09:00:00", - "deletedAt": null, - "courseName": "午间冥想放松", - "coachId": "101", - "courseType": "1", - "startTime": "2026-05-31T12:00:00", - "endTime": "2026-05-31T13:00:00", - "maxMembers": 15, - "currentMembers": 6, - "status": "2", - "location": "冥想室", - "coverImage": "/images/meditation_noon.jpg", - "description": "午间冥想课程,已于5月31日结束。", - "pointCardAmount": 1, - "storedValueAmount": 0 - } - }, - { - "id": "8", - "createBy": "admin", - "updateBy": null, - "createdAt": "2026-05-25T09:00:00", - "updatedAt": "2026-05-25T09:00:00", - "deletedAt": null, - "courseId": "7", - "recommendTitle": "午间放松推荐", - "recommendContent": "午间冥想放松课程,通过呼吸和正念冥想,深度放松身心,缓解工作压力。", - "recommendReason": "适合上班族,午间放松充电,提升下午工作效率", - "priority": 8, - "isActive": true, - "groupCourse": { - "id": "7", - "createBy": "admin", - "updateBy": null, - "createdAt": "2026-05-25T09:00:00", - "updatedAt": "2026-05-25T09:00:00", - "deletedAt": null, - "courseName": "午间冥想放松", - "coachId": "101", - "courseType": "1", - "startTime": "2026-05-31T12:00:00", - "endTime": "2026-05-31T13:00:00", - "maxMembers": 15, - "currentMembers": 6, - "status": "2", - "location": "冥想室", - "coverImage": "/images/meditation_noon.jpg", - "description": "午间冥想课程,已于5月31日结束。", - "pointCardAmount": 1, - "storedValueAmount": 0 - } - } -] \ No newline at end of file +Microsoft Windows [版本 10.0.20348.4893] +(c) Microsoft Corporation。保留所有权利。 + +C:\Users\Administrator\Desktop\APPS\gym-manage\back>java -jar manage-app-1.0.0.jar --DB_PASSWORD="z5kdRFQ-$sL-pass-to-sql" --REDIS_PASSWORD="z5kdRFQ-$sLTerL" +04:38:58.013 [main] INFO cn.novalon.gym.manage.app.ManageApplication -- 搴旂敤绋嬪簭鍚姩涓?.. + +04:38:58.022 [main] INFO cn.novalon.gym.manage.app.ManageApplication -- 鍖呮壂鎻忚矾寰? cn.novalon.gym.manage + +╔═══════════════════════════════════════════════════════════════════╗ +║ ║ +║ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ██╗ ██████╗ ███╗ ██╗ ║ +║ ████╗ ██║██╔═══██╗██║ ██║██╔══██╗██║ ██╔═══██╗████╗ ██║ ║ +║ ██╔██╗ ██║██║ ██║██║ ██║███████║██║ ██║ ██║██╔██╗ ██║ ║ +║ ██║╚██╗██║██║ ██║╚██╗ ██╔╝██╔══██║██║ ██║ ██║██║╚██╗██║ ║ +║ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║███████╗╚██████╔╝██║ ╚████║ ║ +║ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═══╝ ║ +║ ║ +║ ███╗ ███╗ █████╗ ███╗ ██╗ █████╗ ██████╗ ███████╗ ║ +║ ████╗ ████║██╔══██╗████╗ ██║██╔══██╗██╔════╝ ██╔════╝ ║ +║ ██╔████╔██║███████║██╔██╗ ██║███████║██║ ███╗█████╗ ║ +║ ██║╚██╔╝██║██╔══██║██║╚██╗██║██╔══██║██║ ██║██╔══╝ ║ +║ ██║ ╚═╝ ██║██║ ██║██║ ╚████║██║ ██║╚██████╔╝███████╗ ║ +║ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝ ║ +║ ║ +║ ███████╗██╗ ██╗███████╗████████╗███████╗███╗ ███╗ ║ +║ ██╔════╝╚██╗ ██╔╝██╔════╝╚══██╔══╝██╔════╝████╗ ████║ ║ +║ ███████╗ ╚████╔╝ ███████╗ ██║ █████╗ ██╔████╔██║ ║ +║ ╚════██║ ╚██╔╝ ╚════██║ ██║ ██╔══╝ ██║╚██╔╝██║ ║ +║ ███████║ ██║ ███████║ ██║ ███████╗██║ ╚═╝ ██║ ║ +║ ╚══════╝ ╚═╝ ╚══════╝ ╚═╝ ╚══════╝╚═╝ ╚═╝ ║ +║ ║ +╚═══════════════════════════════════════════════════════════════════╝ + + :: Novalon Manage System :: + Version: 1.0.0 + Spring Boot: 3.5.13 + Java: 21.0.10 + PID: 7516 + +2026-06-24T04:39:00.533+08:00 INFO 7516 --- [gym-manage-api] [ main] c.n.gym.manage.app.ManageApplication : Starting ManageApplication v1.0.0 using Java 21.0.10 with PID 7516 (C:\Users\Administrator\Desktop\APPS\gym-manage\back\manage-app-1.0.0.jar started by Administrator in C:\Users\Administrator\Desktop\APPS\gym-manage\back) +2026-06-24T04:39:00.537+08:00 INFO 7516 --- [gym-manage-api] [ main] c.n.gym.manage.app.ManageApplication : The following 1 profile is active: "dev" +2026-06-24T04:39:06.298+08:00 INFO 7516 --- [gym-manage-api] [ main] o.s.b.f.s.DefaultListableBeanFactory : Overriding bean definition for bean 'webSocketMapping' with a different definition: replacing [Root bean: class=null; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; fallback=false; factoryBeanName=checkInWebSocketConfig; factoryMethodName=webSocketMapping; initMethodNames=null; destroyMethodNames=[(inferred)]; defined in class path resource [cn/novalon/gym/manage/checkIn/config/CheckInWebSocketConfig.class]] with [Root bean: class=null; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; fallback=false; factoryBeanName=webSocketConfig; factoryMethodName=webSocketMapping; initMethodNames=null; destroyMethodNames=[(inferred)]; defined in class path resource [cn/novalon/gym/manage/checkIn/config/WebSocketConfig.class]] +2026-06-24T04:39:06.487+08:00 INFO 7516 --- [gym-manage-api] [ main] o.s.b.f.s.DefaultListableBeanFactory : Overriding bean definition for bean 'handlerAdapter' with a different definition: replacing [Root bean: class=null; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; fallback=false; factoryBeanName=checkInWebSocketConfig; factoryMethodName=handlerAdapter; initMethodNames=null; destroyMethodNames=[(inferred)]; defined in class path resource [cn/novalon/gym/manage/checkIn/config/CheckInWebSocketConfig.class]] with [Root bean: class=null; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; fallback=false; factoryBeanName=webSocketConfig; factoryMethodName=handlerAdapter; initMethodNames=null; destroyMethodNames=[(inferred)]; defined in class path resource [cn/novalon/gym/manage/checkIn/config/WebSocketConfig.class]] +2026-06-24T04:39:06.511+08:00 INFO 7516 --- [gym-manage-api] [ main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode +2026-06-24T04:39:06.512+08:00 INFO 7516 --- [gym-manage-api] [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data R2DBC repositories in DEFAULT mode. +2026-06-24T04:39:07.747+08:00 INFO 7516 --- [gym-manage-api] [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 1209 ms. Found 31 R2DBC repository interfaces. +2026-06-24T04:39:07.839+08:00 INFO 7516 --- [gym-manage-api] [ main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode +2026-06-24T04:39:07.847+08:00 INFO 7516 --- [gym-manage-api] [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data Reactive Elasticsearch repositories in DEFAULT mode. +2026-06-24T04:39:07.874+08:00 INFO 7516 --- [gym-manage-api] [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 10 ms. Found 1 Reactive Elasticsearch repository interface. +2026-06-24T04:39:10.252+08:00 INFO 7516 --- [gym-manage-api] [ main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode +2026-06-24T04:39:10.257+08:00 INFO 7516 --- [gym-manage-api] [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data Elasticsearch repositories in DEFAULT mode. +2026-06-24T04:39:10.294+08:00 INFO 7516 --- [gym-manage-api] [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 24 ms. Found 0 Elasticsearch repository interfaces. +2026-06-24T04:39:10.395+08:00 INFO 7516 --- [gym-manage-api] [ main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode +2026-06-24T04:39:10.403+08:00 INFO 7516 --- [gym-manage-api] [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data Redis repositories in DEFAULT mode. +2026-06-24T04:39:10.496+08:00 INFO 7516 --- [gym-manage-api] [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 3 ms. Found 0 Redis repository interfaces. +2026-06-24T04:39:14.498+08:00 INFO 7516 --- [gym-manage-api] [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8084 (http) +2026-06-24T04:39:14.568+08:00 INFO 7516 --- [gym-manage-api] [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] +2026-06-24T04:39:14.577+08:00 INFO 7516 --- [gym-manage-api] [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.53] +2026-06-24T04:39:18.876+08:00 INFO 7516 --- [gym-manage-api] [ main] c.n.gym.manage.sys.config.AsyncConfig : 审计日志异步线程池初始化完成: corePoolSize=5, maxPoolSize=10, queueCapacity=100 +2026-06-24T04:39:18.956+08:00 INFO 7516 --- [gym-manage-api] [ main] c.n.gym.manage.sys.audit.AuditLogAspect : === AuditLogAspect 初始化完成 === +2026-06-24T04:39:19.611+08:00 INFO 7516 --- [gym-manage-api] [ main] c.n.g.m.sys.audit.OperationLogWebFilter : === OperationLogWebFilter 构造函数被调用 === +2026-06-24T04:39:19.617+08:00 INFO 7516 --- [gym-manage-api] [ main] c.n.g.m.sys.audit.OperationLogWebFilter : === OperationLogWebFilter 初始化 === +2026-06-24T04:39:19.619+08:00 INFO 7516 --- [gym-manage-api] [ main] c.n.g.m.sys.audit.OperationLogWebFilter : 操作日志映射配置数量: 10 +2026-06-24T04:39:19.621+08:00 INFO 7516 --- [gym-manage-api] [ main] c.n.g.m.sys.audit.OperationLogWebFilter : PUT:/api/roles/ -> 角色管理:更新角色 +2026-06-24T04:39:19.627+08:00 INFO 7516 --- [gym-manage-api] [ main] c.n.g.m.sys.audit.OperationLogWebFilter : POST:/api/users -> 用户管理:创建用户 +2026-06-24T04:39:19.630+08:00 INFO 7516 --- [gym-manage-api] [ main] c.n.g.m.sys.audit.OperationLogWebFilter : POST:/api/roles -> 角色管理:创建角色 +2026-06-24T04:39:19.631+08:00 INFO 7516 --- [gym-manage-api] [ main] c.n.g.m.sys.audit.OperationLogWebFilter : DELETE:/api/users/ -> 用户管理:删除用户 +2026-06-24T04:39:19.633+08:00 INFO 7516 --- [gym-manage-api] [ main] c.n.g.m.sys.audit.OperationLogWebFilter : PUT:/api/menus/ -> 菜单管理:更新菜单 +2026-06-24T04:39:19.634+08:00 INFO 7516 --- [gym-manage-api] [ main] c.n.g.m.sys.audit.OperationLogWebFilter : DELETE:/api/menus/ -> 菜单管理:删除菜单 +2026-06-24T04:39:19.639+08:00 INFO 7516 --- [gym-manage-api] [ main] c.n.g.m.sys.audit.OperationLogWebFilter : POST:/api/users/ -> 用户管理:用户操作 +2026-06-24T04:39:19.641+08:00 INFO 7516 --- [gym-manage-api] [ main] c.n.g.m.sys.audit.OperationLogWebFilter : POST:/api/menus -> 菜单管理:创建菜单 +2026-06-24T04:39:19.643+08:00 INFO 7516 --- [gym-manage-api] [ main] c.n.g.m.sys.audit.OperationLogWebFilter : DELETE:/api/roles/ -> 角色管理:删除角色 +2026-06-24T04:39:19.645+08:00 INFO 7516 --- [gym-manage-api] [ main] c.n.g.m.sys.audit.OperationLogWebFilter : PUT:/api/users/ -> 用户管理:更新用户 +2026-06-24T04:39:20.557+08:00 INFO 7516 --- [gym-manage-api] [ main] c.n.g.m.s.config.PasswordEncoderConfig : PasswordEncoderConfig 已加载 +2026-06-24T04:39:23.376+08:00 INFO 7516 --- [gym-manage-api] [ main] c.n.g.m.s.config.PasswordEncoderConfig : 创建主密码编码器: BCryptPasswordEncoder(strength=12), 类型: org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder +2026-06-24T04:39:23.391+08:00 INFO 7516 --- [gym-manage-api] [ main] c.n.g.m.s.c.service.impl.SysUserService : 使用的密码编码器类型: org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder +2026-06-24T04:39:23.705+08:00 INFO 7516 --- [gym-manage-api] [ main] c.n.g.m.s.h.a.PasswordDiagnosticHandler : PasswordDiagnosticHandler initialized with encoder: org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder +2026-06-24T04:39:23.756+08:00 INFO 7516 --- [gym-manage-api] [ main] c.n.g.m.sys.handler.auth.SysAuthHandler : SysAuthHandler使用的密码编码器类型: org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder +2026-06-24T04:39:24.465+08:00 INFO 7516 --- [gym-manage-api] [ main] c.n.g.m.sys.handler.auth.SysAuthHandler : DirectEncoder10测试: 密码=test123, 哈希=$2a$10$iJr, 前缀=$2a$10$ +2026-06-24T04:39:24.466+08:00 INFO 7516 --- [gym-manage-api] [ main] c.n.g.m.sys.handler.auth.SysAuthHandler : DirectEncoder12测试: 密码=test123, 哈希=$2a$12$fxz, 前缀=$2a$12$ +2026-06-24T04:39:34.332+08:00 INFO 7516 --- [gym-manage-api] [ main] c.n.g.manage.sys.config.SecurityConfig : SecurityConfig初始化: 当前环境=dev, Swagger启用状态=true +2026-06-24T04:39:34.470+08:00 TRACE 7516 --- [gym-manage-api] [ main] s.w.r.r.m.a.RequestMappingHandlerMapping : + c.n.g.m.s.a.c.AuditLogController: + {GET /api/audit-logs}: query(AuditLogQueryRequest) + {GET /api/audit-logs/{id}}: findById(Long) + {GET /api/audit-logs/entity-type/{entityType}}: findByEntityType(String) + {GET /api/audit-logs/count/entity-type/{entityType}}: countByEntityType(String) + {GET /api/audit-logs/count/operator/{operator}}: countByOperator(String) + {GET /api/audit-logs/entity/{entityId}}: findByEntityId(Long) + {GET /api/audit-logs/statistics}: getStatistics() + {GET /api/audit-logs/operator/{operator}}: findByOperator(String) + {GET /api/audit-logs/time-range}: findByTimeRange(LocalDateTime,LocalDateTime) + {GET /api/audit-logs/count/operation-type/{operationType}}: countByOperationType(String) + {GET /api/audit-logs/operation-type/{operationType}}: findByOperationType(String) +2026-06-24T04:39:34.523+08:00 TRACE 7516 --- [gym-manage-api] [ main] s.w.r.r.m.a.RequestMappingHandlerMapping : + o.s.w.a.MultipleOpenApiWebFluxResource: + {GET /api-docs/{group}, produces [application/json]}: openapiJson(ServerHttpRequest,String,String,Locale) + {GET /api-docs.yaml/{group}, produces [application/vnd.oai.openapi]}: openapiYaml(ServerHttpRequest,String,String,Locale) +2026-06-24T04:39:34.600+08:00 TRACE 7516 --- [gym-manage-api] [ main] s.w.r.r.m.a.RequestMappingHandlerMapping : + o.s.w.a.OpenApiWebfluxResource: + {GET /api-docs, produces [application/json]}: openapiJson(ServerHttpRequest,String,Locale) + {GET /api-docs.yaml, produces [application/vnd.oai.openapi]}: openapiYaml(ServerHttpRequest,String,Locale) +2026-06-24T04:39:34.611+08:00 TRACE 7516 --- [gym-manage-api] [ main] s.w.r.r.m.a.RequestMappingHandlerMapping : + o.s.w.u.SwaggerWelcomeWebFlux: + {GET /swagger-ui.html}: redirectToUi(ServerWebExchange) +2026-06-24T04:39:34.630+08:00 TRACE 7516 --- [gym-manage-api] [ main] s.w.r.r.m.a.RequestMappingHandlerMapping : + o.s.w.u.SwaggerConfigResource: + {GET /api-docs/swagger-config, produces [application/json]}: getSwaggerUiConfig(ServerWebExchange) +2026-06-24T04:39:34.645+08:00 DEBUG 7516 --- [gym-manage-api] [ main] s.w.r.r.m.a.RequestMappingHandlerMapping : 17 mappings in 'requestMappingHandlerMapping' +2026-06-24T04:39:34.706+08:00 INFO 7516 --- [gym-manage-api] [ main] c.n.g.manage.sys.config.SecurityConfig : SecurityConfig: Swagger路径和诊断端点已放行 +2026-06-24T04:39:34.799+08:00 TRACE 7516 --- [gym-manage-api] [ main] o.s.w.r.handler.SimpleUrlHandlerMapping : Mapped [/webSocket/checkIn] onto cn.novalon.gym.manage.checkIn.websocket.MyWebSocketHandler@36c6d53b +2026-06-24T04:39:34.803+08:00 DEBUG 7516 --- [gym-manage-api] [ main] o.s.w.r.handler.SimpleUrlHandlerMapping : Patterns [/webSocket/checkIn] in 'webSocketMapping' +2026-06-24T04:39:35.521+08:00 INFO 7516 --- [gym-manage-api] [ main] c.n.g.m.sys.config.ExceptionLogConfig : 配置异常日志路由 +2026-06-24T04:39:35.531+08:00 TRACE 7516 --- [gym-manage-api] [ main] o.s.w.r.f.s.s.RouterFunctionMapping : Mapped (GET && /api/diagnostic/password) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221995e198@259c7ff3 +(GET && /api/dictionaries) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221995fb70@30865a1 +(GET && /api/dictionaries/{id}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221995fd88@35dc4303 +(GET && /api/dictionaries/type/{type}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221996c000@5707ce70 +(GET && /api/dictionaries/check/exists) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221996c218@11225084 +(POST && /api/dictionaries) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221996c430@51470a6c +(PUT && /api/dictionaries/{id}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221996c648@6d48f1c +(DELETE && /api/dictionaries/{id}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221996c860@6f49a5f8 +(GET && /api/users) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221996ca78@6c6c7ee5 +(GET && /api/users/page) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221996cc90@5911d6e +(GET && /api/users/count) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221996cea8@271fdeaf +(GET && /api/users/username/{username}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221996d0c0@55bd9d1 +(GET && /api/users/check/username) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221996d2d8@a4f14c9 +(GET && /api/users/check/email) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221996d4f0@7b81283 +(POST && /api/users) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221996d708@5e8b5e7d +(GET && /api/users/{id}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221996d920@5c5812c +(PUT && /api/users/{id}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221996db38@77de3401 +(DELETE && /api/users/{id}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221996dd50@30350c73 +(POST && /api/users/{id}/action/change-password) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221996df68@34c9c4be +(POST && /api/users/{id}/action/logical-delete) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221996e180@1d2cffea +(POST && /api/users/logical-delete) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221996e398@1b54ae72 +(POST && /api/users/action/restore) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221996e5b0@39a7fff +(POST && /api/users/{id}/action/restore) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221996e7c8@798ca706 +(GET && /api/users/{id}/roles) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221996e9e0@3a5c472 +(POST && /api/users/{id}/roles) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221996ebf8@3dbcdd4a +(GET && /api/menus) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221996ee10@6d093885 +(GET && /api/menus/tree) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221996f028@712106b5 +(GET && /api/menus/{id}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221996f240@5c8adab7 +(POST && /api/menus) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221996f458@7070ae1 +(PUT && /api/menus/{id}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221996f670@236ec794 +(DELETE && /api/menus/{id}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221996f888@5a0379e2 +(GET && /api/roles) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221996faa0@50564f77 +(GET && /api/roles/page) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221996fcb8@6fb9179e +(GET && /api/roles/count) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221996b000@3abe3f9a +(GET && /api/roles/name/{roleName}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221996b218@7b338d46 +(GET && /api/roles/check-name) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221996b430@19fc48a0 +(GET && /api/roles/{id}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221996b648@3edea9e6 +(POST && /api/roles) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221996b860@75d70348 +(PUT && /api/roles/{id}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221996ba78@12e9e909 +(DELETE && /api/roles/{id}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221996bc90@69ab1abc +(POST && /api/roles/{id}/restore) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221996a800@22f10ff6 +(GET && /api/roles/{id}/permissions) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221996aa18@6ff185a3 +(POST && /api/roles/{id}/permissions) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221996ac30@320d98f5 +(GET && /api/config) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219970000@3c20af2 +(GET && /api/config/{id}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219970218@29e63bc3 +(GET && /api/config/key/{configKey}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219970430@4a596fc1 +(POST && /api/config) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219970648@144d8c4f +(PUT && /api/config/{id}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219970860@7cb81ae +(DELETE && /api/config/{id}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219970a78@1f6b9ab7 +(GET && /api/logs/login) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219970c90@1e4dc9fc +(GET && /api/logs/login/page) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219970ea8@661a018e +(GET && /api/logs/login/count) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x00000222199710c0@3d872a12 +(GET && /api/logs/login/today/count) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x00000222199712d8@7efaa137 +(GET && /api/logs/login/recent) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x00000222199714f0@e08853d +(GET && /api/logs/login/{id}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219971708@3828f303 +(POST && /api/logs/login) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219971920@3c7b89ae +(GET && /api/logs/exception) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219971b38@1723640f +(GET && /api/logs/exception/page) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219971d50@5dc83abb +(GET && /api/logs/exception/count) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219971f68@22876aea +(GET && /api/logs/exception/{id}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219972180@4251b1ed +(POST && /api/logs/exception) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219972398@4edea6b4 +(GET && /api/logs/operation) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x00000222199725b0@52df4036 +(GET && /api/logs/operation/export) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x00000222199727c8@4b96b0bd +(GET && /api/logs/operation/page) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x00000222199729e0@487befb +(GET && /api/logs/operation/count) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219972bf8@3f5743d9 +(GET && /api/logs/operation/{id}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219972e10@17a17af5 +(POST && /api/logs/operation) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219973028@694c23e4 +(POST && /api/auth/login) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219973240@50853850 +(POST && /api/auth/register) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219973458@55c9e012 +(POST && /api/auth/logout) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219973670@49235e8c +(GET && /api/stats/overview) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219973888@4178074f +(GET && /api/dict/types) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219973aa0@38c6f088 +(GET && /api/dict/types/{id}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219973cb8@22955ef2 +(GET && /api/dict/types/type/{dictType}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219973ed0@7111b312 +(POST && /api/dict/types) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x00000222199740e8@70d28000 +(PUT && /api/dict/types/{id}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219974300@7f1f6c22 +(DELETE && /api/dict/types/{id}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219974518@77f5cffd +(GET && /api/dict/data) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219974730@b807b32 +(GET && /api/dict/data/type/{dictType}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219974948@6f3628c2 +(GET && /api/dict/data/{id}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219974b60@6bcc277a +(POST && /api/dict/data) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219974d78@321f97d9 +(PUT && /api/dict/data/{id}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219974f90@3a4e3343 +(DELETE && /api/dict/data/{id}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x00000222199751a8@64e06cf0 +(GET && /api/notices) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x00000222199753c0@6e9d60e1 +(GET && /api/notices/{id}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x00000222199755d8@207283b4 +(GET && /api/notices/status/{status}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x00000222199757f0@6b6d68d2 +(POST && /api/notices) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219975a08@c8d0ea3 +(PUT && /api/notices/{id}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219975c20@912fdbb +(DELETE && /api/notices/{id}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219975e38@3d49b00e +(GET && /api/messages/user/{userId}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219976050@3d62648d +(GET && /api/messages/user/{userId}/page) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219976268@59fe8d94 +(GET && /api/messages/user/{userId}/unread) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219976480@479f5c35 +(GET && /api/messages/user/{userId}/unread/list) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219976698@37264d08 +(GET && /api/messages/user/{userId}/unread/page) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x00000222199768b0@194e116d +(POST && /api/messages) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219976ac8@3471bdfd +(PUT && /api/messages/{id}/read) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219976ce0@93231b2 +(PUT && /api/messages/user/{userId}/read) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219976ef8@cf97391 +(DELETE && /api/messages/{id}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219977110@65d5de1a +(GET && /api/files) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219977328@5cf21908 +(GET && /api/files/{id}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219977540@44d51c85 +(POST && /api/files/upload) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219977758@3f78a75 +(GET && /api/files/{id}/download) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219977970@4819a11f +(GET && /api/files/download/{fileName}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219977b88@46fdb4d +(GET && /api/files/{id}/preview) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219977da0@68b2ce71 +(GET && /api/files/preview/{fileName}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219978000@57a94530 +(DELETE && /api/files/{id}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219978218@52bd1783 +(GET && /api/permissions) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219978430@28f20b2a +(GET && /api/permissions/{id}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219978648@56cd8903 +(GET && /api/permissions/code/{code}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219978860@4bd0d62f +(GET && /api/permissions/check-code) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219978a78@71ebd1d9 +(GET && /api/permissions/count) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219978c90@49f82f95 +(POST && /api/permissions) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219978ea8@73c72238 +(PUT && /api/permissions/{id}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x00000222199790c0@3e6be3c1 +(DELETE && /api/permissions/{id}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x00000222199792d8@112188cc +(POST && /api/auth/phone/one-click-login) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x00000222199794f0@19ad99fa +(POST && /api/auth/phone/send-code) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219979708@5331be15 +(POST && /api/auth/phone/code-login) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219979920@32a53a59 +(POST && /api/member/auth/miniapp/login) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219979b38@2f5fb329 +(GET && /api/member/auth/mp/callback) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219979d50@5ac88d71 +(POST && /api/member/auth/mp/callback) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219979f68@457512b +(GET && /api/member/info) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221997a180@1f3bd40a +(PUT && /api/member/info) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221997a398@4dafea3f +(POST && /api/member/phone/bind) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221997a5b0@531245fe +(GET && /api/member/subscribe/status) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221997a7c8@6d7298be +(POST && /api/admin/member/{id}/phone) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221997a9e0@2c289a9e +(GET && /api/admin/member/{id}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221997abf8@1ee47336 +(PUT && /api/admin/member/{id}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221997ae10@5f18f8a1 +(GET && /api/admin/members) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221997b028@26d63c94 +(GET && /api/admin/members/all) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221997b240@4e42beba +(GET && /api/member-cards/active) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221997b458@73021987 +(GET && /api/member-cards/{memberCardId}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221997b670@6bc72ab6 +(POST && /api/member-cards) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221997b888@7ce299c6 +(POST && /api/member-card-records/purchase) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221997baa0@278e5f8e +(POST && /api/member-card-records/{recordId}/renew) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221997bcb8@44a3b4d9 +(POST && /api/member-card-records/{recordId}/use) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221997bed0@16e3a461 +(POST && /api/member-card-records/{recordId}/refund) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221997c0e8@7e2723d2 +(GET && /api/member-card-records/my-cards/{memberId}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221997c300@2b0061b7 +(GET && /api/member-card-records/{recordId}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221997c518@72dc0d72 +(POST && /api/member-card-records/process-expired) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221997c730@79176fe4 +(POST && /api/member-card-transactions) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221997c948@3ee9eaa7 +(GET && /api/member-card-transactions) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221997cb60@164c2914 +(GET && /api/member-card-transactions/member/{memberId}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221997cd78@4690132 +(GET && /api/member-card-transactions/card/{cardId}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221997cf90@46c33614 +(GET && /api/member-card-transactions/statistics/deduct/{cardId}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221997d1a8@378e50b9 +(GET && /api/member-card-transactions/statistics/renew) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221997d3c0@40fdcbd5 +(GET && /api/member-card-transactions/statistics/purchase/{memberId}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221997d5d8@c512269 +(GET && /api/groupCourse/list) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221997d7f0@537dc661 +(POST && /api/groupCourse/page) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221997da08@16b35f2f +(GET && /api/groupCourse/types) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221997dc20@383e8d4f +(GET && /api/groupCourse/types/search) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221997de38@3df9b24b +(GET && /api/groupCourse/types/categories) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221997e050@a355b +(GET && /api/groupCourse/types/category/{category}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221997e268@c4c4852 +(GET && /api/groupCourse/types/{id}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221997e480@4054f861 +(POST && /api/groupCourse/types) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221997e698@30cf61ca +(PUT && /api/groupCourse/types/{id}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221997e8b0@61be2a65 +(DELETE && /api/groupCourse/types/{id}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221997eac8@f5ee0d8 +(GET && /api/groupCourse/labels) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221997ece0@68ae86a2 +(GET && /api/groupCourse/labels/search) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221997eef8@23247b4c +(GET && /api/groupCourse/labels/{id}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221997f110@33d9904f +(GET && /api/groupCourse/types/{typeId}/labels) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221997f328@4b79eecc +(POST && /api/groupCourse/labels) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221997f540@57fefa97 +(PUT && /api/groupCourse/labels/{id}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221997f758@5700053f +(DELETE && /api/groupCourse/labels/{id}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221997f970@28a4ac78 +(POST && /api/groupCourse/types/{typeId}/labels) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221997fb88@2caf282e +(DELETE && /api/groupCourse/types/{typeId}/labels/{labelId}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x000002221997fda0@ca5456e +(DELETE && /api/groupCourse/types/{typeId}/labels) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219980000@5b5b9038 +(POST && /api/groupCourse/book) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219980218@56912047 +(POST && /api/groupCourse/booking/{bookingId}/cancel) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219980430@32a8ce3 +(GET && /api/groupCourse/bookings/member/{memberId}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219980648@258227dd +(GET && /api/groupCourse/bookings/course/{courseId}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219980860@225fb905 +(GET && /api/groupCourse/bookings/{bookingId}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219980a78@7d932611 +(GET && /api/groupCourse/recommend/list) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219980c90@2a09e0d4 +(GET && /api/groupCourse/recommend/active) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219980ea8@1e019687 +(GET && /api/groupCourse/recommend/{id}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x00000222199810c0@41c983d3 +(GET && /api/groupCourse/recommend/course/{courseId}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x00000222199812d8@250e9655 +(POST && /api/groupCourse/recommend) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x00000222199814f0@4064cd60 +(PUT && /api/groupCourse/recommend/{id}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219981708@5243ce45 +(DELETE && /api/groupCourse/recommend/{id}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219981920@5f1908c5 +(POST && /api/groupCourse/recommend/{id}/enable) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219981b38@e74914d +(POST && /api/groupCourse/recommend/{id}/disable) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219981d50@689d522e +(GET && /api/groupCourse/{id}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219981f68@24e0989e +(GET && /api/groupCourse/{id}/detail) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219982180@33a8f3e7 +(POST && /api/groupCourse) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219982398@19fce226 +(PUT && /api/groupCourse/{id}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x00000222199825b0@1bf2cf15 +(DELETE && /api/groupCourse/{id}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x00000222199827c8@3b76809d +(POST && /api/groupCourse/{id}/cancel) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x00000222199829e0@4fb56bdf +(POST && /api/groupCourse/signin/{memberId}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219982bf8@31a65ddd +(POST && /api/groupCourse/search) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219982e10@3ca1a69b +(POST && /api/checkIn) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219983028@33dcbbfa +(GET && /api/checkIn/qrcode) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219983240@57d3c5cd +(GET && /api/checkIn/records) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219983458@5418225f +(GET && /api/checkIn/records/{id}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219983670@65bcc339 +(GET && /api/checkIn/statistics) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219983888@3369a71f +(GET && /api/checkIn/daily-stats) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219983aa0@7adde112 +(GET && /api/checkIn/records/export) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219983cb8@6f422ecc +(GET && /api/datacount/summary) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219983ed0@6c648d16 +(GET && /api/datacount/member) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x00000222199840e8@12cb9eda +(GET && /api/datacount/booking) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219984300@70b38e0 +(GET && /api/datacount/signin) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219984518@79414283 +(GET && /api/datacount/history) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219984730@31857c80 +(GET && /api/datacount/export) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219984948@7ff61924 +(POST && /api/payment/create) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219984b60@2164f202 +(GET && /api/payment/{orderId}) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219984d78@5242e9f7 +(POST && /api/payment/{orderId}/refund) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x0000022219984f90@48b9ee43 +(POST && /api/payment/notify) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x00000222199851a8@7c9e149 +(POST && /api/payment/qrcode/create) -> cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x00000222199853c0@1bbf750 +2026-06-24T04:39:35.533+08:00 TRACE 7516 --- [gym-manage-api] [ main] o.s.w.r.f.s.s.RouterFunctionMapping : Mapped (GET && /api/exception-logs) -> cn.novalon.gym.manage.sys.config.ExceptionLogConfig$$Lambda/0x0000022219985a68@4473bbc1 +(GET && /api/exception-logs/{id}) -> cn.novalon.gym.manage.sys.config.ExceptionLogConfig$$Lambda/0x0000022219985c80@433f8efa +(GET && /api/exception-logs/username/{username}) -> cn.novalon.gym.manage.sys.config.ExceptionLogConfig$$Lambda/0x0000022219985e98@bb4fc7d +2026-06-24T04:39:35.678+08:00 TRACE 7516 --- [gym-manage-api] [ main] o.s.w.r.handler.SimpleUrlHandlerMapping : Mapped [/webjars/**] onto ResourceWebHandler [classpath [META-INF/resources/webjars/]] +2026-06-24T04:39:35.688+08:00 TRACE 7516 --- [gym-manage-api] [ main] o.s.w.r.handler.SimpleUrlHandlerMapping : Mapped [/**] onto ResourceWebHandler [classpath [META-INF/resources/], classpath [resources/], classpath [static/], classpath [public/]] +2026-06-24T04:39:35.693+08:00 TRACE 7516 --- [gym-manage-api] [ main] o.s.w.r.handler.SimpleUrlHandlerMapping : Mapped [/swagger-ui/**] onto ResourceWebHandler [classpath [META-INF/resources/webjars/swagger-ui/5.32.0/]] +2026-06-24T04:39:35.695+08:00 TRACE 7516 --- [gym-manage-api] [ main] o.s.w.r.handler.SimpleUrlHandlerMapping : Mapped [/swagger-ui/*swagger-initializer.js] onto ResourceWebHandler [classpath [META-INF/resources/webjars/swagger-ui/5.32.0/]] +2026-06-24T04:39:35.696+08:00 TRACE 7516 --- [gym-manage-api] [ main] o.s.w.r.handler.SimpleUrlHandlerMapping : Mapped [/webjars/*swagger-ui/**] onto ResourceWebHandler [classpath [META-INF/resources/webjars/]] +2026-06-24T04:39:35.697+08:00 TRACE 7516 --- [gym-manage-api] [ main] o.s.w.r.handler.SimpleUrlHandlerMapping : Mapped [/webjars/*swagger-ui/*swagger-initializer.js] onto ResourceWebHandler [classpath [META-INF/resources/webjars/]] +2026-06-24T04:39:35.699+08:00 TRACE 7516 --- [gym-manage-api] [ main] o.s.w.r.handler.SimpleUrlHandlerMapping : Mapped [/webjars/*swagger-ui/5.32.0/*swagger-initializer.js] onto ResourceWebHandler [classpath [META-INF/resources/webjars/]] +2026-06-24T04:39:35.703+08:00 DEBUG 7516 --- [gym-manage-api] [ main] o.s.w.r.handler.SimpleUrlHandlerMapping : Patterns [/webjars/**, /**, /swagger-ui/**, /swagger-ui/*swagger-initializer.js, /webjars/*swagger-ui/**, /webjars/*swagger-ui/*swagger-initializer.js, /webjars/*swagger-ui/5.32.0/*swagger-initializer.js] in 'resourceHandlerMapping' +2026-06-24T04:39:35.738+08:00 TRACE 7516 --- [gym-manage-api] [ main] o.s.w.r.handler.SimpleUrlHandlerMapping : Mapped [/ws] onto cn.novalon.gym.manage.notify.websocket.SysWebSocketHandler@6a022b0e +2026-06-24T04:39:35.743+08:00 DEBUG 7516 --- [gym-manage-api] [ main] o.s.w.r.handler.SimpleUrlHandlerMapping : Patterns [/ws] in 'webSocketHandlerMapping' +2026-06-24T04:39:36.492+08:00 INFO 7516 --- [gym-manage-api] [ main] o.s.b.a.e.web.EndpointLinksResolver : Exposing 5 endpoints beneath base path '/actuator' +2026-06-24T04:39:36.651+08:00 DEBUG 7516 --- [gym-manage-api] [ main] o.s.w.r.r.m.a.ControllerMethodResolver : ControllerAdvice beans: 0 @ModelAttribute, 0 @InitBinder, 2 @ExceptionHandler +2026-06-24T04:39:36.785+08:00 DEBUG 7516 --- [gym-manage-api] [ main] o.s.w.s.adapter.HttpWebHandlerAdapter : enableLoggingRequestDetails='false': form data and headers will be masked to prevent unsafe logging of potentially sensitive data +2026-06-24T04:39:40.851+08:00 INFO 7516 --- [gym-manage-api] [ main] org.flywaydb.core.FlywayExecutor : Database: jdbc:postgresql://localhost:55432/manage_system (PostgreSQL 18.4) +2026-06-24T04:39:40.880+08:00 WARN 7516 --- [gym-manage-api] [ main] o.f.c.internal.database.base.Database : Flyway upgrade recommended: PostgreSQL 18.4 is newer than this version of Flyway and support has not been tested. The latest supported version of PostgreSQL is 17. +2026-06-24T04:39:41.149+08:00 INFO 7516 --- [gym-manage-api] [ main] o.f.core.internal.command.DbMigrate : Current version of schema "public": 24 +2026-06-24T04:39:41.162+08:00 INFO 7516 --- [gym-manage-api] [ main] o.f.core.internal.command.DbMigrate : Schema "public" is up to date. No migration necessary. +2026-06-24T04:39:42.920+08:00 INFO 7516 --- [gym-manage-api] [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8084 (http) +2026-06-24T04:39:42.962+08:00 INFO 7516 --- [gym-manage-api] [ main] c.n.gym.manage.app.ManageApplication : Started ManageApplication in 44.04 seconds (process running for 46.777) +2026-06-24T04:39:43.276+08:00 INFO 7516 --- [gym-manage-api] [ main] c.n.g.m.g.initializer.QrCodeInitializer : ===== 开始检查团课二维码缺失情况 ===== +2026-06-24T04:39:43.542+08:00 INFO 7516 --- [gym-manage-api] [ main] c.n.gym.manage.app.ManageApplication : === 检查 IOperationLogService === +2026-06-24T04:39:43.543+08:00 INFO 7516 --- [gym-manage-api] [ main] c.n.gym.manage.app.ManageApplication : IOperationLogService 实现: cn.novalon.gym.manage.sys.core.service.impl.OperationLogService +2026-06-24T04:39:43.545+08:00 INFO 7516 --- [gym-manage-api] [ main] c.n.gym.manage.app.ManageApplication : === 检查已注册的 WebFilter === +2026-06-24T04:39:43.546+08:00 INFO 7516 --- [gym-manage-api] [ main] c.n.gym.manage.app.ManageApplication : WebFilter 总数: 4 +2026-06-24T04:39:43.548+08:00 INFO 7516 --- [gym-manage-api] [ main] c.n.gym.manage.app.ManageApplication : - org.springframework.security.web.server.WebFilterChainProxy (Order: 无) +2026-06-24T04:39:43.548+08:00 INFO 7516 --- [gym-manage-api] [ main] c.n.gym.manage.app.ManageApplication : - cn.novalon.gym.manage.sys.audit.OperationLogWebFilter (Order: 2147483647) +2026-06-24T04:39:43.549+08:00 INFO 7516 --- [gym-manage-api] [ main] c.n.gym.manage.app.ManageApplication : - cn.novalon.gym.manage.sys.filter.RateLimitFilter (Order: 无) +2026-06-24T04:39:43.550+08:00 INFO 7516 --- [gym-manage-api] [ main] c.n.gym.manage.app.ManageApplication : - cn.novalon.gym.manage.sys.security.JwtAuthenticationFilter (Order: 无) +2026-06-24T04:39:43.557+08:00 INFO 7516 --- [gym-manage-api] [ main] c.n.gym.manage.app.ManageApplication : 应用程序启动完成 +2026-06-24T04:39:45.053+08:00 DEBUG 7516 --- [gym-manage-api] [actor-tcp-nio-1] o.s.r2dbc.core.DefaultDatabaseClient : Executing SQL statement [SELECT group_course.course_name, group_course.coach_id, group_course.course_type, group_course.start_time, group_course.end_time, group_course.max_members, group_course.current_members, group_course.status, group_course.location, group_course.cover_image, group_course.description, group_course.point_card_amount, group_course.stored_value_amount, group_course.qr_code_path, group_course.id, group_course.create_by, group_course.update_by, group_course.created_at, group_course.updated_at, group_course.deleted_at FROM group_course WHERE group_course.deleted_at IS NULL] +2026-06-24T04:39:46.087+08:00 INFO 7516 --- [gym-manage-api] [actor-tcp-nio-1] c.n.g.m.g.c.GroupCourseConverter : 转换bean,entity-domain: +2026-06-24T04:39:46.113+08:00 INFO 7516 --- [gym-manage-api] [actor-tcp-nio-1] c.n.g.m.g.c.GroupCourseConverter : 转换bean,entity-domain: +2026-06-24T04:39:46.127+08:00 INFO 7516 --- [gym-manage-api] [actor-tcp-nio-1] c.n.g.m.g.c.GroupCourseConverter : 转换bean,entity-domain: +2026-06-24T04:39:46.131+08:00 INFO 7516 --- [gym-manage-api] [actor-tcp-nio-1] c.n.g.m.g.c.GroupCourseConverter : 转换bean,entity-domain: +2026-06-24T04:39:46.135+08:00 INFO 7516 --- [gym-manage-api] [actor-tcp-nio-1] c.n.g.m.g.c.GroupCourseConverter : 转换bean,entity-domain: +2026-06-24T04:39:46.141+08:00 INFO 7516 --- [gym-manage-api] [actor-tcp-nio-1] c.n.g.m.g.c.GroupCourseConverter : 转换bean,entity-domain: +2026-06-24T04:39:46.149+08:00 INFO 7516 --- [gym-manage-api] [actor-tcp-nio-1] c.n.g.m.g.c.GroupCourseConverter : 转换bean,entity-domain: +2026-06-24T04:39:46.154+08:00 INFO 7516 --- [gym-manage-api] [actor-tcp-nio-1] c.n.g.m.g.c.GroupCourseConverter : 转换bean,entity-domain: +2026-06-24T04:39:46.157+08:00 INFO 7516 --- [gym-manage-api] [actor-tcp-nio-1] c.n.g.m.g.c.GroupCourseConverter : 转换bean,entity-domain: +2026-06-24T04:39:46.159+08:00 INFO 7516 --- [gym-manage-api] [actor-tcp-nio-1] c.n.g.m.g.c.GroupCourseConverter : 转换bean,entity-domain: +2026-06-24T04:39:46.165+08:00 INFO 7516 --- [gym-manage-api] [actor-tcp-nio-1] c.n.g.m.g.c.GroupCourseConverter : 转换bean,entity-domain: +2026-06-24T04:39:46.169+08:00 INFO 7516 --- [gym-manage-api] [actor-tcp-nio-1] c.n.g.m.g.c.GroupCourseConverter : 转换bean,entity-domain: +2026-06-24T04:39:46.187+08:00 INFO 7516 --- [gym-manage-api] [actor-tcp-nio-1] c.n.g.m.g.c.GroupCourseConverter : 转换bean,entity-domain: +2026-06-24T04:39:46.200+08:00 INFO 7516 --- [gym-manage-api] [actor-tcp-nio-1] c.n.g.m.g.c.GroupCourseConverter : 转换bean,entity-domain: +2026-06-24T04:39:46.210+08:00 INFO 7516 --- [gym-manage-api] [actor-tcp-nio-1] c.n.g.m.g.c.GroupCourseConverter : 转换bean,entity-domain: +2026-06-24T04:39:46.214+08:00 INFO 7516 --- [gym-manage-api] [actor-tcp-nio-1] c.n.g.m.g.c.GroupCourseConverter : 转换bean,entity-domain: +2026-06-24T04:39:46.218+08:00 INFO 7516 --- [gym-manage-api] [actor-tcp-nio-1] c.n.g.m.g.c.GroupCourseConverter : 转换bean,entity-domain: +2026-06-24T04:39:46.221+08:00 INFO 7516 --- [gym-manage-api] [actor-tcp-nio-1] c.n.g.m.g.c.GroupCourseConverter : 转换bean,entity-domain: +2026-06-24T04:39:46.226+08:00 INFO 7516 --- [gym-manage-api] [actor-tcp-nio-1] c.n.g.m.g.c.GroupCourseConverter : 转换bean,entity-domain: +2026-06-24T04:39:46.236+08:00 INFO 7516 --- [gym-manage-api] [actor-tcp-nio-1] c.n.g.m.g.c.GroupCourseConverter : 转换bean,entity-domain: +2026-06-24T04:39:46.240+08:00 INFO 7516 --- [gym-manage-api] [actor-tcp-nio-1] c.n.g.m.g.c.GroupCourseConverter : 转换bean,entity-domain: +2026-06-24T04:39:46.243+08:00 INFO 7516 --- [gym-manage-api] [actor-tcp-nio-1] c.n.g.m.g.c.GroupCourseConverter : 转换bean,entity-domain: +2026-06-24T04:39:46.250+08:00 INFO 7516 --- [gym-manage-api] [actor-tcp-nio-1] c.n.g.m.g.c.GroupCourseConverter : 转换bean,entity-domain: +2026-06-24T04:39:46.257+08:00 INFO 7516 --- [gym-manage-api] [actor-tcp-nio-1] c.n.g.m.g.c.GroupCourseConverter : 转换bean,entity-domain: +2026-06-24T04:39:46.259+08:00 INFO 7516 --- [gym-manage-api] [actor-tcp-nio-1] c.n.g.m.g.c.GroupCourseConverter : 转换bean,entity-domain: +2026-06-24T04:39:46.262+08:00 INFO 7516 --- [gym-manage-api] [actor-tcp-nio-1] c.n.g.m.g.c.GroupCourseConverter : 转换bean,entity-domain: +2026-06-24T04:39:46.310+08:00 INFO 7516 --- [gym-manage-api] [actor-tcp-nio-1] c.n.g.m.g.initializer.QrCodeInitializer : ===== 团课二维码检查完毕,共补全 0 个缺失二维码 ===== +2026-06-24T04:40:30.204+08:00 TRACE 7516 --- [gym-manage-api] [nio-8084-exec-1] o.s.w.s.adapter.HttpWebHandlerAdapter : [5a95aafc] HTTP POST "/api/payment/qrcode/create", headers={masked} +2026-06-24T04:40:30.859+08:00 DEBUG 7516 --- [gym-manage-api] [ parallel-2] o.s.w.s.s.DefaultWebSessionManager : Created new WebSession. +2026-06-24T04:40:30.919+08:00 INFO 7516 --- [gym-manage-api] [ parallel-2] c.n.g.m.sys.audit.OperationLogWebFilter : WebFilter 拦截请求: POST /api/payment/qrcode/create +2026-06-24T04:40:30.921+08:00 INFO 7516 --- [gym-manage-api] [ parallel-2] c.n.g.m.sys.audit.OperationLogWebFilter : 未匹配到操作日志配置,跳过: POST /api/payment/qrcode/create +2026-06-24T04:40:30.945+08:00 INFO 7516 --- [gym-manage-api] [ parallel-2] c.n.g.m.sys.audit.OperationLogWebFilter : WebFilter 拦截请求: POST /api/payment/qrcode/create +2026-06-24T04:40:30.956+08:00 INFO 7516 --- [gym-manage-api] [ parallel-2] c.n.g.m.sys.audit.OperationLogWebFilter : 未匹配到操作日志配置,跳过: POST /api/payment/qrcode/create +2026-06-24T04:40:31.063+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.069+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.070+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.072+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.073+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.073+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.078+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/dictionaries" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.079+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "PUT" does not match against value "POST" +2026-06-24T04:40:31.081+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "DELETE" does not match against value "POST" +2026-06-24T04:40:31.083+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.084+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.091+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.092+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.093+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.094+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.095+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.096+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/users" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.096+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.101+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "PUT" does not match against value "POST" +2026-06-24T04:40:31.105+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "DELETE" does not match against value "POST" +2026-06-24T04:40:31.107+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.108+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/users/{id}/action/change-password" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.109+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.110+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/users/{id}/action/logical-delete" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.111+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.120+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/users/logical-delete" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.134+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.137+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/users/action/restore" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.138+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.142+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/users/{id}/action/restore" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.145+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.148+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.148+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/users/{id}/roles" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.151+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.154+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.166+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.171+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.174+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/menus" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.175+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "PUT" does not match against value "POST" +2026-06-24T04:40:31.176+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "DELETE" does not match against value "POST" +2026-06-24T04:40:31.177+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.177+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.179+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.180+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.180+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.181+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.182+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.183+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/roles" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.197+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "PUT" does not match against value "POST" +2026-06-24T04:40:31.221+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "DELETE" does not match against value "POST" +2026-06-24T04:40:31.227+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.231+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/roles/{id}/restore" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.233+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.234+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.235+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/roles/{id}/permissions" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.236+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.236+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.237+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.239+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.239+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/config" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.243+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "PUT" does not match against value "POST" +2026-06-24T04:40:31.245+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "DELETE" does not match against value "POST" +2026-06-24T04:40:31.246+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.247+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.248+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.249+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.250+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.251+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.252+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.252+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/logs/login" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.267+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.270+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.271+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.273+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.274+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.283+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/logs/exception" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.284+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.286+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.287+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.291+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.295+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.297+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.298+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/logs/operation" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.299+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.303+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/auth/login" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.305+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.306+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/auth/register" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.307+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.309+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/auth/logout" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.310+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.314+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.315+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.315+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.315+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.317+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/dict/types" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.318+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "PUT" does not match against value "POST" +2026-06-24T04:40:31.322+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "DELETE" does not match against value "POST" +2026-06-24T04:40:31.333+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.336+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.340+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.342+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.345+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/dict/data" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.346+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "PUT" does not match against value "POST" +2026-06-24T04:40:31.364+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "DELETE" does not match against value "POST" +2026-06-24T04:40:31.367+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.370+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.375+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.379+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.380+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/notices" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.382+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "PUT" does not match against value "POST" +2026-06-24T04:40:31.383+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "DELETE" does not match against value "POST" +2026-06-24T04:40:31.384+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.392+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.394+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.397+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.399+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.400+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.407+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/messages" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.409+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "PUT" does not match against value "POST" +2026-06-24T04:40:31.411+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "PUT" does not match against value "POST" +2026-06-24T04:40:31.416+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "DELETE" does not match against value "POST" +2026-06-24T04:40:31.423+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.430+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.439+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.443+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/files/upload" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.444+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.452+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.455+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.455+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.456+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "DELETE" does not match against value "POST" +2026-06-24T04:40:31.457+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.459+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.469+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.478+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.485+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.488+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.489+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/permissions" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.492+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "PUT" does not match against value "POST" +2026-06-24T04:40:31.499+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "DELETE" does not match against value "POST" +2026-06-24T04:40:31.505+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.510+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/auth/phone/one-click-login" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.515+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.522+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/auth/phone/send-code" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.523+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.525+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/auth/phone/code-login" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.526+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.528+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/member/auth/miniapp/login" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.535+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.537+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.539+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/member/auth/mp/callback" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.541+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.550+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "PUT" does not match against value "POST" +2026-06-24T04:40:31.555+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.557+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/member/phone/bind" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.569+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.570+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.573+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/admin/member/{id}/phone" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.585+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.591+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "PUT" does not match against value "POST" +2026-06-24T04:40:31.595+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.596+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.598+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.603+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.608+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.608+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/member-cards" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.609+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.609+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/member-card-records/purchase" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.610+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.614+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/member-card-records/{recordId}/renew" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.614+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.616+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/member-card-records/{recordId}/use" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.618+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.619+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/member-card-records/{recordId}/refund" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.621+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.625+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.629+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.630+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/member-card-records/process-expired" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.631+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.632+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/member-card-transactions" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.633+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.634+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.634+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.639+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.640+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.640+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.642+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.642+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.644+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/groupCourse/page" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.644+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.644+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.645+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.646+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.646+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.647+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.647+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/groupCourse/types" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.654+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "PUT" does not match against value "POST" +2026-06-24T04:40:31.657+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "DELETE" does not match against value "POST" +2026-06-24T04:40:31.658+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.659+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.661+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.667+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.671+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.673+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/groupCourse/labels" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.674+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "PUT" does not match against value "POST" +2026-06-24T04:40:31.674+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "DELETE" does not match against value "POST" +2026-06-24T04:40:31.676+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.678+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/groupCourse/types/{typeId}/labels" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.679+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "DELETE" does not match against value "POST" +2026-06-24T04:40:31.679+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "DELETE" does not match against value "POST" +2026-06-24T04:40:31.682+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.683+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/groupCourse/book" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.683+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.685+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/groupCourse/booking/{bookingId}/cancel" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.690+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.692+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.694+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.696+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.697+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.697+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.697+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.703+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.705+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/groupCourse/recommend" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.705+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "PUT" does not match against value "POST" +2026-06-24T04:40:31.706+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "DELETE" does not match against value "POST" +2026-06-24T04:40:31.709+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.710+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/groupCourse/recommend/{id}/enable" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.717+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.719+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/groupCourse/recommend/{id}/disable" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.720+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.722+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.723+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.724+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/groupCourse" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.725+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "PUT" does not match against value "POST" +2026-06-24T04:40:31.731+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "DELETE" does not match against value "POST" +2026-06-24T04:40:31.732+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.733+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/groupCourse/{id}/cancel" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.733+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.734+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/groupCourse/signin/{memberId}" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.740+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.741+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/groupCourse/search" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.743+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.744+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/checkIn" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.745+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.747+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.753+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.754+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.755+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.755+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.756+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.757+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.758+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.766+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.772+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.774+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.780+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.785+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/payment/create" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.786+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "GET" does not match against value "POST" +2026-06-24T04:40:31.788+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.795+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/payment/{orderId}/refund" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.797+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.799+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/payment/notify" does not match against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.799+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Method "POST" matches against value "POST" +2026-06-24T04:40:31.804+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.server.RequestPredicates : Pattern "/api/payment/qrcode/create" matches against value "/api/payment/qrcode/create" +2026-06-24T04:40:31.829+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.function.server.RouterFunctions : [5a95aafc] Matched (POST && /api/payment/qrcode/create) +2026-06-24T04:40:31.861+08:00 DEBUG 7516 --- [gym-manage-api] [ parallel-2] o.s.w.r.f.s.s.RouterFunctionMapping : [5a95aafc] Mapped to cn.novalon.gym.manage.app.config.SystemRouter$$Lambda/0x00000222199853c0@1bbf750 +2026-06-24T04:40:32.330+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] org.springframework.web.HttpLogging : [5a95aafc] Decoded [CreatePaymentRequest(memberId=null, orderType=MEMBER_CARD, goodsDesc=购买月卡, transAmt=100, tradeType=QRCODE, remark=会员卡类型ID:1, acctId=null, timeExpire=null, delayAcctFlag=null, feeFlag=null, limitPayType=null, channelNo=null, payScene=null, notifyUrl=null)] +2026-06-24T04:40:32.331+08:00 INFO 7516 --- [gym-manage-api] [ parallel-2] c.n.g.m.payment.handler.PaymentHandler : [Payment] 创建扫码支付订单: memberId=null, orderType=MEMBER_CARD, goodsDesc=购买月卡, transAmt=100 +2026-06-24T04:40:32.359+08:00 INFO 7516 --- [gym-manage-api] [ parallel-2] c.n.g.m.p.s.impl.PaymentServiceImpl : [Alipay] 扫码支付请求: orderId=20260624044032d77433b4, bizContent={"out_trade_no":"20260624044032d77433b4","total_amount":"0.01","subject":"购买月卡","timeout_express":"30m"} +2026-06-24T04:40:38.905+08:00 ERROR 7516 --- [gym-manage-api] [ parallel-2] sdk.comm.err : 2026-06-24 04:40:38^_^alipay.trade.precreate^_^null^_^172.27.254.144^_^Windows Server 2022^_^alipay-sdk-java-4.35.120.ALL^_^https://openapi-sandbox.dl.alipaydev.com/gateway.do?charset=UTF-8&method=alipay.trade.precreate&sign=JpHFUFyV8EXmvqxnUceUpXjuIojp0Q00xLkKTMFB0sLV%2FPJwJc4W7gO7xIp%2BllaLNb5AO1hAz90OA4d59HHR%2FLhZYWpYbDoYZAE8N6025eqFI3X8QcoiCcg3DYXKRNOJ5UKSxbcNnn%2BmRXB%2BBJU9KNkQqlkbmVzAmuaCg%2FDbRT4CYSQYgPs%2FuWYN8LAtarFI6ZuR97O99NR6kpLt%2BZQrWezDEq4NVL%2FMGPIrFkvvVUmry0NOL%2FxkIK0SQRouOTn9qkkW7NJMwevFH%2F99BJNMaTYx9pmec9yULECtkvCSU4KDcnUNTY7mSaS09HmJpBB9mvVLkBFlBCXusYMxij8cNQ%3D%3D¬ify_url=http%3A%2F%2Flocalhost%3A8084%2Fapi%2Fpayment%2Fnotify&version=1.0&app_id=9021000164699238&sign_type=RSA2×tamp=2026-06-24+04%3A40%3A32&alipay_sdk=alipay-sdk-java-4.35.120.ALL&format=json^_^^_^ 504 Gateway Time-out

504 Gateway Time-out

The gateway did not receive a timely response from the upstream server or application. Sorry for the inconvenience.
Please report this message and include the following information to us.
Thank you very much!

URL: http://openapi-sandbox.dl.alipaydev.com/gateway.do?charset=UTF-8&method=alipay.trade.precreate&sign=JpHFUFyV8EXmvqxnUceUpXjuIojp0Q00xLkKTMFB0sLV%2FPJwJc4W7gO7xIp%2BllaLNb5AO1hAz90OA4d59HHR%2FLhZYWpYbDoYZAE8N6025eqFI3X8QcoiCcg3DYXKRNOJ5UKSxbcNnn%2BmRXB%2BBJU9KNkQqlkbmVzAmuaCg%2FDbRT4CYSQYgPs%2FuWYN8LAtarFI6ZuR97O99NR6kpLt%2BZQrWezDEq4NVL%2FMGPIrFkvvVUmry0NOL%2FxkIK0SQRouOTn9qkkW7NJMwevFH%2F99BJNMaTYx9pmec9yULECtkvCSU4KDcnUNTY7mSaS09HmJpBB9mvVLkBFlBCXusYMxij8cNQ%3D%3D&notify_url=http%3A%2F%2Flocalhost%3A8084%2Fapi%2Fpayment%2Fnotify&version=1.0&app_id=9021000164699238&sign_type=RSA2&timestamp=2026-06-24+04%3A40%3A32&alipay_sdk=alipay-sdk-java-4.35.120.ALL&format=json
Server: openapi-59.rz21a.sandbox.alipay.net
Date: 2026/06/24 04:40:38

Powered by Tengine +2026-06-24T04:40:38.907+08:00 ERROR 7516 --- [gym-manage-api] [ parallel-2] c.n.g.m.p.s.impl.PaymentServiceImpl : [Alipay] 扫码支付异常: orderId=20260624044032d77433b4 + +com.alipay.api.AlipayApiException: java.io.IOException: + +504 Gateway Time-out + +

504 Gateway Time-out

+

The gateway did not receive a timely response from the upstream server or application. Sorry for the inconvenience.
+Please report this message and include the following information to us.
+Thank you very much!

+ + + + + + + + + + + + + +
URL:http://openapi-sandbox.dl.alipaydev.com/gateway.do?charset=UTF-8&method=alipay.trade.precreate&sign=JpHFUFyV8EXmvqxnUceUpXjuIojp0Q00xLkKTMFB0sLV%2FPJwJc4W7gO7xIp%2BllaLNb5AO1hAz90OA4d59HHR%2FLhZYWpYbDoYZAE8N6025eqFI3X8QcoiCcg3DYXKRNOJ5UKSxbcNnn%2BmRXB%2BBJU9KNkQqlkbmVzAmuaCg%2FDbRT4CYSQYgPs%2FuWYN8LAtarFI6ZuR97O99NR6kpLt%2BZQrWezDEq4NVL%2FMGPIrFkvvVUmry0NOL%2FxkIK0SQRouOTn9qkkW7NJMwevFH%2F99BJNMaTYx9pmec9yULECtkvCSU4KDcnUNTY7mSaS09HmJpBB9mvVLkBFlBCXusYMxij8cNQ%3D%3D&notify_url=http%3A%2F%2Flocalhost%3A8084%2Fapi%2Fpayment%2Fnotify&version=1.0&app_id=9021000164699238&sign_type=RSA2&timestamp=2026-06-24+04%3A40%3A32&alipay_sdk=alipay-sdk-java-4.35.120.ALL&format=json
Server:openapi-59.rz21a.sandbox.alipay.net
Date:2026/06/24 04:40:38
+
Powered by Tengine + + + + at com.alipay.api.AbstractAlipayClient.doPost(AbstractAlipayClient.java:1014) ~[alipay-sdk-java-4.35.120.ALL.jar!/:na] + at com.alipay.api.AbstractAlipayClient._execute(AbstractAlipayClient.java:921) ~[alipay-sdk-java-4.35.120.ALL.jar!/:na] + at com.alipay.api.AbstractAlipayClient.execute(AbstractAlipayClient.java:395) ~[alipay-sdk-java-4.35.120.ALL.jar!/:na] + at com.alipay.api.AbstractAlipayClient.execute(AbstractAlipayClient.java:377) ~[alipay-sdk-java-4.35.120.ALL.jar!/:na] + at com.alipay.api.AbstractAlipayClient.execute(AbstractAlipayClient.java:372) ~[alipay-sdk-java-4.35.120.ALL.jar!/:na] + at com.alipay.api.AbstractAlipayClient.execute(AbstractAlipayClient.java:366) ~[alipay-sdk-java-4.35.120.ALL.jar!/:na] + at cn.novalon.gym.manage.payment.service.impl.PaymentServiceImpl.alipayQrCodePay(PaymentServiceImpl.java:196) ~[gym-payment-1.0.0.jar!/:1.0.0] + at cn.novalon.gym.manage.payment.handler.PaymentHandler.lambda$createQrCodePayment$2(PaymentHandler.java:65) ~[gym-payment-1.0.0.jar!/:1.0.0] + at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:132) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:79) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:79) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:158) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onNext(FluxContextWrite.java:107) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxMapFuseable$MapFuseableConditionalSubscriber.onNext(FluxMapFuseable.java:299) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxFilterFuseable$FilterFuseableConditionalSubscriber.onNext(FluxFilterFuseable.java:337) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.Operators$BaseFluxToMonoOperator.completePossiblyEmpty(Operators.java:2096) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoCollect$CollectSubscriber.onComplete(MonoCollect.java:145) ~[reactor-core-3.7.17.jar!/:3.7.17] + at org.springframework.http.server.reactive.AbstractListenerReadPublisher$State.onAllDataRead(AbstractListenerReadPublisher.java:501) ~[spring-web-6.2.17.jar!/:6.2.17] + at org.springframework.http.server.reactive.AbstractListenerReadPublisher.handlePendingCompletionOrError(AbstractListenerReadPublisher.java:249) ~[spring-web-6.2.17.jar!/:6.2.17] + at org.springframework.http.server.reactive.AbstractListenerReadPublisher$State$4.onDataAvailable(AbstractListenerReadPublisher.java:404) ~[spring-web-6.2.17.jar!/:6.2.17] + at org.springframework.http.server.reactive.AbstractListenerReadPublisher.onDataAvailable(AbstractListenerReadPublisher.java:125) ~[spring-web-6.2.17.jar!/:6.2.17] + at org.springframework.http.server.reactive.ServletServerHttpRequest$RequestBodyPublisher.checkOnDataAvailable(ServletServerHttpRequest.java:347) ~[spring-web-6.2.17.jar!/:6.2.17] + at org.springframework.http.server.reactive.AbstractListenerReadPublisher.changeToDemandState(AbstractListenerReadPublisher.java:239) ~[spring-web-6.2.17.jar!/:6.2.17] + at org.springframework.http.server.reactive.AbstractListenerReadPublisher$State$2.request(AbstractListenerReadPublisher.java:354) ~[spring-web-6.2.17.jar!/:6.2.17] + at org.springframework.http.server.reactive.AbstractListenerReadPublisher$ReadSubscription.request(AbstractListenerReadPublisher.java:280) ~[spring-web-6.2.17.jar!/:6.2.17] + at reactor.core.publisher.Operators$BaseFluxToMonoOperator.request(Operators.java:2066) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxFilterFuseable$FilterFuseableConditionalSubscriber.request(FluxFilterFuseable.java:411) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxMapFuseable$MapFuseableConditionalSubscriber.request(FluxMapFuseable.java:360) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.request(FluxContextWrite.java:136) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoFlatMap$FlatMapMain.request(MonoFlatMap.java:194) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.set(Operators.java:2366) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onSubscribe(FluxOnErrorResume.java:74) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoFlatMap$FlatMapMain.onSubscribe(MonoFlatMap.java:117) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onSubscribe(FluxContextWrite.java:101) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxMapFuseable$MapFuseableConditionalSubscriber.onSubscribe(FluxMapFuseable.java:265) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxFilterFuseable$FilterFuseableConditionalSubscriber.onSubscribe(FluxFilterFuseable.java:305) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.Operators$BaseFluxToMonoOperator.onSubscribe(Operators.java:2050) ~[reactor-core-3.7.17.jar!/:3.7.17] + at org.springframework.http.server.reactive.AbstractListenerReadPublisher$State$1.subscribe(AbstractListenerReadPublisher.java:322) ~[spring-web-6.2.17.jar!/:6.2.17] + at org.springframework.http.server.reactive.AbstractListenerReadPublisher.subscribe(AbstractListenerReadPublisher.java:112) ~[spring-web-6.2.17.jar!/:6.2.17] + at reactor.core.publisher.FluxSource.subscribe(FluxSource.java:71) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:76) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoDeferContextual.subscribe(MonoDeferContextual.java:55) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:76) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:165) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:79) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:74) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoNext$NextSubscriber.onNext(MonoNext.java:82) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxConcatMapNoPrefetch$FluxConcatMapNoPrefetchSubscriber.innerNext(FluxConcatMapNoPrefetch.java:259) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxConcatMap$ConcatMapInner.onNext(FluxConcatMap.java:865) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:122) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxPeek$PeekSubscriber.onNext(FluxPeek.java:200) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:122) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoNext$NextSubscriber.onNext(MonoNext.java:82) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxConcatArray$ConcatArraySubscriber.onNext(FluxConcatArray.java:180) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoNext$NextSubscriber.onNext(MonoNext.java:82) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxConcatMapNoPrefetch$FluxConcatMapNoPrefetchSubscriber.innerNext(FluxConcatMapNoPrefetch.java:259) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxConcatMap$ConcatMapInner.onNext(FluxConcatMap.java:865) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxConcatMap$WeakScalarSubscription.request(FluxConcatMap.java:480) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.request(Operators.java:2330) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxConcatMapNoPrefetch$FluxConcatMapNoPrefetchSubscriber.request(FluxConcatMapNoPrefetch.java:339) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoNext$NextSubscriber.request(MonoNext.java:108) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxConcatArray$ConcatArraySubscriber.request(FluxConcatArray.java:256) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoNext$NextSubscriber.request(MonoNext.java:108) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxMap$MapSubscriber.request(FluxMap.java:164) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxPeek$PeekSubscriber.request(FluxPeek.java:138) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxMap$MapSubscriber.request(FluxMap.java:164) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.request(Operators.java:2330) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxConcatMapNoPrefetch$FluxConcatMapNoPrefetchSubscriber.request(FluxConcatMapNoPrefetch.java:339) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoNext$NextSubscriber.request(MonoNext.java:108) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.set(Operators.java:2366) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onSubscribe(Operators.java:2240) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoNext$NextSubscriber.onSubscribe(MonoNext.java:70) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxConcatMapNoPrefetch$FluxConcatMapNoPrefetchSubscriber.onSubscribe(FluxConcatMapNoPrefetch.java:164) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:201) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:83) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:76) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:53) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:76) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:53) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.Mono.subscribe(Mono.java:4576) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxFlatMap.trySubscribeScalarMap(FluxFlatMap.java:205) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoFlatMap.subscribeOrReturn(MonoFlatMap.java:53) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:63) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:53) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:76) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:53) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:76) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoDeferContextual.subscribe(MonoDeferContextual.java:55) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:53) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:76) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:53) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.Mono.subscribe(Mono.java:4576) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onComplete(FluxSwitchIfEmpty.java:82) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.onComplete(MonoPeekTerminal.java:299) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.onComplete(MonoPeekTerminal.java:299) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:155) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:74) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxFilter$FilterSubscriber.onNext(FluxFilter.java:113) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.onNext(MonoPeekTerminal.java:180) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxPeekFuseable$PeekFuseableConditionalSubscriber.onNext(FluxPeekFuseable.java:503) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.onNext(MonoPeekTerminal.java:180) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxDefaultIfEmpty$DefaultIfEmptySubscriber.onNext(FluxDefaultIfEmpty.java:122) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoNext$NextSubscriber.onNext(MonoNext.java:82) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxConcatMapNoPrefetch$FluxConcatMapNoPrefetchSubscriber.innerNext(FluxConcatMapNoPrefetch.java:259) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxConcatMap$ConcatMapInner.onNext(FluxConcatMap.java:865) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:158) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:122) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxFilter$FilterSubscriber.onNext(FluxFilter.java:113) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxPeekFuseable$PeekConditionalSubscriber.onNext(FluxPeekFuseable.java:854) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:74) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoNext$NextSubscriber.onNext(MonoNext.java:82) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxFilter$FilterSubscriber.onNext(FluxFilter.java:113) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxFlatMap$FlatMapMain.tryEmitScalar(FluxFlatMap.java:492) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxFlatMap$FlatMapMain.onNext(FluxFlatMap.java:424) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxPeekFuseable$PeekFuseableSubscriber.onNext(FluxPeekFuseable.java:210) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxIterable$IterableSubscription.slowPath(FluxIterable.java:335) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxIterable$IterableSubscription.request(FluxIterable.java:294) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxPeekFuseable$PeekFuseableSubscriber.request(FluxPeekFuseable.java:144) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxFlatMap$FlatMapMain.onSubscribe(FluxFlatMap.java:373) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxPeekFuseable$PeekFuseableSubscriber.onSubscribe(FluxPeekFuseable.java:178) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:201) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:83) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.Mono.subscribe(Mono.java:4576) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxConcatMapNoPrefetch$FluxConcatMapNoPrefetchSubscriber.onNext(FluxConcatMapNoPrefetch.java:207) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxIterable$IterableSubscription.slowPath(FluxIterable.java:335) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxIterable$IterableSubscription.request(FluxIterable.java:294) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxConcatMapNoPrefetch$FluxConcatMapNoPrefetchSubscriber.innerComplete(FluxConcatMapNoPrefetch.java:275) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxConcatMap$ConcatMapInner.onComplete(FluxConcatMap.java:889) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoFlatMap$FlatMapMain.onComplete(MonoFlatMap.java:189) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:144) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxFilter$FilterSubscriber.onComplete(FluxFilter.java:166) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxPeekFuseable$PeekConditionalSubscriber.onComplete(FluxPeekFuseable.java:940) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onComplete(FluxSwitchIfEmpty.java:85) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.Operators$ScalarSubscription.request(Operators.java:2572) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.set(Operators.java:2366) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onSubscribe(Operators.java:2240) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoJust.subscribe(MonoJust.java:55) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.Mono.subscribe(Mono.java:4576) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onComplete(FluxSwitchIfEmpty.java:82) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoNext$NextSubscriber.onComplete(MonoNext.java:102) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxFilter$FilterSubscriber.onComplete(FluxFilter.java:166) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxFlatMap$FlatMapMain.checkTerminated(FluxFlatMap.java:850) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxFlatMap$FlatMapMain.drainLoop(FluxFlatMap.java:612) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxFlatMap$FlatMapMain.drain(FluxFlatMap.java:592) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxFlatMap$FlatMapMain.request(FluxFlatMap.java:349) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxFilter$FilterSubscriber.request(FluxFilter.java:186) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoNext$NextSubscriber.request(MonoNext.java:108) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.request(Operators.java:2330) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxPeekFuseable$PeekConditionalSubscriber.request(FluxPeekFuseable.java:783) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxFilter$FilterSubscriber.request(FluxFilter.java:186) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxMap$MapSubscriber.request(FluxMap.java:164) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoFlatMap$FlatMapMain.request(MonoFlatMap.java:194) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.request(Operators.java:2330) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxConcatMapNoPrefetch$FluxConcatMapNoPrefetchSubscriber.request(FluxConcatMapNoPrefetch.java:339) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoNext$NextSubscriber.request(MonoNext.java:108) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxDefaultIfEmpty$DefaultIfEmptySubscriber.request(FluxDefaultIfEmpty.java:98) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.request(MonoPeekTerminal.java:139) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxPeekFuseable$PeekFuseableConditionalSubscriber.request(FluxPeekFuseable.java:437) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.request(MonoPeekTerminal.java:139) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxFilter$FilterSubscriber.request(FluxFilter.java:186) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.set(Operators.java:2366) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onSubscribe(Operators.java:2240) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxFilter$FilterSubscriber.onSubscribe(FluxFilter.java:85) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.onSubscribe(MonoPeekTerminal.java:152) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxPeekFuseable$PeekFuseableConditionalSubscriber.onSubscribe(FluxPeekFuseable.java:471) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.onSubscribe(MonoPeekTerminal.java:152) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.Operators$BaseFluxToMonoOperator.onSubscribe(Operators.java:2050) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoNext$NextSubscriber.onSubscribe(MonoNext.java:70) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxConcatMapNoPrefetch$FluxConcatMapNoPrefetchSubscriber.onSubscribe(FluxConcatMapNoPrefetch.java:164) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:201) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:83) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:76) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoDeferContextual.subscribe(MonoDeferContextual.java:55) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:76) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:53) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:76) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:53) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.Mono.subscribe(Mono.java:4576) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.subscribeNext(MonoIgnoreThen.java:265) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoIgnoreThen.subscribe(MonoIgnoreThen.java:51) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.Mono.subscribe(Mono.java:4576) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onComplete(FluxSwitchIfEmpty.java:82) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxFilter$FilterSubscriber.onComplete(FluxFilter.java:166) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxPeekFuseable$PeekConditionalSubscriber.onComplete(FluxPeekFuseable.java:940) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onComplete(FluxSwitchIfEmpty.java:85) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.Operators$ScalarSubscription.request(Operators.java:2572) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.set(Operators.java:2366) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onSubscribe(Operators.java:2240) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoJust.subscribe(MonoJust.java:55) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.Mono.subscribe(Mono.java:4576) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onComplete(FluxSwitchIfEmpty.java:82) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoNext$NextSubscriber.onComplete(MonoNext.java:102) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxFilter$FilterSubscriber.onComplete(FluxFilter.java:166) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxFlatMap$FlatMapMain.checkTerminated(FluxFlatMap.java:850) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxFlatMap$FlatMapMain.drainLoop(FluxFlatMap.java:612) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxFlatMap$FlatMapMain.drain(FluxFlatMap.java:592) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxFlatMap$FlatMapMain.onComplete(FluxFlatMap.java:469) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxPeekFuseable$PeekFuseableSubscriber.onComplete(FluxPeekFuseable.java:277) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxIterable$IterableSubscription.slowPath(FluxIterable.java:357) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxIterable$IterableSubscription.request(FluxIterable.java:294) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxPeekFuseable$PeekFuseableSubscriber.request(FluxPeekFuseable.java:144) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxFlatMap$FlatMapMain.onSubscribe(FluxFlatMap.java:373) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxPeekFuseable$PeekFuseableSubscriber.onSubscribe(FluxPeekFuseable.java:178) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:201) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:83) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:76) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:53) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:165) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.Operators$BaseFluxToMonoOperator.completePossiblyEmpty(Operators.java:2096) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxDefaultIfEmpty$DefaultIfEmptySubscriber.onComplete(FluxDefaultIfEmpty.java:134) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:144) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:144) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxFilter$FilterSubscriber.onComplete(FluxFilter.java:166) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxMap$MapConditionalSubscriber.onComplete(FluxMap.java:275) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1865) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoCacheTime$CoordinatorSubscriber.signalCached(MonoCacheTime.java:337) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoCacheTime$CoordinatorSubscriber.onNext(MonoCacheTime.java:354) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxPeek$PeekSubscriber.onNext(FluxPeek.java:200) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:74) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.FluxPeekFuseable$PeekFuseableSubscriber.onNext(FluxPeekFuseable.java:210) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.onNext(MonoPeekTerminal.java:180) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.publisher.MonoPublishOn$PublishOnSubscriber.run(MonoPublishOn.java:181) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.scheduler.SchedulerTask.call(SchedulerTask.java:68) ~[reactor-core-3.7.17.jar!/:3.7.17] + at reactor.core.scheduler.SchedulerTask.call(SchedulerTask.java:28) ~[reactor-core-3.7.17.jar!/:3.7.17] + at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317) ~[na:na] + at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304) ~[na:na] + at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144) ~[na:na] + at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642) ~[na:na] + at java.base/java.lang.Thread.run(Thread.java:1583) ~[na:na] +Caused by: java.io.IOException: + +504 Gateway Time-out + +

504 Gateway Time-out

+

The gateway did not receive a timely response from the upstream server or application. Sorry for the inconvenience.
+Please report this message and include the following information to us.
+Thank you very much!

+ + + + + + + + + + + + + +
URL:http://openapi-sandbox.dl.alipaydev.com/gateway.do?charset=UTF-8&method=alipay.trade.precreate&sign=JpHFUFyV8EXmvqxnUceUpXjuIojp0Q00xLkKTMFB0sLV%2FPJwJc4W7gO7xIp%2BllaLNb5AO1hAz90OA4d59HHR%2FLhZYWpYbDoYZAE8N6025eqFI3X8QcoiCcg3DYXKRNOJ5UKSxbcNnn%2BmRXB%2BBJU9KNkQqlkbmVzAmuaCg%2FDbRT4CYSQYgPs%2FuWYN8LAtarFI6ZuR97O99NR6kpLt%2BZQrWezDEq4NVL%2FMGPIrFkvvVUmry0NOL%2FxkIK0SQRouOTn9qkkW7NJMwevFH%2F99BJNMaTYx9pmec9yULECtkvCSU4KDcnUNTY7mSaS09HmJpBB9mvVLkBFlBCXusYMxij8cNQ%3D%3D&notify_url=http%3A%2F%2Flocalhost%3A8084%2Fapi%2Fpayment%2Fnotify&version=1.0&app_id=9021000164699238&sign_type=RSA2&timestamp=2026-06-24+04%3A40%3A32&alipay_sdk=alipay-sdk-java-4.35.120.ALL&format=json
Server:openapi-59.rz21a.sandbox.alipay.net
Date:2026/06/24 04:40:38
+
Powered by Tengine + + + + at com.alipay.api.internal.util.WebUtils.getResponseAsString(WebUtils.java:520) ~[alipay-sdk-java-4.35.120.ALL.jar!/:na] + at com.alipay.api.internal.util.WebUtils.doPost(WebUtils.java:181) ~[alipay-sdk-java-4.35.120.ALL.jar!/:na] + at com.alipay.api.internal.util.WebUtils.doPost(WebUtils.java:142) ~[alipay-sdk-java-4.35.120.ALL.jar!/:na] + at com.alipay.api.AbstractAlipayClient.doPost(AbstractAlipayClient.java:1002) ~[alipay-sdk-java-4.35.120.ALL.jar!/:na] + ... 228 common frames omitted + +2026-06-24T04:40:39.163+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] org.springframework.web.HttpLogging : [5a95aafc] Encoding [ApiResponse(code=500, message=null, data=null)] +2026-06-24T04:40:39.370+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] o.s.w.s.adapter.HttpWebHandlerAdapter : [5a95aafc] Completed 200 OK, headers={masked} +2026-06-24T04:40:39.374+08:00 TRACE 7516 --- [gym-manage-api] [ parallel-2] org.springframework.web.HttpLogging : [5a95aafc] onComplete diff --git a/gym-manage-api/manage-app/pom.xml b/gym-manage-api/manage-app/pom.xml index 6e95ea5..beeccbe 100644 --- a/gym-manage-api/manage-app/pom.xml +++ b/gym-manage-api/manage-app/pom.xml @@ -48,6 +48,11 @@ gym-checkIn ${project.version} + + cn.novalon.gym.manage + gym-payment + ${project.version} + cn.novalon.gym.manage gym-dataCount 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..9ec7cd4 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 @@ -17,6 +17,7 @@ import cn.novalon.gym.manage.member.handler.MemberHandler; import cn.novalon.gym.manage.member.handler.WechatAuthHandler; import cn.novalon.gym.manage.notify.handler.SysNoticeHandler; import cn.novalon.gym.manage.notify.handler.SysUserMessageHandler; +import cn.novalon.gym.manage.payment.handler.PaymentHandler; import cn.novalon.gym.manage.sys.handler.auth.PasswordDiagnosticHandler; import cn.novalon.gym.manage.sys.handler.auth.SysAuthHandler; import cn.novalon.gym.manage.sys.handler.config.SysConfigHandler; @@ -78,7 +79,8 @@ public class SystemRouter { CourseLabelHandler courseLabelHandler, CheckInHandler checkInHandler, DataStatisticsHandler dataStatisticsHandler, - PhoneAuthHandler phoneAuthHandler) { + PhoneAuthHandler phoneAuthHandler, + PaymentHandler paymentHandler) { return route() // ========== 诊断路由 ========== @@ -360,6 +362,19 @@ public class SystemRouter { .GET("/api/datacount/history", dataStatisticsHandler::queryHistoricalStatistics) .GET("/api/datacount/export", dataStatisticsHandler::exportStatistics) + // ======================================== + // ========== 支付模块路由 ================= + // ======================================== + + // ===== 支付宝App支付 ===== + .POST("/api/payment/create", paymentHandler::createPayment) + .GET("/api/payment/{orderId}", paymentHandler::getPaymentStatus) + .POST("/api/payment/{orderId}/refund", paymentHandler::refundPayment) + .POST("/api/payment/notify", paymentHandler::alipayNotify) + + // ===== 支付宝扫码支付 ===== + .POST("/api/payment/qrcode/create", paymentHandler::createQrCodePayment) + .build(); } } 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..c696a05 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 @@ -2,9 +2,9 @@ spring: cache: type: none r2dbc: - url: r2dbc:postgresql://localhost:5432/manage_system - username: postgres - password: 123456 + url: r2dbc:postgresql://localhost:55432/manage_system + username: novalon + password: novalon123 pool: initial-size: 5 max-size: 20 @@ -12,13 +12,13 @@ spring: max-life-time: 30m acquire-timeout: 3s flyway: - url: jdbc:postgresql://localhost:5432/manage_system - user: postgres - password: 123456 - enabled: false + url: jdbc:postgresql://localhost:55432/manage_system + user: novalon + password: novalon123 + enabled: true locations: classpath:db/migration baseline-on-migrate: true - validate-on-migrate: true + validate-on-migrate: false jwt: diff --git a/gym-manage-api/manage-app/src/main/resources/application-local.yml b/gym-manage-api/manage-app/src/main/resources/application-local.yml index e1035f1..f5b8e75 100644 --- a/gym-manage-api/manage-app/src/main/resources/application-local.yml +++ b/gym-manage-api/manage-app/src/main/resources/application-local.yml @@ -19,11 +19,11 @@ spring: password: 123456 driver-class-name: org.postgresql.Driver flyway: - enabled: false + enabled: true locations: classpath:db/migration baseline-on-migrate: true baseline-version: 0 - validate-on-migrate: true + validate-on-migrate: false sql: init: mode: always diff --git a/gym-manage-api/manage-app/src/main/resources/application.yml b/gym-manage-api/manage-app/src/main/resources/application.yml index e1fe965..7275251 100644 --- a/gym-manage-api/manage-app/src/main/resources/application.yml +++ b/gym-manage-api/manage-app/src/main/resources/application.yml @@ -15,9 +15,9 @@ spring: exclude: - org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration r2dbc: - url: r2dbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_NAME:manage_system} - username: ${DB_USERNAME:postgres} - password: ${DB_PASSWORD:123456} + url: r2dbc:postgresql://${DB_HOST:localhost}:${DB_PORT:55432}/${DB_NAME:manage_system} + username: ${DB_USERNAME:novalon} + password: ${DB_PASSWORD:novalon123} pool: initial-size: 10 max-size: 50 @@ -25,16 +25,16 @@ spring: max-life-time: 1h acquire-timeout: 5s datasource: - url: jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_NAME:manage_system} - username: ${DB_USERNAME:postgres} - password: ${DB_PASSWORD:123456} + url: jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:55432}/${DB_NAME:manage_system} + username: ${DB_USERNAME:novalon} + password: ${DB_PASSWORD:novalon123} driver-class-name: org.postgresql.Driver flyway: - enabled: false + enabled: true locations: classpath:db/migration baseline-on-migrate: true baseline-version: 0 - validate-on-migrate: true + validate-on-migrate: false security: user: name: disabled diff --git a/gym-manage-api/manage-app/src/main/resources/keys.properties b/gym-manage-api/manage-app/src/main/resources/keys.properties index 086c9b0..5dfd3ab 100644 --- a/gym-manage-api/manage-app/src/main/resources/keys.properties +++ b/gym-manage-api/manage-app/src/main/resources/keys.properties @@ -1,2 +1,6 @@ huifu.private-key=MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCNUdQE59I4bV1dIgLs2IRN3Wl8KI2yS4KpTUQWrvvlUcBTztB8rIeN5lr6Yv4VsZ1TG0FAb0D80JjxEQ1+0HizYNOQ1h2935v2r8h7rx3VTLbMOkmsg8Lb8LQRTbPaJOZfsIZMzBytYiRzWunHaMlCr800EA3q5NMj9VjHQuamxKqyzyHqfkIjA/sZ8q9atVjn8ahUjPKdrGA8b79HexHhCgOSLdK+fWw0eMCsWWYP2qECLsvZ+tjfvqSBXx1kg7womwT1VBCf+0Dx+jJPKR3mxQfz2szoucJYuXRo55kA6yCwoeNsjanLDRkPBSy3NHdKrffP6YODhRHG6KHyayyNAgMBAAECggEATIR69TEETVNCEzRgOxe9A2AYRoa6ukhSdhMFA/c5IuCR747yqh7MwtNwfVRuWRazpZUDTr0uhfT4asad9QUx5YZO54RX1EAn9XkWZ4nY8G46J/iDfapWLrp09U2KTVpfdn5hKWH3QRX7wI4AON2O49HGnSL4NjAx9q1YpYOe2bqevxmB4uD9vR/FHMjZ2qWzPaL7pId98x4DCJCiZctBQqb7gxR8EseD1Ddn2HbH5DEUjoxz2umtL+zrcGKDFBy4kM3r+10FP5jFG4fJfZFQwrzMTEkXJriau9DMRTQ83rWHzNvhdTNUuyqah74ez5V1piVEOXCPudzFsMttD2DyzQKBgQDokQH3WtcQqwFTUo+uWVVup4NoQzNZpEGIHK17afRsf9YnkoJVqjhywq1dhQT+yuyW9geJJKWpHQIAS4gGudhNK+j6d//bbvvbkOmtsdWyZ6Z2EmY8JrPkHdHAcadNJX7vrIc1xPeyci4AHhErDHc3hhD5njuSLx3DUp4iODNxKwKBgQCbjyHvP90YGjh49DWklh2NJgR2+OR6Fd0afdmwtGKeCUP5l2To6rfLBbcSRIWCON4iLME5wLLdHcm5wAQukC4dsUB5iZffp21+7hrrwUw/G0MUzFudfhZkjsDU5i+FwnVMqmsLJPUsjJTOfKsJdA2DjnwfMcgxw9FeClGveSFNJwKBgQDCpjuDECDY7oeZeYyQXGzIxKOTbEtaR8Qha/83QCM3fHd9f35evK2qP45iq6bWqnkCkMEV4/pTZNf77zvWhU2oqYvBtxYKTwW1a8BphGJbg60rPZMb3TjLQLoB3B4uz6dCaqBwPH8kd7RQnNm5siFF84vZoLozTAQZKtj3wxorKQKBgQCGKnEONHqwaw0B5T7O8VoTfxKiug/07B6C1sCGk03rF/q0rkquSKK0S/2Vl9u+cOXFe+w7r2OVKjfuKRpyPpBHs7T0HiQLFhBuRVaat2DXnN/CdG8f6rvNhwHxnYanSwx4TxN7zShYf/doEEZEJP/y01ViYkFUCpvtC+FgAo0iSQKBgFrkJGxPo6T7i5ZF7VoZWdARnypfbOl4gF9GMrXykk0fnKBDTof9Bg34a8OX54cf4yEH1ifZzVtNyzPTH9FxV88TksWRouqi1Uo6///kJI6gXROspE91TpNtNGHPgLhebA83WlFtS/H7i5G6RO+6KO0uflSImfy3Owp/ChyFUGmn -huifu.public-key=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjVHUBOfSOG1dXSIC7NiETd1pfCiNskuCqU1EFq775VHAU87QfKyHjeZa+mL+FbGdUxtBQG9A/NCY8RENftB4s2DTkNYdvd+b9q/Ie68d1Uy2zDpJrIPC2/C0EU2z2iTmX7CGTMwcrWIkc1rpx2jJQq/NNBAN6uTTI/VYx0LmpsSqss8h6n5CIwP7GfKvWrVY5/GoVIzynaxgPG+/R3sR4QoDki3Svn1sNHjArFlmD9qhAi7L2frY376kgV8dZIO8KJsE9VQQn/tA8foyTykd5sUH89rM6LnCWLl0aOeZAOsgsKHjbI2pyw0ZDwUstzR3Sq33z+mDg4URxuih8mssjQIDAQAB \ No newline at end of file +huifu.public-key=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjVHUBOfSOG1dXSIC7NiETd1pfCiNskuCqU1EFq775VHAU87QfKyHjeZa+mL+FbGdUxtBQG9A/NCY8RENftB4s2DTkNYdvd+b9q/Ie68d1Uy2zDpJrIPC2/C0EU2z2iTmX7CGTMwcrWIkc1rpx2jJQq/NNBAN6uTTI/VYx0LmpsSqss8h6n5CIwP7GfKvWrVY5/GoVIzynaxgPG+/R3sR4QoDki3Svn1sNHjArFlmD9qhAi7L2frY376kgV8dZIO8KJsE9VQQn/tA8foyTykd5sUH89rM6LnCWLl0aOeZAOsgsKHjbI2pyw0ZDwUstzR3Sq33z+mDg4URxuih8mssjQIDAQAB + +# 支付宝沙箱配置 +ALIPAY_APP_PRIVATE_KEY=MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCDKDW/bBiA8oclszYZmLIAS/yR4p47UGMR0yfsbrTgXwikHZp3YUhsNn2l8Ks2izgfciSwtxaKThnhYoK3h9toC8s09+641iFSq3yHg/vYuaEXMJzlk3Ilg//GFSXcB8mntAEX+xZE5NVyFmmx21OcoLH0kAwwwc9TgLtw71Ix6H6BgVy9oDozbi/ZlpVUNhODqUt9m/Spq7sdrdK78zW63JsqekszgdzyCdJPgVk7WRl4DWzJElT5eA1RKfiykAlxlohLLksU655nrdvRc9ZastRy+pVLGsOwrEDGyzNt6L+9dzKa/kdYSYVigHL7u7RZoUUDCfqJurLLfDM93/brAgMBAAECggEAS7zrlLfCSqxCuNWNVyijGaLHniLkRtI7824hLtobHzohzku+CFQoMz0gP7QD2sJ4TUhnwZhorsM9FLcDTyJn4+RzmwnVU+1rXsbiaYsg4t0HFlIfOD91+g4IpIVP0Ii6vVooC4YWLQCL9Y7VyDwkQ11Uhiqsr3cr10eOdj/tEh4fdC1/eWPvxFUKbIcMEI7DtBe5Y6jwkQ569kFrYsBjFnMWxftym1C/GvUIIhUP+2QWN5SmEcABlUPV4LbgwmkfSkwIvC4qcSR3+irM1Z4jikGtRPrxwVc3I7BjJZ+aLqLL89FL10X96pAq/NvjMS6lC5cumStEj483VjrEZQbOIQKBgQDRYGg4oCOaHR+cRvosQLYA89thzmFaB+MhKkKV9HWbCO/lM4jLX+nxNwPIeKmg9rfxAUuUnVpaLEgfIlQ6awR4bfu21fuSl40HFNSkJct9yWbT3T9dDov7Y1dFexFO59t+s+8/67wO5PP6FBGbPQu3bRLsAfVCeDm+7Tzv73T1EQKBgQCgXN57dVO0yo9jXZFfp+70Dhth9BonTGOZ+s2Wg9yCp8NmHeQ3fUGvWm0yCGCViw23YpYpfXdiRLm4kHe4oRR8xaWpTzdsq/9R7EmkcJc7GxSGCu/uEAcVquTgMol+lP6a5glQNmuCY4gooGs0ASfoBTX7i1+5kj06URRWnsO8OwKBgQC6u2GYBvJZk1nK1d5uszPfO51P6HEYi2o8/OSvuntczqatYr3ArAUH1Unxc/PNE9zO+5m9rGyVeWLFKae3BhTRlz1kPgmFHtZvF1viHbcTsxibIXpOcxfTFzERTYYNOmme4bkh6AsruXQCd/2P1uzpJUU36TMkmfbeWdT8JDLKEQKBgBJcj9zzAjKhPunRvOOHBwVK2DfPC2+Uf5MR9JhXYNhU/TqkEY2I+gxp/jbMXsLAuUFWOHtnslO+KsXHRO4PA5mFyAIq393Bk+p9c7EwcyCBaUgv2GkZzLXea3aAUt37kWuLC2Xz1TuIyf5c3+mEeF1Iu3Wh78P/yqLyxbFlXGQhAoGBAJ+3Ul8bwNKeTus9HGZkBdFylD93krnvb3AVwQBl8PzoBftPICsayOeTzKeYEza5wRVjw5+1zT/jz/hnCiNMFhxWfQ9/66Do7ae4bIsNm0cqnDFF6aIiCOu3PSDjMCgGGVnMcMSjmO5nIUQw2YHEloIzC8wi1XmxfLJBBcKnly2D +ALIPAY_ALIPAY_PUBLIC_KEY=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjZ/rlsJERPZ+GD6UzMYc6n2Rh8m0QV9Z39Iro6eMfxPaERMlKwhJpgmDWXMcpREmwMyoTCK77nLgNLRdc72XGR58aZFsAAQCs7V0kVtUf+IXtfQNNgY4c4tvtLH8E37vJTFBihZ8/UI+XOuX4xf/o+pfK9o2cqaVSt5E9kOrzv8hwDgG9vqOt2beqgETjeKYUwX2cWcAeu0xKoQW6JmzDje9/c7DAtLipmIDFrWVSzu8BFJqFzArv2u84pyDnuaLrqQ5rz3ktbn0QirbGlL5k/6E3CUB55CCF8+jEzQsjQUGQlTch9Dl7XAJLT8Lh1q2bat7KievEa1mNfzJmG5MDQIDAQAB \ No newline at end of file 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..61bb6c5 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 @@ -35,3 +35,14 @@ payment: query-url: "https://api.huifu.com/v4/trade/payment/query" refund-url: "https://api.huifu.com/v4/trade/refund" notify-url: "http://localhost:8084/api/payment/notify" + +# 支付宝配置(沙箱环境) +alipay: + app-id: "9021000164699238" + app-private-key: ${ALIPAY_APP_PRIVATE_KEY} + alipay-public-key: ${ALIPAY_ALIPAY_PUBLIC_KEY} + sign-type: "RSA2" + charset: "UTF-8" + gateway: "https://openapi-sandbox.dl.alipaydev.com/gateway.do" + notify-url: "http://localhost:8084/api/payment/notify" + sandbox: true diff --git a/gym-manage-api/manage-app/src/test/resources/application-test.yml b/gym-manage-api/manage-app/src/test/resources/application-test.yml index 8d11187..57a6741 100644 --- a/gym-manage-api/manage-app/src/test/resources/application-test.yml +++ b/gym-manage-api/manage-app/src/test/resources/application-test.yml @@ -9,10 +9,7 @@ spring: max-size: 10 flyway: - enabled: true - locations: classpath:db/migration - baseline-on-migrate: true - validate-on-migrate: true + enabled: false sql: init: diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V15__Create_GroupCourse_Type_table.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V10__Create_GroupCourse_Type_table.sql similarity index 100% rename from gym-manage-api/manage-db/src/main/resources/db/migration/V15__Create_GroupCourse_Type_table.sql rename to gym-manage-api/manage-db/src/main/resources/db/migration/V10__Create_GroupCourse_Type_table.sql diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V10__Insert_Booking_Test_Data.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V10__Insert_Booking_Test_Data.sql deleted file mode 100644 index c6c4452..0000000 --- a/gym-manage-api/manage-db/src/main/resources/db/migration/V10__Insert_Booking_Test_Data.sql +++ /dev/null @@ -1,86 +0,0 @@ --- V10: 团课预约业务场景测试数据 --- 测试场景: --- 1. 用户A刚刚注册,在没有任何会员卡的情况下申请团课预约 --- 2. 用户B已有时长卡,但已经过期 --- 3. 会员C有时长卡,未过期 --- 4. 会员D有次数卡,但次数卡耗尽次数 --- 5. 会员E有次数卡,且次数充足 --- 6. 会员F有储值卡,但余额不足以支付其预约的团课 --- 7. 会员G有储值卡,余额充足 - --- ============================================ --- 1. 创建测试会员用户 (A-G) --- ============================================ - -INSERT INTO member_user (member_no, nickname, phone, gender, is_deleted, created_at, updated_at) VALUES - ('MEM_TEST_A', '用户A_无卡新用户', '13800001001', 1, false, '2026-06-02 10:00:00', '2026-06-02 10:00:00'), - ('MEM_TEST_B', '用户B_过期时长卡', '13800001002', 1, false, '2026-05-01 10:00:00', '2026-05-01 10:00:00'), - ('MEM_TEST_C', '会员C_有效时长卡', '13800001003', 2, false, '2026-05-01 10:00:00', '2026-05-01 10:00:00'), - ('MEM_TEST_D', '会员D_次数用尽', '13800001004', 1, false, '2026-05-01 10:00:00', '2026-05-01 10:00:00'), - ('MEM_TEST_E', '会员E_次数充足', '13800001005', 2, false, '2026-05-01 10:00:00', '2026-05-01 10:00:00'), - ('MEM_TEST_F', '会员F_储值卡余额不足', '13800001006', 1, false, '2026-05-01 10:00:00', '2026-05-01 10:00:00'), - ('MEM_TEST_G', '会员G_储值卡余额充足', '13800001007', 2, false, '2026-05-01 10:00:00', '2026-05-01 10:00:00'); - --- ============================================ --- 2. 团课课程测试数据(用于预约测试) --- ============================================ - --- 团课A: 收费类型为次数卡(点卡额度=1) -INSERT INTO group_course (course_name, coach_id, course_type, start_time, end_time, max_members, current_members, status, location, description, point_card_amount, stored_value_amount, create_by, created_at, updated_at) VALUES - ('燃脂搏击_次数卡课程', 102, 2, '2026-06-10 19:30:00', '2026-06-10 20:30:00', 20, 0, '0', '综合训练区', '高强度间歇训练,配合音乐快速燃脂,消耗1次', 1, 0.00, 'admin', '2026-06-01 10:00:00', '2026-06-01 10:00:00'); - --- 团课B: 收费类型为储值卡(储值额度=50.00) -INSERT INTO group_course (course_name, coach_id, course_type, start_time, end_time, max_members, current_members, status, location, description, point_card_amount, stored_value_amount, create_by, created_at, updated_at) VALUES - ('高端普拉提_储值卡课程', 103, 1, '2026-06-11 19:00:00', '2026-06-11 20:00:00', 15, 0, '0', '普拉提教室', '精准训练核心肌群,消耗储值50元', 0, 20.00, 'admin', '2026-06-01 10:00:00', '2026-06-01 10:00:00'); - --- ============================================ --- 3. 会员卡类型定义 --- ============================================ - -INSERT INTO member_card (member_card_name, member_card_type, member_card_price, member_card_validity_days, member_card_total_times, member_card_amount, member_card_status, extra_config, created_at, updated_at) VALUES - ('30天时长卡', 'TIME_CARD', 299.00, 30, NULL, NULL, 1, '{}', '2026-01-01 00:00:00', '2026-01-01 00:00:00'), - ('10次卡', 'COUNT_CARD', 499.00, NULL, 10, NULL, 1, '{}', '2026-01-01 00:00:00', '2026-01-01 00:00:00'), - ('储值卡500', 'STORED_VALUE_CARD', 500.00, NULL, NULL, 500.00, 1, '{}', '2026-01-01 00:00:00', '2026-01-01 00:00:00'); - --- ============================================ --- 4. 会员卡记录测试数据 --- ============================================ - --- 场景2: 用户B - 时长卡已过期 (expire_time < 当前时间) --- 购买于30天前,有效期30天,现已过期 -INSERT INTO member_card_record (member_id, member_card_id, status, remaining_times, remaining_amount, expire_time, purchase_time, version, card_composition, created_at, updated_at) VALUES - ((SELECT id FROM member_user WHERE member_no = 'MEM_TEST_B'), (SELECT member_card_id FROM member_card WHERE member_card_name = '30天时长卡'), 'EXPIRED', 0, 0.00, '2026-05-31 23:59:59', '2026-05-01 10:00:00', 0, '{}', '2026-05-01 10:00:00', '2026-06-02 10:00:00'); - --- 场景3: 会员C - 时长卡未过期 (expire_time > 当前时间) --- 购买于今天,有效期30天,还有30天有效期 -INSERT INTO member_card_record (member_id, member_card_id, status, remaining_times, remaining_amount, expire_time, purchase_time, version, card_composition, created_at, updated_at) VALUES - ((SELECT id FROM member_user WHERE member_no = 'MEM_TEST_C'), (SELECT member_card_id FROM member_card WHERE member_card_name = '30天时长卡'), 'ACTIVE', 0, 0.00, '2026-07-02 23:59:59', '2026-06-02 10:00:00', 0, '{}', '2026-06-02 10:00:00', '2026-06-02 10:00:00'); - --- 场景4: 会员D - 次数卡耗尽 (remaining_times = 0) -INSERT INTO member_card_record (member_id, member_card_id, status, remaining_times, remaining_amount, expire_time, purchase_time, version, card_composition, created_at, updated_at) VALUES - ((SELECT id FROM member_user WHERE member_no = 'MEM_TEST_D'), (SELECT member_card_id FROM member_card WHERE member_card_name = '10次卡'), 'ACTIVE', 0, 0.00, '2026-12-31 23:59:59', '2026-05-01 10:00:00', 0, '{}', '2026-05-01 10:00:00', '2026-06-02 10:00:00'); - --- 场景5: 会员E - 次数卡充足 (remaining_times >= point_card_amount) -INSERT INTO member_card_record (member_id, member_card_id, status, remaining_times, remaining_amount, expire_time, purchase_time, version, card_composition, created_at, updated_at) VALUES - ((SELECT id FROM member_user WHERE member_no = 'MEM_TEST_E'), (SELECT member_card_id FROM member_card WHERE member_card_name = '10次卡'), 'ACTIVE', 5, 0.00, '2026-12-31 23:59:59', '2026-05-01 10:00:00', 0, '{}', '2026-05-01 10:00:00', '2026-06-02 10:00:00'); - --- 场景6: 会员F - 储值卡余额不足 (remaining_amount < stored_value_amount of course) --- 团课B收费50元,但会员F储值卡只有30元 -INSERT INTO member_card_record (member_id, member_card_id, status, remaining_times, remaining_amount, expire_time, purchase_time, version, card_composition, created_at, updated_at) VALUES - ((SELECT id FROM member_user WHERE member_no = 'MEM_TEST_F'), (SELECT member_card_id FROM member_card WHERE member_card_name = '储值卡500'), 'ACTIVE', 0, 30.00, '2027-06-02 23:59:59', '2026-05-01 10:00:00', 0, '{}', '2026-05-01 10:00:00', '2026-06-02 10:00:00'); - --- 场景7: 会员G - 储值卡余额充足 (remaining_amount >= stored_value_amount of course) -INSERT INTO member_card_record (member_id, member_card_id, status, remaining_times, remaining_amount, expire_time, purchase_time, version, card_composition, created_at, updated_at) VALUES - ((SELECT id FROM member_user WHERE member_no = 'MEM_TEST_G'), (SELECT member_card_id FROM member_card WHERE member_card_name = '储值卡500'), 'ACTIVE', 0, 200.00, '2027-06-02 23:59:59', '2026-05-01 10:00:00', 0, '{}', '2026-05-01 10:00:00', '2026-06-02 10:00:00'); - --- ============================================ --- 5. 测试场景说明 --- ============================================ - --- 场景1: 用户A无会员卡 -> 预约团课时应提示"无可用会员卡" --- 场景2: 用户B时长卡已过期 -> 预约团课时应提示"会员卡已过期" --- 场景3: 会员C时长卡有效 -> 可以正常预约次数卡团课 --- 场景4: 会员D次数为0 -> 预约团课时应提示"剩余次数不足" --- 场景5: 会员E次数充足 -> 可以正常预约次数卡团课 --- 场景6: 会员F储值卡余额30元,团课需50元 -> 预约时应提示"余额不足" --- 场景7: 会员G储值卡余额200元,团课需50元 -> 可以正常预约储值卡团课 diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V16__Create_Course_Label_tables.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V11__Create_Course_Label_tables.sql similarity index 100% rename from gym-manage-api/manage-db/src/main/resources/db/migration/V16__Create_Course_Label_tables.sql rename to gym-manage-api/manage-db/src/main/resources/db/migration/V11__Create_Course_Label_tables.sql diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V11__Insert_Cancel_Booking_Test_Data.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V11__Insert_Cancel_Booking_Test_Data.sql deleted file mode 100644 index a6d94c8..0000000 --- a/gym-manage-api/manage-db/src/main/resources/db/migration/V11__Insert_Cancel_Booking_Test_Data.sql +++ /dev/null @@ -1,116 +0,0 @@ --- V11: 团课预约取消业务场景测试数据 --- 测试场景: --- 1. 用户张三使用时长卡预约了团课,需要取消预约 --- 2. 用户李四使用次数卡预约了团课,需要取消预约 --- 3. 用户王五使用储值卡预约了团课,需要取消预约 - --- ============================================ --- 1. 创建测试会员用户 (张三、李四、王五) --- ============================================ - -INSERT INTO member_user (member_no, nickname, phone, gender, is_deleted, created_at, updated_at) VALUES - ('MEM_TEST_ZHANG', '张三_时长卡用户', '13800002001', 1, false, '2026-06-02 10:00:00', '2026-06-02 10:00:00'), - ('MEM_TEST_LI', '李四_次数卡用户', '13800002002', 1, false, '2026-06-02 10:00:00', '2026-06-02 10:00:00'), - ('MEM_TEST_WANG', '王五_储值卡用户', '13800002003', 2, false, '2026-06-02 10:00:00', '2026-06-02 10:00:00'); - --- ============================================ --- 2. 确保会员卡类型已存在(如果不存在则创建) --- ============================================ - --- 时长卡类型(30天) -INSERT INTO member_card (member_card_name, member_card_type, member_card_price, member_card_validity_days, member_card_total_times, member_card_amount, member_card_status, extra_config, created_at, updated_at) -SELECT '30天时长卡', 'TIME_CARD', 299.00, 30, NULL, NULL, 1, '{}', '2026-01-01 00:00:00', '2026-01-01 00:00:00' -WHERE NOT EXISTS (SELECT 1 FROM member_card WHERE member_card_name = '30天时长卡'); - --- 次数卡类型(10次) -INSERT INTO member_card (member_card_name, member_card_type, member_card_price, member_card_validity_days, member_card_total_times, member_card_amount, member_card_status, extra_config, created_at, updated_at) -SELECT '10次卡', 'COUNT_CARD', 499.00, NULL, 10, NULL, 1, '{}', '2026-01-01 00:00:00', '2026-01-01 00:00:00' -WHERE NOT EXISTS (SELECT 1 FROM member_card WHERE member_card_name = '10次卡'); - --- 储值卡类型(500元) -INSERT INTO member_card (member_card_name, member_card_type, member_card_price, member_card_validity_days, member_card_total_times, member_card_amount, member_card_status, extra_config, created_at, updated_at) -SELECT '储值卡500', 'STORED_VALUE_CARD', 500.00, NULL, NULL, 500.00, 1, '{}', '2026-01-01 00:00:00', '2026-01-01 00:00:00' -WHERE NOT EXISTS (SELECT 1 FROM member_card WHERE member_card_name = '储值卡500'); - --- ============================================ --- 3. 为测试用户分配会员卡 --- ============================================ - --- 张三:时长卡(有效期内) -INSERT INTO member_card_record (member_id, member_card_id, status, remaining_times, remaining_amount, expire_time, purchase_time, version, card_composition, created_at, updated_at) VALUES - ((SELECT id FROM member_user WHERE member_no = 'MEM_TEST_ZHANG'), - (SELECT member_card_id FROM member_card WHERE member_card_name = '30天时长卡'), - 'ACTIVE', 0, 0.00, '2026-07-02 23:59:59', '2026-06-02 10:00:00', 0, '{}', '2026-06-02 10:00:00', '2026-06-02 10:00:00'); - --- 李四:次数卡(剩余5次) -INSERT INTO member_card_record (member_id, member_card_id, status, remaining_times, remaining_amount, expire_time, purchase_time, version, card_composition, created_at, updated_at) VALUES - ((SELECT id FROM member_user WHERE member_no = 'MEM_TEST_LI'), - (SELECT member_card_id FROM member_card WHERE member_card_name = '10次卡'), - 'ACTIVE', 5, 0.00, '2026-12-31 23:59:59', '2026-05-01 10:00:00', 0, '{}', '2026-05-01 10:00:00', '2026-06-02 10:00:00'); - --- 王五:储值卡(余额200元) -INSERT INTO member_card_record (member_id, member_card_id, status, remaining_times, remaining_amount, expire_time, purchase_time, version, card_composition, created_at, updated_at) VALUES - ((SELECT id FROM member_user WHERE member_no = 'MEM_TEST_WANG'), - (SELECT member_card_id FROM member_card WHERE member_card_name = '储值卡500'), - 'ACTIVE', 0, 200.00, '2027-06-02 23:59:59', '2026-05-01 10:00:00', 0, '{}', '2026-05-01 10:00:00', '2026-06-02 10:00:00'); - --- ============================================ --- 4. 创建用于取消预约测试的团课 --- ============================================ - --- 团课X: 时长卡可用课程(使用次数卡支付,时长卡会员可预约) -INSERT INTO group_course (course_name, coach_id, course_type, start_time, end_time, max_members, current_members, status, location, description, point_card_amount, stored_value_amount, create_by, created_at, updated_at) VALUES - ('晚间瑜伽_取消测试', 101, 1, '2026-06-15 19:00:00', '2026-06-15 20:00:00', 20, 0, '0', '瑜伽教室', '适合所有级别的瑜伽课程,用于测试取消预约功能', 1, 30.00, 'admin', '2026-06-02 10:00:00', '2026-06-02 10:00:00'); - --- ============================================ --- 5. 创建已预约的记录(状态为0-已预约) --- ============================================ - --- 张三使用时长卡预约团课(状态:已预约) -INSERT INTO group_course_booking (course_id, member_id, member_card_id, booking_time, status, course_name, course_start_time, course_end_time, location, created_at, updated_at) VALUES - ((SELECT id FROM group_course WHERE course_name = '晚间瑜伽_取消测试'), - (SELECT id FROM member_user WHERE member_no = 'MEM_TEST_ZHANG'), - (SELECT id FROM member_card_record WHERE member_id = (SELECT id FROM member_user WHERE member_no = 'MEM_TEST_ZHANG')), - '2026-06-02 11:00:00', '0', '晚间瑜伽_取消测试', '2026-06-15 19:00:00', '2026-06-15 20:00:00', '瑜伽教室', - '2026-06-02 11:00:00', '2026-06-02 11:00:00'); - --- 李四使用次数卡预约团课(状态:已预约) -INSERT INTO group_course_booking (course_id, member_id, member_card_id, booking_time, status, course_name, course_start_time, course_end_time, location, created_at, updated_at) VALUES - ((SELECT id FROM group_course WHERE course_name = '晚间瑜伽_取消测试'), - (SELECT id FROM member_user WHERE member_no = 'MEM_TEST_LI'), - (SELECT id FROM member_card_record WHERE member_id = (SELECT id FROM member_user WHERE member_no = 'MEM_TEST_LI')), - '2026-06-02 11:30:00', '0', '晚间瑜伽_取消测试', '2026-06-15 19:00:00', '2026-06-15 20:00:00', '瑜伽教室', - '2026-06-02 11:30:00', '2026-06-02 11:30:00'); - --- 王五使用储值卡预约团课(状态:已预约) -INSERT INTO group_course_booking (course_id, member_id, member_card_id, booking_time, status, course_name, course_start_time, course_end_time, location, created_at, updated_at) VALUES - ((SELECT id FROM group_course WHERE course_name = '晚间瑜伽_取消测试'), - (SELECT id FROM member_user WHERE member_no = 'MEM_TEST_WANG'), - (SELECT id FROM member_card_record WHERE member_id = (SELECT id FROM member_user WHERE member_no = 'MEM_TEST_WANG')), - '2026-06-02 12:00:00', '0', '晚间瑜伽_取消测试', '2026-06-15 19:00:00', '2026-06-15 20:00:00', '瑜伽教室', - '2026-06-02 12:00:00', '2026-06-02 12:00:00'); - --- ============================================ --- 6. 更新团课当前预约人数 --- ============================================ - -UPDATE group_course -SET current_members = current_members + 3 -WHERE course_name = '晚间瑜伽_取消测试'; - --- ============================================ --- 测试数据说明 --- ============================================ --- 用户信息: --- 张三 (MEM_TEST_ZHANG): 13800002001 - 持有有效期内的时长卡 --- 李四 (MEM_TEST_LI): 13800002002 - 持有次数卡(剩余5次) --- 王五 (MEM_TEST_WANG): 13800002003 - 持有储值卡(余额200元) - --- 测试场景: --- 1. 张三使用时长卡预约了团课 -> 取消预约 -> 应恢复时长卡状态,释放课程名额 --- 2. 李四使用次数卡预约了团课 -> 取消预约 -> 应返还扣除的次数,释放课程名额 --- 3. 王五使用储值卡预约了团课 -> 取消预约 -> 应返还扣除的金额,释放课程名额 - --- 预约记录状态说明: --- status = '0' 表示已预约 --- status = '1' 表示已取消(取消后应更新为此状态) diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V17__Create_GroupCourse_Recommend_table.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V12__Create_GroupCourse_Recommend_table.sql similarity index 100% rename from gym-manage-api/manage-db/src/main/resources/db/migration/V17__Create_GroupCourse_Recommend_table.sql rename to gym-manage-api/manage-db/src/main/resources/db/migration/V12__Create_GroupCourse_Recommend_table.sql diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V12__Insert_Time_Conflict_Test_Data.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V12__Insert_Time_Conflict_Test_Data.sql deleted file mode 100644 index 7189cce..0000000 --- a/gym-manage-api/manage-db/src/main/resources/db/migration/V12__Insert_Time_Conflict_Test_Data.sql +++ /dev/null @@ -1,58 +0,0 @@ --- V12: 团课预约时间冲突测试数据 --- 测试场景:会员预约两个时间重叠的团课时,系统应检测到时间冲突并拒绝预约 - --- ============================================ --- 1. 创建测试会员 --- ============================================ - -INSERT INTO member_user (member_no, nickname, phone, gender, is_deleted, created_at, updated_at) VALUES - ('MEM_TIME_CONFLICT', '时间冲突测试会员', '13800009999', 1, false, '2026-06-02 10:00:00', '2026-06-02 10:00:00'); - --- ============================================ --- 2. 创建时间冲突的团课 --- ============================================ - --- 团课A: 6月15日 13:00 - 15:00 -INSERT INTO group_course (course_name, coach_id, course_type, start_time, end_time, max_members, current_members, status, location, description, point_card_amount, stored_value_amount, create_by, created_at, updated_at) VALUES - ('时间冲突测试_A_13点-15点', 102, 2, '2026-06-15 13:00:00', '2026-06-15 15:00:00', 20, 0, '0', '综合训练区', '测试用团课A,用于验证时间冲突检测', 1, 0.00, 'admin', '2026-06-02 10:00:00', '2026-06-02 10:00:00'); - --- 团课B: 6月15日 14:00 - 16:00 (与团课A时间重叠) -INSERT INTO group_course (course_name, coach_id, course_type, start_time, end_time, max_members, current_members, status, location, description, point_card_amount, stored_value_amount, create_by, created_at, updated_at) VALUES - ('时间冲突测试_B_14点-16点', 103, 1, '2026-06-15 14:00:00', '2026-06-15 16:00:00', 15, 0, '0', '普拉提教室', '测试用团课B,与团课A时间重叠(14:00-15:00)', 1, 0.00, 'admin', '2026-06-02 10:00:00', '2026-06-02 10:00:00'); - --- 团课C: 6月15日 10:00 - 12:00 (不冲突,作为对照) -INSERT INTO group_course (course_name, coach_id, course_type, start_time, end_time, max_members, current_members, status, location, description, point_card_amount, stored_value_amount, create_by, created_at, updated_at) VALUES - ('时间冲突测试_C_10点-12点', 101, 1, '2026-06-15 10:00:00', '2026-06-15 12:00:00', 15, 0, '0', '瑜伽教室', '测试用团课C,与团课A/B不冲突', 1, 0.00, 'admin', '2026-06-02 10:00:00', '2026-06-02 10:00:00'); - --- ============================================ --- 3. 会员卡记录(用于预约) --- ============================================ - --- 为时间冲突测试会员创建有效次数卡 -INSERT INTO member_card_record (member_id, member_card_id, status, remaining_times, remaining_amount, expire_time, purchase_time, version, card_composition, created_at, updated_at) VALUES - ((SELECT id FROM member_user WHERE member_no = 'MEM_TIME_CONFLICT'), (SELECT member_card_id FROM member_card WHERE member_card_name = '10次卡'), 'ACTIVE', 10, 0.00, '2026-12-31 23:59:59', '2026-06-02 10:00:00', 0, '{}', '2026-06-02 10:00:00', '2026-06-02 10:00:00'); - --- ============================================ --- 4. 测试场景说明 --- ============================================ --- --- 测试步骤: --- 1. 使用会员 MEM_TIME_CONFLICT 预约团课A (course_name='时间冲突测试_A_13点-15点') --- → 预期:预约成功 --- --- 2. 使用会员 MEM_TIME_CONFLICT 预约团课B (course_name='时间冲突测试_B_14点-16点') --- → 预期:预约失败,提示时间冲突(14:00-15:00与已预约的团课A重叠) --- --- 3. 使用会员 MEM_TIME_CONFLICT 预约团课C (course_name='时间冲突测试_C_10点-12点') --- → 预期:预约成功(与团课A时间不冲突) --- --- 时间冲突判断逻辑: --- 新课程开始时间 < 已预约课程结束时间 AND 新课程结束时间 > 已预约课程开始时间 --- 即:newStartTime < existingEndTime AND newEndTime > existingStartTime --- --- 本测试数据中的时间关系: --- - 团课A: 13:00 - 15:00 --- - 团课B: 14:00 - 16:00 --- → 14:00 < 15:00 AND 16:00 > 13:00 → 冲突 --- - 团课C: 10:00 - 12:00 --- → 10:00 < 15:00 AND 12:00 > 13:00 → 12:00 > 13:00 不成立 → 不冲突 \ No newline at end of file diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V13__Consolidated_Test_Data.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V13__Consolidated_Test_Data.sql new file mode 100644 index 0000000..b109d7b --- /dev/null +++ b/gym-manage-api/manage-db/src/main/resources/db/migration/V13__Consolidated_Test_Data.sql @@ -0,0 +1,342 @@ +-- ============================================================ +-- V13: 整合所有测试数据(基于 2026-06-16 17:13) +-- 说明: 系统初始数据由 V2 处理,本文件仅包含业务测试数据 +-- 所有时间相关字段已更新为基于 2026-06-16 +-- ============================================================ + +-- ============================================================ +-- Section B: 团课类型数据(来源 V15) +-- ============================================================ + +INSERT INTO group_course_type (type_name, base_difficulty, description, category) VALUES +('慢走/椭圆机轻松模式', 1, '几乎无难度,适合所有人', '基础有氧与热身'), +('固定自行车(低阻力)', 2, '注意座椅高度调节即可', '基础有氧与热身'), +('跑步机慢跑', 3, '需要基本协调性,膝盖有压力', '基础有氧与热身'), +('跳绳(连续基础跳)', 3, '需要手脚配合,心肺要求明显', '基础有氧与热身'), +('坐姿腿屈伸/腿弯举', 2, '很容易找到发力感', '固定器械训练'), +('坐姿推胸机', 3, '需注意肩胛后收,避免耸肩', '固定器械训练'), +('高位下拉(坐姿)', 3, '需控制不要过度后仰', '固定器械训练'), +('史密斯机深蹲', 4, '轨迹固定,但需保持核心稳定', '固定器械训练'), +('蝴蝶机夹胸', 3, '易用肘关节代偿,需锁定肩关节', '固定器械训练'), +('平板支撑', 3, '耐力考验,技巧低', '自重基础动作'), +('跪姿俯卧撑', 3, '上肢力量较弱者首选', '自重基础动作'), +('标准俯卧撑', 5, '需核心收紧,身体成直线', '自重基础动作'), +('引体向上(弹力带辅助)', 6, '背部和手臂力量要求高', '自重基础动作'), +('标准引体向上', 8, '力量-体重比极高,多数男性无法完成1次', '自重基础动作'), +('徒手深蹲', 3, '注意膝盖方向与背部直立', '自重基础动作'), +('单腿深蹲(手枪蹲)', 8, '需要极高下肢力量、柔韧性和平衡', '自重基础动作'), +('哑铃二头弯举', 4, '容易晃动借力,但较安全', '自由重量杠铃/哑铃'), +('哑铃侧平举', 5, '极易用斜方肌代偿,真正练到三角肌中束很难', '自由重量杠铃/哑铃'), +('杠铃卧推', 7, '肩关节压力大,起桥、沉肩、稳定手腕均有技巧,有压伤风险', '自由重量杠铃/哑铃'), +('杠铃深蹲(颈后)', 8, '全身协调性、核心抗压、杠位放置、呼吸模式,学习曲线陡峭', '自由重量杠铃/哑铃'), +('传统硬拉', 9, '风险极高,需要精确的脊柱中立、髋铰链、背阔肌收紧,错误时伤腰', '自由重量杠铃/哑铃'), +('高翻/抓举(奥运举重)', 10, '需要爆发力、柔韧、精准衔接,非数月训练不能掌握', '自由重量杠铃/哑铃'), +('波比跳(标准版)', 6, '连续做时心肺压力极大', '高强度与爆发力'), +('冲刺跑(短跑)', 7, '对腘绳肌和脚踝爆发力要求高', '高强度与爆发力'), +('跳箱(合理高度)', 6, '需要落地缓冲技巧', '高强度与爆发力'), +('负重雪橇推', 6, '主要考验腿部耐力和意志力', '高强度与爆发力'), +('双力臂(引体向上后翻腕上杠)', 9, '需要爆发引体 + 极高相对力量', '高强度与爆发力'), +('静态拉伸(坐姿体前屈)', 2, '无风险,但需要坚持', '柔韧与平衡类'), +('瑜伽下犬式', 3, '常见,但需背部与手臂对齐', '柔韧与平衡类'), +('单腿罗马尼亚硬拉(徒手)', 6, '极考验平衡和髋稳定', '柔韧与平衡类'), +('全深蹲(脚跟贴地,亚洲蹲)', 5, '踝关节灵活度限制多数人', '柔韧与平衡类'), +('竖叉/横叉', 8, '需要数月甚至数年拉伸', '柔韧与平衡类'), +('瑜伽入门', 2, '适合初学者的瑜伽课程,注重基础体式', '柔韧与平衡类'), +('瑜伽进阶', 5, '针对有一定基础的学员,包含更复杂体式', '柔韧与平衡类'), +('普拉提', 4, '注重核心力量和身体控制', '柔韧与平衡类'), +('动感单车', 4, '高强度有氧运动,节奏感强', '基础有氧与热身'), +('搏击操', 5, '结合拳击动作的有氧运动', '高强度与爆发力'), +('HIIT训练', 7, '高强度间歇训练,对心肺要求极高', '高强度与爆发力'), +('核心训练', 4, '针对核心肌群的专项训练', '自重基础动作'); + +-- ============================================================ +-- Section C: 团课标签数据(来源 V16) +-- ============================================================ + +INSERT INTO course_label (label_name, color, description) VALUES +('适合新手', '#52c41a', '适合健身初学者'), +('中级过渡', '#faad14', '适合有一定基础的学员'), +('高级进阶', '#f5222d', '适合高级学员'), +('减脂塑形', '#722ed1', '有助于减脂塑形'), +('增肌强化', '#13c2c2', '有助于增肌强化'), +('柔韧性训练', '#eb2f96', '注重柔韧性提升'), +('核心训练', '#1890ff', '注重核心力量'), +('心肺训练', '#fa8c16', '提升心肺功能'), +('低冲击', '#52c41a', '低冲击运动,适合关节保护'), +('高强度', '#f5222d', '高强度间歇训练'), +('团体互动', '#722ed1', '注重团队协作'), +('私教推荐', '#13c2c2', '私教推荐课程'), +('热门课程', '#ff1493', '人气较高的课程'), +('新课上线', '#00ced1', '新上线的课程'); + +-- ============================================================ +-- Section D: 会员卡类型(来源 V10 / V11,已去重合并) +-- ============================================================ + +INSERT INTO member_card (member_card_name, member_card_type, member_card_price, member_card_validity_days, member_card_total_times, member_card_amount, member_card_status, extra_config, created_at, updated_at) VALUES +('30天时长卡', 'TIME_CARD', 299.00, 30, NULL, NULL, 1, '{}', '2026-01-01 00:00:00', '2026-01-01 00:00:00'), +('10次卡', 'COUNT_CARD', 499.00, NULL, 10, NULL, 1, '{}', '2026-01-01 00:00:00', '2026-01-01 00:00:00'), +('储值卡500', 'STORED_VALUE_CARD', 500.00, NULL, NULL, 500.00, 1, '{}', '2026-01-01 00:00:00', '2026-01-01 00:00:00'); + +-- ============================================================ +-- Section E: 测试会员(来源 V10 / V11 / V12 / V14) +-- ============================================================ + +-- E1: 数据统计测试会员(V14,显式 ID 1001-1012) +INSERT INTO member_user (id, member_no, nickname, phone, created_at, updated_at, is_deleted) VALUES +(1001, 'M20260616001', '张三', '13800138001', '2026-06-16 08:00:00', '2026-06-16 08:00:00', false), +(1002, 'M20260616002', '李四', '13800138002', '2026-06-16 09:00:00', '2026-06-16 09:00:00', false), +(1003, 'M20260615003', '王五', '13800138003', '2026-06-15 10:00:00', '2026-06-15 10:00:00', false), +(1004, 'M20260614004', '赵六', '13800138004', '2026-06-14 11:00:00', '2026-06-14 11:00:00', false), +(1005, 'M20260616005', '钱七', '13800138005', '2026-06-16 08:00:00', '2026-06-16 08:00:00', false), +(1006, 'M20260616006', '孙八', '13800138006', '2026-06-16 09:00:00', '2026-06-16 09:00:00', false), +(1007, 'M20260616007', '周九', '13800138007', '2026-06-16 10:00:00', '2026-06-16 10:00:00', false), +(1008, 'M20260613008', '吴十', '13800138008', '2026-06-13 14:00:00', '2026-06-13 14:00:00', false), +(1009, 'M20260612009', '郑十一', '13800138009', '2026-06-12 15:00:00', '2026-06-12 15:00:00', false), +(1010, 'M20260611010', '王十二', '13800138010', '2026-06-11 16:00:00', '2026-06-11 16:00:00', false), +(1011, 'M20260610011', '陈十三', '13800138011', '2026-06-10 17:00:00', '2026-06-10 17:00:00', false), +(1012, 'M20260609012', '刘十四', '13800138012', '2026-06-09 18:00:00', '2026-06-09 18:00:00', false); + +-- E2: 预约场景测试会员(V10: A-G) +INSERT INTO member_user (member_no, nickname, phone, gender, is_deleted, created_at, updated_at) VALUES +('MEM_TEST_A', '用户A_无卡新用户', '13800001001', 1, false, '2026-06-16 10:00:00', '2026-06-16 10:00:00'), +('MEM_TEST_B', '用户B_过期时长卡', '13800001002', 1, false, '2026-06-15 10:00:00', '2026-06-15 10:00:00'), +('MEM_TEST_C', '会员C_有效时长卡', '13800001003', 2, false, '2026-06-15 10:00:00', '2026-06-15 10:00:00'), +('MEM_TEST_D', '会员D_次数用尽', '13800001004', 1, false, '2026-06-15 10:00:00', '2026-06-15 10:00:00'), +('MEM_TEST_E', '会员E_次数充足', '13800001005', 2, false, '2026-06-15 10:00:00', '2026-06-15 10:00:00'), +('MEM_TEST_F', '会员F_储值卡余额不足', '13800001006', 1, false, '2026-06-15 10:00:00', '2026-06-15 10:00:00'), +('MEM_TEST_G', '会员G_储值卡余额充足', '13800001007', 2, false, '2026-06-15 10:00:00', '2026-06-15 10:00:00'); + +-- E3: 取消预约测试会员(V11: 张三/李四/王五) +INSERT INTO member_user (member_no, nickname, phone, gender, is_deleted, created_at, updated_at) VALUES +('MEM_TEST_ZHANG', '张三_时长卡用户', '13800002001', 1, false, '2026-06-15 10:00:00', '2026-06-15 10:00:00'), +('MEM_TEST_LI', '李四_次数卡用户', '13800002002', 1, false, '2026-06-15 10:00:00', '2026-06-15 10:00:00'), +('MEM_TEST_WANG', '王五_储值卡用户', '13800002003', 2, false, '2026-06-15 10:00:00', '2026-06-15 10:00:00'); + +-- E4: 时间冲突测试会员(V12) +INSERT INTO member_user (member_no, nickname, phone, gender, is_deleted, created_at, updated_at) VALUES +('MEM_TIME_CONFLICT', '时间冲突测试会员', '13800009999', 1, false, '2026-06-15 10:00:00', '2026-06-15 10:00:00'); + +-- ============================================================ +-- Section F: 会员卡记录 +-- ============================================================ + +-- F1: V10 预约场景 - 6条会员卡记录 +INSERT INTO member_card_record (member_id, member_card_id, status, remaining_times, remaining_amount, expire_time, purchase_time, version, card_composition, created_at, updated_at) VALUES +((SELECT id FROM member_user WHERE member_no = 'MEM_TEST_B'), (SELECT member_card_id FROM member_card WHERE member_card_name = '30天时长卡'), 'EXPIRED', 0, 0.00, '2026-06-15 23:59:59', '2026-05-16 10:00:00', 0, '{}', '2026-05-16 10:00:00', '2026-06-16 10:00:00'); + +INSERT INTO member_card_record (member_id, member_card_id, status, remaining_times, remaining_amount, expire_time, purchase_time, version, card_composition, created_at, updated_at) VALUES +((SELECT id FROM member_user WHERE member_no = 'MEM_TEST_C'), (SELECT member_card_id FROM member_card WHERE member_card_name = '30天时长卡'), 'ACTIVE', 0, 0.00, '2026-07-16 23:59:59', '2026-06-16 10:00:00', 0, '{}', '2026-06-16 10:00:00', '2026-06-16 10:00:00'); + +INSERT INTO member_card_record (member_id, member_card_id, status, remaining_times, remaining_amount, expire_time, purchase_time, version, card_composition, created_at, updated_at) VALUES +((SELECT id FROM member_user WHERE member_no = 'MEM_TEST_D'), (SELECT member_card_id FROM member_card WHERE member_card_name = '10次卡'), 'ACTIVE', 0, 0.00, '2026-12-31 23:59:59', '2026-05-16 10:00:00', 0, '{}', '2026-05-16 10:00:00', '2026-06-16 10:00:00'); + +INSERT INTO member_card_record (member_id, member_card_id, status, remaining_times, remaining_amount, expire_time, purchase_time, version, card_composition, created_at, updated_at) VALUES +((SELECT id FROM member_user WHERE member_no = 'MEM_TEST_E'), (SELECT member_card_id FROM member_card WHERE member_card_name = '10次卡'), 'ACTIVE', 5, 0.00, '2026-12-31 23:59:59', '2026-05-16 10:00:00', 0, '{}', '2026-05-16 10:00:00', '2026-06-16 10:00:00'); + +INSERT INTO member_card_record (member_id, member_card_id, status, remaining_times, remaining_amount, expire_time, purchase_time, version, card_composition, created_at, updated_at) VALUES +((SELECT id FROM member_user WHERE member_no = 'MEM_TEST_F'), (SELECT member_card_id FROM member_card WHERE member_card_name = '储值卡500'), 'ACTIVE', 0, 30.00, '2027-06-16 23:59:59', '2026-05-16 10:00:00', 0, '{}', '2026-05-16 10:00:00', '2026-06-16 10:00:00'); + +INSERT INTO member_card_record (member_id, member_card_id, status, remaining_times, remaining_amount, expire_time, purchase_time, version, card_composition, created_at, updated_at) VALUES +((SELECT id FROM member_user WHERE member_no = 'MEM_TEST_G'), (SELECT member_card_id FROM member_card WHERE member_card_name = '储值卡500'), 'ACTIVE', 0, 200.00, '2027-06-16 23:59:59', '2026-05-16 10:00:00', 0, '{}', '2026-05-16 10:00:00', '2026-06-16 10:00:00'); + +-- F2: V11 取消预约场景 - 3条 +INSERT INTO member_card_record (member_id, member_card_id, status, remaining_times, remaining_amount, expire_time, purchase_time, version, card_composition, created_at, updated_at) VALUES +((SELECT id FROM member_user WHERE member_no = 'MEM_TEST_ZHANG'), (SELECT member_card_id FROM member_card WHERE member_card_name = '30天时长卡'), 'ACTIVE', 0, 0.00, '2026-07-16 23:59:59', '2026-06-16 10:00:00', 0, '{}', '2026-06-16 10:00:00', '2026-06-16 10:00:00'), +((SELECT id FROM member_user WHERE member_no = 'MEM_TEST_LI'), (SELECT member_card_id FROM member_card WHERE member_card_name = '10次卡'), 'ACTIVE', 5, 0.00, '2026-12-31 23:59:59', '2026-05-16 10:00:00', 0, '{}', '2026-05-16 10:00:00', '2026-06-16 10:00:00'), +((SELECT id FROM member_user WHERE member_no = 'MEM_TEST_WANG'), (SELECT member_card_id FROM member_card WHERE member_card_name = '储值卡500'), 'ACTIVE', 0, 200.00, '2027-06-16 23:59:59', '2026-05-16 10:00:00', 0, '{}', '2026-05-16 10:00:00', '2026-06-16 10:00:00'); + +-- F3: V12 时间冲突场景 - 1条 +INSERT INTO member_card_record (member_id, member_card_id, status, remaining_times, remaining_amount, expire_time, purchase_time, version, card_composition, created_at, updated_at) VALUES +((SELECT id FROM member_user WHERE member_no = 'MEM_TIME_CONFLICT'), (SELECT member_card_id FROM member_card WHERE member_card_name = '10次卡'), 'ACTIVE', 10, 0.00, '2026-12-31 23:59:59', '2026-06-16 10:00:00', 0, '{}', '2026-06-16 10:00:00', '2026-06-16 10:00:00'); + +-- ============================================================ +-- Section G: 团课课程(来源 V7 / V10 / V11 / V12 / V14) +-- ============================================================ + +-- G1: V7 - 7种场景团课 +INSERT INTO group_course (course_name, coach_id, course_type, start_time, end_time, max_members, current_members, status, location, cover_image, description, create_by, created_at, updated_at) VALUES +('极速燃脂单车', 104, 2, '2026-06-17 16:45:00', '2026-06-17 20:20:00', 25, 0, 0, '单车房', '/images/spinning.jpg', '跟随音乐节奏变换阻力和速度,体验爬坡与冲刺的快感。', 'admin', '2026-06-15 11:00:00', '2026-06-15 11:00:00'), +('清晨流瑜伽', 101, 1, '2026-06-20 09:00:00', '2026-06-20 10:30:00', 15, 5, 0, 'A座3楼瑜伽教室', '/images/yoga_flow.jpg', '适合有一定基础的学员,通过流畅的体式连接呼吸。', 'admin', '2026-06-15 10:00:00', '2026-06-15 10:00:00'), +('燃脂搏击', 102, 2, '2026-06-18 18:30:00', '2026-06-18 19:30:00', 20, 20, 0, '综合训练区', '/images/kickboxing.jpg', '高强度间歇训练,名额已满。', 'coach_zhang', '2026-06-15 14:30:00', '2026-06-15 14:30:00'), +('哈他瑜伽', 101, 1, '2026-06-16 17:45:00', '2026-06-16 18:50:00', 12, 3, 0, '瑜伽教室B', '/images/hatha_yoga.jpg', '基础哈他瑜伽,距开始不足30分钟,已停止预约。', 'coach_li', '2026-06-16 08:00:00', '2026-06-16 08:00:00'), +('周末冥想修复', 101, 1, '2026-06-28 15:00:00', '2026-06-28 16:00:00', 12, 3, 1, '冥想室', '/images/meditation.jpg', '通过呼吸和正念冥想,该课程已被取消。', 'coach_wang', '2026-06-10 08:00:00', '2026-06-10 08:00:00'), +('蜜桃臀塑造', 103, 3, '2026-06-10 19:00:00', '2026-06-10 20:00:00', 10, 8, 2, '私教专区', '/images/glute.jpg', '针对性训练臀部肌肉群,课程已结束。', 'coach_li', '2026-05-30 09:15:00', '2026-05-30 09:15:00'), +('午间冥想放松', 101, 1, '2026-06-12 12:00:00', '2026-06-12 13:00:00', 15, 6, 2, '冥想室', '/images/meditation_noon.jpg', '午间冥想课程,已结束。', 'admin', '2026-06-05 09:00:00', '2026-06-05 09:00:00'); + +-- G2: V10 预约场景 +INSERT INTO group_course (course_name, coach_id, course_type, start_time, end_time, max_members, current_members, status, location, description, point_card_amount, stored_value_amount, create_by, created_at, updated_at) VALUES +('燃脂搏击_次数卡课程', 102, 2, '2026-06-18 19:30:00', '2026-06-18 20:30:00', 20, 0, 0, '综合训练区', '消耗1次', 1, 0.00, 'admin', '2026-06-15 10:00:00', '2026-06-15 10:00:00'), +('高端普拉提_储值卡课程', 103, 1, '2026-06-19 19:00:00', '2026-06-19 20:00:00', 15, 0, 0, '普拉提教室', '消耗储值20元', 0, 20.00, 'admin', '2026-06-15 10:00:00', '2026-06-15 10:00:00'); + +-- G3: V11 取消预约场景 +INSERT INTO group_course (course_name, coach_id, course_type, start_time, end_time, max_members, current_members, status, location, description, point_card_amount, stored_value_amount, create_by, created_at, updated_at) VALUES +('晚间瑜伽_取消测试', 101, 1, '2026-06-19 19:00:00', '2026-06-19 20:00:00', 20, 0, 0, '瑜伽教室', '用于测试取消预约功能', 1, 30.00, 'admin', '2026-06-15 10:00:00', '2026-06-15 10:00:00'); + +-- G4: V12 时间冲突场景 +INSERT INTO group_course (course_name, coach_id, course_type, start_time, end_time, max_members, current_members, status, location, description, point_card_amount, stored_value_amount, create_by, created_at, updated_at) VALUES +('时间冲突测试_A_13点-15点', 102, 2, '2026-06-18 13:00:00', '2026-06-18 15:00:00', 20, 0, 0, '综合训练区', '测试用团课A', 1, 0.00, 'admin', '2026-06-15 10:00:00', '2026-06-15 10:00:00'), +('时间冲突测试_B_14点-16点', 103, 1, '2026-06-18 14:00:00', '2026-06-18 16:00:00', 15, 0, 0, '普拉提教室', '与团课A时间重叠', 1, 0.00, 'admin', '2026-06-15 10:00:00', '2026-06-15 10:00:00'), +('时间冲突测试_C_10点-12点', 101, 1, '2026-06-18 10:00:00', '2026-06-18 12:00:00', 15, 0, 0, '瑜伽教室', '与A/B不冲突', 1, 0.00, 'admin', '2026-06-15 10:00:00', '2026-06-15 10:00:00'); + +-- G5: V14 数据统计场景(今天) +INSERT INTO group_course (id, course_name, coach_id, course_type, start_time, end_time, max_members, current_members, status, location, cover_image, description, created_at, updated_at) VALUES +(3001, '瑜伽入门', 1, 1, '2026-06-16 08:00:00', '2026-06-16 09:00:00', 20, 15, 0, '健身房A区', 'https://example.com/yoga.jpg', '适合初学者的瑜伽课程', '2026-06-15 10:00:00', '2026-06-15 10:00:00'), +(3002, '动感单车', 2, 2, '2026-06-16 09:30:00', '2026-06-16 10:30:00', 25, 20, 0, '健身房B区', 'https://example.com/spinning.jpg', '高强度有氧运动', '2026-06-15 11:00:00', '2026-06-15 11:00:00'), +(3003, '普拉提', 3, 1, '2026-06-16 14:00:00', '2026-06-16 15:00:00', 15, 10, 0, '健身房C区', 'https://example.com/pilates.jpg', '核心力量训练', '2026-06-15 12:00:00', '2026-06-15 12:00:00'); + +-- ============================================================ +-- Section H: 团课预约记录 +-- ============================================================ + +-- H1: V11 取消预约测试 +INSERT INTO group_course_booking (course_id, member_id, member_card_id, booking_time, status, course_name, course_start_time, course_end_time, location, created_at, updated_at) VALUES +((SELECT id FROM group_course WHERE course_name = '晚间瑜伽_取消测试'), (SELECT id FROM member_user WHERE member_no = 'MEM_TEST_ZHANG'), (SELECT id FROM member_card_record WHERE member_id = (SELECT id FROM member_user WHERE member_no = 'MEM_TEST_ZHANG')), '2026-06-16 11:00:00', '0', '晚间瑜伽_取消测试', '2026-06-19 19:00:00', '2026-06-19 20:00:00', '瑜伽教室', '2026-06-16 11:00:00', '2026-06-16 11:00:00'), +((SELECT id FROM group_course WHERE course_name = '晚间瑜伽_取消测试'), (SELECT id FROM member_user WHERE member_no = 'MEM_TEST_LI'), (SELECT id FROM member_card_record WHERE member_id = (SELECT id FROM member_user WHERE member_no = 'MEM_TEST_LI')), '2026-06-16 11:30:00', '0', '晚间瑜伽_取消测试', '2026-06-19 19:00:00', '2026-06-19 20:00:00', '瑜伽教室', '2026-06-16 11:30:00', '2026-06-16 11:30:00'), +((SELECT id FROM group_course WHERE course_name = '晚间瑜伽_取消测试'), (SELECT id FROM member_user WHERE member_no = 'MEM_TEST_WANG'), (SELECT id FROM member_card_record WHERE member_id = (SELECT id FROM member_user WHERE member_no = 'MEM_TEST_WANG')), '2026-06-16 12:00:00', '0', '晚间瑜伽_取消测试', '2026-06-19 19:00:00', '2026-06-19 20:00:00', '瑜伽教室', '2026-06-16 12:00:00', '2026-06-16 12:00:00'); + +UPDATE group_course SET current_members = current_members + 3 WHERE course_name = '晚间瑜伽_取消测试'; + +-- H2: V14 数据统计预约 - 今天(6月16日) +INSERT INTO group_course_booking (id, member_id, member_card_id, course_id, booking_time, status, created_at, updated_at) VALUES +(4001, 1001, 1, 3001, '2026-06-16 08:00:00', '2', '2026-06-15 20:00:00', '2026-06-16 08:30:00'), +(4002, 1002, 2, 3001, '2026-06-16 08:00:00', '2', '2026-06-15 21:00:00', '2026-06-16 08:30:00'), +(4003, 1003, 3, 3001, '2026-06-16 08:00:00', '3', '2026-06-15 22:00:00', '2026-06-16 09:00:00'), +(4004, 1004, 4, 3002, '2026-06-16 09:30:00', '2', '2026-06-15 19:00:00', '2026-06-16 09:30:00'), +(4005, 1005, 5, 3002, '2026-06-16 09:30:00', '1', '2026-06-15 20:30:00', '2026-06-16 09:00:00'), +(4006, 1006, 6, 3002, '2026-06-16 09:30:00', '2', '2026-06-15 21:30:00', '2026-06-16 09:30:00'), +(4007, 1007, 7, 3003, '2026-06-16 14:00:00', '2', '2026-06-15 22:30:00', '2026-06-16 14:00:00'), +(4008, 1008, 8, 3003, '2026-06-16 14:00:00', '3', '2026-06-16 08:00:00', '2026-06-16 14:00:00'), +(4009, 1009, 9, 3003, '2026-06-16 14:00:00', '2', '2026-06-16 09:00:00', '2026-06-16 14:00:00'), +(4010, 1010, 10, 3001, '2026-06-16 08:00:00', '2', '2026-06-16 07:00:00', '2026-06-16 08:00:00'), +(4011, 1011, 11, 3002, '2026-06-16 09:30:00', '1', '2026-06-16 08:30:00', '2026-06-16 09:00:00'), +(4012, 1012, 12, 3003, '2026-06-16 14:00:00', '2', '2026-06-16 10:00:00', '2026-06-16 14:00:00'); + +-- H3: V14 数据统计预约 - 昨天(6月15日) +INSERT INTO group_course_booking (id, member_id, member_card_id, course_id, booking_time, status, created_at, updated_at) VALUES +(4013, 1001, 1, 3001, '2026-06-15 08:00:00', '2', '2026-06-14 20:00:00', '2026-06-15 08:30:00'), +(4014, 1002, 2, 3001, '2026-06-15 08:00:00', '2', '2026-06-14 21:00:00', '2026-06-15 08:30:00'), +(4015, 1003, 3, 3002, '2026-06-15 09:30:00', '3', '2026-06-14 22:00:00', '2026-06-15 09:30:00'), +(4016, 1004, 4, 3002, '2026-06-15 09:30:00', '2', '2026-06-14 19:00:00', '2026-06-15 09:30:00'), +(4017, 1005, 5, 3003, '2026-06-15 14:00:00', '1', '2026-06-14 20:30:00', '2026-06-15 13:00:00'), +(4018, 1006, 6, 3003, '2026-06-15 14:00:00', '2', '2026-06-14 21:30:00', '2026-06-15 14:00:00'); + +-- H4: V14 数据统计预约 - 前天(6月14日) +INSERT INTO group_course_booking (id, member_id, member_card_id, course_id, booking_time, status, created_at, updated_at) VALUES +(4019, 1001, 1, 3002, '2026-06-14 09:30:00', '2', '2026-06-13 20:00:00', '2026-06-14 09:30:00'), +(4020, 1002, 2, 3002, '2026-06-14 09:30:00', '2', '2026-06-13 21:00:00', '2026-06-14 09:30:00'), +(4021, 1003, 3, 3003, '2026-06-14 14:00:00', '2', '2026-06-13 22:00:00', '2026-06-14 14:00:00'), +(4022, 1004, 4, 3003, '2026-06-14 14:00:00', '3', '2026-06-13 19:00:00', '2026-06-14 14:00:00'); + +-- ============================================================ +-- Section I: 签到记录(来源 V14,基于 2026-06-16) +-- ============================================================ + +INSERT INTO sign_in_record (id, member_id, sign_in_time, sign_in_type, sign_in_status, source, is_delete) VALUES +(2001, 1001, '2026-06-16 08:00:00', 'QR_CODE', 'SUCCESS', 'MINI_PROGRAM', false), +(2002, 1002, '2026-06-16 08:15:00', 'MANUAL', 'SUCCESS', 'PC_BACKEND', false), +(2003, 1003, '2026-06-16 08:30:00', 'FACE', 'SUCCESS', 'MINI_PROGRAM', false), +(2004, 1004, '2026-06-16 09:00:00', 'QR_CODE', 'SUCCESS', 'MINI_PROGRAM', false), +(2005, 1005, '2026-06-16 09:15:00', 'MANUAL', 'SUCCESS', 'PC_BACKEND', false), +(2006, 1006, '2026-06-16 09:30:00', 'QR_CODE', 'SUCCESS', 'MINI_PROGRAM', false), +(2007, 1007, '2026-06-16 10:00:00', 'FACE', 'SUCCESS', 'MINI_PROGRAM', false), +(2008, 1008, '2026-06-16 10:15:00', 'QR_CODE', 'SUCCESS', 'MINI_PROGRAM', false), +(2009, 1009, '2026-06-16 10:30:00', 'MANUAL', 'SUCCESS', 'PC_BACKEND', false), +(2010, 1010, '2026-06-16 11:00:00', 'QR_CODE', 'SUCCESS', 'MINI_PROGRAM', false), +(2011, 1011, '2026-06-16 11:15:00', 'FACE', 'FAIL', 'MINI_PROGRAM', false), +(2012, 1012, '2026-06-16 11:30:00', 'MANUAL', 'SUCCESS', 'PC_BACKEND', false); + +INSERT INTO sign_in_record (id, member_id, sign_in_time, sign_in_type, sign_in_status, source, is_delete) VALUES +(2013, 1001, '2026-06-15 07:30:00', 'QR_CODE', 'SUCCESS', 'MINI_PROGRAM', false), +(2014, 1002, '2026-06-15 08:00:00', 'MANUAL', 'SUCCESS', 'PC_BACKEND', false), +(2015, 1003, '2026-06-15 08:30:00', 'FACE', 'SUCCESS', 'MINI_PROGRAM', false), +(2016, 1004, '2026-06-15 09:00:00', 'QR_CODE', 'SUCCESS', 'MINI_PROGRAM', false), +(2017, 1005, '2026-06-15 09:30:00', 'MANUAL', 'SUCCESS', 'PC_BACKEND', false), +(2018, 1006, '2026-06-15 10:00:00', 'QR_CODE', 'SUCCESS', 'MINI_PROGRAM', false), +(2019, 1007, '2026-06-15 10:30:00', 'FACE', 'SUCCESS', 'MINI_PROGRAM', false), +(2020, 1008, '2026-06-15 11:00:00', 'QR_CODE', 'SUCCESS', 'MINI_PROGRAM', false); + +INSERT INTO sign_in_record (id, member_id, sign_in_time, sign_in_type, sign_in_status, source, is_delete) VALUES +(2021, 1001, '2026-06-14 07:00:00', 'QR_CODE', 'SUCCESS', 'MINI_PROGRAM', false), +(2022, 1002, '2026-06-14 07:30:00', 'MANUAL', 'SUCCESS', 'PC_BACKEND', false), +(2023, 1003, '2026-06-14 08:00:00', 'FACE', 'FAIL', 'MINI_PROGRAM', false), +(2024, 1004, '2026-06-14 08:30:00', 'QR_CODE', 'SUCCESS', 'MINI_PROGRAM', false), +(2025, 1005, '2026-06-14 09:00:00', 'MANUAL', 'SUCCESS', 'PC_BACKEND', false); + +-- ============================================================ +-- Section J: 团课推荐测试数据(来源 V18,基于 2026-06-16) +-- ============================================================ + +INSERT INTO group_course_recommend (course_id, recommend_title, recommend_content, recommend_reason, priority, is_active, create_by, created_at, updated_at) VALUES +((SELECT id FROM group_course WHERE course_name = '极速燃脂单车'), '本周热门推荐', '极速燃脂单车课程,跟随音乐节奏变换阻力和速度,体验爬坡与冲刺的快感,一节课消耗800大卡!', '教练专业,课程内容丰富,深受学员喜爱,燃脂效果显著', 20, true, 'admin', '2026-06-16 10:00:00', '2026-06-16 10:00:00'); + +INSERT INTO group_course_recommend (course_id, recommend_title, recommend_content, recommend_reason, priority, is_active, create_by, created_at, updated_at) VALUES +((SELECT id FROM group_course WHERE course_name = '清晨流瑜伽'), '新手友好推荐', '清晨流瑜伽课程,适合有一定基础的学员,通过流畅的体式连接呼吸,唤醒身体能量。', '适合新手入门,教练耐心指导,课程节奏适中', 15, true, 'admin', '2026-06-16 11:00:00', '2026-06-16 11:00:00'); + +INSERT INTO group_course_recommend (course_id, recommend_title, recommend_content, recommend_reason, priority, is_active, create_by, created_at, updated_at) VALUES +((SELECT id FROM group_course WHERE course_name = '燃脂搏击'), '高强度燃脂', '燃脂搏击课程,高强度间歇训练,配合音乐快速燃脂,释放压力。', '高强度训练,适合进阶学员,快速燃脂塑形', 10, false, 'admin', '2026-06-16 12:00:00', '2026-06-16 12:00:00'); + +INSERT INTO group_course_recommend (course_id, recommend_title, recommend_content, recommend_reason, priority, is_active, create_by, created_at, updated_at) VALUES +((SELECT id FROM group_course WHERE course_name = '哈他瑜伽'), '基础瑜伽推荐', '基础哈他瑜伽课程,适合所有级别学员,通过基础体式练习提升身体柔韧性和平衡能力。', '零基础友好,适合所有健身水平,放松身心', 12, true, 'coach_li', '2026-06-16 13:00:00', '2026-06-16 13:00:00'); + +INSERT INTO group_course_recommend (course_id, recommend_title, recommend_content, recommend_reason, priority, is_active, create_by, created_at, updated_at) VALUES +((SELECT id FROM group_course WHERE course_name = '蜜桃臀塑造'), '塑形热门课程', '蜜桃臀塑造课程,针对性训练臀部肌肉群,打造完美曲线。', '专业私教指导,动作标准,效果显著,深受女性学员喜爱', 18, true, 'coach_li', '2026-06-10 09:15:00', '2026-06-10 09:15:00'); + +INSERT INTO group_course_recommend (course_id, recommend_title, recommend_content, recommend_reason, priority, is_active, create_by, created_at, updated_at) VALUES +((SELECT id FROM group_course WHERE course_name = '午间冥想放松'), '午间放松推荐', '午间冥想放松课程,通过呼吸和正念冥想,深度放松身心,缓解工作压力。', '适合上班族,午间放松充电,提升下午工作效率', 8, true, 'admin', '2026-06-12 09:00:00', '2026-06-12 09:00:00'); + +INSERT INTO group_course_recommend (course_id, recommend_title, recommend_content, recommend_reason, priority, is_active, create_by, created_at, updated_at) VALUES +((SELECT id FROM group_course WHERE course_name = '极速燃脂单车'), '减脂首选课程', '想要快速减脂?极速燃脂单车是你的最佳选择!专业教练带领,科学训练计划。', '减脂效果最佳,课程强度适中,适合想要快速瘦身的学员', 16, true, 'coach_zhang', '2026-06-16 14:00:00', '2026-06-16 14:00:00'); + +INSERT INTO group_course_recommend (course_id, recommend_title, recommend_content, recommend_reason, priority, is_active, create_by, created_at, updated_at) VALUES +((SELECT id FROM group_course WHERE course_name = '清晨流瑜伽'), '晨练优选', '清晨流瑜伽,唤醒身体能量,开启活力一天!适合晨练爱好者。', '晨练最佳选择,提升身体活力,改善精神状态', 14, true, 'coach_wang', '2026-06-16 15:00:00', '2026-06-16 15:00:00'); + +INSERT INTO group_course_recommend (course_id, recommend_title, recommend_content, recommend_reason, priority, is_active, create_by, created_at, updated_at) VALUES +((SELECT id FROM group_course WHERE course_name = '哈他瑜伽'), '身心平衡推荐', '哈他瑜伽课程,通过体式练习和呼吸控制,达到身心平衡,提升整体健康水平。', '改善身体柔韧性,增强核心力量,提升身体协调性', 11, true, 'coach_li', '2026-06-16 16:00:00', '2026-06-16 16:00:00'); + +INSERT INTO group_course_recommend (course_id, recommend_title, recommend_content, recommend_reason, priority, is_active, create_by, created_at, updated_at) VALUES +((SELECT id FROM group_course WHERE course_name = '午间冥想放松'), '职场减压课程', '午间冥想放松,专为职场人士设计,快速缓解工作压力,提升工作状态。', '职场减压首选,课程时间短,效果显著', 9, false, 'admin', '2026-06-12 10:00:00', '2026-06-12 10:00:00'); + +-- ============================================================ +-- Section K: 统计说明 +-- ============================================================ +-- 今日(2026-06-16)统计预期: +-- 新增会员: 3 (1005, 1006, 1007) +-- 签到会员: 12 +-- 预约总数: 12 (4001-4012),取消: 2,出席: 8,缺席: 2 +-- 签到总数: 12,成功: 11,失败: 1 + +-- ============================================================ +-- Section L: 测试场景汇总 +-- ============================================================ +-- 1. V7 团课预约场景: +-- - 极速燃脂单车: 明天,0人,可预约 +-- - 清晨流瑜伽: 6月20日,5/15人,可预约 +-- - 燃脂搏击: 6月18日,满员,不可预约 +-- - 哈他瑜伽: 今天17:45,距开始<30min,不可预约 +-- - 周末冥想修复: 6月28日,已取消(status=1) +-- - 蜜桃臀塑造: 6月10日,已结束(status=2) +-- - 午间冥想放松: 6月12日,已结束(status=2) +-- +-- 2. V10 会员卡预约资格校验: +-- - 用户A: 无会员卡 → "无可用会员卡" +-- - 用户B: 时长卡过期 → "会员卡已过期" +-- - 会员C: 时长卡有效 → 可正常预约 +-- - 会员D: 次数为0 → "剩余次数不足" +-- - 会员E: 次数充足 → 可正常预约 +-- - 会员F: 储值卡30元,课程需20元 → 可正常预约 +-- - 会员G: 储值卡200元,课程需20元 → 可正常预约 +-- +-- 3. V11 取消预约: +-- - 张三(时长卡)、李四(次数卡)、王五(储值卡) 预约了晚间瑜伽_取消测试 +-- - 取消后应恢复对应卡状态,释放课程名额 +-- +-- 4. V12 时间冲突检测: +-- - 团课A: 6/18 13:00-15:00, 团课B: 6/18 14:00-16:00 → 时间重叠 +-- - 团课C: 6/18 10:00-12:00 → 与A/B不冲突 +-- +-- 5. V14 数据统计: +-- - 3天签到/预约数据,覆盖今日/昨日/前日 +-- +-- 6. V18 团课推荐: +-- - 10条推荐记录,覆盖不同优先级和启用状态 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 deleted file mode 100644 index ca01bc4..0000000 --- a/gym-manage-api/manage-db/src/main/resources/db/migration/V13__Create_SignInRecord_table.sql +++ /dev/null @@ -1,99 +0,0 @@ --- ============================================ --- 签到记录表(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/V14__Insert_DataCount_Test_Data.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V14__Insert_DataCount_Test_Data.sql deleted file mode 100644 index 01ccc12..0000000 --- a/gym-manage-api/manage-db/src/main/resources/db/migration/V14__Insert_DataCount_Test_Data.sql +++ /dev/null @@ -1,120 +0,0 @@ --- 数据统计模块测试数据 --- 用于测试会员、预约、签到统计接口 - --- 插入测试会员数据 -INSERT INTO member_user (id, member_no, nickname, phone, created_at, updated_at, is_deleted) VALUES -(1001, 'M20260601001', '张三', '13800138001', '2026-06-01 08:00:00', '2026-06-01 08:00:00', false), -(1002, 'M20260601002', '李四', '13800138002', '2026-06-01 09:00:00', '2026-06-01 09:00:00', false), -(1003, 'M20260602003', '王五', '13800138003', '2026-06-02 10:00:00', '2026-06-02 10:00:00', false), -(1004, 'M20260603004', '赵六', '13800138004', '2026-06-03 11:00:00', '2026-06-03 11:00:00', false), -(1005, 'M20260609005', '钱七', '13800138005', '2026-06-09 08:00:00', '2026-06-09 08:00:00', false), -(1006, 'M20260609006', '孙八', '13800138006', '2026-06-09 09:00:00', '2026-06-09 09:00:00', false), -(1007, 'M20260609007', '周九', '13800138007', '2026-06-09 10:00:00', '2026-06-09 10:00:00', false), -(1008, 'M20260604008', '吴十', '13800138008', '2026-06-04 14:00:00', '2026-06-04 14:00:00', false), -(1009, 'M20260605009', '郑十一', '13800138009', '2026-06-05 15:00:00', '2026-06-05 15:00:00', false), -(1010, 'M20260606010', '王十二', '13800138010', '2026-06-06 16:00:00', '2026-06-06 16:00:00', false), -(1011, 'M20260607011', '陈十三', '13800138011', '2026-06-07 17:00:00', '2026-06-07 17:00:00', false), -(1012, 'M20260608012', '刘十四', '13800138012', '2026-06-08 18:00:00', '2026-06-08 18:00:00', false) -ON CONFLICT (id) DO NOTHING; - --- 插入测试签到记录数据 (今天的数据) -INSERT INTO sign_in_record (id, member_id, sign_in_time, sign_in_type, sign_in_status, source, is_delete) VALUES -(2001, 1001, '2026-06-09 08:00:00', 'QR_CODE', 'SUCCESS', 'MINI_PROGRAM', false), -(2002, 1002, '2026-06-09 08:15:00', 'MANUAL', 'SUCCESS', 'PC_BACKEND', false), -(2003, 1003, '2026-06-09 08:30:00', 'FACE', 'SUCCESS', 'MINI_PROGRAM', false), -(2004, 1004, '2026-06-09 09:00:00', 'QR_CODE', 'SUCCESS', 'MINI_PROGRAM', false), -(2005, 1005, '2026-06-09 09:15:00', 'MANUAL', 'SUCCESS', 'PC_BACKEND', false), -(2006, 1006, '2026-06-09 09:30:00', 'QR_CODE', 'SUCCESS', 'MINI_PROGRAM', false), -(2007, 1007, '2026-06-09 10:00:00', 'FACE', 'SUCCESS', 'MINI_PROGRAM', false), -(2008, 1008, '2026-06-09 10:15:00', 'QR_CODE', 'SUCCESS', 'MINI_PROGRAM', false), -(2009, 1009, '2026-06-09 10:30:00', 'MANUAL', 'SUCCESS', 'PC_BACKEND', false), -(2010, 1010, '2026-06-09 11:00:00', 'QR_CODE', 'SUCCESS', 'MINI_PROGRAM', false), -(2011, 1011, '2026-06-09 11:15:00', 'FACE', 'FAIL', 'MINI_PROGRAM', false), -(2012, 1012, '2026-06-09 11:30:00', 'MANUAL', 'SUCCESS', 'PC_BACKEND', false) -ON CONFLICT (id) DO NOTHING; - --- 插入测试签到记录数据 (昨天的数据) -INSERT INTO sign_in_record (id, member_id, sign_in_time, sign_in_type, sign_in_status, source, is_delete) VALUES -(2013, 1001, '2026-06-08 07:30:00', 'QR_CODE', 'SUCCESS', 'MINI_PROGRAM', false), -(2014, 1002, '2026-06-08 08:00:00', 'MANUAL', 'SUCCESS', 'PC_BACKEND', false), -(2015, 1003, '2026-06-08 08:30:00', 'FACE', 'SUCCESS', 'MINI_PROGRAM', false), -(2016, 1004, '2026-06-08 09:00:00', 'QR_CODE', 'SUCCESS', 'MINI_PROGRAM', false), -(2017, 1005, '2026-06-08 09:30:00', 'MANUAL', 'SUCCESS', 'PC_BACKEND', false), -(2018, 1006, '2026-06-08 10:00:00', 'QR_CODE', 'SUCCESS', 'MINI_PROGRAM', false), -(2019, 1007, '2026-06-08 10:30:00', 'FACE', 'SUCCESS', 'MINI_PROGRAM', false), -(2020, 1008, '2026-06-08 11:00:00', 'QR_CODE', 'SUCCESS', 'MINI_PROGRAM', false) -ON CONFLICT (id) DO NOTHING; - --- 插入测试签到记录数据 (前天的数据) -INSERT INTO sign_in_record (id, member_id, sign_in_time, sign_in_type, sign_in_status, source, is_delete) VALUES -(2021, 1001, '2026-06-07 07:00:00', 'QR_CODE', 'SUCCESS', 'MINI_PROGRAM', false), -(2022, 1002, '2026-06-07 07:30:00', 'MANUAL', 'SUCCESS', 'PC_BACKEND', false), -(2023, 1003, '2026-06-07 08:00:00', 'FACE', 'FAIL', 'MINI_PROGRAM', false), -(2024, 1004, '2026-06-07 08:30:00', 'QR_CODE', 'SUCCESS', 'MINI_PROGRAM', false), -(2025, 1005, '2026-06-07 09:00:00', 'MANUAL', 'SUCCESS', 'PC_BACKEND', false) -ON CONFLICT (id) DO NOTHING; - --- 插入测试团课数据 -INSERT INTO group_course (id, course_name, coach_id, course_type, start_time, end_time, max_members, current_members, status, location, cover_image, description, created_at, updated_at) VALUES -(3001, '瑜伽入门', 1, 1, '2026-06-09 08:00:00', '2026-06-09 09:00:00', 20, 15, 0, '健身房A区', 'https://example.com/yoga.jpg', '适合初学者的瑜伽课程', '2026-06-01 10:00:00', '2026-06-01 10:00:00'), -(3002, '动感单车', 2, 2, '2026-06-09 09:30:00', '2026-06-09 10:30:00', 25, 20, 0, '健身房B区', 'https://example.com/spinning.jpg', '高强度有氧运动', '2026-06-01 11:00:00', '2026-06-01 11:00:00'), -(3003, '普拉提', 3, 1, '2026-06-09 14:00:00', '2026-06-09 15:00:00', 15, 10, 0, '健身房C区', 'https://example.com/pilates.jpg', '核心力量训练', '2026-06-01 12:00:00', '2026-06-01 12:00:00') -ON CONFLICT (id) DO NOTHING; - --- 插入测试团课预约数据 (今天的数据) -INSERT INTO group_course_booking (id, member_id, member_card_id, course_id, booking_time, status, created_at, updated_at) VALUES -(4001, 1001, 1, 3001, '2026-06-09 08:00:00', '2', '2026-06-08 20:00:00', '2026-06-09 08:30:00'), -(4002, 1002, 2, 3001, '2026-06-09 08:00:00', '2', '2026-06-08 21:00:00', '2026-06-09 08:30:00'), -(4003, 1003, 3, 3001, '2026-06-09 08:00:00', '3', '2026-06-08 22:00:00', '2026-06-09 09:00:00'), -(4004, 1004, 4, 3002, '2026-06-09 09:30:00', '2', '2026-06-08 19:00:00', '2026-06-09 09:30:00'), -(4005, 1005, 5, 3002, '2026-06-09 09:30:00', '1', '2026-06-08 20:30:00', '2026-06-09 09:00:00'), -(4006, 1006, 6, 3002, '2026-06-09 09:30:00', '2', '2026-06-08 21:30:00', '2026-06-09 09:30:00'), -(4007, 1007, 7, 3003, '2026-06-09 14:00:00', '2', '2026-06-08 22:30:00', '2026-06-09 14:00:00'), -(4008, 1008, 8, 3003, '2026-06-09 14:00:00', '3', '2026-06-09 08:00:00', '2026-06-09 14:00:00'), -(4009, 1009, 9, 3003, '2026-06-09 14:00:00', '2', '2026-06-09 09:00:00', '2026-06-09 14:00:00'), -(4010, 1010, 10, 3001, '2026-06-09 08:00:00', '2', '2026-06-09 07:00:00', '2026-06-09 08:00:00'), -(4011, 1011, 11, 3002, '2026-06-09 09:30:00', '1', '2026-06-09 08:30:00', '2026-06-09 09:00:00'), -(4012, 1012, 12, 3003, '2026-06-09 14:00:00', '2', '2026-06-09 10:00:00', '2026-06-09 14:00:00') -ON CONFLICT (id) DO NOTHING; - --- 插入测试团课预约数据 (昨天的数据) -INSERT INTO group_course_booking (id, member_id, member_card_id, course_id, booking_time, status, created_at, updated_at) VALUES -(4013, 1001, 1, 3001, '2026-06-08 08:00:00', '2', '2026-06-07 20:00:00', '2026-06-08 08:30:00'), -(4014, 1002, 2, 3001, '2026-06-08 08:00:00', '2', '2026-06-07 21:00:00', '2026-06-08 08:30:00'), -(4015, 1003, 3, 3002, '2026-06-08 09:30:00', '3', '2026-06-07 22:00:00', '2026-06-08 09:30:00'), -(4016, 1004, 4, 3002, '2026-06-08 09:30:00', '2', '2026-06-07 19:00:00', '2026-06-08 09:30:00'), -(4017, 1005, 5, 3003, '2026-06-08 14:00:00', '1', '2026-06-07 20:30:00', '2026-06-08 13:00:00'), -(4018, 1006, 6, 3003, '2026-06-08 14:00:00', '2', '2026-06-07 21:30:00', '2026-06-08 14:00:00') -ON CONFLICT (id) DO NOTHING; - --- 插入测试团课预约数据 (前天的数据) -INSERT INTO group_course_booking (id, member_id, member_card_id, course_id, booking_time, status, created_at, updated_at) VALUES -(4019, 1001, 1, 3002, '2026-06-07 09:30:00', '2', '2026-06-06 20:00:00', '2026-06-07 09:30:00'), -(4020, 1002, 2, 3002, '2026-06-07 09:30:00', '2', '2026-06-06 21:00:00', '2026-06-07 09:30:00'), -(4021, 1003, 3, 3003, '2026-06-07 14:00:00', '2', '2026-06-06 22:00:00', '2026-06-07 14:00:00'), -(4022, 1004, 4, 3003, '2026-06-07 14:00:00', '3', '2026-06-06 19:00:00', '2026-06-07 14:00:00') -ON CONFLICT (id) DO NOTHING; - --- 预期统计结果 (今日): --- 会员统计: --- 新增会员: 3 (1005, 1006, 1007) --- 活跃会员: 12 (所有会员今日或近期有活动) --- 总会员数: 12 --- 签到会员: 12 --- 预约会员: 9 --- 取消会员: 2 - --- 预约统计: --- 总预约: 12 --- 取消: 2 --- 出席: 8 --- 缺席: 2 --- 出席率: 8/10 = 80% --- 取消率: 2/12 = 16.67% - --- 签到统计: --- 总签到: 12 --- 成功: 11 --- 失败: 1 --- 成功率: 11/12 = 91.67% --- 类型分布: QR_CODE=5, MANUAL=4, FACE=3 diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V18__Insert_GroupCourse_Recommend_test_data.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V18__Insert_GroupCourse_Recommend_test_data.sql deleted file mode 100644 index 87f53c6..0000000 --- a/gym-manage-api/manage-db/src/main/resources/db/migration/V18__Insert_GroupCourse_Recommend_test_data.sql +++ /dev/null @@ -1,43 +0,0 @@ --- ============================================ --- 团课推荐测试数据 --- ============================================ - --- 推荐数据1: 极速燃脂单车 - 高优先级推荐(热门课程) -INSERT INTO group_course_recommend (course_id, recommend_title, recommend_content, recommend_reason, priority, is_active, create_by, created_at, updated_at) VALUES - (1, '本周热门推荐', '极速燃脂单车课程,跟随音乐节奏变换阻力和速度,体验爬坡与冲刺的快感,一节课消耗800大卡!', '教练专业,课程内容丰富,深受学员喜爱,燃脂效果显著', 20, true, 'admin', '2026-06-15 10:00:00', '2026-06-15 10:00:00'); - --- 推荐数据2: 清晨流瑜伽 - 中等优先级推荐(适合新手) -INSERT INTO group_course_recommend (course_id, recommend_title, recommend_content, recommend_reason, priority, is_active, create_by, created_at, updated_at) VALUES - (2, '新手友好推荐', '清晨流瑜伽课程,适合有一定基础的学员,通过流畅的体式连接呼吸,唤醒身体能量。', '适合新手入门,教练耐心指导,课程节奏适中', 15, true, 'admin', '2026-06-15 11:00:00', '2026-06-15 11:00:00'); - --- 推荐数据3: 燃脂搏击 - 低优先级推荐(满员课程,已禁用) -INSERT INTO group_course_recommend (course_id, recommend_title, recommend_content, recommend_reason, priority, is_active, create_by, created_at, updated_at) VALUES - (3, '高强度燃脂', '燃脂搏击课程,高强度间歇训练,配合音乐快速燃脂,释放压力。', '高强度训练,适合进阶学员,快速燃脂塑形', 10, false, 'admin', '2026-06-15 12:00:00', '2026-06-15 12:00:00'); - --- 推荐数据4: 哈他瑜伽 - 中等优先级推荐(基础课程) -INSERT INTO group_course_recommend (course_id, recommend_title, recommend_content, recommend_reason, priority, is_active, create_by, created_at, updated_at) VALUES - (4, '基础瑜伽推荐', '基础哈他瑜伽课程,适合所有级别学员,通过基础体式练习提升身体柔韧性和平衡能力。', '零基础友好,适合所有健身水平,放松身心', 12, true, 'coach_li', '2026-06-15 13:00:00', '2026-06-15 13:00:00'); - --- 推荐数据5: 蜜桃臀塑造 - 高优先级推荐(热门课程,但课程已结束) -INSERT INTO group_course_recommend (course_id, recommend_title, recommend_content, recommend_reason, priority, is_active, create_by, created_at, updated_at) VALUES - (6, '塑形热门课程', '蜜桃臀塑造课程,针对性训练臀部肌肉群,打造完美曲线。', '专业私教指导,动作标准,效果显著,深受女性学员喜爱', 18, true, 'coach_li', '2026-05-25 09:15:00', '2026-05-25 09:15:00'); - --- 推荐数据6: 午间冥想放松 - 低优先级推荐(放松课程) -INSERT INTO group_course_recommend (course_id, recommend_title, recommend_content, recommend_reason, priority, is_active, create_by, created_at, updated_at) VALUES - (7, '午间放松推荐', '午间冥想放松课程,通过呼吸和正念冥想,深度放松身心,缓解工作压力。', '适合上班族,午间放松充电,提升下午工作效率', 8, true, 'admin', '2026-05-25 09:00:00', '2026-05-25 09:00:00'); - --- 推荐数据7: 极速燃脂单车 - 第二个推荐(不同角度推荐) -INSERT INTO group_course_recommend (course_id, recommend_title, recommend_content, recommend_reason, priority, is_active, create_by, created_at, updated_at) VALUES - (1, '减脂首选课程', '想要快速减脂?极速燃脂单车是你的最佳选择!专业教练带领,科学训练计划。', '减脂效果最佳,课程强度适中,适合想要快速瘦身的学员', 16, true, 'coach_zhang', '2026-06-15 14:00:00', '2026-06-15 14:00:00'); - --- 推荐数据8: 清晨流瑜伽 - 第二个推荐(不同角度推荐) -INSERT INTO group_course_recommend (course_id, recommend_title, recommend_content, recommend_reason, priority, is_active, create_by, created_at, updated_at) VALUES - (2, '晨练优选', '清晨流瑜伽,唤醒身体能量,开启活力一天!适合晨练爱好者。', '晨练最佳选择,提升身体活力,改善精神状态', 14, true, 'coach_wang', '2026-06-15 15:00:00', '2026-06-15 15:00:00'); - --- 推荐数据9: 哈他瑜伽 - 第二个推荐(不同角度推荐) -INSERT INTO group_course_recommend (course_id, recommend_title, recommend_content, recommend_reason, priority, is_active, create_by, created_at, updated_at) VALUES - (4, '身心平衡推荐', '哈他瑜伽课程,通过体式练习和呼吸控制,达到身心平衡,提升整体健康水平。', '改善身体柔韧性,增强核心力量,提升身体协调性', 11, true, 'coach_li', '2026-06-15 16:00:00', '2026-06-15 16:00:00'); - --- 推荐数据10: 午间冥想放松 - 第二个推荐(不同角度推荐,已禁用) -INSERT INTO group_course_recommend (course_id, recommend_title, recommend_content, recommend_reason, priority, is_active, create_by, created_at, updated_at) VALUES - (7, '职场减压课程', '午间冥想放松,专为职场人士设计,快速缓解工作压力,提升工作状态。', '职场减压首选,课程时间短,效果显著', 9, false, 'admin', '2026-05-25 10:00:00', '2026-05-25 10:00:00'); \ No newline at end of file diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V20__Add_deleted_at_to_member_user.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V20__Add_deleted_at_to_member_user.sql new file mode 100644 index 0000000..445525b --- /dev/null +++ b/gym-manage-api/manage-db/src/main/resources/db/migration/V20__Add_deleted_at_to_member_user.sql @@ -0,0 +1,9 @@ +-- ============================================ +-- V20: 为 member_user 表添加 deleted_at 字段 +-- 原因:BaseEntity 已映射该字段,但 member_user 表缺失 +-- ============================================ + +ALTER TABLE member_user + ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMP; + +COMMENT ON COLUMN member_user.deleted_at IS '软删除时间'; diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V21__Add_updated_at_to_member_card_transactions.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V21__Add_updated_at_to_member_card_transactions.sql new file mode 100644 index 0000000..ea780bd --- /dev/null +++ b/gym-manage-api/manage-db/src/main/resources/db/migration/V21__Add_updated_at_to_member_card_transactions.sql @@ -0,0 +1,9 @@ +-- ============================================ +-- V21: 为 member_card_transactions 表添加 updated_at 字段 +-- 原因:MemberCardTransaction 继承 BaseEntity 已映射该字段,但表缺失 +-- ============================================ + +ALTER TABLE member_card_transactions + ADD COLUMN IF NOT EXISTS updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP; + +COMMENT ON COLUMN member_card_transactions.updated_at IS '更新时间'; diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V22__Add_Business_Permissions.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V22__Add_Business_Permissions.sql new file mode 100644 index 0000000..d038da2 --- /dev/null +++ b/gym-manage-api/manage-db/src/main/resources/db/migration/V22__Add_Business_Permissions.sql @@ -0,0 +1,100 @@ +-- ============================================ +-- V22: 新增业务模块权限 +-- 描述: 为会员、会员卡、团课、签到、数据统计、员工管理等业务模块创建权限 +-- ============================================ + +-- ============================================ +-- 会员管理权限 +-- ============================================ +INSERT INTO sys_permission (permission_name, permission_code, resource, action, description, status, create_by, update_by, created_at, updated_at) VALUES +('会员查看', 'business:member:view', '/api/admin/members', 'GET', '查看会员列表及详情', 1, 'system', 'system', NOW(), NOW()), +('会员编辑', 'business:member:edit', '/api/admin/member', 'PUT', '编辑会员信息', 1, 'system', 'system', NOW(), NOW()), +('会员手机修改', 'business:member:phone', '/api/admin/member/phone', 'POST', '管理员修改会员手机号', 1, 'system', 'system', NOW(), NOW()); + +-- ============================================ +-- 会员卡管理权限 +-- ============================================ +INSERT INTO sys_permission (permission_name, permission_code, resource, action, description, status, create_by, update_by, created_at, updated_at) VALUES +('会员卡查看', 'business:memberCard:view', '/api/member-cards', 'GET', '查看会员卡列表及详情', 1, 'system', 'system', NOW(), NOW()), +('会员卡创建', 'business:memberCard:create', '/api/member-cards', 'POST', '创建会员卡类型', 1, 'system', 'system', NOW(), NOW()), +('会员卡编辑', 'business:memberCard:edit', '/api/member-cards', 'PUT', '编辑会员卡类型', 1, 'system', 'system', NOW(), NOW()), +('会员卡删除', 'business:memberCard:delete', '/api/member-cards', 'DELETE', '删除会员卡类型', 1, 'system', 'system', NOW(), NOW()), +('会员卡购买', 'business:memberCard:purchase', '/api/member-card-records/purchase', 'POST', '为会员购买/绑定会员卡', 1, 'system', 'system', NOW(), NOW()), +('会员卡退卡', 'business:memberCard:refund', '/api/member-card-records/refund', 'POST', '会员卡退卡/解绑', 1, 'system', 'system', NOW(), NOW()); + +-- ============================================ +-- 团课管理权限 +-- ============================================ +INSERT INTO sys_permission (permission_name, permission_code, resource, action, description, status, create_by, update_by, created_at, updated_at) VALUES +('团课查看', 'business:groupCourse:view', '/api/groupCourse', 'GET', '查看团课列表及详情', 1, 'system', 'system', NOW(), NOW()), +('团课创建', 'business:groupCourse:create', '/api/groupCourse', 'POST', '创建团课', 1, 'system', 'system', NOW(), NOW()), +('团课编辑', 'business:groupCourse:edit', '/api/groupCourse', 'PUT', '编辑团课信息', 1, 'system', 'system', NOW(), NOW()), +('团课删除', 'business:groupCourse:delete', '/api/groupCourse', 'DELETE', '删除团课', 1, 'system', 'system', NOW(), NOW()), +('团课取消', 'business:groupCourse:cancel', '/api/groupCourse/cancel', 'POST', '取消团课', 1, 'system', 'system', NOW(), NOW()); + +-- ============================================ +-- 团课类型管理权限 +-- ============================================ +INSERT INTO sys_permission (permission_name, permission_code, resource, action, description, status, create_by, update_by, created_at, updated_at) VALUES +('团课类型查看', 'business:groupCourseType:view', '/api/groupCourse/types', 'GET', '查看团课类型列表', 1, 'system', 'system', NOW(), NOW()), +('团课类型创建', 'business:groupCourseType:create', '/api/groupCourse/types', 'POST', '创建团课类型', 1, 'system', 'system', NOW(), NOW()), +('团课类型编辑', 'business:groupCourseType:edit', '/api/groupCourse/types', 'PUT', '编辑团课类型', 1, 'system', 'system', NOW(), NOW()), +('团课类型删除', 'business:groupCourseType:delete', '/api/groupCourse/types', 'DELETE', '删除团课类型', 1, 'system', 'system', NOW(), NOW()); + +-- ============================================ +-- 团课推荐管理权限 +-- ============================================ +INSERT INTO sys_permission (permission_name, permission_code, resource, action, description, status, create_by, update_by, created_at, updated_at) VALUES +('团课推荐查看', 'business:groupCourseRecommend:view', '/api/groupCourse/recommend', 'GET', '查看推荐团课列表', 1, 'system', 'system', NOW(), NOW()), +('团课推荐创建', 'business:groupCourseRecommend:create', '/api/groupCourse/recommend', 'POST', '创建推荐团课', 1, 'system', 'system', NOW(), NOW()), +('团课推荐编辑', 'business:groupCourseRecommend:edit', '/api/groupCourse/recommend', 'PUT', '编辑推荐团课', 1, 'system', 'system', NOW(), NOW()), +('团课推荐删除', 'business:groupCourseRecommend:delete', '/api/groupCourse/recommend', 'DELETE', '删除推荐团课', 1, 'system', 'system', NOW(), NOW()); + +-- ============================================ +-- 团课预约管理权限 +-- ============================================ +INSERT INTO sys_permission (permission_name, permission_code, resource, action, description, status, create_by, update_by, created_at, updated_at) VALUES +('团课预约查看', 'business:groupCourseBooking:view', '/api/groupCourse/bookings', 'GET', '查看团课预约记录', 1, 'system', 'system', NOW(), NOW()), +('团课预约创建', 'business:groupCourseBooking:create', '/api/groupCourse/book', 'POST', '预约团课', 1, 'system', 'system', NOW(), NOW()), +('团课预约取消', 'business:groupCourseBooking:cancel', '/api/groupCourse/booking/cancel', 'POST', '取消团课预约', 1, 'system', 'system', NOW(), NOW()); + +-- ============================================ +-- 签到管理权限 +-- ============================================ +INSERT INTO sys_permission (permission_name, permission_code, resource, action, description, status, create_by, update_by, created_at, updated_at) VALUES +('签到查看', 'business:checkIn:view', '/api/checkIn/records', 'GET', '查看签到记录', 1, 'system', 'system', NOW(), NOW()), +('签到操作', 'business:checkIn:create', '/api/checkIn', 'POST', '执行签到操作', 1, 'system', 'system', NOW(), NOW()), +('签到导出', 'business:checkIn:export', '/api/checkIn/records/export', 'GET', '导出签到记录', 1, 'system', 'system', NOW(), NOW()); + +-- ============================================ +-- 数据统计权限 +-- ============================================ +INSERT INTO sys_permission (permission_name, permission_code, resource, action, description, status, create_by, update_by, created_at, updated_at) VALUES +('数据统计查看', 'business:dataCount:view', '/api/datacount', 'GET', '查看数据统计报表', 1, 'system', 'system', NOW(), NOW()), +('数据统计导出', 'business:dataCount:export', '/api/datacount/export', 'GET', '导出统计报表', 1, 'system', 'system', NOW(), NOW()); + +-- ============================================ +-- 员工管理权限 +-- ============================================ +INSERT INTO sys_permission (permission_name, permission_code, resource, action, description, status, create_by, update_by, created_at, updated_at) VALUES +('员工查看', 'business:employee:view', '/api/employees/page', 'GET', '查看员工列表', 1, 'system', 'system', NOW(), NOW()), +('员工创建', 'business:employee:create', '/api/users', 'POST', '创建员工账号', 1, 'system', 'system', NOW(), NOW()), +('员工编辑', 'business:employee:edit', '/api/users', 'PUT', '编辑员工信息', 1, 'system', 'system', NOW(), NOW()), +('员工删除', 'business:employee:delete', '/api/users', 'DELETE', '删除/禁用员工账号', 1, 'system', 'system', NOW(), NOW()), +('员工分配角色', 'business:employee:assignRole', '/api/users/roles', 'POST', '为员工分配角色', 1, 'system', 'system', NOW(), NOW()); + +-- ============================================ +-- 为超级管理员角色分配所有业务权限 +-- ============================================ +INSERT INTO sys_role_permission (role_id, permission_id, create_by, update_by, created_at, updated_at) +SELECT 1, id, 'system', 'system', NOW(), NOW() +FROM sys_permission +WHERE permission_code LIKE 'business:%' + AND status = 1 + AND id NOT IN (SELECT permission_id FROM sys_role_permission WHERE role_id = 1); + +-- ============================================ +-- 重置序列值 +-- ============================================ +SELECT setval('sys_permission_id_seq', (SELECT COALESCE(MAX(id), 1) FROM sys_permission)); +SELECT setval('sys_role_permission_id_seq', (SELECT COALESCE(MAX(id), 1) FROM sys_role_permission)); diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V23__Add_Coach_Test_Data.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V23__Add_Coach_Test_Data.sql new file mode 100644 index 0000000..fa0167c --- /dev/null +++ b/gym-manage-api/manage-db/src/main/resources/db/migration/V23__Add_Coach_Test_Data.sql @@ -0,0 +1,93 @@ +-- ============================================================ +-- V23: 教练账号测试数据 +-- 描述: 创建教练角色、教练用户及相关团课测试数据 +-- 日期: 2026-06-22 +-- ============================================================ + +-- ============================================================ +-- 1. 新增教练专属权限 +-- 教练只能查看自己教授的团课/预约/签到信息,不能修改数据库 +-- ============================================================ +INSERT INTO sys_permission (permission_name, permission_code, resource, action, description, status, create_by, update_by, created_at, updated_at) VALUES +('查看自己的团课', 'coach:course:view', '/api/coach/courses', 'GET', '查看教练自己教授的团课列表及详情', 1, 'system', 'system', NOW(), NOW()), +('查看自己团课的预约', 'coach:booking:view', '/api/coach/bookings', 'GET', '查看教练自己团课的会员预约记录', 1, 'system', 'system', NOW(), NOW()), +('查看自己团课的签到', 'coach:signIn:view', '/api/coach/signins', 'GET', '查看教练自己团课的会员签到记录', 1, 'system', 'system', NOW(), NOW()), +('教练执行签到', 'coach:signIn:create', '/api/coach/signins', 'POST', '教练为自己团课的会员执行签到操作', 1, 'system', 'system', NOW(), NOW()) +ON CONFLICT (permission_code) DO NOTHING; + +-- ============================================================ +-- 2. 教练角色 +-- ============================================================ +INSERT INTO sys_role (id, role_name, role_key, role_sort, status, create_by, update_by, created_at, updated_at) +VALUES (5, '教练', 'coach', 5, 1, 'system', 'system', NOW(), NOW()) +ON CONFLICT (role_key) DO NOTHING; + +-- ============================================================ +-- 3. 教练用户(密码: Test@123) +-- ============================================================ +INSERT INTO sys_user (id, username, password, email, phone, nickname, status, create_by, update_by, created_at, updated_at) +VALUES (20, 'coach', '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C', 'coach@novalon.com', '13900000001', '张教练', 1, 'system', 'system', NOW(), NOW()) +ON CONFLICT (username) DO NOTHING; + +-- ============================================================ +-- 4. 教练角色关联 +-- ============================================================ +INSERT INTO user_role (user_id, role_id, created_by, created_at) +VALUES (20, 5, 'system', NOW()) +ON CONFLICT (user_id, role_id) DO NOTHING; + +-- ============================================================ +-- 5. 为教练角色分配有限权限 +-- 教练只能查看自己教授的团课、查看自己团课的预约/签到信息、为自己团课的会员签到 +-- 不能对数据库进行写操作(签到属于业务操作,非数据库直接写入) +-- ============================================================ +INSERT INTO sys_role_permission (role_id, permission_id, create_by, update_by, created_at, updated_at) +SELECT 5, id, 'system', 'system', NOW(), NOW() +FROM sys_permission +WHERE permission_code IN ( + 'coach:course:view', -- 查看自己的团课 + 'coach:booking:view', -- 查看自己团课的预约信息 + 'coach:signIn:view', -- 查看自己团课的签到信息 + 'coach:signIn:create', -- 为自己团课的会员签到 + 'system:notice:view' -- 查看系统公告 +) +AND NOT EXISTS ( + SELECT 1 FROM sys_role_permission WHERE role_id = 5 AND permission_id = sys_permission.id +); + +-- ============================================================ +-- 6. 为教练分配今天的测试团课 +-- ============================================================ +INSERT INTO group_course (course_name, coach_id, course_type, start_time, end_time, max_members, current_members, status, location, description, point_card_amount, create_by, created_at, updated_at) +VALUES +('核心力量训练', 20, (SELECT id FROM group_course_type WHERE type_name = '核心训练' LIMIT 1), + CURRENT_DATE + TIME '08:00', CURRENT_DATE + TIME '09:00', 15, 3, '0', '健身房B区', '针对核心肌群的专项训练,适合所有水平学员', 1, 'system', NOW(), NOW()), + +('瑜伽入门', 20, (SELECT id FROM group_course_type WHERE type_name = '瑜伽入门' LIMIT 1), + CURRENT_DATE + TIME '10:00', CURRENT_DATE + TIME '11:00', 12, 5, '0', '瑜伽教室', '基础的瑜伽体式教学,新课上线欢迎参加', 1, 'system', NOW(), NOW()), + +('动感单车', 20, (SELECT id FROM group_course_type WHERE type_name = '动感单车' LIMIT 1), + CURRENT_DATE + TIME '14:00', CURRENT_DATE + TIME '15:00', 20, 8, '0', '单车房', '高强度燃脂骑行,动感音乐带你飞', 2, 'system', NOW(), NOW()), + +('晚间普拉提', 20, (SELECT id FROM group_course_type WHERE type_name = '普拉提' LIMIT 1), + CURRENT_DATE + TIME '18:00', CURRENT_DATE + TIME '19:00', 10, 2, '0', '普拉提教室', '注重核心力量和身体控制,放松一天疲劳', 1, 'system', NOW(), NOW()); + +-- ============================================================ +-- 7. 为教练创建一个未来的团课(方便测试时间表) +-- ============================================================ +INSERT INTO group_course (course_name, coach_id, course_type, start_time, end_time, max_members, current_members, status, location, description, point_card_amount, create_by, created_at, updated_at) +VALUES +('HIIT燃脂训练', 20, (SELECT id FROM group_course_type WHERE type_name = 'HIIT训练' LIMIT 1), + CURRENT_DATE + INTERVAL '1 day' + TIME '08:00', CURRENT_DATE + INTERVAL '1 day' + TIME '09:00', + 15, 0, '0', '综合训练区', '高强度间歇训练,挑战心肺极限', 2, 'system', NOW(), NOW()), + +('周末瑜伽', 20, (SELECT id FROM group_course_type WHERE type_name = '瑜伽进阶' LIMIT 1), + CURRENT_DATE + INTERVAL '3 days' + TIME '09:00', CURRENT_DATE + INTERVAL '3 days' + TIME '10:30', + 12, 0, '0', '瑜伽教室', '针对有基础的进阶瑜伽课程', 1, 'system', NOW(), NOW()); + +-- ============================================================ +-- 8. 重置序列值 +-- ============================================================ +SELECT setval('sys_role_id_seq', GREATEST((SELECT COALESCE(MAX(id), 0) FROM sys_role), 5)); +SELECT setval('sys_user_id_seq', GREATEST((SELECT COALESCE(MAX(id), 0) FROM sys_user), 20)); +SELECT setval('group_course_id_seq', (SELECT COALESCE(MAX(id), 0) FROM group_course)); diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V24__Add_Second_Coach_Test_Data.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V24__Add_Second_Coach_Test_Data.sql new file mode 100644 index 0000000..9398aef --- /dev/null +++ b/gym-manage-api/manage-db/src/main/resources/db/migration/V24__Add_Second_Coach_Test_Data.sql @@ -0,0 +1,50 @@ +-- ============================================================ +-- V24: 新增第二个测试教练账号(李教练) +-- 描述: +-- 1. 新增测试教练用户 coachli(李教练) +-- 2. 为李教练分配教练角色(继承教练的有限权限) +-- 3. 为李教练分配未来一周的测试团课 +-- 日期: 2026-06-22 +-- ============================================================ + +-- ============================================================ +-- 1. 第二个测试教练账号(密码: Test@123) +-- ============================================================ +INSERT INTO sys_user (id, username, password, email, phone, nickname, status, create_by, update_by, created_at, updated_at) +VALUES (21, 'coachli', '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C', 'coachli@novalon.com', '13900000002', '李教练', 1, 'system', 'system', NOW(), NOW()) +ON CONFLICT (username) DO NOTHING; + +-- ============================================================ +-- 2. 为李教练分配教练角色(role_id=5) +-- 继承教练角色的所有有限权限 +-- ============================================================ +INSERT INTO user_role (user_id, role_id, created_by, created_at) +VALUES (21, 5, 'system', NOW()) +ON CONFLICT (user_id, role_id) DO NOTHING; + +-- ============================================================ +-- 3. 为李教练分配测试团课(未来一周的课程) +-- ============================================================ +INSERT INTO group_course (course_name, coach_id, course_type, start_time, end_time, max_members, current_members, status, location, description, point_card_amount, create_by, created_at, updated_at) +VALUES +('入门瑜伽', 21, (SELECT id FROM group_course_type WHERE type_name = '瑜伽入门' LIMIT 1), + CURRENT_DATE + INTERVAL '2 days' + TIME '09:00', CURRENT_DATE + INTERVAL '2 days' + TIME '10:00', + 15, 0, '0', '瑜伽教室', '适合零基础学员的入门瑜伽课程', 1, 'system', NOW(), NOW()), + +('燃脂搏击', 21, (SELECT id FROM group_course_type WHERE type_name = '动感单车' LIMIT 1), + CURRENT_DATE + INTERVAL '2 days' + TIME '15:00', CURRENT_DATE + INTERVAL '2 days' + TIME '16:00', + 20, 0, '0', '综合训练区', '高强度搏击操,燃烧卡路里', 2, 'system', NOW(), NOW()), + +('肩颈理疗', 21, (SELECT id FROM group_course_type WHERE type_name = '普拉提' LIMIT 1), + CURRENT_DATE + INTERVAL '4 days' + TIME '10:00', CURRENT_DATE + INTERVAL '4 days' + TIME '11:00', + 12, 0, '0', '普拉提教室', '针对久坐人群的肩颈放松课程', 1, 'system', NOW(), NOW()), + +('晨间有氧', 21, (SELECT id FROM group_course_type WHERE type_name = 'HIIT训练' LIMIT 1), + CURRENT_DATE + INTERVAL '5 days' + TIME '07:00', CURRENT_DATE + INTERVAL '5 days' + TIME '08:00', + 20, 0, '0', '有氧区', '清晨燃脂有氧训练,开启活力一天', 2, 'system', NOW(), NOW()); + +-- ============================================================ +-- 4. 重置序列值 +-- ============================================================ +SELECT setval('sys_user_id_seq', GREATEST((SELECT COALESCE(MAX(id), 0) FROM sys_user), 21)); +SELECT setval('group_course_id_seq', (SELECT COALESCE(MAX(id), 0) FROM group_course)); diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V6__Create_GroupCourse_table.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V6__Create_GroupCourse_table.sql index 0e26497..714cb41 100644 --- a/gym-manage-api/manage-db/src/main/resources/db/migration/V6__Create_GroupCourse_table.sql +++ b/gym-manage-api/manage-db/src/main/resources/db/migration/V6__Create_GroupCourse_table.sql @@ -13,6 +13,8 @@ CREATE TABLE IF NOT EXISTS group_course ( max_members INTEGER DEFAULT 20, current_members INTEGER DEFAULT 0, status VARCHAR(1) DEFAULT '0', + actual_start_time TIMESTAMP, + actual_end_time TIMESTAMP, location VARCHAR(255), cover_image VARCHAR(500), description TEXT, @@ -50,7 +52,9 @@ COMMENT ON COLUMN group_course.start_time IS '开始时间'; COMMENT ON COLUMN group_course.end_time IS '结束时间'; COMMENT ON COLUMN group_course.max_members IS '最大参与人数'; COMMENT ON COLUMN group_course.current_members IS '当前参与人数'; -COMMENT ON COLUMN group_course.status IS '状态(0正常 1已取消 2已结束)'; +COMMENT ON COLUMN group_course.status IS '状态(0正常 1已取消 2已结束 3进行中 4超时)'; +COMMENT ON COLUMN group_course.actual_start_time IS '实际开课时间(教练点击开课时记录)'; +COMMENT ON COLUMN group_course.actual_end_time IS '实际结课时间(教练点击结课时记录)'; COMMENT ON COLUMN group_course.location IS '上课地点'; COMMENT ON COLUMN group_course.cover_image IS '封面图URL'; COMMENT ON COLUMN group_course.description IS '课程描述'; diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V8__Create_Member_And_MemberCard.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V7__Create_Member_And_MemberCard.sql similarity index 100% rename from gym-manage-api/manage-db/src/main/resources/db/migration/V8__Create_Member_And_MemberCard.sql rename to gym-manage-api/manage-db/src/main/resources/db/migration/V7__Create_Member_And_MemberCard.sql diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V7__Insert_GroupCourse_test_data.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V7__Insert_GroupCourse_test_data.sql deleted file mode 100644 index ee7adaa..0000000 --- a/gym-manage-api/manage-db/src/main/resources/db/migration/V7__Insert_GroupCourse_test_data.sql +++ /dev/null @@ -1,28 +0,0 @@ --- 场景1: 0人预约,可预约(正常状态,6月15日,距开始还有14天) -INSERT INTO group_course (course_name, coach_id, course_type, start_time, end_time, max_members, current_members, status, location, cover_image, description, create_by, created_at, updated_at) VALUES - ('极速燃脂单车', 104, 2, '2026-06-02 16:45:00', '2026-06-15 20:20:00', 25, 0, 0, '单车房', '/images/spinning.jpg', '跟随音乐节奏变换阻力和速度,体验爬坡与冲刺的快感,一节课消耗800大卡。', 'admin', '2026-06-01 11:00:00', '2026-06-01 11:00:00'); - --- 场景2: 已有人预约,可预约(正常状态,6月12日,5/15人) -INSERT INTO group_course (course_name, coach_id, course_type, start_time, end_time, max_members, current_members, status, location, cover_image, description, create_by, created_at, updated_at) VALUES - ('清晨流瑜伽', 101, 1, '2026-06-12 09:00:00', '2026-06-12 10:30:00', 15, 5, 0, 'A座3楼瑜伽教室', '/images/yoga_flow.jpg', '适合有一定基础的学员,通过流畅的体式连接呼吸,唤醒身体能量。', 'admin', '2026-06-01 10:00:00', '2026-06-01 10:00:00'); - --- 场景3: 满员,不可预约(正常状态但已满员,6月10日,20/20人) -INSERT INTO group_course (course_name, coach_id, course_type, start_time, end_time, max_members, current_members, status, location, cover_image, description, create_by, created_at, updated_at) VALUES - ('燃脂搏击', 102, 2, '2026-06-10 18:30:00', '2026-06-10 19:30:00', 20, 20, 0, '综合训练区', '/images/kickboxing.jpg', '高强度间歇训练,配合音乐快速燃脂,释放压力。名额已满,无法预约。', 'coach_zhang', '2026-06-01 14:30:00', '2026-06-01 14:30:00'); - --- 场景4: 超出可预约时间,不可预约(正常状态但距开始不足30分钟) --- 当前时间: 2026-06-01 15:00,课程开始: 2026-06-01 15:20 -INSERT INTO group_course (course_name, coach_id, course_type, start_time, end_time, max_members, current_members, status, location, cover_image, description, create_by, created_at, updated_at) VALUES - ('哈他瑜伽', 101, 1, '2026-06-01 15:20:00', '2026-06-01 16:50:00', 12, 3, 0, '瑜伽教室B', '/images/hatha_yoga.jpg', '基础哈他瑜伽,适合所有级别。距开始不足30分钟,已停止预约。', 'coach_li', '2026-06-01 08:00:00', '2026-06-01 08:00:00'); - --- 场景5: 课程已取消,不可预约(status=1) -INSERT INTO group_course (course_name, coach_id, course_type, start_time, end_time, max_members, current_members, status, location, cover_image, description, create_by, created_at, updated_at) VALUES - ('周末冥想修复', 101, 1, '2026-06-20 15:00:00', '2026-06-20 16:00:00', 12, 3, 1, '冥想室', '/images/meditation.jpg', '通过呼吸和正念冥想,深度放松身心。该课程已被取消。', 'coach_wang', '2026-05-28 08:00:00', '2026-05-28 08:00:00'); - --- 场景6: 课程已结束,不可预约(status=2) -INSERT INTO group_course (course_name, coach_id, course_type, start_time, end_time, max_members, current_members, status, location, cover_image, description, create_by, created_at, updated_at) VALUES - ('蜜桃臀塑造', 103, 3, '2026-05-30 19:00:00', '2026-05-30 20:00:00', 10, 8, 2, '私教专区', '/images/glute.jpg', '针对性训练臀部肌肉群,课程已于5月30日结束,无法预约。', 'coach_li', '2026-05-20 09:15:00', '2026-05-20 09:15:00'); - --- 场景7(可选): 已结束但未满员的课程(status=2,即使有空位也不可预约) -INSERT INTO group_course (course_name, coach_id, course_type, start_time, end_time, max_members, current_members, status, location, cover_image, description, create_by, created_at, updated_at) VALUES - ('午间冥想放松', 101, 1, '2026-05-31 12:00:00', '2026-05-31 13:00:00', 15, 6, 2, '冥想室', '/images/meditation_noon.jpg', '午间冥想课程,已于5月31日结束。', 'admin', '2026-05-25 09:00:00', '2026-05-25 09:00:00'); \ No newline at end of file diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V9__Add_GroupCourse_Booking_Snapshot_Fields.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V8__Add_GroupCourse_Booking_Snapshot_Fields.sql similarity index 100% rename from gym-manage-api/manage-db/src/main/resources/db/migration/V9__Add_GroupCourse_Booking_Snapshot_Fields.sql rename to gym-manage-api/manage-db/src/main/resources/db/migration/V8__Add_GroupCourse_Booking_Snapshot_Fields.sql diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V13__Create_sign_in_record_table.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V9__Create_sign_in_record_table.sql similarity index 100% rename from gym-manage-api/manage-db/src/main/resources/db/migration/V13__Create_sign_in_record_table.sql rename to gym-manage-api/manage-db/src/main/resources/db/migration/V9__Create_sign_in_record_table.sql diff --git a/gym-manage-api/pom.xml b/gym-manage-api/pom.xml index 8746a4a..69441c7 100644 --- a/gym-manage-api/pom.xml +++ b/gym-manage-api/pom.xml @@ -47,6 +47,7 @@ gym-checkIn gym-dataCount gym-auth + gym-payment From 581cc995c66a7e135718d2f0a8e9cf89e7313d46 Mon Sep 17 00:00:00 2001 From: liwentao Date: Wed, 24 Jun 2026 07:27:44 +0800 Subject: [PATCH 6/6] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=90=8E=E7=AB=AFbug?= =?UTF-8?q?=EF=BC=8C=E6=9B=B4=E6=96=B0=E6=B5=8B=E8=AF=95=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../groupcourse/dao/GroupCourseDao.java | 9 +- .../groupcourse/domain/GroupCourse.java | 12 - .../groupcourse/domain/GroupCourseDetail.java | 11 - .../groupcourse/entity/GroupCourseEntity.java | 12 - .../handler/BookingSagaHandler.java | 7 +- .../scheduler/GroupCourseExpireScheduler.java | 46 ++++ .../service/impl/GroupCourseService.java | 23 +- .../migration/V13__Consolidated_Test_Data.sql | 208 +++++++++--------- .../db/migration/V23__Add_Coach_Test_Data.sql | 16 +- .../V24__Add_Second_Coach_Test_Data.sql | 10 +- .../V6__Create_GroupCourse_table.sql | 2 - 11 files changed, 182 insertions(+), 174 deletions(-) create mode 100644 gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/scheduler/GroupCourseExpireScheduler.java diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/dao/GroupCourseDao.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/dao/GroupCourseDao.java index dc0f318..c2473b3 100644 --- a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/dao/GroupCourseDao.java +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/dao/GroupCourseDao.java @@ -40,6 +40,10 @@ public interface GroupCourseDao extends R2dbcRepository @Query("UPDATE group_course SET deleted_at = :deletedAt WHERE id = :id") Mono softDelete(Long id, LocalDateTime deletedAt); + @Modifying + @Query("UPDATE group_course SET status = '2', updated_at = :updatedAt WHERE status = '0' AND end_time <= NOW() AND deleted_at IS NULL") + Mono completeExpiredCourses(LocalDateTime updatedAt); + Flux findByCourseTypeAndDeletedAtIsNull(Long courseType); /** @@ -49,8 +53,9 @@ public interface GroupCourseDao extends R2dbcRepository StringBuilder sql = new StringBuilder("SELECT * FROM group_course WHERE deleted_at IS NULL"); List conditions = new ArrayList<>(); - // 默认不查询不可预约的团课(仅查询 status = '0') + // 默认不查询可预约团课(status = '0' 且未过期) conditions.add("status = '0'"); + conditions.add("end_time > NOW()"); // 1. 团课名称模糊查询 if (query.getCourseName() != null && !query.getCourseName().isEmpty()) { @@ -154,7 +159,6 @@ public interface GroupCourseDao extends R2dbcRepository entity.setLocation(row.get("location", String.class)); entity.setCoverImage(row.get("cover_image", String.class)); entity.setDescription(row.get("description", String.class)); - entity.setPointCardAmount(row.get("point_card_amount", Integer.class)); entity.setStoredValueAmount(row.get("stored_value_amount", java.math.BigDecimal.class)); entity.setCreateBy(row.get("create_by", String.class)); entity.setUpdateBy(row.get("update_by", String.class)); @@ -173,6 +177,7 @@ public interface GroupCourseDao extends R2dbcRepository List conditions = new ArrayList<>(); conditions.add("status = '0'"); + conditions.add("end_time > NOW()"); if (query.getCourseName() != null && !query.getCourseName().isEmpty()) { conditions.add("course_name ILIKE :courseName"); diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/domain/GroupCourse.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/domain/GroupCourse.java index f059d36..2369e2d 100644 --- a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/domain/GroupCourse.java +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/domain/GroupCourse.java @@ -52,10 +52,6 @@ public class GroupCourse extends BaseDomain{ @Schema(description = "课程描述", example = "从入门到入土") private String description; - //点卡额度(消耗次数) - @Schema(description = "点卡额度(消耗次数)", example = "1") - private Integer pointCardAmount; - //储值卡额度(消耗金额) @Schema(description = "储值卡额度(消耗金额)", example = "50.00") private java.math.BigDecimal storedValueAmount; @@ -152,14 +148,6 @@ public class GroupCourse extends BaseDomain{ this.description = description; } - public Integer getPointCardAmount() { - return pointCardAmount; - } - - public void setPointCardAmount(Integer pointCardAmount) { - this.pointCardAmount = pointCardAmount; - } - public java.math.BigDecimal getStoredValueAmount() { return storedValueAmount; } diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/domain/GroupCourseDetail.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/domain/GroupCourseDetail.java index 9ca0202..bd693c9 100644 --- a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/domain/GroupCourseDetail.java +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/domain/GroupCourseDetail.java @@ -48,9 +48,6 @@ public class GroupCourseDetail extends BaseDomain { @Schema(description = "课程描述", example = "适合初学者的瑜伽课程") private String description; - @Schema(description = "点卡额度(消耗次数)", example = "1") - private Integer pointCardAmount; - @Schema(description = "储值卡额度(消耗金额)", example = "50.00") private BigDecimal storedValueAmount; @@ -174,14 +171,6 @@ public class GroupCourseDetail extends BaseDomain { this.description = description; } - public Integer getPointCardAmount() { - return pointCardAmount; - } - - public void setPointCardAmount(Integer pointCardAmount) { - this.pointCardAmount = pointCardAmount; - } - public BigDecimal getStoredValueAmount() { return storedValueAmount; } diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/entity/GroupCourseEntity.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/entity/GroupCourseEntity.java index 9e4d98c..5ef3fa7 100644 --- a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/entity/GroupCourseEntity.java +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/entity/GroupCourseEntity.java @@ -54,10 +54,6 @@ public class GroupCourseEntity extends BaseEntity { @Column("description") private String description; - //点卡额度(消耗次数) - @Column("point_card_amount") - private Integer pointCardAmount; - //储值卡额度(消耗金额) @Column("stored_value_amount") private java.math.BigDecimal storedValueAmount; @@ -154,14 +150,6 @@ public class GroupCourseEntity extends BaseEntity { this.description = description; } - public Integer getPointCardAmount() { - return pointCardAmount; - } - - public void setPointCardAmount(Integer pointCardAmount) { - this.pointCardAmount = pointCardAmount; - } - public java.math.BigDecimal getStoredValueAmount() { return storedValueAmount; } diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/handler/BookingSagaHandler.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/handler/BookingSagaHandler.java index b62032d..17249b3 100644 --- a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/handler/BookingSagaHandler.java +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/handler/BookingSagaHandler.java @@ -109,8 +109,8 @@ public class BookingSagaHandler { switch (cardType) { case COUNT_CARD: - // 次卡扣除次数 - return deductCardUsage(recordId, 1, 0.0); + // 次数卡不再支持团课预约 + return Mono.error(new RuntimeException("团课预约仅支持储值卡支付")); case STORED_VALUE_CARD: // 储值卡扣除金额 return deductCardUsage(recordId, 0, DEFAULT_GROUP_COURSE_PRICE); @@ -168,7 +168,8 @@ public class BookingSagaHandler { switch (cardType) { case COUNT_CARD: - return restoreCardUsage(recordId, 1, 0.0); + // 次数卡不再支持团课预约,无需恢复 + return Mono.empty(); case STORED_VALUE_CARD: return restoreCardUsage(recordId, 0, DEFAULT_GROUP_COURSE_PRICE); case TIME_CARD: diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/scheduler/GroupCourseExpireScheduler.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/scheduler/GroupCourseExpireScheduler.java new file mode 100644 index 0000000..da4a5f1 --- /dev/null +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/scheduler/GroupCourseExpireScheduler.java @@ -0,0 +1,46 @@ +package cn.novalon.gym.manage.groupcourse.scheduler; + +import cn.novalon.gym.manage.groupcourse.dao.GroupCourseDao; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; + +/** + * 团课过期状态自动更新定时任务 + * + * 功能:定期检查已过期的团课(end_time <= NOW()),自动将 status 从 0 更新为 2(已结束) + * + * @date 2026-06-24 + */ +@Component +public class GroupCourseExpireScheduler { + + private static final Logger logger = LoggerFactory.getLogger(GroupCourseExpireScheduler.class); + + private final GroupCourseDao groupCourseDao; + + public GroupCourseExpireScheduler(GroupCourseDao groupCourseDao) { + this.groupCourseDao = groupCourseDao; + } + + /** + * 每分钟检查一次,将已过期但状态仍为 0 的团课标记为已结束(status = 2) + */ + @Scheduled(fixedRate = 60000) + public void completeExpiredCourses() { + logger.debug("定时任务开始检查已过期团课,更新状态为已结束"); + + groupCourseDao.completeExpiredCourses(LocalDateTime.now()) + .subscribe( + count -> { + if (count > 0) { + logger.info("定时任务完成,更新了 {} 条过期团课状态为已结束", count); + } + }, + error -> logger.error("过期团课状态更新定时任务执行失败:{}", error.getMessage(), error) + ); + } +} diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/impl/GroupCourseService.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/impl/GroupCourseService.java index 91b871d..bf1b7e0 100644 --- a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/impl/GroupCourseService.java +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/impl/GroupCourseService.java @@ -155,7 +155,6 @@ public class GroupCourseService implements IGroupCourseService { detail.setLocation(course.getLocation()); detail.setCoverImage(course.getCoverImage()); detail.setDescription(course.getDescription()); - detail.setPointCardAmount(course.getPointCardAmount()); detail.setStoredValueAmount(course.getStoredValueAmount()); detail.setQrCodePath(course.getQrCodePath()); detail.setCreatedAt(course.getCreatedAt()); @@ -342,9 +341,6 @@ public class GroupCourseService implements IGroupCourseService { if (groupCourse.getDescription() != null) { existing.setDescription(groupCourse.getDescription()); } - if (groupCourse.getPointCardAmount() != null) { - existing.setPointCardAmount(groupCourse.getPointCardAmount()); - } if (groupCourse.getStoredValueAmount() != null) { existing.setStoredValueAmount(groupCourse.getStoredValueAmount()); } @@ -400,12 +396,11 @@ public class GroupCourseService implements IGroupCourseService { return groupCourseRepository.findByIdAndDeletedAtIsNull(courseId) .switchIfEmpty(Mono.error(new RuntimeException("课程不存在"))) .flatMap(course -> { - Integer pointCardAmount = course.getPointCardAmount() != null ? course.getPointCardAmount() : 1; Double storedValueAmount = course.getStoredValueAmount() != null ? course.getStoredValueAmount().doubleValue() : DEFAULT_GROUP_COURSE_PRICE; - logger.info("课程权益信息: courseId={}, pointCardAmount={}, storedValueAmount={}", - courseId, pointCardAmount, storedValueAmount); + logger.info("课程权益信息: courseId={}, storedValueAmount={}", + courseId, storedValueAmount); return bookingRepository.findByCourseId(courseId) .filter(booking -> "0".equals(booking.getStatus())) // 只处理已预约状态的记录 @@ -416,8 +411,8 @@ public class GroupCourseService implements IGroupCourseService { return Mono.empty(); } - // 根据会员卡类型返还权益 - return refundCardUsage(booking.getId(), memberCardRecordId, pointCardAmount, storedValueAmount) + // 根据会员卡类型返还权益(仅储值卡返还金额) + return refundCardUsage(booking.getId(), memberCardRecordId, storedValueAmount) .doOnSuccess(unused -> logger.info("会员权益返还成功: bookingId={}, memberId={}", booking.getId(), booking.getMemberId())) .doOnError(error -> logger.error("会员权益返还失败: bookingId={}, memberId={}, error={}", @@ -433,11 +428,10 @@ public class GroupCourseService implements IGroupCourseService { * * @param bookingId 预约ID * @param recordId 会员卡记录ID - * @param pointCardAmount 次卡返还次数 * @param storedValueAmount 储值卡返还金额 * @return 空Mono */ - private Mono refundCardUsage(Long bookingId, Long recordId, Integer pointCardAmount, Double storedValueAmount) { + private Mono refundCardUsage(Long bookingId, Long recordId, Double storedValueAmount) { return memberCardRecordService.findById(recordId) .switchIfEmpty(Mono.error(new RuntimeException("会员卡记录不存在"))) .flatMap(record -> { @@ -448,10 +442,9 @@ public class GroupCourseService implements IGroupCourseService { Mono refundOperation; switch (cardType) { case COUNT_CARD: - // 次卡返还次数(基于课程设置的pointCardAmount) - logger.debug("返还次卡次数: recordId={}, amount={}", recordId, pointCardAmount); - refundOperation = memberCardRecordService.renewCard(recordId, pointCardAmount, 0.0, record.getExpireTime()) - .then(Mono.empty()); + // 次卡不再支持团课预约,无需返还 + logger.debug("次卡不返还权益: bookingId={}", bookingId); + refundOperation = Mono.empty(); break; case STORED_VALUE_CARD: // 储值卡返还金额(基于课程设置的storedValueAmount) diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V13__Consolidated_Test_Data.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V13__Consolidated_Test_Data.sql index b109d7b..e785261 100644 --- a/gym-manage-api/manage-db/src/main/resources/db/migration/V13__Consolidated_Test_Data.sql +++ b/gym-manage-api/manage-db/src/main/resources/db/migration/V13__Consolidated_Test_Data.sql @@ -151,39 +151,39 @@ INSERT INTO member_card_record (member_id, member_card_id, status, remaining_tim ((SELECT id FROM member_user WHERE member_no = 'MEM_TIME_CONFLICT'), (SELECT member_card_id FROM member_card WHERE member_card_name = '10次卡'), 'ACTIVE', 10, 0.00, '2026-12-31 23:59:59', '2026-06-16 10:00:00', 0, '{}', '2026-06-16 10:00:00', '2026-06-16 10:00:00'); -- ============================================================ --- Section G: 团课课程(来源 V7 / V10 / V11 / V12 / V14) +-- Section G: 团课课程(来源 V7 / V10 / V11 / V12 / V14,基于 2026-06-24) -- ============================================================ -- G1: V7 - 7种场景团课 -INSERT INTO group_course (course_name, coach_id, course_type, start_time, end_time, max_members, current_members, status, location, cover_image, description, create_by, created_at, updated_at) VALUES -('极速燃脂单车', 104, 2, '2026-06-17 16:45:00', '2026-06-17 20:20:00', 25, 0, 0, '单车房', '/images/spinning.jpg', '跟随音乐节奏变换阻力和速度,体验爬坡与冲刺的快感。', 'admin', '2026-06-15 11:00:00', '2026-06-15 11:00:00'), -('清晨流瑜伽', 101, 1, '2026-06-20 09:00:00', '2026-06-20 10:30:00', 15, 5, 0, 'A座3楼瑜伽教室', '/images/yoga_flow.jpg', '适合有一定基础的学员,通过流畅的体式连接呼吸。', 'admin', '2026-06-15 10:00:00', '2026-06-15 10:00:00'), -('燃脂搏击', 102, 2, '2026-06-18 18:30:00', '2026-06-18 19:30:00', 20, 20, 0, '综合训练区', '/images/kickboxing.jpg', '高强度间歇训练,名额已满。', 'coach_zhang', '2026-06-15 14:30:00', '2026-06-15 14:30:00'), -('哈他瑜伽', 101, 1, '2026-06-16 17:45:00', '2026-06-16 18:50:00', 12, 3, 0, '瑜伽教室B', '/images/hatha_yoga.jpg', '基础哈他瑜伽,距开始不足30分钟,已停止预约。', 'coach_li', '2026-06-16 08:00:00', '2026-06-16 08:00:00'), -('周末冥想修复', 101, 1, '2026-06-28 15:00:00', '2026-06-28 16:00:00', 12, 3, 1, '冥想室', '/images/meditation.jpg', '通过呼吸和正念冥想,该课程已被取消。', 'coach_wang', '2026-06-10 08:00:00', '2026-06-10 08:00:00'), -('蜜桃臀塑造', 103, 3, '2026-06-10 19:00:00', '2026-06-10 20:00:00', 10, 8, 2, '私教专区', '/images/glute.jpg', '针对性训练臀部肌肉群,课程已结束。', 'coach_li', '2026-05-30 09:15:00', '2026-05-30 09:15:00'), -('午间冥想放松', 101, 1, '2026-06-12 12:00:00', '2026-06-12 13:00:00', 15, 6, 2, '冥想室', '/images/meditation_noon.jpg', '午间冥想课程,已结束。', 'admin', '2026-06-05 09:00:00', '2026-06-05 09:00:00'); +INSERT INTO group_course (course_name, coach_id, course_type, start_time, end_time, max_members, current_members, status, location, cover_image, description, stored_value_amount, create_by, created_at, updated_at) VALUES +('极速燃脂单车', 104, 2, '2026-06-25 16:45:00', '2026-06-25 20:20:00', 25, 0, 0, '单车房', '/images/spinning.jpg', '跟随音乐节奏变换阻力和速度,体验爬坡与冲刺的快感。', 50.00, 'admin', '2026-06-24 10:00:00', '2026-06-24 10:00:00'), +('清晨流瑜伽', 101, 1, '2026-06-28 09:00:00', '2026-06-28 10:30:00', 15, 5, 0, 'A座3楼瑜伽教室', '/images/yoga_flow.jpg', '适合有一定基础的学员,通过流畅的体式连接呼吸。', 50.00, 'admin', '2026-06-24 10:00:00', '2026-06-24 10:00:00'), +('燃脂搏击', 102, 2, '2026-06-26 18:30:00', '2026-06-26 19:30:00', 20, 20, 0, '综合训练区', '/images/kickboxing.jpg', '高强度间歇训练,名额已满。', 50.00, 'coach_zhang', '2026-06-24 14:30:00', '2026-06-24 14:30:00'), +('哈他瑜伽', 101, 1, '2026-06-24 17:45:00', '2026-06-24 18:50:00', 12, 3, 0, '瑜伽教室B', '/images/hatha_yoga.jpg', '基础哈他瑜伽,距开始不足30分钟,已停止预约。', 50.00, 'coach_li', '2026-06-24 08:00:00', '2026-06-24 08:00:00'), +('周末冥想修复', 101, 1, '2026-07-06 15:00:00', '2026-07-06 16:00:00', 12, 3, 1, '冥想室', '/images/meditation.jpg', '通过呼吸和正念冥想,该课程已被取消。', 50.00, 'coach_wang', '2026-06-24 08:00:00', '2026-06-24 08:00:00'), +('蜜桃臀塑造', 103, 3, '2026-06-18 19:00:00', '2026-06-18 20:00:00', 10, 8, 2, '私教专区', '/images/glute.jpg', '针对性训练臀部肌肉群,课程已结束。', 50.00, 'coach_li', '2026-06-18 09:15:00', '2026-06-18 09:15:00'), +('午间冥想放松', 101, 1, '2026-06-20 12:00:00', '2026-06-20 13:00:00', 15, 6, 2, '冥想室', '/images/meditation_noon.jpg', '午间冥想课程,已结束。', 50.00, 'admin', '2026-06-20 09:00:00', '2026-06-20 09:00:00'); --- G2: V10 预约场景 -INSERT INTO group_course (course_name, coach_id, course_type, start_time, end_time, max_members, current_members, status, location, description, point_card_amount, stored_value_amount, create_by, created_at, updated_at) VALUES -('燃脂搏击_次数卡课程', 102, 2, '2026-06-18 19:30:00', '2026-06-18 20:30:00', 20, 0, 0, '综合训练区', '消耗1次', 1, 0.00, 'admin', '2026-06-15 10:00:00', '2026-06-15 10:00:00'), -('高端普拉提_储值卡课程', 103, 1, '2026-06-19 19:00:00', '2026-06-19 20:00:00', 15, 0, 0, '普拉提教室', '消耗储值20元', 0, 20.00, 'admin', '2026-06-15 10:00:00', '2026-06-15 10:00:00'); +-- G2: V10 预约场景(已取消次数卡支付,仅保留储值卡) +INSERT INTO group_course (course_name, coach_id, course_type, start_time, end_time, max_members, current_members, status, location, description, stored_value_amount, create_by, created_at, updated_at) VALUES +('燃脂搏击_储值测试', 102, 2, '2026-06-26 19:30:00', '2026-06-26 20:30:00', 20, 0, 0, '综合训练区', '消耗储值50元', 50.00, 'admin', '2026-06-24 10:00:00', '2026-06-24 10:00:00'), +('高端普拉提_储值卡课程', 103, 1, '2026-06-27 19:00:00', '2026-06-27 20:00:00', 15, 0, 0, '普拉提教室', '消耗储值20元', 20.00, 'admin', '2026-06-24 10:00:00', '2026-06-24 10:00:00'); -- G3: V11 取消预约场景 -INSERT INTO group_course (course_name, coach_id, course_type, start_time, end_time, max_members, current_members, status, location, description, point_card_amount, stored_value_amount, create_by, created_at, updated_at) VALUES -('晚间瑜伽_取消测试', 101, 1, '2026-06-19 19:00:00', '2026-06-19 20:00:00', 20, 0, 0, '瑜伽教室', '用于测试取消预约功能', 1, 30.00, 'admin', '2026-06-15 10:00:00', '2026-06-15 10:00:00'); +INSERT INTO group_course (course_name, coach_id, course_type, start_time, end_time, max_members, current_members, status, location, description, stored_value_amount, create_by, created_at, updated_at) VALUES +('晚间瑜伽_取消测试', 101, 1, '2026-06-27 19:00:00', '2026-06-27 20:00:00', 20, 0, 0, '瑜伽教室', '用于测试取消预约功能', 30.00, 'admin', '2026-06-24 10:00:00', '2026-06-24 10:00:00'); -- G4: V12 时间冲突场景 -INSERT INTO group_course (course_name, coach_id, course_type, start_time, end_time, max_members, current_members, status, location, description, point_card_amount, stored_value_amount, create_by, created_at, updated_at) VALUES -('时间冲突测试_A_13点-15点', 102, 2, '2026-06-18 13:00:00', '2026-06-18 15:00:00', 20, 0, 0, '综合训练区', '测试用团课A', 1, 0.00, 'admin', '2026-06-15 10:00:00', '2026-06-15 10:00:00'), -('时间冲突测试_B_14点-16点', 103, 1, '2026-06-18 14:00:00', '2026-06-18 16:00:00', 15, 0, 0, '普拉提教室', '与团课A时间重叠', 1, 0.00, 'admin', '2026-06-15 10:00:00', '2026-06-15 10:00:00'), -('时间冲突测试_C_10点-12点', 101, 1, '2026-06-18 10:00:00', '2026-06-18 12:00:00', 15, 0, 0, '瑜伽教室', '与A/B不冲突', 1, 0.00, 'admin', '2026-06-15 10:00:00', '2026-06-15 10:00:00'); +INSERT INTO group_course (course_name, coach_id, course_type, start_time, end_time, max_members, current_members, status, location, description, stored_value_amount, create_by, created_at, updated_at) VALUES +('时间冲突测试_A_13点-15点', 102, 2, '2026-06-26 13:00:00', '2026-06-26 15:00:00', 20, 0, 0, '综合训练区', '测试用团课A', 50.00, 'admin', '2026-06-24 10:00:00', '2026-06-24 10:00:00'), +('时间冲突测试_B_14点-16点', 103, 1, '2026-06-26 14:00:00', '2026-06-26 16:00:00', 15, 0, 0, '普拉提教室', '与团课A时间重叠', 50.00, 'admin', '2026-06-24 10:00:00', '2026-06-24 10:00:00'), +('时间冲突测试_C_10点-12点', 101, 1, '2026-06-26 10:00:00', '2026-06-26 12:00:00', 15, 0, 0, '瑜伽教室', '与A/B不冲突', 50.00, 'admin', '2026-06-24 10:00:00', '2026-06-24 10:00:00'); --- G5: V14 数据统计场景(今天) -INSERT INTO group_course (id, course_name, coach_id, course_type, start_time, end_time, max_members, current_members, status, location, cover_image, description, created_at, updated_at) VALUES -(3001, '瑜伽入门', 1, 1, '2026-06-16 08:00:00', '2026-06-16 09:00:00', 20, 15, 0, '健身房A区', 'https://example.com/yoga.jpg', '适合初学者的瑜伽课程', '2026-06-15 10:00:00', '2026-06-15 10:00:00'), -(3002, '动感单车', 2, 2, '2026-06-16 09:30:00', '2026-06-16 10:30:00', 25, 20, 0, '健身房B区', 'https://example.com/spinning.jpg', '高强度有氧运动', '2026-06-15 11:00:00', '2026-06-15 11:00:00'), -(3003, '普拉提', 3, 1, '2026-06-16 14:00:00', '2026-06-16 15:00:00', 15, 10, 0, '健身房C区', 'https://example.com/pilates.jpg', '核心力量训练', '2026-06-15 12:00:00', '2026-06-15 12:00:00'); +-- G5: V14 数据统计场景(今天 2026-06-24) +INSERT INTO group_course (id, course_name, coach_id, course_type, start_time, end_time, max_members, current_members, status, location, cover_image, description, stored_value_amount, created_at, updated_at) VALUES +(3001, '瑜伽入门', 1, 1, '2026-06-24 08:00:00', '2026-06-24 09:00:00', 20, 15, 0, '健身房A区', 'https://example.com/yoga.jpg', '适合初学者的瑜伽课程', 50.00, '2026-06-24 10:00:00', '2026-06-24 10:00:00'), +(3002, '动感单车', 2, 2, '2026-06-24 09:30:00', '2026-06-24 10:30:00', 25, 20, 0, '健身房B区', 'https://example.com/spinning.jpg', '高强度有氧运动', 50.00, '2026-06-24 11:00:00', '2026-06-24 11:00:00'), +(3003, '普拉提', 3, 1, '2026-06-24 14:00:00', '2026-06-24 15:00:00', 15, 10, 0, '健身房C区', 'https://example.com/pilates.jpg', '核心力量训练', 50.00, '2026-06-24 12:00:00', '2026-06-24 12:00:00'); -- ============================================================ -- Section H: 团课预约记录 @@ -191,116 +191,116 @@ INSERT INTO group_course (id, course_name, coach_id, course_type, start_time, en -- H1: V11 取消预约测试 INSERT INTO group_course_booking (course_id, member_id, member_card_id, booking_time, status, course_name, course_start_time, course_end_time, location, created_at, updated_at) VALUES -((SELECT id FROM group_course WHERE course_name = '晚间瑜伽_取消测试'), (SELECT id FROM member_user WHERE member_no = 'MEM_TEST_ZHANG'), (SELECT id FROM member_card_record WHERE member_id = (SELECT id FROM member_user WHERE member_no = 'MEM_TEST_ZHANG')), '2026-06-16 11:00:00', '0', '晚间瑜伽_取消测试', '2026-06-19 19:00:00', '2026-06-19 20:00:00', '瑜伽教室', '2026-06-16 11:00:00', '2026-06-16 11:00:00'), -((SELECT id FROM group_course WHERE course_name = '晚间瑜伽_取消测试'), (SELECT id FROM member_user WHERE member_no = 'MEM_TEST_LI'), (SELECT id FROM member_card_record WHERE member_id = (SELECT id FROM member_user WHERE member_no = 'MEM_TEST_LI')), '2026-06-16 11:30:00', '0', '晚间瑜伽_取消测试', '2026-06-19 19:00:00', '2026-06-19 20:00:00', '瑜伽教室', '2026-06-16 11:30:00', '2026-06-16 11:30:00'), -((SELECT id FROM group_course WHERE course_name = '晚间瑜伽_取消测试'), (SELECT id FROM member_user WHERE member_no = 'MEM_TEST_WANG'), (SELECT id FROM member_card_record WHERE member_id = (SELECT id FROM member_user WHERE member_no = 'MEM_TEST_WANG')), '2026-06-16 12:00:00', '0', '晚间瑜伽_取消测试', '2026-06-19 19:00:00', '2026-06-19 20:00:00', '瑜伽教室', '2026-06-16 12:00:00', '2026-06-16 12:00:00'); +((SELECT id FROM group_course WHERE course_name = '晚间瑜伽_取消测试'), (SELECT id FROM member_user WHERE member_no = 'MEM_TEST_ZHANG'), (SELECT id FROM member_card_record WHERE member_id = (SELECT id FROM member_user WHERE member_no = 'MEM_TEST_ZHANG')), '2026-06-24 11:00:00', '0', '晚间瑜伽_取消测试', '2026-06-27 19:00:00', '2026-06-27 20:00:00', '瑜伽教室', '2026-06-24 11:00:00', '2026-06-24 11:00:00'), +((SELECT id FROM group_course WHERE course_name = '晚间瑜伽_取消测试'), (SELECT id FROM member_user WHERE member_no = 'MEM_TEST_LI'), (SELECT id FROM member_card_record WHERE member_id = (SELECT id FROM member_user WHERE member_no = 'MEM_TEST_LI')), '2026-06-24 11:30:00', '0', '晚间瑜伽_取消测试', '2026-06-27 19:00:00', '2026-06-27 20:00:00', '瑜伽教室', '2026-06-24 11:30:00', '2026-06-24 11:30:00'), +((SELECT id FROM group_course WHERE course_name = '晚间瑜伽_取消测试'), (SELECT id FROM member_user WHERE member_no = 'MEM_TEST_WANG'), (SELECT id FROM member_card_record WHERE member_id = (SELECT id FROM member_user WHERE member_no = 'MEM_TEST_WANG')), '2026-06-24 12:00:00', '0', '晚间瑜伽_取消测试', '2026-06-27 19:00:00', '2026-06-27 20:00:00', '瑜伽教室', '2026-06-24 12:00:00', '2026-06-24 12:00:00'); UPDATE group_course SET current_members = current_members + 3 WHERE course_name = '晚间瑜伽_取消测试'; --- H2: V14 数据统计预约 - 今天(6月16日) +-- H2: V14 数据统计预约 - 今天(6月24日) INSERT INTO group_course_booking (id, member_id, member_card_id, course_id, booking_time, status, created_at, updated_at) VALUES -(4001, 1001, 1, 3001, '2026-06-16 08:00:00', '2', '2026-06-15 20:00:00', '2026-06-16 08:30:00'), -(4002, 1002, 2, 3001, '2026-06-16 08:00:00', '2', '2026-06-15 21:00:00', '2026-06-16 08:30:00'), -(4003, 1003, 3, 3001, '2026-06-16 08:00:00', '3', '2026-06-15 22:00:00', '2026-06-16 09:00:00'), -(4004, 1004, 4, 3002, '2026-06-16 09:30:00', '2', '2026-06-15 19:00:00', '2026-06-16 09:30:00'), -(4005, 1005, 5, 3002, '2026-06-16 09:30:00', '1', '2026-06-15 20:30:00', '2026-06-16 09:00:00'), -(4006, 1006, 6, 3002, '2026-06-16 09:30:00', '2', '2026-06-15 21:30:00', '2026-06-16 09:30:00'), -(4007, 1007, 7, 3003, '2026-06-16 14:00:00', '2', '2026-06-15 22:30:00', '2026-06-16 14:00:00'), -(4008, 1008, 8, 3003, '2026-06-16 14:00:00', '3', '2026-06-16 08:00:00', '2026-06-16 14:00:00'), -(4009, 1009, 9, 3003, '2026-06-16 14:00:00', '2', '2026-06-16 09:00:00', '2026-06-16 14:00:00'), -(4010, 1010, 10, 3001, '2026-06-16 08:00:00', '2', '2026-06-16 07:00:00', '2026-06-16 08:00:00'), -(4011, 1011, 11, 3002, '2026-06-16 09:30:00', '1', '2026-06-16 08:30:00', '2026-06-16 09:00:00'), -(4012, 1012, 12, 3003, '2026-06-16 14:00:00', '2', '2026-06-16 10:00:00', '2026-06-16 14:00:00'); +(4001, 1001, 1, 3001, '2026-06-24 08:00:00', '2', '2026-06-23 20:00:00', '2026-06-24 08:30:00'), +(4002, 1002, 2, 3001, '2026-06-24 08:00:00', '2', '2026-06-23 21:00:00', '2026-06-24 08:30:00'), +(4003, 1003, 3, 3001, '2026-06-24 08:00:00', '3', '2026-06-23 22:00:00', '2026-06-24 09:00:00'), +(4004, 1004, 4, 3002, '2026-06-24 09:30:00', '2', '2026-06-23 19:00:00', '2026-06-24 09:30:00'), +(4005, 1005, 5, 3002, '2026-06-24 09:30:00', '1', '2026-06-23 20:30:00', '2026-06-24 09:00:00'), +(4006, 1006, 6, 3002, '2026-06-24 09:30:00', '2', '2026-06-23 21:30:00', '2026-06-24 09:30:00'), +(4007, 1007, 7, 3003, '2026-06-24 14:00:00', '2', '2026-06-23 22:30:00', '2026-06-24 14:00:00'), +(4008, 1008, 8, 3003, '2026-06-24 14:00:00', '3', '2026-06-24 08:00:00', '2026-06-24 14:00:00'), +(4009, 1009, 9, 3003, '2026-06-24 14:00:00', '2', '2026-06-24 09:00:00', '2026-06-24 14:00:00'), +(4010, 1010, 10, 3001, '2026-06-24 08:00:00', '2', '2026-06-24 07:00:00', '2026-06-24 08:00:00'), +(4011, 1011, 11, 3002, '2026-06-24 09:30:00', '1', '2026-06-24 08:30:00', '2026-06-24 09:00:00'), +(4012, 1012, 12, 3003, '2026-06-24 14:00:00', '2', '2026-06-24 10:00:00', '2026-06-24 14:00:00'); --- H3: V14 数据统计预约 - 昨天(6月15日) +-- H3: V14 数据统计预约 - 昨天(6月23日) INSERT INTO group_course_booking (id, member_id, member_card_id, course_id, booking_time, status, created_at, updated_at) VALUES -(4013, 1001, 1, 3001, '2026-06-15 08:00:00', '2', '2026-06-14 20:00:00', '2026-06-15 08:30:00'), -(4014, 1002, 2, 3001, '2026-06-15 08:00:00', '2', '2026-06-14 21:00:00', '2026-06-15 08:30:00'), -(4015, 1003, 3, 3002, '2026-06-15 09:30:00', '3', '2026-06-14 22:00:00', '2026-06-15 09:30:00'), -(4016, 1004, 4, 3002, '2026-06-15 09:30:00', '2', '2026-06-14 19:00:00', '2026-06-15 09:30:00'), -(4017, 1005, 5, 3003, '2026-06-15 14:00:00', '1', '2026-06-14 20:30:00', '2026-06-15 13:00:00'), -(4018, 1006, 6, 3003, '2026-06-15 14:00:00', '2', '2026-06-14 21:30:00', '2026-06-15 14:00:00'); +(4013, 1001, 1, 3001, '2026-06-23 08:00:00', '2', '2026-06-22 20:00:00', '2026-06-23 08:30:00'), +(4014, 1002, 2, 3001, '2026-06-23 08:00:00', '2', '2026-06-22 21:00:00', '2026-06-23 08:30:00'), +(4015, 1003, 3, 3002, '2026-06-23 09:30:00', '3', '2026-06-22 22:00:00', '2026-06-23 09:30:00'), +(4016, 1004, 4, 3002, '2026-06-23 09:30:00', '2', '2026-06-22 19:00:00', '2026-06-23 09:30:00'), +(4017, 1005, 5, 3003, '2026-06-23 14:00:00', '1', '2026-06-22 20:30:00', '2026-06-23 13:00:00'), +(4018, 1006, 6, 3003, '2026-06-23 14:00:00', '2', '2026-06-22 21:30:00', '2026-06-23 14:00:00'); --- H4: V14 数据统计预约 - 前天(6月14日) +-- H4: V14 数据统计预约 - 前天(6月22日) INSERT INTO group_course_booking (id, member_id, member_card_id, course_id, booking_time, status, created_at, updated_at) VALUES -(4019, 1001, 1, 3002, '2026-06-14 09:30:00', '2', '2026-06-13 20:00:00', '2026-06-14 09:30:00'), -(4020, 1002, 2, 3002, '2026-06-14 09:30:00', '2', '2026-06-13 21:00:00', '2026-06-14 09:30:00'), -(4021, 1003, 3, 3003, '2026-06-14 14:00:00', '2', '2026-06-13 22:00:00', '2026-06-14 14:00:00'), -(4022, 1004, 4, 3003, '2026-06-14 14:00:00', '3', '2026-06-13 19:00:00', '2026-06-14 14:00:00'); +(4019, 1001, 1, 3002, '2026-06-22 09:30:00', '2', '2026-06-21 20:00:00', '2026-06-22 09:30:00'), +(4020, 1002, 2, 3002, '2026-06-22 09:30:00', '2', '2026-06-21 21:00:00', '2026-06-22 09:30:00'), +(4021, 1003, 3, 3003, '2026-06-22 14:00:00', '2', '2026-06-21 22:00:00', '2026-06-22 14:00:00'), +(4022, 1004, 4, 3003, '2026-06-22 14:00:00', '3', '2026-06-21 19:00:00', '2026-06-22 14:00:00'); -- ============================================================ --- Section I: 签到记录(来源 V14,基于 2026-06-16) +-- Section I: 签到记录(来源 V14,基于 2026-06-24) -- ============================================================ INSERT INTO sign_in_record (id, member_id, sign_in_time, sign_in_type, sign_in_status, source, is_delete) VALUES -(2001, 1001, '2026-06-16 08:00:00', 'QR_CODE', 'SUCCESS', 'MINI_PROGRAM', false), -(2002, 1002, '2026-06-16 08:15:00', 'MANUAL', 'SUCCESS', 'PC_BACKEND', false), -(2003, 1003, '2026-06-16 08:30:00', 'FACE', 'SUCCESS', 'MINI_PROGRAM', false), -(2004, 1004, '2026-06-16 09:00:00', 'QR_CODE', 'SUCCESS', 'MINI_PROGRAM', false), -(2005, 1005, '2026-06-16 09:15:00', 'MANUAL', 'SUCCESS', 'PC_BACKEND', false), -(2006, 1006, '2026-06-16 09:30:00', 'QR_CODE', 'SUCCESS', 'MINI_PROGRAM', false), -(2007, 1007, '2026-06-16 10:00:00', 'FACE', 'SUCCESS', 'MINI_PROGRAM', false), -(2008, 1008, '2026-06-16 10:15:00', 'QR_CODE', 'SUCCESS', 'MINI_PROGRAM', false), -(2009, 1009, '2026-06-16 10:30:00', 'MANUAL', 'SUCCESS', 'PC_BACKEND', false), -(2010, 1010, '2026-06-16 11:00:00', 'QR_CODE', 'SUCCESS', 'MINI_PROGRAM', false), -(2011, 1011, '2026-06-16 11:15:00', 'FACE', 'FAIL', 'MINI_PROGRAM', false), -(2012, 1012, '2026-06-16 11:30:00', 'MANUAL', 'SUCCESS', 'PC_BACKEND', false); +(2001, 1001, '2026-06-24 08:00:00', 'QR_CODE', 'SUCCESS', 'MINI_PROGRAM', false), +(2002, 1002, '2026-06-24 08:15:00', 'MANUAL', 'SUCCESS', 'PC_BACKEND', false), +(2003, 1003, '2026-06-24 08:30:00', 'FACE', 'SUCCESS', 'MINI_PROGRAM', false), +(2004, 1004, '2026-06-24 09:00:00', 'QR_CODE', 'SUCCESS', 'MINI_PROGRAM', false), +(2005, 1005, '2026-06-24 09:15:00', 'MANUAL', 'SUCCESS', 'PC_BACKEND', false), +(2006, 1006, '2026-06-24 09:30:00', 'QR_CODE', 'SUCCESS', 'MINI_PROGRAM', false), +(2007, 1007, '2026-06-24 10:00:00', 'FACE', 'SUCCESS', 'MINI_PROGRAM', false), +(2008, 1008, '2026-06-24 10:15:00', 'QR_CODE', 'SUCCESS', 'MINI_PROGRAM', false), +(2009, 1009, '2026-06-24 10:30:00', 'MANUAL', 'SUCCESS', 'PC_BACKEND', false), +(2010, 1010, '2026-06-24 11:00:00', 'QR_CODE', 'SUCCESS', 'MINI_PROGRAM', false), +(2011, 1011, '2026-06-24 11:15:00', 'FACE', 'FAIL', 'MINI_PROGRAM', false), +(2012, 1012, '2026-06-24 11:30:00', 'MANUAL', 'SUCCESS', 'PC_BACKEND', false); INSERT INTO sign_in_record (id, member_id, sign_in_time, sign_in_type, sign_in_status, source, is_delete) VALUES -(2013, 1001, '2026-06-15 07:30:00', 'QR_CODE', 'SUCCESS', 'MINI_PROGRAM', false), -(2014, 1002, '2026-06-15 08:00:00', 'MANUAL', 'SUCCESS', 'PC_BACKEND', false), -(2015, 1003, '2026-06-15 08:30:00', 'FACE', 'SUCCESS', 'MINI_PROGRAM', false), -(2016, 1004, '2026-06-15 09:00:00', 'QR_CODE', 'SUCCESS', 'MINI_PROGRAM', false), -(2017, 1005, '2026-06-15 09:30:00', 'MANUAL', 'SUCCESS', 'PC_BACKEND', false), -(2018, 1006, '2026-06-15 10:00:00', 'QR_CODE', 'SUCCESS', 'MINI_PROGRAM', false), -(2019, 1007, '2026-06-15 10:30:00', 'FACE', 'SUCCESS', 'MINI_PROGRAM', false), -(2020, 1008, '2026-06-15 11:00:00', 'QR_CODE', 'SUCCESS', 'MINI_PROGRAM', false); +(2013, 1001, '2026-06-23 07:30:00', 'QR_CODE', 'SUCCESS', 'MINI_PROGRAM', false), +(2014, 1002, '2026-06-23 08:00:00', 'MANUAL', 'SUCCESS', 'PC_BACKEND', false), +(2015, 1003, '2026-06-23 08:30:00', 'FACE', 'SUCCESS', 'MINI_PROGRAM', false), +(2016, 1004, '2026-06-23 09:00:00', 'QR_CODE', 'SUCCESS', 'MINI_PROGRAM', false), +(2017, 1005, '2026-06-23 09:30:00', 'MANUAL', 'SUCCESS', 'PC_BACKEND', false), +(2018, 1006, '2026-06-23 10:00:00', 'QR_CODE', 'SUCCESS', 'MINI_PROGRAM', false), +(2019, 1007, '2026-06-23 10:30:00', 'FACE', 'SUCCESS', 'MINI_PROGRAM', false), +(2020, 1008, '2026-06-23 11:00:00', 'QR_CODE', 'SUCCESS', 'MINI_PROGRAM', false); INSERT INTO sign_in_record (id, member_id, sign_in_time, sign_in_type, sign_in_status, source, is_delete) VALUES -(2021, 1001, '2026-06-14 07:00:00', 'QR_CODE', 'SUCCESS', 'MINI_PROGRAM', false), -(2022, 1002, '2026-06-14 07:30:00', 'MANUAL', 'SUCCESS', 'PC_BACKEND', false), -(2023, 1003, '2026-06-14 08:00:00', 'FACE', 'FAIL', 'MINI_PROGRAM', false), -(2024, 1004, '2026-06-14 08:30:00', 'QR_CODE', 'SUCCESS', 'MINI_PROGRAM', false), -(2025, 1005, '2026-06-14 09:00:00', 'MANUAL', 'SUCCESS', 'PC_BACKEND', false); +(2021, 1001, '2026-06-22 07:00:00', 'QR_CODE', 'SUCCESS', 'MINI_PROGRAM', false), +(2022, 1002, '2026-06-22 07:30:00', 'MANUAL', 'SUCCESS', 'PC_BACKEND', false), +(2023, 1003, '2026-06-22 08:00:00', 'FACE', 'FAIL', 'MINI_PROGRAM', false), +(2024, 1004, '2026-06-22 08:30:00', 'QR_CODE', 'SUCCESS', 'MINI_PROGRAM', false), +(2025, 1005, '2026-06-22 09:00:00', 'MANUAL', 'SUCCESS', 'PC_BACKEND', false); -- ============================================================ --- Section J: 团课推荐测试数据(来源 V18,基于 2026-06-16) +-- Section J: 团课推荐测试数据(来源 V18,基于 2026-06-24) -- ============================================================ INSERT INTO group_course_recommend (course_id, recommend_title, recommend_content, recommend_reason, priority, is_active, create_by, created_at, updated_at) VALUES -((SELECT id FROM group_course WHERE course_name = '极速燃脂单车'), '本周热门推荐', '极速燃脂单车课程,跟随音乐节奏变换阻力和速度,体验爬坡与冲刺的快感,一节课消耗800大卡!', '教练专业,课程内容丰富,深受学员喜爱,燃脂效果显著', 20, true, 'admin', '2026-06-16 10:00:00', '2026-06-16 10:00:00'); +((SELECT id FROM group_course WHERE course_name = '极速燃脂单车'), '本周热门推荐', '极速燃脂单车课程,跟随音乐节奏变换阻力和速度,体验爬坡与冲刺的快感,一节课消耗800大卡!', '教练专业,课程内容丰富,深受学员喜爱,燃脂效果显著', 20, true, 'admin', '2026-06-24 10:00:00', '2026-06-24 10:00:00'); INSERT INTO group_course_recommend (course_id, recommend_title, recommend_content, recommend_reason, priority, is_active, create_by, created_at, updated_at) VALUES -((SELECT id FROM group_course WHERE course_name = '清晨流瑜伽'), '新手友好推荐', '清晨流瑜伽课程,适合有一定基础的学员,通过流畅的体式连接呼吸,唤醒身体能量。', '适合新手入门,教练耐心指导,课程节奏适中', 15, true, 'admin', '2026-06-16 11:00:00', '2026-06-16 11:00:00'); +((SELECT id FROM group_course WHERE course_name = '清晨流瑜伽'), '新手友好推荐', '清晨流瑜伽课程,适合有一定基础的学员,通过流畅的体式连接呼吸,唤醒身体能量。', '适合新手入门,教练耐心指导,课程节奏适中', 15, true, 'admin', '2026-06-24 11:00:00', '2026-06-24 11:00:00'); INSERT INTO group_course_recommend (course_id, recommend_title, recommend_content, recommend_reason, priority, is_active, create_by, created_at, updated_at) VALUES -((SELECT id FROM group_course WHERE course_name = '燃脂搏击'), '高强度燃脂', '燃脂搏击课程,高强度间歇训练,配合音乐快速燃脂,释放压力。', '高强度训练,适合进阶学员,快速燃脂塑形', 10, false, 'admin', '2026-06-16 12:00:00', '2026-06-16 12:00:00'); +((SELECT id FROM group_course WHERE course_name = '燃脂搏击'), '高强度燃脂', '燃脂搏击课程,高强度间歇训练,配合音乐快速燃脂,释放压力。', '高强度训练,适合进阶学员,快速燃脂塑形', 10, false, 'admin', '2026-06-24 12:00:00', '2026-06-24 12:00:00'); INSERT INTO group_course_recommend (course_id, recommend_title, recommend_content, recommend_reason, priority, is_active, create_by, created_at, updated_at) VALUES -((SELECT id FROM group_course WHERE course_name = '哈他瑜伽'), '基础瑜伽推荐', '基础哈他瑜伽课程,适合所有级别学员,通过基础体式练习提升身体柔韧性和平衡能力。', '零基础友好,适合所有健身水平,放松身心', 12, true, 'coach_li', '2026-06-16 13:00:00', '2026-06-16 13:00:00'); +((SELECT id FROM group_course WHERE course_name = '哈他瑜伽'), '基础瑜伽推荐', '基础哈他瑜伽课程,适合所有级别学员,通过基础体式练习提升身体柔韧性和平衡能力。', '零基础友好,适合所有健身水平,放松身心', 12, true, 'coach_li', '2026-06-24 13:00:00', '2026-06-24 13:00:00'); INSERT INTO group_course_recommend (course_id, recommend_title, recommend_content, recommend_reason, priority, is_active, create_by, created_at, updated_at) VALUES -((SELECT id FROM group_course WHERE course_name = '蜜桃臀塑造'), '塑形热门课程', '蜜桃臀塑造课程,针对性训练臀部肌肉群,打造完美曲线。', '专业私教指导,动作标准,效果显著,深受女性学员喜爱', 18, true, 'coach_li', '2026-06-10 09:15:00', '2026-06-10 09:15:00'); +((SELECT id FROM group_course WHERE course_name = '蜜桃臀塑造'), '塑形热门课程', '蜜桃臀塑造课程,针对性训练臀部肌肉群,打造完美曲线。', '专业私教指导,动作标准,效果显著,深受女性学员喜爱', 18, true, 'coach_li', '2026-06-24 09:15:00', '2026-06-24 09:15:00'); INSERT INTO group_course_recommend (course_id, recommend_title, recommend_content, recommend_reason, priority, is_active, create_by, created_at, updated_at) VALUES -((SELECT id FROM group_course WHERE course_name = '午间冥想放松'), '午间放松推荐', '午间冥想放松课程,通过呼吸和正念冥想,深度放松身心,缓解工作压力。', '适合上班族,午间放松充电,提升下午工作效率', 8, true, 'admin', '2026-06-12 09:00:00', '2026-06-12 09:00:00'); +((SELECT id FROM group_course WHERE course_name = '午间冥想放松'), '午间放松推荐', '午间冥想放松课程,通过呼吸和正念冥想,深度放松身心,缓解工作压力。', '适合上班族,午间放松充电,提升下午工作效率', 8, true, 'admin', '2026-06-24 09:00:00', '2026-06-24 09:00:00'); INSERT INTO group_course_recommend (course_id, recommend_title, recommend_content, recommend_reason, priority, is_active, create_by, created_at, updated_at) VALUES -((SELECT id FROM group_course WHERE course_name = '极速燃脂单车'), '减脂首选课程', '想要快速减脂?极速燃脂单车是你的最佳选择!专业教练带领,科学训练计划。', '减脂效果最佳,课程强度适中,适合想要快速瘦身的学员', 16, true, 'coach_zhang', '2026-06-16 14:00:00', '2026-06-16 14:00:00'); +((SELECT id FROM group_course WHERE course_name = '极速燃脂单车'), '减脂首选课程', '想要快速减脂?极速燃脂单车是你的最佳选择!专业教练带领,科学训练计划。', '减脂效果最佳,课程强度适中,适合想要快速瘦身的学员', 16, true, 'coach_zhang', '2026-06-24 14:00:00', '2026-06-24 14:00:00'); INSERT INTO group_course_recommend (course_id, recommend_title, recommend_content, recommend_reason, priority, is_active, create_by, created_at, updated_at) VALUES -((SELECT id FROM group_course WHERE course_name = '清晨流瑜伽'), '晨练优选', '清晨流瑜伽,唤醒身体能量,开启活力一天!适合晨练爱好者。', '晨练最佳选择,提升身体活力,改善精神状态', 14, true, 'coach_wang', '2026-06-16 15:00:00', '2026-06-16 15:00:00'); +((SELECT id FROM group_course WHERE course_name = '清晨流瑜伽'), '晨练优选', '清晨流瑜伽,唤醒身体能量,开启活力一天!适合晨练爱好者。', '晨练最佳选择,提升身体活力,改善精神状态', 14, true, 'coach_wang', '2026-06-24 15:00:00', '2026-06-24 15:00:00'); INSERT INTO group_course_recommend (course_id, recommend_title, recommend_content, recommend_reason, priority, is_active, create_by, created_at, updated_at) VALUES -((SELECT id FROM group_course WHERE course_name = '哈他瑜伽'), '身心平衡推荐', '哈他瑜伽课程,通过体式练习和呼吸控制,达到身心平衡,提升整体健康水平。', '改善身体柔韧性,增强核心力量,提升身体协调性', 11, true, 'coach_li', '2026-06-16 16:00:00', '2026-06-16 16:00:00'); +((SELECT id FROM group_course WHERE course_name = '哈他瑜伽'), '身心平衡推荐', '哈他瑜伽课程,通过体式练习和呼吸控制,达到身心平衡,提升整体健康水平。', '改善身体柔韧性,增强核心力量,提升身体协调性', 11, true, 'coach_li', '2026-06-24 16:00:00', '2026-06-24 16:00:00'); INSERT INTO group_course_recommend (course_id, recommend_title, recommend_content, recommend_reason, priority, is_active, create_by, created_at, updated_at) VALUES -((SELECT id FROM group_course WHERE course_name = '午间冥想放松'), '职场减压课程', '午间冥想放松,专为职场人士设计,快速缓解工作压力,提升工作状态。', '职场减压首选,课程时间短,效果显著', 9, false, 'admin', '2026-06-12 10:00:00', '2026-06-12 10:00:00'); +((SELECT id FROM group_course WHERE course_name = '午间冥想放松'), '职场减压课程', '午间冥想放松,专为职场人士设计,快速缓解工作压力,提升工作状态。', '职场减压首选,课程时间短,效果显著', 9, false, 'admin', '2026-06-24 10:00:00', '2026-06-24 10:00:00'); -- ============================================================ -- Section K: 统计说明 -- ============================================================ --- 今日(2026-06-16)统计预期: +-- 今日(2026-06-24)统计预期: -- 新增会员: 3 (1005, 1006, 1007) -- 签到会员: 12 -- 预约总数: 12 (4001-4012),取消: 2,出席: 8,缺席: 2 @@ -310,33 +310,33 @@ INSERT INTO group_course_recommend (course_id, recommend_title, recommend_conten -- Section L: 测试场景汇总 -- ============================================================ -- 1. V7 团课预约场景: --- - 极速燃脂单车: 明天,0人,可预约 --- - 清晨流瑜伽: 6月20日,5/15人,可预约 --- - 燃脂搏击: 6月18日,满员,不可预约 --- - 哈他瑜伽: 今天17:45,距开始<30min,不可预约 --- - 周末冥想修复: 6月28日,已取消(status=1) --- - 蜜桃臀塑造: 6月10日,已结束(status=2) --- - 午间冥想放松: 6月12日,已结束(status=2) +-- - 极速燃脂单车: 明天(6/25),0人,可预约 +-- - 清晨流瑜伽: 6月28日,5/15人,可预约 +-- - 燃脂搏击: 6月26日,满员,不可预约 +-- - 哈他瑜伽: 6/24 17:45,距开始<30min,不可预约 +-- - 周末冥想修复: 7月6日,已取消(status=1) +-- - 蜜桃臀塑造: 6月18日,已结束(status=2) +-- - 午间冥想放松: 6月20日,已结束(status=2) -- -- 2. V10 会员卡预约资格校验: -- - 用户A: 无会员卡 → "无可用会员卡" -- - 用户B: 时长卡过期 → "会员卡已过期" -- - 会员C: 时长卡有效 → 可正常预约 -- - 会员D: 次数为0 → "剩余次数不足" --- - 会员E: 次数充足 → 可正常预约 --- - 会员F: 储值卡30元,课程需20元 → 可正常预约 --- - 会员G: 储值卡200元,课程需20元 → 可正常预约 +-- - 会员E: 次数充足 → 不可用次数卡预约团课(仅支持储值卡) +-- - 会员F: 储值卡30元,课程需50元 → 余额不足 +-- - 会员G: 储值卡200元,课程需50元 → 可正常预约 -- -- 3. V11 取消预约: --- - 张三(时长卡)、李四(次数卡)、王五(储值卡) 预约了晚间瑜伽_取消测试 +-- - 张三(时长卡)、李四(储值卡)、王五(储值卡) 预约了晚间瑜伽_取消测试 -- - 取消后应恢复对应卡状态,释放课程名额 -- -- 4. V12 时间冲突检测: --- - 团课A: 6/18 13:00-15:00, 团课B: 6/18 14:00-16:00 → 时间重叠 --- - 团课C: 6/18 10:00-12:00 → 与A/B不冲突 +-- - 团课A: 6/26 13:00-15:00, 团课B: 6/26 14:00-16:00 → 时间重叠 +-- - 团课C: 6/26 10:00-12:00 → 与A/B不冲突 -- -- 5. V14 数据统计: --- - 3天签到/预约数据,覆盖今日/昨日/前日 +-- - 3天签到/预约数据,覆盖今天/昨天/前天 -- -- 6. V18 团课推荐: -- - 10条推荐记录,覆盖不同优先级和启用状态 diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V23__Add_Coach_Test_Data.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V23__Add_Coach_Test_Data.sql index fa0167c..48bd01a 100644 --- a/gym-manage-api/manage-db/src/main/resources/db/migration/V23__Add_Coach_Test_Data.sql +++ b/gym-manage-api/manage-db/src/main/resources/db/migration/V23__Add_Coach_Test_Data.sql @@ -58,32 +58,32 @@ AND NOT EXISTS ( -- ============================================================ -- 6. 为教练分配今天的测试团课 -- ============================================================ -INSERT INTO group_course (course_name, coach_id, course_type, start_time, end_time, max_members, current_members, status, location, description, point_card_amount, create_by, created_at, updated_at) +INSERT INTO group_course (course_name, coach_id, course_type, start_time, end_time, max_members, current_members, status, location, description, stored_value_amount, create_by, created_at, updated_at) VALUES ('核心力量训练', 20, (SELECT id FROM group_course_type WHERE type_name = '核心训练' LIMIT 1), - CURRENT_DATE + TIME '08:00', CURRENT_DATE + TIME '09:00', 15, 3, '0', '健身房B区', '针对核心肌群的专项训练,适合所有水平学员', 1, 'system', NOW(), NOW()), + CURRENT_DATE + TIME '08:00', CURRENT_DATE + TIME '09:00', 15, 3, '0', '健身房B区', '针对核心肌群的专项训练,适合所有水平学员', 50.00, 'system', NOW(), NOW()), ('瑜伽入门', 20, (SELECT id FROM group_course_type WHERE type_name = '瑜伽入门' LIMIT 1), - CURRENT_DATE + TIME '10:00', CURRENT_DATE + TIME '11:00', 12, 5, '0', '瑜伽教室', '基础的瑜伽体式教学,新课上线欢迎参加', 1, 'system', NOW(), NOW()), + CURRENT_DATE + TIME '10:00', CURRENT_DATE + TIME '11:00', 12, 5, '0', '瑜伽教室', '基础的瑜伽体式教学,新课上线欢迎参加', 50.00, 'system', NOW(), NOW()), ('动感单车', 20, (SELECT id FROM group_course_type WHERE type_name = '动感单车' LIMIT 1), - CURRENT_DATE + TIME '14:00', CURRENT_DATE + TIME '15:00', 20, 8, '0', '单车房', '高强度燃脂骑行,动感音乐带你飞', 2, 'system', NOW(), NOW()), + CURRENT_DATE + TIME '14:00', CURRENT_DATE + TIME '15:00', 20, 8, '0', '单车房', '高强度燃脂骑行,动感音乐带你飞', 50.00, 'system', NOW(), NOW()), ('晚间普拉提', 20, (SELECT id FROM group_course_type WHERE type_name = '普拉提' LIMIT 1), - CURRENT_DATE + TIME '18:00', CURRENT_DATE + TIME '19:00', 10, 2, '0', '普拉提教室', '注重核心力量和身体控制,放松一天疲劳', 1, 'system', NOW(), NOW()); + CURRENT_DATE + TIME '18:00', CURRENT_DATE + TIME '19:00', 10, 2, '0', '普拉提教室', '注重核心力量和身体控制,放松一天疲劳', 50.00, 'system', NOW(), NOW()); -- ============================================================ -- 7. 为教练创建一个未来的团课(方便测试时间表) -- ============================================================ -INSERT INTO group_course (course_name, coach_id, course_type, start_time, end_time, max_members, current_members, status, location, description, point_card_amount, create_by, created_at, updated_at) +INSERT INTO group_course (course_name, coach_id, course_type, start_time, end_time, max_members, current_members, status, location, description, stored_value_amount, create_by, created_at, updated_at) VALUES ('HIIT燃脂训练', 20, (SELECT id FROM group_course_type WHERE type_name = 'HIIT训练' LIMIT 1), CURRENT_DATE + INTERVAL '1 day' + TIME '08:00', CURRENT_DATE + INTERVAL '1 day' + TIME '09:00', - 15, 0, '0', '综合训练区', '高强度间歇训练,挑战心肺极限', 2, 'system', NOW(), NOW()), + 15, 0, '0', '综合训练区', '高强度间歇训练,挑战心肺极限', 50.00, 'system', NOW(), NOW()), ('周末瑜伽', 20, (SELECT id FROM group_course_type WHERE type_name = '瑜伽进阶' LIMIT 1), CURRENT_DATE + INTERVAL '3 days' + TIME '09:00', CURRENT_DATE + INTERVAL '3 days' + TIME '10:30', - 12, 0, '0', '瑜伽教室', '针对有基础的进阶瑜伽课程', 1, 'system', NOW(), NOW()); + 12, 0, '0', '瑜伽教室', '针对有基础的进阶瑜伽课程', 50.00, 'system', NOW(), NOW()); -- ============================================================ -- 8. 重置序列值 diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V24__Add_Second_Coach_Test_Data.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V24__Add_Second_Coach_Test_Data.sql index 9398aef..3aee92c 100644 --- a/gym-manage-api/manage-db/src/main/resources/db/migration/V24__Add_Second_Coach_Test_Data.sql +++ b/gym-manage-api/manage-db/src/main/resources/db/migration/V24__Add_Second_Coach_Test_Data.sql @@ -25,23 +25,23 @@ ON CONFLICT (user_id, role_id) DO NOTHING; -- ============================================================ -- 3. 为李教练分配测试团课(未来一周的课程) -- ============================================================ -INSERT INTO group_course (course_name, coach_id, course_type, start_time, end_time, max_members, current_members, status, location, description, point_card_amount, create_by, created_at, updated_at) +INSERT INTO group_course (course_name, coach_id, course_type, start_time, end_time, max_members, current_members, status, location, description, stored_value_amount, create_by, created_at, updated_at) VALUES ('入门瑜伽', 21, (SELECT id FROM group_course_type WHERE type_name = '瑜伽入门' LIMIT 1), CURRENT_DATE + INTERVAL '2 days' + TIME '09:00', CURRENT_DATE + INTERVAL '2 days' + TIME '10:00', - 15, 0, '0', '瑜伽教室', '适合零基础学员的入门瑜伽课程', 1, 'system', NOW(), NOW()), + 15, 0, '0', '瑜伽教室', '适合零基础学员的入门瑜伽课程', 50.00, 'system', NOW(), NOW()), ('燃脂搏击', 21, (SELECT id FROM group_course_type WHERE type_name = '动感单车' LIMIT 1), CURRENT_DATE + INTERVAL '2 days' + TIME '15:00', CURRENT_DATE + INTERVAL '2 days' + TIME '16:00', - 20, 0, '0', '综合训练区', '高强度搏击操,燃烧卡路里', 2, 'system', NOW(), NOW()), + 20, 0, '0', '综合训练区', '高强度搏击操,燃烧卡路里', 50.00, 'system', NOW(), NOW()), ('肩颈理疗', 21, (SELECT id FROM group_course_type WHERE type_name = '普拉提' LIMIT 1), CURRENT_DATE + INTERVAL '4 days' + TIME '10:00', CURRENT_DATE + INTERVAL '4 days' + TIME '11:00', - 12, 0, '0', '普拉提教室', '针对久坐人群的肩颈放松课程', 1, 'system', NOW(), NOW()), + 12, 0, '0', '普拉提教室', '针对久坐人群的肩颈放松课程', 50.00, 'system', NOW(), NOW()), ('晨间有氧', 21, (SELECT id FROM group_course_type WHERE type_name = 'HIIT训练' LIMIT 1), CURRENT_DATE + INTERVAL '5 days' + TIME '07:00', CURRENT_DATE + INTERVAL '5 days' + TIME '08:00', - 20, 0, '0', '有氧区', '清晨燃脂有氧训练,开启活力一天', 2, 'system', NOW(), NOW()); + 20, 0, '0', '有氧区', '清晨燃脂有氧训练,开启活力一天', 50.00, 'system', NOW(), NOW()); -- ============================================================ -- 4. 重置序列值 diff --git a/gym-manage-api/manage-db/src/main/resources/db/migration/V6__Create_GroupCourse_table.sql b/gym-manage-api/manage-db/src/main/resources/db/migration/V6__Create_GroupCourse_table.sql index 714cb41..685d19e 100644 --- a/gym-manage-api/manage-db/src/main/resources/db/migration/V6__Create_GroupCourse_table.sql +++ b/gym-manage-api/manage-db/src/main/resources/db/migration/V6__Create_GroupCourse_table.sql @@ -18,7 +18,6 @@ CREATE TABLE IF NOT EXISTS group_course ( location VARCHAR(255), cover_image VARCHAR(500), description TEXT, - point_card_amount INTEGER DEFAULT 1, stored_value_amount DECIMAL(10,2) DEFAULT 0, create_by VARCHAR(50), update_by VARCHAR(50), @@ -58,7 +57,6 @@ COMMENT ON COLUMN group_course.actual_end_time IS '实际结课时间(教练 COMMENT ON COLUMN group_course.location IS '上课地点'; COMMENT ON COLUMN group_course.cover_image IS '封面图URL'; COMMENT ON COLUMN group_course.description IS '课程描述'; -COMMENT ON COLUMN group_course.point_card_amount IS '点卡额度(消耗次数)'; COMMENT ON COLUMN group_course.stored_value_amount IS '储值卡额度(消耗金额)'; COMMENT ON COLUMN group_course.create_by IS '创建人'; COMMENT ON COLUMN group_course.update_by IS '更新人';