首页 文章 精选 留言 我的

精选列表

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

学会这个工具的使用,让你快速生成验证码

前言 验证码是我们做人机验证最常用的方式,常用于敏感操作的验证,比如:登录、注册、修改等。 验证码的原理:不同的客户端拥有不同的 session 对象,在看到验证码图片的时刻,服务器后端代码生成图片并将随机字符存储到 session 中。这样客户端看到的只能是图片,人工识别图片后将字符发送到服务器与 session 中的字符进行比对。 上面只是简单的介绍了验证码的原理,更多细节还需有 javaweb 相关基础知识,这篇文章适合有基础的同学。 最近几天我翻到了以前生成验证码的工具类,使用 Graphics2D 生成的图片,然后再以流的形式写出到客户端,这些代码还是有些问题的,都是硬编码。在以后的使用中我们可能有不同的需求都会导致代码重新修改,自定义一些样式都不是很方便。 所以我找到了 github 上的一个生成验证码的工具:kaptcha,下面我就给大家介绍一下 kaptcha 的使用。 kaptcha 的使用 我们就以一个 maven 构建的 web 项目为例 1、依赖 jar 包 在 pom.xml 文件中添加相关依赖 <dependency> <groupId>com.github.penggle</groupId> <artifactId>kaptcha</artifactId> <version>2.3.2</version> </dependency> 2、配置生成验证码的 servlet 修改 web.xml,添加 kaptcha 提供的 servlet 并配置映射路径 <servlet> <servlet-name>Kaptcha</servlet-name> <servlet-class>com.google.code.kaptcha.servlet.KaptchaServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>Kaptcha</servlet-name> <url-pattern>/captcha</url-pattern> </servlet-mapping> 访问 http://localhost:8080/captcha 这时发现就已经可以产生验证码了,但还有个问题,验证码的随机字符存在哪里了? 3、探索 kaptcha 我们来查看 KaptchaServlet 这个类的源码,doGet 方法 @Override public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // Set to expire far in the past. resp.setDateHeader("Expires", 0); // Set standard HTTP/1.1 no-cache headers. resp.setHeader("Cache-Control", "no-store, no-cache, must-revalidate"); // Set IE extended HTTP/1.1 no-cache headers (use addHeader). resp.addHeader("Cache-Control", "post-check=0, pre-check=0"); // Set standard HTTP/1.0 no-cache header. resp.setHeader("Pragma", "no-cache"); // return a jpeg resp.setContentType("image/jpeg"); // create the text for the image String capText = this.kaptchaProducer.createText(); // store the text in the session req.getSession().setAttribute(this.sessionKeyValue, capText); // store the date in the session so that it can be compared // against to make sure someone hasn't taken too long to enter // their kaptcha req.getSession().setAttribute(this.sessionKeyDateValue, new Date()); // create the image with the text BufferedImage bi = this.kaptchaProducer.createImage(capText); ServletOutputStream out = resp.getOutputStream(); // write the data out ImageIO.write(bi, "jpg", out); } 有这样一段代码,获取字符后存入 session 中,键为 sessionKeyValue 这个变量的值 // create the text for the image String capText = this.kaptchaProducer.createText(); // store the text in the session req.getSession().setAttribute(this.sessionKeyValue, capText); sessionKeyValue 这个变量的值在 init 方法中被赋值 this.sessionKeyValue = config.getSessionKey(); 好我们进入 config.getSessionKey() 中查看代码,发现 session 的键为 Constants 这个类中的常量 Constants.KAPTCHA_SESSION_KEY return this.properties.getProperty(Constants.KAPTCHA_SESSION_CONFIG_KEY, Constants.KAPTCHA_SESSION_KEY); 4、编写测试验证码是否正常使用的 servlet 我们来验证一下,编写一个 servlet import com.google.code.kaptcha.Constants; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; @WebServlet("/test") public class TestKaptchaServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html;charset=utf-8"); PrintWriter out = resp.getWriter(); //获取传入的验证码 String captcha = req.getParameter("captcha"); if (null != captcha) { //获取实际session中的验证码 String code = (String) req.getSession().getAttribute(Constants.KAPTCHA_SESSION_KEY); if (null == code){ resp.sendRedirect("/captcha"); return; } if (code.equalsIgnoreCase(captcha)) { out.print("验证输入正确"); } else { out.print("验证码输入有误"); } } else { out.print("必须输入验证码"); } out.flush(); out.close(); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doGet(req, resp); } } 先访问验证码 http://localhost:8080/captcha 然后访问 http://localhost:8080/test?captcha=验证码 以上这些默认配置能满足一般业务的使用了,下面通过深入解析 kaptcha 的源码自定义配置验证码的宽、高、边框、颜色、字符等 5、深入源码自定义配置 kaptcha 再来看一下 init 方法中的代码 @Override public void init(ServletConfig conf) throws ServletException { super.init(conf); // Switch off disk based caching. ImageIO.setUseCache(false); Enumeration<?> initParams = conf.getInitParameterNames(); while (initParams.hasMoreElements()) { String key = (String) initParams.nextElement(); String value = conf.getInitParameter(key); this.props.put(key, value); } Config config = new Config(this.props); this.kaptchaProducer = config.getProducerImpl(); this.sessionKeyValue = config.getSessionKey(); this.sessionKeyDateValue = config.getSessionDate(); } 这段代码获取 servlet 的初始化参数,并将参数存入 config 对象中,看 config 中的一段代码 public boolean isBorderDrawn() { String paramName = Constants.KAPTCHA_BORDER; String paramValue = this.properties.getProperty(paramName); return this.helper.getBoolean(paramName, paramValue, true); } 没错,所有的参数名字都是 Constants 类中常量名称 public class Constants { public final static String KAPTCHA_SESSION_KEY = "KAPTCHA_SESSION_KEY"; public final static String KAPTCHA_SESSION_DATE = "KAPTCHA_SESSION_DATE"; public final static String KAPTCHA_SESSION_CONFIG_KEY = "kaptcha.session.key"; //省略其他代码 } 所以,可以给 KaptchaServlet 在 web.xml 配置参数,下面这些配置给大家参考 <servlet> <servlet-name>Kaptcha</servlet-name> <servlet-class>com.google.code.kaptcha.servlet.KaptchaServlet</servlet-class> <!-- 是否有边框 --> <init-param> <param-name>kaptcha.border</param-name> <param-value>no</param-value> </init-param> <!-- 字体颜色 --> <init-param> <param-name>kaptcha.textproducer.font.color</param-name> <param-value>red</param-value> </init-param> <!-- 图片宽度 --> <init-param> <param-name>kaptcha.image.width</param-name> <param-value>135</param-value> </init-param> <!-- 使用哪些字符生成验证码 --> <init-param> <param-name>kaptcha.textproducer.char.string</param-name> <param-value>ACDEFHKPRSTWX345679</param-value> </init-param> <!-- 图片高度 --> <init-param> <param-name>kaptcha.image.height</param-name> <param-value>50</param-value> </init-param> <!-- 字体大小 --> <init-param> <param-name>kaptcha.textproducer.font.size</param-name> <param-value>38</param-value> </init-param> <!-- 干扰线的颜色 --> <init-param> <param-name>kaptcha.noise.color</param-name> <param-value>black</param-value> </init-param> <!-- 字符个数 --> <init-param> <param-name>kaptcha.textproducer.char.length</param-name> <param-value>6</param-value> </init-param> <!-- 使用哪些字体 --> <init-param> <param-name>kaptcha.textproducer.font.names</param-name> <param-value>Arial</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>Kaptcha</servlet-name> <url-pattern>/captcha</url-pattern> </servlet-mapping>

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

