首页 文章 精选 留言 我的

精选列表

搜索[整理],共9148篇文章
优秀的个人博客,低调大师

紧急整理了 20 道 Spring Boot 面试题,我经常拿来面试别人!

面试了一些人,简历上都说自己熟悉 Spring Boot, 或者说正在学习 Spring Boot,一问他们时,都只停留在简单的使用阶段,很多东西都不清楚,也让我对面试者大失所望。 下面,我给大家总结下有哪些 Spring Boot 的面试题,这是我经常拿来问面试者的,希望对你有帮助。 1、什么是 Spring Boot? Spring Boot 是 Spring 开源组织下的子项目,是 Spring 组件一站式解决方案,主要是简化了使用 Spring 的难度,简省了繁重的配置,提供了各种启动器,开发者能快速上手。 更多 Spring Boot 详细介绍请看这篇文章《什么是Spring Boot?》。 2、为什么要用 Spring Boot? Spring Boot 优点非常多,如: ● 独立运行 ● 简化配置 ● 自动配置 ● 无代码生成和XML配置 ● 应用监控 ● 上手容易 ● … Spring Boot 集这么多优点于一身,还有理由不使用它呢? 3、Spring Boot 的核心配置文件有哪几个?它们的区别是什么? Spring Boot 的核心配置文件是 application 和 bootstrap 配置文件。 application 配置文件这个容易理解,主要用于 Spring Boot 项目的自动化配置。 bootstrap 配置文件有以下几个应用场景。 ● 使用 Spring Cloud Config 配置中心时,这时需要在 bootstrap 配置文件中添加连接到配置中心的配置属性来加载外部配置中心的配置信息; ● 一些固定的不能被覆盖的属性; ● 一些加密/解密的场景; 具体请看这篇文章《Spring Boot 核心配置文件详解》。 4、Spring Boot 的配置文件有哪几种格式?它们有什么区别? .properties 和 .yml,它们的区别主要是书写格式不同。 1).properties app.user.name=javastack 2).yml app: user: name:javastack 另外,.yml 格式不支持@PropertySource注解导入配置。 5、Spring Boot 的核心注解是哪个?它主要由哪几个注解组成的? 启动类上面的注解是@SpringBootApplication,它也是 Spring Boot 的核心注解,主要组合包含了以下 3 个注解: @SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的功能。 @EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项,如关闭数据源自动配置功能: @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })。 @ComponentScan:Spring组件扫描。 6、开启 Spring Boot 特性有哪几种方式? 1)继承spring-boot-starter-parent项目 2)导入spring-boot-dependencies项目依赖 具体请参考这篇文章《Spring Boot开启的2种方式》。 7、Spring Boot 需要独立的容器运行吗? 可以不需要,内置了 Tomcat/ Jetty 等容器。 8、运行 Spring Boot 有哪几种方式? 1)打包用命令或者放到容器中运行 2)用 Maven/ Gradle 插件运行 3)直接执行 main 方法运行 9、Spring Boot 自动配置原理是什么? 注解 @EnableAutoConfiguration, @Configuration, @ConditionalOnClass 就是自动配置的核心,首先它得是一个配置文件,其次根据类路径下是否有这个类去自动配置。 具体看这篇文章《Spring Boot自动配置原理、实战》。 10、Spring Boot 的目录结构是怎样的? cn +-javastack +-MyApplication.java | +-customer |+-Customer.java |+-CustomerController.java |+-CustomerService.java |+-CustomerRepository.java | +-order +-Order.java +-OrderController.java +-OrderService.java +-OrderRepository.java 这个目录结构是主流及推荐的做法,而在主入口类上加上 @SpringBootApplication 注解来开启 Spring Boot 的各项能力,如自动配置、组件扫描等。具体看这篇文章《Spring Boot 主类及目录结构介绍》。 11、你如何理解 Spring Boot 中的 Starters? Starters可以理解为启动器,它包含了一系列可以集成到应用里面的依赖包,你可以一站式集成 Spring 及其他技术,而不需要到处找示例代码和依赖包。如你想使用 Spring JPA 访问数据库,只要加入 spring-boot-starter-data-jpa 启动器依赖就能使用了。 Starters包含了许多项目中需要用到的依赖,它们能快速持续的运行,都是一系列得到支持的管理传递性依赖。具体请看这篇文章《Spring Boot Starters启动器》。 12、如何在 Spring Boot 启动的时候运行一些特定的代码? 可以实现接口 ApplicationRunner 或者 CommandLineRunner,这两个接口实现方式一样,它们都只提供了一个 run 方法,具体请看这篇文章《Spring Boot Runner启动器》。 13、Spring Boot 有哪几种读取配置的方式? Spring Boot 可以通过 @PropertySource,@Value,@Environment, @ConfigurationProperties 来绑定变量,具体请看这篇文章《Spring Boot读取配置的几种方式》。 14、Spring Boot 支持哪些日志框架?推荐和默认的日志框架是哪个? Spring Boot 支持 Java Util Logging, Log4j2, Lockback 作为日志框架,如果你使用 Starters 启动器,Spring Boot 将使用 Logback 作为默认日志框架,具体请看这篇文章《Spring Boot日志集成》。 15、SpringBoot 实现热部署有哪几种方式? 主要有两种方式: ● Spring Loaded ● Spring-boot-devtools Spring-boot-devtools 使用方式可以参考这篇文章《Spring Boot实现热部署》。 16、你如何理解 Spring Boot 配置加载顺序? 在 Spring Boot 里面,可以使用以下几种方式来加载配置。 1)properties文件; 2)YAML文件; 3)系统环境变量; 4)命令行参数; 等等…… 具体请看这篇文章《Spring Boot 配置加载顺序详解》。 17、Spring Boot 如何定义多套不同环境配置? 提供多套配置文件,如: applcation.propertiesapplication-dev.propertiesapplication-test.propertiesapplication-prod.properties 运行时指定具体的配置文件,具体请看这篇文章《Spring Boot Profile 不同环境配置》。 18、Spring Boot 可以兼容老 Spring 项目吗,如何做? 可以兼容,使用@ImportResource注解导入老 Spring 项目配置文件。 19、保护 Spring Boot 应用有哪些方法? ● 在生产中使用HTTPS ● 使用Snyk检查你的依赖关系 ● 升级到最新版本 ● 启用CSRF保护 ● 使用内容安全策略防止XSS攻击 ● … 更多请看这篇文章《10 种保护 Spring Boot 应用的绝佳方法》。 20、Spring Boot 2.X 有什么新特性?与 1.X 有什么区别? ● 配置变更 ● JDK 版本升级 ● 第三方类库升级 ● 响应式 Spring 编程支持 ● HTTP/2 支持 ● 配置属性绑定 ● 更多改进与加强… 具体请看这篇文章《Spring Boot 2.x 新特性总结及迁移指南》。 终于写完了,希望大家好好学习下再去面试,不然再被面试官问这些问题,又答不上来就尴尬了。弄懂了这些问题,对你理解 Spring Boot 也有非常大的帮助,其实,上面的这些题,在我们的公众号Java技术栈里面都能找到答案。 原文发布时间为:2018-10-13 本文作者:R哥 本文来自云栖社区合作伙伴“Java技术栈”,了解相关信息可以关注“Java技术栈”。

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

整理分享C#通过user32.dll模拟物理按键操作的代码

