Windows下VisualSVN Server与TortoiseSVN安装配置及团队协作实战指南
1. 从“单打独斗”到“团队协作”为什么我们需要版本控制如果你是一个开发者或者是一个需要频繁修改文档、设计稿的团队成员你一定经历过这样的场景电脑里躺着一个名为“最终版”的文件夹打开后里面是“最终版_修改”、“最终版_再改”、“最终版_确定不改了”、“最终版_真的不改了_V2”……更糟糕的是当你想找回三天前的某个版本时却发现它早已淹没在混乱的命名中。或者当团队多人同时修改一个文件时最后合并的版本成了“四不像”谁也不知道到底改了哪里、谁改的、为什么要改。这就是版本控制系统要解决的核心问题。它不是一个简单的“网盘”或“文件同步工具”而是一个记录每一次文件变更的“时光机”和“协作仲裁者”。在众多版本控制工具中SVNSubversion以其集中式管理、权限控制清晰、学习曲线相对平缓的特点在相当长一段时间内尤其是在企业内部开发、项目管理中占据了重要地位。而VisualSVN Server则是Windows平台上搭建SVN服务端的一个非常流行且易用的选择它将复杂的命令行配置封装成了直观的图形界面和安装向导让SVN服务器的部署变得像安装一个普通软件一样简单。本系列文章就将手把手带你完成VisualSVN Server的下载、安装并进行最基本的仓库创建和操作。无论你是刚入行的程序员需要为小组项目搭建一个简单的代码管理环境还是项目经理、美术设计师希望用规范的方式来管理需求文档或设计资产这篇内容都能为你提供一个清晰、可靠的起点。我们不会涉及复杂的网络架构或安全策略而是聚焦于“在单机或局域网内快速搭建一个可用的SVN服务”让你在半小时内就能开始体验版本控制带来的秩序与效率。2. VisualSVN Server的下载与安装避开那些“下一步”的坑在开始之前我们需要明确一点VisualSVN Server是服务端软件它需要安装在一台作为服务器的电脑上可以是你的开发机也可以是一台专门的服务器。团队成员则通过TortoiseSVN俗称“小乌龟”等客户端软件来连接这个服务器进行操作。本节我们专注于服务端的部署。2.1 获取安装包官网与版本选择首先访问VisualSVN的官方网站。这里有一个小技巧直接搜索“VisualSVN Server”很容易找到官网其域名通常包含“visualsvn.com”。务必从官网下载以保证软件的安全和纯净。进入下载页面后你会看到两个主要版本VisualSVN Server和VisualSVN Server Enterprise。对于绝大多数个人、小型团队或测试环境免费的VisualSVN Server标准版Standard功能已经完全足够。它支持创建无限的仓库和用户只是在一些高级特性如高可用集群、与Active Directory深度集成等方面有限制我们初期完全用不到。在版本选择上通常下载最新的稳定版即可。安装包分为32位x86和64位x64版本请根据你的服务器操作系统位数进行选择。如今绝大多数Windows系统都是64位选择“VisualSVN Server x64”即可。下载下来的是一个.msi安装文件。注意虽然VisualSVN Server提供了便捷的图形化安装但在生产环境部署前请务必确认服务器的防火墙规则默认SVN使用3690端口HTTPS使用443端口以及备份策略。对于测试学习在个人电脑上安装完全没问题。2.2 安装过程详解每一步的选择与含义双击运行下载的.msi安装文件启动安装向导。欢迎界面与许可协议直接点击“Next”并接受许可协议。选择组件Choose Components这是第一个关键点。默认选项通常包括VisualSVN Server核心服务程序必选。Management Console管理控制台一个MMC管理单元这是我们后续创建仓库、管理用户的主要图形界面必选。Add subversion commands to PATH将SVN命令行工具添加到系统环境变量。强烈建议勾选。这样你可以在任意命令提示符CMD或PowerShell中直接使用svn命令对于后续的脚本编写或高级操作非常方便。配置设置Configuration Settings这是最重要的步骤决定了服务的基本运行方式。Location安装路径。默认在C:\Program Files\VisualSVN Server\如果C盘空间紧张可以更改到其他盘符。Repositories仓库存储路径。这是所有SVN仓库数据你的代码、文档历史实际存放的位置。务必将其设置在一个空间充足、非系统盘、且便于备份的路径例如D:\SVN_Repositories。这个路径一旦确定后期迁移会比较麻烦。Server Port服务器端口。默认使用HTTPS的443端口和SVN协议的3690端口。使用安全连接HTTPS这是默认且推荐的方式。它会对数据传输进行加密避免密码等敏感信息明文传输。使用HTTPS需要配置证书VisualSVN Server安装时会自动生成一个自签名证书。对于内部网络测试自签名证书会引发客户端的安全警告但可以忽略。对于正式环境建议替换为受信任的证书。使用非安全连接HTTP不推荐因为所有数据都是明文的。仅在绝对封闭、安全的测试环境中可考虑。Use secure connectionhttps://和Use unsecure connectionhttp://的单选框保持默认的HTTPS即可。Authentication身份认证方式。默认是“Subversion authentication”即使用VisualSVN Server自带的用户数据库来管理账号密码。另一种是“Windows authentication”直接使用服务器的Windows账户认证更适合与AD域集成的企业环境。我们选择默认的“Subversion authentication”。准备安装Ready to Install确认上述信息无误后点击“Install”开始安装。安装完成安装完成后勾选“Start VisualSVN Server Manager”即可启动管理控制台。整个安装过程通常只需几分钟。安装完成后你会在开始菜单找到“VisualSVN Server Manager”的快捷方式一个Windows服务“VisualSVN Server”也会自动启动并在后台运行。2.3 安装后验证确保服务已就绪安装完成后我们可以通过几种方式验证服务是否正常运行方式一查看服务。打开“运行”WinR输入services.msc在服务列表中找到“VisualSVN Server”查看其状态是否为“正在运行”。方式二访问测试页面。打开浏览器输入你安装时设置的服务器地址例如https://你的计算机名或IP/svn/。如果能看到一个显示“VisualSVN Server”的页面并且可能提示需要认证因为仓库列表为空或无权访问说明Web服务Apache已经正常启动。方式三使用管理控制台。打开“VisualSVN Server Manager”左侧树形结构显示正常没有红色错误图标则说明管理接口正常。至此你的SVN服务器就已经搭建完成了。它现在就像一座空的图书馆书架仓库已经准备好但还没有图书项目文件也没有借阅规则权限。接下来我们就要创建第一个“书架”。3. 创建你的第一个SVN仓库不仅仅是建个文件夹仓库Repository是SVN的核心它是所有版本化数据的存储中心记录着每一个文件、每一次提交的完整历史。在VisualSVN Server Manager中创建仓库远不止在磁盘上新建一个文件夹那么简单它同时完成了一系列的初始化工作。3.1 仓库创建步骤与参数解析在VisualSVN Server Manager左侧的树形视图中右键点击“Repositories”选择“Create New Repository...”。仓库类型Repository TypeRegular FSFS repository这是默认且推荐的类型。FSFSFile System File System是一种将版本数据存储在普通文件系统中的后端存储格式稳定、高效、易于备份是SVN当前的标准。VDFS repository这是VisualSVN Server企业版支持的专用格式针对大仓库和网络性能进行了优化。我们使用免费的Standard版所以只能选择“Regular FSFS”。仓库名称Repository Name给你的仓库起一个有意义的名字例如MyFirstProject、CompanyDocs。名称中最好避免空格和特殊字符使用英文、数字、下划线或连字符这样在命令行和URL中处理起来更方便。仓库布局Repository LayoutSingle-project repository单项目仓库。仓库根目录直接就是项目的根目录。这是最常见的选择结构清晰一个项目对应一个仓库。Multiple-projects repository多项目仓库。仓库根目录下会预先创建trunk、branches、tags三个标准子目录你可以在trunk下再为每个项目创建子目录。这种布局适合将多个高度相关的小项目或模块放在同一个仓库中管理但通常不如单仓库来得清晰权限管理也更复杂。对于新手强烈建议选择“Single-project repository”。权限设置Repository Access PermissionsAll Subversion users have Read/Write access所有已创建的SVN用户都对这个新仓库拥有读写权限。这在初期团队人员固定且互相信任时比较方便。Customize permissions自定义权限。你可以选择“No access”无人有权限稍后手动配置或指定某个用户/用户组拥有特定权限。从安全和管理规范角度即使初期人少也建议选择“Customize permissions” - “No access”。这样你可以从容地、按需添加用户和权限避免意外修改。点击“Create”VisualSVN Server会在你之前设置的仓库存储路径如D:\SVN_Repositories下创建一个名为MyFirstProject的文件夹。这个文件夹内部包含了SVN用于管理版本历史的数据库文件在db子目录中你绝对不应该直接去修改或删除这个文件夹里的任何内容。所有操作都应通过SVN客户端或管理控制台进行。创建成功后管理控制台会显示仓库的URL例如https://YourServer/svn/MyFirstProject/。这个URL就是团队成员访问这个仓库的“地址”。3.2 理解仓库的初始状态与标准目录结构即使你选择了“Single-project repository”一个健康的SVN项目也通常遵循一种名为“trunk/branches/tags”的目录结构约定。这不是SVN强制的但却是全球开发者社区公认的最佳实践。trunk主干这是项目开发的主线存放当前正在进行的、稳定的开发代码。你可以把它想象成产品的主干道。branches分支用于开发新功能、修复重大Bug或进行实验性开发。从trunk创建分支在分支上独立工作完成后再合并回trunk。这就像从主干道开辟的临时施工便道或试验路段。tags标签用于标记项目的重要节点如发布版本v1.0, v1.1。tags是只读的代表某个时刻trunk或branch的“快照”用于发布和回溯。这就像在道路里程碑上钉一个牌子记录“此处是v1.0版本”。虽然VisualSVN Server创建仓库时不会自动生成这些文件夹但你应该在第一次向仓库提交代码时就手动创建这个结构。具体操作会在客户端部分详述。理解并采用这个结构能为未来的并行开发、版本发布打下良好的基础避免后期目录混乱不堪。4. 用户与权限管理谁可以做什么一个空的仓库没有任何用处。我们需要允许用户访问它并且要控制不同用户能做什么。VisualSVN Server的权限管理非常直观基于“用户/用户组 - 仓库/目录路径 - 权限”这三层模型。4.1 创建用户与用户组在管理控制台左侧展开“Users”和“Groups”。创建用户右键点击“Users” - “Create User”。你需要提供Username登录名如zhangsan。PasswordConfirm Password设置密码。VisualSVN Server的密码策略相对简单建议自行设定复杂密码。Full name用户全名如张三。这是一个描述字段方便管理员识别。 创建后用户就出现在列表中。你可以随时右键用户进行编辑、重置密码或删除。创建用户组右键点击“Groups” - “Create Group”。给组起个名字如Developers、Testers。然后在组属性中将已有的用户如zhangsan添加到这个组的“Member”列表中。使用用户组来管理权限是更高效的方式。当项目人员变动或权限需要批量调整时只需修改组的成员或组的权限而不需要逐个修改几十个用户的权限。4.2 为仓库或目录设置访问权限权限设置是粒度化的可以针对整个仓库也可以针对仓库内的某个子目录。在管理控制台左侧找到你想要设置权限的仓库例如MyFirstProject右键选择“Properties”。切换到“Security”选项卡。这里你可以看到当前的权限列表。点击“Add...”按钮选择你要授权的用户或用户组例如Developers组。在“Permissions”下拉框中为该用户/组选择权限级别No Access禁止访问。连查看目录列表都不行。Read Only只读。可以查看代码历史、下载文件但不能提交修改。Read / Write读写。可以执行所有操作包括提交修改。对于目录还有更细粒度的权限如“Modify”等在高级设置中可见点击“OK”应用。权限继承与覆盖权限设置具有继承性。如果你在仓库根目录给Developers组设置了“Read / Write”权限那么这个组对仓库下的所有子目录都拥有读写权。但是你可以在某个子目录例如/tags上设置更严格的权限如给Everyone设置“Read Only”这个子目录的权限设置会覆盖从父目录继承来的权限。这是实现“tags目录只读”等常见需求的关键。4.3 一个实战权限配置案例假设我们有一个项目MyFirstProject团队构成如下开发人员3人需要读写/trunk和/branches。测试人员2人需要读写/branches/feature-*功能测试分支但对/trunk只有只读权限。项目经理1人需要读写所有目录。所有人员对/tags目录只有只读权限。配置步骤创建用户组dev_group,test_group,pm并将相应用户加入。仓库根权限先不设置任何权限或只给pm组“Read / Write”。设置/trunk权限添加dev_group和pm为“Read / Write”添加test_group为“Read Only”。设置/branches权限添加dev_group、test_group、pm为“Read / Write”。注意这里test_group对/branches有写权但可以通过更细的路径或分支命名规范来约束他们只在自己的测试分支上工作。设置/tags权限添加Everyone或所有组为“Read Only”。通过这样的配置我们就实现了一个符合常见研发流程的权限模型。VisualSVN Server的图形化界面让这一切变得非常直观。5. 客户端初体验TortoiseSVN的安装与基本工作流服务器端准备就绪后团队成员就需要在各自的电脑上安装SVN客户端。在Windows环境下TortoiseSVN是无可争议的首选。它不是一个独立的应用程序而是完美集成到Windows资源管理器右键菜单中的插件让你在熟悉的文件夹界面中就能完成所有SVN操作。5.1 TortoiseSVN的下载与安装访问TortoiseSVN官网下载对应你系统位数32/64位的安装包。安装过程基本是“下一步”到底但有几个点需要注意命令行客户端工具安装向导会问你是否安装命令行工具。建议勾选安装。这样你可以在CMD或PowerShell中使用svn命令对于编写脚本或某些自动化任务很有帮助。选择SSH客户端如果你未来需要通过svnssh://协议访问仓库可以安装TortoiseSVN自带的TortoisePlink基于PuTTY。对于使用VisualSVN Server的HTTPS协议此项非必需。安装完成后需要重启TortoiseSVN集成到Shell安装后通常会提示重启资源管理器或直接重启电脑以使右键菜单生效。安装成功后你在任何一个文件夹或文件上点击右键都应该能看到新增的“SVN Checkout...”、“TortoiseSVN”等菜单项。5.2 核心操作一检出Checkout“检出”是你与远程仓库建立联系的第一个操作。它的作用是将服务器仓库中的某个目录通常是整个项目完整地下载到你的本地工作副本Working Copy并建立关联。在你本地准备一个空文件夹作为工作副本的根目录例如D:\Projects\MyFirstProject。进入这个文件夹在空白处右键 - SVN Checkout...。在弹出的对话框中URL of repository填写你的仓库URL例如https://YourServer/svn/MyFirstProject/。如果你要检出某个子目录如trunk则URL是https://YourServer/svn/MyFirstProject/trunk。Checkout directory会自动填充为当前文件夹路径D:\Projects\MyFirstProject。Revision默认是“HEAD”即最新版本。你也可以检出某个历史版本。点击“OK”会弹出认证窗口输入你在VisualSVN Server中创建的用户名和密码。认证成功后文件开始下载。由于是空仓库此时你的本地文件夹可能也是空的但它已经是一个“工作副本”了文件夹上会有一个绿色的对勾图标如果没有按F5刷新一下。5.3 核心操作二添加Add与提交Commit现在你可以在本地工作副本中开始工作了。假设我们要建立标准的trunk/branches/tags结构。在D:\Projects\MyFirstProject文件夹内手动新建三个空文件夹trunk,branches,tags。添加文件到版本控制选中这三个文件夹右键 - TortoiseSVN - Add。你会看到这三个文件夹的图标上多了一个蓝色的加号这表示它们已被标记为“等待添加到版本库”。这个操作只在本地记录尚未影响到服务器。提交更改到服务器在D:\Projects\MyFirstProject文件夹空白处右键 - SVN Commit...。会弹出一个对话框列出了所有待提交的更改即刚才标记为Add的三个文件夹。在“Message”输入框中必须填写本次提交的日志信息这是良好的习惯例如“Initial import: create standard directory structure”。点击“OK”输入密码后这三个文件夹就被正式提交到了服务器的仓库中。此时你的本地文件夹图标会变成绿色的对勾表示本地副本与服务器最新版本一致。团队成员现在执行“Checkout”这个URL就能看到这个结构了。5.4 核心操作三更新Update与解决冲突当你和团队成员并行工作时你需要定期将别人的修改同步到本地这个操作叫“更新”。在你的工作副本根目录右键 - SVN Update。TortoiseSVN会自动下载服务器上比你本地版本更新的所有改动并尝试合并到你的本地文件中。如果别人修改的文件你本地没动过合并会自动完成。冲突Conflict是版本控制中一个关键概念。当你和同事修改了同一文件的同一区域并且他先提交了你再提交时SVN无法自动决定该保留谁的修改就会报告冲突。解决冲突的典型流程你执行“Commit”时可能会失败并提示“文件已过时”你需要先“Update”。执行“Update”后如果发生冲突冲突的文件图标会变成红色的感叹号。右键该冲突文件选择“Edit conflicts”会打开一个三窗格对比工具TortoiseMerge左侧是服务器最新版他们的右侧是你的本地修改中间是合并结果。你可以手动选择接受哪一部分修改或者直接编辑中间窗格形成最终版本。解决完所有冲突后右键冲突文件选择“Resolved”告诉SVN冲突已手动解决。最后再次执行“Commit”提交合并后的最终版本。理解并熟练处理冲突是团队协作开发的基本功。养成勤“Update”、早提交、写清晰日志的习惯能极大减少冲突的发生和解决难度。6. 从入门到实践建立你的第一个真实项目工作流掌握了基本操作后让我们模拟一个真实的微型项目走完一个完整的开发-测试-发布循环体验SVN的标准工作流。假设项目是一个简单的计算器程序。6.1 初始化项目结构与首次提交规划目录我们已经有了/trunk、/branches、/tags。所有主线开发都在trunk下进行。在Trunk中开始开发在本地工作副本的trunk文件夹下创建项目文件例如calc.py主程序、README.txt说明文档、design设计稿文件夹。选中这些新文件和文件夹右键“Add”标记它们。在trunk目录右键“Commit”提交日志写“Initial project skeleton: main script, readme and design folder”。日常开发与提交你开始编写calc.py实现了加法功能。完成一个小的、完整的功能点后就进行一次提交。提交日志应具体如“Implement add function: support two integer inputs”。避免积累大量修改后一次性提交一个“fixed bugs”这样模糊的日志。6.2 创建分支进行新功能开发现在你要开发一个乘法功能但这个改动可能比较大或者你想在不干扰trunk稳定性的情况下进行实验。创建分支在本地工作副本的根目录右键 - TortoiseSVN - Branch/tag...在对话框中From WC / URL选择/trunk表示从主干创建分支。To URL填写分支路径例如/branches/feature-multiply。URL会自动补全为https://YourServer/svn/MyFirstProject/branches/feature-multiply。创建后切换到该分支这里有一个关键选择。TortoiseSVN提供了“Switch to this branch/tag after creation”选项。我个人的习惯是不勾选。我更喜欢先创建分支然后手动将工作副本的一个独立目录切换到该分支进行开发。这样可以保持trunk目录的纯净随时可以切换回去修复紧急Bug。但如果你勾选当前工作副本会直接指向新分支。点击OK创建分支。这个操作在服务器端瞬间完成类似于创建一个指针并不复制所有文件数据。在分支上工作为分支创建一个新的本地工作目录。或者如果你熟悉“切换Switch”操作可以将原来的trunk工作副本切换到分支URL右键 - TortoiseSVN - Switch...但这样原来的trunk内容就被覆盖了。更清晰的做法是为/branches/feature-multiply单独执行一次“Checkout”到一个新文件夹如D:\Projects\MyFirstProject_Multiply然后在这个新文件夹里开发乘法功能。在分支上完成开发、测试后像在trunk上一样提交代码。6.3 合并分支与打标签发布乘法功能在分支上经过充分测试稳定无误现在需要将它合并回主干。合并到主干确保你的本地trunk工作副本是最新版本先Update。在trunk工作副本目录右键 - TortoiseSVN - Merge...选择合并类型“Merge a range of revisions”合并一个版本范围。这是最常用的用于将分支上的一系列更改合并过来。URL to merge from选择你的分支URL/branches/feature-multiply。Revision range to merge可以选择“All revisions”合并分支上所有提交或者指定具体的版本号范围。点击“Test merge”可以先预览合并结果确认无误后再执行“Merge”。合并操作会将分支的修改应用到本地trunk工作副本。此时你需要仔细检查合并后的代码解决可能出现的冲突虽然从同一个基点创建的分支冲突可能较少但并非没有。确认无误后提交本地trunk工作副本日志写“Merge feature-multiply from branch to trunk”。创建发布标签当trunk到达一个稳定状态比如v1.0版本我们需要为其创建一个只读的快照。在trunk工作副本目录右键 - TortoiseSVN - Branch/tag...From WC / URL选择/trunk。To URL填写标签路径例如/tags/v1.0.0。注意务必在日志中写明这是标签如“Create tag for release v1.0.0”。点击OK。标签创建完成。它本质上是一个特殊的分支但大家约定俗成不对其进行修改。通过权限控制之前设置的/tags只读可以物理上防止误修改。至此一个包含分支开发、合并、发布的完整基础工作流就完成了。这个流程在trunk进行日常开发为重大特性或版本创建branches为发布创建tags是SVN乃至许多版本控制系统协同工作的核心模式。7. 进阶技巧与日常维护要点掌握了基本操作和工作流你已经可以应对90%的日常场景。但要用得顺手、不出错还需要了解一些进阶技巧和养成好的维护习惯。7.1 忽略文件列表svn:ignore你的项目目录里总有一些文件不应该提交到版本库比如编译生成的二进制文件*.class,*.dll,*.exe、IDE配置文件.idea/,.vscode/、本地环境配置文件、系统临时文件等。提交它们会污染仓库并可能引发不必要的冲突。SVN通过属性svn:ignore来设置忽略规则。在资源管理器中右键点击你想要设置忽略规则的文件夹比如项目根目录选择“TortoiseSVN - Properties”。点击“New...”选择属性类型为“svn:ignore”。在属性值框中每行输入一个要忽略的模式例如*.log *.tmp /bin/ /obj/ .idea/ *.user/bin/表示忽略该目录下的所有内容点击OK。这个属性设置需要被提交到服务器这样所有团队成员都会共享这套忽略规则。7.2 查看日志与版本对比“查看日志Show log”是你了解项目历史的窗口。右键任何版本控制的文件或文件夹选择“TortoiseSVN - Show log”。你可以看到所有的提交记录、作者、日期和日志信息。双击任意一次修订版本可以查看那次提交具体修改了哪些文件。“版本对比Diff”是理解代码变更的利器。右键一个文件选择“TortoiseSVN - Diff with previous version”可以比较当前本地文件与它上一次提交版本的差异。在日志窗口中选中两个版本右键选择“Compare revisions”可以比较任意两个历史版本之间的差异。TortoiseSVN内置的对比工具TortoiseMerge色彩清晰支持文本和二进制文件非常实用。7.3 备份与恢复VisualSVN Server的数据核心就是仓库存储路径下的那些文件夹。最简单的备份方式就是定期、完整地复制整个仓库目录如D:\SVN_Repositories到另一个安全的位置如另一块硬盘、网络存储。在备份时需要先停止VisualSVN Server服务以保证数据一致性。对于小型仓库这个方法是可行的。更专业的备份方式是使用VisualSVN Server自带的备份命令svnadmin hotcopy或通过其PowerShell命令行工具进行热备份。恢复时将备份的仓库目录复制回来并在管理控制台中“Add”这个已有的仓库即可。7.4 常见问题排查认证失败检查用户名密码是否正确确认服务器地址和端口HTTPS的443能否访问检查防火墙设置。提交被拒绝“Access denied”检查该用户/用户组对目标仓库或目录是否具有“Read / Write”权限。工作副本已锁定有时异常中断会导致工作副本处于锁定状态。可以尝试执行“Clean up”操作右键 - TortoiseSVN - Clean up...。如果不行最彻底的方法是备份好本地修改过的文件然后删除整个工作副本目录重新Checkout。文件图标不显示TortoiseSVN的图标覆盖可能因系统资源管理器问题而失效。可以尝试重启电脑或在TortoiseSVN设置中调整图标缓存大小和类型。VisualSVN Server和TortoiseSVN的组合为Windows环境下的SVN使用提供了极大的便利。从服务器的一键安装、图形化权限管理到客户端的资源管理器集成、直观的对比合并工具这套工具链极大地降低了版本控制的入门门槛。虽然如今Git因其分布式特性更为流行但SVN集中式模型的简单、直观以及与企业现有流程如严格的权限审批的契合度使其在特定场景下依然具有不可替代的价值。理解其核心概念和工作流不仅是掌握一个工具更是理解版本控制思想本身这对于你未来学习任何其他版本控制系统包括Git都将大有裨益。