Table Store实时数据通道服务Go SDK快速入门

文档 产品简介 官网文档 安装 下载源码包 go get github.com/aliyun/aliyun-tablestore-go-sdk/tunnel 安装依赖 可以在tunnel目录下使用dep安装依赖 dep ensure -v 也可以直接使用go get安装依赖包: go get -u go.uber.org/zap go get github.com/cenkalti/backoff go get github.com/golang/protobuf/proto go get github.com/satori/go.uuid go get github.com/stretchr/testify/assert go get github.com/smartystreets/goconvey/convey go get github.co

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

快速开发android,离不开这10个优秀的开源项目

作为一名菜鸡Android,时常瞻仰大佬们的开源项目是非常必要的。这里我为大家收集整理了10个优秀的开源项目,方便我们日常开发中学习! 作者:ListenToCode博客:https://www.jianshu.com/p/9742ce36b6a8 KnowWeather GitHub地址https://github.com/SilenceDut/KnowWeather) 一款 Android 开源天气 App ,包含天气信息、详情、生活指数等,通知栏,桌面小部件,定时更新天气等等,应用没有任何广告,支持县级、区级城市的天气,原文:https://www.diycode.cc/projects/SilenceDut/KnowWeather 整体框架 根据实际项目浅谈Android项目中的框架搭建 (https://silencedut.github.io/2016/12/05/根据实际项目浅谈Android项目中的框架搭建/) 关于 知天气——天气尽在掌握之中 (https://silencedut.github.io/2016/12/06/知天气——天气尽在掌握之中/) 特点 支持县级、区级天气 JobScheduler与JobService的使用 轮询系统定时更新天气 实现很多定制化很大的,对后台任务严苛的系统中(如魅族系统)通知栏常驻,并且轮询系统正常运行,很多下载量千万级的天气应用无法达到此效果 无广告和强制保活拉活的行为 FolioReader-Android GitHub地址https://github.com/FolioReader/FolioReader-Android 一个 Android 开源电子书(ePub)阅读 App , 原文: https://www.diycode.cc/projects/FolioReader/FolioReader-Android 效果图 Gradle Add following dependency to your app build.gradle compile'com.folioreader:folioreader:0.2.3' NBAPlus GitHub地址https://github.com/SilenceDut/NBAPlus 一个 Android 开源 NBA 资讯和赛事信息的平台 App 效果图 特点 遵循Android开发最佳实践的一种具体实践 Retrofit2.0+RxJava+EventBus+GreenDao的使用 代码结构清晰,扩展性强,易复用到其他项目 结合NBAPlus Server 学习后台开发知识和接口的设计 动态Blur效果。 关于 NBA资讯和赛事信息的平台。 全面、方便、快捷的获取新闻动态、比赛数据。 无广告、推送信息,不后台常驻,空间占用小。web后台部分在这: NBAPlus Server (https://github.com/SilenceDut/nbaplus-server) MyDiary GitHub地址https://github.com/DaxiaK/MyDiary 动漫《你的名字》同款 Android 开源 App,原文: https://www.diycode.cc/projects/erttyy8821/MyDiary 效果图 简诗 GitHub地址https://github.com/wingjay/jianshi 一款优雅的中国风记录 app,包括 Android 端和 Server 端,原文:https://www.diycode.cc/projects/wingjay/jianshi 相关博客 如何在一天之内完成一款具备cool属性的Android产品_简书 (https://www.jianshu.com/p/cf496fc408b2) banya GitHub地址(https://github.com/forezp/banya) 一个基于豆瓣 API 仿网易云音乐的开源项目。原文:https://www.diycode.cc/projects/forezp/banya 项目介绍: 整体采用material design 风格,本人是网易云音乐的粉丝,所以界面模仿了网页云音乐,另外,项目中尽量使用了5.0之后的新控件。 项目整体采用mvp+rxjava+retrofit 框架,使用glide进行图片展示,用butterknif注解,另外使用了java 8 新特性,拉姆达表达式,安卓原生并不支持,需要导插件,具体见项目。 并对retrofit的okhttpClient进行了缓存配置,很遗憾,豆瓣API在服务端并没有对返回数据进行Cache-Control 设置。 后续会采用本地数据库对数据进行缓存,初步定为使用realm。 项目地址:https://github.com/forezp/banya 主界面采用material design 设计风格,使用了NavigationView和DrawerLayout的抽屉效果,CoordinatorLayout和viewpager 配合,使用behavior属性,对toolbar的显示和隐藏进行了控制。使用了tablayout和viewpager配合,切换fragment,整体风格类似于网易云音乐。 用到的豆瓣API有热映榜、top250、搜索图书、搜索音乐,等。 yasea GitHub地址https://github.com/begeekmyfriend/yasea 带美颜滤镜的 Android 直播推流开源客户端项目,通过手机摄像头和麦克风采样,编码为H.264和AAC,再封装FLV格式,推送至RTMP服务器。延迟达到毫秒级别。 Coding-Android GitHub地址https://github.com/Coding/Coding-Android 编译环境 Android Studio 2.2.3,用gradle引用的许多第三方库,第一次加载会有点慢,加载完毕后要build一下,这些待下划线的类(比如 MainActivity_)会在build之后自动生成。 包说明 common 基类和工具类 comment 评论区 enter 输入框 network 对网络做了一点封装 photopick 图片多选控件 umeng 封装了umeng hide 进入staging界面 maopao 冒泡界面 message 消息界面 model 一些数据结构 project 我的项目界面 setting 设置界面 task 我的任务界面 third 一些第三方代码 user 好友界面 Sprayscape GitHub地址https://github.com/googlecreativelab/Sprayscape 原文https://www.diycode.cc/projects/googlecreativelab/SprayscapeSprayscape内置于Unity,支持原生Android。使用适用于Unity的Google VR SDK处理陀螺仪数据和使用NatCam Unity插件进行精确的相机控制,Sprayscape将相机输入映射到360度球体上。GPU使一切成为可能。在用户点击或触摸时,相机进给以每秒60帧的纹理渲染。然后,通过GPU上的片段着色器将该纹理与任何现有纹理合成。同样的着色器还处理从2D相机到360球体的投影,创建您在app中看到的景观。当用户保存景观时,平面全景图像存储在应用数据中并写入包含所有景观的单个图集文件。地图集被加载到景观中,带有陀螺仪导航的景观画廊。共享由原生Android代码处理。当用户通过链接共享景观时,用户将使用Google OAuth登录并提示您在云端硬盘上进行读写访问。所有用户生成的内容都存储在用户的云端硬盘帐户中,因此用户可以随时删除其内容。有了适当的权限,Drive API v3会检查Sprayscape文件夹,如果缺少,则创建一个,然后上传文件。共享URL在“本机共享”对话框中呈现给用户,并且还附加到应用程序上的scape对象,以便日后轻松共享。Facebook的份额也是本地处理的。全景图被准备为具有适当EXIF数据的图像对象,以确保在Facebook上正确呈现,然后通过原生共享呈现给用户。用户选择Facebook分享到他们的网络。 ListenerrMusicPlayer GitHub地址https://github.com/hefuyicoder/ListenerMusicPlayer 一款优雅的遵循 Material Design 的开源音乐播放器,UI参考 腾讯轻听 音乐播放器,使用 Lastfm Api 与 酷狗歌词Api。项目架构采用 mvp-clean,基于 Retrofit2 + Dagger2 + Rxjava + RxBus + Glide。 项目特点 遵循 Material Design 规范,界面清新,交互优雅。 基于 MVP-CLEAN + Retrofit2 + Dagger2 + Rxjava + Glide 功能模块: 我的歌曲、我的歌单、文件夹、我喜欢、最近播放、最近添加、播放排行、本地搜索等。 支持显示歌词及缓存 支持耳机线控播放,耳机拔出自动暂停 动态刷新媒体库,及时获知媒体文件变更 日夜间模式切换,支持动态换肤 阅读更多 面试官:请你介绍一下你的项目经验 Google费尽心力推出了一个新的项目架构 Android 应用防止被二次打包指南 NDK项目实战—高仿360手机助手之卸载监听 (Android)面试题级答案(精选版) 相信自己,没有做不到的,只有想不到的 在这里获得的不仅仅是技术!

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

