首页 文章 精选 留言 我的

精选列表

搜索[快速],共10000篇文章
优秀的个人博客,低调大师

快速搭建 Serverless 在线图片处理应用

作者:倚贤 首先介绍下在本文出现的几个比较重要的概念: 函数计算(Function Compute):函数计算是一个事件驱动的服务,通过函数计算,用户无需管理服务器等运行情况,只需编写代码并上传。函数计算准备计算资源,并以弹性伸缩的方式运行用户代码,而用户只需根据实际代码运行所消耗的资源进行付费。函数计算更多信息参考。ImageMagick:ImageMagick 是一个用于查看、编辑位图文件以及进行图像格式转换的开放源代码软件套装。它可以读取、编辑超过100种图象格式。。参见维基百科词条 ImageMagick 是图片处理的利器,借助 ImageMagick 可以轻松实现图片的裁剪和缩放。虽然很多语言都封装了 ImageMagick 的调用库,但是把图片处理功能和核心业务功能放在同一个服务内,在软件架构上往往不适合。有如下两方面的原因

优秀的个人博客,低调大师

Coreboot 4.11 发布,快速、安全、灵活的开源固件

coreboot 项目发布了最新的 4.11 版本,此次发行周期比以往要短一些。该版本包含自 4.10 版本依赖超过 130 位开发者提交到 1630 个新的提交。其中有 30 位是 coreboot 首次贡献者。 此次版本主要包含几方面的更新: 代码清理 增强对 Intel 设备的支持,包括Kaby Lake 和 Cannon Lake 驱动;ARM Mediatek 8173 支持以及一些老的芯片支持 vboot 提升 新增 25 个主板支持包括如下列表: 新增主板支持列表: AMD PADMELON ASUS P5QL-EM EMULATION QEMU-AARCH64 GOOGLE AKEMI GOOGLE ARCADA CML GOOGLE DAMU GOOGLE DOOD GOOGLE DRALLION GOOGLE DRATINI GOOGLE JACUZZI GOOGLE JUNIPER GOOGLE KAKADU GOOGLE KAPPA GOOGLE PUFF GOOGLE SARIEN CML GOOGLE TREEYA GOOGLE TROGDOR LENOVO R60 LENOVO T410 LENOVO THINKPAD T440P LENOVO X301 RAZER BLADE-STEALTH KBL SIEMENS MC-APL6 SUPERMICRO X11SSH-TF SUPERMICRO X11SSM-F 完整的发行说明请看https://blogs.coreboot.org/blog/2019/11/19/announcing-coreboot-4-11/

优秀的个人博客,低调大师

如何快速安全的插入千万条数据

