Loading...

Tailwind.css的使用----基于nuxt.js

MILLEROS 发布于 2019-09-24

安装Tailwind

在终端项目文件夹下使用npm或yarn安装

npm install tailwindcss --save-dev
# or
yarn add -D tailwindcss

创建Tailwind配置文件(不是必须的)

在终端使用npx生成

npx tailwind init

成功后可以在项目根目录内看到tailwind.config.js

// tailwind.config.js
module.exports = {
  theme: {
    extend: {}
  },
  variants: {},
  plugins: []
}

引入tailwind

在assets/css内创建tailwind.css文件,并引入

@tailwind base;

@tailwind components;

@tailwind utilities;

在nuxt.config.js内配置postcss

首先我们要在nuxt.config.js内引入path模块

const path = require('path');

然后配置postcss

// 引入我们刚刚创建的tailwind.css
css: [
    '~assets/css/tailwind.css', 
],
// 配置postcss
build: {
    postcss: {
      plugins: {
        tailwindcss: path.resolve(__dirname, './tailwind.config.js')
      }
    }
}

使用

<div class="bg-purple text-white sm:bg-green md:bg-blue md:text-yellow lg:bg-red xl:bg-orange">
  Test
</div>

参考资料

继续阅读

勇于承认自己的不足和错误

MILLEROS 发布于 2019-08-28

“一个有勇气承认自己错误的人,也可以得到某种满足感。这不仅是消除罪恶感和自我辩护的气氛,而且有利于解决实质性问题。” ----《卡耐基沟通的艺术与处世智慧》

我记得从很小的时候开始,我就比较喜欢与人争论错误。

在我大致四五岁时,坊间流行养蚕,一只只肥肥的蚕虫是当时和小伙伴课余时间作对比最大的乐趣。但是当时家里经济困难,我自己手上是没有蚕儿的,所以那时候特别喜欢去邻居家玩耍,当时小伙伴家放在茶几上的蚕卵,一颗一颗的产在纸板上,听小伙伴说里面有些都是坏的,随即用手去挤破某些蚕卵;当时的我比较不懂事,也跟着挤破。后来我回到家被小伙伴的哥要求赔偿了,当时的我根本就不承认这事是我干的,以至于在后面很长一段时间内我都是上学被堵路的。

现在想起,虽然我还是很气愤当时小伙伴哥哥的做法,但是确实是我自己的问题,如果我当时用于承认错误,情景会不会是另一番呢。不得而知。

后面慢慢长大其实我有了一些对自己的思考,当时从深圳回去老家读高三的时候,在回去前在我心里萦绕许久的不是如何适应老家的高三生活,而是怎么向他们展示“大城市”出来的孩子的品行,这样的行为让现在的我很不齿,但是得承认在那段时间内,这样的想法对我的改变确实很大---有可能并没有在学业上有什么帮助。

在高三的那个班级里,有个同学很喜欢向他人炫耀自己所拥有的东西,以至于大家都很不喜欢和他玩耍,毕竟在那时候的环境里,大家都是家境不太好,要么是留守儿童,要么就是真的家庭困难的。他这样的做法很让人反感,台下就没安静过。 后来有次办理举行元旦晚会的安排,他因为是班干部的原因,被要求上台说两句话。大家其实很不情愿,毕竟大家当时都不喜欢他。 “在过去的一段时间里,我都比较倾向于向人展示自己的东西,这段时间我考虑了很多,这确实是我的问题。” 台下的人纷纷应和,我看到台上的他其实是有点尴尬的。后来下台我问他为什么要这么做,他只说了一句话: “说出来的感觉太好了,我憋了很久,原来承认自己的不足,看到大家了解了更多的我,我很高兴。”

这完全不是我编写的故事,试想一下,如果当时他还是照例讲一些希望大家和谐相处的意见,或者是指责大家对他的偏见,那样一点好处都没有,只能是当时愉快了一下自己---当众打那些不喜欢我的人的脸,多爽啊。因为这个事情确实也让我有了很多的想法。

在上大学期间,我比较喜欢自己安安静静捣鼓东西,而且当时我有一个怪癖---如果有人打扰到我做事情,我会毫不犹豫的对他倾泻我的怒火,通常是很短很暴躁的怒火,当然伤害也是很大的。

有一次我正在做网站建设方面的工作,我感到我的身后突然被拍了一下,一瞬间我的怒火就来了,但是我忍住了,但是又来了一下,我顿时就忍不住了,转身就是一顿臭骂,后面发现是我的同学来找我去玩,他很严肃的和我说“你就是喜欢这样随意发泄你的怒火,我就找你玩一下,并没有打扰你工作的意思,你就乱扣帽子”,那一次之后的很长一段时间,那个同学都没有来找过我了,我一直在反思自己,我一开始觉得就是他的问题,他不该那样打扰我,我发泄自己的情绪一点问题都没有。

在后面的一次课上,班级很多人迟到,任课老师在课堂上大发雷霆,大家都不敢说话,但是很多人在群里说老师大题小做,或者是其他一些话指责老师不对。后来老师气消了,大大方方的承认自己的问题所在,并保证后面不再这样了,并且向同学们说明迟到确实不好。然后我看到群里开始出现了很多人对老师的理解,并且课堂上同学们也表现出了更大的积极性,后面的课堂也极少出现迟到的情况了。

我说这个事情不是想说明“打一巴掌给颗糖有奇效”的战略奇效。我是想通过我的做法和老师的做法做一个对比。试想一下如果当时老师和我的做法一样,死不承认自己的问题,把所有的责任都推到对方身上,那样同学们只会越来越厌恶这个老师。而且迟到的问题也根本不会得到解决。

一个有勇气承认自己错误的人,也可以得到某种满足感。这不仅是消除罪恶感和自我辩护的气氛,而且有利于解决实质性问题。

前一段时间在网络上闹得沸沸扬扬的cxk事件,大致情况是一个“奶油练习生cxk”成为了NBA形象大使,这让很多篮球爱好者都很愤慨,觉得这是对篮球事业的侮辱,篮球事业不能被一个“娘炮”所代表。这件事情随后蔓延到全网络,越来越多人加入了对cxk的口诛笔伐,从而衍生出了一个流行词汇“鸡你太美”,而在这个网络舆论下,cxk背后的团队居然还对当时的某个网站发出了律师函,觉得这一切的负面问题都是这些网站传播导致的。这一操作再次引爆了全网络。

与之对比的是,当时的另一个明星wyf,原先也是流量起家,因为“大碗宽面”的rap,而被全网人做成了鬼畜视频。他并没有像cxk这般到处律师函,而是发布了单曲《大碗宽面》,来自嘲这件事。

其实这两个例子不能说是承认自己的错误,只能说是认识自己的不足,并且接受大众的指责和不满,但是这两手不同的操作确实导致了两种不同的效果---而且是差距如此之大。

“用争斗的方法,你永远不会得到满足;但用让步的方法,你的收获将比你期望的更多。”这个道理人人都懂,只是实行起来往往会因为我们的瞬时情绪以及其他一些看法而造成困难。但是不管如何,积极承认自己的错误、不足只会给你加分。

继续阅读

outlook 已知的兼容问题

MILLEROS 发布于 2019-08-27

OutLook的宣言是不离不弃

是不是很讨厌为Email代码兼容Outlook? 太遗憾了!虽然光都有尽头,但对Outlook的代码兼容没有。

为了应付Email的怪癖,我们花了很多时间测试,确保我们搞定了所有Outlook的坑洼沟洄。在这个指导中,我们会分享一下数年来我们应付这种烦人的邮件客户端的编程经验,主要包括四个部分:

  1. Outlook必知的17个tricks
  2. 移除table间距的3种办法
  3. 移除Outlook2013图片间距
  4. Outlook 2007、2010中的CSS padding

1. Outlook必知的17个tricks

为Outlook设计就像追逐行踪飘忽的大白鲸。我们花费了数小时研究,跟踪缺陷和故障,努力在一个不完善的环境中实现像素级别的完美展示。虽然Outlook时最难啃的骨头,但是搞定它仍是当务之急。

下面是17条改善Outlook上Email的建议:

1.1 忽略图片的margin和padding

Outlook 2007-2013不支持image的margin与padding样式,必要的时候尝试hspacevspace

<img src="http://www.emailonacid.com/example.jpg" align="left" vspace="10" hspace="10" />

或者直接在图片内预留好间隙(要是遇到屏幕适配就麻烦大了)。

1.2 文本不自动折行

对于table中的文字,比如 aaaaaaaaaaaaaaaaa ,如果希望它们自动折行,需要这样

<td style="word-break:break-all;">

1.3 Outlook自动为table cell 添加1px border

如果table使用了背景颜色可以看到td有1px的白边,在内嵌的style中加入样式

table td { border-collapse: collapse; }

或者使用内联样式

<td style="border-collapse: collapse;">... </td>

1.4 Outlook有时忽略link的样式

如果a标签没有href属性,那么Outlook 2007和 2013将不支持其内联样式

<a style="font-size: 20px; color: #004990;">Shop Flooring </a>

这种没有作用,应该修改为

<a href="http://www.test.com" style="font-size: 20px; color: #004990;">Shop Flooring </a>

1.5 用table实现重要的间距

Email中最安全的呈现间距方式就是使用table

<table cellpadding="2" cellspacing="2" border="0">
    <tr>
        <td valign="top">•</td>
        <td>Test</td>
    </tr>
    <tr>
        <td valign="top">1.</td>
        <td>Test</td>
    </tr>
</table>

1.6 使用cellpadding和cellspacing做间距

不要在table上使用margin和padding,cellpadding和cellspacing是比较安全的方式,可以达到padding和margin的效果

如果使用table的align属性

<table align="left">

情况会有些小复杂,后续提到

注意;对table的对齐属性要额外的小心,你永远不知道这会对其渲染引擎(Word rending engine)造成什么影响。因为它会有可能使用自己的定位,而不是用我们设定的对齐属性

1.7 Outlook有时会移除padding

Outlook 2007 和 2010 会把div和h1~h6转换为p并包裹上span,这个会把html的容器从块元素转换为内联元素

<h2 style="font-size: 15px; font-weight: bold; margin: 0; padding: 17px 0 0 0;">
    NEW FASHIONS
</h2>

会被解析为

<p class= style='mso-outline-level:3'> 
    <b>
        <span style='font-size:11.5pt'>
            NEW FASHIONS<o:p></o:p>
        </span>
    </b> 
</p>

一个解决办法就是把padding加到td上

<td style="padding: 17px 0 0 0">
    <h2 style="font-size: 15px; font-weight: bold; margin: 0">
        NEWFASHIONS
    </h2>
</td>

另外一个解决办法就是不使用div和h1~h6,使用p

1.8 使用 MSO 控制行高

在使用行高前添加mso-line-height-rule:exactly

<td style="mso-line-height-rule:exactly; line-height:50px;">

这只在块元素上有效,所以想在font或span中用就洗洗睡了吧,这只是微软的CSS属性,对其他客户端无效

1.9 Outlook按字面解释

确保td的rowspan属性 被设置了正确的值,大部分浏览器对错误设着的rowspan宽大处理了,但是Outlook 07 和10 会按照你声明的值解释渲染,如果声明多了就凉了,即使table中没有那么多也一样渲染

1.10 Outlook有时忽略width和height

带有width和height的块元素可能显示和预期不一致,如果水平和垂直空间时由email内容决定的,但是有时候自定义的空间和对齐不好使,所以为了最好效果,可以使用透明的图片占位或者table cell的height属性来设置宽高。

1.11 td有最矮2px的限制

在Outlook 2007和2010中,td最小高度被限制为2px,如果使用td里面是1px的透明gif和背景颜色,还会出现一个水平条

1.12 背景图片需要使用vml

在Outlook 2007、2010、2013中背景图片不使用VML的话是不起作用的。VML是一个基于XML的过时的二维矢量图形文件格式,是Office Open XML标准的一部分。这个问题很难搞定,可以看看这篇博客看看这么使用VML搞定背景图片

也可以使用Bulletproof background images搞定。

1.13 Outlook把一些div转换为p

Outlook经常会把div转换为p,这个我们真心不理解什么时候和为什么,貌似是个误无解的问题。

1.14 Outlook使用word渲染引擎

Outlook使用text boundaries 和page breaks略微有些不同,Text boundaries被用来在打印页面的时候分离页面元素。并不输出HTML代码