HBase实操 | 使用Spark通过BulkLoad快速导入数据到HBase

HBase社区直播本期分享专家:明惠(网名:过往记忆)-阿里云数据架构师 视频地址: https://yq.aliyun.com/live/590?spm=a2c4e.11155435.0.0.460177969kCLxf PPT地址: https://yq.aliyun.com/download/3033 PS:欢迎关注HBase+Spark团队号https://yq.aliyun.com/teams/382博客,问答,直播,各类HBase资料,线下meetup都会发布到这里。 1.文档编写目的 在项目中有需求需要将Hive表中的数据存储在HBase中。使用Spark访问Hive表,将读表数据导入到HBase中,写入HBase有两种方式:一种是通过HBase的API接口批量的将数据写入HBase,另一种是通过BulkLoad的方式生成HF

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

阿里云Kubernetes容器服务 - 快速部署虚拟节点virtual-nodes

容器和Serverless的多样结合 随着容器和Serverless概念的普及,阿里云容器服务团队和弹性计算团队合作推出了多个Serverless容器产品和解决方案,包括Serverless Kubernetes、弹性容器实例ECI、virtual-nodes addon功能。这些不同的产品为用户提供了丰富的无服务器容器应用场景支持。 Serverless Kubernetes 阿里云容器服务的Serverless Kubernetes已经公测一段时间,用户无需管理服务器,同时能够正常使用kubernetes各种API操作,包括创建pod、service、ingress、job等。无服务器的最大价值不仅是免去了服务器运维的负担,更为应用提供了强大的弹性扩容能力,弹性不再受限于集群的节点规模,而且应用仅按资源使用量付费。 Kubernete

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

ELK日志系统之使用Rsyslog快速方便的收集Nginx日志

