diff --git a/gym-manage-api/KeyAnalyzer.java b/gym-manage-api/KeyAnalyzer.java
new file mode 100644
index 0000000..e460fe7
--- /dev/null
+++ b/gym-manage-api/KeyAnalyzer.java
@@ -0,0 +1,202 @@
+import java.util.Base64;
+import java.math.BigInteger;
+import java.io.ByteArrayInputStream;
+
+public class KeyAnalyzer {
+ public static void main(String[] args) {
+ String privateKeyBase64 = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCcxAwrhZF+izz1gxQKOr4jnC05obIBHbl0DzHOcd4CaaDV7Kv1NRJKi33JhdDsW4JGgu16e5Rtzq1VzU5VWz5EKgGL46maOFwCkngJUTP/LC3JVf/wtJmYCm8xNE8C7GNNIqKzYEvUOEfXqagpVvVrGsQ9FpzFp2rP9hBmHY3yizVyPX/uT9S+af5TRxiaItj3SSJGgloaEMrnKOpb/EH7JwPSS0liAyT/NxPfOyyZHc22AvaAIOE5y+0PMUIKPuIdfpOrej3LVpO1Arc2hSgmdB+YIPSiBVYPXa6AuAmil9mpbtSikQJ7Uu7lX4JyTW4QxQ06rPFKnFWVKkzivAElAgMBAAECggEAJd0AJ37iTlMpDQ90xqe7hvRQxAu256gbQ9nrqLY97g0/KIw6WEZSPakFX6gvdvb/NzKmUyAIEKGLoh6tXdZk6qfOqc/6BeK47nIcBfwT9/zerjNUVvn34w4aHyNINieMMHQ+Id8PUZmqWH+Euz9ilVTosuyEPwUZulLvUQqwXzU5VnwVghURbUhDd+ecBJACWgemRun6d5241PQXNYAdH1k7cETd8GfIi3qclhhJrxi7tu5tq4YGCXQIoz7HCLim7GIvT0M+FRgSw2EOrHnAQNFeQ/vQbP71ttLoTxehL6Se9dfWrV5OI+Y/T7vR2F84Qt0iNbaxyJGir7siKDFIwQKBgQDXDzinx3/TasplM78pR/0CtuuKr1Ch02LOPrTosJ1qf1OohxQThowhOTxMsBlgYSKu9s1QRffUUXEqYXxd2B6lzDKfggwO6U2XxIcxWeNow0xoFfqcXYSg7Ga2sCr9uhdwxIdFQNF7SNBpT8ht4fJrRX6mWY1nHybpyTDQ4xoQNQKBgQC6m+yiOoi5JD3zVSSJq/iq5DJPA5B4aoP+t5u9lp2Q7iVO0QI5ilBlEKGE4VOU0glnXlDTfuqEYooMY85ekl4WGb3AOT0PhLL2i+gO2nlWBzf4HPzB/hibjfyPyniRM03cHkG3HXucL7Sne6FwERcfjEqjUd2cdP1l89PNrq4rMQKBgQCMmjABSWYh8/y1I5rEQ4OAJdVjC3GdC1Xa35ZpVybjvLEWSpHunhW5lvD8dllw8LC7UTI0XDpGPqTM/4VO2YBYB2PFc0Gs8g0/v0ZgFpOeJ6kpl80MM/wFNemFYTIKRoMSv/psZY9PmfBgGcBBTuquBXZjDcNr+yr2yAm5V/DvTQKBgHqRi94KoF8q5N39IKCkqhJlDH5FkxDktYoKw2rFkPzuzuZz9gghRyj6wXxsG9/2DWMt2dzw0czehFoa/CO188KEadPmRKr6uCmkP2nyKhxNZX+8WnB5G2Sg4DD6BjMpBYz8+qDx5ozx8LDJTYI0V4HLPgMD9JGdbgsXGhlREOkhAoGALr6IQOXnviWNAhCdc7rrsaMLMPbLZ1wqzWtQUG1JxDobbpzEP4CW/mvW5pMn58mSBg5qbXhyDI4fFP0CPb98QIz2tGnIzYyFzdKmF5Z1N7X1OF9O+tsSqASoBZzTqB4fr/o4mz0s9JCeriBR2LWjsbsDU13DTLsfQpWbtOnIy70=";
+
+ System.out.println("========== 密钥分析开始 ==========");
+ System.out.println("密钥长度: " + privateKeyBase64.length());
+
+ byte[] keyBytes = Base64.getDecoder().decode(privateKeyBase64);
+ System.out.println("解码后长度: " + keyBytes.length + " bytes");
+
+ // 打印前30字节
+ System.out.print("前30字节(hex): ");
+ for (int i = 0; i < 30 && i < keyBytes.length; i++) {
+ System.out.printf("%02X ", keyBytes[i]);
+ }
+ System.out.println();
+
+ // 解析ASN.1结构
+ try {
+ parseASN1(keyBytes);
+ } catch (Exception e) {
+ System.out.println("解析失败: " + e.getMessage());
+ e.printStackTrace();
+ }
+ }
+
+ static void parseASN1(byte[] keyBytes) throws Exception {
+ ByteArrayInputStream bis = new ByteArrayInputStream(keyBytes);
+
+ // 读取SEQUENCE
+ int tag = bis.read();
+ System.out.println("第一个tag: 0x" + Integer.toHexString(tag));
+
+ if (tag == 0x30) {
+ System.out.println("这是SEQUENCE");
+
+ // 读取长度
+ int len = readASN1Length(bis);
+ System.out.println("SEQUENCE长度: " + len);
+
+ // 检查下一个tag
+ int nextTag = bis.read();
+ System.out.println("下一个tag: 0x" + Integer.toHexString(nextTag));
+
+ if (nextTag == 0x02) {
+ // INTEGER
+ int intLen = readASN1Length(bis);
+ byte[] versionBytes = new byte[intLen];
+ bis.read(versionBytes);
+ int version = new BigInteger(versionBytes).intValue();
+ System.out.println("版本INTEGER值: " + version);
+
+ // 检查这是PKCS#8还是PKCS#1
+ // PKCS#8: version=0后是SEQUENCE(算法标识符)
+ // PKCS#1: version=0后是INTEGER(modulus)
+ int afterVersionTag = bis.read();
+ System.out.println("版本后的tag: 0x" + Integer.toHexString(afterVersionTag));
+
+ if (afterVersionTag == 0x30) {
+ System.out.println(">>> 这是PKCS#8格式 (version后是SEQUENCE)");
+
+ // 解析PKCS#8
+ // AlgorithmIdentifier: SEQUENCE { OID, NULL }
+ int algLen = readASN1Length(bis);
+ System.out.println("AlgorithmIdentifier长度: " + algLen);
+
+ // 跳过AlgorithmIdentifier内容
+ byte[] algBytes = new byte[algLen];
+ bis.read(algBytes);
+
+ // 打印OID
+ System.out.print("OID bytes: ");
+ for (int i = 0; i < algBytes.length; i++) {
+ System.out.printf("%02X ", algBytes[i]);
+ }
+ System.out.println();
+
+ // 下一个应该是OCTET STRING (包含私钥)
+ int octetTag = bis.read();
+ System.out.println("下一个tag: 0x" + Integer.toHexString(octetTag));
+
+ if (octetTag == 0x04) {
+ System.out.println("这是OCTET STRING (包含私钥数据)");
+ int octetLen = readASN1Length(bis);
+ System.out.println("OCTET STRING长度: " + octetLen);
+
+ // OCTET STRING内容是PKCS#1私钥
+ byte[] pkcs1Bytes = new byte[octetLen];
+ bis.read(pkcs1Bytes);
+
+ System.out.print("PKCS#1私钥前20字节: ");
+ for (int i = 0; i < 20; i++) {
+ System.out.printf("%02X ", pkcs1Bytes[i]);
+ }
+ System.out.println();
+
+ // 解析PKCS#1私钥
+ ByteArrayInputStream pkcs1Stream = new ByteArrayInputStream(pkcs1Bytes);
+ int pkcs1Tag = pkcs1Stream.read();
+ System.out.println("PKCS#1第一个tag: 0x" + Integer.toHexString(pkcs1Tag));
+
+ if (pkcs1Tag == 0x30) {
+ int pkcs1Len = readASN1Length(pkcs1Stream);
+ System.out.println("PKCS#1 SEQUENCE长度: " + pkcs1Len);
+
+ // version
+ int vTag = pkcs1Stream.read();
+ int vLen = readASN1Length(pkcs1Stream);
+ byte[] vBytes = new byte[vLen];
+ pkcs1Stream.read(vBytes);
+ System.out.println("PKCS#1版本: " + new BigInteger(vBytes));
+
+ // 解析私钥参数
+ parsePKCS1(pkcs1Stream);
+ }
+ }
+ } else if (afterVersionTag == 0x02) {
+ System.out.println(">>> 这是PKCS#1格式 (version后是INTEGER)");
+
+ // 继续解析PKCS#1
+ parsePKCS1(bis);
+ } else {
+ System.out.println(">>> 未知格式 (tag: 0x" + Integer.toHexString(afterVersionTag) + ")");
+ }
+ }
+ }
+ }
+
+ static void parsePKCS1(ByteArrayInputStream bis) throws Exception {
+ System.out.println("\n========== 解析PKCS#1私钥参数 ==========");
+
+ // modulus
+ BigInteger modulus = readASN1Integer(bis);
+ System.out.println("modulus长度: " + modulus.bitLength() + " bits");
+
+ // publicExponent
+ BigInteger publicExponent = readASN1Integer(bis);
+ System.out.println("publicExponent: " + publicExponent);
+
+ // privateExponent
+ BigInteger privateExponent = readASN1Integer(bis);
+ System.out.println("privateExponent长度: " + privateExponent.bitLength() + " bits");
+
+ // prime1
+ BigInteger prime1 = readASN1Integer(bis);
+ System.out.println("prime1长度: " + prime1.bitLength() + " bits");
+
+ // prime2
+ BigInteger prime2 = readASN1Integer(bis);
+ System.out.println("prime2长度: " + prime2.bitLength() + " bits");
+
+ // 验证 prime1 * prime2 == modulus
+ BigInteger calculatedModulus = prime1.multiply(prime2);
+ boolean valid = calculatedModulus.equals(modulus);
+ System.out.println("\n验证 prime1 * prime2 == modulus: " + valid);
+
+ if (!valid) {
+ System.out.println(">>> 密钥数学关系不正确!这是无效的RSA私钥");
+ System.out.println("计算得到的modulus长度: " + calculatedModulus.bitLength() + " bits");
+ }
+
+ // exponent1
+ BigInteger exponent1 = readASN1Integer(bis);
+ System.out.println("exponent1长度: " + exponent1.bitLength() + " bits");
+
+ // exponent2
+ BigInteger exponent2 = readASN1Integer(bis);
+ System.out.println("exponent2长度: " + exponent2.bitLength() + " bits");
+
+ // coefficient
+ BigInteger coefficient = readASN1Integer(bis);
+ System.out.println("coefficient长度: " + coefficient.bitLength() + " bits");
+
+ System.out.println("\n========== 解析完成 ==========");
+ }
+
+ static int readASN1Length(ByteArrayInputStream bis) throws Exception {
+ int firstByte = bis.read();
+ if ((firstByte & 0x80) == 0) {
+ return firstByte;
+ }
+ int numBytes = firstByte & 0x7F;
+ int length = 0;
+ for (int i = 0; i < numBytes; i++) {
+ length = (length << 8) | (bis.read() & 0xFF);
+ }
+ return length;
+ }
+
+ static BigInteger readASN1Integer(ByteArrayInputStream bis) throws Exception {
+ int tag = bis.read();
+ if (tag != 0x02) throw new Exception("期望INTEGER tag: 0x02, 实际: 0x" + Integer.toHexString(tag));
+ int len = readASN1Length(bis);
+ byte[] data = new byte[len];
+ bis.read(data);
+ return new BigInteger(1, data);
+ }
+}
\ No newline at end of file
diff --git a/gym-manage-api/db/migration/V1__Create_all_tables.sql b/gym-manage-api/db/migration/V1__Create_all_tables.sql
deleted file mode 100644
index ab538c2..0000000
--- a/gym-manage-api/db/migration/V1__Create_all_tables.sql
+++ /dev/null
@@ -1,407 +0,0 @@
--- Novalon管理系统数据库初始化脚本
--- 版本: V1
--- 描述: 创建所有核心表结构(合并版)
-
--- ============================================
--- 用户与角色相关表
--- ============================================
-
--- 用户表
-CREATE TABLE IF NOT EXISTS sys_user (
- id BIGSERIAL PRIMARY KEY,
- username VARCHAR(50) NOT NULL UNIQUE,
- password VARCHAR(255) NOT NULL,
- email VARCHAR(100),
- phone VARCHAR(20),
- nickname VARCHAR(100),
- status INTEGER DEFAULT 1,
- role_id BIGINT,
- create_by VARCHAR(50),
- update_by VARCHAR(50),
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- deleted_at TIMESTAMP
-);
-
--- 角色表
-CREATE TABLE IF NOT EXISTS sys_role (
- id BIGSERIAL PRIMARY KEY,
- role_name VARCHAR(100) NOT NULL,
- role_key VARCHAR(100) NOT NULL UNIQUE,
- role_sort INTEGER DEFAULT 0,
- status INTEGER DEFAULT 1,
- create_by VARCHAR(50),
- update_by VARCHAR(50),
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- deleted_at TIMESTAMP
-);
-
--- 用户角色关联表(支持多对多关系)
-CREATE TABLE IF NOT EXISTS user_role (
- id BIGSERIAL PRIMARY KEY,
- user_id BIGINT NOT NULL,
- role_id BIGINT NOT NULL,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- created_by VARCHAR(50),
- CONSTRAINT fk_user_role_user FOREIGN KEY (user_id) REFERENCES sys_user(id) ON DELETE CASCADE,
- CONSTRAINT fk_user_role_role FOREIGN KEY (role_id) REFERENCES sys_role(id) ON DELETE CASCADE,
- CONSTRAINT uk_user_role UNIQUE (user_id, role_id)
-);
-
--- ============================================
--- 权限相关表
--- ============================================
-
--- 权限表
-CREATE TABLE IF NOT EXISTS sys_permission (
- id BIGSERIAL PRIMARY KEY,
- permission_name VARCHAR(100) NOT NULL,
- permission_code VARCHAR(100) NOT NULL UNIQUE,
- resource VARCHAR(200) NOT NULL,
- action VARCHAR(50) NOT NULL,
- description VARCHAR(500),
- status INTEGER DEFAULT 1,
- create_by VARCHAR(50),
- update_by VARCHAR(50),
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- deleted_at TIMESTAMP
-);
-
--- 角色权限关联表
-CREATE TABLE IF NOT EXISTS sys_role_permission (
- id BIGSERIAL PRIMARY KEY,
- role_id BIGINT NOT NULL,
- permission_id BIGINT NOT NULL,
- create_by VARCHAR(50),
- update_by VARCHAR(50),
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- FOREIGN KEY (role_id) REFERENCES sys_role(id) ON DELETE CASCADE,
- FOREIGN KEY (permission_id) REFERENCES sys_permission(id) ON DELETE CASCADE,
- UNIQUE (role_id, permission_id)
-);
-
--- ============================================
--- 菜单相关表
--- ============================================
-
--- 菜单表
-CREATE TABLE IF NOT EXISTS sys_menu (
- id BIGSERIAL PRIMARY KEY,
- menu_name VARCHAR(50) NOT NULL,
- parent_id BIGINT DEFAULT 0,
- order_num INTEGER DEFAULT 0,
- menu_type VARCHAR(1) DEFAULT 'C',
- perms VARCHAR(100),
- component VARCHAR(200),
- status INTEGER DEFAULT 1,
- create_by VARCHAR(50),
- update_by VARCHAR(50),
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- deleted_at TIMESTAMP
-);
-
--- ============================================
--- 字典相关表
--- ============================================
-
--- 字典类型表
-CREATE TABLE IF NOT EXISTS sys_dict_type (
- id BIGSERIAL PRIMARY KEY,
- dict_name VARCHAR(100) NOT NULL,
- dict_type VARCHAR(100) NOT NULL UNIQUE,
- status VARCHAR(1) DEFAULT '0',
- remark VARCHAR(500),
- create_by VARCHAR(50),
- update_by VARCHAR(50),
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- deleted_at TIMESTAMP
-);
-
--- 字典数据表
-CREATE TABLE IF NOT EXISTS sys_dict_data (
- id BIGSERIAL PRIMARY KEY,
- dict_sort INTEGER DEFAULT 0,
- dict_label VARCHAR(100) NOT NULL,
- dict_value VARCHAR(100) NOT NULL,
- dict_type VARCHAR(100) NOT NULL,
- css_class VARCHAR(100),
- list_class VARCHAR(100),
- is_default VARCHAR(1) DEFAULT 'N',
- status VARCHAR(1) DEFAULT '0',
- create_by VARCHAR(50),
- update_by VARCHAR(50),
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- deleted_at TIMESTAMP
-);
-
--- 字典表(通用字典)
-CREATE TABLE IF NOT EXISTS sys_dictionary (
- id BIGSERIAL PRIMARY KEY,
- type VARCHAR(100) NOT NULL,
- code VARCHAR(100) NOT NULL,
- name VARCHAR(100) NOT NULL,
- value VARCHAR(500),
- remark VARCHAR(500),
- sort INTEGER DEFAULT 0,
- create_by VARCHAR(50),
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- deleted_at TIMESTAMP
-);
-
--- ============================================
--- 系统配置表
--- ============================================
-
--- 系统配置表
-CREATE TABLE IF NOT EXISTS sys_config (
- id BIGSERIAL PRIMARY KEY,
- config_name VARCHAR(100) NOT NULL,
- config_key VARCHAR(100) NOT NULL UNIQUE,
- config_value VARCHAR(500) NOT NULL,
- config_type VARCHAR(1) DEFAULT 'N',
- create_by VARCHAR(50),
- update_by VARCHAR(50),
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- deleted_at TIMESTAMP
-);
-
--- ============================================
--- 日志相关表
--- ============================================
-
--- 登录日志表
-CREATE TABLE IF NOT EXISTS sys_login_log (
- id BIGSERIAL PRIMARY KEY,
- username VARCHAR(50),
- ip VARCHAR(50),
- location VARCHAR(255),
- browser VARCHAR(50),
- os VARCHAR(50),
- status VARCHAR(1),
- message VARCHAR(255),
- login_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
-);
-
--- 异常日志表
-CREATE TABLE IF NOT EXISTS sys_exception_log (
- id BIGSERIAL PRIMARY KEY,
- username VARCHAR(50),
- title VARCHAR(100),
- exception_name VARCHAR(100),
- method_name VARCHAR(255),
- method_params TEXT,
- exception_msg TEXT,
- exception_stack TEXT,
- ip VARCHAR(50),
- create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
-);
-
--- 操作日志表
-CREATE TABLE IF NOT EXISTS operation_log (
- id BIGSERIAL PRIMARY KEY,
- username VARCHAR(50),
- operation VARCHAR(100),
- method VARCHAR(200),
- params TEXT,
- result TEXT,
- ip VARCHAR(50),
- duration BIGINT,
- status VARCHAR(1) DEFAULT '0',
- error_msg TEXT,
- create_by VARCHAR(50),
- update_by VARCHAR(50),
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- deleted_at TIMESTAMP
-);
-
--- 审计日志表
-CREATE TABLE IF NOT EXISTS audit_log (
- id BIGSERIAL PRIMARY KEY,
- entity_type VARCHAR(100) NOT NULL,
- entity_id BIGINT,
- operation_type VARCHAR(20) NOT NULL,
- operator VARCHAR(100),
- operation_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- before_data JSONB,
- after_data JSONB,
- changed_fields TEXT[],
- ip_address VARCHAR(50),
- user_agent TEXT,
- description TEXT,
- create_by VARCHAR(50),
- update_by VARCHAR(50),
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- deleted_at TIMESTAMP
-);
-
--- 审计日志归档表
-CREATE TABLE IF NOT EXISTS audit_log_archive (
- id BIGSERIAL PRIMARY KEY,
- entity_type VARCHAR(100) NOT NULL,
- entity_id BIGINT,
- operation_type VARCHAR(20) NOT NULL,
- operator VARCHAR(100),
- operation_time TIMESTAMP,
- before_data JSONB,
- after_data JSONB,
- changed_fields TEXT[],
- ip_address VARCHAR(50),
- user_agent TEXT,
- description TEXT,
- created_at TIMESTAMP,
- archived_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
-);
-
--- ============================================
--- 通知与消息表
--- ============================================
-
--- 系统公告表
-CREATE TABLE IF NOT EXISTS sys_notice (
- id BIGSERIAL PRIMARY KEY,
- notice_title VARCHAR(50) NOT NULL,
- notice_type VARCHAR(1) NOT NULL,
- notice_content TEXT,
- status VARCHAR(1) DEFAULT '0',
- create_by VARCHAR(50),
- update_by VARCHAR(50),
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- deleted_at TIMESTAMP
-);
-
--- 用户消息表
-CREATE TABLE IF NOT EXISTS sys_user_message (
- id BIGSERIAL PRIMARY KEY,
- user_id BIGINT NOT NULL,
- notice_id BIGINT,
- message_title VARCHAR(255),
- message_content TEXT,
- is_read VARCHAR(1) DEFAULT '0',
- read_time TIMESTAMP,
- create_by VARCHAR(50),
- update_by VARCHAR(50),
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- deleted_at TIMESTAMP
-);
-
--- ============================================
--- 文件管理表
--- ============================================
-
--- 文件管理表
-CREATE TABLE IF NOT EXISTS sys_file (
- id BIGSERIAL PRIMARY KEY,
- file_name VARCHAR(255) NOT NULL,
- file_path VARCHAR(500) NOT NULL,
- file_size BIGINT,
- file_type VARCHAR(100),
- file_extension VARCHAR(10),
- storage_type VARCHAR(50),
- create_by VARCHAR(50),
- update_by VARCHAR(50),
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- deleted_at TIMESTAMP
-);
-
--- ============================================
--- OAuth2相关表
--- ============================================
-
--- OAuth2客户端表
-CREATE TABLE IF NOT EXISTS oauth2_client (
- id BIGSERIAL PRIMARY KEY,
- client_id VARCHAR(100) NOT NULL UNIQUE,
- client_secret VARCHAR(255) NOT NULL,
- client_name VARCHAR(100),
- web_server_redirect_uri VARCHAR(500),
- scope VARCHAR(500),
- authorized_grant_types VARCHAR(500),
- access_token_validity_seconds INTEGER,
- refresh_token_validity_seconds INTEGER,
- auto_approve VARCHAR(1) DEFAULT 'false',
- enabled VARCHAR(1) DEFAULT 'true',
- create_by VARCHAR(50),
- update_by VARCHAR(50),
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- deleted_at TIMESTAMP
-);
-
--- ============================================
--- 表注释
--- ============================================
-
-COMMENT ON TABLE sys_user IS '系统用户表';
-COMMENT ON TABLE sys_role IS '系统角色表';
-COMMENT ON TABLE user_role IS '用户角色关联表';
-COMMENT ON TABLE sys_permission IS '系统权限表';
-COMMENT ON TABLE sys_role_permission IS '角色权限关联表';
-COMMENT ON TABLE sys_menu IS '系统菜单表';
-COMMENT ON TABLE sys_dict_type IS '字典类型表';
-COMMENT ON TABLE sys_dict_data IS '字典数据表';
-COMMENT ON TABLE sys_dictionary IS '通用字典表';
-COMMENT ON TABLE sys_config IS '系统配置表';
-COMMENT ON TABLE sys_login_log IS '登录日志表';
-COMMENT ON TABLE sys_exception_log IS '异常日志表';
-COMMENT ON TABLE operation_log IS '操作日志表';
-COMMENT ON TABLE audit_log IS '审计日志表';
-COMMENT ON TABLE audit_log_archive IS '审计日志归档表';
-COMMENT ON TABLE sys_notice IS '系统公告表';
-COMMENT ON TABLE sys_user_message IS '用户消息表';
-COMMENT ON TABLE sys_file IS '文件管理表';
-COMMENT ON TABLE oauth2_client IS 'OAuth2客户端表';
-
-COMMENT ON TABLE sys_exception_log IS '异常日志表';
-COMMENT ON COLUMN sys_exception_log.id IS '主键ID';
-COMMENT ON COLUMN sys_exception_log.username IS '操作用户';
-COMMENT ON COLUMN sys_exception_log.title IS '异常标题';
-COMMENT ON COLUMN sys_exception_log.exception_name IS '异常名称';
-COMMENT ON COLUMN sys_exception_log.method_name IS '方法名称';
-COMMENT ON COLUMN sys_exception_log.method_params IS '方法参数';
-COMMENT ON COLUMN sys_exception_log.exception_msg IS '异常消息';
-COMMENT ON COLUMN sys_exception_log.exception_stack IS '异常堆栈';
-COMMENT ON COLUMN sys_exception_log.ip IS 'IP地址';
-COMMENT ON COLUMN sys_exception_log.create_time IS '创建时间';
-
-COMMENT ON TABLE audit_log IS '审计日志表';
-COMMENT ON COLUMN audit_log.id IS '主键ID';
-COMMENT ON COLUMN audit_log.entity_type IS '实体类型(如User, Role等)';
-COMMENT ON COLUMN audit_log.entity_id IS '实体ID';
-COMMENT ON COLUMN audit_log.operation_type IS '操作类型(CREATE, UPDATE, DELETE)';
-COMMENT ON COLUMN audit_log.operator IS '操作人';
-COMMENT ON COLUMN audit_log.operation_time IS '操作时间';
-COMMENT ON COLUMN audit_log.before_data IS '变更前数据(JSON格式)';
-COMMENT ON COLUMN audit_log.after_data IS '变更后数据(JSON格式)';
-COMMENT ON COLUMN audit_log.changed_fields IS '变更字段列表';
-COMMENT ON COLUMN audit_log.ip_address IS 'IP地址';
-COMMENT ON COLUMN audit_log.description IS '操作描述';
-COMMENT ON COLUMN audit_log.created_at IS '记录创建时间';
-
-COMMENT ON TABLE audit_log_archive IS '审计日志归档表';
-COMMENT ON COLUMN audit_log_archive.id IS '主键ID';
-COMMENT ON COLUMN audit_log_archive.entity_type IS '实体类型(如User, Role等)';
-COMMENT ON COLUMN audit_log_archive.entity_id IS '实体ID';
-COMMENT ON COLUMN audit_log_archive.operation_type IS '操作类型(CREATE, UPDATE, DELETE)';
-COMMENT ON COLUMN audit_log_archive.operator IS '操作人';
-COMMENT ON COLUMN audit_log_archive.operation_time IS '操作时间';
-COMMENT ON COLUMN audit_log_archive.before_data IS '变更前数据(JSON格式)';
-COMMENT ON COLUMN audit_log_archive.after_data IS '变更后数据(JSON格式)';
-COMMENT ON COLUMN audit_log_archive.changed_fields IS '变更字段列表';
-COMMENT ON COLUMN audit_log_archive.ip_address IS 'IP地址';
-COMMENT ON COLUMN audit_log_archive.user_agent IS '用户代理';
-COMMENT ON COLUMN audit_log_archive.description IS '操作描述';
-COMMENT ON COLUMN audit_log_archive.created_at IS '记录创建时间';
-COMMENT ON COLUMN audit_log_archive.archived_at IS '归档时间';
diff --git a/gym-manage-api/gym-auth/pom.xml b/gym-manage-api/gym-auth/pom.xml
new file mode 100644
index 0000000..42aa5be
--- /dev/null
+++ b/gym-manage-api/gym-auth/pom.xml
@@ -0,0 +1,123 @@
+
+
+ 4.0.0
+
+
+ cn.novalon.gym.manage
+ gym-manage-api
+ 1.0.0
+
+
+ gym-auth
+ jar
+
+ Gym Auth
+ Phone Authentication Module - Phone Number Login Services
+
+
+
+ cn.novalon.gym.manage
+ manage-common
+ ${project.version}
+
+
+ cn.novalon.gym.manage
+ manage-db
+ ${project.version}
+
+
+ cn.novalon.gym.manage
+ manage-sys
+ ${project.version}
+
+
+ cn.novalon.gym.manage
+ gym-member
+ ${project.version}
+
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+
+
+ org.springdoc
+ springdoc-openapi-starter-webflux-ui
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+ org.springframework.data
+ spring-data-commons
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ io.projectreactor
+ reactor-test
+ test
+
+
+ cn.hutool
+ hutool-all
+ 5.8.25
+
+
+
+ com.aliyun
+ aliyun-java-sdk-dypnsapi
+ 1.2.12
+
+
+ com.aliyun
+ aliyun-java-sdk-core
+ 4.6.0
+
+
+ com.aliyun
+ dysmsapi20170525
+ 2.0.0
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 3.4.2
+
+
+ default-jar
+ package
+
+ jar
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.11.0
+
+ 21
+ 21
+
+
+ org.projectlombok
+ lombok
+ ${lombok.version}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/config/AuthConfig.java b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/config/AuthConfig.java
new file mode 100644
index 0000000..d8d842d
--- /dev/null
+++ b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/config/AuthConfig.java
@@ -0,0 +1,7 @@
+package cn.novalon.gym.manage.auth.config;
+
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class AuthConfig {
+}
\ No newline at end of file
diff --git a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/config/SmsProperties.java b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/config/SmsProperties.java
new file mode 100644
index 0000000..fe9e35c
--- /dev/null
+++ b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/config/SmsProperties.java
@@ -0,0 +1,47 @@
+package cn.novalon.gym.manage.auth.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 阿里云短信配置属性类
+ *
+ * @author auto-generated
+ * @date 2026-06-20
+ */
+@Data
+@Configuration
+@ConfigurationProperties(prefix = "alibaba.cloud.sms")
+public class SmsProperties {
+
+ /**
+ * 访问密钥ID
+ */
+ private String accessKeyId;
+
+ /**
+ * 访问密钥密钥
+ */
+ private String accessKeySecret;
+
+ /**
+ * 短信签名名称
+ */
+ private String signName;
+
+ /**
+ * 短信模板CODE
+ */
+ private String templateCode;
+
+ /**
+ * 短信验证码长度(默认6位)
+ */
+ private int codeLength = 6;
+
+ /**
+ * 短信验证码有效期(秒,默认300秒=5分钟)
+ */
+ private long codeExpireSeconds = 300;
+}
diff --git a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/dto/PhoneCodeLoginDto.java b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/dto/PhoneCodeLoginDto.java
new file mode 100644
index 0000000..4d21283
--- /dev/null
+++ b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/dto/PhoneCodeLoginDto.java
@@ -0,0 +1,26 @@
+package cn.novalon.gym.manage.auth.dto;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Pattern;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 手机号验证码登录请求DTO
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class PhoneCodeLoginDto {
+
+ @NotBlank(message = "手机号不能为空")
+ @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
+ private String phone;
+
+ @NotBlank(message = "验证码不能为空")
+ @Pattern(regexp = "^\\d{4,6}$", message = "验证码格式不正确")
+ private String code;
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..c3d0758
--- /dev/null
+++ b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/dto/PhoneLoginDto.java
@@ -0,0 +1,28 @@
+package cn.novalon.gym.manage.auth.dto;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 手机号一键登录请求DTO
+ * uniapp官方一键登录流程:前端通过云函数获取access_token和openid传给后端换取手机号
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class PhoneLoginDto {
+
+ @NotBlank(message = "access_token不能为空")
+ private String accessToken;
+
+ @NotBlank(message = "openid不能为空")
+ private String openid;
+
+ private String nickname;
+
+ private String avatar;
+}
diff --git a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/dto/SendCodeRequest.java b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/dto/SendCodeRequest.java
new file mode 100644
index 0000000..c16602d
--- /dev/null
+++ b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/dto/SendCodeRequest.java
@@ -0,0 +1,22 @@
+package cn.novalon.gym.manage.auth.dto;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Pattern;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 发送短信验证码请求DTO
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class SendCodeRequest {
+
+ @NotBlank(message = "手机号不能为空")
+ @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
+ private String phone;
+}
diff --git a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/dto/SmsLoginDto.java b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/dto/SmsLoginDto.java
new file mode 100644
index 0000000..d97cff5
--- /dev/null
+++ b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/dto/SmsLoginDto.java
@@ -0,0 +1,25 @@
+package cn.novalon.gym.manage.auth.dto;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Pattern;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 短信验证码登录请求DTO
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class SmsLoginDto {
+
+ @NotBlank(message = "手机号不能为空")
+ @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
+ private String phone;
+
+ @NotBlank(message = "验证码不能为空")
+ private String code;
+}
diff --git a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/handler/PhoneAuthHandler.java b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/handler/PhoneAuthHandler.java
new file mode 100644
index 0000000..562920a
--- /dev/null
+++ b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/handler/PhoneAuthHandler.java
@@ -0,0 +1,59 @@
+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;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Component;
+import org.springframework.web.reactive.function.server.ServerRequest;
+import org.springframework.web.reactive.function.server.ServerResponse;
+import reactor.core.publisher.Mono;
+
+import java.util.Map;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+@Tag(name = "手机号认证", description = "手机号一键登录与验证码登录")
+public class PhoneAuthHandler {
+
+ private final PhoneAuthService phoneAuthService;
+
+ @Operation(summary = "手机号一键登录", description = "使用uniapp官方运营商认证,直接手机号登录或注册")
+ public Mono oneClickLogin(ServerRequest request) {
+ log.info("收到手机号一键登录请求");
+
+ return request.bodyToMono(PhoneLoginDto.class)
+ .flatMap(phoneAuthService::oneClickLogin)
+ .flatMap(response -> ServerResponse.ok()
+ .contentType(MediaType.APPLICATION_JSON)
+ .bodyValue(response));
+ }
+
+ @Operation(summary = "发送短信验证码", description = "使用阿里云发送短信验证码")
+ public Mono sendSmsCode(ServerRequest request) {
+ log.info("收到发送短信验证码请求");
+
+ 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 ? "验证码发送成功" : "验证码发送失败")));
+ }
+
+ @Operation(summary = "手机号验证码登录", description = "使用阿里云短信验证码登录或注册")
+ public Mono codeLogin(ServerRequest request) {
+ log.info("收到手机号验证码登录请求");
+
+ return request.bodyToMono(PhoneCodeLoginDto.class)
+ .flatMap(phoneAuthService::codeLogin)
+ .flatMap(response -> ServerResponse.ok()
+ .contentType(MediaType.APPLICATION_JSON)
+ .bodyValue(response));
+ }
+}
\ No newline at end of file
diff --git a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/service/PhoneAuthService.java b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/service/PhoneAuthService.java
new file mode 100644
index 0000000..2407a9b
--- /dev/null
+++ b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/service/PhoneAuthService.java
@@ -0,0 +1,37 @@
+package cn.novalon.gym.manage.auth.service;
+
+import cn.novalon.gym.manage.auth.dto.PhoneLoginDto;
+import cn.novalon.gym.manage.auth.dto.PhoneCodeLoginDto;
+import cn.novalon.gym.manage.auth.vo.PhoneLoginVO;
+import reactor.core.publisher.Mono;
+
+/**
+ * 手机号认证服务接口
+ */
+public interface PhoneAuthService {
+
+ /**
+ * 手机号一键登录(uniapp官方运营商认证)
+ * 已注册则直接登录,未注册则自动注册后登录
+ *
+ * @param request 登录请求
+ * @return 登录响应
+ */
+ Mono oneClickLogin(PhoneLoginDto request);
+
+ /**
+ * 发送短信验证码(阿里云)
+ *
+ * @param phone 手机号
+ * @return 是否发送成功
+ */
+ Mono sendSmsCode(String phone);
+
+ /**
+ * 手机号验证码登录(阿里云)
+ *
+ * @param request 登录请求
+ * @return 登录响应
+ */
+ Mono codeLogin(PhoneCodeLoginDto request);
+}
\ No newline at end of file
diff --git a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/service/SmsService.java b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/service/SmsService.java
new file mode 100644
index 0000000..914f4ff
--- /dev/null
+++ b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/service/SmsService.java
@@ -0,0 +1,35 @@
+package cn.novalon.gym.manage.auth.service;
+
+/**
+ * 短信服务接口
+ *
+ * @author auto-generated
+ * @date 2026-06-20
+ */
+public interface SmsService {
+
+ /**
+ * 发送短信验证码
+ *
+ * @param phone 手机号
+ * @return 发送结果
+ */
+ boolean sendVerificationCode(String phone);
+
+ /**
+ * 验证短信验证码
+ *
+ * @param phone 手机号
+ * @param code 验证码
+ * @return 验证结果
+ */
+ boolean verifyCode(String phone, String code);
+
+ /**
+ * 获取验证码(用于测试或特殊场景)
+ *
+ * @param phone 手机号
+ * @return 验证码
+ */
+ String getVerificationCode(String phone);
+}
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
new file mode 100644
index 0000000..468f5a1
--- /dev/null
+++ b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/service/impl/PhoneAuthServiceImpl.java
@@ -0,0 +1,229 @@
+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;
+import cn.novalon.gym.manage.auth.service.SmsService;
+import cn.novalon.gym.manage.auth.vo.PhoneLoginVO;
+import cn.novalon.gym.manage.common.exception.ErrorCode;
+import cn.novalon.gym.manage.common.exception.SystemException;
+import cn.novalon.gym.manage.member.entity.Member;
+import cn.novalon.gym.manage.member.es.entity.MemberES;
+import cn.novalon.gym.manage.member.es.repository.MemberESRepository;
+import cn.novalon.gym.manage.member.repository.IMemberRepository;
+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;
+import org.springframework.stereotype.Service;
+import reactor.core.publisher.Mono;
+
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.List;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class PhoneAuthServiceImpl implements PhoneAuthService {
+
+ private final IMemberRepository memberRepository;
+ private final MemberESRepository memberESRepository;
+ private final EsSyncUtils esSyncUtils;
+ private final JwtTokenProvider jwtTokenProvider;
+ private final SmsService smsService;
+ private final SmsProperties smsProperties;
+
+ private EsSyncUtils.EntitySyncer memberSyncer;
+
+ @PostConstruct
+ public void init() {
+ this.memberSyncer = esSyncUtils.bind(Member.class, MemberES.class, memberESRepository);
+ }
+
+ @Override
+ public Mono oneClickLogin(PhoneLoginDto request) {
+ log.info("手机号一键登录请求, accessToken: {}, openid: {}", request.getAccessToken(), request.getOpenid());
+
+ 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);
+ })
+ .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
+ public Mono sendSmsCode(String phone) {
+ log.info("发送短信验证码, phone: {}", phone);
+ return Mono.fromCallable(() -> smsService.sendVerificationCode(phone));
+ }
+
+ @Override
+ public Mono codeLogin(PhoneCodeLoginDto request) {
+ log.info("手机号验证码登录, phone: {}", request.getPhone());
+
+ return Mono.fromCallable(() -> smsService.verifyCode(request.getPhone(), request.getCode()))
+ .flatMap(verified -> {
+ if (!verified) {
+ log.warn("验证码验证失败, phone: {}", request.getPhone());
+ return Mono.error(new SystemException(ErrorCode.SYSTEM_INTERNAL_ERROR, "验证码错误或已过期"));
+ }
+
+ String encryptedPhone = encryptPhone(request.getPhone());
+
+ return memberRepository.findByPhone(encryptedPhone)
+ .flatMap(existingMember -> {
+ log.info("手机号已注册,直接登录, memberId: {}", existingMember.getId());
+ return doLogin(existingMember, false, null);
+ })
+ .switchIfEmpty(Mono.defer(() -> {
+ log.info("手机号未注册,创建新会员");
+ PhoneLoginDto registerRequest = new PhoneLoginDto();
+ return createNewMemberAndLogin(registerRequest, encryptedPhone);
+ }));
+ });
+ }
+
+ private Mono createNewMemberAndLogin(PhoneLoginDto request, String encryptedPhone) {
+ String memberNo = MemberNoGenerator.generate();
+ log.info("生成会员号: {}", memberNo);
+
+ Member member = new Member();
+ member.setMemberNo(memberNo);
+ member.setPhone(encryptedPhone);
+ member.setNickname(request != null ? request.getNickname() : null);
+ member.setAvatar(request != null ? request.getAvatar() : null);
+ member.setLastLoginAt(LocalDateTime.now());
+ member.setIsDeleted(false);
+
+ return memberRepository.save(member)
+ .doOnSuccess(memberSyncer::sync)
+ .flatMap(savedMember -> {
+ log.info("新会员创建成功, memberId: {}, memberNo: {}", savedMember.getId(), savedMember.getMemberNo());
+ return doLogin(savedMember, true, request);
+ });
+ }
+
+ private Mono doLogin(Member member, boolean isNewUser, PhoneLoginDto request) {
+ log.info("登录成功, memberId: {}", member.getId());
+
+ member.setLastLoginAt(LocalDateTime.now());
+
+ if (isNewUser && request != null) {
+ if ((member.getNickname() == null || member.getNickname().isEmpty())
+ && request.getNickname() != null && !request.getNickname().isEmpty()) {
+ member.setNickname(request.getNickname());
+ }
+ if ((member.getAvatar() == null || member.getAvatar().isEmpty())
+ && request.getAvatar() != null && !request.getAvatar().isEmpty()) {
+ member.setAvatar(request.getAvatar());
+ }
+ }
+
+ return memberRepository.save(member)
+ .doOnSuccess(memberSyncer::sync)
+ .map(this::buildLoginResponse);
+ }
+
+ private PhoneLoginVO buildLoginResponse(Member member) {
+ boolean needCompleteInfo = member.getNickname() == null || member.getNickname().isEmpty();
+
+ List roles = new ArrayList<>();
+ String accessToken = jwtTokenProvider.generateToken(String.valueOf(member.getId()), member.getId(), roles);
+
+ log.info("JWT Token 生成成功, memberId: {}", member.getId());
+
+ PhoneLoginVO vo = new PhoneLoginVO();
+ vo.setMemberId(member.getId());
+ vo.setMemberNo(member.getMemberNo());
+ vo.setAccessToken(accessToken);
+ vo.setRefreshToken(accessToken);
+ vo.setExpiresIn(86400);
+ vo.setIsNewUser(member.getCreatedAt() == null ? false :
+ member.getCreatedAt().isAfter(LocalDateTime.now().minusMinutes(1)));
+ vo.setNeedCompleteInfo(needCompleteInfo);
+ vo.setNickname(member.getNickname());
+ vo.setAvatar(member.getAvatar());
+ vo.setPhone(member.getPhone() != null ? decryptPhone(member.getPhone()) : null);
+ return vo;
+ }
+
+ private String encryptPhone(String phoneNumber) {
+ try {
+ return AesUtil.encrypt(phoneNumber);
+ } catch (Exception e) {
+ log.error("手机号加密失败", e);
+ throw new SystemException(ErrorCode.SYSTEM_INTERNAL_ERROR, "手机号加密失败");
+ }
+ }
+
+ private String decryptPhone(String encryptedPhone) {
+ try {
+ return AesUtil.decrypt(encryptedPhone);
+ } catch (Exception e) {
+ log.error("手机号解密失败", e);
+ return null;
+ }
+ }
+}
diff --git a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/service/impl/SmsServiceImpl.java b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/service/impl/SmsServiceImpl.java
new file mode 100644
index 0000000..2162e13
--- /dev/null
+++ b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/service/impl/SmsServiceImpl.java
@@ -0,0 +1,148 @@
+package cn.novalon.gym.manage.auth.service.impl;
+
+import cn.hutool.core.util.RandomUtil;
+import cn.novalon.gym.manage.auth.config.SmsProperties;
+import cn.novalon.gym.manage.auth.service.SmsService;
+import cn.novalon.gym.manage.common.constant.RedisKeyConstants;
+import cn.novalon.gym.manage.common.util.RedisUtil;
+import com.aliyun.dysmsapi20170525.Client;
+import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
+import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
+import com.aliyun.teaopenapi.models.Config;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import reactor.core.publisher.Mono;
+
+import java.time.Duration;
+
+/**
+ * 短信服务实现类
+ *
+ * @author auto-generated
+ * @date 2026-06-20
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class SmsServiceImpl implements SmsService {
+
+ private final SmsProperties smsProperties;
+ private final RedisUtil redisUtil;
+
+ @Override
+ public boolean sendVerificationCode(String phone) {
+ log.info("发送短信验证码, phone: {}", phone);
+
+ try {
+ // 生成验证码
+ String code = generateCode();
+
+ // 发送短信
+ boolean sent = sendSms(phone, code);
+
+ if (sent) {
+ // 将验证码存入Redis,5分钟过期
+ String cacheKey = RedisKeyConstants.SMS_CODE + phone;
+ redisUtil.setWithExpire(cacheKey, code, smsProperties.getCodeExpireSeconds())
+ .doOnSuccess(result -> log.info("验证码已缓存, phone: {}, code: {}, expire: {}s",
+ phone, code, smsProperties.getCodeExpireSeconds()))
+ .block();
+
+ log.info("短信验证码发送成功, phone: {}", phone);
+ return true;
+ }
+
+ log.warn("短信验证码发送失败, phone: {}", phone);
+ return false;
+
+ } catch (Exception e) {
+ log.error("发送短信验证码异常, phone: {}", phone, e);
+ return false;
+ }
+ }
+
+ @Override
+ public boolean verifyCode(String phone, String code) {
+ log.info("验证短信验证码, phone: {}", phone);
+
+ try {
+ String cacheKey = RedisKeyConstants.SMS_CODE + phone;
+ String cachedCode = redisUtil.get(cacheKey, String.class).block();
+
+ if (cachedCode == null) {
+ log.warn("验证码已过期或不存在, phone: {}", phone);
+ return false;
+ }
+
+ if (cachedCode.equals(code)) {
+ // 验证成功后删除验证码
+ redisUtil.delete(cacheKey).block();
+ log.info("验证码验证成功, phone: {}", phone);
+ return true;
+ }
+
+ log.warn("验证码不匹配, phone: {}, input: {}, cached: {}", phone, code, cachedCode);
+ return false;
+
+ } catch (Exception e) {
+ log.error("验证短信验证码异常, phone: {}", phone, e);
+ return false;
+ }
+ }
+
+ @Override
+ public String getVerificationCode(String phone) {
+ try {
+ String cacheKey = RedisKeyConstants.SMS_CODE + phone;
+ return redisUtil.get(cacheKey, String.class).block();
+ } catch (Exception e) {
+ log.error("获取验证码异常, phone: {}", phone, e);
+ return null;
+ }
+ }
+
+ /**
+ * 生成验证码
+ */
+ private String generateCode() {
+ return RandomUtil.randomNumbers(smsProperties.getCodeLength());
+ }
+
+ /**
+ * 发送短信
+ */
+ private boolean sendSms(String phone, String code) throws Exception {
+ log.info("调用阿里云短信API发送短信, phone: {}, code: {}", phone, code);
+
+ // 创建阿里云短信客户端
+ Config config = new Config()
+ .setAccessKeyId(smsProperties.getAccessKeyId())
+ .setAccessKeySecret(smsProperties.getAccessKeySecret())
+ .setEndpoint("dysmsapi.aliyuncs.com");
+
+ Client client = new Client(config);
+
+ // 构建发送短信请求
+ SendSmsRequest request = new SendSmsRequest()
+ .setPhoneNumbers(phone)
+ .setSignName(smsProperties.getSignName())
+ .setTemplateCode(smsProperties.getTemplateCode())
+ .setTemplateParam("{\"code\":\"" + code + "\"}");
+
+ // 发送短信
+ SendSmsResponse response = client.sendSms(request);
+
+ log.info("阿里云短信API响应, phone: {}, code: {}, message: {}",
+ phone, response.getBody().getCode(), response.getBody().getMessage());
+
+ // 判断发送结果
+ if ("OK".equals(response.getBody().getCode())) {
+ return true;
+ }
+
+ log.error("阿里云短信发送失败, phone: {}, code: {}, message: {}",
+ phone, response.getBody().getCode(), response.getBody().getMessage());
+ return false;
+ }
+}
diff --git a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/vo/PhoneLoginVO.java b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/vo/PhoneLoginVO.java
new file mode 100644
index 0000000..f5f4651
--- /dev/null
+++ b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/vo/PhoneLoginVO.java
@@ -0,0 +1,36 @@
+package cn.novalon.gym.manage.auth.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 手机号一键登录响应VO
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class PhoneLoginVO {
+
+ private Long memberId;
+
+ private String memberNo;
+
+ private String phone;
+
+ private String accessToken;
+
+ private String refreshToken;
+
+ private Integer expiresIn;
+
+ private Boolean isNewUser;
+
+ private Boolean needCompleteInfo;
+
+ private String nickname;
+
+ private String avatar;
+}
\ No newline at end of file
diff --git a/gym-manage-api/gym-auth/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/gym-manage-api/gym-auth/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 0000000..216a5f9
--- /dev/null
+++ b/gym-manage-api/gym-auth/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1,5 @@
+cn.novalon.gym.manage.auth.handler.PhoneAuthHandler
+cn.novalon.gym.manage.auth.service.impl.PhoneAuthServiceImpl
+cn.novalon.gym.manage.auth.service.impl.SmsServiceImpl
+cn.novalon.gym.manage.auth.config.AuthConfig
+cn.novalon.gym.manage.auth.config.SmsProperties
\ No newline at end of file
diff --git a/gym-manage-api/gym-checkIn/spotbugs-exclude.xml b/gym-manage-api/gym-checkIn/spotbugs-exclude.xml
new file mode 100644
index 0000000..369b0aa
--- /dev/null
+++ b/gym-manage-api/gym-checkIn/spotbugs-exclude.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/config/WebSocketConfig.java b/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/config/WebSocketConfig.java
new file mode 100644
index 0000000..e4d6ea1
--- /dev/null
+++ b/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/config/WebSocketConfig.java
@@ -0,0 +1,43 @@
+package cn.novalon.gym.manage.checkIn.config;
+
+import cn.novalon.gym.manage.checkIn.websocket.MyWebSocketHandler;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.reactive.HandlerMapping;
+import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping;
+import org.springframework.web.reactive.socket.WebSocketHandler;
+import org.springframework.web.reactive.socket.server.support.WebSocketHandlerAdapter;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Configuration
+public class WebSocketConfig {
+
+ @Autowired
+ private MyWebSocketHandler myWebSocketHandler;
+
+ /**
+ * 注册 WebSocket 路由映射
+ * 路径对应前端连接的 ws://xxx/webSocket/checkIn
+ */
+ @Bean
+ public HandlerMapping webSocketMapping() {
+ Map map = new HashMap<>();
+ map.put("/webSocket/checkIn", myWebSocketHandler);
+
+ SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
+ mapping.setUrlMap(map);
+ mapping.setOrder(10); // 设置优先级
+ return mapping;
+ }
+
+ /**
+ * 注册 WebSocket 处理器适配器(必须)
+ */
+ @Bean
+ public WebSocketHandlerAdapter handlerAdapter() {
+ return new WebSocketHandlerAdapter();
+ }
+}
\ No newline at end of file
diff --git a/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/entity/SignInRecord.java b/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/entity/SignInRecord.java
index 4946012..63cf034 100644
--- a/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/entity/SignInRecord.java
+++ b/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/entity/SignInRecord.java
@@ -206,4 +206,4 @@ public class SignInRecord {
public void restore() {
this.isDelete = false;
}
-}
\ No newline at end of file
+}
diff --git a/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/handler/CheckInHandler.java b/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/handler/CheckInHandler.java
index ceae33f..16716f9 100644
--- a/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/handler/CheckInHandler.java
+++ b/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/handler/CheckInHandler.java
@@ -2,6 +2,8 @@ package cn.novalon.gym.manage.checkIn.handler;
import cn.novalon.gym.manage.checkIn.service.impl.CheckServiceImpl;
import cn.novalon.gym.manage.checkIn.websocket.MyWebSocketHandler;
+import cn.novalon.gym.manage.checkIn.vo.SignInRecordVO;
+import cn.novalon.gym.manage.checkIn.vo.SignInStatsVO;
import cn.novalon.gym.manage.sys.util.AuthUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -12,6 +14,7 @@ import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
+import cn.novalon.gym.manage.checkIn.websocket.MyWebSocketHandler;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
@@ -36,6 +39,7 @@ public class CheckInHandler {
public Mono checkIn(ServerRequest request) {
Long memberId = authUtil.getMemberIdOrThrow(request);
+
return request.bodyToMono(Map.class)
.flatMap(body -> {
String qrContent = (String) body.get("qrContent");
@@ -190,4 +194,4 @@ public class CheckInHandler {
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(Map.of("code", 200, "message", "success", "data", stats)));
}
-}
\ No newline at end of file
+}
diff --git a/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/service/ICheckInService.java b/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/service/ICheckInService.java
index 5b4894f..e162423 100644
--- a/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/service/ICheckInService.java
+++ b/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/service/ICheckInService.java
@@ -78,4 +78,4 @@ public interface ICheckInService {
* @return 签到统计VO
*/
Mono getDailySignInStats(LocalDate date);
-}
\ No newline at end of file
+}
diff --git a/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/service/impl/CheckServiceImpl.java b/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/service/impl/CheckServiceImpl.java
index ad41210..e041842 100644
--- a/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/service/impl/CheckServiceImpl.java
+++ b/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/service/impl/CheckServiceImpl.java
@@ -2,6 +2,8 @@ package cn.novalon.gym.manage.checkIn.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.json.JSONUtil;
+import cn.hutool.extra.qrcode.QrCodeUtil;
+import cn.hutool.extra.qrcode.QrConfig;
import cn.novalon.gym.manage.checkIn.config.QRCodeConfig;
import cn.novalon.gym.manage.checkIn.constant.QRRedisKey;
import cn.novalon.gym.manage.checkIn.entity.SignInRecord;
@@ -19,8 +21,6 @@ import cn.novalon.gym.manage.member.entity.MemberCardRecord;
import cn.novalon.gym.manage.member.enums.MemberCardType;
import cn.novalon.gym.manage.member.repository.MemberCardRecordRepository;
import cn.novalon.gym.manage.member.repository.MemberCardRepository;
-import cn.hutool.extra.qrcode.QrCodeUtil;
-import cn.hutool.extra.qrcode.QrConfig;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
diff --git a/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/websocket/MyWebSocketHandler.java b/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/websocket/MyWebSocketHandler.java
index 6b94625..f3bd900 100644
--- a/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/websocket/MyWebSocketHandler.java
+++ b/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/websocket/MyWebSocketHandler.java
@@ -199,4 +199,4 @@ public class MyWebSocketHandler implements WebSocketHandler {
cleanupTimeoutConnections();
return qrContentToSink.size();
}
-}
\ No newline at end of file
+}
diff --git a/gym-manage-api/gym-dataCount/pom.xml b/gym-manage-api/gym-dataCount/pom.xml
index 9e8ddec..32e1c3f 100644
--- a/gym-manage-api/gym-dataCount/pom.xml
+++ b/gym-manage-api/gym-dataCount/pom.xml
@@ -100,8 +100,20 @@
- org.springframework.boot
- spring-boot-maven-plugin
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.11.0
+
+ 21
+ 21
+
+
+ org.projectlombok
+ lombok
+ ${lombok.version}
+
+
+
diff --git a/gym-manage-api/gym-groupCourse/pom.xml b/gym-manage-api/gym-groupCourse/pom.xml
index bfafbbb..12e0171 100644
--- a/gym-manage-api/gym-groupCourse/pom.xml
+++ b/gym-manage-api/gym-groupCourse/pom.xml
@@ -35,11 +35,6 @@
manage-common
${project.version}
-
- cn.novalon.gym.manage
- manage-sys
- ${project.version}
-
cn.novalon.gym.manage
manage-db
@@ -86,8 +81,8 @@
gym-member
${project.version}
-
-
+
+
com.google.zxing
core
@@ -98,20 +93,20 @@
javase
3.5.3
-
com.aliyun.oss
aliyun-sdk-oss
- 3.17.1
+ 3.17.4
- org.springframework.boot
- spring-boot-maven-plugin
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 3.4.2
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 391e27f..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
@@ -14,24 +14,24 @@ import java.time.format.DateTimeFormatter;
* 阿里云OSS工具类
*/
public class OSSUtil {
-
+
private static final Logger logger = LoggerFactory.getLogger(OSSUtil.class);
-
+
// OSS配置信息
private static final String ENDPOINT = "oss-cn-beijing.aliyuncs.com";
private static final String ACCESS_KEY_ID = "LTAI5t9TFh9Vayeahz45kZjg";
private static final String ACCESS_KEY_SECRET = "zD6NlCeH5UhjBs4vnQVqn8Ksi3CaZz";
private static final String BUCKET_NAME = "ycc-filesaver";
-
+
// OSS访问地址前缀
private static final String OSS_URL_PREFIX = "https://" + BUCKET_NAME + "." + ENDPOINT + "/";
-
+
// 文件存储目录
private static final String QRCODE_DIR = "qrcode/";
-
+
/**
* 上传文件到阿里云OSS
- *
+ *
* @param localFilePath 本地文件路径
* @param fileName 文件名(不含路径)
* @return OSS访问地址
@@ -41,22 +41,22 @@ public class OSSUtil {
try {
// 创建OSS客户端
ossClient = new OSSClientBuilder().build(ENDPOINT, ACCESS_KEY_ID, ACCESS_KEY_SECRET);
-
+
// 构建OSS文件路径:qrcode/2026/06/18/xxx.png
String datePath = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));
String ossFilePath = QRCODE_DIR + datePath + "/" + fileName;
-
+
// 创建上传请求
PutObjectRequest putObjectRequest = new PutObjectRequest(BUCKET_NAME, ossFilePath, new File(localFilePath));
-
+
// 上传文件
ossClient.putObject(putObjectRequest);
-
+
// 构建访问地址
String accessUrl = OSS_URL_PREFIX + ossFilePath;
-
+
logger.info("文件上传到OSS成功: localPath={}, ossUrl={}", localFilePath, accessUrl);
-
+
return accessUrl;
} catch (Exception e) {
logger.error("文件上传到OSS失败 - localPath: {}, error: {}", localFilePath, e.getMessage(), e);
@@ -67,10 +67,10 @@ public class OSSUtil {
}
}
}
-
+
/**
* 上传文件到阿里云OSS(自定义存储路径)
- *
+ *
* @param localFilePath 本地文件路径
* @param ossDirectory OSS存储目录
* @param fileName 文件名(不含路径)
@@ -81,21 +81,21 @@ public class OSSUtil {
try {
// 创建OSS客户端
ossClient = new OSSClientBuilder().build(ENDPOINT, ACCESS_KEY_ID, ACCESS_KEY_SECRET);
-
+
// 构建OSS文件路径
String ossFilePath = ossDirectory + fileName;
-
+
// 创建上传请求
PutObjectRequest putObjectRequest = new PutObjectRequest(BUCKET_NAME, ossFilePath, new File(localFilePath));
-
+
// 上传文件
ossClient.putObject(putObjectRequest);
-
+
// 构建访问地址
String accessUrl = OSS_URL_PREFIX + ossFilePath;
-
+
logger.info("文件上传到OSS成功: localPath={}, ossUrl={}", localFilePath, accessUrl);
-
+
return accessUrl;
} catch (Exception e) {
logger.error("文件上传到OSS失败 - localPath: {}, error: {}", localFilePath, e.getMessage(), e);
diff --git a/gym-manage-api/gym-member/spotbugs-exclude.xml b/gym-manage-api/gym-member/spotbugs-exclude.xml
new file mode 100644
index 0000000..369b0aa
--- /dev/null
+++ b/gym-manage-api/gym-member/spotbugs-exclude.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/handler/MemberCardRecordHandler.java b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/handler/MemberCardRecordHandler.java
index 6d5b091..bc3669d 100644
--- a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/handler/MemberCardRecordHandler.java
+++ b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/handler/MemberCardRecordHandler.java
@@ -32,6 +32,7 @@ public class MemberCardRecordHandler {
@Operation(summary = "购买会员卡", description = "支持时长卡、次卡、储值卡,自动设置到期提醒")
public Mono purchaseCard(ServerRequest request) {
+
return request.bodyToMono(PurchaseRequest.class)
.flatMap(body -> memberCardService.purchaseCard(
body.getMemberId(),
diff --git a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/handler/MemberHandler.java b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/handler/MemberHandler.java
index baa75f7..977ca11 100644
--- a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/handler/MemberHandler.java
+++ b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/handler/MemberHandler.java
@@ -1,5 +1,6 @@
package cn.novalon.gym.manage.member.handler;
+import cn.novalon.gym.manage.common.exception.NotFoundException;
import cn.novalon.gym.manage.member.config.WechatProperties;
import cn.novalon.gym.manage.member.dto.AdminUpdatePhoneDto;
import cn.novalon.gym.manage.member.dto.SearchMemberDto;
@@ -16,6 +17,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.math.NumberUtils;
+import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
@@ -50,7 +52,11 @@ public class MemberHandler {
return memberService.getMemberInfo(memberId)
.flatMap(info -> ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
- .bodyValue(info));
+ .bodyValue(info))
+ .onErrorResume(NotFoundException.class, e ->
+ ServerResponse.status(HttpStatus.NOT_FOUND)
+ .contentType(MediaType.APPLICATION_JSON)
+ .bodyValue(java.util.Map.of("code", 404, "message", e.getMessage())));
}
@Operation(summary = "更新会员信息", description = "更新会员昵称、性别、生日、头像、地址等信息")
diff --git a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/MemberCardServiceImpl.java b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/MemberCardServiceImpl.java
index 070a938..3d87017 100644
--- a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/MemberCardServiceImpl.java
+++ b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/MemberCardServiceImpl.java
@@ -1,5 +1,6 @@
package cn.novalon.gym.manage.member.service.impl;
+import cn.novalon.gym.manage.common.util.RedisUtil;
import cn.novalon.gym.manage.member.entity.MemberCard;
import cn.novalon.gym.manage.member.entity.MemberCardRecord;
import cn.novalon.gym.manage.member.entity.MemberCardTransaction;
@@ -15,7 +16,6 @@ import cn.novalon.gym.manage.member.repository.MemberCardRecordRepository;
import cn.novalon.gym.manage.member.repository.MemberCardRepository;
import cn.novalon.gym.manage.member.service.IMemberCardService;
import cn.novalon.gym.manage.member.service.IMemberCardTransactionService;
-import cn.novalon.gym.manage.common.util.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
diff --git a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/MemberServiceImpl.java b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/MemberServiceImpl.java
index e40ff5f..38b7083 100644
--- a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/MemberServiceImpl.java
+++ b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/MemberServiceImpl.java
@@ -65,21 +65,37 @@ public class MemberServiceImpl implements MemberService {
public Mono getMemberInfo(Long memberId) {
String cacheKey = MEMBER_INFO_CACHE_PREFIX + memberId;
+ // 先查缓存
return redisUtil.get(cacheKey, MemberInfoVO.class)
.flatMap(cached -> {
if (cached != null) {
log.debug("从缓存获取会员信息, memberId: {}", memberId);
return Mono.just(cached);
}
- return memberRepository.findById(memberId)
- .map(this::buildMemberInfoResponse)
- .flatMap(vo -> redisUtil.setWithExpire(cacheKey, vo, CACHE_EXPIRE_SECONDS)
- .then(Mono.just(vo)))
- .switchIfEmpty(Mono.error(() -> {
- log.error("会员不存在: memberId={}", memberId);
- throw new NotFoundException(ErrorCode.NOT_FOUND_USER, "会员不存在");
- }));
- });
+ // 缓存没有,查数据库
+ return queryFromDatabaseAndCache(memberId, cacheKey);
+ })
+ .switchIfEmpty(Mono.defer(() -> {
+ // 缓存返回null,查数据库
+ return queryFromDatabaseAndCache(memberId, cacheKey);
+ }));
+ }
+
+ /**
+ * 从数据库查询并更新缓存
+ */
+ private Mono queryFromDatabaseAndCache(Long memberId, String cacheKey) {
+ return memberRepository.findById(memberId)
+ .map(this::buildMemberInfoResponse)
+ .flatMap(vo -> {
+ // 查询到数据后更新缓存
+ return redisUtil.setWithExpire(cacheKey, vo, CACHE_EXPIRE_SECONDS)
+ .then(Mono.just(vo));
+ })
+ .switchIfEmpty(Mono.error(() -> {
+ log.error("会员不存在: memberId={}", memberId);
+ throw new NotFoundException(ErrorCode.NOT_FOUND_USER, "会员不存在");
+ }));
}
@Override
diff --git a/gym-manage-api/manage-app/pom.xml b/gym-manage-api/manage-app/pom.xml
index 5682d05..6e95ea5 100644
--- a/gym-manage-api/manage-app/pom.xml
+++ b/gym-manage-api/manage-app/pom.xml
@@ -53,6 +53,11 @@
gym-dataCount
${project.version}
+
+ cn.novalon.gym.manage
+ gym-auth
+ ${project.version}
+
org.springframework.boot
diff --git a/gym-manage-api/manage-app/src/main/java/cn/novalon/gym/manage/app/ManageApplication.java b/gym-manage-api/manage-app/src/main/java/cn/novalon/gym/manage/app/ManageApplication.java
index 7d11ca4..c0c53cd 100644
--- a/gym-manage-api/manage-app/src/main/java/cn/novalon/gym/manage/app/ManageApplication.java
+++ b/gym-manage-api/manage-app/src/main/java/cn/novalon/gym/manage/app/ManageApplication.java
@@ -10,14 +10,13 @@ import org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDeta
import org.springframework.context.annotation.Bean;
import org.springframework.data.elasticsearch.repository.config.EnableReactiveElasticsearchRepositories;
import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories;
-import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.web.server.WebFilter;
import java.util.List;
@SpringBootApplication(scanBasePackages = "cn.novalon.gym.manage", exclude = {
ReactiveUserDetailsServiceAutoConfiguration.class })
-@EnableScheduling
+//@EnableScheduling
@EnableR2dbcRepositories(basePackages = {
"cn.novalon.gym.manage.db.dao",
"cn.novalon.gym.manage.sys.audit.repository" ,
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 e75fcf0..482d931 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
@@ -4,6 +4,7 @@ package cn.novalon.gym.manage.app.config;
import cn.novalon.gym.manage.checkIn.handler.CheckInHandler;
import cn.novalon.gym.manage.datacount.handler.DataStatisticsHandler;
import cn.novalon.gym.manage.file.handler.SysFileHandler;
+import cn.novalon.gym.manage.auth.handler.PhoneAuthHandler;
import cn.novalon.gym.manage.groupcourse.handler.GroupCourseBookingHandler;
import cn.novalon.gym.manage.groupcourse.handler.GroupCourseHandler;
import cn.novalon.gym.manage.groupcourse.handler.GroupCourseRecommendHandler;
@@ -76,7 +77,8 @@ public class SystemRouter {
GroupCourseTypeHandler groupCourseTypeHandler,
CourseLabelHandler courseLabelHandler,
CheckInHandler checkInHandler,
- DataStatisticsHandler dataStatisticsHandler) {
+ DataStatisticsHandler dataStatisticsHandler,
+ PhoneAuthHandler phoneAuthHandler) {
return route()
// ========== 诊断路由 ==========
@@ -192,10 +194,13 @@ public class SystemRouter {
// ========== 消息路由 ==========
.GET("/api/messages/user/{userId}", messageHandler::getMessagesByUser)
+ .GET("/api/messages/user/{userId}/page", messageHandler::getMessagesByUserPage)
.GET("/api/messages/user/{userId}/unread", messageHandler::getUnreadCount)
.GET("/api/messages/user/{userId}/unread/list", messageHandler::getUnreadList)
+ .GET("/api/messages/user/{userId}/unread/page", messageHandler::getUnreadMessagesPage)
.POST("/api/messages", messageHandler::createMessage)
.PUT("/api/messages/{id}/read", messageHandler::markAsRead)
+ .PUT("/api/messages/user/{userId}/read", messageHandler::markAllAsRead)
.DELETE("/api/messages/{id}", messageHandler::deleteMessage)
// ========== 文件路由 ==========
@@ -218,6 +223,11 @@ public class SystemRouter {
.PUT("/api/permissions/{id}", permissionHandler::updatePermission)
.DELETE("/api/permissions/{id}", permissionHandler::deletePermission)
+ // ========== 手机号认证路由 ==========
+ .POST("/api/auth/phone/one-click-login", phoneAuthHandler::oneClickLogin)
+ .POST("/api/auth/phone/send-code", phoneAuthHandler::sendSmsCode)
+ .POST("/api/auth/phone/code-login", phoneAuthHandler::codeLogin)
+
// ========== 会员模块路由 - 微信认证 ==========
.POST("/api/member/auth/miniapp/login", wechatAuthHandler::miniappLogin)
.GET("/api/member/auth/mp/callback", wechatAuthHandler::verifyMpSignature)
@@ -349,6 +359,7 @@ public class SystemRouter {
.GET("/api/datacount/signin", dataStatisticsHandler::getSignInStatistics)
.GET("/api/datacount/history", dataStatisticsHandler::queryHistoricalStatistics)
.GET("/api/datacount/export", dataStatisticsHandler::exportStatistics)
+
.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 33e428e..46b6a66 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:55432/manage_system
- username: novalon
- password: novalon123
+ url: r2dbc:postgresql://localhost:5432/manage_system
+ username: postgres
+ password: 123456
pool:
initial-size: 5
max-size: 20
@@ -12,10 +12,10 @@ spring:
max-life-time: 30m
acquire-timeout: 3s
flyway:
- url: jdbc:postgresql://localhost:55432/manage_system
- user: novalon
- password: novalon123
- enabled: true
+ url: jdbc:postgresql://localhost:5432/manage_system
+ user: postgres
+ password: 123456
+ enabled: false
locations: classpath:db/migration
baseline-on-migrate: true
validate-on-migrate: true
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 73b95b3..e1035f1 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
@@ -4,8 +4,8 @@ spring:
activate:
on-profile: local
r2dbc:
- url: r2dbc:postgresql://localhost:55432/manage_system
- username: novalon
+ url: r2dbc:postgresql://localhost:5432/manage_system
+ username: postgres
password: 123456
pool:
initial-size: 5
@@ -14,8 +14,8 @@ spring:
max-life-time: 30m
acquire-timeout: 3s
datasource:
- url: jdbc:postgresql://localhost:55432/manage_system
- username: novalon
+ url: jdbc:postgresql://localhost:5432/manage_system
+ username: postgres
password: 123456
driver-class-name: org.postgresql.Driver
flyway:
diff --git a/gym-manage-api/manage-app/src/main/resources/application-test.yml b/gym-manage-api/manage-app/src/main/resources/application-test.yml
index 0b1fba2..d131944 100644
--- a/gym-manage-api/manage-app/src/main/resources/application-test.yml
+++ b/gym-manage-api/manage-app/src/main/resources/application-test.yml
@@ -5,9 +5,9 @@ spring:
application:
name: manage-app
r2dbc:
- url: r2dbc:postgresql://localhost:55432/manage_system
- username: novalon
- password: novalon123
+ url: r2dbc:postgresql://localhost:5432/manage_system
+ username: postgres
+ password: 123456
pool:
initial-size: 5
max-size: 20
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 4fee231..e1fe965 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:55432}/${DB_NAME:manage_system}
- username: ${DB_USERNAME:novalon}
- password: ${DB_PASSWORD:novalon123}
+ url: r2dbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_NAME:manage_system}
+ username: ${DB_USERNAME:postgres}
+ password: ${DB_PASSWORD:123456}
pool:
initial-size: 10
max-size: 50
@@ -25,12 +25,12 @@ spring:
max-life-time: 1h
acquire-timeout: 5s
datasource:
- url: jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:55432}/${DB_NAME:manage_system}
- username: ${DB_USERNAME:novalon}
- password: ${DB_PASSWORD:novalon123}
+ url: jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_NAME:manage_system}
+ username: ${DB_USERNAME:postgres}
+ password: ${DB_PASSWORD:123456}
driver-class-name: org.postgresql.Driver
flyway:
- enabled: true
+ enabled: false
locations: classpath:db/migration
baseline-on-migrate: true
baseline-version: 0
@@ -54,7 +54,7 @@ spring:
profiles:
active: dev
config:
- import: classpath:member-config.yml
+ import: classpath:keys.properties,classpath:member-config.yml
@@ -94,3 +94,13 @@ springdoc:
show-actuator: false
default-consumes-media-type: application/json
default-produces-media-type: application/json
+
+alibaba:
+ cloud:
+ sms:
+ access-key-id: ${ALIBABA_ACCESS_KEY_ID:}
+ access-key-secret: ${ALIBABA_ACCESS_KEY_SECRET:}
+ sign-name: ${ALIBABA_SMS_SIGN_NAME:}
+ template-code: ${ALIBABA_SMS_TEMPLATE_CODE:}
+ code-length: 6
+ code-expire-seconds: 300
diff --git a/gym-manage-api/manage-app/src/main/resources/keys.properties b/gym-manage-api/manage-app/src/main/resources/keys.properties
new file mode 100644
index 0000000..086c9b0
--- /dev/null
+++ b/gym-manage-api/manage-app/src/main/resources/keys.properties
@@ -0,0 +1,2 @@
+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
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 a76b243..05ce7cb 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
@@ -21,4 +21,17 @@ wechat:
spring:
elasticsearch:
- uris: http://localhost:9200
\ No newline at end of file
+ uris: http://localhost:9200
+
+payment:
+ huifu:
+ sys-id: "6666000207573586"
+ product-id: "XLSISV"
+ huifu-id: "6666000207573586"
+ acct-id: "F28226571"
+ private-key: ${huifu.private-key}
+ public-key: ${huifu.public-key}
+ create-url: "https://api.huifu.com/v4/trade/payment/create"
+ 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"
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 053180d..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,4 +61,13 @@ public final class RedisKeyConstants {
* appType: miniapp(小程序), mp(公众号)
*/
public static final String WECHAT_ACCESS_TOKEN = "wechat:access_token:";
+
+ // ==================== 认证模块 ====================
+
+ /**
+ * 手机短信验证码缓存
+ * 格式:sms:code:{phone}
+ * 用途:存储登录/注册等场景的短信验证码
+ */
+ public static final String SMS_CODE = "sms:code:";
}
\ No newline at end of file
diff --git a/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/exception/ErrorCode.java b/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/exception/ErrorCode.java
index baea580..cd05077 100644
--- a/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/exception/ErrorCode.java
+++ b/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/exception/ErrorCode.java
@@ -7,6 +7,7 @@ public class ErrorCode {
public static final String PERMISSION_PREFIX = "PERMISSION_";
public static final String CONFLICT_PREFIX = "CONFLICT_";
public static final String SYSTEM_PREFIX = "SYSTEM_";
+ public static final String AUTH_PREFIX = "AUTH_";
public static final String VALIDATION_REQUIRED = VALIDATION_PREFIX + "001";
public static final String VALIDATION_INVALID_FORMAT = VALIDATION_PREFIX + "002";
@@ -29,4 +30,7 @@ public class ErrorCode {
public static final String SYSTEM_INTERNAL_ERROR = SYSTEM_PREFIX + "001";
public static final String SYSTEM_DATABASE_ERROR = SYSTEM_PREFIX + "002";
public static final String SYSTEM_NETWORK_ERROR = SYSTEM_PREFIX + "003";
+
+ public static final String AUTH_PHONE_ERROR = AUTH_PREFIX + "001";
+ public static final String AUTH_CODE_ERROR = AUTH_PREFIX + "002";
}
diff --git a/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/entity/SysUserMessageEntity.java b/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/entity/SysUserMessageEntity.java
index b443460..9c3d13c 100644
--- a/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/entity/SysUserMessageEntity.java
+++ b/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/entity/SysUserMessageEntity.java
@@ -1,5 +1,6 @@
package cn.novalon.gym.manage.db.entity;
+import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.Table;
@@ -21,10 +22,10 @@ public class SysUserMessageEntity {
@Column("user_id")
private Long userId;
- @Column("title")
+ @Column("message_title")
private String title;
- @Column("content")
+ @Column("message_content")
private String content;
@Column("message_type")
@@ -33,9 +34,13 @@ public class SysUserMessageEntity {
@Column("is_read")
private String isRead;
- @Column("create_time")
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @Column("created_at")
private LocalDateTime createTime;
+ @Column("deleted_at")
+ private LocalDateTime deletedAt;
+
public Long getId() {
return id;
}
@@ -91,4 +96,12 @@ public class SysUserMessageEntity {
public void setCreateTime(LocalDateTime createTime) {
this.createTime = createTime;
}
+
+ public LocalDateTime getDeletedAt() {
+ return deletedAt;
+ }
+
+ public void setDeletedAt(LocalDateTime deletedAt) {
+ this.deletedAt = deletedAt;
+ }
}
diff --git a/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/repository/SysUserMessageRepository.java b/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/repository/SysUserMessageRepository.java
index 7548b7b..c2d6ac4 100644
--- a/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/repository/SysUserMessageRepository.java
+++ b/gym-manage-api/manage-db/src/main/java/cn/novalon/gym/manage/db/repository/SysUserMessageRepository.java
@@ -1,5 +1,7 @@
package cn.novalon.gym.manage.db.repository;
+import cn.novalon.gym.manage.common.dto.PageRequest;
+import cn.novalon.gym.manage.common.dto.PageResponse;
import cn.novalon.gym.manage.notify.core.domain.SysUserMessage;
import cn.novalon.gym.manage.notify.core.repository.ISysUserMessageRepository;
import cn.novalon.gym.manage.db.converter.SysUserMessageConverter;
@@ -13,6 +15,8 @@ import org.springframework.stereotype.Repository;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
+import java.util.List;
+
/**
* 用户消息仓储实现类
*
@@ -75,6 +79,87 @@ public class SysUserMessageRepository implements ISysUserMessageRepository {
return r2dbcEntityTemplate.count(dbQuery, SysUserMessageEntity.class);
}
+ @Override
+ public Mono> findByUserIdPage(Long userId, PageRequest pageRequest) {
+ int page = pageRequest.getPage();
+ int size = pageRequest.getSize();
+ String sort = pageRequest.getSort();
+ String order = pageRequest.getOrder();
+
+ Sort sortObj = Sort.unsorted();
+ if (sort != null && !sort.isEmpty()) {
+ sortObj = Sort.by(Sort.Direction.fromString(order), sort);
+ }
+
+ org.springframework.data.domain.PageRequest pageable = org.springframework.data.domain.PageRequest.of(page, size, sortObj);
+
+ SysUserMessageQueryCriteria criteria = new SysUserMessageQueryCriteria();
+ criteria.setUserId(userId);
+ org.springframework.data.relational.core.query.Query dbQuery = QueryUtil.getQuery(criteria);
+
+ return r2dbcEntityTemplate.select(SysUserMessageEntity.class)
+ .matching(dbQuery.with(pageable))
+ .all()
+ .collectList()
+ .zipWith(r2dbcEntityTemplate.count(dbQuery, SysUserMessageEntity.class))
+ .map(tuple -> {
+ long total = tuple.getT2();
+ int totalPages = (int) Math.ceil((double) total / size);
+ List messageList = tuple.getT1().stream()
+ .map(sysUserMessageConverter::toDomain)
+ .toList();
+ return new PageResponse<>(messageList, totalPages, total, page, size);
+ });
+ }
+
+ @Override
+ public Mono> findByUserIdAndIsReadPage(Long userId, String isRead, PageRequest pageRequest) {
+ int page = pageRequest.getPage();
+ int size = pageRequest.getSize();
+ String sort = pageRequest.getSort();
+ String order = pageRequest.getOrder();
+
+ Sort sortObj = Sort.unsorted();
+ if (sort != null && !sort.isEmpty()) {
+ sortObj = Sort.by(Sort.Direction.fromString(order), sort);
+ }
+
+ org.springframework.data.domain.PageRequest pageable = org.springframework.data.domain.PageRequest.of(page, size, sortObj);
+
+ SysUserMessageQueryCriteria criteria = new SysUserMessageQueryCriteria();
+ criteria.setUserId(userId);
+ criteria.setIsRead(isRead);
+ org.springframework.data.relational.core.query.Query dbQuery = QueryUtil.getQuery(criteria);
+
+ return r2dbcEntityTemplate.select(SysUserMessageEntity.class)
+ .matching(dbQuery.with(pageable))
+ .all()
+ .collectList()
+ .zipWith(r2dbcEntityTemplate.count(dbQuery, SysUserMessageEntity.class))
+ .map(tuple -> {
+ long total = tuple.getT2();
+ int totalPages = (int) Math.ceil((double) total / size);
+ List messageList = tuple.getT1().stream()
+ .map(sysUserMessageConverter::toDomain)
+ .toList();
+ return new PageResponse<>(messageList, totalPages, total, page, size);
+ });
+ }
+
+ @Override
+ public Mono markAllAsReadByUserId(Long userId) {
+ org.springframework.data.relational.core.query.Update update = org.springframework.data.relational.core.query.Update.update("is_read", "1");
+
+ org.springframework.data.relational.core.query.Query query = org.springframework.data.relational.core.query.Query.query(
+ org.springframework.data.relational.core.query.Criteria.where("user_id").is(userId)
+ .and("deleted_at").isNull()
+ );
+
+ return r2dbcEntityTemplate.update(SysUserMessageEntity.class)
+ .matching(query)
+ .apply(update);
+ }
+
@Override
public Mono save(SysUserMessage message) {
SysUserMessageEntity entity = sysUserMessageConverter.toEntity(message);
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/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 54c4a4e..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
@@ -61,7 +61,9 @@ public class JwtAuthenticationFilter extends AbstractGatewayFilterFactory countByUserIdAndIsRead(Long userId, String isRead);
+ Mono> findByUserIdPage(Long userId, PageRequest pageRequest);
+
+ Mono> findByUserIdAndIsReadPage(Long userId, String isRead, PageRequest pageRequest);
+
+ Mono markAllAsReadByUserId(Long userId);
+
Mono save(SysUserMessage message);
Mono findById(Long id);
diff --git a/gym-manage-api/manage-notify/src/main/java/cn/novalon/gym/manage/notify/core/service/ISysUserMessageService.java b/gym-manage-api/manage-notify/src/main/java/cn/novalon/gym/manage/notify/core/service/ISysUserMessageService.java
index 587da34..bc79f0f 100644
--- a/gym-manage-api/manage-notify/src/main/java/cn/novalon/gym/manage/notify/core/service/ISysUserMessageService.java
+++ b/gym-manage-api/manage-notify/src/main/java/cn/novalon/gym/manage/notify/core/service/ISysUserMessageService.java
@@ -1,5 +1,7 @@
package cn.novalon.gym.manage.notify.core.service;
+import cn.novalon.gym.manage.common.dto.PageRequest;
+import cn.novalon.gym.manage.common.dto.PageResponse;
import cn.novalon.gym.manage.notify.core.domain.SysUserMessage;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -12,6 +14,12 @@ public interface ISysUserMessageService {
Flux getUnreadMessages(Long userId);
+ Mono> getMessagesByUserPage(Long userId, PageRequest pageRequest);
+
+ Mono> getUnreadMessagesPage(Long userId, PageRequest pageRequest);
+
+ Mono markAllAsRead(Long userId);
+
Mono createMessage(SysUserMessage message);
Mono markAsRead(Long id);
diff --git a/gym-manage-api/manage-notify/src/main/java/cn/novalon/gym/manage/notify/core/service/impl/SysUserMessageServiceImpl.java b/gym-manage-api/manage-notify/src/main/java/cn/novalon/gym/manage/notify/core/service/impl/SysUserMessageServiceImpl.java
index 664c565..19db56c 100644
--- a/gym-manage-api/manage-notify/src/main/java/cn/novalon/gym/manage/notify/core/service/impl/SysUserMessageServiceImpl.java
+++ b/gym-manage-api/manage-notify/src/main/java/cn/novalon/gym/manage/notify/core/service/impl/SysUserMessageServiceImpl.java
@@ -1,5 +1,7 @@
package cn.novalon.gym.manage.notify.core.service.impl;
+import cn.novalon.gym.manage.common.dto.PageRequest;
+import cn.novalon.gym.manage.common.dto.PageResponse;
import cn.novalon.gym.manage.notify.core.domain.SysUserMessage;
import cn.novalon.gym.manage.notify.core.repository.ISysUserMessageRepository;
import cn.novalon.gym.manage.notify.core.service.ISysUserMessageService;
@@ -33,6 +35,16 @@ public class SysUserMessageServiceImpl implements ISysUserMessageService {
return messageRepository.findByUserIdAndIsReadOrderByCreateTimeDesc(userId, "0");
}
+ @Override
+ public Mono> getMessagesByUserPage(Long userId, PageRequest pageRequest) {
+ return messageRepository.findByUserIdPage(userId, pageRequest);
+ }
+
+ @Override
+ public Mono> getUnreadMessagesPage(Long userId, PageRequest pageRequest) {
+ return messageRepository.findByUserIdAndIsReadPage(userId, "0", pageRequest);
+ }
+
@Override
public Mono createMessage(SysUserMessage message) {
message.setCreateTime(LocalDateTime.now());
@@ -40,6 +52,11 @@ public class SysUserMessageServiceImpl implements ISysUserMessageService {
return messageRepository.save(message);
}
+ @Override
+ public Mono markAllAsRead(Long userId) {
+ return messageRepository.markAllAsReadByUserId(userId);
+ }
+
@Override
public Mono markAsRead(Long id) {
return messageRepository.findById(id)
diff --git a/gym-manage-api/manage-notify/src/main/java/cn/novalon/gym/manage/notify/handler/SysUserMessageHandler.java b/gym-manage-api/manage-notify/src/main/java/cn/novalon/gym/manage/notify/handler/SysUserMessageHandler.java
index 4e14d3d..14416e0 100644
--- a/gym-manage-api/manage-notify/src/main/java/cn/novalon/gym/manage/notify/handler/SysUserMessageHandler.java
+++ b/gym-manage-api/manage-notify/src/main/java/cn/novalon/gym/manage/notify/handler/SysUserMessageHandler.java
@@ -1,5 +1,6 @@
package cn.novalon.gym.manage.notify.handler;
+import cn.novalon.gym.manage.common.dto.PageRequest;
import cn.novalon.gym.manage.notify.core.domain.SysUserMessage;
import cn.novalon.gym.manage.notify.core.service.ISysUserMessageService;
import org.springframework.stereotype.Component;
@@ -23,6 +24,24 @@ public class SysUserMessageHandler {
return ServerResponse.ok().body(messages, SysUserMessage.class);
}
+ public Mono getMessagesByUserPage(ServerRequest request) {
+ Long userId = Long.parseLong(request.pathVariable("userId"));
+
+ int page = Integer.parseInt(request.queryParam("page").orElse("0"));
+ int size = Integer.parseInt(request.queryParam("size").orElse("10"));
+ String sort = request.queryParam("sort").orElse("createTime");
+ String order = request.queryParam("order").orElse("desc");
+
+ PageRequest pageRequest = new PageRequest();
+ pageRequest.setPage(page);
+ pageRequest.setSize(size);
+ pageRequest.setSort(sort);
+ pageRequest.setOrder(order);
+
+ return messageService.getMessagesByUserPage(userId, pageRequest)
+ .flatMap(pageResponse -> ServerResponse.ok().bodyValue(pageResponse));
+ }
+
public Mono getUnreadCount(ServerRequest request) {
Long userId = Long.parseLong(request.pathVariable("userId"));
return messageService.getUnreadCount(userId)
@@ -35,6 +54,24 @@ public class SysUserMessageHandler {
return ServerResponse.ok().body(messages, SysUserMessage.class);
}
+ public Mono getUnreadMessagesPage(ServerRequest request) {
+ Long userId = Long.parseLong(request.pathVariable("userId"));
+
+ int page = Integer.parseInt(request.queryParam("page").orElse("0"));
+ int size = Integer.parseInt(request.queryParam("size").orElse("10"));
+ String sort = request.queryParam("sort").orElse("createTime");
+ String order = request.queryParam("order").orElse("desc");
+
+ PageRequest pageRequest = new PageRequest();
+ pageRequest.setPage(page);
+ pageRequest.setSize(size);
+ pageRequest.setSort(sort);
+ pageRequest.setOrder(order);
+
+ return messageService.getUnreadMessagesPage(userId, pageRequest)
+ .flatMap(pageResponse -> ServerResponse.ok().bodyValue(pageResponse));
+ }
+
public Mono createMessage(ServerRequest request) {
return request.bodyToMono(SysUserMessage.class)
.flatMap(messageService::createMessage)
@@ -48,6 +85,12 @@ public class SysUserMessageHandler {
.switchIfEmpty(ServerResponse.notFound().build());
}
+ public Mono markAllAsRead(ServerRequest request) {
+ Long userId = Long.parseLong(request.pathVariable("userId"));
+ return messageService.markAllAsRead(userId)
+ .flatMap(count -> ServerResponse.ok().bodyValue(count));
+ }
+
public Mono deleteMessage(ServerRequest request) {
Long id = Long.parseLong(request.pathVariable("id"));
return messageService.deleteMessage(id)
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 1c3ea7c..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
@@ -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
@@ -58,8 +58,13 @@ public class SecurityConfig {
.pathMatchers("/api/member-card-records/**").permitAll()
.pathMatchers("/**").permitAll()
.pathMatchers("/api/member-card-transactions/**").permitAll()
- .pathMatchers("/api/checkIn/**").permitAll();
-
+ .pathMatchers("/api/groupCourse/page").permitAll()
+ .pathMatchers("/api/checkIn/**").permitAll()
+ .pathMatchers("/api/payment/**").permitAll()
+ .pathMatchers("/api/payment/create").permitAll()
+ .pathMatchers("/api/payment/query").permitAll()
+ .pathMatchers("/api/payment/notify").permitAll()
+ .pathMatchers("/api/payment/refund").permitAll();
if (isDevOrTest) {
spec.pathMatchers("/swagger-ui.html").permitAll()
diff --git a/gym-manage-api/manage-sys/src/test/java/cn/novalon/gym/manage/sys/TokenGenerator.java b/gym-manage-api/manage-sys/src/test/java/cn/novalon/gym/manage/sys/TokenGenerator.java
new file mode 100644
index 0000000..8632ad7
--- /dev/null
+++ b/gym-manage-api/manage-sys/src/test/java/cn/novalon/gym/manage/sys/TokenGenerator.java
@@ -0,0 +1,46 @@
+package cn.novalon.gym.manage.sys;
+
+import cn.novalon.gym.manage.sys.security.JwtTokenProvider;
+import cn.novalon.gym.manage.common.config.JwtProperties;
+import org.junit.jupiter.api.Test;
+
+public class TokenGenerator {
+
+ @Test
+ public void generateTokenForUser1_Dev() {
+ // dev环境secret
+ JwtProperties jwtProperties = new JwtProperties();
+ jwtProperties.setSecret("novalon-gym-manage-jwt-secret-key-for-development-only-2026");
+ JwtTokenProvider provider = new JwtTokenProvider(jwtProperties);
+
+ String token = provider.generateToken("admin", 1L);
+ System.out.println("【Dev环境】Token for userId=1:");
+ System.out.println(token);
+ System.out.println();
+ }
+
+ @Test
+ public void generateTokenForUser1_Local() {
+ // local环境secret
+ JwtProperties jwtProperties = new JwtProperties();
+ jwtProperties.setSecret("U2FsdGVkX1+vZ5Y9QmKxL8nN3rP7tW2jH4fG6dA8sB1cE5yN0zX3qV7wM4");
+ JwtTokenProvider provider = new JwtTokenProvider(jwtProperties);
+
+ String token = provider.generateToken("admin", 1L);
+ System.out.println("【Local环境】Token for userId=1:");
+ System.out.println(token);
+ System.out.println();
+ }
+
+ @Test
+ public void generateTokenForUser1_Default() {
+ // 默认环境secret
+ JwtProperties jwtProperties = new JwtProperties();
+ jwtProperties.setSecret("U2FsdGVkX1+vZ5Y9QmKxL8nN3rP7tW2jH4fG6dA8sB1cE5yN0zX3qV7wM4");
+ JwtTokenProvider provider = new JwtTokenProvider(jwtProperties);
+
+ String token = provider.generateToken("admin", 1L);
+ System.out.println("【默认环境】Token for userId=1:");
+ System.out.println(token);
+ }
+}
diff --git a/gym-manage-api/pom.xml b/gym-manage-api/pom.xml
index 91a2e5a..8746a4a 100644
--- a/gym-manage-api/pom.xml
+++ b/gym-manage-api/pom.xml
@@ -46,6 +46,7 @@
gym-groupCourse
gym-checkIn
gym-dataCount
+ gym-auth
diff --git a/vue b/vue
new file mode 160000
index 0000000..bc48e69
--- /dev/null
+++ b/vue
@@ -0,0 +1 @@
+Subproject commit bc48e695cb1fdc19fd04ce30cecb386658fcf3e2