☰
基于C#与MySQL的房屋租赁管理系统设计与实现全解析
2026/10/4 7:40:15 网站建设 项目流程

简介:基于C#与MySQL的房屋租赁管理系统是一份可参考的课程设计/毕业设计项目,面向计算机、软件工程、通信工程等专业学生。通过C#实现界面层与业务逻辑,使用ADO.NET连接MySQL,代码涉及对象建模、数据绑定、异常处理及SQL增删改查等关键实践;压缩包内还包含ER图、数据流图、课程设计报告和数据库脚本,便于对照理解完整开发流程。

资源包共69个文件,约12.81MB,其中25个.cs源码文件是核心程序,.resx/.resources为界面资源,.sql为数据库脚本,.msi为MySQL连接组件安装包,.docx和.png分别对应设计文档与图形建模资料,另有.sln/.csproj工程文件可直接打开调试。

目前已有276人浏览学习。借助该资源可快速掌握C#桌面应用与MySQL协同开发的常见做法,也能为撰写课程设计说明、绘制系统结构提供直观参考,适合相关专业学生借鉴,对巩固C#语法、数据库设计和软件工程实践也有帮助。

1. 基于C#+Mysql的房屋租赁管理系统:先想清楚它解决什么问题

张姐手里有三十多套房,以前用Excel台账,月底对账差几百块是常事。后来我帮她做的,就是这个典型的“基于C#+Mysql房屋租赁管理系统”——C#负责界面和业务逻辑,MySQL负责存房源、租客、合同、账单。它能解决的核心问题很具体:哪套房空着、哪份合同快到期、哪个租客欠了多少钱,打开系统一眼看全,不用再翻表格。适合三类人:拿它做毕业设计的学生、想给自家房源做管理工具的小房东、刚入门C#想找个完整项目练手的新手。这套东西真正的难点,不在C#语法,也不在MySQL命令,而是把“房源—合同—账单”三条状态线理清楚并落地。

2. 数据库先行:六张核心表怎么设计才扛得住租赁业务

2.1 房源、租客、合同的关系:为什么合同是中间表

做这类系统最忌讳一上来就写窗体。界面可以后面改,表结构错了,所有C#代码都要跟着返工。租赁业务的数据关系其实很清晰:一套房源可以有多份历史合同,但同一时刻只能有一份生效合同;一个租客可以签多份合同;一份合同同时对应一个房源和一个租客。这就是典型的一对多关系,而“合同”就是房源和租客之间的中间表。

后面所有界面,都是围绕这张中间表展开的:列表页显示“哪个房源租给了谁”,账单页按合同生成,预警页按合同结束日期筛选。所以建表顺序应该是:先把house和tenant这两张基础表定下来,再把contract作为关联核心建好,最后补payment账单表。C#里的实体类也按这个顺序写,类名直接跟表名对齐,后面写DAL层时不容易乱。

我见过有人把房源状态直接写在合同表里,说“有生效合同就是已租”,听着合理,但退租后的历史合同会污染状态查询。所以房源表里单独留一个status字段,由业务层在签订和退租时同步修改。这个设计在答辩时也更容易讲清楚:房源状态和合同历史分离,查询逻辑简单。

2.2 建表SQL:house、tenant、contract三张表的字段和约束

先建库,再建表。数据库字符集直接指定utf8mb4,免得后面中文乱码,也省得部署到新机器时再排查一遍。