Rsyslog Rsyslog是高速的日志收集处理服务,它具有高性能、安全可靠和模块化设计的特点,能够接收来自各种来源的日志输入(例如:file,tcp,udp,uxsock等),并通过处理后将结果输出的不同的目的地(例如:mysql,mongodb,elasticsearch,kafka等),每秒处理日志量能够超过百万条。 Rsyslog作为syslog的增强升级版本已经在各linux发行版默认安装了,无需额外安装。 收集Nginx日志 ELK通过Rsyslog收集日志流程图如下: ● 处理流程为:Nginx --syslog--> Rsyslog --omkafka--> Kafka --> Logstash --> Elasticsearch --> Kibana● Nginx产生日志通过syslog系统服务传给Rsyslog服务端,Rsyslog接收到日志后通过omkafka模块将日志写入Kafka,Logstash读取Kafka队列然后写入Elasticsearch,用户通过Kibana检索Elasticsearch里存储的日志 ● Rsyslog服务系统自带无需安装,所以整个流程中客户端不需要额外安装应用 ● 服务端虽然Rsyslog也已安装,但默认没有omkafka模块,如果需要Rsyslog写入Kafka需要先安装这个模块 ● omkafka模块在rsyslog v8.7.0之后的版本才支持,所以需要先通过rsyslogd -v命令查看rsyslog版本,如果版本较低则需要升级 Rsyslog升级 1.添加rsyslog源的key # apt-key adv --recv-keys --keyserver keys.gnupg.net AEF0CF8E 2.添加rsyslog源地址 echo "deb http://debian.adiscon.com/v8-stable wheezy/" >> /etc/apt/sources.list echo "deb-src http://debian.adiscon.com/v8-stable wheezy/" >> /etc/apt/sources.list 3.升级rsyslog服务 # apt-get update && apt-get -y install rsyslog 添加omkafka模块 1.安装编译工具,下边autoreconf需要用到,不然无法生成configure文件 # apt-get -y install pkg-config autoconf automake libtool unzip 2.omkafka需要安装一堆的依赖包 # apt-get -y install libdbi-dev libmysqlclient-dev postgresql-client libpq-dev libnet-dev librdkafka-dev libgrok-dev libgrok1 libgrok-dev libpcre3-dev libtokyocabinet-dev libglib2.0-dev libmongo-client-dev libhiredis-dev # apt-get -y install libestr-dev libfastjson-dev uuid-dev liblogging-stdlog-dev libgcrypt-dev # apt-get -y install flex bison librdkafka1 librdkafka-dev librdkafka1-dbg 3.编译安装omkafka模块 # mkdir tmp && cd tmp # git init # git pull git@github.com:VertiPub/omkafka.git # autoreconf -fvi # ./configure --sbindir=/usr/sbin --libdir=/usr/lib --enable-omkafka && make && make install && cd .. Rsyslog收集nginx日志 Client端Nginx配置 log_format jsonlog '{' '"host": "$host",' '"server_addr": "$server_addr",' '"http_x_forwarded_for":"$http_x_forwarded_for",' '"remote_addr":"$remote_addr",' '"time_local":"$time_local",' '"request_method":"$request_method",' '"request_uri":"$request_uri",' '"status":$status,' '"body_bytes_sent":$body_bytes_sent,' '"http_referer":"$http_referer",' '"http_user_agent":"$http_user_agent",' '"upstream_addr":"$upstream_addr",' '"upstream_status":"$upstream_status",' '"upstream_response_time":"$upstream_response_time",' '"request_time":$request_time' '}'; access_log syslog:server=rsyslog.domain.com,facility=local7,tag=nginx_access_log,severity=info jsonlog; 1.Nginx在v1.10之后的版本才支持syslog的方式处理日志,请确保你的Nginx版本高于1.10 2.为了降低logstash的处理压力,同时也为了降低整个配置的复杂度,我们nginx的日志直接采用json格式 3.抛弃文本文件记录nginx日志,改用syslog直接将日志传输到远端的rsyslog服务器,以便我们后续的处理;这样做的另一个非常重要的好处是我们再也无需考虑nginx日志的分割和定期删除问题(一般我们为了方便管理通常会采用logrotate服务来对日志进行按天拆分和定期删除,以免磁盘被占满) 4.access_log直接输出到syslog服务,各参数解释如下: ●syslog:指明日志用syslog服务接收 ●server:接收syslog发送日志的Rsyslog服务端地址,默认使用udp协议,端口是514 ●facility:指定记录日志消息的类型,例如认证类型auth、计划任务cron、程序自定义的local0-7等,没有什么特别的含义,不必深究,默认的值是local7 ●tag:给日志添加一个tag,主要是为了方便我们在服务端区分是哪个服务或者client传来的日志,例如我们这里给了tag: nginx_access_log,如果有多个服务同时都写日志给rsyslog,且配置了不通的tag,在rsyslog服务端就可以根据这个tag找出哪些是nginx的日志 ●severity:定义日志的级别,例如debug,info,notice等,默认是error Server端Rsyslog配置 # cat /etc/rsyslog.d/rsyslog_nginx_kafka_cluster.conf module(load="imudp") input(type="imudp" port="514") # nginx access log ==> rsyslog server(local) ==> kafka module(load="omkafka") template(name="nginxLog" type="string" string="%msg%") if $inputname == "imudp" then { if ($programname == "nginx_access_log") then action(type="omkafka" template="nginxLog" broker=["10.82.9.202:9092","10.82.9.203:9092","10.82.9.204:9092"] topic="rsyslog_nginx" partitions.auto="on" confParam=[ "socket.keepalive.enable=true" ] ) } :rawmsg, contains, "nginx_access_log" ~ 1.在rsyslog.d目录下添加一个专门处理nginx日志的配置文件 2.rsyslog配置文件重要配置解释如下: ●module:加载模块,这里我们需要加载imudp模块来接收nginx服务器syslog发过来的日志数据,也需要加载omkafka模块来将日志写入到kafka ●input:开启udp协议,端口514,也可以同时开启tcp协议,两者可以共存 ●template:定义一个模板,名字叫nginxLog,模板里可以定义日志的格式,因为我们传的已经是json了,不需要再匹配格式,所以这里不额外定义,注意模板名字要唯一 ●action:在匹配到inputname为imudp且programname为nginx_access_log(就是我们上边nginx配置里边的tag)之后的处理方式,这里的配置为匹配到的日志通过omkafka模块写入kafka集群,还有一些关于omkafka更详细的配置参考上边给出的omkafka模块官方文档 ●:rawmsg, contains:最后这一行的意思是忽略包含nginx_access_log的日志,没有这一行的话rsyslog服务默认会把所有日志都记录到message里边一份,我们已经把日志输出到kafka了,本地就没必要再记录了 3.omkafka模块检查kafka里边topic是否存在,如果不存在则创建,无需手动创建kafka的topic Server端logstash配置 input { kafka { bootstrap_servers => "10.82.9.202:9092,10.82.9.203:9092,10.82.9.204:9092" topics => ["rsyslog_nginx"] } } filter { mutate { gsub => ["message", "\\x", "\\\x"] } json { source => "message" } date { match => ["time_local","dd/MMM/yyyy:HH:mm:ss Z"] target => "@timestamp" } } output { elasticsearch { hosts => ["10.82.9.205", "10.82.9.206", "10.82.9.207"] index => "rsyslog-nginx-%{+YYYY.MM.dd}" } } 重要配置参数解释如下: ● input:配置kafka的集群地址和topic名字 ● filter:一些过滤策略,因为传入kafka的时候是json格式,所以不需要额外处理,唯一需要注意的是如果日志中有中文,例如url中有中文内容时需要替换 \x,不然json格式会报错 ●output:配置ES服务器集群的地址和index,index自动按天分割 联调测试 配置完成后分别重启rsyslog服务和nginx服务,访问nginx产生日志 1.查看kafka是否有正常生成topic # bin/kafka-topics.sh --list --zookeeper 127.0.0.1:2181 __consumer_offsets rsyslog_nginx 2.查看topic是否能正常接收日志 # bin/kafka-console-consumer.sh --zookeeper localhost:2181 --topic rsyslog_nginx {"host": "domain.com","server_addr": "172.17.0.2","http_x_forwarded_for":"58.52.198.68","remote_addr":"10.120.89.84","time_local":"28/Aug/2018:14:26:00 +0800", "request_method":"GET","request_uri":"/","status":200,"body_bytes_sent":1461,"http_referer":"-","http_user_agent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36","upstream_addr":"-","upstream_status":"-", "upstream_response_time":"-","request_time":0.000} 3.kibana添加index,查看Elasticsearch中是否有数据,如果前两步都正常,kibana搜不到index或index没有数据,多半是index名字写错了之类的基础问题,仔细检查 kibana查询展示 ●打开Kibana添加rsyslog-nginx-*的Index,并选择timestamp,创建Index Pattern ●进入Discover页面,可以很直观的看到各个时间点请求量的变化,根据左侧Field实现简单过滤,例如我们想查看所有访问状态为404的uri,可以点击request_uri和status后边的add,这两项的内容将出现在右侧,然后点击status下边404状态码后边的加号,则只查看状态为404的请求,点击上方auto-refresh可以设置页面自动刷新时间 ●通过各种条件的组合查询可以实现各种各样的需求,例如每秒请求、带宽占用、异常比例、慢响应、TOP IP、TOP URL等等各种情况,并且可以通过Visualize很方便的将这些信息绘制图标,生成Dashboard保存 写在最后 ●Nginx的access log绝对是网站的一个宝藏,通过日志量的变化可以知道网站的流量情况,通过对status状态的分析可以知道我们提供服务的可靠性,通过对特定活动url的追踪可以实时了解活动的火爆程度,通过对某些条件的组合查询也能为网站运营提供建议和帮助,从而使我们的网站更友好更易用 ●Rsyslog服务的单点问题可以通过部署多个Rsyslog服务过三层负载来保证高可用,不过以我们的经验来说rsyslog服务还是很稳定的,跑了一年多,每分钟日志处理量在20w左右,没有出现过宕机情况,不想这么复杂的话可以写个check rsyslog服务状态的脚本跑后台,挂了自动拉起来 ●整个过程中我们使用了UDP协议,第一是因为Nginx日志的syslog模式默认支持的就是UDP协议,翻了官网没找到支持TCP的方式,我想这也是考虑到UDP协议的性能要比TCP好的多,第二也考虑到如果使用TCP遇到网络不稳定的情况下可能会不停的重试或等待,影响到Nginx的稳定。对于因为内容过长超过以太网数据帧长度的问题暂时没有遇到 原文发布时间为:2018-08-30 本文作者:37丫37 本文来自云栖社区合作伙伴“ 运维咖啡吧”,了解相关信息可以关注“ 运维咖啡吧”。

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