对系统模拟按键方面的知识和按键映射代码做了一下梳理,在这里分享出来,适用于开发自动操作工具和游戏外挂。 主代码: public const int KEYEVENTF_EXTENDEDKEY = 0x0001; //Key click flag public const int KEYEVENTF_KEYUP = 0x0002; //Key up flag [DllImport("user32.dll")] private static extern void keybd_event(byte bVk, byte bSCan, int dwFlags, int dwExtraInfo); [DllImport("user32.dll")] private static extern byte MapVirtualKey(byte wCode, int wMap); public static void 模拟按下按键(VirtualKeyCode 虚拟按键代码) { var code = (byte)虚拟按键代码; keybd_event(code, 0, 0, 0); } public static void 模拟弹起按键(VirtualKeyCode 虚拟按键代码) { var code = (byte) 虚拟按键代码; keybd_event(code, 0, KEYEVENTF_KEYUP, 0); } public static void 模拟单击按键(VirtualKeyCode 虚拟按键代码) { var code = (byte)虚拟按键代码; keybd_event(code, 0, KEYEVENTF_EXTENDEDKEY, 0); } 网上关于keybd_event的dwFlags参数功能说法很混乱,经我测试貌似是KEYEVENTF_EXTENDEDKEY表示一次单击,0表示按下,KEYEVENTF_KEYUP表示弹起,不一定完全正确,希望高人指点一下。 另外MapVirtualKey的作用实在不懂,所以就没用上,看到有人这么调用不知有什么区别: var code = (byte)虚拟按键代码; keybd_event(code, MapVirtualKey(code,0), 0, 0); 我试过好像也没什么变化~到底MapVirtualKey是干什么用的呢?? VirtualKeyCode枚举: /// <summary> /// 虚拟按键代码 /// 参考于 http://msdn.microsoft.com/zh-cn/library/dd375731(v=vs.85).aspx /// </summary> public enum VirtualKeyCode { /// <summary> /// Left mouse button /// </summary> Left_mouse_button = 0x01, /// <summary> /// Right mouse button /// </summary> Right_mouse_button = 0x02, /// <summary> /// Control-break processing /// </summary> Control_break_processing = 0x03, /// <summary> /// Middle mouse button (three-button mouse) /// </summary> Middle_mouse_button = 0x04, /// <summary> /// X1 mouse button /// </summary> X1_mouse_button = 0x05, /// <summary> /// X2 mouse button /// </summary> X2_mouse_button = 0x06, /// <summary> /// Undefined /// </summary> Undefined1 = 0x07, /// <summary> /// BACKSPACE key /// </summary> BACKSPACE_key = 0x08, /// <summary> /// TAB key /// </summary> TAB_key = 0x09, /// <summary> /// CLEAR key /// </summary> CLEAR_key = 0x0C, /// <summary> /// ENTER key /// </summary> ENTER_key = 0x0D, /// <summary> /// SHIFT key /// </summary> SHIFT_key = 0x10, /// <summary> /// CTRL key /// </summary> CTRL_key = 0x11, /// <summary> /// ALT key /// </summary> ALT_key = 0x12, /// <summary> /// PAUSE key /// </summary> PAUSE_key = 0x13, /// <summary> /// CAPS LOCK key /// </summary> CAPS_LOCK_key = 0x14, /// <summary> /// IME Kana mode /// </summary> IME_Kana_mode = 0x15, /// <summary> /// IME Hanguel mode (maintained for compatibility; use VK_HANGUL) /// </summary> IME_Hanguel_mode = 0x15, /// <summary> /// IME Hangul mode /// </summary> IME_Hangul_mode = 0x15, /// <summary> /// Undefined /// </summary> Undefined2 = 0x16, /// <summary> /// IME Junja mode /// </summary> IME_Junja_mode = 0x17, /// <summary> /// IME final mode /// </summary> IME_final_mode = 0x18, /// <summary> /// IME Hanja mode /// </summary> IME_Hanja_mode = 0x19, /// <summary> /// IME Kanji mode /// </summary> IME_Kanji_mode = 0x19, /// <summary> /// Undefined /// </summary> Undefined = 0x1A, /// <summary> /// ESC key /// </summary> ESC_key = 0x1B, /// <summary> /// IME convert /// </summary> IME_convert = 0x1C, /// <summary> /// IME nonconvert /// </summary> IME_nonconvert = 0x1D, /// <summary> /// IME accept /// </summary> IME_accept = 0x1E, /// <summary> /// IME mode change request /// </summary> IME_mode_change_request = 0x1F, /// <summary> /// SPACEBAR /// </summary> SPACEBAR = 0x20, /// <summary> /// PAGE UP key /// </summary> PAGE_UP_key = 0x21, /// <summary> /// PAGE DOWN key /// </summary> PAGE_DOWN_key = 0x22, /// <summary> /// END key /// </summary> END_key = 0x23, /// <summary> /// HOME key /// </summary> HOME_key = 0x24, /// <summary> /// LEFT ARROW key /// </summary> LEFT_ARROW_key = 0x25, /// <summary> /// UP ARROW key /// </summary> UP_ARROW_key = 0x26, /// <summary> /// RIGHT ARROW key /// </summary> RIGHT_ARROW_key = 0x27, /// <summary> /// DOWN ARROW key /// </summary> DOWN_ARROW_key = 0x28, /// <summary> /// SELECT key /// </summary> SELECT_key = 0x29, /// <summary> /// PRINT key /// </summary> PRINT_key = 0x2A, /// <summary> /// EXECUTE key /// </summary> EXECUTE_key = 0x2B, /// <summary> /// PRINT SCREEN key /// </summary> PRINT_SCREEN_key = 0x2C, /// <summary> /// INS key /// </summary> INS_key = 0x2D, /// <summary> /// DEL key /// </summary> DEL_key = 0x2E, /// <summary> /// HELP key /// </summary> HELP_key = 0x2F, /// <summary> /// 0 key /// </summary> _0_key = 0x30, /// <summary> /// 1 key /// </summary> _1_key = 0x31, /// <summary> /// 2 key /// </summary> _2_key = 0x32, /// <summary> /// 3 key /// </summary> _3_key = 0x33, /// <summary> /// 4 key /// </summary> _4_key = 0x34, /// <summary> /// 5 key /// </summary> _5_key = 0x35, /// <summary> /// 6 key /// </summary> _6_key = 0x36, /// <summary> /// 7 key /// </summary> _7_key = 0x37, /// <summary> /// 8 key /// </summary> _8_key = 0x38, /// <summary> /// 9 key /// </summary> _9_key = 0x39, /// <summary> /// A key /// </summary> A_key = 0x41, /// <summary> /// B key /// </summary> B_key = 0x42, /// <summary> /// C key /// </summary> C_key = 0x43, /// <summary> /// D key /// </summary> D_key = 0x44, /// <summary> /// E key /// </summary> E_key = 0x45, /// <summary> /// F key /// </summary> F_key = 0x46, /// <summary> /// G key /// </summary> G_key = 0x47, /// <summary> /// H key /// </summary> H_key = 0x48, /// <summary> /// I key /// </summary> I_key = 0x49, /// <summary> /// J key /// </summary> J_key = 0x4A, /// <summary> /// K key /// </summary> K_key = 0x4B, /// <summary> /// L key /// </summary> L_key = 0x4C, /// <summary> /// M key /// </summary> M_key = 0x4D, /// <summary> /// N key /// </summary> N_key = 0x4E, /// <summary> /// O key /// </summary> O_key = 0x4F, /// <summary> /// P key /// </summary> P_key = 0x50, /// <summary> /// Q key /// </summary> Q_key = 0x51, /// <summary> /// R key /// </summary> R_key = 0x52, /// <summary> /// S key /// </summary> S_key = 0x53, /// <summary> /// T key /// </summary> T_key = 0x54, /// <summary> /// U key /// </summary> U_key = 0x55, /// <summary> /// V key /// </summary> V_key = 0x56, /// <summary> /// W key /// </summary> W_key = 0x57, /// <summary> /// X key /// </summary> X_key = 0x58, /// <summary> /// Y key /// </summary> Y_key = 0x59, /// <summary> /// Z key /// </summary> Z_key = 0x5A, /// <summary> /// Left Windows key (Natural keyboard) /// </summary> Left_Windows_key = 0x5B, /// <summary> /// Right Windows key (Natural keyboard) /// </summary> Right_Windows_key = 0x5C, /// <summary> /// Applications key (Natural keyboard) /// </summary> Applications_key = 0x5D, /// <summary> /// Reserved /// </summary> Reserved1 = 0x5E, /// <summary> /// Computer Sleep key /// </summary> Computer_Sleep_key = 0x5F, /// <summary> /// Numeric keypad 0 key /// </summary> Numeric_keypad_0_key = 0x60, /// <summary> /// Numeric keypad 1 key /// </summary> Numeric_keypad_1_key = 0x61, /// <summary> /// Numeric keypad 2 key /// </summary> Numeric_keypad_2_key = 0x62, /// <summary> /// Numeric keypad 3 key /// </summary> Numeric_keypad_3_key = 0x63, /// <summary> /// Numeric keypad 4 key /// </summary> Numeric_keypad_4_key = 0x64, /// <summary> /// Numeric keypad 5 key /// </summary> Numeric_keypad_5_key = 0x65, /// <summary> /// Numeric keypad 6 key /// </summary> Numeric_keypad_6_key = 0x66, /// <summary> /// Numeric keypad 7 key /// </summary> Numeric_keypad_7_key = 0x67, /// <summary> /// Numeric keypad 8 key /// </summary> Numeric_keypad_8_key = 0x68, /// <summary> /// Numeric keypad 9 key /// </summary> Numeric_keypad_9_key = 0x69, /// <summary> /// Multiply key /// </summary> Multiply_key = 0x6A, /// <summary> /// Add key /// </summary> Add_key = 0x6B, /// <summary> /// Separator key /// </summary> Separator_key = 0x6C, /// <summary> /// Subtract key /// </summary> Subtract_key = 0x6D, /// <summary> /// Decimal key /// </summary> Decimal_key = 0x6E, /// <summary> /// Divide key /// </summary> Divide_key = 0x6F, /// <summary> /// F1 key /// </summary> F1_key = 0x70, /// <summary> /// F2 key /// </summary> F2_key = 0x71, /// <summary> /// F3 key /// </summary> F3_key = 0x72, /// <summary> /// F4 key /// </summary> F4_key = 0x73, /// <summary> /// F5 key /// </summary> F5_key = 0x74, /// <summary> /// F6 key /// </summary> F6_key = 0x75, /// <summary> /// F7 key /// </summary> F7_key = 0x76, /// <summary> /// F8 key /// </summary> F8_key = 0x77, /// <summary> /// F9 key /// </summary> F9_key = 0x78, /// <summary> /// F10 key /// </summary> F10_key = 0x79, /// <summary> /// F11 key /// </summary> F11_key = 0x7A, /// <summary> /// F12 key /// </summary> F12_key = 0x7B, /// <summary> /// F13 key /// </summary> F13_key = 0x7C, /// <summary> /// F14 key /// </summary> F14_key = 0x7D, /// <summary> /// F15 key /// </summary> F15_key = 0x7E, /// <summary> /// F16 key /// </summary> F16_key = 0x7F, /// <summary> /// F17 key /// </summary> F17_key = 0x80, /// <summary> /// F18 key /// </summary> F18_key = 0x81, /// <summary> /// F19 key /// </summary> F19_key = 0x82, /// <summary> /// F20 key /// </summary> F20_key = 0x83, /// <summary> /// F21 key /// </summary> F21_key = 0x84, /// <summary> /// F22 key /// </summary> F22_key = 0x85, /// <summary> /// F23 key /// </summary> F23_key = 0x86, /// <summary> /// F24 key /// </summary> F24_key = 0x87, /// <summary> /// NUM LOCK key /// </summary> NUM_LOCK_key = 0x90, /// <summary> /// SCROLL LOCK key /// </summary> SCROLL_LOCK_key = 0x91, /// <summary> /// Left SHIFT key /// </summary> Left_SHIFT_key = 0xA0, /// <summary> /// Right SHIFT key /// </summary> Right_SHIFT_key = 0xA1, /// <summary> /// Left CONTROL key /// </summary> Left_CONTROL_key = 0xA2, /// <summary> /// Right CONTROL key /// </summary> Right_CONTROL_key = 0xA3, /// <summary> /// Left MENU key /// </summary> Left_MENU_key = 0xA4, /// <summary> /// Right MENU key /// </summary> Right_MENU_key = 0xA5, /// <summary> /// Browser Back key /// </summary> Browser_Back_key = 0xA6, /// <summary> /// Browser Forward key /// </summary> Browser_Forward_key = 0xA7, /// <summary> /// Browser Refresh key /// </summary> Browser_Refresh_key = 0xA8, /// <summary> /// Browser Stop key /// </summary> Browser_Stop_key = 0xA9, /// <summary> /// Browser Search key /// </summary> Browser_Search_key = 0xAA, /// <summary> /// Browser Favorites key /// </summary> Browser_Favorites_key = 0xAB, /// <summary> /// Browser Start and Home key /// </summary> Browser_Start_and_Home_key = 0xAC, /// <summary> /// Volume Mute key /// </summary> Volume_Mute_key = 0xAD, /// <summary> /// Volume Down key /// </summary> Volume_Down_key = 0xAE, /// <summary> /// Volume Up key /// </summary> Volume_Up_key = 0xAF, /// <summary> /// Next Track key /// </summary> Next_Track_key = 0xB0, /// <summary> /// Previous Track key /// </summary> Previous_Track_key = 0xB1, /// <summary> /// Stop Media key /// </summary> Stop_Media_key = 0xB2, /// <summary> /// Play/Pause Media key /// </summary> Play_Or_Pause_Media_key = 0xB3, /// <summary> /// Start Mail key /// </summary> Start_Mail_key = 0xB4, /// <summary> /// Select Media key /// </summary> Select_Media_key = 0xB5, /// <summary> /// Start Application 1 key /// </summary> Start_Application_1_key = 0xB6, /// <summary> /// Start Application 2 key /// </summary> Start_Application_2_key = 0xB7, /// <summary> /// Used for miscellaneous characters; it can vary by keyboard. /// </summary> Used_for_miscellaneous_characters1 = 0xBA, /// <summary> /// Used for miscellaneous characters; it can vary by keyboard. /// </summary> Used_for_miscellaneous_characters2 = 0xBF, /// <summary> /// Used for miscellaneous characters; it can vary by keyboard. /// </summary> Used_for_miscellaneous_characters3 = 0xC0, /// <summary> /// Used for miscellaneous characters; it can vary by keyboard. /// </summary> Used_for_miscellaneous_characters4 = 0xDB, /// <summary> /// Used for miscellaneous characters; it can vary by keyboard. /// </summary> Used_for_miscellaneous_characters5 = 0xDC, /// <summary> /// Used for miscellaneous characters; it can vary by keyboard. /// </summary> Used_for_miscellaneous_characters6 = 0xDD, /// <summary> /// Used for miscellaneous characters; it can vary by keyboard. /// </summary> Used_for_miscellaneous_characters7 = 0xDE, /// <summary> /// Used for miscellaneous characters; it can vary by keyboard. /// </summary> Used_for_miscellaneous_characters8 = 0xDF, /// <summary> /// Reserved /// </summary> Reserved2 = 0xE0, /// <summary> /// OEM specific /// </summary> OEM_specific1 = 0xE1, /// <summary> /// Either the angle bracket key or the backslash key on the RT 102-key keyboard /// </summary> Either_the_angle_bracket_key_or_the_backslash_key_on_the_RT_102_key_keyboard = 0xE2, /// <summary> /// IME PROCESS key /// </summary> IME_PROCESS_key = 0xE5, /// <summary> /// OEM specific /// </summary> OEM_specific2 = 0xE6, /// <summary> /// Used to pass Unicode characters as if they were keystrokes. The VK_PACKET key is the low word of a 32-bit Virtual Key value used for non-keyboard input methods. For more information, see Remark in KEYBDINPUT, SendInput, WM_KEYDOWN, and WM_KEYUP /// </summary> Used_to_pass_Unicode_characters_as_if_they_were_keystrokes = 0xE7, /// <summary> /// Unassigned /// </summary> Unassigned = 0xE8, /// <summary> /// Attn key /// </summary> Attn_key = 0xF6, /// <summary> /// CrSel key /// </summary> CrSel_key = 0xF7, /// <summary> /// ExSel key /// </summary> ExSel_key = 0xF8, /// <summary> /// Erase EOF key /// </summary> Erase_EOF_key = 0xF9, /// <summary> /// Play key /// </summary> Play_key = 0xFA, /// <summary> /// Zoom key /// </summary> Zoom_key = 0xFB, /// <summary> /// Reserved /// </summary> Reserved = 0xFC, /// <summary> /// PA1 key /// </summary> PA1_key = 0xFD, /// <summary> /// Clear key /// </summary> Clear_key = 0xFE } 调用演示://模拟实现Ctrl+O操作 模拟按下按键(VirtualKeyCode.CTRL_key) 模拟单击按键(VirtualKeyCode.O_key) 模拟弹起按键(VirtualKeyCode.CTRL_key) 本文转自斯克迪亚博客园博客,原文链接:http://www.cnblogs.com/SkyD/p/4070743.html,如需转载请自行联系原作者

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