CREATE DATABASE IF NOT EXISTS rent_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE rent_system; CREATE TABLE house ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, house_no VARCHAR(20) NOT NULL COMMENT '房源编号,如 A-3-1201', address VARCHAR(100) NOT NULL COMMENT '详细地址', area DECIMAL(8,2) NOT NULL DEFAULT 0 COMMENT '建筑面积(㎡)', price_month DECIMAL(10,2) NOT NULL COMMENT '月租金', status TINYINT NOT NULL DEFAULT 0 COMMENT '0空置 1已租 2维修', remark VARCHAR(200) NOT NULL DEFAULT '', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_house_no (house_no) ) ENGINE=InnoDB COMMENT='房源表'; CREATE TABLE tenant ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL COMMENT '租客姓名', phone VARCHAR(20) NOT NULL, id_card VARCHAR(18) NOT NULL DEFAULT '', emergency_contact VARCHAR(50) NOT NULL DEFAULT '', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY idx_tenant_phone (phone) ) ENGINE=InnoDB COMMENT='租客表'; CREATE TABLE contract ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, contract_no VARCHAR(30) NOT NULL COMMENT '合同编号', house_id INT UNSIGNED NOT NULL, tenant_id INT UNSIGNED NOT NULL, start_date DATE NOT NULL, end_date DATE NOT NULL, deposit DECIMAL(10,2) NOT NULL DEFAULT 0 COMMENT '押金', rent_month DECIMAL(10,2) NOT NULL COMMENT '月租金,签订时的快照', pay_cycle TINYINT NOT NULL DEFAULT 1 COMMENT '1月付 3季付 6半年付 12年付', status TINYINT NOT NULL DEFAULT 1 COMMENT '1生效 0已结束', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_contract_no (contract_no), KEY idx_contract_house_status (house_id, status), KEY idx_contract_end_date (end_date), CONSTRAINT fk_contract_house FOREIGN KEY (house_id) REFERENCES house(id), CONSTRAINT fk_contract_tenant FOREIGN KEY (tenant_id) REFERENCES tenant(id) ) ENGINE=InnoDB COMMENT='租赁合同表';

house_no加唯一索引,录入重复房源时数据库直接拒绝,不用在C#里做一次存在性检查。rent_month在合同里存一份快照,以后房东涨价,历史账单不受影响。status用TINYINT不用字符串,查询快,代码里用常量对应即可。外键约束一定要建,它逼着代码先建房源再建合同,删除时也不会留下孤儿数据。

索引方面,contract表上加了两个联合索引:house_id+status用于查“某套房的有效合同”,end_date用于到期预警扫描。后面查询全是走这两个方向,索引建在这些列上,比建在id上实用得多。

2.3 payment账单表:金额、时间、状态字段的默认值怎么定

CREATE TABLE payment ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, contract_id INT UNSIGNED NOT NULL, period_start DATE NOT NULL COMMENT '账单周期开始', period_end DATE NOT NULL COMMENT '账单周期结束', amount DECIMAL(10,2) NOT NULL COMMENT '应收金额', paid_amount DECIMAL(10,2) NOT NULL DEFAULT 0 COMMENT '已收金额', status TINYINT NOT NULL DEFAULT 0 COMMENT '0未缴 1已缴 2部分缴纳', pay_time DATETIME DEFAULT NULL, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY idx_payment_contract_status (contract_id, status), CONSTRAINT fk_payment_contract FOREIGN KEY (contract_id) REFERENCES contract(id) ) ENGINE=InnoDB COMMENT='账单表'; CREATE TABLE user ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL, password_hash VARCHAR(64) NOT NULL COMMENT 'SHA256 哈希', role TINYINT NOT NULL DEFAULT 1 COMMENT '1管理员 2操作员', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_username (username) ) ENGINE=InnoDB COMMENT='系统用户表'; CREATE TABLE operation_log ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, user_id INT UNSIGNED NOT NULL, action VARCHAR(100) NOT NULL, target VARCHAR(100) NOT NULL DEFAULT '', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY idx_log_user_time (user_id, create_time) ) ENGINE=InnoDB COMMENT='操作日志表';

几个设计取舍。金额一律decimal(10,2),十亿以内够用,绝不用float/double,这是最容易埋雷的地方。状态字段用TINYINT,新手可能觉得“用VARCHAR存‘paid’更好读”,但VARCHAR带着中文没法做索引排序,代码里读取的映射也啰嗦。想让状态字段默认值为0,建表时直接写DEFAULT 0,比在C#里每次new对象赋初值可靠得多,漏一处就是NULL。