快速将log4j.xml 转换为 logback.xml

应用的日志输出频率太高,log4j同步日志输出的时候锁竞争太过激烈,导致业务线程阻塞。系统load 200+,线程数2000+ 。所以想通过切换到logback 来提升日志输出的内容。 想要切换到logback要做两件事情: * 切换pom中依赖的jar包 * 将log4j.xml 转换成 logback.xml 第一件事情比较简单,但是第二件事情稍微有些麻烦,特别是碰到原本日志Appender就非常多的情况下(我们配置了超过50个Appender)一个一个手动改就很繁琐了。在网上找了很多都是从 log4j.properties 转换为 logback.xml 的,我们这边还是很少有用到log4j.properties的。 在Google 上找了好久找到了一个文章 http://rpuchkovskiy.blogspot.com/2014/11/xslt-to-convert-log4jxml-config-to.html。通过他可以把 log4j.xml 转换为 logback.xml ,不过他的实现比较老,我结合我们的使用场景稍微修改了下。 <!-- This XLST script converts log4j.xml.bak file to logback.xml file trying to mimic its behavior as close as possible @author Roman Puchkovskiy --> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" exclude-result-prefixes="log4j xalan" xmlns:log4j="http://jakarta.apache.org/log4j/" xmlns:xalan="http://xml.apache.org/xslt" xmlns:xslt="http://www.w3.org/1999/XSL/Transform"> <xsl:output indent="yes" xalan:indent-amount="4"/> <xsl:variable name="vLower" select="'abcdefghijklmnopqrstuvwxyz'"/> <xsl:variable name="vUpper" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"/> <xsl:template match="/log4j:configuration"> <configuration> <xsl:apply-templates select="appender"/> <xsl:apply-templates select="logger"/> <xsl:apply-templates select="root"/> <xsl:apply-templates select="comment()"/> </configuration> </xsl:template> <xsl:template match="appender"> <appender> <xsl:attribute name="name"> <xsl:value-of select="@name"/> </xsl:attribute> <xsl:attribute name="class"> <xsl:choose> <!-- 定义了Appender 之间的映射关系 --> <xsl:when test="@class = 'org.apache.log4j.DailyRollingFileAppender'">ch.qos.logback.core.rolling.RollingFileAppender</xsl:when> <xsl:when test="@class = 'com.alibaba.common.logging.spi.log4j.DailyRollingFileAppender'">ch.qos.logback.core.rolling.RollingFileAppender</xsl:when> <xsl:when test="@class = 'org.apache.log4j.AsyncAppender'">ch.qos.logback.classic.AsyncAppender</xsl:when> <xsl:when test="@class = 'org.apache.log4j.ConsoleAppender'">ch.qos.logback.core.ConsoleAppender</xsl:when> <xsl:when test="@class = 'org.apache.log4j.net.SMTPAppender'">ch.qos.logback.classic.net.SMTPAppender</xsl:when> <xsl:when test="@class = 'org.apache.log4j.net.SocketAppender'">ch.qos.logback.classic.net.SocketAppender</xsl:when> <xsl:when test="@class = 'org.apache.log4j.net.SyslogAppender'">ch.qos.logback.classic.net.SyslogAppender</xsl:when> <xsl:otherwise> <xsl:message terminate="yes">Unknown appender class: <xsl:value-of select="@class"/> </xsl:message> </xsl:otherwise> </xsl:choose> </xsl:attribute> <xsl:apply-templates select="param"/> <xsl:apply-templates select="layout"/> <xsl:apply-templates select="filter"/> </appender> <xsl:call-template name="newline"/> </xsl:template> <xsl:template match="param"> <xsl:choose> <!-- 将log4j file 转换为 logback 的file --> <xsl:when test="@name = 'file'"> <xsl:element name="{concat(translate(substring(@name,1,1), $vUpper, $vLower),substring(@name, 2))}"> <xsl:value-of select="@value"/> </xsl:element> </xsl:when> </xsl:choose> </xsl:template> <xsl:template match="layout"> <xsl:choose> <!-- 将PatternLayout 做转换 --> <xsl:when test="@class = 'org.apache.log4j.PatternLayout'"> <xsl:choose> <xsl:when test="../@class = 'org.apache.log4j.ConsoleAppender' or ../@class = 'org.apache.log4j.DailyRollingFileAppender' or ../@class = 'com.alibaba.common.logging.spi.log4j.DailyRollingFileAppender'"> <encoder> <pattern> <xsl:value-of select="param[@name = 'ConversionPattern']/@value"/> </pattern> <charset>UTF-8</charset> </encoder> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <xsl:element name="fileNamePattern"> <xsl:value-of select="../param[@name = 'file']/@value" />.%d{yyyy-MM-dd}</xsl:element> <maxHistory>7</maxHistory> </rollingPolicy> </xsl:when> <xsl:when test="../@class = 'org.apache.log4j.net.SocketAppender' or ../@class = 'org.apache.log4j.net.SyslogAppender'"> <xsl:comment>this is NOT needed tor this logger, so it is commented out</xsl:comment> <xsl:comment><![CDATA[ <layout> <pattern>]]><xsl:value-of select="param[@name = 'ConversionPattern']/@value"/><![CDATA[</pattern> </layout>]]> </xsl:comment> </xsl:when> <xsl:otherwise> <layout> <pattern> <xsl:value-of select="param[@name = 'ConversionPattern']/@value"/> </pattern> </layout> </xsl:otherwise> </xsl:choose> </xsl:when> <xsl:otherwise> <xsl:message terminate="yes">Unknown layout class: <xsl:value-of select="@class"/> </xsl:message> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template match="filter"> <xsl:choose> <xsl:when test="@class = 'org.apache.log4j.varia.LevelRangeFilter' and param[@name = 'LevelMin']/@value != '' and param[@name = 'LevelMax']/@value = 'FATAL'"> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level> <xsl:value-of select="param[@name = 'LevelMin']/@value"/> </level> </filter> </xsl:when> <xsl:otherwise> <xsl:message terminate="yes">Don't know what to do with filter</xsl:message> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template match="logger"> <logger> <xsl:attribute name="name"> <xsl:value-of select="@name"/> </xsl:attribute> <xsl:attribute name="level"> <xsl:choose> <xsl:when test="level/@value = 'FATAL'">OFF</xsl:when> <xsl:otherwise> <xsl:value-of select="level/@value"/> </xsl:otherwise> </xsl:choose> </xsl:attribute> <xsl:if test="@additivity != ''"> <xsl:attribute name="additivity"> <xsl:value-of select="@additivity"/> </xsl:attribute> </xsl:if> <xsl:apply-templates select="appender-ref"/> </logger> </xsl:template> <xsl:template match="appender-ref"> <appender-ref> <xsl:attribute name="ref"> <xsl:value-of select="@ref"/> </xsl:attribute> </appender-ref> </xsl:template> <xsl:template match="root"> <xsl:call-template name="newline"/> <root> <xsl:attribute name="level"> <xsl:value-of select="level/@value"/> </xsl:attribute> <xsl:apply-templates select="appender-ref"/> <xsl:apply-templates select="comment()"/> </root> </xsl:template> <xsl:template match="comment()"> <xsl:copy-of select="."/> </xsl:template> <xsl:template name="newline"> <!-- don't reformat this! --> <xsl:text> </xsl:text> </xsl:template> </xsl:stylesheet> 使用方式很简单: String[] parmas = {"-IN", "log4j.xml", "-XSL", "log4j-to-logback.xsl", "-OUT", "logback.xml"}; org.apache.xalan.xslt.Process.Process.main(parmas);

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