堪称为经典游戏设计帖整理20个点击回复超高的精品贴

一:Android源码之OpenGL 人物走动源码 代码介绍: OpenGL 人物走动源码,记得前两天发了一个OpenGL的立方体程序,这个比那个厉害,这个是一个可以走 动的小怪兽!http://www.apkbus.com/android-21070-1-1.html 二:Android游戏源码分享之【21点游戏源码】 代码介绍: 一个21点游戏,界面还不错啦!好像还有广告,你还可以顺便学学在程序里加广告的方法。http://www.apkbus.com/android-21046-1-1.html 三:Android 疯狂足球游戏源码 代码介绍: Android 疯狂足球游戏源码,虽然界面很丑,但好赖功能实现了,自己可以美化一下界面.http://www.apkbus.com/android-20986-1-1.html 四:Android扫雷游戏源码 代码介绍: MineSweeper是一个不错的Android开源扫雷游戏,对于初学Android开发网的网友可能有很大的帮助,对 于Java游戏开发也有一定的参考意义。该游戏主要有以下技术值得学习: 1. 个性化字体,计分器使用的 是LED字体,可以帮助我们如何导入外部字体在Android平台中显示。 2. 带图片的Toast,下面的You won in 36 seconds这个Toast使用了自定义的布局,可以显示图片和文字。 3. 自定义Button控件,可 以看到标记是否为雷,显示附近地雷数量的按钮控件,初学者可以很容易的学习到Android开发中常用的 自定义控件技术。http://www.apkbus.com/android-21084-1-1.html 五:Android游戏掩码分享之经典的坦克游戏 代码介绍: 好代码加载上直接可以运行不错。不过运行速度稍慢了一些。http://www.apkbus.com/android-21082-1-1.html 六:Android游戏源码分享之中国象棋 代码介绍: 挑战你自己!集中你的智慧,来享受博弈的乐趣吧!http://www.apkbus.com/android-21047-1-1.html 七:分享20个Android游戏源码,希望大家喜欢哈!http://www.apkbus.com/android-21834-1-1.html 八:Android游戏源码:水果连连看[经典]http://www.apkbus.com/android-21045-1-1.html 九:APRG 《魔域BOSS之战》源码http://www.apkbus.com/android-24301-1-1.html 十:一个android真正实用的游戏框架http://www.apkbus.com/android-825-1-1.html 十一:Android贪吃蛇源码分享http://www.apkbus.com/android-21072-1-1.html 十二:游戏源码分享人机对战 五子棋代源码 绝对给力http://www.apkbus.com/android-21081-1-1.html 十三:连连看游戏源码http://www.apkbus.com/android-21071-1-1.html 十四:【切水果】原理写出来的第一个小游戏http://www.apkbus.com/android-20356-1-1.html 十五:一个模拟模仿筛子的android游戏http://www.apkbus.com/android-21080-1-1.html 十六:Android游戏源码分享一个测试反应速度的小游戏http://www.apkbus.com/android-21069-1-1.html 十七:让玩家自定义手势玩转Android游戏!—Android Gesture之【输入法】http://www.apkbus.com/android-379-1-1.html 十八:斑竹原创android游戏(连连看)http://www.apkbus.com/android-3014-1-1.html 十九:小游戏水果连连看---初学者有兴趣的可以看看http://www.apkbus.com/android-18669-1-1.html 二十:Android游戏开发之爆炸效果[天幕杯]http://www.apkbus.com/android-1605-1-1.htmlPS:我的任何帖子是不需要豆豆的哦,只希望巴友好好对待,多多分享 本文转自qianqianlianmeng博客园博客,原文链接:http://www.cnblogs.com/aimeng/archive/2012/06/02/2531751.html ,如需转载请自行联系原作者

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

【回放视频+PPT下载整理】编程语言系列讲座:深度学习JavaScript和React技术

编程语言系列讲座JavaScript篇,我们邀请了行业资深专家靖鑫和逸翾与大家一起学习最流行的编程语言,本次系列直播将对于JavaScript中的对象、函数和异步编程进行详细解读,并带领大家学习React技术栈,包括快速掌握组件化和搭建页面、Mobx状态管理框架和React Diff算法及新架构Fiber。 数十款阿里云产品限时折扣中,赶快点击这里,领券开始云上实践吧! 4月16日直播讲座 演讲嘉宾为阿里巴巴高级前端工程师逸翾,主讲内容包括JavaScript中的继承、函数式编程和单线程异步编程。 Javascript中的对象 视频链接:https://yq.aliyun.com/video/play/1421 PDF下载:https://yq.aliyun.com/download/2576 回顾文章:https://yq.aliyun.

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