根据我们研究,我们了解到text boundaries 最大可以拉伸到23.7英寸高(1790px),超过了就会自动插入分割线然后创建新的text boundary。因此如果email中的table的高度高于1790px,它就会变成多个

最好的解决办法就是不要搞这么高的table,每次添加新的table都会创建新的text boundry,只要table不超过1790px就不会创建多个text boundry

使用table嵌套的时候要注意内层的table别超过了,适当拆分一下,重构比较麻烦,设计的时候注意一下最好。

1.15 太高的图片会被自动剪切

在Outlook中图片最高1728px,超过1728 的部分会被截掉(怎么会有人这么丧心病狂在Email中放这么高的图片)

我们也见过Outlook自动缩小图片,使其最高位1827px

我们建议你剪裁图片,堆叠在一起

1.16 定义了尺寸的图片可能渲染不正确

拉伸图片可能不会正确的渲染,所有的图形应该使用属性来设置宽高大小。不要依赖于CSS定义图片尺寸,这点对于Email很重要

<!--有效-->
<img src="cid:ALogo" width="500" height="200">

<!--无效-->
<img src="cid:ALogo" style="width: 500px;height: 200px">

<!--无效-->
<img src="cid:ALogo" width="500px" height="200px">

1.17 带动画的gif不支持

Outlook不支持带动画的gif,只会展示第一帧,这也太TM丧心病狂了,gif图都不支持啊!!!!

2. 移除table间距的3种办法

当我们尝试左右并列table或者上下堆叠table的时候会发现Outlook 2007 和 2010 有些小问题,如果发现不想要的间距,可以看看下面的方法

步骤1 在css中添加border-collapse属性

<style type="text/css">
    table {border-collapse: collapse;}
</style>

步骤2 border、cellpadding、cellspacing设为0

<table border="0" cellpadding="0" cellspacing="0">

步骤3 如果为table添加了align属性则还需要做些事情 在这种情况下,可能会发现table间十分大的间距,下面说一下解决办法

  1. 为td添加bgcolor属性,根据你的布局选颜色
  2. 为table添加1px的border,颜色和td一样
  3. 为了适应border,减少table的宽度2px
  4. 把第一个td的内容用p包裹,添加样式mso-table-lspace:0;mso-table-rspace:0;
<style type="text/css">
table {
    border-collapse: collapse;
}
</style>
<table border="0" width="600" cellspacing="0" cellpadding="0">
    <tr>
        <td>
            <table align="left" width="198" border="0" cellpadding="0" cellspacing="0" style="border:1px solid #00CC99">
                <tr>
                    <td bgcolor="#00CC99">
                        <p style="mso-table-lspace:0;mso-table-rspace:0;">Content in 1</p>
                    </td>
                </tr>
                <tr>
                    <td bgcolor="#00CC99">Content in 1</td>
                </tr>
            </table>
            <table align="left" width="198" border="0" cellpadding="0" cellspacing="0" style="border:1px solid #33FFFF">
                <tr>
                    <td bgcolor="#33FFFF">
                        <p style="mso-table-lspace:0;mso-table-rspace:0;">Content in 2</p>
                    </td>
                </tr>
                <tr>
                    <td bgcolor="#33FFFF">Content in 2</td>
                </tr>
            </table>
            <table align="left" width="198" border="0" cellpadding="0" cellspacing="0" style="border:1px solid #993366">
                <tr>
                    <td bgcolor="#993366">
                        <p style="mso-table-lspace:0;mso-table-rspace:0;">Content in 3</p>
                    </td>
                </tr>
                <tr>
                    <td bgcolor="#993366">Content in 3</td>
                </tr>
            </table>
        </td>
    </tr>
</table>

关于这种方法的说明

这个问题只会在在table的align被设置为left|right的时候发生 只需要把第一个td的内容用p包裹 如果table cells有不同颜色就得再套一层table了 这种方法对于image堆叠不起作用,最好把图片做成一个这种情况

3. 移除Outlook2013图片间距

Outlook2013填了一些坑,但又挖了新坑。堆叠的图片会显示大概10px的间距(WDNMD 这简直就是巨坑啊)。

这个问题只在图片高度小于20px的时候出现,开心的是通过简单的小技巧就可以解决:为td设置和图片高度一样的行高

虽然作者说不知道为什么,我们可以大胆猜测一下,image时行内元素,Outlook为td设置了1.3之类的行高,这样上下就会有间距,设了行高就行了,也有人提到行高设为0或者image设为display:block都能解决

<td width="600" height="80" style="line-height: 80px;">
    <img height="80" src="http://www.website.com/images/Nature_01.jpg" width="600" />
</td>

4. Outlook 2007、2010中的CSS padding

针对Outlook设计的人都很清楚已经被table绑架了,只能用很少的css,如果认为仅仅是这样,那你就太年轻了,Outlook会把你的代码转换为微软的word代码。

比如说很重要的就是Outlook经常去掉div和h1~h6,换为p和span,有时候保留外部的container有时候删除

<h2 style="font-size: 15px; font-weight: bold; margin: 0; padding: 17px 0 0 0;">NEW FASHIONS</h2>
<div style="font-size: 15px; font-weight: bold; margin: 0; padding: 17px 0 0 0;">NEW FASHIONS</div>
<p style="font-size: 15px; font-weight: bold; margin: 0; padding: 17px 0 0 0;">NEW FASHIONS</p>

上面的代码被更换为

<style>
p.MsoNormal,
li.MsoNormal,
div.MsoNormal {
    mso-style-unhide: no;
    mso-style-qformat: yes;
    mso-style-parent: "";
    margin: 0in;
    margin-bottom: .0001pt;
    mso-pagination: widow-orphan;
    font-size: 12.0pt;
    font-family: "Times New Roman", "serif";
    mso-fareast-font-family: "Times New Roman";
    mso-fareast-theme-font: minor-fareast;
}

h2 {
    mso-style-priority: 9;
    mso-style-unhide: no;
    mso-style-qformat: yes;
    mso-style-link: "Heading 2 Char";
    mso-margin-top-alt: auto;
    margin-right: 0in;
    mso-margin-bottom-alt: auto;
    margin-left: 0in;
    mso-pagination: widow-orphan;
    mso-outline-level: 2;
    font-size: 18.0pt;
    font-family: "Times New Roman", "serif";
    mso-fareast-font-family: "Times New Roman";
    mso-fareast-theme-font: minor-fareast;
}

p {
    mso-style-noshow: yes;
    mso-style-priority: 99;
    mso-margin-top-alt: auto;
    margin-right: 0in;
    mso-margin-bottom-alt: auto;
    margin-left: 0in;
    mso-pagination: widow-orphan;
    font-size: 12.0pt;
    font-family: "Times New Roman", "serif";
    mso-fareast-font-family: "Times New Roman";
    mso-fareast-theme-font: minor-fareast;
}
</style>
<h2 style='margin:0in;margin-bottom:.0001pt'>
    <span style='font-size:11.5pt; mso-fareast-font-family:"Times New Roman"'> NEW FASHIONS
        <o:p></o:p>
    </span>
</h2>
<div>
    <p class=MsoNormal>
        <b>
            <span style='font-size:11.5pt;mso-fareast-font-family:"Times New Roman"'>
                NEW FASHIONS
                <o:p></o:p>
            </span> 
        </b>
    </p>
</div>
<p style='margin:0in;margin-bottom:.0001pt'> 
    <b>
        <span style='font-size:11.5pt'> NEW FASHIONS
            <o:p></o:p>
        </span>
    </b>
</p>

反正我是没眼看了(小声哔哔)。

转换之后本来对块元素设置的css到了span上,很多就不好使了,为了避免作者建议使用margin,其实我觉得还是老老实实使用table得了

原文地址: https://www.emailonacid.com/blog/article/email-development/tips_and_tricks_outlook_07-13/

继续阅读

[前端查漏补缺01]什么是CSS-BFC?

MILLEROS 发布于 2019-08-19

前言

最近在忙着刷面试题,看到了很多未曾接触过的概念,今天接触到了一个叫做BFC的概念,想想工作一年多了,才知道这个概念,挺丢人的,所以抓紧研究一下这个叫做BFC的东西到底是什么。

好了,废话不多说,进入正题。

目录

BFC介绍

BFC就是css布局的一个概念,是一块区域,一个环境。按照我的理解,我把它理解成JS里面的作用域,但是又不完全相同。反正就是一个区域的划分。

这说的有点让人懵逼,我们接着往下看。

BFC(Block formatting context)直译为"块级格式化上下文"。它是一个独立的渲染区域,只有Block-level box(我们平常说的盒子模型)参与, 它规定了内部的Block-level Box如何布局,并且与这个区域外部毫不相干

我们常说的文档流其实分为定位流、浮动流和普通流三种。而普通流其实就是指BFC中的FC。

FC是formatting context的首字母缩写,直译过来是格式化上下文,它是页面中的一块渲染区域,有一套渲染规则,决定了其子元素如何布局,以及和其他元素之间的关系和作用。 常见的FC有BFC、IFC(行级格式化上下文),还有GFC(网格布局格式化上下文)和FFC(自适应格式化上下文),这里就不再展开了。

通俗一点的方式解释:

BFC 可以简单的理解为元素具有一套默认的渲染规则(属性),且这个属性(规则)不能被使用这个语言的开发者修改,使用了这种渲染规则(属性)的元素对外会表现出特定的渲染方式。

BFC的主要内容

触发条件

满足下列条件之一就可触发BFC:

  1. 根元素,即HTML元素
  2. float的值不为none
  3. overflow的值不为visible
  4. display的值为inline-block、table-cell、table-caption
  5. position的值为absolute或fixed

布局规则

  1. 内部的Box会在垂直方向,一个接一个地放置。
  2. Box垂直方向的距离由margin决定。属于同一个BFC的两个相邻Box的margin会发生重叠
  3. 每个元素的margin box的左边, 与包含块border box的左边相接触(对于从左往右的格式化,否则相反)。即使存在浮动也是如此。
  4. BFC的区域不会与float box重叠。
  5. BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此。
  6. 计算BFC的高度时,浮动元素也参与计算

作用

  1. 自适应两栏布局
  2. 可以阻止元素被浮动元素覆盖
  3. 可以包含浮动元素——清除内部浮动
  4. 分属于不同的BFC时可以阻止margin重叠

BFC规则解析

内部的Box会在垂直方向,一个接一个地放置

上文定义中提到过的块级盒:block-level box,在这里解析一波: block-level-box

我们平常说的盒子是由margin、border、padding、content组成的,实际上每种类型的四条边定义了一个盒子,分别是分别是content box、padding box、border box、margin box,这四种类型的盒子一直存在,即使他们的值为0.决定块盒在包含块中与相邻块盒的垂直间距的便是margin-box。

提示:Box之间的距离虽然也可以使用padding来控制,但是此时实际上还是属于box内部里面,而且使用padding来控制的话就不能再使用border属性了。

布局规则1就是我们平常div一行一行块级放置的样式,大家想一下就知道了,这里就不展开了。

Box垂直方向的距离由margin决定。属于同一个BFC的两个相邻Box的margin会发生重叠

规则2

上文提到过,决定块盒在包含块中与相邻块盒的垂直间距的便是margin-box。,上面的栗子就是这种情况。 演示中css属性设置:上面的box:margin-bottom: 100px;下面的box:margin-top: 100px;(他们是同一侧的margin,所以会发生margin重叠的情况,两个div的距离实际上只有100px。)

阻止margin重叠

当两个相邻块级子元素分属于不同的BFC时可以阻止margin重叠 操作方法:给其中一个div外面包一个div,然后通过触发外面这个div的BFC,就可以阻止这两个div的margin重叠

<div class="aside"></div>
<div class="text">
    <div class="main"></div>
</div>
<style>
.aside {
    margin-bottom: 100px;
    /*margin属性*/
    width: 100px;
    height: 150px;
    background: #f66;
}

.main {
    margin-top: 100px;
    /*margin属性*/
    height: 200px;
    background: #fcc;
}

.text {
    /*盒子main的外面包一个div,通过改变此div的属性使两个盒子分属于两个不同的BFC,以此来阻止margin重叠*/
    overflow: hidden;
    /*此时已经触发了BFC属性。*/
}
</style>

阻止margin重叠

每个元素的margin box的左边, 与包含块border box的左边相接触(对于从左往右的格式化,否则相反)。即使存在浮动也是如此

<div class="par">
    <div class="child"></div>
    <!-- 给这两个子div加浮动,浮动的结果,如果没有清除浮动的话,父div不会将下面两个div包裹,但还是在父div的范围之内。 -->
    <div class="child"></div>