前言 最近有个需求解析一个订单文件,并且说明文件可达到千万条数据,每条数据大概在20个字段左右,每个字段使用逗号分隔,需要尽量在半小时内入库。 思路 1.估算文件大小 因为告诉文件有千万条,同时每条记录大概在20个字段左右,所以可以大致估算一下整个订单文件的大小,方法也很简单使用FileWriter往文件中插入一千万条数据,查看文件大小,经测试大概在1.5G左右; 2.如何批量插入 由上可知文件比较大,一次性读取内存肯定不行,方法是每次从当前订单文件中截取一部分数据,然后进行批量插入,如何批次插入可以使用insert(...)values(...),(...)的方式,经测试这种方式效率还是挺高的; 3.数据的完整性 截取数据的时候需要注意,需要保证数据的完整性,每条记录最后都是一个换行符,需要根据这个标识保证每次截取都是整条数,不要出现半条数据这种情况; 4.数据库是否支持批次数据 因为需要进行批次数据的插入,数据库是否支持大量数据写入,比如这边使用的mysql,可以通过设置max_allowed_packet来保证批次提交的数据量; 5.中途出错的情况 因为是大文件解析,如果中途出现错误,比如数据刚好插入到900w的时候,数据库连接失败,这种情况不可能重新来插一遍,所有需要记录每次插入数据的位置,并且需要保证和批次插入的数据在同一个事务中,这样恢复之后可以从记录的位置开始继续插入。 实现 1.准备数据表 这里需要准备两张表分别是:订单状态位置信息表,订单表; CREATE TABLE `file_analysis` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `file_type` varchar(255) NOT NULL COMMENT '文件类型 01:类型1,02:类型2', `file_name` varchar(255) NOT NULL COMMENT '文件名称', `file_path` varchar(255) NOT NULL COMMENT '文件路径', `status` varchar(255) NOT NULL COMMENT '文件状态 0初始化;1成功;2失败:3处理中', `position` bigint(20) NOT NULL COMMENT '上一次处理完成的位置', `crt_time` datetime NOT NULL COMMENT '创建时间', `upd_time` datetime NOT NULL COMMENT '更新时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 CREATE TABLE `file_order` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `file_id` bigint(20) DEFAULT NULL, `field1` varchar(255) DEFAULT NULL, `field2` varchar(255) DEFAULT NULL, `field3` varchar(255) DEFAULT NULL, `field4` varchar(255) DEFAULT NULL, `field5` varchar(255) DEFAULT NULL, `field6` varchar(255) DEFAULT NULL, `field7` varchar(255) DEFAULT NULL, `field8` varchar(255) DEFAULT NULL, `field9` varchar(255) DEFAULT NULL, `field10` varchar(255) DEFAULT NULL, `field11` varchar(255) DEFAULT NULL, `field12` varchar(255) DEFAULT NULL, `field13` varchar(255) DEFAULT NULL, `field14` varchar(255) DEFAULT NULL, `field15` varchar(255) DEFAULT NULL, `field16` varchar(255) DEFAULT NULL, `field17` varchar(255) DEFAULT NULL, `field18` varchar(255) DEFAULT NULL, `crt_time` datetime NOT NULL COMMENT '创建时间', `upd_time` datetime NOT NULL COMMENT '更新时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=10000024 DEFAULT CHARSET=utf8 2.配置数据库包大小 mysql> show VARIABLES like '%max_allowed_packet%'; +--------------------------+------------+ | Variable_name | Value | +--------------------------+------------+ | max_allowed_packet | 1048576 | | slave_max_allowed_packet | 1073741824 | +--------------------------+------------+ 2 rows in set mysql> set global max_allowed_packet = 1024*1024*10; Query OK, 0 rows affected 通过设置max_allowed_packet,保证数据库能够接收批次插入的数据包大小;不然会出现如下错误: Caused by: com.mysql.jdbc.PacketTooBigException: Packet for query is too large (4980577 > 1048576). You can change this value on the server by setting the max_allowed_packet' variable. at com.mysql.jdbc.MysqlIO.send(MysqlIO.java:3915) at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2598) at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2778) at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2834) 3.准备测试数据 public static void main(String[] args) throws IOException { FileWriter out = new FileWriter(new File("D://xxxxxxx//orders.txt")); for (int i = 0; i < 10000000; i++) { out.write( "vaule1,vaule2,vaule3,vaule4,vaule5,vaule6,vaule7,vaule8,vaule9,vaule10,vaule11,vaule12,vaule13,vaule14,vaule15,vaule16,vaule17,vaule18"); out.write(System.getProperty("line.separator")); } out.close(); } 使用FileWriter遍历往一个文件里插入1000w条数据即可,这个速度还是很快的,不要忘了在每条数据的后面添加换行符(\n\r); 4.截取数据的完整性 除了需要设置每次读取文件的大小,同时还需要设置一个参数,用来每次获取一小部分数据,从这小部分数据中获取换行符(\n\r),如果获取不到一直累加直接获取为止,这个值设置大小大致同每条数据的大小差不多合适,部分实现如下: ByteBuffer byteBuffer = ByteBuffer.allocate(buffSize); // 申请一个缓存区 long endPosition = batchFileSize + startPosition - buffSize;// 子文件结束位置 long startTime, endTime; for (int i = 0; i < count; i++) { startTime = System.currentTimeMillis(); if (i + 1 != count) { int read = inputChannel.read(byteBuffer, endPosition);// 读取数据 readW: while (read != -1) { byteBuffer.flip();// 切换读模式 byte[] array = byteBuffer.array(); for (int j = 0; j < array.length; j++) { byte b = array[j]; if (b == 10 || b == 13) { // 判断\n\r endPosition += j; break readW; } } endPosition += buffSize; byteBuffer.clear(); // 重置缓存块指针 read = inputChannel.read(byteBuffer, endPosition); } } else { endPosition = fileSize; // 最后一个文件直接指向文件末尾 } ...省略,更多可以查看Github完整代码... } 如上代码所示开辟了一个缓冲区,根据每行数据大小来定大概在200字节左右,然后通过遍历查找换行符(\n\r),找到以后将当前的位置加到之前的结束位置上,保证了数据的完整性; 5.批次插入数据 通过insert(...)values(...),(...)的方式批次插入数据,部分代码如下: // 保存订单和解析位置保证在一个事务中 SqlSession session = sqlSessionFactory.openSession(); try { long startTime = System.currentTimeMillis(); FielAnalysisMapper fielAnalysisMapper = session.getMapper(FielAnalysisMapper.class); FileOrderMapper fileOrderMapper = session.getMapper(FileOrderMapper.class); fileOrderMapper.batchInsert(orderList); // 更新上次解析到的位置,同时指定更新时间 fileAnalysis.setPosition(endPosition + 1); fileAnalysis.setStatus("3"); fileAnalysis.setUpdTime(new Date()); fielAnalysisMapper.updateFileAnalysis(fileAnalysis); session.commit(); long endTime = System.currentTimeMillis(); System.out.println("===插入数据花费:" + (endTime - startTime) + "ms==="); } catch (Exception e) { session.rollback(); } finally { session.close(); } ...省略,更多可以查看Github完整代码... 如上代码在一个事务中同时保存批次订单数据和文件解析位置信息,batchInsert通过使用mybatis的<foreach>标签来遍历订单列表,生成values数据; 总结 以上展示了部分代码,完整的代码可以查看Github地址中的batchInsert模块,本地设置每次截取的文件大小为2M,经测试1000w条数据(大小1.5G左右)插入mysql数据库中,大概花费时间在20分钟左右,当然可以通过设置截取的文件大小,花费的时间也会相应的改变。 完整代码 Github