Linux(创建大文件)快速把服务器空间写满

有时我们需要测试服务器空间不足时程序的性能。这时你就需要手动对服务器创建大文件使其空间不足了。 我在百度之后发现了三个命令:truncate dd fallocate truncate命令: 它的作用是指定一个文件的大小,如果该文件不存在,就会创建该文件。如果指定文件的大小小于原先的大小,会丢失内容。 这个命令指定的文件大小其实是虚拟的。只是显示出来的大小。如果你指定一个非常大的文件。其实服务器剩余空间并不会减少。 用法:turncate -s 1G test.txt--创建一个虚拟大小1G的test文件,其真实大小为0 dd命令: 可以创建虚拟大小的文件,也可以创建真实占用空间的文件。 真实:dd if=/dev/zero of=test.txt count=10 bs=512M --创建一个5G大的test.txt文件 虚拟:dd if=/dev/zero of=test.txt count=10 bs=512M seek=10G --创建一个5G大的test.txt文件,但显示容量为10G 参数含义: if 输入文件 of 输出文件 count 创建的文件构成的块数 bs 每块的容量大小 seek 指定的虚拟大小 /dev/zero 一个不断返回0值字节的字符设备,为了提供写入的字符。 fallocate命令: 可以直接分配一个指定容量的真实大小文件,且速度很快。 用法:falloate -l 5G test.txt --创建一个大小为5G的真实文件 选择:最后我还是使用fallocate进行文件的创建,因为它速度较快。 注: 1.文件的虚拟大小可以通过ls/ll命令进行查看,真是大小可以通过进入文件所在目录,然后 du -sh *,找到对应文件名,看其真实大小。 2.dd命令十分强大,我只是使用了它其中的一种,以后还需学习其他用法。

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