</div>

解析:给这两个子div加浮动,浮动的结果,如果没有清除浮动的话,父div不会将下面两个div包裹,但还是在父div的范围之内,左浮是子div的左边接触父div的borderbox的左边,右浮是子div接触父div的borderbox右边,除非设置margin来撑开距离,否则一直是这个规则。

可以包含浮动元素——清除内部浮动

给父divpar加上 overflow: hidden;

清除浮动原理:触发父div的BFC属性,使下面的子div都处在父div的同一个BFC区域之内,此时已成功清除浮动。

清除浮动

还可以向同一个方向浮动来达到清除浮动的目的,清除浮动的原理是两个div都位于同一个浮动的BFC区域之中。

BFC的区域不会与float box重叠

<div class="aside"></div>
<div class="text">
    <div class="main"></div>
</div>
<style>
.aside {
    width: 100px;
    height: 150px;
    float: left;
    background: #f66;
}

.main {
    height: 200px;
    overflow: hidden;
    /*触发main盒子的BFC*/
    background: #fcc;
}

.text {
    width: 500px;
}
</style>

上面aside盒子有一个浮动属性,覆盖了main盒子的内容,main盒子没有清除aside盒子的浮动。只做了一个动作,就是触发自身的BFC,然后就不再被aside盒子覆盖了。所以BFC的区域不会与float box重叠

float box

自适应两栏布局

自适应两栏布局

还是上面的代码,此时BFC的区域不会与float box重叠,因此会根据包含块(父div)的宽度,和aside的宽度,自适应宽度。

BFC 与 Layout

IE 作为浏览器中的奇葩,当然不可能按部就班的支持 BFC 标准,于是乎 IE 中有了 Layout 这个东西。Layout 和 BFC 基本是等价的,为了处理 IE 的兼容性,在需要触发 BFC 时,我们除了需要用触发条件中的 CSS 属性来触发 BFC,还需要针对 IE 浏览器使用 zoom: 1 来触发 IE 浏览器的 Layout。

有趣的文本:

.par {
    margin-top: 3rem;
    border: 5px solid #fcc;
    width: 300px;
}

.child {
    border: 5px solid #f66;
    width: 100px;
    height: 100px;
    float: left;
}

环绕文字

这里两个div被撑开,是因为父div被p标签撑开了,并不是因为清除浮动的原因,从下面这张图片可以清楚的知道。

焕然文字

其实以上的几个例子都体现了BFC布局规则第五条————

BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此

文本环绕float

<div style="float: left; width: 100px; height: 100px; background: #000;">
</div>
<div style="height: 200px; background: #AAA;">
    <div style=" width: 30px; height: 30px; background: red;"></div>
    <p>content</p>
    <p>content</p>
    <p>content</p>
    <p>content</p>
    <p>content</p>
</div>

文本环绕

问题:为什么 div 的左上角被覆盖了,而文本却没有被覆盖,float不是应该跟普通流不在一个层级吗?是因为float属性不生效吗?

解决:

float的定义和用法:

float 属性定义元素在哪个方向浮动。以往这个属性总应用于图像,使文本围绕在图像周围,不过在 CSS 中,任何元素都可以浮动。浮动元素会生成一个块级框,而不论它本身是何种元素。

解决

从上图可以看到,float属性确实生效,将float隐藏后,下面还有一个红色的div,这个div是被黑色div所覆盖掉的。div会被float覆盖,而文本却没有被float覆盖,是因为float当初设计的时候就是为了使文本围绕在浮动对象的周围

总结

其实说起来BFC在我们的日常开发中经常会遇到,只是我们没有去总结而已。当然总结并且熟练运用这些概念,可以让我们在编写代码是更加的高效。

参考转载: 布局概念-关于CSS-BFC深入理解

继续阅读

使用NodeJS爬取知乎热榜

MILLEROS 发布于 2019-08-01

之前用Python写过一些小爬虫,但是作为前端,我还是对Nodejs比较熟悉,由于最近大量的APP下架,想看很多咨询都看不来,遂便想着利用Nodejs写一个爬虫,爬取我想要看的网站的热门内容。

本文所述的内容需建立在您对Nodejs有一定了解,能独立编写一些简单的请求响应功能,高级部分则要求更高

废话不多说,具体流程请看下面

1. 准备工作

在工作开始之前我们需要准备以下几个条件:

1.1 基础软件

  • Nodejs v8.9.3
  • npm v6.7.0

以上软件的安装可以查看Nodejs官网

1.2 npm软件包

  • koa //Nodejs的框架,和express类似
  • iconv-lite //用来转换网站字符编码
  • axios //用来请求目标网站/接口
  • cheerio //用来解析目标网站返回的html页面,使得可以在服务端类似客户端用jQuery操作DOM一样操作解析后的html元素

关于以上的软件包具体的使用方法可以去google搜索官方文档查看

2. 开始

2.1 安装依赖

首先我们建立一个项目文件夹NodejsFetchZhihu.com

$ mkdir NodejsFetchZhihu.com
$ cd NodejsFetchZhihu.com/

$ npm init -y //初始化此文件夹为Nodejs项目文件夹,生成一个package.json 文件

$ npm install koa iconv-lite axios cheerio --save //安装1.2内的依赖

2.2 建立http服务

新建文件server.js

const Koa = require('koa');
const app = new Koa();

app.use(async ctx => { //此拦截器表示所有请求进来我们都返回hello World
  ctx.body = 'Hello World';
});

console.log('站点建立在http://localhost:3000上');
app.listen(3000);

2.3 运行

到这里,我们已经初步建立了一个可访问的http站点

$ node server.js //运行
站点建立在http://localhost:3000上

访问 http://localhost:3000 我们就可以看到页面返回了hello World

hello world

3. 爬取

开始前我们要先打探好我们要爬取的网页的地址,例如我们此刻要爬取的是知乎热榜,那么可以从下图看到知乎热榜的页面地址是 https://www.zhihu.com/hot 爬取

3.1 修改中间件并爬取

我们刚刚server.js设定的所有请求都会直接返回“hello world”,现在将它修改成请求知乎热榜页面

此处我们使用axios请求目标地址,具体文档请参考axios官网

const Koa = require('koa');
const axios = require('axios');
const app = new Koa();

app.use(async ctx => { //此拦截器表示所有请求进来我们都返回hello World
    let _res = await axios.get('https://www.zhihu.com/hot').then((res) => {
        return res
    });

    ctx.body = JSON.stringify(_res.data);
});

console.log('站点建立在http://localhost:3000上');
app.listen(3000);

运行后我们可以看到我们的站点返回了以下内容 返回结果

我们可以看到返回结果里面有一些登录字样,这说明知乎热榜是需要登录才能查看的,所以这里我们需要设定以下axios的请求头

我们将代码修改成下面所示

const Koa = require('koa');
const axios = require('axios');
const app = new Koa();

app.use(async ctx => { //此拦截器表示所有请求进来我们都返回hello World
    let _headers = {};
    _headers.cookie = '你的cookie';

    let _res = await axios.get('https://www.zhihu.com/hot', { headers: _headers }).then((res) => {
        return res
    });

    ctx.body = JSON.stringify(_res.data);
});

console.log('站点建立在http://localhost:3000上');
app.listen(3000);

这里涉及到一个概念,就是网站cookie,但是在这我就不赘述这个是什么,以及原理,我们只需要知道这个cookie是用来记录我们是否登录了zhihu.com的标记。

那么怎么获取cookie呢?这里我们首先打开并登录https://www.zhihu.com/hot 然后打开开发者工具,切换到Network面板,随便点击一个请求(如果没有则刷新一下页面),在右侧的headers里面往下翻可以看到我们的cookie cookie 在哪里

复制这段cookie,粘贴到代码里面的“你的cookie”处,然后保存运行。

返回结果

我们可以看到,这里的红线处已经有了知乎热榜上的内容了

3.2 解析网页结果

首先我们先分析知乎热榜的页面结构:

知乎热榜页面结构

从上图我们可以看到,知乎热榜,用来显示热榜数据的div类名叫做HotList-list,单个知乎热榜信息的条目类名是HotItem,所以我们先获取条目列表

const cheerio = require("cheerio"); //用于解析html文本
//取基本数据
const _list = $('html').find('HotList-list');

然后遍历条目,假设这里我们有需求取知乎热榜记录中的标题和热度

let _dataTmp = []; //下发到客户端的数据
await _list.each((_index, _el) => {
    const _item = $(_el);

    let _itemTmp = {}; //存储类目值

    let _title = _item.find('.HotItem-title').text().replace(/[\r\n]/g, ''); //标题
    let _Hot = _item.find('.HotItem-metrics').text().replace(/[\r\n]/g, ''); //热度

    _itemTmp = {
        title: _title,
        hot: _Hot
    };

    _dataTmp[_index] = _itemTmp; //下发到客户端的数据
});

查看完整代码

const Koa = require('koa');
const axios = require('axios');
const cheerio = require("cheerio");
const app = new Koa();

app.use(async ctx => { //此拦截器表示所有请求进来我们都返回hello World
    let _headers = {};
    _headers.cookie = '_zap=0b7d5fde-a8b3-4d26-93d6-292ee4aa6488; d_c0="AKDnag8Xcg6PTg2vbEVtmCLLSwxAuwaqJKw=|1540954624"; _xsrf=a5bf2345-7d9d-4e96-82bf-02c7dcb4c9f2; l_n_c=1; q_c1=a47cb58a45fc4185a896490e8ffbd9a1|1563335283000|1546479842000; n_c=1; client_id="MzE2OTQ2Njc0Mw==|1563335331|411d415eb2357dc431e6aa3d30f5a4e8946c467a"; accountcallback=%7B%22status%22%3A%22new%22%2C%22fullname%22%3A%22MillerOS%22%2C%22type%22%3A%22sina%22%7D; tst=h; tshl=; l_cap_id="N2U0ZTNhMjQ3MzIzNGJjNjk1YWQ3OGI1YmRjMThkZWI=|1563516259|3bb9087cb4582ced6da3bf6c94c8b1c4f326e234"; r_cap_id="YzMxYzNiYTg4NzY4NDFlODk1MGY4NTExYmVmNWVlYjE=|1563516259|704737df69afd1df6913062aeb7fdf7155a387ab"; cap_id="MDYzZWIyNzI2ZGIxNDgzZjgzZTcyYTZiNmIyMDg0ZWQ=|1563516259|522c5e94cde2b2e6e4bdbdb5516d26d8e938c89b"; capsion_ticket="2|1:0|10:1563516306|14:capsion_ticket|44:ZjExOTE5NWYyYmQyNDZkMTg2YWIxZjBlOGFhYTJhZmY=|1969f6465d2e739d0d865b28577abcee45b3c54964bec458122582b7a837f8f0"; z_c0="2|1:0|10:1563516317|4:z_c0|92:Mi4xdkUwSUNBQUFBQUFBb09kcUR4ZHlEaVlBQUFCZ0FsVk5uYXNlWGdEYmg5bjJjeGNpZmFMandXVmxHdm9CSW5HN1lR|1b4b01dbfd08aba411b467447ca18ed7334e0a1d2a5e5fb7cd0e1032fa7cc473"; tgw_l7_route=060f637cd101836814f6c53316f73463';

    let _res = await axios.get('https://www.zhihu.com/hot', { headers: _headers }).then(async (res) => {
        const $ = cheerio.load(res.data); //格式化数据

        //取基本数据
        const _list = $('html').find('.HotList-list .HotItem');

        let _dataTmp = []; //下发到客户端的数据
        await _list.each((_index, _el) => {
            const _item = $(_el);

            let _itemTmp = {}; //存储类目值

            let _title = _item.find('.HotItem-title').text().replace(/[\r\n]/g, ''); //标题
            let _Hot = _item.find('.HotItem-metrics').text().replace(/[\r\n]/g, ''); //热度

            _itemTmp = {
                title: _title,
                hot: _Hot
            };

            _dataTmp[_index] = _itemTmp; //下发到客户端的数据
        });

        return _dataTmp
    });

    ctx.body = JSON.stringify(_res);
});

console.log('站点建立在http://localhost:3000上');
app.listen(3000);

保存并运行,查看结果 运行结果

可以看到,在这里我们已经获取到了知乎热榜内的数据,那么拥有这些数据,我们就可以放在自己的网站上,轻松的查看这些内容,亦或者是发送到自己的邮箱。