优秀的个人博客,低调大师

如何快速搭建一个短链接服务?

摘要: 很简单的短链接教程。 原文:十分钟实现短链接服务(Node + Express + MongoDB) 作者:MudOnTire Fundebug经授权转载,版权归原作者所有。 短链接我们或多或少都使用过,所谓短链接就是根据较长的原链接url生成一段较短的链接,访问短链接可以跳转到对应的原链接,这样做好处在于:1. url更加美观;2. 便于保存和传播;3. 某些网站内容发布有字数限制,短链接可以节约字数。 短链接实现的原理非常简单,可以概括为: 为每个原链接生成不重复的唯一短链接 将原链接和对应短链接成对保存到数据库 访问短链接时,web服务器将目标重定向到对应的原链接 根据以上思路,我们自己也可以分分钟实现一个短链接生成服务。本文示例使用 node + express + mongodb。 1. 初始化项目 (1). 安装如下依赖: package.json: "dependencies": { "config": "^3.2.2", // 读取项目配置 "express": "^4.17.1", // web服务器 "mongoose": "^5.6.9", // 操作mongodb "shortid": "^2.2.14", // 生成不重复的唯一Id "valid-url": "^1.0.9" // 判断url格式是否正确 } (2). 增加项目配置: 主要用于存放MongoDB的连接字符串和短链接的base url。 config/default.json: { "mongoURI": "mongodb://localhost:27017/url-shorten-service", "baseUrl": "http://localhost:5000" } (3). 增加MongoDB连接方法 config/db.js: const mongoose = require('mongoose'); const config = require('config'); const db = config.get('mongoURI'); const connectDB = async () => { try { await mongoose.connect(db, { useNewUrlParser: true }); console.log(`MongoDB Connected to: ${db}`); } catch (error) { console.error(error.message); process.exit(1); } } module.exports = connectDB; (4). 启动express: index.js: const express = require('express'); const connectDB = require('./config/db'); const app = express(); // 连接MongoDB connectDB(); app.use(express.json({ extended: false })); // 路由,稍后设置 app.use('/', require('./routes/index')); app.use('/api/url', require('./routes/url')); const port = 5000; app.listen(port, () => { console.log(`Server running on port ${port}`); }); 2. 定义数据库模型 我们需要将原链接和对应短链接保存到数据库,简单起见,我们只需要保存一个短链接编码,相应的短链接可以使用base url和编码拼接而成。 models/url.js: const mongoose = require('mongoose'); const urlSchema = new mongoose.Schema({ urlCode: String, longUrl: String }); module.exports = mongoose.model('Url', urlSchema); 3. 生成短链接编码 这是我们实现的关键一步,思路是:用户传入一个长链接,我们首先使用 valid-url 判断传入的url是否合法,不合法则返回错误,如果合法我们在数据库中搜索是否有该长链接的记录,如果有则直接返回该条记录,如果没有则生成一条新记录,并生成对应的短链接。借助于 shortId,我们可以很方便的生成一个不重复的唯一编码。 routes/url.js: const epxress = require("express"); const router = epxress.Router(); const validUrl = require('valid-url'); const shortId = require('shortid'); const config = require('config'); const Url = require('../models/url'); router.post('/shorten', async (req, res, next) => { const { longUrl } = req.body; if (validUrl.isUri(longUrl)) { try { let url = await Url.findOne({ longUrl }); if (url) { res.json({ shortUrl: `${config.get('baseUrl')}/${url.urlCode}` }); } else { const urlCode = shortId.generate(); url = new Url({ longUrl, urlCode }); await url.save(); res.json({ shortUrl: `${config.get('baseUrl')}/${urlCode}` }); } } catch (error) { res.status(500).json('Server error'); } } else { res.status(401).json('Invalid long url'); } }); module.exports = router; 4. 访问短链接跳转到原链接 最后一步非常简单,当用户访问我们生成的短链接时,我们根据url中的短链接编码查询到对应记录,如果存在对应记录我们使用express的res.redirect方法将访问重定向至原链接,如果不存在则返回错误。 routes/index.js const epxress = require("express"); const router = epxress.Router(); const Url = require('../models/url'); router.get('/:code', async (req, res, next) => { try { const urlCode = req.params.code; const url = await Url.findOne({ urlCode }); if (url) { // 重定向至原链接 res.redirect(url.longUrl); } else { res.status(404).json("No url found"); } } catch (error) { res.status(500).json("Server error"); } }); module.exports = router; 测试一下: 访问短链接: 这样,一个简单的短链接生成服务就完成了,往往在我们看来很神奇的技术其实背后的原理和实现很简单,希望本文对大家有所启发。 最后,推荐大家使用Fundebug,一款很好用的BUG监控工具~ 本文Demo地址:https://github.com/MudOnTire/url-shortener-service 关于Fundebug Fundebug专注于JavaScript、微信小程序、微信小游戏、支付宝小程序、React Native、Node.js和Java线上应用实时BUG监控。 自从2016年双十一正式上线,Fundebug累计处理了20亿+错误事件,付费客户有阳光保险、核桃编程、荔枝FM、掌门1对1、微脉、青团社等众多品牌企业。欢迎大家免费试用!

优秀的个人博客,低调大师

快速降低成本建立自己的网站

网站是什么? 网站的定义 网站是什么?可能大部分人头脑中出现的第一映像就是淘宝啊、京东啊、网易,但若要问对于网站的定义,很多人却答不出来,百度百科是这样定义网站的: 网站(Website)是指在因特网上根据一定的规则,使用HTML(标准通用标记语言下的一个应用)等工具制作的用于展示特定内容相关网页的集合。简单地说,网站是一种沟通工具,人们可以通过网站来发布自己想要公开的资讯,或者利用网站来提供相关的网络服务。人们可以通过网页浏览器来访问网站,获取自己需要的资讯或者享受网络服务。 网站现状 目前国内网站数量接近400万个,有个人的,也有企业的,还有政府机关单位的,作为我们个人,有时候也想拥有一个属于自己的网站,并不是所有的人建立网站都是为了赚钱盈利,可能一部份人只是想把自己的一些东西通过互联网推送出去,分享给更多的人看到。 网站的用途是什么? 从企业的角度来看 1 可降低广告宣传费用,让客户快捷地找到企业资料2 随时可获取和发布商业信息,寻找潜在客户,促成贸易。3 可提供每天24小时的产品宣传服务。4 利用互联网扩大自己的知名度。5 在网上出售商品,降低销售费用。6 更快捷地了解客户需求。7 有利于开拓国际市场。8 更好地与供应商、销售渠道和合作伙伴沟通与交流。9 改善组织结构和管理体系,提高工作效率,及时适应市场变化。10 可以树立现代化形象 从个人的角度来看 1 完全依据兴趣,享受建站的乐趣,对建站充满好奇心。2 学习建站技能和建站技术。3 信息交流和信息分享。通过互联网可以让更多人知道你。4 学习网站seo优化,建了网站,才能进行seo优化实践。5 建站赚钱,根据想法或者有价值的产品或者广告进行变现。 如何零基础拥有自己的网站? 那大多数人都是不具备制作网站技能的,当然,有人使用的方法是直接花钱请人为自己做一个网站并且帮助日常维护,这个方法虽然可行,但是似乎失去了自己创建网站的乐趣。那接下来小编就来教大家另外一种创建个人网站的方法。 网站必要资源:域名+云主机(云服务器或者虚拟主机)+建站程序 第一步:我们得想好我们要做一个什么类型的网站及我们做网站的目的是什么。确定类型的时候尽量垂直某一个领域。比如你要做技术博客类,信息资讯类的。 第二步:购买域名,确实好了网站的方向后,我们得先去购买一个域名,购买域名的平台很多,在这里就推荐一个大的平台吧,就是阿里云,购买域名的时候记住,一定要购买.com的域名,因为.com的域名是国际域名,适用性非常强。注册域名时域名尽量和网站主题相关,虽然好一点的域名都被别人抢注完了,但我们只是做个人小网站,所以不必太过于在乎域名。 第三步:选定云主机(云服务器)。这里也是推荐使用阿里云的云主机,稳定和安全。如果你是学生身份,可以选阿里云学生款 不是学生的选择这款阿里云全民上云 。 第四步:网站备案。如果在第三步选择了香港地域或者国外地域的云服务器,那么这一步就可以免备案。选择国内地域的云服务器一定要备案!!个人备案是不收费的。备案时间大概需要半个月。 第五步:搭建网站程序。如果你没有技术,可以选择阿里云自营建站一套龙建站服务。有web开发技术的话,可以自行搭建web程序。这一块小编后续会撰写相关的文章。 第六步:域名地址解析绑定阿里云如何正确解析域名完整步骤(图文操作) 首先购买好了服务器,会有一个公网IP地址,然后在把你的公网IP地址与你购买的域名绑定一下,就可以用域名访问的网站了。 第七步:网站搭建成功如果你要做其它领域的网站,可以找其它网站模板建站,阿里云平台提供了大量的网站模板---阿里云网站模板,有兴趣的可以去在线浏览下,大家有兴趣可以参考下。如果你有web技术,完全可以自己开发网站包括模板。 总结:上面所有步骤中,最麻烦的属于搭建网站程序。如果你不差钱,可以直接购买阿里云官方的网站模板产品,只要会打字就行,一年也就四五百元钱,包含了云主机的价格。如果你想自己学技术来搭建网站程序,那就要好好花点时间了。在这里我推荐用wordPress建站程序。可以省掉很多精力和学习成本。熟练的话,一上午就能建一个网站(不包括网站优化) 【提示】这里着重推荐建站程序WordPress,原因很简单:功能强大,主题和插件多,易于扩展,你用别的建站程序,主题和插件如果不能友好的扩展,后面你的网站会不好加新功能。 如何找免费的网站模板? 这里以wordpress为例。推荐几个免费的WordPress模板地址。当然网上很多WordPress免费模板的网站。还有就是如果你需要一些功能强大的网站主题模板,一般需要收费。免费的模板,一般功能不多。 快来多多支持小编吧!优惠多多哦

优秀的个人博客,低调大师

# iOS 使用 InjectionIII 注入动态库实现快速调试

前言 最近在看 "戴老师" 专栏推出的 "App 如何通过注入动态库的方式实现极速编译调试?" 感触很深, 相信每个 iOS 的小伙伴在写代码的时候, 都存在这个烦恼, 每次修改个小功能, 都需要重新 Build 一次, 才能运行, 当项目功能不断积累到一定程度时, 编译时间可能超乎我们想象, 每次修改个 Color, text 等等, 都要经历一次漫长的等待。 真羡慕写 前端 Flutter 的同事呀, code 一修改好, 不到 1S 就能很快的看到修改结果.原来在 iOS 上早就有大佬推出了 注入动态库的方式来解决由于 OC 采用 "编译器" VS "链接器" 特性进行编译链接导致的 调试周期过长的问题。 使用 InjectionIII 解决项目编译时间过长 使用 InjectionIII 可以加快调试的速度, 并且可以保证程序不需要重启, 即可达到源码修改后的效果。 并且其代码是完全开源的 InjectionIII。 其实现原理大家可以直接跳转到 "戴老师" 专栏去查看 App 如何通过注入动态库的方式实现极速编译调试? 讲的非常详细. 安装方式 方式一: 直接通过 github clone 项目下来, 运行。 如果遇到签名报错, 可直接到 Build Setting 将 Code Signing Identity 将签名取消掉。 方式二: 直接到 App Store 搜索 InjectionIII 下载 运行即可。 运行之后点击应用图标选择 Open Project, 并且选择我们要注入动态库的应用. 然后在我们注入的项目中 "AppDelegate" 的 - (BOOL)application:(UIApplication )application didFinishLaunchingWithOptions:(NSDictionary )launchOptions 添加代码 #if DEBUG // iOS [[NSBundle bundleWithPath:@"/Applications/InjectionIII.app/Contents/Resources/iOSInjection.bundle"] load]; #endif XCode10 是这个 #if DEBUG // iOS [[NSBundle bundleWithPath:@"/Applications/InjectionIII.app/Contents/Resources/iOSInjection10.bundle"] load]; #endif 最后到我们需要监听的页面里面重写这个方法即可 OC: - (void)injected { [self viewDidLoad]; [self viewWillAppear:YES]; [self viewWillDisappear:YES]; } Swift extension UIViewController { @objc func injected() { viewDidLoad() viewWillAppear(true) viewDidAppear(true) } } 参考 App 如何通过注入动态库的方式实现极速编译调试?InjectionIII 最后 希望此篇文章对您有所帮助,如有不对的地方,希望大家能留言指出纠正。谢谢!!!!!学习的路上,与君共勉!!! 本文原创作者:Jersey. 欢迎转载,请注明出处和本文链接

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册