ios 开发,通讯录信息调用常用方法,这个比较全,不用再整理

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 ABAddressBookRef addressBook = ABAddressBookCreate(); CFArrayRef results = ABAddressBookCopyArrayOfAllPeople(addressBook); for ( int i = 0; i < CFArrayGetCount(results); i++) { ABRecordRef person = CFArrayGetValueAtIndex(results, i); //读取firstname NSString *personName = ( NSString *)ABRecordCopyValue(person, kABPersonFirstNameProperty); if (personName != nil ) textView.text = [textView.text stringByAppendingFormat:@ "\n姓名:%@\n" ,personName]; //读取lastname NSString *lastname = ( NSString *)ABRecordCopyValue(person, kABPersonLastNameProperty); if (lastname != nil ) textView.text = [textView.text stringByAppendingFormat:@ "%@\n" ,lastname]; //读取middlename NSString *middlename = ( NSString *)ABRecordCopyValue(person, kABPersonMiddleNameProperty); if (middlename != nil ) textView.text = [textView.text stringByAppendingFormat:@ "%@\n" ,middlename]; //读取prefix前缀 NSString *prefix = ( NSString *)ABRecordCopyValue(person, kABPersonPrefixProperty); if (prefix != nil ) textView.text = [textView.text stringByAppendingFormat:@ "%@\n" ,prefix]; //读取suffix后缀 NSString *suffix = ( NSString *)ABRecordCopyValue(person, kABPers*****uffixProperty); if (suffix != nil ) textView.text = [textView.text stringByAppendingFormat:@ "%@\n" ,suffix]; //读取nickname呢称 NSString *nickname = ( NSString *)ABRecordCopyValue(person, kABPersonNicknameProperty); if (nickname != nil ) textView.text = [textView.text stringByAppendingFormat:@ "%@\n" ,nickname]; //读取firstname拼音音标 NSString *firstnamePhonetic = ( NSString *)ABRecordCopyValue(person, kABPersonFirstNamePhoneticProperty); if (firstnamePhonetic != nil ) textView.text = [textView.text stringByAppendingFormat:@ "%@\n" ,firstnamePhonetic]; //读取lastname拼音音标 NSString *lastnamePhonetic = ( NSString *)ABRecordCopyValue(person, kABPersonLastNamePhoneticProperty); if (lastnamePhonetic != nil ) textView.text = [textView.text stringByAppendingFormat:@ "%@\n" ,lastnamePhonetic]; //读取middlename拼音音标 NSString *middlenamePhonetic = ( NSString *)ABRecordCopyValue(person, kABPersonMiddleNamePhoneticProperty); if (middlenamePhonetic != nil ) textView.text = [textView.text stringByAppendingFormat:@ "%@\n" ,middlenamePhonetic]; //读取organization公司 NSString *organization = ( NSString *)ABRecordCopyValue(person, kABPersonOrganizationProperty); if (organization != nil ) textView.text = [textView.text stringByAppendingFormat:@ "%@\n" ,organization]; //读取jobtitle工作 NSString *jobtitle = ( NSString *)ABRecordCopyValue(person, kABPersonJobTitleProperty); if (jobtitle != nil ) textView.text = [textView.text stringByAppendingFormat:@ "%@\n" ,jobtitle]; //读取department部门 NSString *department = ( NSString *)ABRecordCopyValue(person, kABPersonDepartmentProperty); if (department != nil ) textView.text = [textView.text stringByAppendingFormat:@ "%@\n" ,department]; //读取birthday生日 NSDate *birthday = ( NSDate *)ABRecordCopyValue(person, kABPersonBirthdayProperty); if (birthday != nil ) textView.text = [textView.text stringByAppendingFormat:@ "%@\n" ,birthday]; //读取note备忘录 NSString *note = ( NSString *)ABRecordCopyValue(person, kABPersonNoteProperty); if (note != nil ) textView.text = [textView.text stringByAppendingFormat:@ "%@\n" ,note]; //第一次添加该条记录的时间 NSString *firstknow = ( NSString *)ABRecordCopyValue(person, kABPersonCreationDateProperty); NSLog (@ "第一次添加该条记录的时间%@\n" ,firstknow); //最后一次修改該条记录的时间 NSString *lastknow = ( NSString *)ABRecordCopyValue(person, kABPersonModificationDateProperty); NSLog (@ "最后一次修改該条记录的时间%@\n" ,lastknow); //获取email多值 ABMultiValueRef email = ABRecordCopyValue(person, kABPersonEmailProperty); int emailcount = ABMultiValueGetCount(email); for ( int x = 0; x < emailcount; x++) { //获取email Label NSString * emailLabel = ( NSString *)ABAddressBookCopyLocalizedLabel(ABMultiValueCopyLabelAtIndex(email, x)); //获取email值 NSString * emailContent = ( NSString *)ABMultiValueCopyValueAtIndex(email, x); textView.text = [textView.text stringByAppendingFormat:@ "%@:%@\n" ,emailLabel,emailContent]; } //读取地址多值 ABMultiValueRef address = ABRecordCopyValue(person, kABPersonAddressProperty); int count = ABMultiValueGetCount(address); for ( int j = 0; j < count; j++) { //获取地址Label NSString * addressLabel = ( NSString *)ABMultiValueCopyLabelAtIndex(address, j); textView.text = [textView.text stringByAppendingFormat:@ "%@\n" ,addressLabel]; //获取該label下的地址6属性 NSDictionary * personaddress =( NSDictionary *) ABMultiValueCopyValueAtIndex(address, j); NSString * country = [personaddress valueForKey:( NSString *)kABPersonAddressCountryKey]; if (country != nil ) textView.text = [textView.text stringByAppendingFormat:@ "国家:%@\n" ,country]; NSString * city = [personaddress valueForKey:( NSString *)kABPersonAddressCityKey]; if (city != nil ) textView.text = [textView.text stringByAppendingFormat:@ "城市:%@\n" ,city]; NSString * state = [personaddress valueForKey:( NSString *)kABPersonAddressStateKey]; if (state != nil ) textView.text = [textView.text stringByAppendingFormat:@ "省:%@\n" ,state]; NSString * street = [personaddress valueForKey:( NSString *)kABPersonAddressStreetKey]; if (street != nil ) textView.text = [textView.text stringByAppendingFormat:@ "街道:%@\n" ,street]; NSString * zip = [personaddress valueForKey:( NSString *)kABPersonAddressZIPKey]; if (zip != nil ) textView.text = [textView.text stringByAppendingFormat:@ "邮编:%@\n" ,zip]; NSString * coutntrycode = [personaddress valueForKey:( NSString *)kABPersonAddressCountryCodeKey]; if (coutntrycode != nil ) textView.text = [textView.text stringByAppendingFormat:@ "国家编号:%@\n" ,coutntrycode]; } //获取dates多值 ABMultiValueRef dates = ABRecordCopyValue(person, kABPersonDateProperty); int datescount = ABMultiValueGetCount(dates); for ( int y = 0; y < datescount; y++) { //获取dates Label NSString * datesLabel = ( NSString *)ABAddressBookCopyLocalizedLabel(ABMultiValueCopyLabelAtIndex(dates, y)); //获取dates值 NSString * datesContent = ( NSString *)ABMultiValueCopyValueAtIndex(dates, y); textView.text = [textView.text stringByAppendingFormat:@ "%@:%@\n" ,datesLabel,datesContent]; } //获取kind值 CFNumberRef recordType = ABRecordCopyValue(person, kABPersonKindProperty); if (recordType == kABPersonKindOrganization) { // it's a company NSLog (@ "it's a company\n" ); } else { // it's a person, resource, or room NSLog (@ "it's a person, resource, or room\n" ); } //获取IM多值 ABMultiValueRef instantMessage = ABRecordCopyValue(person, kABPersonInstantMessageProperty); for ( int l = 1; l < ABMultiValueGetCount(instantMessage); l++) { //获取IM Label NSString * instantMessageLabel = ( NSString *)ABMultiValueCopyLabelAtIndex(instantMessage, l); textView.text = [textView.text stringByAppendingFormat:@ "%@\n" ,instantMessageLabel]; //获取該label下的2属性 NSDictionary * instantMessageContent =( NSDictionary *) ABMultiValueCopyValueAtIndex(instantMessage, l); NSString * username = [instantMessageContent valueForKey:( NSString *)kABPersonInstantMessageUsernameKey]; if (username != nil ) textView.text = [textView.text stringByAppendingFormat:@ "username:%@\n" ,username]; NSString * service = [instantMessageContent valueForKey:( NSString *)kABPersonInstantMessageServiceKey]; if (service != nil ) textView.text = [textView.text stringByAppendingFormat:@ "service:%@\n" ,service]; } //读取电话多值 ABMultiValueRef phone = ABRecordCopyValue(person, kABPersonPhoneProperty); for ( int k = 0; k<ABMultiValueGetCount(phone); k++) { //获取电话Label NSString * personPhoneLabel = ( NSString *)ABAddressBookCopyLocalizedLabel(ABMultiValueCopyLabelAtIndex(phone, k)); //获取該Label下的电话值 NSString * personPhone = ( NSString *)ABMultiValueCopyValueAtIndex(phone, k); textView.text = [textView.text stringByAppendingFormat:@ "%@:%@\n" ,personPhoneLabel,personPhone]; } //获取URL多值 ABMultiValueRef url = ABRecordCopyValue(person, kABPersonURLProperty); for ( int m = 0; m < ABMultiValueGetCount(url); m++) { //获取电话Label NSString * urlLabel = ( NSString *)ABAddressBookCopyLocalizedLabel(ABMultiValueCopyLabelAtIndex(url, m)); //获取該Label下的电话值 NSString * urlContent = ( NSString *)ABMultiValueCopyValueAtIndex(url,m); textView.text = [textView.text stringByAppendingFormat:@ "%@:%@\n" ,urlLabel,urlContent]; } //读取照片 NSData *image = ( NSData *)ABPersonCopyImageData(person); UIImageView *myImage = [[UIImageView alloc] initWithFrame:CGRectMake(200, 0, 50, 50)]; [myImage setImage:[UIImage imageWithData:image]]; myImage.opaque = YES ; [textView addSubview:myImage]; } CFRelease(results); CFRelease(addressBook); 本文转自夏雪冬日博客园博客,原文链接:http://www.cnblogs.com/heyonggang/p/3472867.html,如需转载请自行联系原作者

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

爆肝整理5000字!HTAP的关键技术有哪些?| StoneDB学术分享会#3

在最新一届国际数据库顶级会议 ACM SIGMOD 2022 上,来自清华大学的李国良和张超两位老师发表了一篇论文:《HTAP Database: What is New and What is Next》,并做了 《HTAP Database:A Tutorial》 的专项报告。这几期学术分享会的文章,StoneDB将系统地梳理一下两位老师的报告,带读者了解 HTAP 的发展现状和未来趋势。 在深度干货!一篇Paper带您读懂HTAP这期分享中我们已经把HTAP产生的背景和现有的HTAP数据库及其技术栈做了一个简单的介绍,这一期,我们将着重讲一讲报告中对HTAP关键技术的解读。 在正式开始前,先给上期简单收个尾,报告中提到 HTAP 数据库除了以下四种: Primary Row Store + InMemory Column Store Distributed Row Store + Column Store Replica Disk Row Store + Distributed Column Store Primary Column Store + Delta Row Store 还补充了3种,感兴趣的读者可以自行了解: Row-based HTAP systems:如 Hyper(Row)、BatchDB等 Column-based HTAP systems:如 Caldera、RateupDB等 Spark-based HTAP systems:如 Splice Machine、Wildfire等 下面进入正文,本篇报告中主要介绍了HTAP的五大类关键技术,分别是: Transaction Processing(事务处理技术) Analytical Processing(查询分析技术) Data Synchronization(数据同步技术) Query Optimization(查询优化技术) Resource Scheduling(资源调度技术) Overview of HTAP Techniques 这些关键技术被最先进的HTAP数据库采用。然而,它们在各种指标上各有利弊,例如效率、可扩展性和数据新鲜度。 An Overview of HTAP Techniques 一、事务处理技术(OLTP) HTAP数据库中的OLTP工作负载是通过行存储处理的,但不同的架构会导致不同的TP技术。它主要由两种类型组成。 1使用内存增量更新的单机事务处理 Standalone Transaction Processing with In-Memory Delta Update 关键技术点: 通过MVCC协议进行单机事务处理 通过内存增量更新进行insert/delete/update操作 相关数据库:Oracle、SQL Server、SAP HANA 和 StoneDB 等。 Standalone TP for insert/delete/update operations 上面这张图介绍了单机事务处理执行insert/delete/operations操作的一个逻辑过程,总体上是借助MVCC+logging,它依赖于MVCC协议和日志记录技术来处理事务。具体来说,每个插入首先写入日志和行存储,然后附加到内存中的增量存储。更新创建具有新生命周期的行的新版本,即开始时间戳和结束时间戳,即旧版本在删除位图中被标记为删除行。因此,事务处理是高效的,因为 DML 操作是在内存中执行的。请注意,某些方法可能会将数据写入行存储 或增量行存储 ,并且它们可能仅在事务提交时写入日志。 一般有三种方式来实现内存增量存储,分别是:Heaptable(堆表)、Index organized table(索引组织表) 和 L1 cache(一级缓存),具体区别如下表: Three implementations for an in-memory delta store 三者主要的区别在于插入(insertion)速度、查询(lookup)速度和容量(capacity)大小上。 2使用日志回放的分布式事务处理 Distributed Transaction Processing with Log Replay 关键技术点: Two-Phase Commit(2PC)`+Paxos 用于分布式TP和数据复制 使用Log replay(日志回放)更新行存储和列存储 相关数据库:F1 Lightning 注:这里有稍有不同的分布式TP方案,就是采用主从复制(Master-slave replication)的方式实现HTAP,比如 Singlestore。 Master node handles the transactions, then replicate the logs to slave nodes 如上图所示,这种主从复制的方式下,主节点处理事务,然后将日志复制到从节点再做分析。 另外就是通过2PC+Raft+logging,它依赖于分布式架构。 Modified Raft Protocol for TP and AP nodes 它通过分布式事务处理提供了高可扩展性。ACID事务在分布式节点上使用两阶段提交 (2PC) 协议、基于Raft的共识算法和预写日志 (WAL) 技术进行处理。特别是leader节点接收到SQL引擎的请求,然后在本地追加日志,异步发送日志给follower节点。如果大多数节点(即法定人数)成功附加日志,则领导者提交请求并在本地应用它。与第一种内存增量更新的方式相比,这种基于日志的方式由于分布式事务处理效率较低。 3对比 最后我们将提到事务处理技术做一个对比: Comparisons of TP techniques in HTAP Databases 二、查询分析技术(OLAP) 对于HTAP数据库,OLAP负载使用面向列的技术执行,例如压缩数据上的聚合和单指令多数据 (SIMD) 指令。这里介绍两大类型和三种方式: 1内存增量遍历 + 单机列扫描 Standalone Columnar Scan with In-Memory Delta Traversing 关键技术点: 单指令多数据(SIMD),向量化处理 内存增量遍历(In-Memory Delta Traversing) 相关数据库:Oracle、SQL Server 举几个例子: 在(a)里,我们查询订单表中名称为JASON的花了多少钱,那么基于SIMD的查询方式,是可以通过CPU把数据Load到寄存器中,只需要一个CPU执行的周期,就可以同时去扫描查到满足条件的两个数据。 如果基于传统的火山模型,用的是一种迭代模型,就需要访问四行数据,而基于这种列存的方式,只要访问一次就可以得到结果。 同样的,还可以通过这种方式做基于Bloom Filter的向量化连接,也可以提高查询分析的效率。 在增量表中获取可见值,并跳过删除表中的陈旧数据 除此之外,我们在做列存扫描的同时要去扫描一些可见的增量数据来实现实时数据的分析,也去扫描删除表中是否有过期的数据,最终达到数据是新鲜的。 2日志文件扫描 + 分布式列扫描 Distributed Columnar Scan with Log File Scanning 关键技术点: 列段上的分布式查询处理 基于磁盘的日志文件归并与扫描 相关数据库:F1 Lightning Distributed Columnar Scan Samwel, Bart, et al. "F1 query: Declarative querying at scale."PVLDB 11(12) , 2018: 1835-1848. 如上图所示的分布式列扫描,传统方法就是基于多个Worker来做并发的多节点下进行算子下推,扫描之后,在上层做一些聚集、HASH、排序等一些操作。 Log File Scanning Yang, Jiacheng, et al. "F1 Lightning: HTAP as a Service." PVLDB 13(12), 2020: 3313-3325. 与分布式列扫描同时进行的还有日志文件扫描,我们还要基于列存储来做算子下推来查到当前增量存储中最新的数据,再进行数据合并。上图里的F1 Lighting就可以支持把10个小时的数据合并到Delta中。 3技术总结 内存中增量和列扫描 将内存中的增量和列数据一起扫描,因为增量存储可能包括尚未合并到列存储的更新记录。由于它已经扫描了最近可见的delta tuples in内存,因此OLAP的数据新鲜度很高。 基于日志的增量和列扫描 将基于日志的增量数据和列数据一起扫描以获取传入查询。与第一种类似,第二种使用列存储扫描最新的增量用于OLAP。但是,由于读取可能尚未合并的delta文件,这样的过程更加昂贵。因此,数据新鲜度较低,因为发送和合并delta文件的高延迟。 列扫描 只扫描列数据以获得高效率,因为没有读取任何增量数据的开销。但是,当数据在行存储中经常更新时,这种技术会导致新鲜度低。 4对比 Comparisons of AP techniques in HTAP Databases 对于第一种单机列扫描+内存增量遍历来说,优点是数据新鲜度较高,缺点是需要比较多的内存空间。对第二种分布式列扫描+日志文件扫描来说,优点是扩展性高,缺点是效率比较低。 三、数据同步技术(DS) 由于在查询时读取增量数据的成本很高,因此需要定期将增量数据合并到主列存储中。这个时候,数据同步(Data Synchronization)技术,就非常重要了,也是分为两大类型。 1类型一:内存增量合并 关键技术: 基于阈值的合并(Threshold-based merging) 两阶段数据迁移(Two-phase data migration) 基于字典的迁移(Dictionary-based migration) 相关数据库:Oracle、SQL Server、SAP HANA Threshold-based merging 举个例子,如果阈值达到列存储90%的容量时,我们就把Delta Table中的数据合并到Column Store当中,当然这种方式有个缺点——如果阈值设置的太大可能会导致AP的性能发生抖动,所以看到图里我们加了一个Trick-based,最好是定小量按期地去合并,做一个权衡。 Two-phase data migration 两阶段数据的迁移,可以拿SQL Server举例,如图所示: 第一阶段:迁移准备 第(1)步先插入RID去把需要隐藏的数据写入到删除表当中; 第(2)步把这些数据分配RID后生成Delta Colunm Store; 第二阶段:迁移操作 第(3)步,把刚刚插入进去的RID删除; 第(4)步,最后真正把Delta中相关的数据删除,最后才把生成的增量列存合并到主列存中,达到了数据迁移的效果。 Dictionary-based migration 第三种基于字典的迁移,SAP HANA就是采用这种方式。如图所示,它第一阶段主要是针对每一列的数据做一个本地字典的方式映射过去增加对应的数据向量,第二阶段,才会把这个字典加入到全局的字典,做一个全局的排序,最后合并到数据向量当中。 2类型二:基于日志的增量合并 关键技术:LSM-tree 和 B-tree 相关数据库:F1 Lighting 这种类型分两层: Memory-resident deltas(驻留内存的增量),主要是row-wise Disk-resident deltas(驻留磁盘的增量),主要是column-wise Log-based delta merge 如图所示,第一阶段会把小文件、小delta按它到来的顺序合并到增量的文件当中,第二阶段会通过查找B+树来做一个有序合并,最终让合并的Chunk是有序的。这主要涉及解决基于多版本的一些Log怎么去做合并和折叠,其中B+树主要的作用就是在有序合并时去加速数据查找的过程。 3技术总结 可归类为有3种数据合并技术。分别是: 内存中增量合并; 基于磁盘的增量合并; 从主行存储重建。 第一个类别定期将新插入的内存中增量数据合并到主列存储。引入了几种技术来优化合并过程,包括两阶段基于事务的数据迁移、字典编码排序合并和基于阈值的变化传播。 第二类 将基于磁盘的增量文件合并到主列存储。为了加快合并过程,增量数据可以通过B+树进行索引,因此可以通过键查找有效地定位增量项。 第三类从主行存储重建内存列存储。这对于增量更新超过某个阈值的情况很典型,因此重建列存储比合并这些具有大内存占用的更新更有效。 4对比 Comparisons of DS techniques in HTAP Databases 总体来看,基于内存的增量合并效率比较高,扩展性差点儿;基于日志的合并,扩展性好,但是多重合并的代价会比较高。 四、查询优化技术 HTAP中查询优化技术主要涉及三个维度,包括: Query Optimization in HTAP databases 1In-Memory Column Selection 解决的问题:要从行存储区中选择哪些列进入内存呢?简单的做法是全部选择进去,但那样存储和更新的代价会很高,所以一般是基于历史的统计信息和现有内存的预算来做权衡选择。 解决方式:有两种,第一种是热力图(Heatmap),第二种是整数规划(Integer programming) 基于Heatmap,比如Oracle 通过访问频度来管理列存并进行热力图的制作。 Oracle 21c. Automating Management of In-Memory Objects., 2021. 如图所示,首先根据最下方持久化行存(Persistent Storage)来选定一些候选列集(Candidate Columns),通过记录候选集的访问频度。有些列就会变为Hot Columns,那么就可以把最新的数据Load进去(图中左下方的Populating)来达到加速查询的效果;慢慢地也有一些列会变为Cold Columns,那么就把这些冷列进行压缩(Compressed Columnar data),然后最后排除(图中右下方的Evicating)到内存当中,再选取其他被高频访问的列重复进行。 基于Integer programming,比如Hyrise Integer programming for 0/1 Knapsack problem,第一种基于热力图的方式完全没有考虑选择列的代价,那么第二种就是把代价函数(cost-based optimization problem)考虑进去,再从二级存储中选择列。 Boissier, Martin, et al. "Hybrid data layouts for tiered HTAP databases with paretooptimal data placements." ICDE, 2018. 这个目标函数 就是要去优化所有的查询代价函数 ,而每个查询代价函数要去衡量所选择列的代价。总目标是最小化这个代价,要小于这个最大的 预算。(这里不展开讲了,感兴趣可以阅读这篇论文,也比较经典) 2Hybrid Row and Column Scan 解决的问题:内存中选择好列之后,如何利用混合行/列扫描加速查询呢?这个比较前沿了,像Google的AlloyDB也支持这个特性。 解决方式:基于规则的计划选择(Rule-based)或者基于代价(Cost-based)的计划选择。主要决定查询优化器要把哪些算子下推到列存引擎当中。 Rule-based Optimization(RBO) 先定义一些扫描的规则,比如先看列扫描,再看行扫描。相关数据库:Oracle、SQL server。 举个例子,上图中我们查找一些北京车子注册的证书和颜色,在这种规则下就可以生成一个逻辑计划树,在这个逻辑计划树里,我们先去查找底层表到底是行存还是列存,如果是列存,就做列存的方式处理,如果是行存就做一个B+树的扫描。最后合并做一个连接再返回最终结果。 Cost-based Optimization(CBO) 基于代价的方式,解决的方式是先去对比列存扫描和行存/索引扫描的代价分别是多少,然后再决定查询算子在哪里执行。 Compare the cost of row/index scan with the cost of columnar scan 3CPU/GPU Acceleration for HTAP 这个基于硬件去做HTAP的加速。比如,基于CPU/GPU异构处理器的方式进行HTAP的处理。相关数据库:RateupDB、Caldera。 CPU/GPU processors for HTAP Task-parallel nature of CPUs for handling OLTP CPU任务并行的特性可以用来处理OLTP Data-parallel nature of GPUs for handling OLAP GPU数据并行的特性可以用来处理OLAP 涉及到一些并行计算的知识,感兴趣可以了解了解~ 4技术总结 主要涉及三大技术: HTAP的列选择; 混合行/列扫描; HTAP 的 CPU/GPU加速。 第一种类型依靠历史工作负载和统计数据来选择从主存储中提取的频繁访问的列到内存中。因此,可以将查询下推到内存中的列存储以进行加速。缺点是可能没有选择新查询的列,导致基于行的查询处理。现有方法依靠历史工作负载的访问模式来加载热数据并驱逐冷数据。 第二种类型利用混合行/列扫描来加速查询使用这些技术,可以分解复杂的查询以在行存储或列存储上执行,然后组合结果。这对于可以使用基于行的索引扫描和完整的基于列的扫描执行的 SPJ 查询来说是典型的。我们引入了基于成本的方法来选择混合行/列访问路径。 第三种技术利用异构CPU/GPU架构来加速HTAP工作负载。这些技术分别利用CPU的任务并行性和GPU的数据并行性来处理OLTP和OLAP。然而,这些技术有利于高OLAP吞吐量,同时具有低OLTP吞吐量。 5对比 Query Optimization in HTAP databases 五、资源调度技术 对于 HTAP 数据库,资源调度是指为 OLTP 和OLAP 工作负载分配资源。当前可以动态控制OLTP 和 OLAP 工作负载的执行模式,以更好地利用资源。 Resource Scheduling 1基于工作负载驱动的调度 Workload-driven Scheduling for HTAP,就是根据HTAP工作负载的执行性能进行动态的调度,线程和内存这些根据OLTP和OLAP的性能需求动态分配资源。相关数据库: SAP HANA。 Assign more threads to OLTP or OLAP Psaroudakis, Iraklis, et al. "Task scheduling for highly concurrent analytical and transactional main-memory workloads." In ADMS, 2013. 举个栗子,第一种方式会分配更多的线程给OLTP或者OLAP,怎么分配呢?会在Scheduler里配置一个Watch-dog监测器,来监测当前的Worker,有一些被阻塞的Worker就分配多一些Thread,有一些比较活跃的的,就让它继续执行。 Isolate the workload execution and assign more cache for OLAP Sirin, Utku, Sandhya Dwarkadas, and Anastasia Ailamaki. "Performance Characterization of HTAP Workloads." In ICDE, 2021. 如表所示,在第三层Cache当中进行一些调度,通过实验可以得知增加OLTP存储资源时,OLAP的颜色是会变深的,这意味着影响越严重,所以还是建议在第三层尽量给OLAP多分配一些资源。 2基于新鲜度驱动的调度 Freshness-driven Scheduling for HTAP Switches the execution modes {S1, S2, S3} Default S2; When freshness < threshold, jump to S1 or S3 Raza, Aunn, et al. "Adaptive HTAP through elastic resource scheduling." In SIGMOD, 2020. 3技术总结 调度技术有两种类型,工作负载驱动的方法和新鲜度驱动的方法。前者根据执行工作负载的性能调整OLTP和OLAP任务的并行度。例如,当CPU资源被OLAP线程饱和时,任务调度器可以在增大OLTP线程的同时降低OLAP的并行度。后一个切换了OLTP和OLAP的资源分配和数据交换的执行模式。例如,调度程序单独控制OLTP和OLAP的执行以实现高吞吐量,然后定期同步数据。一旦数据新鲜度变低,它就会切换到共享 CPU、内存和数据的执行模式。其他与 HTAP 相关的技术,还有新的 HTAP 索引技术和横向扩展技术。 4对比 Resource Scheduling in HTAP databases 第一种优点是比较容易实现,但是新鲜度较低;第二种方式优点是新鲜度高,但来回切换容易导致系统震荡。 六、其他相关的HTAP技术 这里不展开介绍了,感兴趣可以看看相关的Paper。 1Multi-Versioned Indexes for HTAP Parallel Binary Tree (P-Tree) Sun, Yihan, et al. "On supporting efficient snapshot isolation for hybrid workloads with multiversioned indexes." PVLDB 13(2), 2019. Multi-Version Partitioned B-Tree (MV-PBT) Riegger, Christian, et al. "MV-PBT: multi-version indexing for large datasets and HTAP workloads." In EDBT, 2020. 2Adaptive Data Organization for HTAP H2O [Alagiannis et al, SIGMOD 2014], Casper [Athanassoulis et al, VLDB 2019] Peloton [Arulraj et al, SIGMOD 2016], Proteus [Abebe et al, SIGMOD 2022] Abebe, Michael, Horatiu Lazu, and Khuzaima Daudjee. Proteus: Autonomous Adaptive Storage for Mixed Workloads. In SIGMOD, 2022. 责编:宇亭 HTAP的基准测试 下一期我们将介绍针对HTAP基准测试的相关进展,在我们的第二期学术分享会里,也介绍过来自慕尼黑工业大学(TMU)数据库组的研究,感兴趣可以前往查看,这篇文章只是其中一种基准测试的方法,想了解还有哪些方法么?欢迎关注StoneDB公众号,我下期见~ StoneDB 已经正式开源,欢迎关注我们 https://github.com/stoneatom/stonedb 什么是真正的HTAP?(一)背景篇 什么是真正的HTAP?(二)挑战篇 官网:https://stonedb.io/ Github:https://github.com/stoneatom/stonedb Slack:https://stonedb.slack.com/ssb/redirect#/shared-invite/email

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

整理全网优秀的API接口设计及相关优秀的接口管理、在线文档生成工具

2 --> 一、优秀的接口设计 在日常开发中,总会接触到各种接口。前后端数据传输接口,第三方业务平台接口。一个平台的前后端数据传输接口一般都会在内网环境下通信,而且会使用安全框架,所以安全性可以得到很好的保护。这篇文章重点讨论一下提供给第三方平台的业务接口应当如何设计?我们应该考虑哪些问题? 主要从以上三个方面来设计一个安全的API接口。 1.1 安全性问题 安全性问题是一个接口必须要保证的规范。如果接口保证不了安全性,那么你的接口相当于直接暴露在公网环境中任人蹂躏。 1.1.1 调用接口的先决条件-token 获取token一般会涉及到几个参数appid,appkey,timestamp,nonce,sign。我们通过以上几个参数来获取调用系统的凭证。 appid和appkey可以直接通过平台线上申请,也可以线下直接颁发。appid是全局唯一的,每个appid将对应一个客户,appkey需要高度保密。 timestamp是时间戳,使用系统当前的unix时间戳。时间戳的目的就是为了减轻DOS攻J。防止请求被拦截后一直尝试请求接口。服务器端设置时间戳阀值,如果请求时间戳和服务器时间超过阀值,则响应失败。 nonce是随机值。随机值主要是为了增加sign的多变性,也可以保护接口的幂等性,相邻的两次请求nonce不允许重复,如果重复则认为是重复提交,响应失败。 sign是参数签名,将appkey,timestamp,nonce拼接起来进行md5加密(当然使用其他方式进行不可逆加密也没问题)。 token,使用参数appid,timestamp,nonce,sign来获取token,作为系统调用的唯一凭证。token可以设置一次有效(这样安全性更高),也可以设置时效性,这里推荐设置时效性。如果一次有效的话这个接口的请求频率可能会很高。token推荐加到请求头上,这样可以跟业务参数完全区分开来。 1.1.2 使用POST作为接口请求方式 一般调用接口最常用的两种方式就是GET和POST。两者的区别也很明显,GET请求会将参数暴露在浏览器URL中,而且对长度也有限制。为了更高的安全性,所有接口都采用POST方式请求。 1.1.3 客户端IP白名单 ip白名单是指将接口的访问权限对部分ip进行开放。这样就能避免其他ip进行访问***,设置ip白名单比较麻烦的一点就是当你的客户端进行迁移后,就需要重新联系服务提供者添加新的ip白名单。设置ip白名单的方式很多,除了传统的防火墙之外,spring cloud alibaba提供的组件sentinel也支持白名单设置。为了降低api的复杂度,推荐使用防火墙规则进行白名单设置。 1.1.4 单个接口针对ip限流 限流是为了更好的维护系统稳定性。使用redis进行接口调用次数统计,ip+接口地址作为key,访问次数作为value,每次请求value+1,设置过期时长来限制接口的调用频率。 1.5 记录接口请求日志 使用aop全局记录请求日志,快速定位异常请求位置,排查问题原因。 1.1.6 敏感数据脱敏 在接口调用过程中,可能会涉及到订单号等敏感数据,这类数据通常需要脱敏处理,最常用的方式就是加密。加密方式使用安全性比较高的RSA非对称加密。非对称加密算法有两个密钥,这两个密钥完全不同但又完全匹配。只有使用匹配的一对公钥和私钥,才能完成对明文的加密和解密过程。 1.2 幂等性问题 幂等性是指任意多次请求的执行结果和一次请求的执行结果所产生的影响相同。说的直白一点就是查询操作无论查询多少次都不会影响数据本身,因此查询操作本身就是幂等的。但是新增操作,每执行一次数据库就会发生变化,所以它是非幂等的。 幂等问题的解决有很多思路,这里讲一种比较严谨的。提供一个生成随机数的接口,随机数全局唯一。调用接口的时候带入随机数。第一次调用,业务处理成功后,将随机数作为key,操作结果作为value,存入redis,同时设置过期时长。第二次调用,查询redis,如果key存在,则证明是重复提交,直接返回错误。 1.3 数据规范问题 1.3.1 版本控制 一套成熟的API文档,一旦发布是不允许随意修改接口的。这时候如果想新增或者修改接口,就需要加入版本控制,版本号可以是整数类型,也可以是浮点数类型。一般接口地址都会带上版本号,http://ip:port//v1/list。 1.3.2 响应状态码规范 一个牛逼的API,还需要提供简单明了的响应值,根据状态码就可以大概知道问题所在。我们采用http的状态码进行数据封装,例如200表示请求成功,4xx表示客户端错误,5xx表示服务器内部发生错误。状态码设计参考如下: 分类 描述 1xx 信息,服务器收到请求,需要请求者继续执行操作 2xx 成功 3xx 重定向,需要进一步的操作以完成请求 4xx 客户端错误,请求包含语法错误或无法完成请求 5xx 服务端错误 状态码枚举类: public enum CodeEnum {​ // 根据业务需求进行添加 SUCCESS(200,"处理成功"), ERROR_PATH(404,"请求地址错误"), ERROR_SERVER(505,"服务器内部发生错误"); private int code; private String message; CodeEnum(int code, String message) { this.code = code; this.message = message; }​ public int getCode() { return code; }​ public void setCode(int code) { this.code = code; }​ public String getMessage() { return message; }​ public void setMessage(String message) { this.message = message; }} 1.3.3 统一响应数据格式 为了方便给客户端响应,响应数据会包含三个属性,状态码(code),信息描述(message),响应数据(data)。客户端根据状态码及信息描述可快速知道接口,如果状态码返回成功,再开始处理数据。 响应结果定义及常用方法: public class R implements Serializable {​ private static final long serialVersionUID = 793034041048451317L;​ private int code; private String message; private Object data = null;​ public int getCode() { return code; } public void setCode(int code) { this.code = code; }​ public String getMessage() { return message; } public void setMessage(String message) { this.message = message; }​ public Object getData() { return data; }​ /** * 放入响应枚举 */ public R fillCode(CodeEnum codeEnum){ this.setCode(codeEnum.getCode()); this.setMessage(codeEnum.getMessage()); return this; }​ /** * 放入响应码及信息 */ public R fillCode(int code, String message){ this.setCode(code); this.setMessage(message); return this; }​ /** * 处理成功,放入自定义业务数据集合 */ public R fillData(Object data) { this.setCode(CodeEnum.SUCCESS.getCode()); this.setMessage(CodeEnum.SUCCESS.getMessage()); this.data = data; return this; }} 1.4 接口设计总结 本篇文章从安全性、幂等性、数据规范等方面讨论了API设计规范。除此之外,一个好的API还少不了一个优秀的接口文档。接口文档的可读性非常重要,虽然很多程序员都不喜欢写文档,而且不喜欢别人不写文档。为了不增加程序员的压力,推荐使用swagger或其他接口管理工具,通过简单配置,就可以在开发中测试接口的连通性,上线后也可以生成离线文档用于管理API。 二、推荐些优秀的在线文档生成工具 一些优秀的在线文档、接口文档的生成工具,需满足如下条件: 必须是开源的 能够实时生成在线文档 支持全文搜索 支持在线调试功能 界面优美 在此推荐一些满足上述条件的在线接口文档工具。 2.1 Knife4j gitee地址:https://gitee.com/xiaoym/knife4j 开源协议:Apache-2.0 License 推荐指数:★★★★ 示例地址:http://swagger-bootstrap-ui.xiaominfo.com/doc.html Knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案,前身是swagger-bootstrap-ui,取名kni4j是希望她能像一把匕首一样小巧,轻量,并且功能强悍。 优点:基于swagger生成实时在线文档,支持在线调试,全局参数、国际化、访问权限控制等,功能非常强大。 缺点:界面有一点点丑,需要依赖额外的jar包 个人建议:如果公司对ui要求不太高,可以使用这个文档生成工具,比较功能还是比较强大的。 2.2 smartdoc gitee地址:https://gitee.com/smart-doc-team/smart-doc 开源协议:Apache-2.0 License 用户:小米、科大讯飞、1加 推荐指数:★★★★ 示例: smart-doc是一个java restful api文档生成工具,smart-doc颠覆了传统类似swagger这种大量采用注解侵入来生成文档的实现方法。smart-doc完全基于接口源码分析来生成接口文档,完全做到零注解侵入,只需要按照java标准注释的写就能得到一个标准的markdown接口文档。 优点:基于接口源码分析生成接口文档,零注解侵入,支持html、pdf、markdown格式的文件导出。 缺点:需要引入额外的jar包,不支持在线调试 个人建议:如果实时生成文档,但是又不想打一些额外的注解,比如:使用swagger时需要打上@Api、@ApiModel等注解,就可以使用这个。 2.3 redoc github地址:https://github.com/Redocly/redoc 开源协议:MIT License 用户:docker、redocly 推荐指数:★★★☆ 示例: redoc自己号称是一个最好的在线文档工具。它支持swagger接口数据,提供了多种生成文档的方式,非常容易部署。使用redoc-cli能够将您的文档捆绑到零依赖的 HTML文件中,响应式三面板设计,具有菜单/滚动同步。 优点:非常方便生成文档,三面板设计 缺点:不支持中文搜索,分为:普通版本 和 付费版本,普通版本不支持在线调试。另外UI交互个人感觉不适合国内大多数程序员的操作习惯。 个人建议:如果想快速搭建一个基于swagger的文档,并且不要求在线调试功能,可以使用这个。 2.4 yapi github地址:https://github.com/YMFE/yapi 开源协议:Apache-2.0 License 用户:腾讯、阿里、百度、京东等大厂 推荐指数:★★★★★ 示例: yapi是去哪儿前端团队自主研发并开源的,主要支持以下功能: 可视化接口管理 数据mock 自动化接口测试 数据导入(包括swagger、har、postman、json、命令行) 权限管理 支持本地化部署 支持插件 支持二次开发 优点:功能非常强大,支持权限管理、在线调试、接口自动化测试、插件开发等,BAT等大厂等在使用,说明功能很好。 缺点:在线调试功能需要安装插件,用户体检稍微有点不好,主要是为了解决跨域问题,可能有安全性问题。不过要解决这个问题,可以自己实现一个插件,应该不难。 个人建议:如果不考虑插件安全的安全性问题,这个在线文档工具还是非常好用的,可以说是一个神器,笔者在这里强烈推荐一下。 2.5 apidoc github地址:https://github.com/apidoc/apidoc 开源协议:MIT License 推荐指数:★★★★☆ 示例: apidoc 是一个简单的 RESTful API 文档生成工具,它从代码注释中提取特定格式的内容生成文档。支持诸如 Go、Java、C++、Rust 等大部分开发语言,具体可使用 apidoc lang 命令行查看所有的支持列表。 apidoc 拥有以下特点: 跨平台,linux、windows、macOS 等都支持; 支持语言广泛,即使是不支持,也很方便扩展; 支持多个不同语言的多个项目生成一份文档; 输出模板可自定义; 根据文档生成 mock 数据; 优点:基于代码注释生成在线文档,对代码的嵌入性比较小,支持多种语言,跨平台,也可自定义模板。支持搜索和在线调试功能。 缺点:需要在注释中增加指定注解,如果代码参数或类型有修改,需要同步修改注解相关内容,有一定的维护工作量。 个人建议:这种在线文档生成工具提供了另外一种思路,swagger是在代码中加注解,而apidoc是在注解中加数据,代码嵌入性更小,推荐使用。 2.6 showdoc(记得好像开始收费了) github地址:https://github.com/star7th/showdoc 开源协议:Apache Licence 用户:超过10000+互联网团队正在使用 推荐指数:★★★★☆ 示例: ShowDoc就是一个非常适合IT团队的在线文档分享工具,它可以加快团队之间沟通的效率。 它都有些什么功能: 响应式网页设计,可将项目文档分享到电脑或移动设备查看。同时也可以将项目导出成word文件,以便离线浏览。 权限管理,ShowDoc上的项目有公开项目和私密项目两种。公开项目可供任何登录与非登录的用户访问,而私密项目则需要输入密码验证访问。密码由项目创建者设置。 ShowDoc采用markdown编辑器,点击编辑器上方的按钮可方便地插入API接口模板和数据字典模板。 ShowDoc为页面提供历史版本功能,你可以方便地把页面恢复到之前的版本。 支持文件导入,文件可以是postman的json文件、swagger的json文件、showdoc的markdown压缩包,系统会自动识别文件类型。 优点:支持项目权限管理,多种格式文件导入,全文搜索等功能,使用起来还是非常方便的。并且既支持部署自己的服务器,也支持在线托管两种方式。 缺点:不支持在线调试功能 个人建议:如果不要求在线调试功能,这个在线文档工具值得使用。 2.7 Apizza(极客专属的接口协作管理工具) 官网地址:http://www.apizza.net/ 2.7.1 主要功能 api跨域调试量身定制的chrome插件,本地,在线接口,都可以调。 云端存储,企业安全版支持本地数据中心。 一键分享,与团队共享你的API文档。 支持Postman,Swagger格式 导入Postman/Swagger Json 生成文档。 导出离线文档,部署本地服务器。 api Mock 根据文档自动生成返回结果,提供独立URL方便前端测试。 支持多种文档 http接口文档,markdown说明文档。 Apizza接口文档工具有一个很大不足的地方,那是Apizza个人免费版有人数限制,所有超过8人的团队如果想免费用,你是不用考虑Apizza的。如果你看到有文章或公众号上说Apizza是免费的,那简直是胡扯,他肯定没用过。当然如果你不缺钱,可以付费开通企业版。我们团队也是用了半年多Apizza,后来由于人员增加,Apizza里又无法再新添加新成员,迫使我们不得不放弃Apizza。 2.8 APIJSON Gitee地址:https://gitee.com/Tencent/APIJSON 推荐指数:★★★★ APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这套协议实现的 ORM 库。 为简单的增删改查、复杂的查询、简单的事务操作 提供了完全自动化的万能 API。 能大幅降低开发和沟通成本,简化开发流程,缩短开发周期。 适合中小型前后端分离的项目,尤其是 BaaS、Serverless、互联网创业项目和企业自用项目。 通过万能的 API,前端可以定制任何数据、任何结构。 大部分 HTTP 请求后端再也不用写接口了,更不用写文档了。 前端再也不用和后端沟通接口或文档问题了。再也不会被文档各种错误坑了。 后端再也不用为了兼容旧接口写新版接口和文档了。再也不会被前端随时随地没完没了地烦了。 2.8.1 特点功能 对于前端 不用再向后端催接口、求文档 数据和结构完全定制,要啥有啥 看请求知结果,所求即所得 可一次获取任何数据、任何结构 能去除重复数据,节省流量提高速度 对于后端 提供通用接口,大部分 API 不用再写 自动生成文档,不用再编写和维护 自动校验权限、自动管理版本、自动防 SQL 注入 开放 API 无需划分版本,始终保持兼容 支持增删改查、复杂查询、跨库连表、远程函数等 2.8.2 APIJSON 接口展示 2.9 其他一些接口管理神器 由于 API 在软件开发过程中如此关键,那么对 API 的管理就显得格外重要。通过 API 管理工具和平台能够大大简化 API 管理的难度和复杂度。下面列举了一些顶级 API 管理工具和平台,可供您参考。 2.9.1 API Umbrella API Umbrella 是用于管理 API 和微服务的顶级开源工具之一。通过为不同的域授予不同的管理员权限,它可以使多个团队使用同一个 Umbrella。该平台还提供速率限制,API 密钥,缓存,实时分析和 Web 管理界面等功能。 2.9.2 Gravitee.io Gravitee.io 是一个用于管理 API 的开源平台,这个工具是灵活的并且是轻量级的。它具有开箱即用的功能,例如速率限制,IP 过滤,跨域资源共享,即插即用选项,具有基于 OAuth2 和 JSON Web 令牌策略的开发者门户,负载平衡等。 但是,此 API 管理工具的主要功能是能够生成细粒度的报告以理解 API 的数据是如何使用的。 2.9.3 APIman.io APIman.io 是由 Red Hat 引入的一个顶级 API 管理平台,这个平台在 GitHub 中可以找到,为后端开发人员提供了很多便利。这包括: 快速运行 具有可分离策略引擎的基于策略的治理 异步功能 增强的结算和分析选项 REST API 可用性的管理 限速,还有其他 2.9.4 WSO2 API 管理器 WSO2 API Manager 是一个完整的生命周期 API 管理平台,可以随时随地运行。可以在企业内部和私有云上执行 API 的分发和部署。除此之外,它还提供了一些其他的便利。其中一些是: 高度定制化 管理策略易用, 为 SOAP 或 RESTful API 设计和原型的可能性, 更好的访问控制和货币化设施等 2.9.5 Kong Enterprise Kong 是一种广泛采用的开源微服务 API 工具,它使开发人员能够快速,轻松,安全地管理一切。它的企业版带有许多特性和功能,例如: 开源插件的可用性 一键式操作 通用语言基础架构功能 强大的可视化监控功能 常规软件运行状况检查 OAuth2.0 权限,以及 更广泛的社区支持 2.9.6 Tyk.io Tyk.io 用 Go 编程语言编写,也是公认的开源 API 网关。 它带有开发者门户,详细的文档,用于 API 分析的仪表板,API 的速率限制,身份验证以及各种其他此类规范,可帮助组织专注于微服务环境和容器化。但是,其基于商业的服务仅适用于付费版本。 2.9.7 Fusio Fusio 是另一个开源 API 管理工具,开发人员可以使用它从不同的数据类型创建和维护 REST API。它具有高效的生命周期管理功能,例如用于管理控制的后端仪表板,详细的文档,用于传入请求的 JSON 验证以及满足用户权限的范围处理。 而且,此 APIM 平台会自动生成 OAI 和 RAML 要求,并根据定义的架构创建自定义的客户端 SDK。 2.9.8 Apigility Apigility 由 Zend 框架设计和维护,是考虑用于 API 管理的下一个开源框架。该平台创建并展示其代码的 JSON 表示形式。它还为他们提供了不同的版本控制选项,以及通过 OAuth2 进行身份验证的简便性和包含 API 蓝图的文档。 API 接口管理,这 15 种开源工具助你管理 API Apigility 2.9.9 SwaggerHub SwaggerHub 被 40 多个组织考虑用于管理 API,它也是最好的开源 API 管理工具之一。 该平台为后端开发领域的设计人员和开发人员提供了广泛的选择。它为他们提供了强大而直观的编辑器,可在保持设计一致性的同时提供更高的效率和速度。 此外,它还提供了智能错误反馈,语法自动完成和多种样式验证器可用性的机会。 2.9.10 API Axle 在 Exicon 的支持下,API Axle 是另一种开源,简单且轻量级的代理,为开发人员提供了很多好处,例如:实时分析 强大的身份验证, 记录 API 流量以进行统计和报告, 易于创建和管理 API 密钥,以及 支持 REST API 设计以及 Go,PHP 和 Node.js 库的使用。 2.9.11 IBM Bluemix API 该 API 管理工具使开发人员可以使用 200 多种软件和中间件模式来为混合云构建可移植且兼容的应用程序。它还提供各种预先构建的服务和强大的机制,用于调节 API 访问,管理多个 API 版本,维持速率限制以及跟踪性能指标和所涉及的每个 API 的分析。 2.9.12 Repose Repose 是一个开源的 RESTful 中间件平台,在不断变化的 API 市场中起着举足轻重的作用。该平台为组织提供了各种 API 处理功能,包括身份验证,API 验证,速率限制和 HTTP 请求日志记录。 该 API 管理平台旨在提供格式正确且经过验证的信任下游请求的下游服务。而且,它本质上具有高度可扩展性和可扩展性,这意味着开发人员可以根据不断增长的需求轻松地使用它。 2.9.13 SnapLogic 企业集成云 SnapLogic 是一个不错的集成平台即服务(iPaaS)工具,可帮助组织获取,维持和增长其客户群。其具备的特征是: 它是快速的,多点的,并具有可灵活满足面向批处理和实时应用程序数据集成需求的选项。它具有可扩展的体系结构,其运行方式类似于 Web 服务器,但也提供了拥抱多功能性的选项。它还带有创新的数据流解决方案,鼓励组织将著名的 SaaS 应用程序如 SugarCRM 和 Salesforce)添加到其传统流程中。 2.9.14 DreamFactory DreamFactory API 管理平台是下一个项目要考虑的最好的免费开源工具之一,其受欢迎的原因如下: 它为开发人员提供了无需手动编写 API 即可进行移动应用程序开发的方法。 它使他们能够将任何 SQL / NoSQL 数据库,外部 HTTP / SOAP 服务或文件存储系统集成到 DreamFactory 环境中,并自动获得全面,灵活,完全文档化且随时可用的 REST API。除了访问用于分页,复杂过滤器,虚拟外键,相关表联接等的 API 参数之外,该平台还为 SQL 数据库提供了详细的 REST API。 DreamFactory API 管理平台的另一个独特功能是,它可以立即将 JSON 请求转换为 SOAP,反之亦然。此外,该平台还以易于管理的形式提供了高度安全的用户管理,SSO 身份验证,CORS,JSON Web 令牌,SAML 集成,API 端点上基于角色的访问控制,OAuth 和 LDAP。API 接口管理,这 15 种开源工具助你管理 API DreamFactory 2.9.15 3Scale 最后但并非最不重要的一点是,3Scale 是此 API 管理工具列表的补充。 API 管理工具由 Red Hat 拥有,它使大小型企业都可以通过以下功能轻松安全地管理其 API: 它采用了一个分布式的云层来集中 API 程序的控制。这样可以更轻松地控制分析,可访问性,开发人员工作流程,获利等。 由于它托管在分布式云托管层上,因此具有高度的灵活性和可扩展性。 3Scale API 的 OpenShift 集成功能使您能够以自动化且封闭的方式运行高性能应用程序。这个完整的生命周期 API 管理平台使开发人员可以随时计划,设计,应用,发布,管理,分析,优化和淘汰您的 API,以提供卓越的体验。 它具有通过 Web 或移动应用程序轻松共享组织数据,服务和内容的功能。最重要的是,3scale API 管理平台为您提供了将各种加密,身份验证和授权协议注入开发环境的机会。这使后端开发公司能够为其目标用户群提供适合他们的高度安全的移动应用程序体验。 上面共享的所有 API 管理工具都是开源的,有望成为技术堆栈的有益补充。但是,为了确保您选择最适合自己的业务应用程序的需求,我们接下来将介绍一些有关选择 API 管理工具的技巧。 三、第三方API接口测试问题反馈文档 原文地址:蒲公英不是梦:第三方API接口测试问题反馈文档 站在第三方技术人员的角度去思考他们需要什么信息来辅助他们定位问题。 文档说明解释了为什么发这份文档给他们 问题反馈记录汇总记录了所有可能存在问题的接口,因为有时候处理接口并不是一次性就能完善的,需要不断的协调并进行修改,文档的目的也是为了记录我们处理接口问题的过程,做留档。 接口地址用于说明我们测试这个接口的时候是用了这样的url,可以让第三方技术人员判断我们是不是测错接口了。 测试人员用于第三方技术人员直接于测试人员联系并做出解释。 测试时间记录的测试发生的时间,方便他们查找日志文档。 请求方式、请求头部信息、请求参数可以让第三方技术人员快速判断测试人员是否按照接口要求进行测试,此外请求猜数也方便第三方技术人员自己测试进行问题复现。 响应状态码则直接告诉他们接口有没有通。 实际返回值和预期返回值可以让第三方技术人员进行对比我们想要得到什么样的数据。 问题描述记录我们发现什么问题以及希望解决什么样的问题。 个人网站:https://www.lovebetterworld.com/ 往后余生,只想分享一些干货,分享一些工作,学习当中的笔记、总结,并帮助需要帮助的任何人,关注我,大家一起来学习吧!

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

最强整理:一线互联网移动架构师设计思想解读开源框架

设计思想解读开源框架 一、热修复设计 1.1 AOT/JIT、dexopt 与 dex2oat 1.2 CLASS_ISPREVERIFIED问题与解决 1.3 即时生效与重启生效热修复原理 1.4 Gradle自动补丁包生成 二、插件化框架解读 2.1 Class文件加载Dex原理 2.2 Android资源加载与管理 2.3 四大组件的加载与管理Activity、Service 2.4 so库的加载原理 2.5 Android系统服务的运行原理 三、组件化框架设计 3.1 组件化之集中式路由--阿里巴巴ARouter原理 3.2 APT技术自动生成代码与动态类加载 3.3 Java SPI机制实现组件服务调用 3.4 拦截器AOP编程(跳转前预处理--登录),路由参数传递与IOC注入 3.5 手写组件化式路由 四、图片加载框架 4.1 图片加载框架选型 4.1.1 Universal ImangeLoader、Glide、Picasso与Fresco 4.1.2 Glide 4.1.3 Picasso 4.1.4 Fresco 4.2 Glide原理分析 4.2.1 Glide的基本用法 4.2.2 从源码的角度理解Glide的执行流程上篇、下篇 4.2.3 深入探究Glide的缓存机制 4.2.4 玩转Glide的回调与监听 4.2.5 Glide强大的图片变换功能 4.2.6 探究Glide的自定义模块功能 4.2.7 实现带进度的Glide图片加载功能 4.2.8 带你全面了解Glide 4的用法 4.3 手写图片加载框架实战 五、网络访问框架设计 5.1 网络通信必备基础 5.1.1 Restful URL 5.1.2 HTTP协议& TCP/IP协议 5.1.3 SSL握手与加密 5.1.4 DNS解析 5.1.5 Socket通信原则 5.1.5.1 SOCKS代理 5.1.5.2 HTTP普通代理与隧道代理 5.2 OkHttp源码解读 5.2.1 Socket连接池复用机制 5.2.2 HTTP协议重定向与缓存处理 5.2.3 高并发请求队列:任务分发 5.2.4 责任链模式拦截器设计 5.3 Retrofit源码解析 六、RXJava响应式编程框架设计 6.1 链式调用 6.2 扩展的观察者模式 6.3 事件变换设计 6.4 Scheduler线程控制 七、IOC架构设计 7.1 依赖注入与控制反转 7.2 ButterKnife原理上篇、中篇、下篇 7.3 Dagger架构设计核心解密 八、Android架构组件Jetpack 8.1 LiveData原理 8.2 Navigation如何解决tabLayout问题 8.3 ViewModel如何感知View生命周期及内核原理 8.4 Room架构方式方法 8.5 dataBinding为什么能够支持MVVM 8.6 WorkManager内核揭秘 8.7 Lifecycles生命周期 最后 Alvin老师已经将精品网课、书籍、BAT面试文档、项目专题源码等资料已分享在网盘中,并在持续更新中。欢迎关注Alvin老师微信号VX:wxid_mgooud8xhvag12 前往领取! Android架构师之路很漫长,一起共勉吧!喜欢的话别忘记点击关注和赞哦

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

【web前端面试题整理07】我不理解表现与数据分离。。。

拜师传说 今天老夫拜师了,老夫有幸认识一个JS高手,在此推荐其博客,悄悄告诉你,我拜他为师了,他承诺我只收我一个男弟子。。。。。 师尊刚注册的账号,现在博客数量还不多,但是后面点会有干货哦,值得期待。 http://www.cnblogs.com/aaronjs/ 前言 上周回到了成都,这周就准备找工作了,对成都的聚美优品其实比较有好感的,所以昨天就先去面试了,感觉技术面试的还不错啦,结果最后HR说经理不在,让我等经理反馈。 我当时相信了,但是回来想想感觉可能失败了,但是我不知道哪里出了问题。 现在遇到的前端面试都大同小异的,具体的题目就不列出来了,因为泄露公司机密不太好,我就说说自己印象比较深刻的一些东西吧。 今天发生了一次电话面试,下午参加了一家公司的面试,感觉公司还是很有发展潜力的,比较靠谱,若是有后文应该会选择吧。 回到家中后,有两件事一直放在心中,第一个就是昨天为什么失败了,另一个就是我那抄袭的简历啦。。。。 话说简历抄袭 我简历上面抄了一段话:对表现与数据分离有一定了解! 于是上午就被人问到了,并且那位大哥还把工作中遇到的一个问题与我交流了一番(问题等下提出),当时我对这块的理解其实比较模糊,然后就给绕过去了。 于是上午就一直觉得有件事情堵得慌,到下午面试结束后,确实忍不住了,我们一起来看看这个问题吧! 什么是表现与数据分离 我一来就看了一篇文章,点进去一看结果说的是表现与结构分离,其大意是: 表现应该由CSS来控制,结构应该由html来控制,比如我们伟大的禅意花园。 于是我觉得,我简历是不是应该改成,表现与结构分离呢。。。。 但是我感觉表现与数据分离还是有点道理呢,所以我们还是回到我们的表现与数据分离吧。 ----------华丽的分割线---------- 半个小时过去了,我去查询了资料也去请教了前辈,最后得出了两个东西: MVC MVVM 很显然,他们说的这个也许是我找寻的答案,但是我心里面视乎并不买账,于是我找寻了以后截了一个图: 这是一个搞后端的大哥写的博客,我们看到采用MVC开发的主要目的就是表现与数据分离。 看来真的就是MVC啦。。。 好了,这个地方暂时打住,我们来看看当时的原话 模拟面试 面试官问我,说简历上写的对表现与数据分离有一定理解,可以描述一下么 我很淡定的说了句让我思考下,然后开始搜索着我的大脑硬盘,好像压根就没有这个词呢,于是胡乱蒙了个是不是html与css应该分离? 答案当然是否定的,然后面试哥把他在项目中遇到的一个问题抛了出来: 他有一个国家列表,现在要将国家列表放到A中,然后B可以由A选择,也可以有总列表选择 但是B中添加后,若是A中没有要动态为A增加这项。 为了方便各位理解,我上个图 不知道各位听到这个题作何感受,我当时其实就有一种方式可以实现(最简单,最土的),压根就没有想到这个题会和“表现与数据分离”有关系。 但是既然问到了,应该还是有联系吧,不知道各位怎么看待呢? 于是我们回到问题本身吧 再论行为与数据分离 我们工作中可能会碰到这种情况,美工每次拿过来的东西感觉都有点不一样,你不能说出有好大的不一样,反正就是有地方不一样。 然后一拿来后整个家伙全部完蛋,js报错啦,为什么报错你们懂的,这里我来现身说法,整一个真实的例子吧 当年我年少轻狂还在索贝时,我们有设计,但是设计同事的CSS很差,给我们的html+css经常有未闭合的标签,经常会出现莫名其妙的双引号。 但是当时我CSS也不好,其它同事完全就是搞后端的,所以在这个情况下我们艰难的做出了我们的第一版产品,与设计图一致(还是在设计的代码上修改的)。 完了领导看了提出了一点修改意见,于是设计同事又改了下图,相信我他只是简单的改了一点东西,然后形成了html交给了我们。。。。 这次拿来后我看到了这次的html结构和上次完全是两码事,而我们的产品又是单页应用,很多地方完全是javascript生成的,或者html标签与data形成了模板,现在要改。。。改毛线啊。。。 为了具体描述我们所遇到的困境我这里详细描述下: ① 我们根据设计第一版图做好了页面 ② 页面上有很多应用,是采用类似于js模板化的东西(小的封装的,很戳) ③ 页面根据数据库设计,前端使用js控制页面加载,最后渲染出我们的页面 好了整个代码还有点小复杂,完了现在设计哥来了一个完全不同的结构了,当时内部没有CSS高手,甚至说没有CSS熟手。 我负责改这个工作,我依稀记得我改出了翔。。。。最后终于改完了! 然后根据领导指示,设计同事给出了我们第三套HTML代码,这个和原来的差距相对大一点,但是主要模块还在,于是我看空中到处都是翔了。。。。 我不知道我前面的描述各位遇到过没有,但是当时我没能很好的解决这个问题,就只能缝缝补补的改着,每次出新东西后又是一场悲剧。 回到现实 其实我遇到的这个问题我感觉可以很好的说明表现数据分离的重要性了,但是什么是表现数据分离,我们还是需要将它说清楚啦。 PS:其实我遇到的问题比较复杂吧,我感觉都不完全是表现与数据分离了...... 我说不清楚什么是表现与数据分离,那我们举点反例吧: ① 服务器端(php/.net/java),将html标签与数据一起打印出来 Response.Write("<div>......</div>"); 这段代码各位一定见过,可以想象,一个系统一年后要改版,这个地方的代码修改可能有点痛苦吧(特别是原来的开发人员不在了) 这个是服务器端的,那么我们的客户端呢(咳咳,让我想想啦,想不到啦。。。) ② 我想到一个例子,不知道合适不,拿出来说说吧 前段时间,我们前端有个地区控件的东西,他大概是这个样子的: var area = '<table>......</table>'; 他在字符串中将所有的地区全部写了出来(包括地区编号(隐藏)),这样做会有问题的: 假设我哪天想改变一下结构适应新的布局(响应式布局神马的随便啦),那么我们会发现无从下手 ③ 还是回到我遇到那个问题(重点来了哦) 我遇到那个问题其实主要是我在js中使用的id,或者class或者标签子选择器都不在了,新的代码上来后肯定出错啦。 这个问题很尖锐的,原来我使用了一套html+css页面,我们根据这个写了一大套javascript的东西,耗时3个月。 然后我们换了一套页面,但是其中功能还是我们那套js 然后我们再换了一套页面,其中的js还是我们那套js 最后最变态的要求出来了(因为产品会给不同的电视台使用),我们的产品在不同的人看起来页面是不一样的,但是其中的功能是一样的!!! 屌丝们,接招吧,各位哥哥,你们会怎么解决这个问题呢? 正是因为有上面那种莫名其妙的需求,所以才会出现我们前端MVC这种莫名其妙的东西。。。 因为按道理来说前端是不应该出现MVC的,我们的html就是model,我们的css就是view,我们的js就是controller。 结果现在单是javascript就搞出来了一套MVC,这不坑爹么? 至此,不知道各位对表现与数据分离有新的了解没有??? MVC——表现与数据分离 话不多说,先上一段代码(原来的代码,抄过来的): 复制代码 1 <html xmlns="http://www.w3.org/1999/xhtml"> 2 <head> 3 <title></title> 4 <script src="../jquery-1.7.1.js" type="text/javascript"></script> 5 <script type="text/javascript"> 6 $(document).ready(function () { 7 var end = $('#end'); 8 $('#pili').change(function () { 9 var name = ''; 10 var p = $(this).val(); 11 if (p == '叶小钗') { 12 name = '刀狂剑痴'; 13 } 14 if (p == '一页书') { 15 name == '百世经纶'; 16 } 17 if (p == '素还真') { 18 name = '清香白莲'; 19 } 20 21 end.html(name + p); 22 }); 23 }); 24 </script> 25 </head> 26 <body> 27 <select id="pili"> 28 <option value="叶小钗">叶小钗</option> 29 <option value="一页书">一页书</option> 30 <option value="素还真">素还真</option> 31 </select> 32 <div id="end"></div> 33 </body> 34 </html> 复制代码 我们重新看看上面的代码,很简单的逻辑,select改变后变化end的值,好了现在需求发生改变: ① select变成使用input模拟select ② 在手机上还是使用select算了 ③ 总会有莫名其妙的需求,这个就是 好吧,现在的代码你该怎么写呢?是不是会写几个代码,或者你压根不知道怎么写呢???于是看看我们的MVC的实现吧 PS:代码是我可耻的抄的。。。。但我可是自豪的一个字一个字的敲的哦,窃知识不算偷...... 复制代码 1 <html xmlns="http://www.w3.org/1999/xhtml"> 2 <head> 3 <title></title> 4 <script src="../jquery-1.7.1.js" type="text/javascript"></script> 5 <script type="text/javascript"> 6 $(document).ready(function () { 7 //定义一个controller 8 var piliController = { 9 //选择视图 10 start: function () { 11 this.view.start(); 12 }, 13 //将用户操作映射到模型更新上 14 set: function (name) { 15 this.model.setPerson(name); 16 } 17 }; 18 piliController.model = { 19 piliKV: { 20 '叶小钗': '刀狂剑痴', 21 '一页书': '百世经纶', 22 '素还真': '清香白莲' 23 }, 24 curPerson: null, 25 //数据模型负责业务逻辑和数据存储 26 setPerson: function (name) { 27 this.curPerson = this.piliKV[name] ? name : null; 28 this.onchange(); 29 }, 30 //通知数据同步更新 31 onchange: function () { 32 piliController.view.update(); 33 }, 34 //相应视图对当前状态的查询 35 getPiliAction: function () { 36 return this.curPerson ? this.piliKV[this.curPerson] + this.curPerson : '???'; 37 } 38 }; 39 piliController.view = { 40 //用户触发change事件 41 start: function () { 42 $('#pili').change(this.onchange); 43 }, 44 onchange: function () { 45 piliController.set($('#pili').val()); 46 }, 47 update: function () { 48 $('#end').html(piliController.model.getPiliAction()); 49 } 50 }; 51 piliController.start(); 52 }); 53 </script> 54 </head> 55 <body> 56 <select id="pili"> 57 <option value="叶小钗">叶小钗</option> 58 <option value="一页书">一页书</option> 59 <option value="素还真">素还真</option> 60 </select> 61 <div id="end"></div> 62 </body> 63 </html> 复制代码 我们来看看这个神一样的代码。。。。我们一开始会认为他有这些问题: ① 代码维护困难,至少我认为很困难 ② 徒增复杂性,性能会有问题 ③ 我并不能说服自己说自己懂了。。。。 于是我们就放弃了MVC啦,但是我们回过头来好好审视下他,我们会发现不一样的东西: ① 我们好像就在view中使用了选择器获取dom,其它地方压根不认识dom这个丫的。 ② 我们的数据似乎在model中,我们可以随意改变,但是并不会影响到我们dom ③ view和model是完全独立的,我们的controller恰好把他们串联起来了 看着这神奇的魔法,我似懂非懂的点了点头,你妹的MVC还真他妈够劲!!! 结语 听我唧唧歪歪说了这么多,不知道各位理解到了表现与数据分离是什么意思没,我感觉自己有所得,所以写出来与各位分享。 这次只是很浅的理解,后面我会请教各位大牛,然后再写一篇心得,希望届时能好好解决这个问题,其中上面面试的问题也一并解答吧,今天暂时到这。 希望这篇博客能对各位有所帮助,当然最重要的是: 哥,有什么想法要说出来哦,不要藏着啊!!! 本文转自叶小钗博客园博客,原文链接:http://www.cnblogs.com/yexiaochai/p/3167465.html,如需转载请自行联系原作者

资源下载

更多资源
优质分享App

优质分享App

近一个月的开发和优化,本站点的第一个app全新上线。该app采用极致压缩,本体才4.36MB。系统里面做了大量数据访问、缓存优化。方便用户在手机上查看文章。后续会推出HarmonyOS的适配版本。

Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

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等操作系统。