4. 结语

关于爬虫的学问还有非常深入的,爬虫作为当今领域不可或缺的角色,不管是搜索引擎,还是资讯app,很多地方都又这爬虫的身影,作为这个时代的开发者,我们编写爬虫的时候要注意几个问题

4.1 合法性

不爬取存储触犯法律网站的内容,且如果是需要另外展示出来,最好注明一下内容的来源,某些明确不让爬取的网页就不要爬取了。敏感信息自己看看就行,不要呈现给他人。

4.2 资源

爬取内容时应该注意频率,不能高频检索目标网站,这样不仅会造成目标网站的资源被大大的浪费,而且我们的爬虫也极其容易被目标网站限制

4.3 优质性

如果我们的爬虫爬取的数据是为了提供给我们自己的用户使用,则应该保证爬取内容的优质性,低俗内容就过滤掉为好,要不然咳咳,不就成了百毒那样了吗?

5. 附件

完整的项目代码可以参考这里 https://github.com/MillerO/NodejsFetchZhihu.com

继续阅读

密钥权限错误,Permissions too open

MILLEROS 发布于 2019-04-25

权限错误

今天在刚组装好的新电脑上用U盘里的密钥登录服务器,见鬼的是遇到了权限错误的问题,并且一直解决不了。一直像如下一样提示我密钥权限错误,无法读取:

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@         WARNING: UNPROTECTED PRIVATE KEY FILE!          @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions for 'test.pem' are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.
Load key "test.pem": bad permissions
root@youdomain.com: Permission denied (publickey,gssapi-keyex,gssapi-with-mic).

后面谷歌了一堆的解决办法依然是没有办法。后面发现是在U盘里面的密钥文件夹一直是只读状态,后面单独把密钥拷贝到桌面就解决了这个问题。

为了方便大家在以后遇到这个问题好解决,我在这里详细说一下具体的方案。

解决方法

解决方法这里要分为两种,一种是密钥文件的所属文件夹的权限问题,另一种是密钥文件的权限问题,这里我为大家做一下区分,方便快速定位原因。

文件的权限问题

密钥文件的权限问题一般是会提示具体的权限过大的:

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@         WARNING: UNPROTECTED PRIVATE KEY FILE!          @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0644 for 'test.pem' are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.
bad permissions: ignore key: test.pem
Permission denied (publickey,password).

例如此处提示了权限0644过大的问题

Permissions 0644 for 'test.pem' are too open

遇到此类问题我们一般的做法是降低权限,例如此处权限为0644,我们则将此文件权限设置为0600:

chmod 600 ./test.pem

如果依然提示权限过大则降为500/400即可解决。

文件夹权限问题

我此次遇到的就是文件夹权限问题,而且见鬼的是那个在U盘里的文件夹我右键更改掉只读权限关闭属性窗口后依然会回到只读状态,索性我就把它拷贝出来了。

遇到密钥文件夹权限问题的时候,一般不会提示具体的权限值,而是直接说你这个密钥文件权限太大了,读取不到,被忽略了:

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@         WARNING: UNPROTECTED PRIVATE KEY FILE!          @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions for 'test.pem' are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.
Load key "test.pem": bad permissions
root@youdomain.com: Permission denied (publickey,gssapi-keyex,gssapi-with-mic).

这种时候则只需要把密钥文件拷贝到另外一个可以访问到的目录即可,如果此时依然不能读取到,且提示了具体的权限值,则回到本文的文件权限问题一栏进行解决。

结语

遇到文件不能读取问题的时候,不管是密钥文件也好还是其他文件也罢,首先我们应该检查文件权限,然后是是否是只读状态,然后确定不是文件问题再检查此文件所在的文件夹,包括所有父级文件夹的权限是否都是合理的,以此来定位具体的问题在哪里。

继续阅读

使用docker-compose部署WordPress

MILLEROS 发布于 2019-04-24

1. 前言

Docker这个词接触过很久了,但是与之对应的Docker程序却一直未曾瞻仰过,这次因为需要对公司内现有的协作工具,例如git等程序统一迁移到新的服务器上,但是因为之前一直是直接在环境内安装程序,导致现在迁移起来十分麻烦,需要考虑各种兼容性的问题。

除此之外还考虑到现有的后端程序员小伙伴会使用不同版本的开发环境,导致合并代码时会有很多版本的兼容性问题,故此考虑使用docker-compose来编写docker-compose.yml文件统一开发环境,同时也可以使用统一开发环境部署更多不同语言的开发环境而不受干扰。

2. 准备工作

2.1 安装docker

关于docker的安装这里就不再赘述,如果对docker的安装还不太了解可参考安装 Docker | Docker中文文档

2.2 关于docker-compose

此处我们使用的是docker-compose安装,这里说一下为什么要使用docker-compose安装环境。

在基础的docker中,我们也可以进行docker内程序的安装与卸载等功能,但是我们操作的时候得一个一个命令单个单个的程序进行安装,很麻烦,而且如果遇到需要迁移某些服务时,我们得提前下线相关服务,此时如果我们的docker进程的实例命名不合理的话,很容易因为时间过长都分不清哪些实例才是我们需要下架的,这时docker-compose就派上用场了。

使用过Nodejs的同学应该知道,在应用程序运行之前,我们先要安装应用程序的依赖,而此时我们只需要执行npm install即可,是因为在当前目录下存在一个叫做package.json的文件,此文件记录了所有此项目需要的依赖以及版本,npm install的本质就是告诉npm去这个文件内找需要的依赖,然后下载下来。而这里的docker-compose就类似于npm的安装效用,npm需要package.json,docker-compose就需要docker-compose.yml。当然两者是不可以对比的,npm属于包管理器。

这里提到的docker-compose.yml使用的是yaml语言,关于这个语言的格式不了解请查看基本语法

2.3 安装docker-compose

这里我是用的是CentOS,所以我使用的是yum进行包管理,如果你和我一样使用yum进行包管理,可直接使用下面的命令进行安装:

yum install docker-compose

安装完成后可直接使用docker-compose命令查看是否安装成功。

2.4 编写docker-compose.yml

为了防止以后因为项目过多而导致yml配置文件难以区分,我们应该为每一个docker项目分配单独的目录

$ mkdir wordpress
$ cd wordpress
$ touch docker-compose.yml
vim docker-compose.yml

我们需要在打开的文档内输入以下内容:

version: '3.3'

services:
   db:
     image: mysql:5.7
     volumes:
       - db_data:/var/lib/mysql
     restart: always
     environment:
       MYSQL_ROOT_PASSWORD: somewordpress
       MYSQL_DATABASE: wordpress
       MYSQL_USER: wordpress
       MYSQL_PASSWORD: wordpress

   wordpress:
     depends_on:
       - db
     image: wordpress:latest
     ports:
       - "8000:80"
     restart: always
     environment:
       WORDPRESS_DB_HOST: db:3306
       WORDPRESS_DB_USER: wordpress
       WORDPRESS_DB_PASSWORD: wordpress
       WORDPRESS_DB_NAME: wordpress
volumes:
    db_data: {}

关于此处的各配置意思可参考Docker Compose 配置文件详解

我们此处需要修改的是environment内的参数

  • MYSQL_ROOT_PASSWORD:MYSQL内Root用户的密码
  • MYSQL_DATABASE:此时需要为wordpress提供的数据库名称
  • MYSQL_USER:提供给wordpress使用的用户
  • MYSQL_PASSWORD:提供给wordpress使用的用户的密码
  • WORDPRESS_DB_HOST:提供给wordpress使用的数据库的服务地址
  • WORDPRESS_DB_USER:在MYSQL内设置的提供给wordpress使用的数据库用户
  • WORDPRESS_DB_PASSWORD:在MYSQL内设置的提供给wordpress使用的数据库用户的用户密码
  • WORDPRESS_DB_NAME:在MYSQL内设置的提供给wordpress使用的数据库名称

此处我们也可以对ports进行修改,8000:80的意思就是将wordpress需要使用的80端口映射到服务器主机的8000端口上。

3. 安装

说了这么多终于到了安装步骤,其实前面编辑好了docker-compose.yml文件之后,一切事情都简单了,我们只需要在docker-compose.yml所在的目录执行以下命令即可进行安装。

$ docker-compose up -d
Creating network "wordpress_default" with the default driver
Creating wordpress_db_1        ... done
Creating wordpress_db_1        ... 
Creating wordpress_wordpress_1 ... done

出现以上情况则说明服务启动成功,我们可以使用下面的命令进行服务的管理,当然是要在当前docker-compose.yml文件所在的目录内运行。

$ docker-compose start  //启动服务,前提是您docker-compose.yml内的服务是已经存在于docker内了
$ docker-compose stop //停止服务,前提是您docker-compose.yml内的服务是已经存在于docker内了
$ docker-compose down //停止并删除服务组,但是服务组的数据还会被保存,下次启用依然可以使用旧的数据
$ docker-compose down --volumes //停止并删除服务组,数据也会一并删除,不可恢复

前面提到需要确认服务组是否存在与docker的进程内,我们可以使用下面的命令来查看:

docker ps -a

同样的我们也可使用

docker stop/start [实例名/ID]

来停止或开启服务。

4. 结语

关于Docker的更多功能需要我们的不断探索,我也是处于新人阶段,很多东西并不是特别理解,也许会有地方出错,欢迎指正。

继续阅读

JavaScript操作符

MILLEROS 发布于 2019-04-01

1. 一元操作符

只能操作一个值的操作符叫做一元操作符。

1.1 递增和递减操作符

递增和递减操作符一般是连用两个一元操作符来完成操作。递增和递减操作符又分为前置递增/递减后置递增/递减

1.1.1 前置递增/递减

var age = 22;
console.log(--age); //21

1.1.2 后置递增/递减

var age = 22;
console.log(age--); //21

1.1.3 在运算中运用递增或递减运算符

在实际的等式运算中对变量可以进行任意的一元运算,但是对于实际数值使用运算则可能会出错。

var num1 = 4;
console.log(num1-- + 1); //5
console.log(num1); // 3
console.log(--num1 + 1); //3

console.log(--4 + 1); // Uncaught ReferenceError: Invalid left-hand side expression in prefix operation
console.log(4-- + 1); // Uncaught ReferenceError: Invalid left-hand side expression in prefix operation
console.log(1 + 4--); // Uncaught ReferenceError: Invalid left-hand side expression in prefix operation

这里有两处地方值得我们注意,第一处是在代码的第二行和第四行处,为何前置和后置递减操作分别运算加一操作会得出两个不同的结果。 首先第二行后置递减,此处的运算使用的num1值是num1执行后置递减前的值。 然后第四行前置递减,此处的运算使用的num1值是num1执行第二行后置递减接着再执行第四行前置递减后的值。 第二处值得我们注意的是直接对数值进行递增或递减操作是会报错的。

1.2 运用一元操作符进行数值转换

我们除了可以在等式运算中使用一元运算符之外,还可以在JavaScript中对几乎任何值进行一元操作,不过在对非数值应用一元操作符时,该操作符会像Number()转型函数一样对这个值执行转换。 换句话说,布尔值false和true将被转换为0和1,字符串值会被按照一组特殊的规则进行解析,而对象则是先调用他们的valueOf()和toString()方法,再转换得到的值。

var s1 = "01";
var s2 = "1.1";
var s3 = "z";
var b = false;
var f = 1.1;
var o = {
    valueOf:function(){
        return -1;
    }
};

s1 = + s1;     //数值1
s2 = + s2;     //数值1.1
s3 = +s3       //NaN
b = +b;        //数值0
f = +f;         //数值1.1
o = +o;        //数值-1

2. 位操作符

位操作符作用在最基本的二进制上,也就是内存中,此处的位指的就是内存中二进制各数值的位置得来的。ECMAScript中的所有数值都是以IEEE-754 64位格式存储,但操作符并不直接操作64位的值,而是先将64位转换成32位的整数,然后再执行操作。

对于有符号的整数,32位中的前31位用于表示整数的值,第32位用于表示数值的符号:0表示正数,1表示负数。这个标识符好的位叫做符号位,符号位的值军订了其他位数值的格式。

正数以纯二进制格式存储,31位中的每一位都表示2的幂。第一位(位0)表示2º,以此类推。32位中表示数值时,没有用到的位使用0填充。例如 18的二进制表示:00000000000000000000000000010010,或者简洁表示为10010。拥有五个有效位五位本身军订了实际的值。