如何快速搭建一个简单的塔防小游戏

C语言是所有编程语言的基础,当我们对C语言有足够深入的理解后,就能轻松入门其他语言,比如现在流行的Python。现在,我将带领大家看一个基于C语言经典算法,使用Python编写的塔防小游戏。 在塔防游戏中,有许多敌人向着同一目标前进。在很多塔防游戏当中,有一条或几条事先预定好的路径。在一些中,比如经典的《Desktop Tower Defense》,你可以将塔放在任何位置,它们充当障碍影响敌人选择的路径。试一试,点击地图来移动墙壁: 我们如何来实现这种效果? 像A*这样的图搜索算法经常被用来寻找两点之间的最短路径。你可以用这个来为每一个敌人找到前往目标的路径。在这种类型的游戏当中,我们有很多不同的图搜索算法来。这是一些经典方法 单源,单目标: 贪心搜索算法 A*算法 – 在游戏当中常用 单源多目标或多源单目标 广度优先算法-无加权边 Dijkstra算法-有加权边 Bellman-Ford算法-支持负权重 多源多目标 Floyd-Warshall算法 Johnson’s算法 像《Desktop Tower Defense》这样的游戏会有很多个敌人(源)和一个共同的目的地。这使得它被归为多源单目标一类。我们可以执行一个算法,一次算出所有敌人的路径,而不是为每个敌人执行一次A*算法。更好的是,我们可以计算出每个位置的最短路径,所以当敌人挤在一块或者新敌人被创建时,他们的路径已经被计算好了。 我们先来看看有时也被称作“洪水填充法”(FIFO变种)的广度优先算法。虽然图搜索算法是适用于任何由节点和边构成的图,但是我还是使用方形网格来表示这些例子。网格是图的一个特例。每个网格瓦片是图节点,网格瓷砖之间的边界是图的边。我会在另一篇文章当中探讨非网格图。 广度优先搜索始于一个节点,并访问邻居节点。关键的概念是“边界”,在已探索和未开发的区域之间的边界。边界从原始节点向外扩展,直到探索了整张图。 边界队列是一个图节点(网格瓦片)是否需要被分析的列表/数组。它最开始仅仅包含一个元素,起始节点。每个节点上的访问标志追踪我们是否采访过该节点。开始的时候除了起始节点都标志为FALSE。使用滑块来查看边界是如何扩展的: 这个算法是如何工作的?在每一步,获得一个元素的边界并把它命名为current。然后寻找current的每个邻居,next。如果他们还没有被访问过的话,将他们都添加到边界队列里面。下面是一些python代码: Python frontier = Queue() frontier.put(start) visited = {} visited[start] = True while not frontier.empty(): current = frontier.get() for next in graph.neighbors(current): if next not in visited: frontier.put(next) visited[next] = True frontier = Queue() frontier.put(start) visited = {} visited[start] = True while not frontier.empty(): current = frontier.get() for next in graph.neighbors(current): if next not in visited: frontier.put(next) visited[next] = True 现在已经看见代码了,试着步进上面的动画。注意边界队列,关于current的代码,还有next节点的集合。在每一步,有一个边界元素成为current节点,它的邻居节点会被标注,并且未被拜访过的邻居节点会被添加到边界队列。有一些邻居节点可能已经被访问过,他们就不需要被添加到边界队列里面了。 这是一个相对简单的算法,并且对于包括AI在内的很多事情都是有用的。我有三种主要使用它的办法: 1.标识所有可达的点。这在你的图不是完全连接的,并且想知道哪些点是可达的时候是很有用的。这就是我再上面用visited这部分所做的。 2.寻找从一个点到所有其他点或者所有点到一个点的路径。我在文章开始部分的动画demo里面使用了它。 3.测量从一个点到所有其他点的距离。这在想知道一个移动中的怪物的距离时是很有用的。 如果你正在生成路径,你可能会想知道从每个点移动的方向。当你访问一个邻居节点的时候,要记得你是从哪个节点过来的。让我们把visited重命名为came_from并且用它来保存之前位置的轨迹: Python frontier = Queue() frontier.put(start) came_from = {} came_from[start] = None while not frontier.empty(): current = frontier.get() for next in graph.neighbors(current): if next not in came_from: frontier.put(next) came_from[next] = current frontier = Queue() frontier.put(start) came_from = {} came_from[start] = None while not frontier.empty(): current = frontier.get() for next in graph.neighbors(current): if next not in came_from: frontier.put(next) came_from[next] = current 我们来看看它看起来是怎样的: 如果你需要距离,你可以在起始节点讲一个计数器设置为0,并在每次访问邻居节点的时候将它加一。让我们把visitd重命名为distance,并且用它来存储一个计数器: Python frontier = Queue() frontier.put(start) distance = {} distance[start] = 0 while not frontier.empty(): current = frontier.get() for next in graph.neighbors(current): if next not in distance: frontier.put(next) distance[next] = 1 + distance[current] frontier = Queue() frontier.put(start) distance = {} distance[start] = 0 while not frontier.empty(): current = frontier.get() for next in graph.neighbors(current): if next not in distance: frontier.put(next) distance[next] = 1 + distance[current] 我们来看看它看起来是怎样的: 如果你想同时计算路径和距离,你可以使用两个变量。 这就是广度优先检索算法。对于塔防风格的游戏,我用它来计算所有位置到一个指定位置的路径,而不是重复使用A*算法为每个敌人分开计算路径。我用它来寻找一个怪物指定行动距离内所有的位置。我也是用它来进行程序化的地图生成。Minecraft使用它来进行可见性提出。由此可见这是一个不错的算法。 下一步 我有python和c++代码的实现。 如果你想要找到从一个点出发而不是到达一个点的路径,只需要在检索路径的时候翻转came_from指针。 如果你想要知道一些点而不是一个点的路径,你可以在图的边缘为你的每个目标点添加一个额外的点。额外的点不会出现在网格中,但是它会表示在图中的目标位置。 提前退出:如果你是在寻找一个到达某一点或从某一点出发,。我在A*算法的文章当中描述了这种情况。 加权边:如果你需要不同的移动成本,广度优先搜索可以替换为为Dijkstra算法。我在A*算法的文章当中描述了这种情况。 启发:如果你需要添加一种指导寻找目标的方法,广度优先算法可以替换为最佳优先算法。我在A*算法的文章当中描述了这种情况。 如果你从广度优先算法,并且加上了提前退出,加权边和启发,你会得到A*。如你所想,我在A*算法的文章当中描述了这种情况。 满满的自豪感,真的很想知道大家的想法,还请持续关注更新,更多干货和资料请直接联系我,也可以加群710520381,邀请码:柳猫,欢迎大家共同讨论

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