时间字段分两种:业务时间用DATE,比如合同起止、账单周期;记录时间用DATETIME加DEFAULT CURRENT_TIMESTAMP,查询时按时间倒序排列,不需要代码手工赋值。user表和operation_log表属于系统层,第一版就建好。很多项目做完功能才想起补日志,结果登录、权限都没法追溯,返工成本反而更高。

3. 搭C#项目骨架:四层结构、MySQL连接串与登录模块

3.1 项目分四层:UI、BLL、DAL、Common 各管什么

解压这类.zip项目包时,我一般先看有没有SQL脚本,有就先执行脚本,再看代码结构。代码我按四层来组织:

RentSystem/ ├── RentSystem.sln ├── RentSystem.Common/ # 工具类:DbHelper、加密、全局会话 ├── RentSystem.DAL/ # 数据访问:所有 SQL 都在这层 ├── RentSystem.BLL/ # 业务逻辑:合同状态、账单生成 └── RentSystem.UI/ # WinForm 窗体:登录、主界面、各管理页

规则只有一条:UI不直接写SQL,DAL不弹窗体。新手最容易写出“在按钮点击事件里拼SQL”的情况,项目一复杂就没法维护。四层不是给答辩看的,是给自己留后路——哪天要换数据库,或者把WinForm换成网页端,只改UI和DAL之间的接口就行。

如果觉得Dapper这类ORM配置麻烦,这个项目直接用原生MySqlCommand完全够用。数据量在几万条以内,原生Command的性能和ORM几乎没有差别。下面所有示例都用原生方式,省去一层学习成本。

3.2 连接MySQL的最小可用代码:驱动、连接串与DbHelper

首先在NuGet里搜索MySql.Data,装最新稳定版即可,不用纠结具体小版本。如果本机还没装MySQL,官方社区版按安装教程装好5.7或8.0就行。连接串是新手第一道坎:

// RentSystem.Common/DbHelper.cs using System; using MySql.Data.MySqlClient; namespace RentSystem.Common { public static class DbHelper { private static readonly string ConnStr = "Server=localhost;Port=3306;Database=rent_system;Uid=root;Pwd=123456;" + "SslMode=None;CharSet=utf8mb4;Pooling=true;"; public static MySqlConnection GetConnection() { var conn = new MySqlConnection(ConnStr); conn.Open(); return conn; } } }

参数说明:SslMode=None跳过MySQL 8.0默认的SSL握手,老版本驱动不写这个字段,连8.0会报SSL错误;CharSet=utf8mb4对应数据库字符集,三处不一致就会出现乱码;Pooling=true让连接复用,频繁开关窗体的系统里,连接池能省掉大量握手开销。

这段代码有个简化,连接串直接写在类里,仅教学演示用。真实项目里一定要挪到App.config或外部配置文件,否则密码一泄露就是大事故。

接着写DAL层的最小查询:

// RentSystem.DAL/UserDal.cs using System; using MySql.Data.MySqlClient; using RentSystem.Common; namespace RentSystem.DAL { public class UserDal { public User Login(string username, string password) { using (var conn = DbHelper.GetConnection()) using (var cmd = new MySqlCommand( "SELECT id, username, role FROM user WHERE username=@u AND password_hash=@p", conn)) { cmd.Parameters.AddWithValue("@u", username); cmd.Parameters.AddWithValue("@p", Sha256(password)); using (var reader = cmd.ExecuteReader()) { if (!reader.Read()) return null; return new User { Id = reader.GetInt32(0), Username = reader.GetString(1), Role = reader.GetInt32(2) }; } } } } }

这里两个关键点:参数化查询不要用字符串拼接,拼接会把引号变成注入入口;口令先做SHA256哈希再比库里的password_hash,数据库里永远不存明文。哈希方法写成一个工具,放在Common里:

public static string Sha256(string input) { using (var sha = System.Security.Cryptography.SHA256.Create()) { var bytes = sha.ComputeHash(System.Text.Encoding.UTF8.GetBytes(input)); return BitConverter.ToString(bytes).Replace("-", "").ToUpperInvariant(); } }