1 0 0 1 0
2^4x1 2³x0 2²x0 2¹x0 2ºx0
16 0 0 2 0
合计 18

负数同样以二进制码存储,但使用的格式是二进制补码。计算一个数值的二进制补码,需要三个步骤:

  1. 求这个数值绝对值的二进制码,例如-18,先求18的二进制码。
  2. 求二进制反码,也就是将0替换为1,将1替换为0。
  3. 得到的二进制反码加1。

根据三个步骤求-18的二进制码: 0000 0000 0000 0000 0000 0000 0001 0010 求二进制反码,0与1互换 1111 1111 1111 1111 1111 1111 1110 1101 二进制反码加1 1111 1111 1111 1111 1111 1111 1110 1110

此时便就得到了二进制表示的-18。

但是在我们在JavaScript中计算二进制的时候,负数的二进制并不是如实显示上述的计算结果,例如-18的二进制在JavaScript中显示的是:

var num = -18;
console.log(num.toString(2)); // "-10010",也就是直接在18的二进制码前面加上了负号。
继续阅读

Let’s Encrypt Certbot 报错

MILLEROS 发布于 2019-03-18

关于给网站添加https的必要性在这里就不再赘述了,合理的添加https可以防止很多安全问题,其中就包括恶心的运营商广告注入问题。

现在普遍流行的https提供商非Let’s Encrypt莫属了,根据其提供的Certbot工具,我们可以轻松、优雅的为我们的域名创建Nginx/Apache证书;工具为我们生成的证书有效期限均为90天,所以我们每过90天便需要对我们的证书进行更新。在更新证书的过程中,我们经常会遇到各种各样的问题。

以下是我个人遇到的一些问题汇总,长期更新。

更新域名证书

  1. Client with the currently selected authenticator does not support any combination of challenges that will satisfy the CA.

出现这种错误一般情况下是所使用的客户端未更新或者Nginx/Apache服务未及时停止所致应对措施如下: 更新Certbot:

yum upgrade certbot //使用yum安装的certbot时
apt-get upgrade certbot //使用apt-get安装的certbot时
...

//使用手动安装方法时
wget https://dl.eff.org/certbot-auto //下载wget工具
chmod a+x ./certbot-auto //修改权限
./certbot-auto --help //测试打开certbot帮助

Nginx/Apache服务未及时停止

//Apache
sudo certbot --authenticator standalone --installer apache -d example.com -d www.example.com --pre-hook "systemctl stop apache2" --post-hook "systemctl start apache2"

//Nginx
sudo certbot --authenticator standalone --installer nginx -d example.com -d www.example.com --pre-hook "systemctl stop nginx" --post-hook "systemctl start nginx"

关于证书的维护问题

服务器如果缺少日常维护,经常会多出很多无用的问题,例如当前的certbot续期出现问题时,我们执行某些命令更新证书时常常会出现同一域名出现了多个证书,实际有用的只有一个,此时我们就需要删除无用的证书以及对应的Nginx/Apache配置文件。

从证书中删除域名

删除证书的时候,需要删除archive中的文件和live中的符号链接,同时还需要删除证书更新的配置文件:

rm -rf /etc/letsencrypt/live/www.example.com/
rm -rf /etc/letsencrypt/archive/www.example.com/
rm -rf /etc/letsencrypt/renewal/www.example.com.conf

这里要谨慎使用rm -rf命令,因为在输入文件路径时我们可能误按回车键,如果使用了-rf指令啧我们会在不收到任何提示的情况下删除某个文件

删除了旧的证书,我们就不用在每次自动更新证书时选择需要更新的证书,也会减少多种因为Nginx/Apache配置文件与证书文件不对应而产生的问题。

继续阅读

整理的一些前端安全问题

MILLEROS 发布于 2019-02-25

Web security 1.0

主要整理了前端的一些常见的安全问题。

JS逻辑代码

1. XSS攻击

主要表现为注入代码,在客户端渲染数据时,应该无条件相信用户的输入是不可信的,也就是说当渲染用户输入的数据时,应当提前剔除可执行代码或者直接进行转义。 XSS攻击通常是下面的攻击形式:

  • 使用vue等框架渲染时
<div class="content" v-html="data.content"></div> <!-- 🈲直接渲染用户的输入为html -->
<div class="content">{{data.content}}</div> <!-- ✅使用带有转义作用的渲染方式 -->
<script>
    data(){
        return {
            content:"<script>alert('您被XSS攻击了')</script>"
        }
    }
</script>
  • 使用JS、JQ渲染数据时
<div class="content"></div>
<script>
    var _incomeMsg = '<script>alert('您被XSS攻击了')</script>';

    document.getElementByClass('content')[0].innerHtml = _incomeMsg; // 🈲直接渲染用户的输入为html
    document.getElementByClass('content')[0].innerText = _incomeMsg; // ✅使用带有转义作用的渲染方式

    $('.content').append(_incomeMsg);// 🈲直接渲染用户的输入为html
    $('.content').after(_incomeMsg);// 🈲直接渲染用户的输入为html
    $('.content').before(_incomeMsg);// 🈲直接渲染用户的输入为html

    $('.content').text(_incomeMsg);// ✅使用带有转义作用的渲染方式
</script>

<!-- 执行exec等函数 -->
<script>
function html2Escape(sHtml) {
 return sHtml.replace(/[<>&"]/g,function(_index){
   return {'<':'&lt;','>':'&gt;','&':'&amp;','"':'&quot;'}[_index];
 });
}

var _url = "https://lingtaikj.com?username=<script>alert('您被XSS攻击了')</script>"
var param = /=(.+)$/.exec(_url);
var value = decodeURIComponent(param[1]);

$('.content').append('<a class="appendLink">' + _url + '</a>');// 🈲直接渲染用户的输入为html
$('.content').append('<a class="appendLink">' + html2Escape(_url) + '</a>');// ✅使用函数将html标签转换为实体编码
</script>

2. CSRF攻击

CSFR跨站请求伪造攻击,主要利用不合理的POST或GET请求来达成某种目的。

2.1 GET请求

某些时候,一些后端工程师为了方便,会将某些post请求写成GET请求,殊不知这样是违反HTTP标准的,同时也容易被黑客所利用:

<?php
// 从cookie中获取用户名,看似稳妥
$username = $_COOKIE['username'];
$productId = $_GET['pid'];
// 这里进行购买操作
//store_into_database($username, $productId);
?>
<meta charset="utf-8" />
<?php
echo $username . '买入商品:' . $productId;
?>

此时我们直接使用浏览器访问目标端口即可达成请求

2.2 POST请求

虽然我们此时按照了HTTP标准来编写请求接口,但是此时还是可以破解的。

<?php
$username = $_COOKIE['username'];
// 换为post了,可以规避黑客直接的提交
$productId = $_POST['pid'];
// 这里进行购买操作
//store_into_database($username, $productId);
?>
<meta charset="utf-8" />
<?php
echo $username . '买入商品:' . $productId;
?>

此时在html网页中可以伪造一个表单等待用户点击。

<!DOCYTPE HTML>
<html>
    <head>
        <meta charset="utf-8" />
        <script src="https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/js/lib/jquery-1.10.2_d88366fd.js"></script>
    </head>
    <body>
        <button id="clickme">点我看相册</button>
        <script>
            $('#clickme').on('click', function () {
                // 用户再不知情的情况下,提交了表单,服务器这边也会以为是用户提交过来的。
                $('#myform').submit();
            });
        </script>
        <form id="myform" style="display:none;" target="myformer" method="post" action="http://myhost:8082/lab/xsrflab/submit.php">
            <input type="hidden" name="pid" value="1">
        </form>
        <iframe name="myformer" style="display:none;"></iframe>
    </body>
</html>

2.3 如何防范两种请求潜在的CSRF攻击

除了要遵守HTTP标准来编写接口以外,我们可以在请求时给请求加上验证码,如果验证码填写不对,则请求无效,但是缺点是降低用户体验,用户每次请求都需要输入验证码,这是极其不友好的。

另外一种方法是在POST等敏感请求上加上二次验证,即在每个请求上加上页面token验证,当用户每次访问某个页面时,生成一个token下发给用户,当用户请求时验证token是否有效,这样当黑客在使用CSRF时则不会带有网站生成的请求Token,此连接操作自然是无效的。

3. 网络劫持

3.1 HTTP劫持

很多的时候,我们的网站不是直接就访问到我们的服务器上的,中间会经过很多层代理,如果在某一个环节,数据被中间代理层的劫持者所截获,他们就能获取到使用你网站的用户的密码等保密数据。比如,我们的用户经常会在各种饭馆里面,连一些奇奇怪怪的wifi,如果这个wifi是黑客所建立的热点wifi,那么黑客就可以结果该用户收发的所有数据。这里,建议站长们网站都使用https进行加密。这样,就算网站的数据能被拿到,黑客也无法解开。

如果你的网站还没有进行https加密的化,则在表单提交部分,最好进行非对称加密--即客户端加密,只有服务端能解开。这样中间的劫持者便无法获取加密内容的真实信息了。

3.2 iframe劫持

此种表现为自己的网站被其他网站利用iframe嵌套了,如果我们的网站因为某些原因被第三方网站嵌套了,第三方网站可以将嵌套我们网站的iframe透明度设为0覆盖在当前网页之上,然后利用背景诱导用户点击,那么此时就有可能发生不可预料的风险。

解决方法

3.2.1 防网页被嵌套

为了防止网站被钓鱼,可以使用window.top来防止你的网页被iframe.

//禁止任意形式的被嵌套
if(window != window.top){
    window.top.location.href = correctURL;
}

//同域名的网页可嵌套
if (top.location.host != window.location.host) {
  top.location.href = window.location.href;
}
3.2.2 X-Frame-Options

X-Frame-Options是一个响应头,主要是描述服务器的网页资源的iframe权限。目前的支持度是IE8+

X-Frame-Options: DENY
拒绝任何iframe的嵌套请求

X-Frame-Options: SAMEORIGIN
只允许同源请求,例如网页为 foo.com/123.php,則 foo.com 底下的所有网页可以嵌入此网页,但是 foo.com 以外的网页不能嵌入

X-Frame-Options: ALLOW-FROM http://s3131212.com
只允许指定网页的iframe请求,不过兼容性较差Chrome不支持
3.3.3 CSP页面防护

和X-Frames-Options一样,都需要在服务器端设置好相关的Header. CSP 的作用, 真的是太大了,他能够极大的防止你的页面被XSS攻击,而且可以制定js,css,img等相关资源的origin,防止被恶意注入。不过兼容性不好。

Content-Security-Policy: default-src 'self'
3.2.2 sandbox

主要用于防止自己的网页嵌套他人网站时的安全问题

<iframe sandbox src="..."> ... </iframe>

sandbox还忠实的实现了“Secure By Default”原则,也就是说,如果你只是添加上这个属性而保持属性值为空,那么浏览器将会对iframe实施史上最严厉的调控限制,基本上来讲就是除了允许显示静态资源以外,其他什么都做不了。比如不准提交表单、不准弹窗、不准执行脚本等等,连Origin都会被强制重新分配一个唯一的值,换句话讲就是iframe中的页面访问它自己的服务器都会被算作跨域请求。 另外,sandbox也提供了丰富的配置参数,我们可以进行较为细粒度的控制。一些典型的参数如下:

allow-forms:允许iframe中提交form表单
allow-popups:允许iframe中弹出新的窗口或者标签页(例如,window.open(),showModalDialog(),target=”_blank”等等)
allow-scripts:允许iframe中执行JavaScript
allow-same-origin:允许iframe中的网页开启同源策略

4. 控制台注入代码

通常此种攻击方式较为罕见,主要表现为黑客利用不懂代码的小白,诱骗他们在控制台粘贴执行某些代码,从而达到窃取用户信息的目的。

解决方案 在控制台显著标识警示信息。可参考天猫官网,打开控制台查看安全警告

5. 钓鱼网站

一般钓鱼网站会伪造成和正常网站,此时提示用户输入账号密码,提交之后用户对方便获得了用户的账号密码等信息。

一般是在原网站上发布钓鱼网站的外链,诱导用户点击。

解决办法 在网站内所有不可信的外链均执行页面unload提示,或者将所有的不可信外链改成拦截后需要用户确认为新窗口打开。

Cookie安全

