『格格不入』是中文IT技术交流平台，创建于2017年，包含原创博客、技术分享、资源下载等服务，提供原创、优质、完整内容的专业IT技术开发社区（前沿技术分享,你我共同成长）。
引言：打破操作系统壁垒在日常工作中，Linux和Windows系统共存已成为常态。作为CentOS用户，您可能需要访问Windows服务器上的共享文件资源。本文将详细介绍如何在CentOS系统中挂载Windows共享目录，实现跨平台文件共享的无缝体验。核心优势：无需第三方软件命令行与图形界面双方法支持永久挂载配置解决常见连接问题一、前期准备1.1 Windows端配置创建共享文件夹（右键 > 属性 > 共享）设置共享权限（添加用户并分配权限）获取关键信息：Windows主机IP地址（cmd中运行 ipconfig）共享名称（如 ShareFolder）用户名和密码1.2 防火墙设置在Linux访问局域网Windows系统中的共享目录，需要设置Windows服务器的防火墙。开放”ICMPv4”访问：打开防火墙设置在 Windows 上，打开“控制面板”。点击“系统和安全”。点击“Windows Defender 防火墙”。在左侧，点击“高级设置”。创建“允许ICMPv4”入站规则在“Windows Defender 防火墙高级安全”窗口中，选择“入站规则”。在右侧，点击“新建规则”。选择“自定义”，然后点击“下一步”。在“规则类型”中，选择“所有程序”，然后点击“下一步”。在“协议和端口”中，选择“ICMPv4”，然后点击“下一步”。在“作用域”中，选择“任何 IP 地址”，然后点击“下一步”。（考虑主机访问安全，建议此处只允许放行Linux对应的IP地址）在“操作”中，选择“允许连接”，然后点击“下一步”。在“配置文件”中，选择适当的配置文件（域、私有、公共），然后点击“下一步”。给规则命名，例如“允许 ICMP”，然后点击“完成”。开放445访问端口重复上述防火墙操作步骤，相关配置修改如下：选择入站规则：在左侧，选择“入站规则”。新建规则：在右侧，点击“新建规则”。选择规则类型：选择“自定义”，然后点击“下一步”。选择程序：选择“所有程序”，然后点击“下一步”。选择协议和端口：在“协议类型”中，选择“TCP”。在“特定本地端口”中，输入 445（这是 SMB/CIFS 使用的端口），然后点击“下一步”。选择作用域：在“远程 IP 地址”中，选择“这些 IP 地址”，然后添加你的 Linux 机器的 IP 地址（例如 192.168.1.100）。如果你希望允许整个子网，可以输入 192.168.1.0/24。点击“下一步”。选择操作：选择“允许连接”，然后点击“下一步”。选择配置文件：选择适当的配置文件（域、私有、公共），然后点击“下一步”。命名规则：给规则命名，例如“允许 Linux 访问 SMB”，然后点击“完成”。在Windows上的防火墙相关配置已经结束，建议创建Windows共享访问账户，找到需要共享的目录执行创建共享操作。1.3 CentOS端准备确保网络连通性：ping 192.168.1.100 # 替换为Windows主机IP更新系统：sudo yum update -y二、安装必要软件包CentOS需要安装CIFS工具集来支持SMB协议：sudo yum install cifs-utils samba-client -y验证安装：rpm -q cifs-utils # 预期输出：cifs-utils-版本号三、命令行挂载方法3.1 创建本地挂载点sudo mkdir /mnt/win_share3.2 临时挂载共享目录sudo mount -t cifs //192.168.1.100/ShareFolder /mnt/win_share \ -o username=windows_user,password=your_password参数说明：-t cifs：指定文件系统类型//192.168.1.100/ShareFolder：Windows共享路径/mnt/win_share：本地挂载点username/password：Windows凭据3.3 验证挂载df -hT | grep cifs # 输出应显示挂载的共享目录 ls /mnt/win_share # 查看共享文件四、图形界面挂载方法（适合桌面版CentOS）打开文件管理器在地址栏输入：smb://192.168.1.100/ShareFolder输入Windows用户名和密码点击"连接"按钮连接成功后：共享目录将出现在侧边栏支持拖放文件操作书签功能可保存常用共享五、高级配置与优化5.1 永久挂载（配置/etc/fstab）编辑fstab文件实现开机自动挂载：sudo nano /etc/fstab添加以下内容：//192.168.1.100/ShareFolder /mnt/win_share cifs \ username=windows_user,password=your_password,uid=1000,gid=1000,file_mode=0777,dir_mode=0777 0 0参数解析：uid/gid：指定用户/组ID（通过 id命令查看）file_mode/dir_mode：设置文件/目录权限0 0：不备份不检查应用配置：sudo mount -a # 测试fstab配置5.2 安全凭据存储创建凭据文件避免密码明文暴露：sudo nano /etc/samba/credentials内容：username=windows_user password=your_password设置严格权限：sudo chmod 600 /etc/samba/credentials在fstab中使用凭据文件：//192.168.1.100/ShareFolder /mnt/win_share cifs \ credentials=/etc/samba/credentials,uid=1000,gid=1000 0 0六、故障排除指南6.1 常见错误及解决方案错误1：挂载失败（权限不足）mount error(13): Permission denied解决方案：确认Windows共享权限检查用户名/密码是否正确在挂载命令中添加 sec=ntlm选项错误2：协议版本不匹配Server supports SMB version X, but supports only Y解决方案：指定SMB版本-o vers=2.1 # 或 vers=3.0错误3：SELinux阻止访问SELinux is preventing access...解决方案：sudo setsebool -P samba_enable_home_dirs on sudo restorecon -Rv /mnt/win_share6.2 连接测试工具查看可用共享：smbclient -L //192.168.1.100 -U windows_user手动连接测试：smbclient //192.168.1.100/ShareFolder -U windows_user七、最佳实践建议网络安全：使用专用网络而非公共网络共享定期更新Windows和CentOS系统考虑使用VPN进行远程访问性能优化：-o cache=strict,rsize=65536,wsize=65536增加读写缓存大小禁用不必要的文件属性备份策略：# 示例备份命令 rsync -avz /mnt/win_share/ /backup/windows_share/定期同步重要数据使用cron设置自动化备份结论：无缝跨平台协作通过本指南，您已掌握在CentOS中访问Windows共享资源的多种方法。无论是命令行还是图形界面，临时访问还是永久挂载，都能满足不同场景的需求。跨平台文件共享不再是技术障碍，而是提高工作效率的桥梁。技术演进提示：随着SMB 3.1.1协议的普及，未来可探索更安全的加密传输方式，如AES-128-GCM加密，进一步提升数据传输安全性。如果在实践过程中遇到任何问题，欢迎在评论区留言讨论！
基于C#和MailKit构建企业级邮件客户端的实战指南
一、场景价值与工具选型1.1 企业邮件系统需求自动化处理工单邮件系统监控报警通知批量发送业务报表邮件数据归档分析1.2 为什么选择MailKit？🔐 全面支持SSL/TLS加密📦 完善的MIME消息处理⚡ 真正的异步IO实现📈 活跃的开源社区支持二、快速搭建开发环境2.1 创建项目与依赖安装# 创建控制台项目 dotnet new console -n EmailClient # 添加NuGet包 dotnet add package MailKit dotnet add package MimeKit2.2 配置邮箱连接参数public class EmailConfig { // 腾讯企业邮箱示例 public const string ImapServer = "imap.exmail.qq.com"; public const int ImapPort = 993; public const string SmtpServer = "smtp.exmail.qq.com"; public const int SmtpPort = 465; // 实际项目建议使用配置文件或密钥管理服务 public const string Username = "your@company.com"; public const string Password = "your_auth_code"; }三、核心功能实现详解3.1 未读邮件处理模块public void ProcessUnreadEmails() { using var client = new ImapClient(); // 建立安全连接 client.Connect(EmailConfig.ImapServer, EmailConfig.ImapPort, SecureSocketOptions.SslOnConnect); client.Authenticate(EmailConfig.Username, EmailConfig.Password); var inbox = client.Inbox; inbox.Open(FolderAccess.ReadWrite); // 高效搜索未读邮件 var uids = inbox.Search(SearchQuery.NotSeen); foreach (var uid in uids) { var message = inbox.GetMessage(uid); ProcessMessage(message); // 标记为已读（符合企业合规要求） inbox.AddFlags(uid, MessageFlags.Seen, true); } } private void ProcessMessage(MimeMessage message) { Console.WriteLine($"\n📨 收到新邮件 [{message.Date}]"); Console.WriteLine($"主题：{message.Subject}"); Console.WriteLine($"发件人：{message.From}"); // 智能解析正文内容 var textPart = message.TextBody; var htmlPart = message.HtmlBody; Console.WriteLine($"内容预览：{textPart?.Truncate(100)}"); // 安全保存附件 SaveAttachments(message.Attachments); } /// /// 安全保存邮件附件 /// /// 附件集合 private void SaveAttachments(IEnumerable<MimeEntity> attachments) { // 创建附件存储目录 var attachmentDir = Path.Combine(Directory.GetCurrentDirectory(), "Attachments"); Directory.CreateDirectory(attachmentDir); int index = 1; foreach (var attachment in attachments) { try { string fileName; if (attachment is MessagePart rfc822) { // 处理邮件型附件（.eml） fileName = $"forward_{DateTime.Now:yyyyMMddHHmmss}_{index}.eml"; using var stream = File.Create(Path.Combine(attachmentDir, fileName)); rfc822.Message.WriteTo(stream); } else if (attachment is MimePart part) { // 获取安全文件名 fileName = SanitizeFileName(part.FileName ?? $"unnamed_{index}.bin"); // 保存普通附件 using var stream = File.Create(Path.Combine(attachmentDir, fileName)); part.Content.DecodeTo(stream); } else { Console.WriteLine("⚠️ 未知附件类型"); continue; } Console.WriteLine($"📎 保存附件：{fileName}"); index++; } catch (Exception ex) { Console.WriteLine($"❗ 附件保存失败：{ex.Message}"); } } } /// /// 清理非法文件名字符 /// private string SanitizeFileName(string fileName) { var invalidChars = Path.GetInvalidFileNameChars(); return new string(fileName .Where(c => !invalidChars.Contains(c)) .ToArray()) .Replace(" ", "_"); // 替换空格为下划线 }关键技术点：使用 SearchQuery.NotSeen精准定位未读邮件通过 FolderAccess.ReadWrite实现已读标记自动处理多部分MIME消息体附件处理最佳实践路径安全：// 使用Path组合防止路径注入 var safePath = Path.Combine(attachmentDir, fileName);大文件处理：// 使用文件流分块读取 using var fs = new FileStream(path, FileMode.Create, FileAccess.Write); await part.Content.DecodeToAsync(fs);类型过滤：// 限制可接收的附件类型 var allowedTypes = new[] { "application/pdf", "image/png" }; if (!allowedTypes.Contains(part.ContentType.MimeType)) { Console.WriteLine("🚫 禁止的文件类型"); continue; }病毒扫描：// 调用杀毒软件接口（示例使用Windows Defender） var scanResult = Process.Start("MpCmdRun.exe", $"-Scan -ScanType 3 -File \"{filePath}\""); if (scanResult.ExitCode != 0) { File.Delete(filePath); }3.2 智能邮件发送模块public async Task SendEmailAsync() { var message = new MimeMessage(); message.From.Add(new MailboxAddress("系统通知", EmailConfig.Username)); // 支持多种分隔符的收件人输入 Console.Write("收件人（多个用逗号分隔）："); var recipients = Console.ReadLine()? .Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries); message.To.AddRange(recipients.Select(MailboxAddress.Parse)); // 构建富文本内容 Console.Write("邮件主题："); message.Subject = Console.ReadLine(); var builder = new BodyBuilder(); Console.Write("HTML内容："); builder.HtmlBody = Console.ReadLine(); // 处理混合附件 Console.Write("附件路径（支持本地/网络地址）："); await ProcessAttachmentsAsync(builder, Console.ReadLine()); message.Body = builder.ToMessageBody(); using var client = new SmtpClient(); await client.ConnectAsync(EmailConfig.SmtpServer, EmailConfig.SmtpPort, SecureSocketOptions.SslOnConnect); await client.AuthenticateAsync(EmailConfig.Username, EmailConfig.Password); await client.SendAsync(message); }附件处理关键技术：private async Task ProcessAttachmentsAsync(BodyBuilder builder, string input) { foreach (var item in input.Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries)) { var path = item.Trim(); if (Uri.IsWellFormedUriString(path, UriKind.Absolute)) { await AddWebAttachmentAsync(builder, new Uri(path)); } else if (File.Exists(path)) { builder.Attachments.Add(path); } } } private async Task AddWebAttachmentAsync(BodyBuilder builder, Uri uri) { try { using var httpClient = new HttpClient(); var response = await httpClient.GetAsync(uri); var fileName = Path.GetFileName(uri.LocalPath) ?? "attachment.bin"; builder.Attachments.Add(fileName, await response.Content.ReadAsStreamAsync()); } catch (Exception ex) { Console.WriteLine($"⚠️ 附件下载失败：{uri} \n错误：{ex.Message}"); } }四、企业级最佳实践4.1 安全防护方案// 在Program初始化时添加 ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, errors) => { // 生产环境应验证证书指纹 return errors == SslPolicyErrors.None; };4.2 性能优化策略使用 ObjectPool管理SMTP/IMAP连接对大型附件启用分块传输异步处理CPU密集型操作4.3 可观测性设计// 在客户端初始化时添加 client.Connected += (s, e) => Logger.LogInformation($"已连接 {e.Host}:{e.Port}"); client.Disconnected += (s, e) => Logger.LogWarning($"连接断开：{(e.IsRequested ? "正常" : "异常")}");五、完整控制台实现using System.Net; using MailKit.Net.Imap; using MailKit.Net.Smtp; using MailKit.Search; using MailKit.Security; using MimeKit; namespace EnterpriseEmailClient; class Program { static async Task Main(string[] args) { Console.Title = "企业邮件客户端 v1.0"; Console.OutputEncoding = System.Text.Encoding.UTF8; try { Console.WriteLine("=== 企业邮件管理系统 ==="); Console.WriteLine("1. 收取未读邮件"); Console.WriteLine("2. 发送邮件"); Console.WriteLine("======================="); Console.Write("请选择操作："); var choice = Console.ReadLine(); if (choice == "1") { ProcessUnreadEmails(); } else if (choice == "2") { await SendEmailAsync(); } else { Console.WriteLine("无效操作！"); } } catch (Exception ex) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine($"操作失败：{ex.Message}"); Console.ResetColor(); } finally { Console.WriteLine("\n按任意键退出..."); Console.ReadKey(); } } // 以下是之前提到的 ProcessUnreadEmails() 和 SendEmailAsync() 方法 // [此处插入3.1和3.2节的代码实现...] }关键实现解析异步主方法：static async Task Main(string[] args)使用C# 7.1+的异步Main方法特性需要在项目文件添加 <LangVersion>latest</LangVersion>异常处理增强：catch (AuthenticationException) { Console.WriteLine("认证失败，请检查用户名/密码"); } catch (SocketException) { Console.WriteLine("网络连接失败，请检查服务器配置"); }用户体验优化：// 显示彩色状态提示 Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine("✓ 邮件收取完成"); Console.ResetColor();六、项目文件配置（.csproj）<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net6.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> <LangVersion>latest</LangVersion> </PropertyGroup> <ItemGroup> <PackageReference Include="MailKit" Version="3.4.3" /> <PackageReference Include="MimeKit" Version="3.4.3" /> </ItemGroup> </Project>完整代码结构树EnterpriseEmailClient/ ├── Program.cs # 主入口/界面逻辑 ├── Services/ │ ├── EmailReceiver.cs # 邮件接收服务 │ └── EmailSender.cs # 邮件发送服务 ├── Models/ │ └── EmailConfig.cs # 配置模型 └── Utils/ └── FileHelper.cs # 附件处理工具七、扩展开发建议邮件智能分类：集成ML.NET实现邮件自动分类审计日志系统：记录所有邮件操作记录邮件撤回功能：实现Exchange撤回协议大文件分卷压缩：自动处理超大附件八、常见问题解答Q1：如何解决附件乱码问题？// 强制指定附件编码 var attachment = new MimePart("application", "octet-stream") { Content = new MimeContent(File.OpenRead(path)), ContentDisposition = new ContentDisposition(ContentDisposition.Attachment), ContentTransferEncoding = ContentEncoding.Base64, FileName = Encoding.UTF8.GetBytes(filename) };Q2：如何处理邮件发送超时？var client = new SmtpClient { Timeout = 30000, // 30秒超时 ServerCertificateValidationCallback = (s,c,h,e) => true };九、项目成果展示未读邮件数量：3 📨 收到新邮件 [2023-08-20 14:30:00] 主题：季度销售报告 发件人：Sales <sales@partner.com> 内容预览：本季度总销售额达到$1,200,000，同比... 已保存附件：Q3_Sales_Report.pdf ✅ 邮件发送成功！耗时：2.3s
基于Tesseract.js的智能手写文字识别工具开发
引言随着人工智能和机器学习技术的快速发展，文字识别（OCR）技术已经成为了许多应用场景中的核心功能。无论是从图像中提取文本，还是识别手写文字，OCR技术都在不断进步。本文将介绍如何使用Tesseract.js开发一个基于Web的智能手写文字识别工具，并分享一些开发过程中的技术细节。项目概述本项目是一个基于Tesseract.js的在线手写文字识别系统，用户可以在网页上通过鼠标或触摸屏手写文字，系统会自动识别并显示识别结果。该工具支持笔画粗细调整、自动识别、图像预处理等功能，适用于多种场景下的手写文字识别需求。技术栈Tesseract.js ：一个基于Tesseract OCR引擎的JavaScript库，支持在浏览器中进行文字识别。HTML5 Canvas ：用于绘制手写文字的画布。JavaScript ：处理用户交互、图像预处理和文字识别逻辑。实现细节1. 初始化Tesseract OCR引擎Tesseract.js是一个强大的OCR库，支持多种语言的文字识别。在项目中，我们首先需要初始化Tesseract引擎，并加载中文和英文的语言模型。async function initTesseract() { try { scheduler = Tesseract.createScheduler(); worker = await Tesseract.createWorker({ logger: m => console.log(m) }); // 加载中文和英文语言包 await worker.loadLanguage('chi_sim+eng'); await worker.initialize('chi_sim+eng'); // 设置识别参数 await worker.setParameters({ tessedit_char_whitelist: '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ你我他的是在有和人这中大为上个国我的时要出会可也得里说之用年行家方后作成开面而于经', tessedit_pageseg_mode: '6', // 假设文本是统一块 }); scheduler.addWorker(worker); console.log('Tesseract 初始化完成'); } catch (error) { console.error('Tesseract 初始化失败:', error); } }2. 画布绘制与交互用户可以通过鼠标或触摸屏在HTML5 Canvas上绘制手写文字。我们通过监听鼠标和触摸事件来捕获用户的绘制动作，并将绘制的笔画存储在一个数组中，以便后续的重绘和识别。function startDrawing(e) { isDrawing = true; const rect = canvas.getBoundingClientRect(); [lastX, lastY] = [ e.clientX - rect.left, e.clientY - rect.top ]; currentStroke = [{x: lastX, y: lastY}]; // 清除之前的识别计时器 if (recognizeTimer) { clearTimeout(recognizeTimer); recognizeTimer = null; } } function draw(e) { if (!isDrawing) return; const rect = canvas.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; ctx.beginPath(); ctx.moveTo(lastX, lastY); ctx.lineTo(x, y); ctx.stroke(); currentStroke.push({x: x, y: y}); [lastX, lastY] = [x, y]; } function stopDrawing() { if (isDrawing) { isDrawing = false; strokes.push(currentStroke); currentStroke = []; scheduleRecognition(); } }3. 自动识别与图像预处理为了提高识别准确率，我们提供了自动识别功能。当用户停止书写1.5秒后，系统会自动触发文字识别。此外，用户还可以选择是否对图像进行预处理，如二值化处理，以增强识别效果。function scheduleRecognition() { if (!document.getElementById('autoRecognize').checked) return; if (recognizeTimer) { clearTimeout(recognizeTimer); } recognizeTimer = setTimeout(() => { recognizeText(); }, 1500); // 1.5秒后自动识别 } function preprocessImage(imageData) { const canvas2 = document.createElement('canvas'); canvas2.width = imageData.width; canvas2.height = imageData.height; const ctx2 = canvas2.getContext('2d'); ctx2.putImageData(imageData, 0, 0); const data = ctx2.getImageData(0, 0, canvas2.width, canvas2.height); const pixels = data.data; // 二值化处理 for (let i = 0; i < pixels.length; i += 4) { const gray = (pixels[i] + pixels[i + 1] + pixels[i + 2]) / 3; const value = gray > 128 ? 255 : 0; pixels[i] = pixels[i + 1] = pixels[i + 2] = value; } ctx2.putImageData(data, 0, 0); return canvas2.toDataURL('image/png'); }4. 文字识别与结果显示当用户完成书写后，系统会将Canvas中的图像数据传递给Tesseract.js进行识别，并将识别结果显示在页面上。async function recognizeText() { if (!scheduler || !worker) { console.log('Tesseract未初始化'); return; } const loading = document.getElementById('loading'); const result = document.getElementById('result'); loading.style.display = 'block'; result.textContent = '正在识别中...'; try { let imageData; if (document.getElementById('preprocess').checked) { imageData = preprocessImage(ctx.getImageData(0, 0, canvas.width, canvas.height)); } else { imageData = canvas.toDataURL('image/png'); } const { data: { text } } = await scheduler.addJob('recognize', imageData); result.textContent = `识别结果: ${text.trim()}`; } catch (error) { console.error(error); result.textContent = '识别失败: ' + error.message; } finally { loading.style.display = 'none'; } }总结通过本项目，我们实现了一个基于Tesseract.js的智能手写文字识别工具。该工具不仅支持手写文字的实时识别，还提供了图像预处理、自动识别等功能，能够有效提高识别准确率。未来，我们可以进一步优化识别算法，增加更多语言支持，并将其应用于更多实际场景中。完整代码： 智能手写文字识别 智能手写文字识别 笔画粗细: 8 预处理: 增强对比度 自动识别(停笔1.5秒后) 清除 撤销 正在识别中... 准备就绪，请开始书写... © 2025 by Arvin-Hugh. All rights reserved.
迄今为止，程序员最佳输入法非Rime莫属
基本介绍很称心如意，很符合我的使用标准，无广告、无弹窗、不联网、词库完全自定义。界面风格全靠手码，千人千面，自己想要什么，自己画。关键还是跨平台的产品，不仅能在Windows上面跑，还能在MacOS和Linux上面运行，只是名字有点特殊，Windows叫：小狼毫，MacOS叫：鼠须管。如何安装和下载官网：Rime下载地址：在Github上面找到release面板即可下载 （下载0.16.3）安装：无脑操作，直接下一步下一步即可，无须特殊配置雾凇拼音GitHub地址：iDvel / rime-ice下载安装完成之后，强烈推荐安装的词库，符合大部分人的输入要求，下载完成后，进入“用户文件夹”，将下载的文件解压，然后点击“重新部署”，在点击“输入法设定“，选择自己需要的输入方案重头戏，如何配置皮肤进入用户文件夹后，找到“weasel.custome.yaml”文件，直接编辑，这里我编写了两套皮肤，是模仿苹果的，我觉得还挺好看，给大伙看看效果：如果还需要弄成其他的颜色，大家也可以大胆尝试。如果你们喜欢这个配色，可以直接将下面的代码复制到“weasel.custome.yaml”文件中，完成后需要点击重新部署才能使用。customization: distribution_code_name: Weasel distribution_version: 0.16.3 generator: "Weasel::UIStyleSettings" modified_time: "Fri Nov 15 09:28:21 2024" rime_version: 1.11.2 patch: preset_color_schemes: mac_blue_write: {author: arvin, back_color: 0xFFFFFFFF, border_color: 0xd2d2d2FF, candidate_format: "%c\u2005%@\u2005", candidate_back_color: 0xFFFFFFFF, candidate_text_color: 0x333333FF, corner_radius: 6, hilited_corner_radius: 6, color_format: rgba, comment_text_color: 0x5C5C5CFF, hilited_back_color: 0x315efb1a, hilited_candidate_back_color: 0x315efb1a, hilited_candidate_text_color: 0x0158ccFF, hilited_comment_text_color: 0x0158ccFF, hilited_label_color: 0x315efbFF, hilited_text_color: 0x0158ccFF, label_color: 0x333333FF, name: "苹果蓝白", shadow_color: 0x20212447, text_color: 0x333333FF} mac_blue_dark: {author: arvin, back_color: 0x181818, border_color: 0x0808080D, border_width: 0, candidate_format: "%c\u2005%@\u2005", candidate_back_color: 0x181818, candidate_text_color: 0xFFFFFF, color_format: abgr, comment_text_color: 0xFFFFFFFF, hilited_back_color: 0xFFc85c2f, hilited_candidate_back_color: 0xFFc85c2f, hilited_candidate_text_color: 0xFFFFFFFF, hilited_comment_text_color: 0xFFFFFF, hilited_label_color: 0xFFFFFF, hilited_text_color: 0xFFFFFF, label_color: 0xFFFFFF, name: "苹果黑", shadow_color: 0x080808DE, text_color: 0xFF333333} style: color_scheme: mac_blue_dark comment_font_face: "PingFang SC" comment_font_point: 11 display_tray_icon: true font_face: "Segoe UI Emoji, PingFang SC, Microsoft YaHei, SF Pro, Noto Color Emoji" font_point: 11 horizontal: true inline_preedit: true label_font_face: "PingFang SC" label_font_point: 10 label_format: "%s" layout: {align_type: bottom, border_width: 1, candidate_spacing: 16, corner_radius: 6, hilite_padding: 4, hilite_spacing: 3, margin_x: 7, margin_y: 7, max_height: 60, round_corner: 4, shadow_offset_x: 2, shadow_offset_y: 2, shadow_radius: 6, spacing: 5} mark_text: "" preedit_type: composition "style/color_scheme": mac_blue_write app_options: code.exe: {ascii_punct: true} #在VS Code中默认使用英文标点 hbuilderx.exe: {ascii_punct: true} #HBuilderX navicat.exe: {ascii_punct: true} devenv.exe: {ascii_punct: true} #VS 2022不喜欢，没关系，这里还有在线皮肤设计，你想怎么设计，就怎么设计：皮肤在线设计：润笔 、 RIME 西米 其他还有很多可以自定义的地方，比如自定义词典啥的，我这边也没有来得及多研究，总之挺好用，大家都用上！！
Java 格式化输出 printf 用法符号解释使用案例%s输出字符串System.out.printf("你好,我叫:%s",myName);%d输出整数System.out.printf("你好,今年:%d岁",age);%f输出小数System.out.printf("你好,今天一共消费:%f元",money);%n输出换行 %b输出布尔类型 %S输出字符串,英文大写 import java.util.Date; /** * 使用printf输出 * 关键技术点 * 使用java.io.PrintStream的printf方法实现C风格的输出 * printf 方法的第一个参数为输出的格式,第二个参数是可变长的,表示待输出的数据对象 */ public class Demo { public static void main(String[] args) { /*** 输出字符串 ***/ // %s表示输出字符串，也就是将后面的字符串替换模式中的%s System.out.printf("%s", new Integer(1212)); // %n表示换行 System.out.printf("%s%n", "end line"); // 还可以支持多个参数 System.out.printf("%s = %s%n", "Name", "Zhangsan"); // %S将字符串以大写形式输出 System.out.printf("%S = %s%n", "Name", "Zhangsan"); // 支持多个参数时，可以在%s之间插入变量编号，1$表示第一个字符串，3$表示第3个字符串 System.out.printf("%1$s = %3$s %2$s%n", "Name", "san", "Zhang"); /*** 输出boolean类型 ***/ System.out.printf("true = %b; false = ", true); System.out.printf("%b%n", false); /*** 输出整数类型***/ Integer iObj = 342; // %d表示将整数格式化为10进制整数 System.out.printf("%d; %d; %d%n", -500, 2343L, iObj); // %o表示将整数格式化为8进制整数 System.out.printf("%o; %o; %o%n", -500, 2343L, iObj); // %x表示将整数格式化为16进制整数 System.out.printf("%x; %x; %x%n", -500, 2343L, iObj); // %X表示将整数格式化为16进制整数，并且字母变成大写形式 System.out.printf("%X; %X; %X%n", -500, 2343L, iObj); /*** 输出浮点类型***/ Double dObj = 45.6d; // %e表示以科学技术法输出浮点数 System.out.printf("%e; %e; %e%n", -756.403f, 7464.232641d, dObj); // %E表示以科学技术法输出浮点数，并且为大写形式 System.out.printf("%E; %E; %E%n", -756.403f, 7464.232641d, dObj); // %f表示以十进制格式化输出浮点数 System.out.printf("%f; %f; %f%n", -756.403f, 7464.232641d, dObj); // 还可以限制小数点后的位数 System.out.printf("%.1f; %.3f; %f%n", -756.403f, 7464.232641d, dObj); /*** 输出日期类型***/ // %t表示格式化日期时间类型，%T是时间日期的大写形式，在%t之后用特定的字母表示不同的输出格式 Date date = new Date(); long dataL = date.getTime(); // 格式化年月日 // %t之后用y表示输出日期的年份（2位数的年，如99） // %t之后用m表示输出日期的月份，%t之后用d表示输出日期的日号 System.out.printf("%1$ty-%1$tm-%1$td; %2$ty-%2$tm-%2$td%n", date, dataL); // %t之后用Y表示输出日期的年份（4位数的年）， // %t之后用B表示输出日期的月份的完整名， %t之后用b表示输出日期的月份的简称 System.out.printf("%1$tY-%1$tB-%1$td; %2$tY-%2$tb-%2$td%n", date, dataL); // 以下是常见的日期组合 // %t之后用D表示以 "%tm/%td/%ty"格式化日期 System.out.printf("%1$tD%n", date); //%t之后用F表示以"%tY-%tm-%td"格式化日期 System.out.printf("%1$tF%n", date); /*** 输出时间类型***/ // 输出时分秒 // %t之后用H表示输出时间的时（24进制），%t之后用I表示输出时间的时（12进制）， // %t之后用M表示输出时间的分，%t之后用S表示输出时间的秒 System.out.printf("%1$tH:%1$tM:%1$tS; %2$tI:%2$tM:%2$tS%n", date, dataL); // %t之后用L表示输出时间的秒中的毫秒 System.out.printf("%1$tH:%1$tM:%1$tS %1$tL%n", date); // %t之后p表示输出时间的上午或下午信息 System.out.printf("%1$tH:%1$tM:%1$tS %1$tL %1$tp%n", date); // 以下是常见的时间组合 // %t之后用R表示以"%tH:%tM"格式化时间 System.out.printf("%1$tR%n", date); // %t之后用T表示以"%tH:%tM:%tS"格式化时间 System.out.printf("%1$tT%n", date); // %t之后用r表示以"%tI:%tM:%tS %Tp"格式化时间 System.out.printf("%1$tr%n", date); /*** 输出星期***/ // %t之后用A表示得到星期几的全称 System.out.printf("%1$tF %1$tA%n", date); // %t之后用a表示得到星期几的简称 System.out.printf("%1$tF %1$ta%n", date); // 输出时间日期的完整信息 System.out.printf("%1$tc%n", date); } } /** *printf方法中,格式为"%s"表示以字符串的形式输出第二个可变长参数的第一个参数值; *格式为"%n"表示换行;格式为"%S"表示将字符串以大写形式输出;在"%s"之间用"n$"表示 *输出可变长参数的第n个参数值.格式为"%b"表示以布尔值的形式输出第二个可变长参数 *的第一个参数值. */ /** * 格式为"%d"表示以十进制整数形式输出;"%o"表示以八进制形式输出;"%x"表示以十六进制 * 输出;"%X"表示以十六进制输出,并且将字母(A、B、C、D、E、F)换成大写.格式为"%e"表 * 以科学计数法输出浮点数;格式为"%E"表示以科学计数法输出浮点数,而且将e大写;格式为 * "%f"表示以十进制浮点数输出,在"%f"之间加上".n"表示输出时保留小数点后面n位. */ /** * 格式为"%t"表示输出时间日期类型."%t"之后用y表示输出日期的二位数的年份(如99)、用m * 表示输出日期的月份,用d表示输出日期的日号;"%t"之后用Y表示输出日期的四位数的年份 * (如1999)、用B表示输出日期的月份的完整名,用b表示输出日期的月份的简称."%t"之后用D * 表示以"%tm/%td/%ty"的格式输出日期、用F表示以"%tY-%tm-%td"的格式输出日期. */ /** * "%t"之后用H表示输出时间的时(24进制),用I表示输出时间的时(12进制),用M表示输出时间 * 分,用S表示输出时间的秒,用L表示输出时间的秒中的毫秒数、用 p 表示输出时间的是上午还是 * 下午."%t"之后用R表示以"%tH:%tM"的格式输出时间、用T表示以"%tH:%tM:%tS"的格式输出 * 时间、用r表示以"%tI:%tM:%tS %Tp"的格式输出时间. */ /** * "%t"之后用A表示输出日期的全称,用a表示输出日期的星期简称. */
AxWindowsMediaPlayer (Windows Media Play)控件的常用属性、方法及事件
概述AxWindowsMediaPlayer 对象是 Windows 媒体播放器 控件的根对象。本案例AxWindowsMediaPlayer控件取名: axPlay常用属性 [表-1]属性名数据类型描述或用法URLstring设置或获取媒体位置，本机或网络地址uiModestring设置或获取播放器的页面显示模式取值范围：Full, Mini, None, InvisibleplayStateWMPLib.WMPPlayState获取播放状态常见取值范围：wmppsStopped(停止)wmppsPaused(暂停)wmppsPlaying(正在播放)wmppsBuffering(正在缓冲)wmppsTransitioning(正在连接)wmppsReady(准备就绪)enableContextMenubool是否启用右键菜单fullScreenbool是否全屏显示Ctlcontrols-播放器控制属性，请查考[表-2]settings-播放器基本设置，请查考[表-3]currentMedia-播放器当前媒体，请查考[表-4]currentPlaylist-播放器当前播放列表，请查考[表-5]// 设置播放器的播放路径 axPlay.URL = @"C:\Demo\我爱的人.mp4"; // 设置播放器的页面显示模式 axPlay.uiMode = "Full"; // 获取播放器播放状态 var state = axPlay.playState; if(state == WMPLib.WMPPlayState.wmppsStopped) { // 播放器已停止播放 } // 禁用右键菜单 axPlay.enableContextMenu = false; // 开启全屏显示 axPlay.fullScreen = true;播放器基本控制属性常用方法 [表-2] [使用案例: axPlay.Ctlcontrols]方法名/属性名返回值类型描述或用法axPlay.Ctlcontrols.play()void控制播放器进行播放axPlay.Ctlcontrols.pause()void暂停axPlay.Ctlcontrols.stop()void停止axPlay.Ctlcontrols.next()void播放下一个媒体元素[需要提供播放列表]axPlay.Ctlcontrols.previous()void播放上一个媒体元素[需要提供播放列表]axPlay.Ctlcontrols.fastForward()void快进axPlay.Ctlcontrols.fastReverse()void快退axPlay.Ctlcontrols.currentPositiondouble获取或设置当前播放进度axPlay.Ctlcontrols.currentPositionStringstring获取当前播放进度,返回字符串格式。如“00:23”播放器基本控制属性常用方法 [表-3] [使用案例: axPlay.settings]方法名/属性名返回值类型描述或用法axPlay.settings.volumeint设置或获取音量，0-100axPlay.settings.autoStartbool是否自动播放axPlay.settings.mutebool是否开启静音axPlay.settings.playCountint播放次数播放器基本控制属性常用方法 [表-4] [使用案例: axPlay.currentMedia]方法名/属性名返回值类型描述或用法axPlay.currentMedia.durationdouble媒体总长度axPlay.currentMedia.durationStringstring媒体总长度，字符串格式。如“03:24”axPlay.currentMedia.getItemInfo(string bstrItemName)string获取当前媒体信息，bstrItemName参数填写字符串如下: "Title"=媒体标题"Author"=艺术家"Copyright"=版权信息"Description"=媒体内容描述"Duration"=持续时间（秒）"FileSize"=文件大小"FileType"=文件类型"sourceURL"=原始地址axPlay.currentMedia.setItemInfo(string bstrItemName, string bstrVal)void通过属性名设置媒体信息播放器基本控制属性常用方法 [表-5] [使用案例: axPlay.currentPlaylist]方法名/属性名返回值类型描述或用法axPlay.currentPlaylist.countint当前播放列表所包含媒体数axPlay.currentPlaylist.Item[int index]-获取或设置指定项目媒体信息，其子属性同wmp.currentMedia// 使当前播放器播放 axPlay.Ctlcontrols.play(); // 暂停 axPlay.Ctlcontrols.pause(); // 停止 axPlay.Ctlcontrols.stop(); // 播放下一个媒体 axPlay.Ctlcontrols.next(); // 获取或设置当前播放进度 double position = axPlay.Ctlcontrols.currentPosition; // 获取当前播放进度,返回字符串格式。如“00:23” string positions = axPlay.Ctlcontrols.currentPositionString; // 设置音量 axPlay.settings.volume = 50; // 设置自动播放 axPlay.settings.autoStart = true; // 开启静音 axPlay.settings.mute = true;[scode type="blue" size=""]如需了解更多，请参阅：Microsoft Ignite[/scode]
建立数据库新建数据库：GoodsInfo新建数据表：Category 【商品类别表】列名称数据类型是否为空说明CIDint否标识列，主键，商品类别编号CNamenvarchar(50)否商品类别名称新建数据表：Goods 【商品信息表】列名称数据类型是否为空说明GIDint否标识列，主键，商品编号GNamenvarchar(50)否商品名称CIDint否商品所属类别编号，外键Pricemoney否商品售价Brandnvarchar(20)否商品品牌名称Inventoryint否商品库存数据表建立结束后，请自行建立表与表之间的关系，并使用INSERT语句向每张表中至少添加3条测试数据。新建项目使用Visual Studio新建控制台应用程序：GoodsManagerSys，并创建数据库帮助类：DBHelper.cs，在数据库帮助类中完成实现对应方法。创建业务处理类：GoodsBusiness.cs，创建以下方法，请自行查看方法说明与注释。方法名返回值参数说明方法说明Menu()void/用于显示主菜单AddGoodsInfo()void/新增商品ShowGoodsCategory()void/显示商品分类列表ShowGoodsList()void/显示商品列表运行效果图为了让你们能够复制显示文本,项目运行效果图将以文本的形式提供----------------商品信息管理系统---------------- 1. 显示所有商品分类信息 2. 显示所有商品信息 3. 新增商品信息 4. 退出 ----------------------------------------------- 请选择操作菜单: 1 ----------------------------------------------- 商品分类编号 商品分类名称 ----------------------------------------------- 1 数码产品 2 家居用品 3 家用百货 4 休闲食品 ----------------------------------------------- 按任意键返回主菜单: x ----------------商品信息管理系统---------------- 1. 显示所有商品分类信息 2. 显示所有商品信息 3. 新增商品信息 4. 退出 ----------------------------------------------- 请选择操作菜单: 2 ----------------------------------------------- 商品编号 商品名称 品牌 售价 分类 库存 ----------------------------------------------- 1 华为Meta60 pro 华为 8999 数码产品 10 2 iPhone 15 pro 苹果 7999 数码产品 29 3 康师傅牛肉面 康师傅 5.5 休闲食品 9000 4 舒肤佳沐浴露 舒肤佳 49.9 家用百货 700 ----------------------------------------------- 按任意键返回主菜单: o ----------------商品信息管理系统---------------- 1. 显示所有商品分类信息 2. 显示所有商品信息 3. 新增商品信息 4. 退出 ----------------------------------------------- 请选择操作菜单: 3 您将要新增商品信息,请按要求依次输入 请输入商品名称: 山泉水 请输入商品品牌: 娃哈哈 请输入商品售价: 2.5 请输入商品入库库存: 200 请选择商品所属类别: ----------------------------------------------- 商品分类编号 商品分类名称 ----------------------------------------------- 1 数码产品 2 家居用品 3 家用百货 4 休闲食品 ----------------------------------------------- 请输入类别编号: 5 很抱歉,您输入的编号无效,请重新输入: 7 很抱歉,您输入的编号无效,请重新输入: 1 商品信息添加成功|失败,请按任意键返回主菜单: q ----------------商品信息管理系统---------------- 1. 显示所有商品分类信息 2. 显示所有商品信息 3. 新增商品信息 4. 退出 ----------------------------------------------- 请选择操作菜单: 4 谢谢您的使用,再见!运行与测试代码编写完成后，请自行测试。测试结束后，数据库无需分离，代码无需关闭，请联系专业课老师检查本次作业。
概述概述： ADO.NET是一组公开数据访问服务的类，可以对数据源进行连接，对数据进行增、删、改、查等操作。ADO.NET为创建分布式数据共享应用程序提供了一组发放的组件。特点： 与数据源断开连接后也可以使用数据ADO.NET核心组件DataSet（数据集）：独立于数据源的数据访问，DataSet是一个内存中的缓存，用于在应用程序中保存从数据库中检索的数据。它可以包含多个DataTable对象，关系和约束，可以方便地在应用程序中进行数据处理。.NET 数据提供程序[scode type="green" size="small"]1️⃣直接对数据源进行操作2️⃣连接数据源、执行命令和检索结果3️⃣实现数据操作和对数据的快速、只进只读访问[/scode].NET 数据提供程序中的4大核心对象[scode type="blue" size="small"]1️⃣Connection：用于开启程序和数据库之间的连接，它是连接数据库的关键组件，可以打开和关闭与数据库的连接。通过Connection对象，你可以指定数据库的位置、凭据、连接超时等参数。2️⃣Command：用于对数据库发出一些操作指令，它允许你执行SQL语句（例如SELECT、INSERT、UPDATE、DELETE等），并从数据库中检索数据或修改数据。Command对象也可以执行存储过程。3️⃣DataAdapter：在数据源和DataSet之间执行数据传输工作，DataAdapter是用于填充DataSet（数据集）和更新数据库的桥梁。它可以从数据库中检索数据，并将其填充到DataSet中的DataTable对象中。同样，它也可以从DataSet中获取更改，并将这些更改更新回数据库。4️⃣DataReader：DataReader是一种只进只读的数据访问方式。当你执行查询并希望逐行读取结果时，DataReader非常有用。它比DataSet更轻量级，对于大型数据集来说，性能更好，因为它不需要将整个结果集加载到内存中。[/scode]Connection连接对象语法：SqlConnection connection = new Sqlconnection(String ConnectionString);Connection对象常用属性和方法：属性🔨 / 方法📦说 明🔨 ConnectionString设置/获取应用程序连接数据库的连接字符串📦 void Open()用于打开与指定数据库的连接📦 void Close()关闭与数据库的连接使用案例演示：// 第一步: 定义链接字符串 private string constr constr = @"server=数据库服务引擎IP地址或主机名称;database=数据库名称;user=用户名;pwd=密码"; /* 数据库链接字符串拼接方式有很多种 * 例如: * Data Source=服务器名称或IP地址;Initial Catalog=数据库名;User Id=用户名;pwd=密码; * Data Source=服务器名称或IP地址;Initial Catalog=数据库名;Integrated Security=True; //Windows集成验证 */ // 第二步: 创建连接对象,此处演示案例为连接SQL Server数据库 SqlConnection connection = new Sqlconnection(constr); //在创建连接对象时,需要提供连接字符串 // 第三步: 打开或关闭连接对象 connection.Open(); //打开连接。如需关闭,请使用Close()方法[scode type="red" size="small"]⚠注意在.NET Core 框架下，不会自动添加System.Data和System.Data.SqlClient的引用，需要在菜单栏，选择"工具(T)" >"NuGet包管理器(N)";在“NuGget – 解决方案”窗口“浏览”选项卡的搜索框中输入“System.Data.SqlClient”关键字;点中搜索列表中匹配的条目，勾选项需要添加引用的项目，点击安装[/scode][scode type="green" size="small"]💬提示：在进行数据库连接或操作数据库时，请在代码中添加合理的try-catch-finally异常处理语句try{ //可能会出现异常的代码 }catch(Exception ex){ //出现异常后进行的处理代码 }finally{ //不管是否出现异常都需要执行的代码 }[/scode]Command命令对象功能:执行返回单条记录的SELECT语句，例如聚合函数SUM、AVG等执行返回具有多条记录的SELECT语句执行DML语句，例如修改、插入、删除记录等执行DDL语句，如创建表语法：SqlCommand command=new SqlCommand(String sql,SqlConnection conn);Command对象常用属性和方法：属性🔨 / 方法📦说 明🔨 ConnectionCommand对象使用的数据库连接🔨 CommandText要执行的SQL语句📦 SqlDataReader ExecuteReader()执行查询命令，返回DataReader对象📦 Object ExecuteScalar()执行查询返回单一值,例如执行COUNT(*)✔执行返回单个值的select语句,如聚合函数SUM、AVG等📦 int ExecuteNonQuery()用于执行非查询类操作， 返回的值是本次操作所影响的行数✔执行DML语句，如修改、插入、删除记录等演示案例-使用Command对象查找BookDB数据库用户表中是否存在指定用户：新建数据库帮助类DBHelper：using System; using System.Data.SqlClient; namespace Demo { /// /// 数据库帮助类 /// public class DBHelper { // 定义数据库连接字符串 private const string constr = @"server=.;database=BookDB;user=sa;pwd=123123"; /// /// 执行SQL语句,返回数据结果集中的第一行的第一列的值 /// /// 需要执行的SQL语句 /// <returns>返回数据结果集中的第一行的第一列的值,会返回NULL</returns> public object ExecuteScalar(string sql) { // 创建数据库连接对象并提供数据库连接字符串凭证 SqlConnection con = new SqlConnection(constr); // 创建命令对象,提供数据库连接对象以及需要执行的SQL语句 SqlCommand cmd = new SqlCommand(sql, con); // 使用异常处理 try { con.Open(); return cmd.ExecuteScalar(); } catch (Exception ex) { Console.WriteLine($"数据库访问异常,错误信息:{ex.Message}"); } finally { con.Close(); } return null; } } }测试类Program：using System; namespace Demo { class Program { static void Main(string[] args) { // 提示用户输入用户名与登录密码 Console.Write("请输入您的用户名:"); string name = Console.ReadLine(); Console.Write("请输入您的登录密码:"); string pwd = Console.ReadLine(); // 拼接SQL语句,判断该用户是否存在 string checkUserInfo = $"SELECT COUNT(0) FROM UserInfo WHERE Name = '{name}' AND Password = '{pwd}'"; // 创建DBHelper对象 DBHelper db = new DBHelper(); // 调用该方法 int res = Convert.ToInt32(db.ExecuteScalar(checkUserInfo)); // 判断执行结果 if (res == 1) { Console.WriteLine("用户登录成功!"); } else { Console.WriteLine("您输入的账号或密码不存在,请稍后再试!"); } } } }DataReader数据读取对象功能:从数据源中检索只读、只进的数据流每次读取一行数据DataReader对象常用属性和方法：属性🔨 / 方法📦说 明🔨 FieldCount获取当前行的列数🔨 HasRows属性获取一个值，该值指示SqlDataReader是否包含一行或多行,如果是则返回true,否则返回false;🔨 IsClosed判断当前对象是否已关闭。若值为true，则表示当前对象已关闭；否则，值为false📦 Read()前进到下一行记录。若读到一条记录，则返回true；否则，返回false📦 Close()关闭DataReader对象演示案例：// 调用DBHelper类中的查询一行的方法 ... SqlDataReader reader = db.ExecuteReader(sql); while (reader != null && reader.HasRows && reader.Read()){ //循环读取数据行 string name = reader["UserName"].ToString(); //...省略其他代码 } // 循环读取结束后,关闭读取器 if(reader != null) { reader.Close(); //关闭DataReader }DataAdapter数据适配器对象功能： 表示一组SQL命令和一个数据库连接，它们用于填充DataSet和更新数据源创建DataAdapter对象：//创建Connection对象 //... con.Open(); SqlDataAdapter adapter = new SqlDataAdapter("查询SQL语句", con);创建DataSet对象，调用DataAdapter对象提供的Fill()方法，将数据从数据源加载到DataSet中：// 创建数据集对象 DataSet ds=new DataSet(); // 填充数据集 adapter.Fill(ds);DataSet简介概述:是数据的脱机容器，是临时存储数据的仓库不包含数据库连接的概念(数据可以来自数据库也可以来自其他类型的文件)DataSet 由一组数据表组成,每个表又由数据列和数据行组成总结
前言这篇文章主要是介绍数据库的基础查询相关操作，适合没有任何数据库操作基础的人员查阅。如果您有相关的数据库开发经验，那这篇文章将对您没有任何帮助。表连接分类联接查询分为内联接、外联接、交叉联接。内连接[scode type="green" size="small"]1️⃣ 最典型、最常用的连接查询2️⃣ 根据两张表中共同的列进行匹配3️⃣ 两个表存在主外键关系时，通常会使用内连接查询[/scode]演示案例表结构分析：图书信息表 [BookInfo]IdNamePriceAuthorCId1C#高级156克里斯琴•内格尔12SQL Server高级编程128美维埃拉13WinForm程序设计58王希斌14美术概论20艾苓2图书分类表 [BookCategory]CIdCategoryName1IT类2艺术类3科普类内连接语法：SELECT 列名 FROM 表名1 INNER JOIN 表名2 ON 表名1.列 = 表名2.列 --INNER可以省略，ON用来设置两个表之间的关联条件 ... WHERE 筛选条件 /* INNER JOIN 关键字后面是关联表，ON关键字后是表间联接字段的关系 */演示案例：SELECT B.Id, C.CategoryName, B.Name, B.Price, B.Author FROM BookInfo B INNER JOIN BookCategory C ON B.CId = C.CId -- 使用WHERE子句实现内连接[不推荐],下面代码执行后可以得到相同的执行结果 SELECT B.Id, C.CategoryName, B.Name, B.Price, B.Author FROM BookInfo B,BookCategory C WHERE B.CId = C.CId执行结果：IdNamePriceAuthorCategoryName1C#高级156克里斯琴•内格尔IT类2SQL Server高级编程128美维埃拉IT类3WinForm程序设计58王希斌IT类4美术概论20艾苓艺术类外连接[scode type="green" size="small"]特点：1️⃣ 外连接可以分为主表和从表2️⃣ 主表的数据会被完全显示出来3️⃣ 从表中只显示满足连接条件的数据4️⃣ 外连接结果一般会比主表和从表中数据量最小的表中的数据多分类：左外连接（LEFT JOIN）右外连接（RIGHT JOIN）完全连接（FULL JOIN）[/scode]演示案例表结构分析：用户信息表 [UserInfo]UserIdUserName1张三2李四3夏颖4李沁图书借阅记录表 [BorrowInfo]BIdUserIdBorrowDate112021-02-03212021-02-03322021-02-05432021-03-05内连接语法：SELECT 列名 FROM 表名1 LEFT [OUTER] JOIN 表名2 ON 表名1.列 = 表名2.列 --OUTER可以省略，ON用来设置两个表之间的关联条件,LEFT可以替换为:RIGHT \ FULL ... WHERE 筛选条件演示案例：-- 统计每位用户的借书情况并显示没有借阅过图书的用户 SELECT U.UserName,B.BorrowDate,B.BId FROM UserInfo U LEFT OUTER JOIN BorrowInfo B ON B.UserId = U.UserId查询结果：UserNameBIdBorrowDate张三12021-02-03张三22021-02-03李四32021-02-05夏颖42021-03-05李沁NULLNULL[scode type="blue" size="small"]主表（左表）UserInfo中的数据逐条匹配表BorrowInfo中的数据• 匹配，返回到结果集• 无匹配，NULL值返回到结果集💬 左外联接是以左表为基础的，左表的记录将会全部表示出来，而右表只会显示符合搜索条件的记录。右表记录不足的地方均为NULL右外连接：右外连接的原理与左外连接相同右表逐条匹配记录，否则使用NULL填充完全连接：会从两个连接表中获取数据如果有对应关系，则会填上对应数据；否则，以NULL值填充[/scode]聚合函数概念：对一组值进行计算，并返回计算后的值，具有统计数据的作用函数名称说明注意SUM()返回表达式中所有值的和• 用于返回表达式中所有值的和• 只能用于数字列• 如果参与计算记录在该列上的值为空值（NULL），则此条记录在计算中将被忽略AVG()返回表达式的平均值• 用于返回表达式中所有数值的平均值• 该列值为空（NULL）的记录将被忽略MIN()返回表达式的最小值• 返回表达式中的最小值• 如果出现空值（NULL），则该记录将被忽略• 用于数值、字符串和日期类型的数据列MAX()返回表达式的最大值• 返回表达式中的最大值，和MIN()注意点一致COUNT()返回查询记录的总个数• 返回提供的组或记录集中的计数• 用于除 text、image、ntext以外任何类型的列• 使用星号()作为COUNT函数的参数 [不推荐,推荐使用数字0或者列名]• 可以不必指定特定的列而计算所有的行数• 当对所有的行进行计数时，包括包含空值（NULL）的行DISTINCT()返回一个集合，并从指定的集合中删除重复项/[scode type="red" size="small"]⚠ 注意：在使用聚合函数查询后，查询列表中将不能出现普通列，除非该列也是用了聚合函数或使用了分组(GROUP BY)[/scode]演示案例：-- 查询出学员编号为'S230100801'的考试总成绩,平均分,最高分以及最低分 SELECT SUM(Result) '总成绩', AVG(Result) '平均分', MAX(Result) '最高分', MIN(Result) '最低分', FROM ResultInfo WHERE StudentNo = 'S230100801' -- 查询出学员编号为'S230100801'的考试成绩大于90分的次数 SELECT COUNT(0) '大于90分的考试次数' FROM Result WHERE StudentNo = 'S230100801' AND Result >= 90分组查询语法：SELECT 分组列名1,分组列名2,聚合函数(列名)... FROM 表名 WHERE 原始表数据筛选条件 GROUP BY 分组列名1,分组列名2... HAVING 分组后的数据筛选条件 ORDER BY 排序列名[scode type="red" size="small"]⚠ 注意：在使用分组查询后，查询列表中将不能出现普通列，除非该列使用了分组(GROUP BY)或者使用了聚合函数[/scode]演示案例：-- 查询出每门科目的考试平均分 SELECT SubjectID,AVG(Result) FROM ResultInfo GROUP BY SubjectID -- 查询出每门科目的考试最高分,按降序排列 SELECT SubjectID,MAX(Result) FROM ResultInfo GROUP BY SubjectID ORDER BY MAX(Result) DESC -- 查询出每个学员,每个科目的考试总成绩,按学员编号排列 SELECT StudentNo,SubjectID,SUM(Result) FROM ResultInfo GROUP BY StudentNo,SubjectID ORDER BY StudentNo -- 查询出每门科目考试成绩大于80分的总人数 SELECT SubjectID,COUNT(0) FROM ResultInfo WHERE Result >= 80 GROUP BY SubjectID -- 查询出每门科目的考试平均分且平均分高于80分并按降序排列 SELECT SubjectID,AVG(Result) FROM ResultInfo GROUP BY SubjectID HAVING AVG(Result) >= 80 ORDER BY AVG(Result) DESCWHERE与HAVING对比WHERE 是对分组之前的原始表记录中的数据进行筛选HAVING 是对分组之后的分组数据进行筛选，HAVING只能配合GROUP BY使用，不能单独使用ORDER BY 子句只允许出现在查询语句的最末尾
前言这篇文章主要是介绍数据库的基础查询相关操作，适合没有任何数据库操作基础的人员查阅。如果您有相关的数据库开发经验，那这篇文章将对您没有任何帮助。概念模糊查询提取的数据不一定是确切的，查询者对查询条件也是模糊的、大概的、不特别明确的。本篇文章会使用：LIKE 、BETWEEN ... AND ... 、IN 三种查询方式。LIKE英文直译：相似；类似；像…一样案例分析：-- 查询出姓李且是名字是两个字的学员信息 SELECT * FROM Student WHERE Name LIKE '李_' /* 查询出来的数据有: * 李明、李阳、李芳、李伟、李霞、李婷、李峰、李梅、李雪、李霞等... */ -- 查询出姓李的学员信息(名字可以是多个字) SELECT * FROM Student WHERE Name LIKE '李%' /* 查询出来的数据有: * 李、李芳、李明霞、李春梅、李婷、李冬梅、李芳、李梅、李春霞等... */ -- 查询出名字中包含李的学员信息 SELECT * FROM Student WHERE Name LIKE '%李%' /* 查询出来的数据有: * 李、张李、李明霞、李春梅、王李婷、李冬梅、李芳、李梅、李春霞等... */LIKE在实际开发过程中使用频率极高，通常LIKE需要和统配符一同使用该运算符只用于字符串，因此仅与字符数据类型（如 char或 varchar等）联合使用通配符解释示例_一个任意字符A LIKE 'V_'，则符合条件的A如“VR”、“VC”等%任意长度的字符串(可以是0个、1个或多个)B LIKE 'SQL%'，则符合条件的A如“SQL Server”、“SQL Server高级编程”等[]括号中所指定范围内的一个字符C LIKE '900[1-2]'，则符合条件的C如“9001”或“9002”[^]不在括号中所指定范围内的任意一个字符D LIKE '900[^1-2]'，则符合条件的D如“9003”或“9007”等BETWEEN...AND...英文直译：介于…之间；在…之间案例分析：-- 查询出年龄在18岁到20岁之间的学员信息 SELECT * FROM Student WHERE Age BETWEEN 18 AND 20 -- 查询出年龄不在18岁到20岁之间的学员信息 SELECT * FROM Student WHERE Age NOT BETWEEN 18 AND 20[scode type="yellow" size="small"]⚠注意：该运算符用于查找介于两个已知值之间的一组未知值，可以是数字、文本或者日期初值要小于等于终值，如果终值小于初值，语句执行无错误，但是不会返回查询结果查找结果包含初值和终值[/scode]IN英文直译：在……里面， 在……之内案例分析：-- 查询出地址在'长沙'、'衡阳'、'深圳'和'怀化'的学员信息 SELECT * FROM Student WHERE [Address] IN ('长沙','衡阳','深圳','怀化') -- 查询出地址不在'长沙'、'衡阳'、'深圳'和'怀化'的学员信息 SELECT * FROM Student WHERE [Address] NOT IN ('长沙','衡阳','深圳','怀化')