BitConverter的方式在.NET Framework和.NET 6下都通用,比Convert.ToHexString少踩版本坑。

3.3 登录模块:参数化查询与全局会话

LoginForm的“登录”按钮只做三件事:校验输入非空、调用UserDal的Login方法、成功就把用户对象存到全局静态类:

// RentSystem.Common/Session.cs public static class Session { public static User CurrentUser { get; set; } }

登录成功后打开MainForm,把LoginForm隐藏而不是关闭,这样退出主窗体时还能回到登录页。失败时弹MessageBox提示“用户名或密码错误”,不要区分“用户不存在”和“密码错误”,减少撞库信息泄露。到此骨架就算跑通了,接下来所有业务模块,都是“SQL写在DAL、窗体绑定UI”这个套路。

4. 核心业务模块:房源状态流转、合同预警与账单生成

4.1 房源管理:列表查询、筛选与“空置→已租”的状态流转

房源列表是最先写的模块,因为它最简单,适合验证骨架。

// RentSystem.DAL/HouseDal.cs public List<House> SearchHouses(string keyword, int status) { var sql = "SELECT id, house_no, address, area, price_month, status, remark FROM house WHERE 1=1"; if (!string.IsNullOrEmpty(keyword)) sql += " AND (house_no LIKE @kw OR address LIKE @kw)"; if (status >= 0) sql += " AND status = @status"; sql += " ORDER BY create_time DESC"; var list = new List<House>(); using (var conn = DbHelper.GetConnection()) using (var cmd = new MySqlCommand(sql, conn)) { if (!string.IsNullOrEmpty(keyword)) cmd.Parameters.AddWithValue("@kw", $"%{keyword}%"); if (status >= 0) cmd.Parameters.AddWithValue("@status", status); using (var reader = cmd.ExecuteReader()) { while (reader.Read()) { list.Add(new House { Id = reader.GetInt32("id"), HouseNo = reader.GetString("house_no"), Address = reader.GetString("address"), Area = reader.GetDecimal("area"), PriceMonth = reader.GetDecimal("price_month"), Status = reader.GetByte("status"), Remark = reader.GetString("remark") }); } } } return list; }

这里用“WHERE 1=1+拼接条件”是常见做法,目的是让条件追加不用判断是否要AND。注意keyword必须走参数化,即使它来自文本框。ORDER BY create_time DESC让新录的房源排前面,比按id排序直观。

窗体里把查询结果赋给DataGridView前,先关掉自动生成列:

dataGridView1.AutoGenerateColumns = false; dataGridView1.DataSource = list;

然后手动配置列:房源编号绑定house_no,地址绑定address,状态列用DataGridViewComboBoxColumn显示“空置/已租/维修”。否则每个字段都生成一列,还要处理字段名映射,纯给自己添乱。

状态流转的代码是整个房源模块的心脏。签订合同时,必须让“插入合同”和“把房源改成已租”同时成功或同时失败:

START TRANSACTION; INSERT INTO contract (contract_no, house_id, tenant_id, start_date, end_date, rent_month) VALUES (...); UPDATE house SET status = 1 WHERE id = @houseId; COMMIT;

一句话总结:不要先插合同再回头改房源,中间断了一步,数据就对不上了。

4.2 合同管理:租期计算与30天到期预警

合同管理页面要解决的核心问题是“哪些合同快到期了”。查询逻辑是:

SELECT c.id, c.contract_no, h.address, t.name, c.end_date, DATEDIFF(c.end_date, CURDATE()) AS remain_days FROM contract c JOIN house h ON c.house_id = h.id JOIN tenant t ON c.tenant_id = t.id WHERE c.status = 1 AND c.end_date BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 30 DAY) ORDER BY remain_days;

字段说明:remain_days是剩余天数,CURDATE()返回当天日期,DATE_ADD(CURDATE(), INTERVAL 30 DAY)算出30天后的日期。三个条件组合起来就是“还在生效期内、30天内到期、按紧迫程度排序”。把里面30换成任意数字,就能做成“提前7天提醒”或者“提前90天提醒”。一般在MainForm左下角放一个DataGridView,用Timer每5分钟刷一次。