Cookie主要是用来维护用户的登录状态的,同时有可能会存储其他敏感信息;当我们网页中的Cookie被他人,例如黑客利用XSS攻击或者其他方式获取了您的Cookie,那么他将可以利用您的Cookie以您的身份登录网站并进行操作。

  • 解决办法 要解决用户的Cookie可能被窃取的问题,通常的做法是后端工程师在处理登录注册操作时将用户的cookie设置为HttpOnly
setcookie("Session", "sessionKey", NULL, NULL, NULL, NULL, TRUE/*httpOnly*/);
继续阅读

简单的Python爬虫爬取网页信息

MILLEROS 发布于 2018-06-25

前言

从一开始使用Nodejs编写爬虫爬去网页信息开始,就对爬虫感兴趣了,以为可以做很多低级但是繁琐的事情,当然,这里的低级是一方面是因为以我现在的阶段不需要爬取太多深层次的东西,另一方面则是爬虫的编写水平还跟不上,当然肯定是后者才是重点hhhh。

爬虫的原理

编写网页爬虫一般是对页面进行解析,现在百度一搜索爬虫,最多的就是爬取豆瓣电影等网站的爬虫代码。

爬虫代码

# encoding=utf-8
import requests
import time
from bs4 import BeautifulSoup

_downLoadUrl = 'https://so.gushiwen.org/mingju/'
_count = 0
_all = 200

def getMs():
    millis = int(round(time.time() * 1000))
    return millis

def downLoadPage(url):
    headers = {
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.80 Safari/537.36'
    }
    data = requests.get(url,headers=headers).content
    return data

def getList(doc):
    soup = BeautifulSoup(doc,'html.parser')
    _list = soup.find('div',class_='sons')
    _pageCount = 0
    for _item in _list.find_all('div',class_='cont'):
        _pageCount += 1
        _detail = '[' + str(_pageCount) + '/' + str(_count) + ']' + _item.get_text()

        # print(_detail)
        file = open("gushiwen.txt","a")
        file.write(_detail)
        file.close()

    # global _all
    # _all = int(soup.find('label',id_='sumPage').get_text()) #获取所有页数

    page = soup.find('a',attrs={"class":"amore", "href": True}) #获取下一页
    if page:
        return _downLoadUrl + page['href'].replace('/mingju/','')
    return None

def main():
    url = _downLoadUrl
    _start = getMs()

    while url:
        global _count
        _count +=1

        doc = downLoadPage(url)
        url = getList(doc)

        print('第' + str(_count) + '页已经爬取完成,还剩' + str(_all - _count) + '页待爬取!')

        if (url == None):
            _end = getMs()
            print('爬取完成,耗时' + str(_end - _start) + '毫秒!')

if __name__ == '__main__':
    main()
继续阅读

Nuxt.js -- Vue.js 单页应用的服务端渲染

MILLEROS 发布于 2018-05-15

简介

Vuejs现在几乎成了单页应用的代名词,但作为单页应用,在搜索引擎的友好程度上显然是不行的,当网站爬虫爬取到vue应用时如果没有做过特殊的处理,爬虫通常是爬取不到页面的主要内容的,因为vue单页应用通常使用的都是前端渲染,而爬虫爬取通常是不执行JS的;所以在种种情况下Vue的服务端渲染显然是作为对SEO优化最好的选择,因此Nuxt.js应运而生。

Nuxt.js

Nuxt.js 是一个基于 Vue.js 的通用应用框架,它预设了利用 Vue.js 开发 服务端渲染(SSR, Server Side Render) 的应用所需要的各种配置,同时也可以一键生成静态站点。

作为框架,Nuxt.js 为 客户端/服务端 这种典型的应用架构模式提供了许多有用的特性,例如异步数据加载、中间件支持、布局支持等。区别于其他 vue SSR 框架,Nuxt.js 有以下比较明显的特性。

  • 自动代码分层
  • 强大的路由功能,支持异步数据(路由无需额外配置)
  • HTML头部标签管理(依赖 vue-meta 实现)
  • 内置 webpack 配置,无需额外配置

起步

项目创建

使用基于vue-cli的脚手架工具

$ vue init nuxt-community/starter-template <project-name>

通过此命令行命令初始化一个nuxt项目。

目录结构

├─assets                      资源目录,未编译的静态资源如less、js
├─components             组件目录,例如页面共用的头部,底部
├─layouts                    布局目录,例如所有页面都分成三部分,上中下
├─middleware             用于存放应用的中间件,允许您定义一个自定义函数运行在一个页面或一组页面渲染之前。
├─node_modules           
├─pages                      页面目录,放置页面除去组件后的主要部分
  ├─index.vue
  ├─....                 
├─plugins                    插件 例如jq,swiper等插件的文件
├─static                      静态文件目录
├─store                       vuex store 

vuex store : vuex状态树

配置项

// nuxt.config.js 文件配置
const path = require('path')

module.exports = {
  // Headers of the page
  head: {
    title: '默认共用title', //对应HTML<title />
    meta: [ //对应HTML<meta>
      { charset: 'utf-8' },
      { 'http-equiv': 'pragma', content: 'no-cache' },
      { 'http-equiv': 'cache-control', content: 'no-cache' },
      { 'http-equiv': 'expires', content: '0' },
      { content: 'telephone=no', name: 'format-detection' }
    ],
    // html head 中创建 script 标签
    script: [
      { innerHTML: require('./assets/js/flexible_nuxt'), type: 'text/javascript', charset: 'utf-8'}
    ],
    // 不对<script>标签中内容做转义处理
    __dangerouslyDisableSanitizers: ['script']
  },
  // Global CSS
  css: ['~/assets/css/reset.css', '~/assets/css/main.less'],
  // Global env
  env: {
    __ENV: process.env.__ENV
  },
  build: {
    vendor: ['axios'],
    postcss: [
      require('postcss-px2rem')({
        remUnit: 75
      })
    ],
    extend (config, ctx) {
      if (ctx.isClient) {
        // 拓展 webpack 配置
        config.entry['polyfill'] = ['babel-polyfill']
        config.module.rules.push({
          enforce: 'pre',
          test: /\.(js|vue)$/,
          loader: 'eslint-loader',
          exclude: /(node_modules)/
        })
        // 添加 alias 配置
        Object.assign(config.resolve.alias, {
          'utils': path.resolve(__dirname, 'utils')
        })
      }
    }
  },
  plugins: [{src: '~plugins/toast', ssr: false}, {src: '~plugins/dialog', ssr: false}]
}

头部标签配置

Nuxt.js 通过 vue-meta 实现头部标签管理,在 nuxt.config.js 中的 head 配置。所有的页面都会走这个配置,如果想要修改某一页面的title,可以在 pages/yourpage.vue 文件下,添加如下配置,这时该页面的标题就变成了“This is Miller”,其余页面还保持原有标题不变。

<script>
    export default {
        head () {
            return {
                title:'This is Miller'
            }
        }
    }
    //do something
</script>

在config header配置中, __dangerouslyDisableSanitizers: ['script'] 主要是为了不对innerHTML中内容做转义处理

路由

Nuxt.js 依据 pages 目录结构,自动生成 vue-router 模块的路由配置。

详情可查看Nuxt.js官网路由板块

布局

Nuxt.js布局方式如下图所示: Nuxt.js布局方式

布局配置文件存储在layouts文件夹下,默认布局为default.vue:

<template>
  <div id="milleros">
    <AppHeader />
    <nuxt/>
    <AppFooter />
    <AppLoading />
  </div>
</template>