java中System.in和System.out快速学会使用

import java.io.*; /** * @author LiaoYangJun * @createTime 2018/3/23. */ public class Demo2 { /** * 因为用的是字节流所以 * 控制台 中文 字符串 数字都可以输入输出 * @param args */ public static void main(String[] args) { // 标准的输入流对象 --读取操作 InputStream is = System.in; // 标准的输出流对象---写的操作 OutputStream os = System.out; try { // System.in是一个很原始、很简陋的输入流对象,通常不直接使用它来读取用户的输入。 // 一般会在外面封装过滤流: BufferedReader br = new BufferedReader(new InputStreamReader(is)); // 然后调用br.readLine()方法进行读取。 String inputStr = br.readLine(); // 把数据写出去,写出的类型是字节型 os.write(inputStr.getBytes()); } catch (IOException e) { e.printStackTrace(); } } } 输入 张三123sdasd 输出 张三123sdasd

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

Python3快速入门——(2)list和tuple(列表和元组)

1、List基础结构 #python中数据类型转换 str_eight= str (8) #整型8装换位字符型'8' str_eight_two="8" int_eight= int (str_eight_two) #字符型转换为整型 float_eight=float(str_eight_two) #字符型转换为浮点型 #python 中的运算符**表示指数 china=10 print(china**2) #结果为100 #List months=[] #声明months为list类型 print(type(months)) #<class 'list'> print(months) #[] 此时list为空 months. append ("January") #向list中添加元素(字符串) months.append("February") months.append(2) #向list中添加元素(整型)向同一个list中可以添加不同类型的元素 print(months) #输出list 结果为:['January', 'February', 2] 2、List索引 int_months=[1,2,3,4,5,6,7,8,9,10,11,12] #声明一个list length= len (int_months) #获取list的长度即有多少个元素 month1=int_months[0] #获取list中的第一个元素,每个list索引是默认从0开始的 month2=int_months[1] #获取list中的第二个元素 index=len(int_months)-1 #获取list最后一个元素的索引 last_value=int_months[index] #获取list最后一个元素 last_value1=int_months[-1] #索引-1代表最后一个元素的索引,-2代表倒数第二个元素的索引 #获取list中的某段元素(切片操作) months=["Jan","Feb","Mar","Apr","May","Jun","Jul"] two_four=months [2:4] #获取索引为2,3的值,不包括索引4的值(取头不取尾),注意索引是从0开始的,输出结果为["Mar","Apr"] three_six=months [3:] #获取索引3以后的所有元素 ['Apr', 'May', 'Jun', 'Jul'] months=[ "Jan" , "Feb" , "Mar" , "Apr" , "May" , "Jun" , "Jul" ] b=months[: 5:2 ] #取前5个元素,每两个取一个 print (b) #结果为['Jan', 'Mar', 'May'] b=months[:: 3 ] #对所有元素,每三个取一个 ;#结果为['Jan', 'Apr', 'Jul'] #字符串也可以用切片操作,只是操作结果仍是字符串 #tuple也可以用切片操作,只是操作的结果仍是tuple 3、list和tuple(列表和元组) list和tuple是Python内置的有序集合,一个可变,一个不可变 #list 列表 classmates=[ "Bob" , "Jim" , "Mar" , "Asia" , "Kry" ] classmates.append( "Bill" ) #往list中追加元素到末尾 classmates.insert( 1 , "Aut" ) #把元素插入到指定的位置,比如索引号为1的位置 classmates.pop() #要删除list末尾的元素,用pop()方法 classmates.pop( 1 ) #要删除指定位置的元素,用pop(i)方法,其中i是索引位置 classmates[ 1 ]= "Git" #要把某个元素替换成别的元素,可以直接赋值给对应的索引位置 #tuple 元组 names=( "Bob" , "Jim" ,[ "Mar" , "Asia" ], "Kry" ) #声明一个元组 #names这个tuple不能变,它也没有append(),insert()这样的方法。其他获取元素的方法和list是一样的 #可以正常地使用names[0],names[-1],但不能赋值成另外的元素 #当定义一个tuple时,在定义的时候,tuple的元素就必须被确定下来,可以为空names=() names[ 2 ][ 0 ]= "X" names[ 2 ][ 1 ]= "Y" print (names) # 结果为('Bob', 'Jim', ['X', 'Y'], 'Kry') #表面上看,tuple的元素确实变了,但其实变的不是tuple的元素,而是list的元素。 # tuple一开始指向的list并没有改成别的list,所以,tuple所谓的“不变”是说,tuple的每个元素,指向永远不变。即指向'a',就不能改成指向'b',指向一个list,就不能改成指向其他对象,但指向的这个list本身是可变的!

资源下载

更多资源
优质分享App

优质分享App

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

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应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

用户登录
用户注册