如果合同到期且没有续签,还要把对应房源状态改回空置。这个动作我建议放在“退租”按钮里手动触发,而不是让系统定时自动改——万一租客实际还住着,系统自动腾房会出大问题。自动预警、手动确认,是这类业务场景比较稳妥的节奏。

提示:把30天预警阈值做成可配置项,部署到不同房东手里就不用改代码了。

4.3 账单管理:按缴费周期生成账单与欠费统计

账单生成依赖contract表里的pay_cycle。月付周期是1,季付是3,半年是6,年付是12。生成逻辑用C#循环比较直观:

// RentSystem.BLL/PaymentService.cs public void GeneratePayments(Contract contract, MySqlConnection conn, MySqlTransaction tx) { var cursor = contract.StartDate; while (cursor < contract.EndDate) { var periodEnd = cursor.AddMonths(contract.PayCycleMonths); if (periodEnd > contract.EndDate) periodEnd = contract.EndDate; var months = (periodEnd.Year - cursor.Year) * 12 + (periodEnd.Month - cursor.Month); var amount = contract.RentMonth * months; // 插入 payment:period_start=cursor, period_end=periodEnd, amount=amount cursor = periodEnd; } }

逻辑说明:从合同开始日期出发,每往前推一个周期就生成一条账单,最后一段不足整周期就按实际跨月数算。租金用合同里的rent_month快照,不用房源当前价,避免中途调价影响历史账单。如果要追求极致,也可以把这段逻辑写成MySQL存储过程,但调试不方便,我还是推荐留在C#里,出问题时能用调试器一行行走。

收银时更新账单状态:

UPDATE payment SET paid_amount = paid_amount + @paid, status = IF(paid_amount + @paid >= amount, 1, 2) WHERE id = @paymentId;

注意这里IF里引用的paid_amount是更新前的旧值,MySQL的SET表达式在同一语句里不会读到刚写的新值,别被“字段顺序靠前就先生效”误导。部分缴纳时status=2,下次再收,status再判断。

欠费统计报表是房东最看重的一页:

SELECT c.contract_no, t.name, h.address, SUM(p.amount - p.paid_amount) AS debt FROM payment p JOIN contract c ON p.contract_id = c.id JOIN tenant t ON c.tenant_id = t.id JOIN house h ON c.house_id = h.id WHERE p.status IN (0, 2) GROUP BY p.contract_id HAVING debt > 0 ORDER BY debt DESC;

按合同分组汇总未缴金额,HAVING debt > 0只留欠费的,ORDER BY debt DESC让欠得最多的排最前。GROUP BY后面挂上合同编号,就能在下钻页面查到该合同名下的每一条账单明细。

5. 避坑指南:从MySQL 8.0到WinForm卡顿的五个常见问题

这套系统我前后写了三轮,第一轮踩的坑比功能还多。下面五条是按复现频率排的,每一条都能让程序在演示现场翻车。

5.1 MySQL 8.0连接报错:SslMode=None和认证插件二选一

现象:程序一启动连接就抛异常,报 “Authentication method 'caching_sha2_password' failed” 或SSL握手失败。

原因:MySQL 8.0默认认证插件是caching_sha2_password,默认还开启SSL。老版本的MySql.Data驱动不认识这个组合,连接自然失败。这类问题看着像玄学,实际是协议版本差异。

解决:两招任选。一是连接串加SslMode=None,跳过SSL握手,只保留认证插件适配;二是把用户认证方式改回兼容模式:

ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的密码';

如果你只想快速跑通项目不折腾,直接装MySQL 5.7.44这样的5.7维护版本,默认就是mysql_native_password,连SslMode=None都不用写。

5.2 中文乱码:数据库、连接串、代码三处必须统一

现象:C#界面显示正常,写入MySQL再读出来全是??。

原因:建库时没指定字符集,MySQL服务端默认可能是latin1;连接串没指定CharSet,C#端按其他编码发送,两边对不上。