这里<nuxt/>是一个页面的主体,所有/pages/*.vue中的内容都会插入到此标签中;

<AppHeader />、<AppFooter />、<AppLoading />为conponents组件,是所有页面共用的组件。

所以layouts布局就是配置不同组件和页面主体的结构。

我们可以根据不同的页面定制不同的layouts布局:

<script>
export default {
  layout: 'demo_layout',
  //do something
}
</script>

vuex

在根目录创建 store 目录,就会默认引用 vuex 模块,除此之外,还进行了以下的操作: 1)将 vuex 模块 加到 vendors 构建配置中去; 2)设置 Vue 根实例的 store 配置项。

Nuxt.js 支持两种使用 store 的方式:

  • 普通方式:store/index.js 返回一个 Vuex.Store 实例
  • 模块方式:store 目录下的每个 .js 文件会被转换成为状态树指定命名的子模块 (当然,index 是根模块,相当于设置了namespaced: true)

Nuxt.js提供了模块方式的简单写法:使用状态树模块化的方式,store/index.js 不需要返回 Vuex.Store 实例,直接将 state、mutations 和 actions 暴露出来即可。示例如下:

export const state = () => ({
  accesstoken: ''
})

export const mutations = {
  setAccesstoken (state, accesstoken) {
    state.accesstoken = accesstoken
  }
}

asyncData

asyncData方法是服务端渲染的重点!!!

因为asyncData可以在组件(仅限于页面组件)每次加载之前被调用。而且可以再服务端或路由调用之前被调用

Nuxt.js 会将 asyncData 返回的数据融合组件 data 方法返回的数据一并返回给当前组件。

详细查看异步数据
继续阅读

记一次服务器文件被恶意篡改的解决过程

MILLEROS 发布于 2018-05-02

前言

本人也是个linux小白,下面的内容是记录一次服务器找出源文件被篡改的原因的过程。

最近遇到公司服务器文件被篡改的情况,主要表现就是公司官网主页的SEO信息被替换成了广告,一开始以为只是普通的劫持,但是后来上到服务器发现是源文件直接被篡改了,然后找了一圈服务器登录记录以及命令执行记录都没发现有可以目标存在,后来又怀疑是设置了定时任务,然而也并没有发现。在没有明确发现可以目标的情况下,而且下午就放五一小长假了就没去深究,直接把文件改回来了。

刚放完假的第一天回来发现文件再次被篡改,篡改时间为5月1日下午两点十几分,这就让我有点头疼了,但是一圈检查下来还是没有发现任何记录,怀疑是被删掉了或者干脆就是自动某个自动脚本修改的,因为修改之后的源文件编码不对了,原先是utf-8,现在直接就是GBK了。

本来想查看文件修改记录的,但是发现服务器上并没有发现任何监控程序启动,所以只能将文件再次改回去,然后使用最基础的auditd官网来监控整个被篡改过文件的目录。

auditd

使用auditd监控文件变化记录。

到了这里我们其实已经可以先设定一个设计规则了,然后等待上述说到的文件夹被篡改后查看审计报告后在做计划了。

等待

等待所监控的文件再次被修改

文件再次被篡改

服务器首页文件在修正后并且添加了监控的情况下再次被篡改,使用命令

$ sudo ausearch -f /var/www/html

并未发现存在文件修改记录,猜测文件并非从服务器端直接被修改,且注入的文字为HTML实体编码,修改后的文件编码被改变。

猜测为注入攻击

查看网站访问记录

发现可疑访问

查看apache日志发现每次文件被篡改都显示有http://www.link114.cn/ 的访问

whois反查

通过whois反查查到以下注册人信息

  • 名称:陈镇威
  • 邮箱:520vigo@163.com
  • 域名列表:hackerjs.cn、link114.cn、link114.com.cn、netloss.cn、showdownload.org

其中link114.cn为买链帮手,判断是此人所为

查到个人博客

通过google查到此邮箱所关联的网易博客,但最近的博客更新时间为2008年,无法找到更多有用信息

返回梳理发生过程

通过对文件从修改正确到再次被篡改的过程中梳理得出了我从一开始就忽略的问题——文件夹权限问题

通过ls查看/var/www/文件夹权限为777,其下所有文件夹几乎均为权限777,这就会导致一个问题,那就是任何人都可以使用工具向我们的服务器内HTTP PUT文件,而且脚本为最高权限。

调整文件、文件夹权限

/var/www/下的所有文件/文件夹所属用户和组修改为apache,修改权限为755

后来测试发现项目上传文件夹无法上传,将权限修改为drwxrwx---后可以上传了。

总结

通过这次的教训,深刻认识到了服务器文件夹权限的重要性,往严重了说,这次对方修改meta信息算是轻得了,严重了都可以造成整个服务器被格盘或者干脆被当成肉机还不知情。

继续阅读

使用auditd监控文件/文件夹修改记录

MILLEROS 发布于 2018-05-02

什么是auditd

auditd(或 auditd 守护进程)是Linux审计系统中用户空间的一个组件,其负责将审计记录写入磁盘。说白了就是开启的一个守护进程根据指定的规则来监控系统的某些情况变化。

安装audited

我当前使用的操作系统是Centos 6,发现audited默认安装了,要查看audited是否安装了只需输入以下代码

$ sudo auditctl -v

显示了版本信息说明已安装

auditctl version 2.4.5

否则就使用apt-get或者yum、rpm等包管理工具安装

$ sudo yum install auditd

安装完毕后默认以下的工具也会存在

  • auditctl : 即时控制审计守护进程的行为的工具,比如如添加规则等等。
  • /etc/audit/audit.rules : 记录审计规则的文件。
  • aureport : 查看和生成审计报告的工具。
  • ausearch : 查找审计事件的工具
  • auditspd : 转发事件通知给其他应用程序,而不是写入到审计日志文件中。
  • autrace : 一个用于跟踪进程的命令。
  • /etc/audit/auditd.conf : auditd工具的配置文件。

安装完后记得查看auditd是否启动

$ sudo service auditd status

否则使用以下命令启动

$ sudo service auditd start

使用以下命令停止

$ sudo service auditd stop

首次安装 auditd 后, 审计规则是空的:

$ sudo auditctl -l
No rules

如何使用auditd

Audit 文件和目录访问审计

我们使用审计工具的一个基本的需求是监控文件和目录的更改。使用auditd工具,我们可通过如下命令来配置(注意,以下命令需要root权限)。

文件审计
$ sudo auditctl -w /var/www/html/index.html -p rwxa

选项 :

  • -w path : 指定要监控的路径,上面的命令指定了监控的文件路径 /etc/passwd,必须是以‘/’开头的绝对路径
  • -p : 指定触发审计的文件/目录的访问权限
  • rwxa : 指定的触发条件,r 读取权限,w 写入权限,x 执行权限,a 属性(attr)
目录审计

使用类似的命令来对目录进行审计,如下:

$ sudo auditctl -w /var/www/html/

以上命令将监控对 /var/www/html/ 目录 的所有访问。 现在,可以运行 auditctl -l 命令即可查看所有已配置的规则。

$ auditctl -l
-w /var/www/html/index.html -p rwxa
-w /var/www/html/ -p rwxa

查看审计日志

添加规则后,我们可以查看 auditd 的日志。使用 ausearch 工具可以查看auditd日志。

我们已经添加规则监控 /var/www/html/ 文件。现在可以使用 ausearch 工具的以下命令来查看审计日志了。

$ sudo ausearch -f /var/www/html/
----
time->Wed May  2 11:20:31 2018
type=PATH msg=audit(1525231231.453:528): item=1 name="/var/www/html/.index.html.swp" inode=929483 dev=fc:01 mode=0100644 ouid=0 ogid=0 rdev=00:00 nametype=DELETE
type=PATH msg=audit(1525231231.453:528): item=0 name="/var/www/html/" inode=921826 dev=fc:01 mode=040777 ouid=0 ogid=0 rdev=00:00 nametype=PARENT
type=CWD msg=audit(1525231231.453:528):  cwd="/var/www/html/"
type=SYSCALL msg=audit(1525231231.453:528): arch=c000003e syscall=87 success=yes exit=0 a0=ea54a0 a1=1 a2=1 a3=0 items=2 ppid=15009 pid=15413 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=pts4 ses=54129 comm="vim" exe="/usr/bin/vim" key=(null)

配置项:

  • -f 设定ausearch 调出 /var/www/html

解读输出结果:

  • time : 审计时间
  • name : 审计对象
  • cwd : 当前路径
  • syscall : 相关的系统调用
  • auid : 审计用户ID
  • uid 和 gid : 访问文件的用户ID和用户组ID
  • comm : 用户访问文件的命令
  • exe : 上面命令的可执行文件路径

以上审计日志显示文件曾被vim(/usr/bin/vim)改动过。

查看审计报告

一旦定义审计规则后,它会自动运行。过一段时间后,我们可以看看auditd是如何帮我们跟踪审计的。

Auditd提供了另一个工具叫 aureport 。从名字上可以猜到, aureport 是使用系统审计日志生成简要报告的工具。

我们已经配置auditd去跟踪/var/www/html。auditd参数设置后一段时间后,audit.log 文件就创建出来了。

生成审计报告,我们可以使用aureport工具。不带参数运行的话,可以生成审计活动的概述。

$ sudo aureport

Summary Report
======================
Range of time in logs: 05/02/2018 11:18:36.200 - 05/02/2018 12:31:43.069
Selected time for report: 05/02/2018 11:18:36 - 05/02/2018 12:31:43.069
Number of changes in configuration: 4
Number of changes to accounts, groups, or roles: 0
Number of logins: 2
Number of failed logins: 1
Number of authentications: 4
Number of failed authentications: 6
Number of users: 1
Number of terminals: 8
Number of host names: 3
Number of executables: 4
Number of commands: 2
Number of files: 123
Number of AVC's: 0
Number of MAC events: 0
Number of failed syscalls: 790
Number of anomaly events: 0
Number of responses to anomaly events: 0
Number of crypto events: 33
Number of integrity events: 0
Number of virt events: 0
Number of keys: 0
Number of process IDs: 176
Number of events: 1878

如上,报告包含了大多数重要区域的信息。

上图可以看出有 1 次授权失败。 使用aureport,我们可以深入查看这些信息。

使用以下命令查看授权失败的详细信息:

$ sudo aureport -au

Authentication Report
============================================
# date time acct host term exe success event
============================================
1. 05/02/2018 11:42:11 root 14.*.79.* ssh /usr/sbin/sshd no 1161
2. 05/02/2018 11:42:17 root 14.*.79.* ssh /usr/sbin/sshd no 1162
3. 05/02/2018 11:42:17 root 14.*.79.* ssh /usr/sbin/sshd no 1163
4. 05/02/2018 11:42:22 root 14.*.79.* ssh /usr/sbin/sshd yes 1164
5. 05/02/2018 11:42:22 root 14.*.79.* ssh /usr/sbin/sshd yes 1167
6. 05/02/2018 12:31:30 root 14.*.79.* ssh /usr/sbin/sshd no 1866
7. 05/02/2018 12:31:38 root 14.*.79.* ssh /usr/sbin/sshd no 1867
8. 05/02/2018 12:31:38 root 14.*.79.* ssh /usr/sbin/sshd no 1868
9. 05/02/2018 12:31:42 root 14.*.79.* ssh /usr/sbin/sshd yes 1869
10. 05/02/2018 12:31:42 root 14.*.79.* ssh /usr/sbin/sshd yes 1872

从上图可以看出,由一个用户在特定的时间(从上一次登录成功的时间开始计算)授权失败。

如果我们想看所有账户修改相关的事件,可以使用-m参数。

$ sudo aureport -m

Account Modifications Report
=================================================
# date time auid addr term exe acct success event
=================================================
<no events of interest were found>

我这里因为刚开启auditd服务所以并未有记录存在。

Auditd 配置文件

我们已经添加如下规则:

  • $ sudo auditctl -w /var/www/html/index.html -p rwxa
  • $ sudo auditctl -w /var/www/html/

现在,如果确信这些规则可以正常工作,我们可以将其添加到 /etc/audit/audit.rules 中使得规则永久有效。以下介绍如何将他们添加到/etc/audit/audit.rules中去。

$ sudo vim /etc/audit/audit.rules

# This file contains the auditctl rules that are loaded
# whenever the audit daemon is started via the initscripts.
# The rules are simply the parameters that would be passed
# to auditctl.

# First rule - delete all
-D

# Increase the buffers to survive stress events.
# Make this bigger for busy systems
-b 320

# Feel free to add below this line. See auditctl man page

-w /var/www/html/index.html -p rwxa
-w /var/www/html/

编辑完后保存,然后重启auditd守护进程

$ sudo service auditd restart

参考文档:

继续阅读

linux NAT端口转发实现nodejs网站https访问

MILLEROS 发布于 2018-04-30

什么是certbot?

在开始这个教程前我们必须要了解的就是什么是certbot。

certbot是Let's Encrypt为了用户方便生成https证书而开发的自动化程序,通过certbot我们可以生成四个文件分别用于为https加密所用。

安装certbot

首先最保险的方法是你需要上Certbot的官网来选择您的服务器对应的操作系统版本

cerbot

我这里使用的语言是nodejs,操作系统是CentOS7,接着下面会出现教程教你如何安装certbot:

certbot安装方法

这里官网提供的方法是使用yum来安装Certbot

$ sudo yum install certbot

但是在CentOS7上,使用yum安装的certbot很可能会出现所安装的certbot和pyOpenSSL版本不对的情况(什么都别说,我就遇到了,可是把我折腾苦了),那么什么是版本不对呢?出现以下错误就说明你的yum安装的pyOpenSSL不对:

Traceback (most recent call last):
  File "/usr/bin/certbot", line 9, in <module>
    load_entry_point('certbot==0.13.0', 'console_scripts', 'certbot')()
  File "/usr/lib/python2.7/site-packages/pkg_resources.py", line 378, in load_entry_point
    return get_distribution(dist).load_entry_point(group, name)
  File "/usr/lib/python2.7/site-packages/pkg_resources.py", line 2566, in load_entry_point
    return ep.load()
  File "/usr/lib/python2.7/site-packages/pkg_resources.py", line 2260, in load    entry = __import__(self.module_name, globals(),globals(), ['__name__'])
  File "/usr/lib/python2.7/site-packages/certbot/main.py", line 17, in <module>
    from certbot import client  File "/usr/lib/python2.7/site-packages/certbot/client.py", line 10, in <module>
    from acme import client as acme_client
  File "/usr/lib/python2.7/site-packages/acme/client.py", line 31, in <module>
    requests.packages.urllib3.contrib.pyopenssl.inject_into_urllib3()  # type: ignore  File "/usr/lib/python2.7/site-packages/requests/packages/urllib3/contrib/pyopenssl.py", line 112, in inject_into_urllib3
    _validate_dependencies_met()
  File "/usr/lib/python2.7/site-packages/requests/packages/urllib3/contrib/pyopenssl.py", line 147, in _v
alidate_dependencies_met
    raise ImportError("'pyOpenSSL' module missing required functionality. "ImportError: 'pyOpenSSL' module missing required functionality. Try upgrading to v0.14 or newer.

使用pip安装绕过yum安装的版本不对问题

错误提示使用v0.14或者更新版本,先看看pip中的版本:

$ pip search certbot

显示版本为0.14.0;

卸载yum安装的certbot和pyOpenSSL:

$ yum remove certbot pyOpenSSL

升级pip(如果不是最新版本,pip会提示你升级后再使用):

$ pip install –upgrade pip
pip安装certbot: pip install certbot。 碰到了Python.h或者pyconfig.h找不到的错误,于是安装Python的devel包: yum install -y python-devel;再次运行命令,提示找不到opensslv.h头文件,于是安装OpenSSL的devel包: yum install -y openssl-devel;再次运行pip install certbot命令,成功安装certbot;pip安装certbot: pip install certbot。 碰到了Python.h或者pyconfig.h找不到的错误,于是安装Python的devel包: yum install -y python-devel;再次运行命令,提示找不到opensslv.h头文件,于是安装OpenSSL的devel包: yum install -y openssl-devel;再次运行pip install certbot命令,成功安装certbot;

测试certbot是否可用: certbot certificates。输出正常,说明pip安装了最新版的certbot,并且能正确运行。

生成https证书

前面安装完certbot之后就到了最重要的地方了,就是生成https证书。

首先运行下列命令

$ certbot certonly

会出现两种选择,我们选择第二种

How would you like to authenticate with the ACME CA?
-------------------------------------------------------------------------------
1: Spin up a temporary webserver (standalone)
2: Place files in webroot directory (webroot)
-------------------------------------------------------------------------------
Select the appropriate number [1-2] then [enter] (press 'c' to cancel):

接着这里我们需要输入自己的域名:www.mydomain.com (注意这里有一个空格) mydomain.com,我们这里绑定的是两个域名,一个是带www的,一个是没有www的,如果你只需要其中一个的话这里可以只写一个

Please enter in your domain name(s) (comma and/or space separated)  (Enter 'c'to cancel):

这里要求我们输入网站的根目录

Select the webroot for milleros.com:
-------------------------------------------------------------------------------
1: Enter a new webroot
-------------------------------------------------------------------------------
Press 1 [enter] to confirm the selection (press 'c' to cancel):

如果前面输入域名的时候您输入的是两个则这里会再次提示输入网站根目录:

Select the webroot for yourdomain:
-------------------------------------------------------------------------------
1: Enter a new webroot
2: /root/www/static(你前面输入过的网站根目录)
-------------------------------------------------------------------------------

如果根目录一致的话我们就选择2.

输入完根目录之后certbot会自动为我们生成证书,生成成功会有以下提示:

Waiting for verification...
Cleaning up challenges
Generating key (2048 bits): /etc/letsencrypt/keys/0000_key-certbot.pem
Creating CSR: /etc/letsencrypt/csr/0000_csr-certbot.pem

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at
   /etc/letsencrypt/live/xxx.ml/fullchain.pem. Your cert will
   expire on 2017-06-07. To obtain a new or tweaked version of this
   certificate in the future, simply run certbot again. To
   non-interactively renew *all* of your certificates, run "certbot
   renew"
 - If you like Certbot, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le
注意:这里我遇到过一个很坑爹的问题,就是输入了网站根目录之后certbot提示我生成证书时认证失败:
Failed authorization procedure. example.com (http-01): urn:acme:error:unauthorized :: The client lacks sufficient authorization :: Invalid response from http://example.com/.well-known/acme-ch    <head><title>404 Not Found</title></head>
    <body bgcolor="white">    <center><h1>404 Not Found</h1></center>    <hr><center>", www.example.com (http-01): urn:acme:error:unauthorized :: The client lacks sufficient authorization :: Invalid response from http://www.example.com/.well-known/acme-challenge/k
    <head><title>404 Not Found</title></head>
    <body bgcolor="white">    <center><h1>404 Not Found</h1></center>    <hr><center>"

    IMPORTANT NOTES:
     - If you lose your account credentials, you can recover through       e-mails sent to example@example.com.
     - The following errors were reported by the server:

       Domain: example.com
       Type:   unauthorized
       Detail: Invalid response from
       http://example.com/.well-known/acme-challenge/wGNv57IGJjHQ9wyzzALktpNaPzfnTtN3m7u3QuO4p40:       "<html>
       <head><title>404 Not Found</title></head>
       <body bgcolor="white">       <center><h1>404 Not Found</h1></center>       <hr><center>"

       Domain: www.example.com
       Type:   unauthorized
       Detail: Invalid response from
       http://www.example.com/.well-known/acme-challenge/kFJ0CSuKOdgcT2xmciB4GGNCcnUPoIbpQmA9jOII_Bk:       "<html>
       <head><title>404 Not Found</title></head>
       <body bgcolor="white">       <center><h1>404 Not Found</h1></center>       <hr><center>"

       To fix these errors, please make sure that your domain name was
       entered correctly and the DNS A record(s) for that domain
       contain(s) the right IP address.
     - Your account credentials have been saved in your Certbot
       configuration directory at /etc/letsencrypt. You should make a
       secure backup of this folder now. This configuration directory will
       also contain certificates and private keys obtained by Certbot so
       making regular backups of this folder is ideal.

这里我研究了很久,网上很多人说可能是网站根目录下生成的文件夹.well-known 权限不够,但是我去根目录下看了,连那个文件夹都没看到,所以确定肯定不是权限问题,于是我在分析了一下错误信息里面包含404 not found的错误信息,可能是路由的问题,于是我在重复certbot生成https证书的过程中到了输入根目录的时候在结尾对加了一级/public(我用来存放客户端代码的文件夹),发现果然可以用了,顺利生成了https证书;

创建https服务器

经过刚刚的certbot操作,我们已经生成了如下四个文件:

  • cert.pem
  • chain.pem
  • fullchain.pem
  • privkey.pem

首先我们需要在原先的http服务器上做如下改动:

var express = require('express');
var fs = require('fs');
var http = require('http');
var https = require('https');

//引入刚刚生成的https文件
var privateKey  = fs.readFileSync('/path/to/privkey.pem', 'utf-8');
var certificate = fs.readFileSync('/path/to/cert.pem', 'utf-8');
var ca = fs.readFileSync('/path/to/fullchain.pem', 'utf-8');
var credentials = {key: privateKey, cert: certificate ,ca:ca};

//实例化
expressvar app = express();

//创建http以及https服务
var httpServer = http.createServer(app);
var httpsServer = https.createServer(credentials, app);

//监听端口
httpServer.listen(8321);
console.log('http server start on port 8321');
httpsServer.listen(8322);
console.log('http server start on port 8322');
//默认的http协议端口是80端口,https端口是443端口。

大功告成 然后我们可以直接使用app来进行各种路由操作,现在就大功告成啦:

只有https访问

如果您不想要http访问,只想要https访问,只需要修改我们之前的两条规则:

//var httpServer = http.createServer(app); //之前的创建http服务器
var httpServer = http.createServer(function(req,res){
    res.writeHead(301, {'Location': 'https://milleros.com/'});
    res.end();
}); //将http请求重定向至https
var httpsServer = https.createServer(credentials, app);

上面这段代码的重定向存在一点问题,就是当我们访问的地址不是主页时,而且请求http时则会跳转回https的主页,这显然不是我们想要的结果,所以我们把原先的代码改成这样

var express = require('express');
var fs = require('fs');
var http = require('http');
var https = require('https');

//引入刚刚生成的https文件
var privateKey  = fs.readFileSync('/path/to/privkey.pem', 'utf-8');
var certificate = fs.readFileSync('/path/to/cert.pem', 'utf-8');
var ca = fs.readFileSync('/path/to/fullchain.pem', 'utf-8');
var credentials = {key: privateKey, cert: certificate ,ca:ca};

//实例化
expressvar app = express();

//创建http以及https服务
var httpServer = http.createServer(app);
var httpsServer = https.createServer(credentials, app);

//监听端口
httpServer.listen(8321);
console.log('http server start on port 8321');
httpsServer.listen(8322);
console.log('http server start on port 8322');
//默认的http协议端口是80端口,https端口是443端口。

//新增一个路由来拦截请求
app.use(function(req,res,next){
    console.log(req.protocol);
    if(req.protocol != "https"){
    res.writeHead(301, {'Location': 'https://milleros.com' + req.path});
    res.end();
    }
    next();//保证请求不被阻塞
});

这样就可以保证,网站中的每一个http请求都可以被重定向至https了。

结语

linux是一个很复杂的操作系统(至少我这么认为),在我们平时进行的操作中经常会遇到各种各样的问题,有些问题在网络上我们可以很轻易的找到解决问题的方法,但是往往很多时候我们遇到的问题是很特殊的,网上几乎很难找到解决的办法,这个时候我们不应该着急,而应该静下心来仔细捋一捋思路,找找可能会导致问题出现的地方。

正是因为linux的复杂性,很多时候我们误操作会导致很多不可预料的问题,像这次我就是因为pyOpenSSL版本不对的问题在寻求很多方法未果之后选择了对系统自带的Python进行升级,正是因为我这一疯狂的想法从而让我误删除了site-packages,这一误操作直接导致的就是yum和pip无法使用,从而让我不得不重置了整个linux系统。

虽然对linux的误操作很容易造成不可估量的错误,但我还是鼓励大家大胆尝试,毕竟只有不停地跌倒才会有更多爬起来的经验,才能让我们更加的强大!

继续阅读

Ueditor + HighLightJs实现代码块高亮

MILLEROS 发布于 2018-04-30

百度Ueditor

Ueditor是百度的一款富文本编辑器,目前在国内大多数网站都会使用到它,而今天我们就来看看,像我们这种三句不离代码的码农对于代码高亮来说是再基本不过的功能了,那么何为代码高亮呢?

普通代码显示 举例一段html代码

<!DOCTYPE html>

<html>

    <head>

        <meta charset="UTF-8">

        <title>Milleros的官方网站</title>

    </head>

    <body>

        <p>欢迎来到milleros的官方网站</p>

    </body>

</html>



这是一段普通的未加任何修饰的html代码段,像这样的代码存在于网页上,我相信如果是我,我连看的勇气都没有。

高亮代码显示 还是上面那段代码,但是现在加上高亮功能

<!DOCTYPE html>
    <html>
        <head>
            <meta charset="UTF-8">
            <title>Milleros的官方网站</title>
        </head>
        <body>
            <p>欢迎来到milleros的官方网站</p>
        </body>
    </html>

可以看到上面的代码的可读性变高了许多。

如何使用 使用方法很简单,当我们使用Ueditor编辑文章时可以在插入代码时点击Ueditor工具栏上的

snipaste_20170528_123657.png

来选择对应的语言,如果工具栏没有这个选项的话就说明您的编辑器没有默认配置这个功能,这个时候您需要对Ueditor的配置文件ueditor.config.js进行相应功能的配置。选择对应语言之后Ueditor编辑器内会出现专门用于放置代码块的pre标签

snipaste_20170528_124333.png



接着需要在我们需要渲染代码文章的页面引入两个文件

样式文件:

<link rel="stylesheet" type="text/css" href="/ueditor/third-party/SyntaxHighlighter/shCoreDefault.css">

脚本文件:

<script type="text/javascript" src="/ueditor/third-party/SyntaxHighlighter/shCore.js"></script>

激活高亮:

当上述文件均被引入之后,需要在页面js内写入下面的代码来执行代码高亮

SyntaxHighlighter.all();
            提示:代码高亮的执行需要在DOM加载完之后再执行,也就是需要执行代码高亮的代码已经被渲染到页面上之后再执行代码高亮。

代码高亮主题 官方主题分类 对于这种视觉上优化的功能,没有主题切换肯定是不“人道”的,百度Ueditor使用的代码高亮工具是第三方的SyntaxHighlighter高亮工具,从它的官网可以看到官方提供了自己的主题

    snipaste_20170528_124928.png

官方主题使用
    官方对于主题的应用,显然也考虑到了简单易用的原则;对于上述的主题样式我们可以使用
<link type="text/css" rel="Stylesheet" href="PATH/Styles/shThemeDjango.css"/>
    来更换对应的主题文件。

主题文件
    对于我们使用的Ueditor,因为是使用第三方库的原因,可能会存在主题文件不完整的问题存在,像楼主使用的是nodejs版的Ueditor,而内置的主题文件只有默认样式,所以我们需要把缺失的主题文件放到对应的文件夹内。具体的位置需要大家自己去看下 Ueditor的目录结构。
继续阅读

有些人25岁就死了,75岁才埋下

MILLEROS 发布于 2018-04-30

在很小很小的时候,我并不懂什么是理想,什么是梦想。

  • “你的理想是什么”

  • “我长大后想当警察,为民除害“

相信这个情景在我们是孩童时代经常会遇到的,那时候的我们总是带着小小的天真坚定说出自己可能并不了解的理想。

初中,依稀记得每次写到有关理想的作文,我总是写着自己要当警察的理想,但是随着我每次对梦想的探讨,都会让我加深对自己理想的动摇,因为我发现,每一次写一篇文章下来,总是绞劲脑汁都无法深入到这个理想的精髓。

高中是我动摇最大的时代,那三年让我彻底的摒弃了自己坚持了从小学到初中结束的理想,因为此刻的我发现,理想就是理想,是理想的生活,是理想中的理想。

我的大学南门有一排写字间,是学校划分给即将毕业的学生创业孵化的摇篮。记得我第一次来到这里的时候是得知自己被录取后,第二天就和妈妈来到了学校,那时候第一眼见到这一排的创业公司,觉得特别吃惊,因为在这时候的我看来,这些把自己的梦想亲自塑造出来的人该是多么的厉害。

某一个晚上,和舍友的闲聊

  • “我刚刚去创意园了“

  • “你去创意园干啥“

  • “我有一个师兄在那边开了自己的公司,我就过去坐一坐了“

  • “那你师兄是做啥的“

  • “他开的是买衣服的“

  • “卖衣服!?“

  • “对,实体店加淘宝店,他以前是玩乐队的,他说以前深职的乐队特别多,玩摇滚的也很多,听他说当时还专门出了一个深职的专辑,不过没有发行,他卖的衣服都是自己设计的“

昨天晚上

  • “那位师兄不做了,他打算自己去找工作了”;

我当时很吃惊(还是这个词),吃惊之后就是满满的无奈。

每个人活着都有自己想做的事,可能对于我来说,大学就是提供给了我最好的时机做自己最喜欢的事,当然对于大多数的大学生来说同样也是如此,但是每每在我们努力朝着自己的方向前进的时候,深深的无力感总会压迫我们脆弱得一塌糊涂的坚持,也许某一天就会垮下去,接受社会的冲刷,让我们认清楚,不管理想有多么的伟大,最后终究会沦为生活的玩偶。这个时候我们或许是25岁了吧…


有些人25岁就死了,75岁才埋下。

继续阅读