首页 文章 精选 留言 我的

精选列表

搜索[增删改查],共9362篇文章
优秀的个人博客,低调大师

android 35 ListView增删改

MainActivity package com.sxt.day05_11; import java.util.ArrayList; import java.util.List; import android.app.Activity; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.Intent; import android.content.DialogInterface.OnClickListener; import android.os.Bundle; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.AdapterView.OnItemLongClickListener; import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; import com.sxt.day05_11.entity.GeneralBean; public class MainActivity extends Activity { ListView mlvGeneral; List<GeneralBean> mGenerals; GeneralAdapter mAdapter; private static final int ACTION_DETAILS=0; private static final int ACTION_ADD=1; private static final int ACTION_DELETE=2; private static final int ACTION_UPDATE=3; int mPosition; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initData(); initView(); setListener(); } private void setListener() { mlvGeneral.setOnItemLongClickListener(new OnItemLongClickListener() { @Override public boolean onItemLongClick(AdapterView<?> parent, View view, final int position, long id) { AlertDialog.Builder builder=new AlertDialog.Builder(MainActivity.this); builder.setTitle("选择以下操作") .setItems(new String[]{"查看详情","添加数据","删除数据","修改数据"}, new OnClickListener() {//倒的包是import android.content.DialogInterface.OnClickListener; @Override public void onClick(DialogInterface dialog, int which) { switch (which) { case ACTION_DETAILS: showDetails(position);//这个position在mGenerals.get(position);调用,说明position是资源总条数范围, break; case ACTION_ADD: break; case ACTION_DELETE: mAdapter.remove(position);//调用适配器的删除方法 break; case ACTION_UPDATE: //启动修改的Activity,并将当前的军事家对象传递过去 Intent intent=new Intent(MainActivity.this, UpdateActivity.class); intent.putExtra("general", mGenerals.get(position));//mGenerals.get(position)的对象要实现序列化接口 mPosition=position; startActivityForResult(intent, ACTION_UPDATE);//要求返回结果,ACTION_UPDATE是requestCode break; } } private void showDetails(int position) { GeneralBean general=mGenerals.get(position); AlertDialog.Builder builder=new AlertDialog.Builder(MainActivity.this); builder.setTitle(general.getName()) .setMessage(general.getDetails()) .setPositiveButton("返回", null);//直接关闭,没有响应事件 AlertDialog dialog = builder.create(); dialog.show(); } }); AlertDialog dialog = builder.create(); dialog.show(); return true; } }); } private void initView() { mlvGeneral=(ListView) findViewById(R.id.lvGeneral); mAdapter=new GeneralAdapter(mGenerals, this); mlvGeneral.setAdapter(mAdapter); } private void initData() { String[] names=getResources().getStringArray(R.array.general); String[] details=getResources().getStringArray(R.array.details); int[] resid={ R.drawable.baiqi,R.drawable.caocao,R.drawable.chengjisihan, R.drawable.hanxin,R.drawable.lishimin,R.drawable.nuerhachi, R.drawable.sunbin,R.drawable.sunwu,R.drawable.yuefei, R.drawable.zhuyuanzhang }; mGenerals=new ArrayList<GeneralBean>(); for (int i = 0; i < resid.length; i++) { GeneralBean general=new GeneralBean(resid[i], names[i], details[i]); mGenerals.add(general); } } class GeneralAdapter extends BaseAdapter{ List<GeneralBean> generals; MainActivity context; public void remove(int position){//适配器移出方法,就是移除资源总数据, generals.remove(position); notifyDataSetChanged();//BaseAdapter的方法,执行后安卓系统会调用getView()方法重新绘制, } public void add(GeneralBean general){ mGenerals.add(general); notifyDataSetChanged(); } public void update(int position,GeneralBean general){ mGenerals.set(position, general); notifyDataSetChanged(); } public GeneralAdapter(List<GeneralBean> generals, MainActivity context) { super(); this.generals = generals; this.context = context; } @Override public int getCount() { return generals.size(); } @Override public Object getItem(int position) { // TODO Auto-generated method stub return null; } @Override public long getItemId(int position) { // TODO Auto-generated method stub return 0; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder=null; if(convertView==null){ convertView=View.inflate(context, R.layout.item_general, null); holder=new ViewHolder(); holder.ivThumb=(ImageView) convertView.findViewById(R.id.ivThumb); holder.tvName=(TextView) convertView.findViewById(R.id.tvName); convertView.setTag(holder); }else{//滚屏的时候重复利用convertView holder=(ViewHolder) convertView.getTag(); } GeneralBean general=generals.get(position); holder.ivThumb.setImageResource(general.getResid()); holder.tvName.setText(general.getName()); return convertView; } class ViewHolder{ ImageView ivThumb; TextView tvName; } } @Override //处理UpdateActivity返回的结果 protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if(resultCode!=RESULT_OK){//判断resultCode return ; } switch (requestCode) {//判断requestCode case ACTION_UPDATE: GeneralBean general=(GeneralBean) data.getSerializableExtra("general");//获取修改完以后的对象 mAdapter.update(mPosition, general);//调用适配器的更新方法,mPosition是修改的对象在集合的索引 break; case ACTION_ADD: break; } } } mainactivity页面: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <ListView android:id="@+id/lvGeneral" android:layout_width="match_parent" android:layout_height="match_parent"/> </RelativeLayout> 修改Activity: package com.sxt.day05_11; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.EditText; import android.widget.ImageView; import com.sxt.day05_11.entity.GeneralBean; public class UpdateActivity extends Activity { //要修改对象的名字(控件显示),详细信息(控件显示), EditText metName,metDetails; ImageView mivThumb;//图片 int mPhotoId;//图片id @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_update); initView(); initData(); setListener(); } private void setListener() { setOKClickListtener(); setCancelClickListener(); } private void setCancelClickListener() { findViewById(R.id.btnCancel).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { finish(); } }); } private void setOKClickListtener() { findViewById(R.id.btnOK).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { String details=metDetails.getText().toString(); String name=metName.getText().toString(); //不能根据图片控件获取图片的id,所以多用一个变量保存图片的id GeneralBean general=new GeneralBean(mPhotoId, name, details); Intent intent=new Intent(UpdateActivity.this, MainActivity.class); intent.putExtra("general", general); setResult(RESULT_OK, intent);//RESULT_OK是resultCode结果码 finish();//关闭当前页面 } }); } //接收数据 private void initData() { Intent intent = getIntent(); GeneralBean general=(GeneralBean) intent.getSerializableExtra("general"); //数据显示在控件里面 metDetails.setText(general.getDetails()); metName.setText(general.getName()); mivThumb.setImageResource(general.getResid());//图片是根据id获取的 mPhotoId=general.getResid();//保存图片id } private void initView() { //用布局实例化对象 metDetails=(EditText) findViewById(R.id.etDetails); metName=(EditText) findViewById(R.id.etName); mivThumb=(ImageView) findViewById(R.id.iv_updae_thumb); } } 修改Activity页面: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:id="@+id/iv_updae_thumb" android:layout_width="80dp" android:layout_height="80dp" android:scaleType="fitXY" android:src="@drawable/baiqi"/> <EditText 可编辑的输入框 android:id="@+id/etName" android:layout_below="@id/iv_updae_thumb" android:layout_width="80dp" android:layout_height="wrap_content" android:text="白起"/> <EditText android:id="@+id/etDetails" android:layout_width="match_parent" android:layout_height="110dp" android:text="@string/detail" android:layout_toRightOf="@id/iv_updae_thumb"/> <Button android:id="@+id/btnOK" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="修改" android:layout_below="@id/etDetails" android:layout_marginLeft="50dp" android:layout_marginTop="20dp"/> <Button android:id="@+id/btnCancel" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="放弃" android:layout_below="@id/etDetails" android:layout_marginTop="20dp" android:layout_toRightOf="@id/btnOK" android:layout_marginLeft="50dp"/> </RelativeLayout> item_general.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" > 横向 <ImageView android:id="@+id/ivThumb" android:layout_width="60dp" android:layout_height="60dp" android:scaleType="fitXY" 自动缩放 android:src="@drawable/baiqi"/> <TextView android:id="@+id/tvName" android:layout_width="wrap_content" android:layout_height="60dp" android:gravity="center_vertical" 内部垂直居中 android:textSize="20sp" android:text="白起" android:layout_marginLeft="10dp"/> </LinearLayout> 本文转自农夫山泉别墅博客园博客,原文链接:http://www.cnblogs.com/yaowen/p/4889382.html,如需转载请自行联系原作者

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

【熵教育】Anders-SpringBoot中的Http应用:WebFlux——熵学院

我们今天开始进入Spring WebFlux.WebFlux是Spring5.0开始引入的.有别于SpringMVC的Servlet实现,它是完全支持异步和非阻塞的.在正式使用Spring WebFlux之前,我们首先得了解他和Servlet的区别,以及他们各自的优势,这样我们才能够给合适的场景选择合适的开发工具. 首先我们要问几个问题,为什么要有异步?在异步之前,软件行业做过哪些努力,他们的优势是什么?基于这几个问题,我们今天分享以下三个知识点: 从Http1.X 到Http2.0 从Servlet2.x到Servlet3.x WebFlux的出场 1. 从Http1.x到Http2.0 异步和同步是无法分开的.他们对性能的理解和处理也是各有千秋.传统的web项目因为是基于阻塞I/O模型而建立的,所以他们只能通过对整个链路的优化来提升性能,而这里的性能就包括了伸缩性和响应速度.这里面比较重要的一个环节就是网络传输.相对而言,这也是距离我们的用户最近的一个环节,因此他们对并发的处理以及对响应速度的处理就比其他的会更直接地影响我们的用户. 1.1 Http/1.x 在http1.x中,我们都知道,http会先进行三次握手,握手成功之后,开始传递数据,服务器响应完毕,就进行四次挥手,最后关闭链接.刚开始应用这个概念的时候,是非常受欢迎的,因为在那时候传递的还是静态页面或者动态数据比较少的资源,因此无论是客户端还是服务器端,他都节省了更多的资源.但随着互联网的飞速发展,这种方式就遇到了问题.如果每次传递数据都需要三次握手四次挥手的话,那么随着数据访问量的增加,那么三次握手四次挥手带来的资源消耗就会成为影响系统的瓶颈.这就好像一根针重量可以忽略,但当我们聚集上亿根针的时候,那么他的重量和所占用的空间,就成了必须要考虑的问题了. 那能不能建立好一次链接之后,我多传递几次数据,然后在关闭呢?当然可以,这就是长链接,也就是大家常说的"Keep-Alive".而HTTP1.1则是默认就开启了Keep-Alive.Keep-Alive虽然暂时性的解决了建立链接所带来的开销,也一定程度的提高了响应速度,但后来又凸显了另外两个问题: 首先,因为http是串行文件传输.所以当客户端请求a文件时,b文件只能等待.等待a链接到服务器,服务器处理文件,服务器返回文件这三个步骤完成后,b才能接着处理.我们假设,链接服务器,服务器处理,服务器返回各需要1秒,那么b处理完的时候就需要6秒,以此类推.(当然,这里有个前提,服务器和浏览器都是单通道的.)这就是我们说的阻塞. 其次,链接数的问题.我们都知道服务器的链接数是有限的.并且浏览器也对链接数有限制.这样能接入进来的服务就是有个数限制的,当达到这个限制的时候,其他的就需要等待链接被断开,然后新的请求才能够进入.这个比较容易理解. 之所以http1.x会使用串行文件传输,是因为http传输的无论是request还是response都是基于文本的,所以接收端无法知道数据的顺序,因此必须按着顺序传输.这也就限制了只要请求就必须新建立一个链接,这也就导致了第二个问题的出现. 1.2 Http/2 为了从根本上行解决http1.x所遗留的这两个问题,http2引入了二进制数据帧和流的概念.其中帧的作用就是对数据进行顺序标识,这样的话,接收端就可以根据顺序标识来进行数据合并了.同时,因为数据有了顺序,服务器和客户端就可以并行的传输数据,而这就是流所作的事情. 这样,因为服务器和客户端可以借助流进行并行的传递数据,那么同一台客户端就可以使用一个链接来进行传输,此时服务器能处理的并发数就有了质的飞跃. http/2的这个新特性,就是多路复用.我们可以看到,多路复用的本质就是并行传输.那web对请求的处理是否可以使用这个思路呢? 2.Servlet 现在我们来讨论Servlet与Netty.这两个一个主要是以同步阻塞的方式服务的,另一个是异步非阻塞的.这也就造成了他们适用的场景是不同的. 2.1 Servlet 做JavaWeb研发的几乎没有不知道Servlet的.在Servlet 3.0之前,Servlet采用Thread-Per-Request的方式处理请求,即每一次Http请求都由某一个线程从头到尾负责处理。如果一个请求需要进行IO操作,比如访问数据库、调用第三方服务接口等,那么其所对应的线程将同步地等待IO操作完成, 而IO操作是非常慢的,所以此时的线程并不能及时地释放回线程池以供后续使用,在并发量越来越大的情况下,这将带来严重的性能问题。为了解决这一的问题,Servlet3.0引入了异步处理. 在Servlet 3.0中,我们可以从HttpServletRequest对象中获得一个AsyncContext对象,该对象构成了异步处理的上下文,Request和Response对象都可从中获取。AsyncContext可以从当前线程传给另外的线程,并在新的线程中完成对请求的处理并返回结果给客户端,初始线程便可以还回给容器线程池以处理更多的请求。如此,通过将请求从一个线程传给另一个线程处理的过程便构成了Servlet 3.0中的异步处理。 这里举个例子,对于一个需要完成长时处理的Servlet来说,其实现通常为: packagetop.lianmengtu.testjson.servlet; importjavax.servlet.ServletException; importjavax.servlet.annotation.WebServlet; importjavax.servlet.http.HttpServlet; importjavax.servlet.http.HttpServletRequest; importjavax.servlet.http.HttpServletResponse; importjava.io.IOException; //@WebServlet("/syncHello"),因为使用的SpringBoot模拟,所以注释掉该注解 publicclassMyServletextendsHttpServlet{ @Override protectedvoiddoGet(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException{ super.doGet(req,resp); newLongRunningProcess().run(); System.out.println("HelloWorld"); } } LongRunningProcess实现如下: packagetop.lianmengtu.testjson.servlet; importjava.util.concurrent.ThreadLocalRandom; publicclassLongRunningProcess{ publicvoidrun(){ try{ intmillis=ThreadLocalRandom.current().nextInt(2000); StringcurrentThread=Thread.currentThread().getName(); System.out.println(currentThread+"sleepfor"+millis+"milliseconds."); Thread.sleep(millis); }catch(InterruptedExceptione){ e.printStackTrace(); } } } 我们现在将MyServlet注入到Spring容器中: @Bean publicServletRegistrationBeanservletRegistrationBean(){ returnnewServletRegistrationBean(newMyServlet(),"/syncHello"); } 此时的SyncHelloServlet将顺序地先执行LongRunningProcess的run()方法,然后在控制台打印HelloWorld.而3.0则提供了对异步的支持,因此在Servlet3.0中我们可以这么写: @Override protectedvoiddoGet(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException{ AsyncContextasyncContext=req.startAsync(); asyncContext.start(()->{ newLongRunningProcess().run(); try{ asyncContext.getResponse().getWriter().print("HelloWorld"); }catch(IOExceptione){ e.printStackTrace(); } asyncContext.complete(); }); } 此时,我们先通过request.startAsync()获取到该请求对应的AsyncContext,然后调用AsyncContext的start()方法进行异步处理,处理完毕后需要调用complete()方法告知Servlet容器。start()方法会向Servlet容器另外申请一个新的线程(可以是从Servlet容器中已有的主线程池获取,也可以另外维护一个线程池,不同容器实现可能不一样),然后在这个新的线程中继续处理请求,而原先的线程将被回收到主线程池中。事实上,这种方式对性能的改进不大,因为如果新的线程和初始线程共享同一个线程池的话,相当于闲置下了一个线程,但同时又占用了另一个线程。 Servlet 3.0对请求的处理虽然是异步的,但是对InputStream和OutputStream的IO操作却依然是阻塞的,对于数据量大的请求体或者返回体,阻塞IO也将导致不必要的等待。因此在Servlet 3.1中引入了非阻塞IO,通过在HttpServletRequest和HttpServletResponse中分别添加ReadListener和WriterListener方式,只有在IO数据满足一定条件时(比如数据准备好时),才进行后续的操作。 虽然Servlet3.1提供了异步的方式,并且做的也比Servlet3.0更彻底,但是如果我们使用了Servlet3.1提供的异步接口,像刚刚的代码演示的那样,那么我们在之后的处理中就没有办法再使用他原来的接口了.这就让我们处于了一种非此即彼的状况中.如果是这样,Servlet系列的技术,如SpringMVC也就是这样了.那怎么办呢? 3. WebFlux的出场 现在我们会从以下几个层面来探讨WebFlux 为什么要有WebFlux? Reactive定义与ReactiveAPI WebFlux中的性能问题 WebFlux的并发模型 WebFlux的适用性 3.1为什么要有WebFlux 首先,为什么要有webFlux? 在前面两部分,我们一直在探讨并发问题.为了解决并发,我们需要使用非阻塞的web技术栈.因为非阻塞的web栈使用的线程数更少,对硬件资源的要求更低.虽然Servlet3.1为非阻塞I/O提供了一些支持,但刚刚我们提到了,如果我们使用Servlet3.1里的非阻塞API,会导致我们无法再使用它原来的API.并且,自从非阻塞I/O以及异步概念出现之后,就诞生了一批专为异步和非阻塞I/O设计的服务器,比如Netty,这就催生了新的能服务于各种非阻塞I/O服务器的统一的API. WebFlux诞生的另一个重要原因是函数式程序设计.随着脚本型语言(Nodejs,Angular等)的扩张,函数式程序设计以及后继式API也相继火起来.以至于Java也在Java8中引入了Lambda来对函数式程序设计进行支持,又引入了StreamAPI来对后继式程序进行支持.由此,对具备函数式编程和后继式程序设计的Web框架的需求也越来越大了。 3.2Reactive的定义与API Reactive的定义 我们接触了"非阻塞"和"函数式",那reactive是什么意思呢? "reactive"这个术语指的是:围绕着对改变做出响应的程序设计模型---网络组件对IO事件做出响应,UIController对鼠标事件做出响应等等.在那种情况下,非阻塞取代了阻塞是响应式的,我们正处于响应模式中,当操作完成和数据变得可用的时候发起通知. 还有另一个重要的机制那就是我们在spring team里整合"reactive"以及非阻塞式背压机制.在同步里,命令式的代码,阻塞式地调用服务为普通的表单充当背压机制强迫调用者等待.在非阻塞式编程中,控制事件的频率就变得很重要防止快速的生产者不会压垮他的目的地. Reactive Streams 是一个定义了使用背压机制的异步组件之间交互设计的小型说明书(在Java9中也采纳了).例如,一个数据仓库(可以看做Publisher)可以生产数据,然后HTTP Server(看做订阅者)可以写入到响应里.Reactive Streams的主要目的是让订阅者可以控制生产者产生数据的速度有多快或有多慢. Reactive API Reactive Streams 在互操作性上扮演了一个很重要的角色.类库和基础设施组件虽然有趣,但对于应用程序API来说却用处甚少,因为他们太底层了.应用程序需要一个更高级别更丰富的函数式API来编写异步逻辑---和Java8里的StreamAPI很类似,不过不仅仅是为集合做准备的. Reactor 是为SpringWebFlux选择的一个reactive类库.它提供了Mono和Flux类型的API来处理0..1(Mono)和0..N(Flux)数据序列化通过一组丰富的操作集和ReactiveX vocabulary of operators对齐.Reactor 是一个Reactive Streams类库,所以他所有的操作都支持非阻塞背压机制.Reactor强烈地聚焦于Server端的Java.他在发展上和Spring有着紧密的协作. WebFlux要求Reactor作为一个核心依赖,但凭借Reactive Streams也可以和其他的reactive libraries一起使用.一般来说,一个WebFlux API 接收一个Publisher作为输入,转换给一个内置的Reactor类型来使用,最后返回一个Flux或一个Mono作为输出.所以,你可以批准任何的Publisher作为输入,你可以应用操作在输出上,但你因为你使用了其他的reactive library所以你需要进行转换.只要可行(例如,注解controllers),WebFlux可以在使用RXJava和另一个reactive library之间透明的改变.看Reactive Libraries获取更多地细节. 3.3 性能 性能这个词有很多特征和含义.Reactive 和非阻塞通常不会使应用程序运行地更快.在某些场景下,他们也可以.(例如,在并行条件下使用WebClient来执行远程调用的话).整体来说,非阻塞方式可能需要做更多的工作并且他也会稍微增加请求处理的时间. 对reactive和非阻塞好处的预期关键在于使用小,固定的线程数和更少的内存来扩展的能力.这使应用程序在加载的时候更加有弹性,因为他们以一种更可以预测的方式扩展.然而为了看到这些好处,你需要一些延迟(包括比较慢的不可预知的网络I/O).那是响应式堆栈开始显示他力量的地方,并且这些不同是非常吸引人的. 3.4并发模型 Spring MVC和Spring WebFlux都支持注解Controllers,但他们在并发模型和对阻塞和线程的默认呈现(assumptions)上是非常不同的.在Spring MVC(和通用的servlet应用)中,都假设应用程序是阻塞当前线程的(例如,远程调用),并且出于这个原因,servlet容器处理请求的期间使用一个巨大的线程池来吸收潜在的阻塞. 在Spring WebFlux(和非阻塞服务器)中,假设应用程序是非阻塞的,所以,非阻塞服务器使用小的,固定代销的线程池(event loop workders)来处理请求. "弹性伸缩"和"小数量的线程"或许听起来矛盾,但是对于不会阻塞当前线程(用依赖回调来取代)意味着你不需要额外的线程,因为非阻塞调用给处理了. 调用一个阻塞API 要是你需要使用阻塞库怎么办?Reactor和RxJava都提供了publishOn操作用一个不同的线程来继续处理.那意味着有一个简单的脱离舱口(一个可以离开非阻塞的出口).然而,请牢记,阻塞API对于并发模型来说不太合适. 易变的状态 在Reactor和RxJava里,你通过操作符生命逻辑,在运行时在不同的阶段里,都会形成一个进行数据序列化处理的管道.这样做的一个主要好处就是把应用程序从不同的状态保护中解放了出来,因为管道中的应用代码是绝不会被同时调用的. 线程模型 在运行了一个使用Spring WebFlux的服务器上,你期望看到什么线程呢? 在一个"vanilla"Spring WebFlux服务器上(例如,没有数据访问也没有其他可选的依赖),你能够看到一个服务器线程和几个其他的用来处理请求的线程(一般来说,线程的数目和CPU的核数是一样的).然而,Servlet容器在启动的时候就使用了更多的线程(例如,tomcat是10个),来支持servlet(阻塞)I/O和servlet3.1(非阻塞)I/O的用法. 响应式的WebClient操作是用Event Loop方式.所以你可以看到少量的固定数量的线程和他关联.(例如,使用了Reactor Netty连接的reactor-http-nio).然而,如果Reactor Netty在客户端和服务端都被使用了,这两者之间的event loop资源默认是被共享的. Reactor和RxJava提供了抽象化的线程池,调度器目的是结合publishOn操作符在不同的线程池之间切换操作.调度器有一个名字,建议这个名字是一个具体的并发策略--例如,"parallel"(因为CPU-bound使用有限的线程数来工作)或者"elastic"(因为I/O-bound使用大量的线程来工作).如果你看到这类的线程,这就意味着一些代码正在使用一个具体的使用了Scheduler策略的线程池. 数据访问库和其他第三方库依赖也创建和使用了他们自己的线程. 下次我们来分享Spring WebFlux的使用. 本文相关视频

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

【熵教育】Jacob-你所遗漏的SpringBoot日志管理知识——熵学院

Spring Boot对所有内部日志记录使用了Commons Logging,但是底层日志实现是开放的。可以为 Java Util日志、Log4J2和Logback。对于每种日志都预先配置为使用控制台输出和可选的文件输出。默认为Logback 日志配置 通过将相应的库添加到classpath可以激活各种日志系统,然后在classpath根目录下提供合适的配置文件可以进一步定制日志系统,配置文件也可以通过Spring Environment的logging.config属性指定。 以下文件会根据你选择的日志系统进行加载: 日志系统 定制配置 Logback logback-spring.xml,logback-spring.groovy,logback.xml或logback.groovy Log4j log4j.properties或log4j.xml Log4j2 log4j2-spring.xml或log4j2.xml JDK (Java Util Logging) logging.properties 注 如果可能的话,建议你使用-spring变种形式定义日志配置(例如,使用logback-spring.xml而不是logback.xml)。如果你使用标准的配置路径,Spring可能不能够完全控制日志初始化。 注 Java Util Logging从可执行jar运行时会导致一些已知的类加载问题,我们建议尽可能不使用它。 以下是从Spring Envrionment转换为System properties的一些有助于定制的配置属性: Spring Environment System Property Comments logging.exception-conversion-word LOG_EXCEPTION_CONVERSION_WORD 记录异常使用的关键字 logging.file LOG_FILE 如果指定就会在默认的日志配置中使用 logging.path LOG_PATH 如果指定就会在默认的日志配置中使用 logging.pattern.console CONSOLE_LOG_PATTERN 日志输出到控制台(stdout)时使用的模式(只支持默认的logback设置) logging.pattern.file FILE_LOG_PATTERN 日志输出到文件时使用的模式(如果LOG_FILE启用,只支持默认的logback设置) logging.pattern.level LOG_LEVEL_PATTERN 用来渲染日志级别的格式(默认%5p,只支持默认的logback设置) PID PID 当前的处理进程(process)ID(能够找到,且还没有用作OS环境变量) 所有支持的日志系统在解析配置文件时都能获取系统属性的值,具体可以参考spring-boot.jar中的默认配置。 log4j2配置 一份史诗级配置 /home/logs/log/dev \ \ \ \ </Appenders> <Loggers> <Root level="debug"> <AppenderRef ref="Console"/> <AppenderRef ref="debug"/> <AppenderRef ref="info"/> <AppenderRef ref="warn"/> <AppenderRef ref="error"/> </Root> </Loggers> 各个文件输出到不同级别的目录 设置最大保存时间为15天 每个文件最大50M tomcat的日志 在properties文件添加如下配置: server.tomcat.basedir=/home/logs/log-api/tomcat-logs server.tomcat.accesslog.enabled=true server.tomcat.accesslog.pattern=%t %a "%r" %s %D (%D ms) server.use-forward-headers=true lombok的神组合 使用lombok的@Slf4j 注解,省去配置声明log的繁琐,提高开发效率。 idea的 grep console组合 Grep Console 自定义设置控制台输出颜色,这样控制台就能比较明显的看到警告或者错误的信息,方便查找问题 视频:https://space.bilibili.com/313762729/#/

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

hive 增删改 实例

pom <dependency> <groupId>org.apache.hive</groupId> <artifactId>hive-jdbc</artifactId> <version>0.11.0</version> </dependency> <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-common</artifactId> <version>2.2.0</version> </dependency> public class TeminalHiveCoreTest { public static void main(String[] args) throws SQLException { String tableName = " terminal "; String params ="appId,deviceMac,deviceId,deviceToken,deviceImsi,deviceModel,deviceManufacture,channel,appKey,userId,appVersion,versionCod,sdkType,sdkVersion,os,country,language,timezone,resolution,access,accessSubtype,carrier,cpu"; String partitionId ="appId"; String clusterId ="deviceToken"; String baseHdfsPath="hdfs://dev10.aoiplus.openpf:9000"; String hdfsPath=baseHdfsPath +"/user/hadoop/storm/terminalInfo_terminalInfo-3-0-1483076684221.log"; Connection connection = HiveJdbcConnection.getInstance().getConnection(HiveJdbcConnection.url, HiveJdbcConnection.user, HiveJdbcConnection.password); Statement stmt = connection.createStatement(); ResultSet res = null; HiveSqlDateDefineLanguage hqddl = new HiveSqlDateDefineLanguage().setCon(connection).setStmt(stmt); hqddl.dropTableByName(tableName); hqddl.createTableByParams(tableName, params,partitionId,clusterId); hqddl.loadSQL(tableName, hdfsPath); HiveSqlDateManageLanguage hqdml = new TermialHiveSqlDMLOperator().setCon(connection).setStmt(stmt).setRes(res); List<TerminalInfo> list = hqdml.findAll(tableName); System.out.println(list); HiveJdbcConnection.getInstance().close(res, stmt, connection); } } package com.curiousby.baoyou.cn.hive.base; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; /** * @Type HiveJdbcConnection.java * @Desc * @author hadoop * @date 2016年12月29日 下午3:04:42 * @version */ public class HiveJdbcConnection { public static String driverName = "org.apache.hive.jdbc.HiveDriver"; public static String url ="jdbc:hive2://172.23.27.120:10000/default"; public static String user ="hive"; public static String password="hive"; static{ try { Class.forName(driverName); } catch (ClassNotFoundException e) { e.printStackTrace(); throw new ExceptionInInitializerError(); } } private static HiveJdbcConnection instance = null; public static HiveJdbcConnection getInstance(){ if (instance == null ) { synchronized (HiveJdbcConnection.class) { if (instance == null ) { instance = new HiveJdbcConnection(); } } } return instance; } public Connection getConnection(String url,String user,String password) throws SQLException { return DriverManager.getConnection(url, user, password); } public void close(ResultSet rs, Statement st, Connection conn) { try { if (rs != null) rs.close(); } catch (SQLException e) { e.printStackTrace(); } finally { try { if (st != null) st.close(); } catch (SQLException e) { e.printStackTrace(); } finally { if (conn != null) try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } } public static void main(String[] args) throws SQLException { System.out.println(HiveJdbcConnection.getInstance().getConnection(url, user, password)); } } public class HiveSqlDateDefineLanguage implements SqlDateDefineLanguageInterface,SqlLoadData{ private Statement stmt; private ResultSet res; private Connection con; @Override public boolean createDatabase(String sql) { return false; } @Override public boolean createTable(String sql) { try { stmt.execute(sql); } catch (SQLException e) { e.printStackTrace(); return false; } return true; } /* CREATE [EXTERNAL] TABLE [IF NOT EXISTS] table_name [(col_name data_type [COMMENT col_comment], ...)] [COMMENT table_comment] [PARTITIONED BY (col_name data_type [COMMENT col_comment], ...)] [CLUSTERED BY (col_name, col_name, ...) [SORTED BY (col_name [ASC|DESC], ...)] INTO num_buckets BUCKETS] [ROW FORMAT row_format] [STORED AS file_format] [LOCATION hdfs_path]*/ public boolean createTableByParams(String tableName,String params,String partitionedId,String clusteredId ){ String properties =""; String[] objects = params.split(","); for (int i = 0; i < objects.length; i++) { properties += objects[i] +" string,"; } properties = properties.substring(0, properties.length()-1); String sql =" create table " + tableName + " ("+ properties +") " +" comment '"+tableName+"' " // +" PARTITIONED BY ("+partitionedId+" string) " +" clustered by ("+clusteredId+") into 2 buckets " + " row format delimited fields terminated by ',' " // +" stored as orc " +" TBLPROPERTIES ('transactional'='true') " ; System.out.println(sql); return execute(sql); } @Override public boolean dropTable(String sql) { try { stmt.execute(sql); } catch (SQLException e) { e.printStackTrace(); return false; } return true; } public boolean dropTableByName(String tableName) { String sql ="drop table if exists " + tableName; return execute(sql); } public boolean execute(String sql) { try { stmt.execute(sql); } catch (SQLException e) { e.printStackTrace(); return false; } return true; } public Statement getStmt() { return stmt; } public ResultSet getRes() { return res; } public Connection getCon() { return con; } public HiveSqlDateDefineLanguage setStmt(Statement stmt) { this.stmt = stmt; return this; } public HiveSqlDateDefineLanguage setRes(ResultSet res) { this.res = res; return this; } public HiveSqlDateDefineLanguage setCon(Connection con) { this.con = con; return this; } @Override public boolean loadSQL(String tableName, String hdfsPath) { String sql = "LOAD DATA INPATH '"+hdfsPath+"' OVERWRITE INTO TABLE "+tableName; return execute(sql); } @Override public boolean loadLocalSQL(String tableName, String localPath) { String sql = "LOAD DATA local INPATH '"+localPath+"' OVERWRITE INTO TABLE "+tableName; return execute(sql); } } public abstract class HiveSqlDateManageLanguage extends SqlEntiyFormat<TerminalInfo> implements SqlDateManageLanguageInterface<TerminalInfo> { private Statement stmt; private ResultSet res; private Connection con; @Override public List<TerminalInfo> findAll(String tableName) { try { res = stmt.executeQuery("select * from " +tableName); List< TerminalInfo> list = formate(res); return list; } catch (SQLException e) { } return null; } @Override public TerminalInfo findOne(String key, String value) { return null; } @Override public TerminalInfo findOne(String sql) { return null; } @Override public List<TerminalInfo> findAllBySql(String sql) { return null; } @Override public boolean update(String sql) { return false; } @Override public boolean delete(String sql) { return false; } @Override public boolean insert(String sql) { return false; } @Override public boolean insert(TerminalInfo t) { return false; } public Statement getStmt() { return stmt; } public ResultSet getRes() { return res; } public Connection getCon() { return con; } public HiveSqlDateManageLanguage setStmt(Statement stmt) { this.stmt = stmt; return this; } public HiveSqlDateManageLanguage setRes(ResultSet res) { this.res = res; return this; } public HiveSqlDateManageLanguage setCon(Connection con) { this.con = con; return this; } } public class TermialHiveSqlDMLOperator extends HiveSqlDateManageLanguage{ @Override public void sqlMapper(ResultSet res, List<TerminalInfo> list) throws SQLException { TerminalInfo entity = new TerminalInfo(); entity .setAppId(res.getString(1)) .setDeviceMac(res.getString(2)) .setDeviceId(res.getString(3)) .setDeviceToken(res.getString(4)) .setDeviceImsi(res.getString(5)) .setDeviceModel(res.getString(6)) .setDeviceManufacture(res.getString(7)) .setChannel(res.getString(8)) .setAppKey(res.getString(9)) .setUserId(res.getString(10)) .setAppVersion(res.getString(1)) .setVersionCode(res.getString(12)) .setSdkType(res.getString(13)) .setSdkVersion(res.getString(14)) .setOs(res.getString(15)) .setCountry(res.getString(16)) .setLanguage(res.getString(17)) .setTimezone(res.getString(18)) .setResolution(res.getString(19)) .setAccess(res.getString(20)) .setAccessSubtype(res.getString(21)) .setCarrier(res.getString(22)) .setCpu(res.getString(23)) ; list.add(entity); } } public interface SqlLoadData { public boolean loadSQL(String tableName,String hdfsPath); public boolean loadLocalSQL(String tableName,String localPath); } public interface SqlMapper<T> { public void sqlMapper(ResultSet res , List< T> list) throws SQLException; } public abstract class SqlEntiyFormat<T> implements SqlMapper<T>{ public List<T> formate( ResultSet res) throws SQLException{ List< T> list = new ArrayList<T>(); while (res.next()) { sqlMapper(res, list); } return list; } } public interface SqlDateManageLanguageInterface<T> { public List<T> findAll(String tableName); public T findOne(String key ,String value); public T findOne (String sql); public List<T> findAllBySql (String sql); public boolean update (String sql); public boolean delete (String sql); public boolean insert(String sql); public boolean insert(T t); } /** * @Type SqlDateDefineLanguageInterface.java * @Desc CREATE DATABASE - 创建新数据库 ALTER DATABASE - 修改数据库 CREATE TABLE - 创建新表 ALTER TABLE - 变更(改变)数据库表 DROP TABLE - 删除表 CREATE INDEX - 创建索引(搜索键) DROP INDEX - 删除索引 * @author hadoop * @date 2016年12月29日 下午2:54:34 * @version */ public interface SqlDateDefineLanguageInterface { public boolean createDatabase(String sql); public boolean createTable(String sql); public boolean dropTable(String sql); public boolean dropTableByName(String tableName); } 捐助开发者 在兴趣的驱动下,写一个免费的东西,有欣喜,也还有汗水,希望你喜欢我的作品,同时也能支持一下。 当然,有钱捧个钱场(右上角的爱心标志,支持支付宝和PayPal捐助),没钱捧个人场,谢谢各位。 谢谢您的赞助,我会做的更好!

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

Go语言恶意软件暴20倍

根据以色列安全公司Intezer的最新报告《地鼠年:2020年恶意软件综述》,在过去的四年中,随着国家黑客和网络犯罪纷纷转向新的技术生态系统,用Go编程语言编写的新恶意软件激增了2000%。 报告显示,尽管Go语言(有时被称为Golang),大约在九年前就被首次用于恶意软件,但直到2019年它才在网络犯罪分子中流行。 从那时起,Go已成为一种越来越普遍的选择,主要是因为它可以在Windows、Linux和Mac操作系统上运行,并且对研究人员进行逆向工程提出了难度较大的挑战。 Intezer还称赞Go具有“非常易于使用的编写良好的网络堆栈”。 报告指出,去年俄罗斯支持的黑客使用Zebrocy恶意软件的变体攻击了东欧国家。俄罗斯黑客还使用Go语言开发了WellMess恶意软件,该恶意软件针对英国、加拿大和美国的新冠疫苗研究人员。 在网络犯罪方面,Go语言编写的恶意软件包括用于发动DDoS攻击和非法开采加密货币的僵尸网络(IPStorm),以及全部用Go语言编写的勒索软件变体(Nefilim、EKANS)。 Intezer总结说,用户将需要专门的运行保护工具来应对Go恶意软件不断增长的威胁。 “我们看到攻击者针对操作系统的恶意软件来自同一Go代码库。由于许多因素,传统的防病毒程序很难识别Go恶意软件。基于代码重用的检测方法已证明是有效的,特别是在检测恶意软件家族方面。随着更多有价值的资产转移到云中,Go恶意软件对云环境的攻击也可能会增加。” 【本文是51CTO专栏作者“安全牛”的原创文章,转载请通过安全牛(微信公众号id:gooann-sectv)获取授权】 戳这里,看该作者更多好文

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

利用bootstrap模态框+ajax对数据进行增删改

接上篇:1.数据库改动,添加外键,多对多model.py from django.db import models # Create your models here. GENDER = { ('male', '男'), ('female', '女'), } class User(models.Model): username=models.CharField(max_length=32,null=False) password=models.CharField(max_length=32,null=False) email = models.EmailField(null=True) name = models.CharField(max_length=20, null=True) tel = models.CharField(max_length=20, null=True) gender = models.CharField(max_length=20, choices=GENDER) birthday = models.DateTimeField(null=True) class Proxy(models.Model): ng_name=models.CharField(max_length=20) class Domain(models.Model): domain=models.CharField(max_length=125,null=False) src=models.CharField(max_length=125,null=False) User=models.ForeignKey(User,on_delete=models.CASCADE) m=models.ManyToManyField(Proxy) 核心部分之view from django.core.exceptions import ValidationError from django.db import IntegrityError from django.shortcuts import redirect, render, HttpResponse from cdnpanel.models import User, Domain, Proxy import json def index(request): if request.COOKIES.get("is_login"): username = request.COOKIES.get('username') data = Domain.objects.filter(User__username=username) engine = Proxy.objects.all() user = User.objects.all() return render(request, 'index.html', {'data': data, "engine": engine, "user": user}) else: return redirect('/login') def login(request): if request.method == "POST": ajax_rep = {"status": "err", "msg": None} user = request.POST.get('username') pwd = request.POST.get('password') if not User.objects.filter(username=user).exists(): ajax_rep['msg'] = "用户不存在" elif not User.objects.filter(username=user, password=pwd).exists(): ajax_rep['msg'] = "密码错误" else: ajax_rep['status'] = 'ok' rep = HttpResponse(json.dumps(ajax_rep)) rep.set_cookie("is_login", True) rep.set_cookie("username", user) return rep return HttpResponse(json.dumps(ajax_rep)) return render(request, "login.html") def register(request): if request.method == "POST": ajax_rsp = {"status": "ok", "msg": None} try: if request.is_ajax(): username = request.POST.get('username') password = request.POST.get('password') name = request.POST.get('name') email = request.POST.get('email') tel = request.POST.get('tel') gender = request.POST.get('gender') birthday = request.POST.get('birthday') if User.objects.filter(username=username).exists(): ajax_rsp["status"] = "err" ajax_rsp["msg"] = "用户已存在" else: User.objects.create( username=username, password=password, email=email, name=name, tel=tel, gender=gender, birthday=birthday, ) except IntegrityError: ajax_rsp["status"] = "err" ajax_rsp['msg'] = "性别为必选项" except ValidationError: ajax_rsp["status"] = "err" ajax_rsp['msg'] = "日期为必选项" return HttpResponse(json.dumps(ajax_rsp)) else: return render(request, "register.html") def add_domain(request): if request.is_ajax(): print(request.POST) ajax_rsp = {'status': "err", "msg": None, "yourself": "yes"} try: domain = request.POST.get("domain") src = request.POST.get("src") user = request.POST.get("username") if not user == request.COOKIES.get('username'): ajax_rsp["yourself"] = "no" user_id = User.objects.get(username=user).id ng_id = [] ng = request.POST.getlist('ng_name') for n in ng: nid = Proxy.objects.get(ng_name=n).id ng_id.append(nid) Domain.objects.create(domain=domain, src=src, User_id=user_id) obj = Domain.objects.get(domain=domain) obj.m.set(ng_id) ajax_rsp['data_id'] = obj.id ajax_rsp['status'] = 'ok' except Exception as e: print(e) ajax_rsp['msg'] = str(e) return HttpResponse(json.dumps(ajax_rsp)) return redirect('/index') def del_domain(request): if request.is_ajax(): ajax_rsp = {'status': "err", "msg": None} try: del_data_id=request.POST.get("del_data_id") print(del_data_id) Domain.objects.get(id=del_data_id).delete() ajax_rsp['status'] = "ok" except Exception as e: print(e) ajax_rsp['msg'] = str(e) return HttpResponse(json.dumps(ajax_rsp)) def update_domain(request): if request.is_ajax(): if request.method == "GET": ajax_rsp = {"status": "err"} try: data_id=request.GET.get("data_id") domain=Domain.objects.filter(id=data_id).first() ajax_rsp["domain"]=domain.domain ajax_rsp["src"]=domain.src ajax_rsp["user"]=User.objects.get(domain__domain=domain.domain,domain__id=data_id).username engine_list=[] for item in Proxy.objects.filter(domain__domain=domain.domain): engine_list.append(item.ng_name) ajax_rsp["engine"]=engine_list ajax_rsp["status"]='ok' print(ajax_rsp) except Exception as e: print(e) ajax_rsp['msg'] = str(e) return HttpResponse(json.dumps(ajax_rsp)) elif request.method == "POST": print(request.POST) ajax_rsp = {'status': "err", "msg": None, "yourself": "yes"} try: old_id = request.POST.get("data_id") domain = request.POST.get("domain") src = request.POST.get("src") user = request.POST.get("username") if not user == request.COOKIES.get('username'): ajax_rsp["yourself"] = "no" user_id = User.objects.get(username=user).id ng_id = [] ng = request.POST.getlist('ng_name') for n in ng: nid = Proxy.objects.get(ng_name=n).id ng_id.append(nid) Domain.objects.filter(id=old_id).update(domain=domain, src=src, User_id=user_id) obj = Domain.objects.get(domain=domain,src=src) obj.m.set(ng_id, clear=True) ajax_rsp['status'] = 'ok' except Exception as e: ajax_rsp['msg'] = str(e) return HttpResponse(json.dumps(ajax_rsp)) def test(request): print(Domain.objects.get(domain="2343165sada4864sdsa.tw")) return render(request, 'test.html') 核心部分之index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <!-- 最新版本的 Bootstrap 核心 CSS 文件 --> {% load static %} <link rel="stylesheet" href="{% static 'plugin/bootstrap/css/bootstrap.min.css' %}"> <link href="//netdna.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"> <style> .table{ width: 300px; } </style> <body> <p></p> <div> <button type="button" class="btn btn-success" data-toggle="modal" data-target="#addModal">添加</button> </div> <p></p> <div> <table class="table table-bordered"> <thead> <tr> <th>域名</th> <th>源</th> <th>操作</th> </tr> </thead> <tbody> {% for item in data %} <tr data_id="{{ item.id }}"> <td>{{ item.domain }}</td> <td>{{ item.src }}</td> <td> <a data-toggle="modal" data-target="#delModal" id="del_icon" class="fa fa-trash-o fa-lg"></a> <a data-toggle="modal" data-target="#editModal" class="fa fa-pencil fa-fw"></a> </td> </tr> {% endfor %} </tbody> {# 添加模态对话框#} <div class="modal fade" id="addModal" tabindex="-1" role="dialog" aria-labelledby="addModalLabel"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> <h4 class="modal-title" id="addModalLabel">New message</h4> </div> <div class="modal-body"> <form action="add_domain" id="modal_form" method="post"> <div class="form-group"> <label for="inputEmail3" class="col-sm-2 control-label">域名:</label> <input type="text" name="domain" class="form-control" id="inputEmail3" placeholder="域名"> </div> <div class="form-group"> <label class="col-sm-2 control-label">源:</label> <input name="src" class="form-control" placeholder="1.1.1.1"> </div> <div class="form-group"> {% for ng in engine %} <input type="checkbox" name="ng_name" value="{{ ng.ng_name }}">{{ ng.ng_name }} {% endfor %} </div> <div class="form-group"> <select name="username" > {% for u in user %} <option value="{{ u.username }}">{{ u.username }}</option> {% endfor %} </select> </div> </form> </div> <div class="modal-footer"> <button type="button" class="btn btn-default" data-dismiss="modal">Close</button> <button type="button" class="btn btn-submit add_submit">提交</button> </div> </div> </div> </div> </table> </div> {#删除模态框#} <!-- Modal --> <div class="modal fade " id="delModal" tabindex="-1" role="dialog" aria-labelledby="delModalLabel"> <div class="modal-dialog" role="document"> <div class="modal-content "> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> <h4 class="modal-title" id="delModalLabel" style="color: red" >危险!!</h4> </div> <div class="modal-body alert alert-danger" > 此操作不可以逆!是否删除? </div> <div class="modal-footer"> <button type="button" class="btn btn-default" data-dismiss="modal">Close</button> <button type="button" class="btn btn-danger del_submit">确认删除</button> </div> </div> </div> </div> {#编辑模态框#} <div class="modal fade" id="editModal" tabindex="-1" role="dialog" aria-labelledby="editModalLabel"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> <h4 class="modal-title" id="editModalLabel">update</h4> </div> <div class="modal-body"> <form action="update_domain" id="modal_form" method="post"> <div class="form-group"> <label for="inputEmail3" class="col-sm-2 control-label">域名:</label> <input type="text" name="domain" class="form-control" id="inputEmail3" > </div> <div class="form-group"> <label class="col-sm-2 control-label">源:</label> <input name="src" class="form-control" > </div> <div class="form-group"> {% for ng in engine %} <input type="checkbox" name="ng_name" value="{{ ng.ng_name }}">{{ ng.ng_name }} {% endfor %} </div> <div class="form-group"> <select name="username" > {% for u in user %} <option value="{{ u.username }}">{{ u.username }}</option> {% endfor %} </select> </div> </form> </div> <div class="modal-footer"> <button type="button" class="btn btn-default" data-dismiss="modal">Close</button> <button type="button" class="btn btn-submit edit_submit">提交</button> </div> </div> </div> </div> </table> </div> </body> <script src="/static/js/jquery-3.5.1.min.js"> </script> <script src="{% static 'plugin/bootstrap/js/bootstrap.min.js' %}"> </script> <script> $(".add_submit").click(function (){ var data=$("#addModal form").serialize() console.log(data) $.ajax({ data:data, dataType:"json", "type":"POST", "url":'/add_domain', "success":function (arg) { {#若添加的是自己的数据则在前端显示#} if(arg.yourself=="yes") { console.log(arg.msg) postdata = "{\"" + data.replace(RegExp("=", "g"), "\":\"").replace(RegExp("&", 'g'), "\",\"") + "\"}"; postdata = JSON.parse(postdata) console.log(postdata.domain) data_id=arg.data_id var tr_ele = $("<tr></tr>") tr_ele.attr("data_id",data_id) var dtd_ele = $("<td></td>").text(postdata.domain) var std_ele = $("<td></td>").text(postdata.src) var action_ele = '<td><a data-toggle="modal" data-target="#delModal" id="del_icon" class="fa fa-trash-o fa-lg"></a> <a data-toggle="modal" data-target="#editModal" class="fa fa-pencil fa-fw"></a></td>' tr_ele.append(dtd_ele) tr_ele.append(std_ele) tr_ele.append(action_ele) $("tbody").append(tr_ele) } $('#addModal').modal('hide') document.getElementById("modal_form").reset() } }) }) {# 删除函数 #} var data_id; {# 定义一个全局变量传递修改数据的id值 #} {#$(".fa-trash-o").click(function (){#} $(document).on("click",".fa-trash-o",function (){ data_id=$(this).parent().parent().attr("data_id") $(this).parent().parent().attr("id","del_targer"){# 因为没法通过data_id删除元素,给点击的tr标签加了个唯一id标识 #} $(".del_submit").click(function(){ var postdata={}; postdata['del_data_id']=data_id console.log(data_id) $.ajax({ data:postdata, dataType:"json", "type": "POST", "url":'/del_domain', "success":function (arg){ if(arg.status=="ok"){ $("#del_targer").remove() $('#delModal').modal('hide') } } }) }) }) {# 编辑函数 #} {# $(".fa-pencil").click(function (){#} $(document).on("click",".fa-pencil",function (){ data_id=data_id=$(this).parent().parent().attr("data_id") {# 直接从前端获取数据太麻烦,这里用ajax获取数据赋值#} $(this).parent().parent().attr("id","edit_targer") $.ajax({ type:"GET", data:{"data_id":data_id}, "url":'/update_domain', dataType:"json", "success":function (arg){ if(arg.status=="ok"){ $("#editModal input[name='domain']").val(arg.domain) $("#editModal input[name='src']").val(arg.src) $("#editModal option[value="+arg.user+"]").prop("selected",true) for (item in arg.engine) { ng_name=arg.engine[item] $("#editModal input[value="+ng_name+"]").prop("checked",true) } } } }) $(".edit_submit").click(function (){ var data=$("#editModal form").serialize() postdata = "{\"" + data.replace(RegExp("=", "g"), "\":\"").replace(RegExp("&", 'g'), "\",\"") + "\"}"; postdata = JSON.parse(postdata) postdata["data_id"]=data_id $.ajax({ data:postdata, dataType:"json", "type":"POST", "url":'/update_domain', "success":function (arg) { {#若修改的是自己的数据则在前端显示#} if (arg.status == 'ok') { if (arg.yourself == "yes") { var tr_ele = $("#edit_targer") tr_ele.empty() var dtd_ele = $("<td></td>").text(postdata.domain) var std_ele = $("<td></td>").text(postdata.src) var action_ele = '<td><a data-toggle="modal" data-target="#delModal" id="del_icon" class="fa fa-trash-o fa-lg"></a> <a data-toggle="modal" data-target="#editModal" class="fa fa-pencil fa-fw"></a></td>' tr_ele.append(dtd_ele) tr_ele.append(std_ele) tr_ele.append(action_ele) }else { var tr_ele = $("#edit_targer") tr_ele.empty() } $('#editModal').modal('hide') $("#edit_targer").attr("id","") } } }) }) }) $(".btn-default").click(function (){ document.getElementById("modal_form").reset() $(":checked").prop("checked",false) $(":selected").prop("selected",false) $("#del_targer").attr("id","") $("#edit_targer").attr("id","") }) </script> </html> url部分 urlpatterns = [ path('admin/', admin.site.urls), re_path('index', views.index), re_path('register', views.register), re_path('login', views.login), re_path('add_domain', views.add_domain), re_path('del_domain', views.del_domain), re_path('update_domain', views.update_domain), re_path('test', views.test), ] 思路历程简介: 1.登陆上篇的登陆没有设置cookie,所以每次都要重复登陆,操作其实很简单,当用户名和密码匹配成功了,在cookie中设置自定义字段is_login=true,然后在index函数中取得该值并判断,代码部分如下 def index(request): if request.COOKIES.get("is_login"): username = request.COOKIES.get('username') data = Domain.objects.filter(User__username=username) engine = Proxy.objects.all() user = User.objects.all() return render(request, 'index.html', {'data': data, "engine": engine, "user": user}) else: return redirect('/login') def login(request): if request.method == "POST": ajax_rep = {"status": "err", "msg": None} user = request.POST.get('username') pwd = request.POST.get('password') if not User.objects.filter(username=user).exists(): ajax_rep['msg'] = "用户不存在" elif not User.objects.filter(username=user, password=pwd).exists(): ajax_rep['msg'] = "密码错误" else: ajax_rep['status'] = 'ok' rep = HttpResponse(json.dumps(ajax_rep)) rep.set_cookie("is_login", True) rep.set_cookie("username", user) return rep return HttpResponse(json.dumps(ajax_rep)) return render(request, "login.html") 2.添加数据添加数据流程主要是:获取数据-->处理数据-->取得返回结果后展示 获取数据前还要先做填写数据的模态框,这里主要有个问题,因为涉及外键和多对多,所以要先有这些数据可以选,也就是模板里面必须传入外键表的值和多对多的值,我这里是render(request, 'index.html', {'data': data, "engine": engine, "user": user})然后用select元素作为多对多的选择项,checkbox为User的单选 <1>获取数据因为填数据用的是form表单,所以这里获取数据很简单用serialize()函数var data=$("#addModal form").serialize()<2>处理数据利用ajax传送,view函数处理即可<3>数据显示这里有个问题,serialize()函数处理出来的数据不是字典形式,而是类似get请求的形式:”username=linzb&password=123456“,所以要对其进行分割取值,才能对元素进行赋值(这里我还多赋值一个yourself字段,判断是否为当前登录用户,是才显示) 3.删除要删除,首先服务端要知道数据是什么,其次,服务端删除成功后,前端要删除对应元素,所以删除部分可以归结为一个问题:如何对原来的数据进行定位? 这里我利用了一个全局变量data_id(生成数据的时候在tr上赋值数据id),在点击删除按钮的时候,通过this关键字找到这个数据id并传递给data_id,并在id打了个del_tager标记,于是问题解决: <1>data_id通过ajax传递给服务端,服务端找到对应数据del <2>返回结果后,前端通过#del_tager找到标签并删除 (这里也许有人会问为什么不直接通过data_id,而要多此一举,这是因为我测试多次这方法行不通,才选择用del_targer) 4.编辑编辑比添加多了两步<1>打开模态框的时候要为模态框赋值(这里我采用ajax从服务端取值,这方法不推荐,从前端取值对服务端更友善)<2>成功执行后要在前端进行修改而不是添加 debug: 1.django 设置cookie需要用变量过渡,直接引用会报错 正确: rep=HttpResponse(json.dumps(ajax_rep)) rep.set_cookie("is_login",True) return rep 错误: return HttpResponse(json.dumps(ajax_rep)).set_cookie("is_login",True) 2. 给标签加 data-toggle="modal" data-target="#exampleModal"属性,点击没反应 测试发现是忘了加载bootstrap.min.js 3.ValueError: too many values to unpack (expected 2) 排查发现:Proxy.objects.get(ng_name=n).id写成了: Proxy.objects.get(n).id 少了字段名 4.添加时的元素位置和插入问题 想要的代码形式: <tbody> <tr> <td>已存在的数据</tr> </tr> <tr> <td>新增加的数据<td> </tr> </tbody> var tr_ele =$("<tr></tr>") var dtd_ele =$("<td></td>").text(新增加的数据) tr_ele.append(std_ele) $("tbody").append(tr_ele) 之前用:tr_ele.text(dtd_ele)和$("tbody tr").append(tr_ele)一直搞不定 错误原因在于没有意识到append是在内部添加,before()和after() 是在外部 5.Bootstrap 模态框使用 <1>以id为标识,内容标签包裹头部,body和尾部 <div class="modal fade" id="delModal" tabindex="-1" role="dialog" aria-labelledby="delModalLabel"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> </div> <div class="modal-body " > </div> <div class="modal-footer"> </div> </div> </div> </div> <2>用data-target指向id引用 <a data-toggle="modal" data-target="#delModal" id="del_icon" class="fa fa-trash-o fa-lg"></a> 6.jquery attr()和prop()区别 <1>取自定义属性用attr() (下面摘抄自:https://www.jb51.net/article/140974.htm) 1.添加属性名称该属性就会生效应该使用prop(); 2.是有true,false两个属性使用prop();(如'checked','selected','disabled'等) 3.其他则使用attr(); 7.模态框退出,清空表单输入值的方法(利用form表单的reset) 由于jquery没有reset()方法,可以给表单赋予id,然后reset() document.getElementById("modal_form").reset() 选择按钮则需要去掉对应属性 $(":checked").prop("checked",false) $(":selected").prop("selected",false) 8.编辑模态框完成后发现添加数据时报错 后面print (POST数据)发现数据取了多行 原因:取数据时没有区分form表单 原来:var data=$("form").serialize() 修改:var data=$("#addModal form").serialize() 9.多对多表更新,加上clear=True就可以 obj.m.set(ng_id,clear=True) 意为先清空后添加 10.做删除的时候利用tr传递data_id,添加的时候也必须加上 11.事件委托on语法: $(document).on("click",".fa-trash-o",function (){}) on("事件","选择标签",函数(){}) 删除时的思路:点击模态按钮时必须获取当前的值,用全局变量传递 要删除整行,于是放在tr标签上 12.如果发现清空数据有问题用(找到对应位置即可,51编辑器不好用,懒得重复改): document.getElementById("modal_form").reset() $(":checked").prop("checked",false) $(":selected").prop("selected",false) $("#del_targer").attr("id","") $("#edit_targer").attr("id","")

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

降本笑?割韭菜?

本月,滴滴崩溃事件闹得轰轰烈烈,各种离谱派单层出不穷,而造成这一混乱的,则是底层出故障的云。尴尬的是,这已经不是第一次云崩溃事件了,距离上一次阿里云事件,还不到一个月。 一时之间,各种有关“云”的讨论纷纷扬扬:有人眼馋马斯克的 X 下云省钱,觉得反正都有风险,还不如自己弄,这样更可掌控,也更清楚;有人则认为上云才是未来的趋势,想要发挥出软件的最大优势,上云更合适。 那么,实际操作中,到底是自建云更安全,还是公有云更有保障?对普通的厂商而言,该怎么选择呢?对此,开源中国邀请了前滴滴软件开发工程师李鹤、AutoMQ 联合创始人 & CTO 周新宇、前淘宝 DBA 蒋明、磐吉云数 CEO 冯若航、公众号《瑞典马工》主理人马工,一起来讨论。 上云到底有没有必要?云是不是在割韭菜? 正方:周新宇 speaking —— 我个人大概是在16年就加入阿里巴巴巴的中间件团队了,服务了很多客户。从我个人的经验来讲: 第一,没有云之前,硬件出了问题,解决的门槛很高。阿里内部上云之前,因为消息中间件集群规模很大,硬件它总是在出问题,比如说因为网卡问题导致了 TCD 重视率很高,硬盘出现局部的不可写;比如服务器某个硬件温度过高,导致一些局部的节点不可用。好在阿里有专业的团队帮忙解决,但这在小企业就很难负担了。 第二,公有云比专有云效率高。像我们专门做交付,交付完才是第一步,后面有很多的运维工作,这些运维工作,如果我们远程去做的话,效率比较低,如果去驻厂的话,成本又比较高。公有云的模式,通过聚集算力和统一标准,带来了很多效率上的提升。 第三,云计算能加速业务的创新,提高社会运转效率。像在线教育行业,几年前,基本上都利用了公有云的优势来快速进行业务的创新。如果没有云计算,如果不上云,这些完全不可能发生。还有疫情期间的远程会议,一定程度上保障了社会各方面的正常运转。背后的钉钉这些企业软件,也都是云计算在提供算力。 过去,大部分企业还是以云托管的方式上云为主,基本上就是传统的软件架构,通过这个方式进行上云。但不管怎么样,一定程度上解决了效率和创新问题。今天在降本增效的浪潮下,企业用云,可能不能暴力地照搬以前的架构了,架构需要面向云的能力去设计、去优化,把云原生的能力发挥出来。 反方:冯若航 speaking —— 我比较喜欢用实打实的数据来定量分析云。我的观点是:在降本增效成为主旋律的大背景下,对于有一定规模的云上企业来说,下云自建是一个非常经济务实的选项。我认为公有云它是有适用光谱的,绝对不是他们宣称的数字化万金油。不在这个光谱范围内的业务,如果选择上云,或者是留在云上,那就是被割韭菜了。 我这有歪诗一首:世人常道云上好,托管服务烦恼少。我言云乃杀猪盘,溢价百倍实厚颜。赛博地主搞垄断,坐地起价剥血汗。运维外包嫖开源,租赁电脑炒概念。 公有云的商业模式概括起来就是一个事儿:租——租服务器租带宽租磁盘租运维,这跟租房没有什么本质的区别,自建我们就可以类比为买房。那么租房还是买房的决定性因素是什么?我认为是租售比。房子的租售比一般在几百一个月,那大家买房可能要掂量。那么,服务器的租售比、云算力的租售比一般在半年左右使用费=购买价格。云磁盘的租售比就更离谱了,只有十几天到一个月。你用一块云盘十几天就能买下来,你用一台云服务器,六个月就能把它买下来,那么你为什么要把一个业务跑在这租的东西上,而不是直接把它买下来?如果你的业务生命周期超过六个月,你可以考虑把它买下来,而不是租。 为什么云的价格这么贵?我认为根本原因在硬件上。硬件遵循着摩尔定律在指数增长,成本在指数下降,而这个指数变化并没有在公有云的价格上反映出来,所以公有云从最开始的普惠基础设施,发展到今天变成了一个杀猪盘。它的资源租赁价格已经达到了自建单价的几十倍上百倍,堪称终极的成本刺客。 这也引发了一些新的变化,比如说马斯克的 X(Twitter)、DHH 的 37Signals,算了账单之后,明智地选择了从云上搬迁下来,节约了每年上千万甚至上亿的成本。我认为,这些案例对于整个行业,都是非常具有借鉴意义的里程碑标志。 所以,我认为云的适用光谱就是三件事——小规模,高弹性,全球化。不在这三个场景之内的业务,你选择留在云上,就是在为几倍几十倍的溢价交智商税、被割韭菜。 正方:周新宇 speaking —— 我觉得这里面有一个误区,不能拿这个硬件的成本去跟软件、甚至跟云服务对比。比如说冯总以前写过一篇文章,就是拿本地盘跟 EBS 价格做对比。实际上我认为 EBS 它本身是一个软件服务,它背后是一整套的完整的分布式系统,云服务已经提供了至少三个九的可用性。但本地盘它是硬件,它的故障概率是比较高的,不同的厂商,年化的故障率可能都有差异,有些可能甚至高达5%左右。任何硬件坏了都可能导致无法访问这个本地盘的数据,但在云上,ECS 也好 EBS 也好,它们都是软件,你可以理解为它们就是存算分离的。从应用角度来看,ECS、EBS 都是无状态的,EBS 还解决了一个多副本问题。 如果今天要用本地盘,肯定得主副本,那数据的复制带来的网络带宽消耗、计算资源消耗、存储空间消耗,都需要考虑到成本里面。另外,EBS 它后面是一个大规模的存储节点区域,是能够应对大量磁盘故障的,也能够解决这个数据完整性问题。如果真的要拿 EBS 跟本地盘去对比的话,我觉得至少得让用户去自建一套分布式存储系统,跟使用 EBS 做对比,还得把运维的人力成本也考虑进去。这些在自建、规模比较小的情况下,是很难算清楚的。 反方:冯若航 speaking —— 上云的成本比自建要高得多。我自己15年的时候在淘宝的 CNZZ,友盟+这个部门算是第一波被推上阿里云的内部 BU。在上云之前,我们有一个自己的机房,几百台服务器,一年所有成本算进去1000万。后来上了阿里云大数据全家桶数据库 ODPS 这些东西,每年计算3000万存储4000万。从1000万变成7000万这件事直接给了我对云的第一印象,因为阿里云是手把手出工程师加入我们团队帮我们改造业务搬上云的,从原来的每年1000万搬到了后来的每年7000万,而干的事情,本质上却还是一模一样的,都是统计和计算规模,也没有出现特别的变化。在上完云之后,我们的效能并没有出现变化,但是成本却是实打实地翻了七倍。 这是我自己亲身经历的一个案例。如果说更有共性的一件事,我觉得可以参考一下 Amazon。AWS 在2013年提出的公有云价值,他举了六个点:弹性、敏捷、全球化出海、将资本支出转变为运营支出,以及更低的成本、消除重复建设。 但是,这些公有云价值主张在2023年很多已经不成立了,甚至说很多已经没有价值了。我认为还有价值的点就是弹性、agility 和全球化出海,但是这里面覆盖的光谱其实并不多,特别是在高价值用户群体里面并没有覆盖那么多,更多是小微初创小规模业务会用到这些点。 比如这个 CAPEX 转为 OPEX,将资本支出转为运营支出。这一点,我认为除了对于那种连六个月都活不过的小业务有价值之外,凡是超过六个月,买肯定比租合算了。lower cost 是 AWS 当初相对于这些企业级解决方案来说的,它更便宜。比如说 Oracle 一盒1月你要付一万块钱,那么 AWS 上的 RDS 每个月只要1000块钱,是不是很便宜?但是那个时候,你可以说只有我这一家有云,所以我可以用这个价值定价,但现在谁家没有一个 RDS ?开源的 RDS 管控都出来了。那么这就变成了成本定价。既然是成本定价,我用云数据库加硬件,用这种开源的方案加上硬件20块钱一盒,1月不比这1000块钱或者400块钱的 RDS 要香吗?lower cost 这个事儿已经完全变味儿了,现在不是 lower cost 是 higher cost。 至于消除重复建设这个事,我认为现在开源干得已经比这好了,各家都有自己的 EC2 VPS,但是 K8s 很明显一统了这些无状态服务调度天下,所以我认为在2023年公有云的价值就剩下了全球化合规出海,它的适用光谱已经缩小到了小规模业务和高弹性业务和出海业务这三样。以前我们业界大概有一个规模估算,你在云上的年消费在100到300万这个区间,你就应该考虑下云了。我认为,随着资源云和开源平替的出现,100万-300万的阈值将会被进一步拉低至10万-30万或者1万-3万。我认为这件事很有可能会发生,而且正在发生。 如今上云还安全吗?稳定性有多强? 反方:马工 speaking —— 对于安全,我有很多话说。作为一个软件工程师最基本的是,你不能把密码直接写死、hard code 就编码到你的代码里面,更不能把它提交到 Github 上,这属于初级的实习生犯的错误对不对? 但是我看了一下,国内的腾讯云阿里云和华为云什么的,全都教用户把那个 Access ID,编码到代码里面。阿里云和腾讯云去年已经改正了,因为我写文章揭露他们。但是至今为止,华为云和火山引擎上面的范例里面还是页编码,Access key,这是非常不负责任的一个做法。 我为什么说他们是一个草台班子,因为这就相当于修了一座桥,然后把桥的地基给抽掉了,或者说做了一个保险库,但是把钥匙给插在那个锁上了。就这样还谈安全? 我们可以看一下更近的例子,滴滴出了事故影响了上千万的出行,它连具体的技术原因都没提出来,只说我们会改进、我们是一个内部系统。但这个内部系统是什么?你怎么改进这个系统?基础系统是外购的,还是自研的?什么都没说,但是北京那个地铁追尾,人家就成立调查组了,调查组就会有调查结论,就会有限期改正通知书,你得复盘,你得汇报,监管部门会过来检查,然后发通知给其他的地铁公司,让别人吸取他的教训。这才是一个真正的工程行业。 我赞同周新宇说的“云厂商的故障比自建机房的故障更令人瞩目”,但是,我的机房出故障,我可以得到最全面的信息,云厂商出故障,我得不到所有的信息,甚至他不给我信息。比如阿里云至今也没有披露技术细节。甚至有一些厂商,他是隐瞒故障,他不会跟你通知,而是想着偷偷修复了你就不知道了。这是一个非常让我们担心的问题:没有透明度。这样你也无法从业务上规避它,只能求它别出事了。这是非常危险的。 正方:周新宇 speaking —— 今天云厂商确实做得不够好,但这也是会改进的。云厂商在安全和数据完成这块都有很大的投入,我们以前做一个架构,要经过很多层的安全架构评审,并不是说啥都不做。当然,云计算这个技术也好,云计算这种商业模式也好,它肯定是有进步空间的,不能因为当前云厂商某些地方还不够成熟,就完全否定上云的优势或者是云计算的优势。 正方:蒋明 speaking —— 尤其是大规模的数据库用户,还是上云才能解决需求。像肯德基,阿里云出事以后,他们就把业务迁到了抖音建的云上,并没有迁到自己的机房里。 根据我的经验,自建机房的话,如果只是一两台机器托管一下的话,还是比较简单的。但是当机器达到四五千台的时候,那你就会遇到 CPU 的故障、内存的故障和磁盘的故障,这时候,你就会很依赖监控系统。我有过大概4000多台物理机的这种机房托管经验,当时用的是南京的管理系统,也是腾讯的开源软件,实时监控用的是阿里的监控软件 SLS,哪个磁盘出故障了,就发给线下的运维,让他去换。如果全部都是自建的话,根本就做不到。 像我们现在的政务云、水电煤背后的技术支持,全部都是在云上的,政府的政务处理系统,银行的交易系统也全部都在云上,如果云真的一无是处,那我们生活当中,支付宝就没办法扫了,钱也付不出去。 反方:冯若航 speaking —— 我觉得你说的问题非常严峻,就是所谓的云集中的问题。Gartner 最近发布的2023年三季度新兴风险报告里面,云集中风险已经连续第二年进入“五大新兴风险”综合榜单,在中国排第三位。云集中说的就是云厂商已成为了新的单点,爆炸半径极大。阿里云这一挂,有多少服务宕机了?如果政务云金融云这些都跑在阿里云,那挂了怎么办? 去年阿里云香港区域故障就导致香港政府很多单位的网站和电子政务不能用。 所以,这就是一个下云的重要 argument ——为了安全性和自主可控。上云其实就是放弃了自主可控,放弃了一部分的安全性。你没有办法对机密性、完整性去进行任何的验证,甚至是追索。 我是搞信息安全的,安全里面有三个点:CIA ——机密性,数据完整性和可用性。就是不丢,不坏,不宕。但是云厂商它不给你兜机密性和完整性,它只给你兜可用性,而且是非常逊色的可用性。 比如说云 RDS for PostgreSQL,这个云数据库的基础版甚至都没有望归档,也就是说它没有数据库 PITR 时间点恢复的能力,一个数据库服务竟然没有基础的时间点恢复能力,我认为这对于一个数据库用户来说是完全不可接受的。至于机密性,那就更没法保证了,你甚至没有办法去验证发现任何数据丢失。但要是自建机房,就不会存在这样的风险。 中小企业该怎么选? 正方:周新宇 speaking —— 如果是一家初创企业,那肯定是毫无疑问要上云,第一天就应该把你的 IT 设施构建在云上,这样未来的业务创新或试错成本都非常低。这是,如果你的企业已经在重度用云,今天因为成本的问题在考虑是否下云,那我觉得是要慎重的,因为不管是上云还是下云,折腾一次代价是比较大的。所以说更好的方式是去分析云上的账单,这里相对于自建有很大的优势:在云上你的一切价格账单都是透明的,到底贵在哪里,可以针对性地去降本。 正方:蒋明 speaking —— 可以用阿里云、腾讯云、AWS 的基础建设能力,比如 ECS 或 OSS,加上第三方的开源软件,比如 TiDB、AutoMQ,这些软件,它价格便宜,又能用云厂商最便宜的基础能力构建一个企业的自动化 DevOps 系统,让你用最低的成本,在云上创业。至于后面企业大了,像马斯克的 X 公司,那自建云是最佳的选择,毕竟成本会更低。 反方:冯若航 speaking —— 从务实的角度来讲,小企业其实是适合云的,但是你也不要把所有的东西都深度依赖云。 第一,你有自建能力,这是你跟云厂商谈价格折扣的最大筹码! 第二,优先使用资源云。什么是资源云?像租用它的虚拟机,尽可能的避免使用它的专有服务、被供应商锁定。 第三,如果你在云上非要用这些服务,请避免使用 AKSKIAM 这些让你陷入供应商锁定的东西。这些不仅会让你陷入云单点故障中,更是会把你绑在一个你下不来的账单下。 反方:马工 speaking —— 我觉得云是一个操作系统,你用云就相当于从 Windows 系统切换到 Linux,这需要非常大的努力,需要一个范式转移。这个转移路上,你必须要有人带着你走。但我目前看云自己也不知道怎么走。这有一个大胆的估测: 90%的云厂商的员工,没有自己的云账号; 90%的云厂商的员工,没有考过云的认证; 90%的云厂商的员工,从来没有在云上维护过或者部署过一个生产系统。 我觉得大家还是谨慎一点,除非云厂商能够证明云计算的价值点,然后给出一个很明确的路径,不然的话你就不要那么急躁地为了云而云。另外,腾讯会议或者钉钉那不是云,跟微信一样,只是一个很普通的 Saas 系统,这个跟你要负责任的企业 IT 系统是完全不一样的。 大家对此怎么看呢?快留言说说你的经验吧~ 直播回放如下,错过的赶紧扫码看看回放吧↓↓↓

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

2020年工控系统漏洞数量暴

根据Claroty最新发布的报告,2020年下半年披露的ICS漏洞同比增加了25%,比2020年上半年增加了33%。71%的已披露工业控制系统(ICS)漏洞可通过网络攻击媒介远程利用。 2020年上半年,共披露了59个供应商的449个ICS产品漏洞,其中70%的漏洞CVSS评分为高或“关键”,76%的漏洞不需要身份验证即可被利用。 Claroty研究副总裁Amir Preminger表示:“由于数字化转型,IT和OT网络的加速融合提高了ICS流程的效率,但也增加了对手可用的攻击面。” “国家黑客在研究工控系统网络外围的诸多方面,而网络犯罪分子也特别关注ICS流程,这强调了对安全技术的需求,例如在工业环境中基于网络的检测和安全的远程访问。令人振奋的是,网络安全行业对ICS的兴趣日益增长,研究者们希望更加清楚地了解这些漏洞,以便帮助企业远离威胁。” 关键基础设施行业中,制造业、能源、水处理和商业设施部门受2020年上半年披露的漏洞的影响最大,并且比前两年全面增加: 关键制造业比2019年上半年增长15%,比2018年上半年增长66%; 能源比2019年上半年增长8%,比2018年上半年增长74%; 自2019年上半年起,供水和废水处理漏洞增加了54%,与2018年上半年相比增加了63%; 商业设施较2019年上半年增加14%,较2018年上半年增加140%。 2020年披露的ICS漏洞数量与2018年相比增加了30%以上,与2019年相比增加了近25%。近年来,ICS漏洞数量飙升主要有两个因素,对ICS漏洞风险意识的增强,以及研究人员和供应商更加努力地识别和修复安全漏洞。这种增长同时也表明针对CS产品的安全研究逐渐成熟。 第三方研究人员贡献了61%的漏洞发现,其中许多是网络安全公司。这标志着重点已发生变化,将ICS与IT安全研究结合起来,进一步证明了IT与OT之间加速融合。在所有第三方发现中,有22家公司首次披露信息,这是ICS漏洞研究市场增长的积极信号。 【本文是51CTO专栏作者“安全牛”的原创文章,转载请通过安全牛(微信公众号id:gooann-sectv)获取授权】 戳这里,看该作者更多好文 【责任编辑:赵宁宁 TEL:(010)68476606】 点赞 0

资源下载

更多资源
Mario

Mario

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

腾讯云软件源

腾讯云软件源

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

Rocky Linux

Rocky Linux

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

Sublime Text

Sublime Text

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

用户登录
用户注册