解决:建库用DEFAULT CHARACTER SET utf8mb4;连接串加CharSet=utf8mb4;已有库执行ALTER DATABASE rent_system CHARACTER SET utf8mb4。排查命令是SHOW VARIABLES LIKE 'character_set_server';,三处字符集统一成utf8mb4后再没出现过乱码。

5.3 DataGridView假死:分页和后台线程的取舍

现象:房源数据到两三千行,窗体滚动卡顿,点筛选按钮要等两三秒。

原因:所有查询都在UI线程同步执行,DataGridView一次性绑定了全部数据。控件一多、数据一多,WinForm消息循环被SQL阻塞。

解决:分页是首选,SQL每页固定行数:

var sql = "SELECT ... FROM house LIMIT @offset, @pageSize";

配合后台线程刷新:

Task.Run(() => { var list = _houseDal.GetPage(page, pageSize); this.BeginInvoke(new Action(() => dataGridView1.DataSource = list)); });

线程里不准直接改控件,BeginInvoke把更新动作丢回主线程执行。pageSize一般取100到200,滚动翻页而不是一次全查。

5.4 Decimal与DateTime的坑:类型映射和默认值

现象:账单金额显示成0.10000000000000001,合同起止日期读出0001/1/1。

原因:MySQL字段用了float/double,C#的decimal一映射就出现二进制浮点误差;DateTime列允许NULL,读出DBNull直接GetDateTime就炸。

解决:金额字段全部decimal(10,2),状态字段要默认0的建表时写DEFAULT 0。读取时做空值判断:

var payTime = reader.IsDBNull(reader.GetOrdinal("pay_time")) ? (DateTime?)null : reader.GetDateTime("pay_time");

有几张表涉及金额和日期,就挨个查一遍,别只改一张。

5.5 同时租同一套房:事务和条件更新保底

现象:两位操作员同时看中同一套房,A签完合同B又签,两边都显示成功。

原因:签合同是两步——先查status再插合同,两个会话都查到status=0,查和写之间没有锁。

解决:把“查”改成“条件更新”:

UPDATE house SET status = 1 WHERE id = @id AND status = 0;

影响行数为0,说明房源已被抢占,直接提示。这一步和INSERT contract放在同一个事务里,要么都成功,要么都回滚。事务粒度小,锁等待时间短,比在应用层加锁靠谱。

6. 从能跑到能交付:权限、操作日志与一键初始化

权限控制不用复杂框架。user表里role字段1管理员2操作员,登录后把User放进Session,MainForm加载时根据角色隐藏敏感菜单,比如操作员看不到“删除合同”的按钮。操作日志也别等上线再补,在每个BLL的写操作里顺手插一条operation_log,写清楚谁在什么时间对什么目标做了什么。部署到生产前检查两件事:root口令有没有写在代码里?备份脚本能不能跑通?这两件事比功能齐全重要。

这里给一套最小部署脚本:

:: 新机器第一次部署:建库、建表、写入初始管理员 mysql -uroot -p < init.sql :: 每周备份数据库(Windows计划任务调用) mysqldump -uroot -p rent_system > "D:\backup\rent_%date:~0,10%.sql"

注意%date:~0,10%取的是系统日期前10位,区域格式带斜杠时文件名会多出斜杠,Windows按路径处理会报错。要稳就用PowerShell里的Get-Date格式化,或者干脆用固定文件名加时间戳后缀。备份文件定期清理,不然半年下来磁盘全是dump文件。

还有一件事容易被忽略:WinForm发布出来是IL中间码,别人拿反编译工具能看到你的源码结构。不要求做混淆,但至少别把数据库口令硬编码在程序里。我见过有人的发布包里带着root明文密码,这种交付的口碑基本就没了。

我做这套系统的第一版,没有日志没有备份,后来一个操作员输错金额,对了两天账才找回。从那时起我养成了个习惯:功能可以少,权限、日志、备份这三件事永远排在最前面。你这个房屋租赁管理系统不管自用还是交付,也先把这三件套补齐,再谈界面做得多好看。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询