Android注解支持(Support Annotations)

注解支持(Support Annotations)

Android Support Library19.1版本开始引入了一个新的注解库,它包含很多有用的元注解,你能用它们修饰你的代码,帮助你发现bugSupport Library自己本身也用到了这些注解,所以作为Support Library的用户,Android Studio已经基于这些注解校验了你的代码并且标注其中潜在的问题。Support Library 22.2版本又新增了13个新的注解以供使用。

使用注解库

注解默认是没有包含的;他们被包装成一个独立的库。(support library现在由一些更小的库组成:v4-support, appcompat, gridlayout, mediarouter等等)

(如果你正在使用appcompat库,那么你已经可以使用这些注解了,因为appcomat它自己也依赖它。)

添加使用注解最简单的方式就是打开Project Structure对话框。首先在左边选中module,然后在右边选中Dependencies标签页,点击面板底部的+按钮,选择Library Dependency,假设你已经把Android Support Repository安装到你的SDK中了,那么注解库将会出现在列表中,你只需点击选中它即可(这里是列表中的第一个):
150820193163761

点击OK完成Project Structure的编辑。这会修改你的build.gradle文件,当然你也可以手动编辑它:

dependencies {
    compile 'com.android.support:support-annotations:22.2.0'
}

对于Android ApplicationAndroid Library这两个类型的module(你应用了com.android.application或者com.android.library插件的)来说,你需要做的已经都做好了。如果你想只在Java Module使用这些注解,那么你就明确的包含SDK仓库了,因为Support Libraries不能从jcenter获得(Android Gradle插件会自动的包含这些依赖,但是Java插件却没有。)

repositories {
   jcenter()
   maven { url '<your-SDK-path>/extras/android/m2repository' }
}

执行注解

当你用Android StudioIntelliJ IDEA的时候,如果给标注了这些注解的方法传递错误类型的参数,那么IDE就会实时标记出来。

Gradle插件1.3.0-beta1版本开始,并且安装了Android M Preview平台工具的情况下,通过命令行调用gradlelint任务就可以执行这些检查。如果你想把标记问题作为持续集成的一部分,那么这种方式是非常有用的。说明:这并不包含Nullness注解。本文中所介绍的其他注解都可以通过lint执行检查。

Nullness Annotations

@Nullable注解能被用来标注给定的参数或者返回值可以为null
类似的,@NonNull注解能被用来标注给定的参数或者返回值不能为null

如果一个本地变量的值为null(比如因为过早的代码检查它是否为null),而你又把它作为参数传递给了一个方法,并且该方法的参数又被@NonNull标注,那么IDE会提醒你,你有一个潜在的崩溃问题。
v4 support library中的FragmentActivity的示例代码:

import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
...

/**
 * Add support for inflating the <fragment> tag.
 */
@Nullable
@Override
public View onCreateView(String name, @NonNull Context context, @NonNull AttributeSet attrs) {
...

(如果你执行Analyze -> Infer Nullity...,或者你在键入时把@NonNull替换成了@NotNull,那么IDE可能会提供附加的IntelliJ注解。参考底部的“IntelliJ Annotations”段落了解更多)

注意@NonNull@Nullable并不是对立的:还有第三种可能:未指定。当你没有指定@NonNull或者@Nullable的时候,工具就不能确定,所以这个API也就不起作用。

最初,我们在findViewById方法上标注@Nullable,从技术上说,这是正确的:findViewById可以返回null。但是如 果你知道你在做什么的时候(如果你传递给他一个存在的id)他是不会返回null的。当我们使用@Nullable注解它的时候,就意味着源代码编辑器中会有大量的代码出现高亮警告。如果你已经意识到每次使用该方法都应该明确的进行null检查,那么就只能用@Nullable标注返回值。有个经验规则: 看现有的“好的代码”(比如审查产品代码),看看这些API是怎么被使用的。如果该代码为null检查结果,你应该为方法注解@Nullable

资源类型注解

Android的资源值通常都是使用整型传递。这意味着获取一个drawable使用的参数,也能很容易的传递给一个获取string的方法;因为他们都是int类型,编译器很难区分。

资源类型注解可以在这种情况下提供类型检查。比如一个被@StringRes住进诶的int类型参数,如果传递一个不是R.string类型的引用将会被IDE标注:
150820193163762
ActionBar为例:

import android.support.annotation.StringRes;
 ... 
public abstract void setTitle(@StringRes int resId);

有很多不同资源类型的注解:如下的每一个Android资源类型:
@StringRes, @DrawableRes, @ColorRes, @InterpolatorRes,等等。一般情况下,如果有一个foo类型的资源,那么它的相应的资源类型注解就是FooRes.

除此之外,还有一个名为@AnyRes特殊的资源类型注解。它被用来标注一个未知的特殊类型的资源,但是它必须是一个资源类型。比如在框架中,它被用在Resources#getResourceName(@AnyRes int resId)上,使用的时候,你可以这样getResources().getResourceName(R.drawable.icon)用,也可以getResources().getResourceName(R.string.app_name)这样用,但是却不能这样getResources().getResourceName(42)用。

请注意,如果你的API支持多个资源类型,你可以使用多个注解来标注你的参数。

IntDef/StringDef: 类型定义注解

整型除了可以作为资源的引用之外,也可以用作“枚举”类型使用。

@IntDef和”typedef”作用非常类似,你可以创建另外一个注解,然后用@IntDef指定一个你期望的整型常量值列表,最后你就可以用这个定义好的注解修饰你的API了。

appcompat库里的一个例子:

import android.support.annotation.IntDef;
...
public abstract class ActionBar {
...
@IntDef({NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS})
@Retention(RetentionPolicy.SOURCE)
public @interface NavigationMode {}

public static final int NAVIGATION_MODE_STANDARD = 0;
public static final int NAVIGATION_MODE_LIST = 1;
public static final int NAVIGATION_MODE_TABS = 2;

@NavigationMode
public abstract int getNavigationMode();

public abstract void setNavigationMode(@NavigationMode int mode);

上面非注解的部分是现有的API。我们创建了一个新的注解(NavigationMode)并且用@IntDef标注它,通过@IntDef我们为返回值或者参数指定了可用的常量值。我们还添加了@Retention(RetentionPolicy.SOURCE)告诉编译器这个新定义的注解不需要被记录在生成的.class文件中(译者注:源代码级别的,生成class文件的时候这个注解就被编译器自动去掉了)。

使用这个注解后,如果你传递的参数或者返回值不在指定的常量值中的话,IDE将会标记出这种情况。
150820193163768

你也可以指定一个整型是一个标记性质的类型;这样客户端代码就通过|,&等操作符同时传递多个常量了:

@IntDef(flag=true, value={
        DISPLAY_USE_LOGO,
        DISPLAY_SHOW_HOME,
        DISPLAY_HOME_AS_UP,
        DISPLAY_SHOW_TITLE,
        DISPLAY_SHOW_CUSTOM
})
@Retention(RetentionPolicy.SOURCE)
public @interface DisplayOptions {}

最后,还有一个字符串版本的注解,就是@StringDef,它和@IntDef的作用基本上是一样,所不同的是它是针对字符串的。该注解一般不常用,但是有的时候非常有用,比如在限定向Activity#getSystemService方法传递的参数范围的时候。

要了解关于类型注解的更多详细信息,请参考
https://developer.android.com/tools/debugging/annotations.html#enum-annotations

线程注解: @UiThread, @WorkerThread, …

(Support library 22.2及其之后版本支持.)

如果你的方法只能在指定的线程类型中被调用,那么你就可以使用以下4个注解来标注它:

  • @UiThread
  • @MainThread
  • @WorkerThread
  • @BinderThread

如果一个类中的所有方法都有相同的线程需求,那么你可以注解类本身。比如android.view.View,就被用@UiThread标注。

关于线程注解使用的一个很好的例子就是AsyncTask

@WorkerThread
protected abstract Result doInBackground(Params... params);

@MainThread
protected void onProgressUpdate(Progress... values) {
}

如果你在重写的doInBackground方法里尝试调用onProgressUpdate方法或者View的任何方法,IDE工具就会马上把它标记为一个错误:
150820193163766

@UiThread还是@MainThread?

在进程里只有一个主线程。这个就是@MainThread。同时这个线程也是一个@UiThread。比如Activity的主要窗口就运行在这个线程上。然而它也有能力为应用创建其他线程。这很少见,一般具备这样功能的都是系统进程。通常是把和生命周期有关的用@MainThread标注,和View层级结构相关的用@UiThread标注。但是由于@MainThread本质上是一个@UiThread,而大部分情况下@UiThread又是一个@MainThread,所以工具(lint ,Android Studio,等等)可以把他们互换,所以你能在一个可以调用@MainThread方法的地方也能调用@UiThread方法,反之亦然。

RGB颜色整型

当你的API期望一个颜色资源的时候,可以用@ColorRes标注,但是当你有一个相反的使用场景时,这种用法就不可用了,因为你并不是期望一个颜色资源id,而是一个真实的RGB或者ARGB的颜色值。

在这种情况下,你可以使用@ColorInt注解,表示你期望的是一个代表颜色的整数值:

public void setTextColor(@ColorInt int color)

有了这个,当你传递一个颜色id而不是颜色值的时候,lint就会标记出这段不正确的代码:
150820193163767

值约束: @Size, @IntRange, @FloatRange

如果你的参数是一个float或者double类型,并且一定要在某个范围内,你可以使用@FloatRange注解:

public void setAlpha(@FloatRange(from=0.0, to=1.0) float alpha)  { … }

如果有人使用该API的时候传递一个0-255的值,比如尝试调用setAlpha(128),那么工具就会捕获这一问题:

1508201931637610

(你也可以指定是否包括起始值。)

同样的,如果你的参数是一个int或者long类型,你可以使用@IntRange注解约束其值在一个特定的范围内:

public void setAlpha(@IntRange(from=0,to=255) int alpha) { … }

把这些注解应用到参数上是非常有用的,因为用户很有可能会提供错误范围的参数,比如上面的setAlpha例子,有的API<c/ode>是采用0-255的方式,而有的是采用0-1float值的方式。

最后,对于数据、集合以及字符串,你可以用@Size注解参数来限定集合的大小(当参数是字符串的时候,可以限定字符串的长度)。

举几个例子

  • 集合不能为空: @Size(min=1)
  • 字符串最大只能有23个字符: @Size(max=23)
  • 数组只能有2个元素: @Size(2)
  • 数组的大小必须是2的倍数 (例如图形API中获取位置的x/y坐标数组: @Size(multiple=2)

150820193163765

权限注解: @RequiresPermission

如果你的方法的调用需要调用者有特定的权限,你可以使用@RequiresPermission注解:

@RequiresPermission(Manifest.permission.SET_WALLPAPER) 
public abstract void setWallpaper(Bitmap bitmap) throws IOException;

如果你至少需要权限集合中的一个,你可以使用anyOf属性:

@RequiresPermission(anyOf = { Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION}) 
public abstract Location getLastKnownLocation(String provider);

如果你同时需要多个权限,你可以用allOf属性:

@RequiresPermission(allOf = { Manifest.permission.READ_HISTORY_BOOKMARKS, Manifest.permission.WRITE_HISTORY_BOOKMARKS}) public static final void updateVisitedHistory(ContentResolver cr, String url, boolean real)  { … }

对于intents的权限,可以直接在定义的intent常量字符串字段上标注权限需求(他们通常都已经被@SdkConstant注解标注过了):

@RequiresPermission(android.Manifest.permission.BLUETOOTH) 
public static final String ACTION_REQUEST_DISCOVERABLE = "android.bluetooth.adapter.action.REQUEST_DISCOVERABLE";

对于content providers的权限,你可能需要单独的标注读和写的权限访问,所以可以用@Read或者@Write标注每一个权限需求:

@RequiresPermission.Read(@RequiresPermission(READ_HISTORY_BOOKMARKS)) 
@RequiresPermission.Write(@RequiresPermission(WRITE_HISTORY_BOOKMARKS)) 
public static final Uri BOOKMARKS_URI = Uri.parse("content://browser/bookmarks");

150820193163763

方法重写: @CallSuper

如果你的API允许使用者重写你的方法,但是呢,你又需要你自己的方法(父方法)在重写的时候也被调用,这时候你可以使用@CallSuper标注:

@CallSuper 
protected void onCreate(@Nullable Bundle savedInstanceState)  { … }

用了这个后,当重写的方法没有调用父方法时,工具就会给予标记提示:

150820193163764

(Android Studio 1.3 Preview 1lint检查有个关于这个注解的bug,这个bug就是即使是对的重写也会报错,这个bug已经在Preview 2版本修改,可以通过canary channel更新到Preview 2版本。)

返回值: @CheckResult

如果你的方法返回一个值,你期望调用者用这个值做些事情,那么你可以使用@CheckResult注解标注这个方法。

你并不需要微每个非空方法都进行标注。它主要的目的是帮助哪些容易被混淆,难以被理解的API的使用者。

比如,可能很多开发者都对String.trim()一知半解,认为调用了这个方法,就可以让字符串改变以去掉空白字符。如果这个方法被@CheckResult标注,工具就会对那些没有使用trim()返回结果的调用者发出警告。

Android中,Context#checkPermission这个方法已经被@CheckResult标注了:

@(suggest="#enforcePermission(String,int,int,String)") 
public abstract int checkPermission(@NonNull String permission, int pid, int uid);

这是非常重要的,因为有些使用context.checkPermission的开发者认为他们已经执行了一个权限 —-但其实这个方法仅仅只做了检查并且反馈一个是否成功的值而已。如果开发者使用了这个方法,但是又不用其返回值,那么这个开发者真正想调用的可能是这个 Context#enforcePermission方法,而不是checkPermission

150820193163769

@VisibleForTesting

你可以把这个注解标注到类、方法或者字段上,以便你在测试的时候可以使用他们。

@Keep

我们还在注解库里添加了@Keep注解,但是Gradle插件还支持(尽管已经在进行中)。被这个注解标注的类和方法在混淆的时候将不会被混淆。

在你自己的库中使用注解

如果你在你自己的库中使用了这些注解,并且是通过Gradle构建生成aar包,那么在构建的时候Android Gradle插件会提取注解信息放在AAR文件中供引用你的库的客户端使用。在AAR文件中你可以看到一个名为annotations.zip的文件,这 个文件记录的就是注解信息,使用的是IntelliJ的扩展注解XML格式。这是必须的,因为.class文件不能包含足够的要处理以上@IntDef注解的信息;注意我们只需记录该常量的一个引用,而不是它的值。当且仅当你的工程依赖注解库的时候,Android Gradle插件会把提取注解的任务作为构建的一部分执行它。(说明:只有源保留注解被放置在.aar文件中;class级别的会被放在 classes.jar里。)

IntelliJ注解

IntelliJAndroid Studio就是基于它开发的,IntelliJ有一套它自己的注解;IntDef分析其实重用的是MagicConstant分析的代码,IntelliJ null分析其实用的是一组配置好的null注解。如果你执行Analyze -> Infer Nullity…,它会试图找出所有的null约束并添加他们。这个检查有时会插入IntelliJ注解。你可以通过搜索,替换为Android注解库的 注解,或者你也可以直接用IntelliJ注解。在build.gradle里或者通过Project Structure对话框的Dependencies面板都可以添加如下依赖:

dependencies { 
	compile 'com.intellij:annotations:12.0' 
}

参考链接


Android注解支持(Support Annotations)

IntelliJ IDEA 2016.1.1的Struts2项目中引入Junit4.12单元测试

使用IntelliJ IDEA 2016.1建立Strut2工程并使用Tomcat调试建立项目后,想引入Junit进行TDD的开发,总结如下:

1.点击工程右上方的"Project Structure"图标

select_structure

2.在弹出的界面中的左侧选择"Libraries",然后如下图所示,按照编号的顺序,依次点击,输入数据。注意,如果此时"OK"按键是灰色的,那么需要点击右侧的放大镜图标,让IntelliJ IDEA去服务器上搜索一下,等他搜索完成了,那么下面的"OK"按键自然是可以点击的了。

maven_add_junit4

3.点击"OK"按键

choose_modules

4.此时发现提示存在一个"Problem",点击然后按照下图展示修复这个问题。

click_success_problems

fix_junit4_problem

fix_junit4_problem_select_menu

点击"OK",关闭这个页面。

5.在工程的"src"目录下面新建名为"test"的目录,他的子目录与源代码的目录相同即可,IntelliJ IDEA会自动把这个目录当作单元测试的目录,如下图所示:

UnitTestForder

测试代码如下:

import org.junit.Before;
import org.junit.After;
import org.junit.Test;

public class testBasic {
    @Before
    public void before() throws Exception {

    }

    @Test
    public void myTest(){

    }

    @After
    public void after() throws Exception {
    }
}

然后右击测试文件,会出现如下图的菜单选项DebugTestFile

有时候他不会出现这些菜单,那说明还没有测试函数,添加一个空的@Test测试函数就可以了,比如下面的样子:

import org.junit.Test; 
import org.junit.Before; 
import org.junit.After; 

/** 
* Basic Tester. 
* 
* @author <Authors name> 
* @since <pre>五月 10, 2016</pre> 
* @version 1.0 
*/ 
public class BasicTest { 

    @Before
    public void before() throws Exception {
    }

    @Test
    public void myTest(){

    }

    @After
    public void after() throws Exception {
    }
}

也可以使用JunitGenerator V2.0这个插件来自动生成单元测试代码。
打开IntelliJ IDEA工具,Alt+Ctrl+S,弹出窗口如下:IDEASettings

在文本框中输入Plugins进行插件搜索设置。

IdeaPluginsInstall

点击按钮,从插件资源库中安装新的插件。

从插件资源库中搜索JunitGenerator V2.0版本,点击右侧的Install按钮.

JunitGeneratorV2Install

安装完成后,点击"Restart IntelliJ IDEA"重启IntelliJ IDEA。

JunitGeneratorV2InstallRestartIdea

现在可通过此工具自动完成test类的生成了,在需要进行单元测试的类中Alt+Insert

AutoGeneratorJunit

生成的自动测试代码如下图所示

AutoJunitSouces

char*-vector

 

1.如果是想要read到vector中,首先给vector分配足够的大小,之后使用&V[0]即可

std::vector<char> buffer(lSize);
// 如果vector已经存在的话,使用resize方法
// buffer.resize(lSize)
std::fread(&buffer[0], 1, buffer.size(), pFile);

2.非read情况,想要将vector<char>转为char*的话

局部使用可以直接

reinterpret_cast<char*> (&buf[0]);

c++ 11后支持

reinterpret_cast<char*>(buf.data());

非局部需要把vector拷贝到char*,首先给char*分配内存,然后拷贝

char* cbuffer = (char*)malloc(lSize * sizeof(char));

std::copy(buffer.begin(), buffer.end(), cbuffer);

从零创建WordPress自定义插件

序曲


使用WordPress统计插件WP Statistics之后,经常会出现某个IP的访问突然出现比较多的情况,那么这个时候就需要了解这个IP访问的是哪个页面,是否发生了访问攻击。但是遗憾的是,WP Statistics并没有根据IP地址查询访问记录的功能,因此,我们就自己写一个查询插件好了。

创建插件的文件和文件夹


WordPress插件存储在wp-content/plugins/文件夹中,而我们的新建文件也要存放在这个文件夹中。一般情况下,如果所制作的插件非常简单,可直接把所有代码放在一个PHP文件中,然后把其放在wp-content/plugins/文件夹中。但是,我们这里要制作的插件要使用两个文件(一个是主要的插件文件,另一个为执行管理页面的文件),因此,我们需要把新创建的文件另放在一个文件夹中,我们这里把这个文件夹命名为wp-statistics-visitor-query

创建插件的功能文件


我们就要创建插件主要文件了,我们把其命名为wp-statistics-visitor-query.php。当然,你也可把其命名为其它名称,这并不重要。

然后再文件的头部增加插件的描述信息,如果不增加描述信息,WordPress是找不到这个插件的。增加的内容如下:

<?php
/*
Plugin Name: WP Statistics Visitors Query
Plugin URI: http://www.mobibrw.com
Description: Plugin for query WP Statistics Visitors
Author: LongSky
Version: 1.0
Author URI: http://www.mobibrw.com
*/
?>

这样操作完成后,就可以在插件管理界面中找到名字为"WP Statistics Visitors Query"的插件了。

使用行为钩子(Action Hook)


虽然插件现在已在管理面板显示,但是由于它只含有header信息,因此它并没有其它功用。现在,我们就来增加它的功能吧。
WordPress允许用户把插件代码放在模板的任意位置,包括页面的空间位置及页面创建过程中的逻辑位置。在此,我们将进一步了解后者,逻辑位置——即行为钩子。

行为钩子

我们可把行为钩子视为回调函数。WordPress执行某项操作时,如,显示页脚,它就会让插件来执行自己的代码并且要在确切的时间运行。
为了方便大家理解,我们以my_plugin这个普通插件为例,当系统显示页脚时,这个插件就会执行mp_footer()函数。因此,显示页脚时,使用名为add_action()这个特殊的函数,我们就会告知WordPress调用mp_footer()函数:

add_action()函数把行为钩子名称作为其第一个参数,同时把必须执行的函数名称作为其第二个参数。我们将把此函数调用添加到插件的主要文件(即包括header信息的文件),通常把它放在需要执行的函数代码正下方(本例中即放在mp_footer()下)。可在WordPress Codex查看所有可用的行为钩子 。

创建插件管理页面

我们要先创建新菜单条目并把其放置在设置菜单中。

WordPress提供了新建菜单可调用的钩子(即admin_menu),因此,这也是我们创建菜单条目的最佳地点。

既然已确认需使用的行为钩子,我们接着就要定义行为钩子运行时要调用的函数了,我们把其称为visitors_query_admin_actions()。代码显示如下:

<?php
function visitors_query_admin_actions() {
}
add_action('admin_menu', 'visitors_query_admin_actions');
?>

正如您所看到的,我们已创建了visitors_query_admin_actions()函数,并用add_action()函数让其与admin_menu行为钩子结合。接下来,我们就要给visitors_query_admin_actions()函数添加一些代码以创建真正的菜单条目了。

WordPress其它许多操作类似,添加新菜单条目也非常简单,只需调用一个函数就可完成!把新菜单条目添加到设置菜单需要使用add_options_page()函数,然后把以下代码添加到visitors_query_admin_actions()函数中。

<?php
function ip_admin() {
}

function visitors_query_admin_actions() {
	add_options_page("Query Visitor From IP", "Query Visitor From IP", 1, "Query Visitor From IP", "ip_admin");
}
add_action('admin_menu', 'visitors_query_admin_actions');
?>

此时刷新管理页面,设置页面下就已包括新建菜单条目了。
QueryVisitorFromIP
WordPress中的每个菜单都使用不同的函数来添加其子菜单条目。例如,如果要给工具添加子菜单条目,我们就应该使用add_management_page()函数,而非设置页面使用的add_options_page()。更多信息,请查看WordPress Codex中的添加管理菜单
重新回到新添加的代码,或许您已注意到了最后一个参数。它即是请求新增菜单条目时,系统将调用的函数,我们也将使用它来创建插件的管理页面。
我们最好把此页面功能放在单独的文件中,并给其命名为query_visitor_import_admin.php

<?php
function ip_admin() {
	include('query_visitor_import_admin.php');
}

// 只有管理员才能使用这个功能,基本安全防护
function is_administrator() {
	if( current_user_can( 'manage_options' ) ) { return True; }
	return False;  // 非管理员
}

function visitors_query_admin_actions() {
	if(is_administrator()) {
		add_options_page("Query Visitor From IP", "Query Visitor From IP", 1, "Query Visitor From IP", "ip_admin");
	}
}

add_action('admin_menu', 'visitors_query_admin_actions');
?>

此时点击设置菜单下的链接,系统将显示空白页面,这是因为query_visitor_import_admin.php文件现在没有任何内容。
接下来我们配置页面中的内容,如下:

<?php
        function ip_query($ipaddress) {
                $q = "select referred from wp_statistics_visitor where ip='$ipaddress';";
                global $wpdb;
                $rows = $wpdb->get_results($q,ARRAY_N);
                $i=0;
                while ($i< count($rows)){
                        $row = $rows[$i];
                        /*echo var_dump($row[0]);*/
                        echo "<br/>";
                        echo $row[0];
                        $i++;
                }
        }
?>
		
<div class="wrap">
	<form name="visitors_query_form" method="post" action="<?php echo str_replace( '%7E', '~', $_SERVER['REQUEST_URI']); ?>">
		<p><?php _e("IP Address: " ); ?><input type="text" name="visitors_ip" value="" size="20"><?php _e(" ex: 192.168.1.1" ); ?></p>
		<hr />
		<p class="submit">
			<input type="submit" name="Submit" value="Query" />
		</p>
	</form>
	<hr />
	<p>
		<?php
			$ipaddress = $_POST['visitors_ip'];
			_e("Query $ipaddress :");
		?>
	</p>
	<hr />
	<div>
                <?php
                        $ipaddress = $_POST['visitors_ip'];
                        if(is_administrator()) {
                                ip_query($ipaddress);
                        } else {
                                echo "only administrator can use this function";
                        } 
                 ?>
	</div>	
</div>

如此调整之后,需要禁用一下插件,然后再启用,否则可能不会生效。

代码详解


熟悉HTML和PHP的人都会理解以上代码,但我们这里仍将简单解释一下。

  • 我们首先用wrap类创建一个div,它是一个标准的WordPress类,使新建页面能够和WordPress管理版块的其它页面风格一致。
  • 表单将使用POST方法回发数据。这意味着表单数据将由同一页面接收,这样以来,我们就可添加数据库更新代码到同一文件。
  • 查询数据库的时候使用WordPress自带的全局数据库对象$wpdb来访问数据库。
  • 为了安全,我们只允许管理员权限的人员调用我们的插件功能。

参考链接


从零创建WordPress自定义插件

简易hash函数

unsigned long hash(unsigned char *str)
{
unsigned long hash = 5381;
int c;

while (c = *str++)
hash = ((hash << 5) + hash) + c; /* hash * 33 + c */

return hash;
}

用这个函数hash几张图片,得到了同样的值,应该是遇到了0,改进一下

把while循环改成for循环就可以了,不过需要知道字符串的长度

unsigned long hash(unsigned char *str, int size)
{
unsigned long hash = 5381;
int i;

for (i = 0; i < size; i++)
{
int c = (int)str[i];
hash = hash * 33 + c;
}

return hash;
}

提高WEB页面加载速度

从输入url,到打开页面发生了什么?

引用知乎上一个比较好玩的答案:

你发现快要过年了,于是想给你的女朋友买一件毛衣,你打开了www.taobao.com。这时你的浏览器首先查询DNS服务器,将www.taobao.com转换成ip地址。不过首先你会发现,你在不同的地区或者不同的网络(电信、联通、移动)的情况下,转换后的ip地址很可能是不一样的,这首先涉及到负载均衡的第一步,通过DNS解析域名时将你的访问分配到不同的入口,同时尽可能保证你所访问的入口是所有入口中可能较快的一个(这和后文的CDN不一样)。

你通过这个入口成功的访问了www.taobao.com的实际的入口ip地址。这时你产生了一个PV,即Page View,页面访问。每日每个网站的总PV量是形容一个网站规模的重要指标。淘宝网全网在平日(非促销期间)的PV大概是16-25亿之间。同时作为一个独立的用户,你这次访问淘宝网的所有页面,均算作一个UV(Unique Visitor用户访问)。最近臭名昭著的12306.cn的日PV量最高峰在10亿左右,而UV量却远小于淘宝网十余倍,这其中的原因我相信大家都会知道。

因为同一时刻访问www.taobao.com的人数过于巨大,所以即便是生成淘宝首页页面的服务器,也不可能仅有一台。仅用于生成www.taobao.com首页的服务器就可能有成百上千台,那么你的一次访问时生成页面给你看的任务便会被分配给其中一台服务器完成。这个过程要保证公正、公平、平均(暨这成百上千台服务器每台负担的用户数要差不多),这一很复杂的过程是由几个系统配合完成,其中最关键的便是LVS,Linux Virtual Server,世界上最流行的负载均衡系统之一,正是由目前在淘宝网供职的章文嵩博士开发的。

经过一系列复杂的逻辑运算和数据处理,用于这次给你看的淘宝网首页的HTML内容便生成成功了。对web前端稍微有点常识的童鞋都应该知道,下一步浏览器会去加载页面中用到的css、js、图片等样式、脚本和资源文件。但是可能相对较少的同学才会知道,你的浏览器在同一个域名下并发加载的资源数量是有限制的,例如ie6-7是两个,ie8是6个,chrome各版本不大一样,一般是4-6个。我刚刚看了一下,我访问淘宝网首页需要加载126个资源,那么如此小的并发连接数自然会加载很久。所以前端开发人员往往会将上述这些资源文件分布在好多个域名下,变相的绕过浏览器的这个限制,同时也为下文的CDN工作做准备。

据不可靠消息,在双十一当天高峰,淘宝的访问流量最巅峰达到871GB/S。这个数字意味着需要178万个4mb带宽的家庭宽带才能负担的起,也完全有能力拖垮一个中小城市的全部互联网带宽。那么显然,这些访问流量不可能集中在一起。并且大家都知道,不同地区不同网络(电信、联通等)之间互访会非常缓慢,但是你却发现很少发现淘宝网访问缓慢。这便是CDN,Content Delivery Network,即内容分发网络的作用。淘宝在全国各地建立了数十上百个CDN节点,利用一些手段保证你访问的(这里主要指js、css、图片等)地方是离你最近的CDN节点,这样便保证了大流量分散已经在各地访问的加速。
这便出现了一个问题,那就是假若一个卖家发布了一个新的宝贝,上传了几张新的宝贝图片,那么淘宝网如何保证全国各地的CDN节点中都会同步的存在这几张图片供用户使用呢?这里边就涉及到了大量的内容分发与同步的相关技术。淘宝开发了分布式文件系统TFS(taobao file system)来处理这类问题。

好了,这时你终于加载完了淘宝首页,那么你习惯性的在首页搜索框中输入了'毛衣'二字并敲回车,这时你又产生了一个PV,然后,淘宝网的主搜索系统便开始为你服务了。它首先对你输入的内容基于一个分词库进行的分词操作。众所周知,英文是以词为单位的,词和词之间是靠空格隔开,而中文是以字为单位,句子中所有的字连起来才能描述一个意思。例如,英文句子I am a student,用中文则为:“我是一个学生”。计算机可以很简单通过空格知道student是一个单词,但是不能很容易明白“学”、“生”两个字合起来才表示一个词。把中文的汉字序列切分成有意义的词,就是中文分词,有些人也称为切词。我是一个学生,分词的结果是:我 是 一个 学生。

进行分词之后,还需要根据你输入的搜索词进行你的购物意图分析。用户进行搜索时常常有如下几类意图:(1)浏览型:没有明确的购物对象和意图,边看边买,用户比较随意和感性。Query例如:”2010年10大香水排行”,”2010年流行毛衣”, “zippo有多少种类?”;(2)查询型:有一定的购物意图,体现在对属性的要求上。Query例如:”适合老人用的手机”,”500元 手表”;(3)对比型:已经缩小了购物意图,具体到了某几个产品。Query例如:”诺基亚E71 E63″,”akg k450 px200″;(4)确定型:已经做了基本决定,重点考察某个对象。Query例如:”诺基亚N97″,”IBM T60″。通过对你的购物意图的分析,主搜索会呈现出完全不同的结果来。

之后的数个步骤后,主搜索系统便根据上述以及更多复杂的条件列出了搜索结果,这一切是由一千多台搜索服务器完成。然后你开始逐一点击浏览搜索出的宝贝。你开始查看宝贝详情页面。经常网购的亲们会发现,当你买过了一个宝贝之后,即便是商家多次修改了宝贝详情页,你仍然能够通过‘已买到的宝贝’查看当时的快照。这是为了防止商家对在商品详情中承诺过的东西赖账不认。那么显然,对于每年数十上百亿比交易的商品详情快照进行保存和快速调用不是一个简单的事情。这其中又涉及到数套系统的共同协作,其中较为重要的是Tair,淘宝自行研发的分布式KV存储方案。

然后无论你是否真正进行了交易,你的这些访问行为便忠实的被系统记录下来,用于后续的业务逻辑和数据分析。这些记录中访问日志记录便是最重要的记录之一,但是前边我们得知,这些访问是分布在各个地区很多不同的服务器上的,并且由于用户众多,这些日志记录都非常庞大,达到TB级别非常正常。那么为了快速及时传输同步这些日志数据,淘宝研发了TimeTunnel,用于进行实时的数据传输,交给后端系统进行计算报表等操作。

你的浏览数据、交易数据以及其它很多很多的数据记录均会被保留下来。使得淘宝存储的历史数据轻而易举的便达到了十数甚至更多个PB(1PB=1024 TB=1048576GB)。如此巨大的数据量经过淘宝系统1:120的极限压缩存储在淘宝的数据仓库中。并且通过一个叫做云梯的,由2000多台服务器组成的超大规模数据系统不断的进行分析和挖掘。

从这些数据中淘宝能够知道小到你是谁,你喜欢什么,你的孩子几岁了,你是否在谈恋爱,喜欢玩魔兽世界的人喜欢什么样的饮料等,大到各行各业的零售情况、各类商品的兴衰消亡等等海量的信息。

上文中绝大多数是一些WEB后端技术,那么前端能做什么呢?

1.减少HTTP请求数

什么是HTTP请求数,一个css-link标签,一个script标签,一个img-src,都需要一次HTTP请求。当然css-link、script很多时候是同步的,且block页面首次渲染,img是异步的。

页面加载中,大多数时间用在HTTP请求上,如果能减少HTTP请求数,就能大幅提高性能。

1)从设计入手,更少的元素显示更丰富的页面内容。

2)css,js文件合并/压缩,很多优秀的工具如grunt可以很方便的完成这件事。

3)CSS Sprites技术,即把所有背景图放到一张图片内,使用CSS技术:background-image,background-position来显示图片的不同部分。

<div>
    <span id="image1" class="nav"></span>
    <span id="image2" class="nav"></span>
    <span id="image3" class="nav"></span>
    <span id="image4" class="nav"></span>
    <span id="image5" class="nav"></span>
</div>
.nav {
    width: 50px;
    height: 50px;
    display: inline-block;
    border: 1px solid #000;
    background-image: url('E:/1.png');
}
#image1 {
        background-position: 0 0;
}
#image2 {
        background-position: -95px 0;
}
#image3 {
        background-position: -185px 0;
}
#image4 {
        background-position: -275px 0;
}
#image5 {
        background-position: -366px -3px;
}

但这个只适用于图片元素紧挨在一起,而且定位比较繁琐,很容易出错,且不能指定手工形状。

2.减少DNS查找次数

3.避免跳转

值得一提的一个现象,如真正的url是 http://xxx.xxx.xx/,而我们输入了http://xxx.xxx.xx,这就会造成一次跳转。

4.可缓存的AJAX

5.延迟加载

确定一下页面哪些内容是页面首次渲染时必须加载的,哪些内容稍微延后加载效果会更好?

6.预加载

预加载是浏览器引擎的一种技术,如页面加载,遇到了js脚本,就需要同步的去解析/执行JS。这时候,开一个线程,遍历一下页面中其他需要下载的元素,去下载它们。

业务预加载:业务的预加载是指在空闲的时候(onload之后),可以使用JS脚本去加载那些尚未显示出来的内容,一般是img,也可以是css和js。

7.减少DOM数量

8.根据域名划分页面内容

9.尽量少的iframe

10.使用CDN

11.为文件头指定Expires和Cache-Contorl

对于静态内容:设置文件头过期时间Expires的值为“Never expire”(永不过期)
对于动态内容:使用恰当的Cache-Control文件头来帮助浏览器进行有条件的请求

12.Gzip

13.使用Etag

14.使用flush

15.使用GET来完成AJAX请求

16.css置于顶部

17.不使用css表达式

18.使用外部js和css

19.js置于页面底部

20.减少JS对DOM的访问

21.优化图像

22.不要缩放图像

http://lehsyh.iteye.com/blog/2111265

 

 

Windows 7在dia 0.97.2 中输入中文

Windows 7电脑上装了dia 0.97.2,结果画图时不能输入中文,能把输入法调出来,但是输入的字却消失不见。折腾半天,最后发现十分简单。。。
菜单里选择"输入法"->"简单",就可以了,不能用默认的 "系统(Windows IME)"。
在Ubuntu下却要用 "系统(SCIM)",选 "简单" 的话,连输入法都调不出来。

参考链接


在dia中输入中文

HP ProLiant MicroServer Gen8使用Super GRUB2 Disk从TF卡启动光驱位安装的Debian 8.3

简介


MicroServer Gen8属于HPE(Hewlett Packard Enterprise,惠普企业级产品)而不是HP,MicroServer Gen8的支持页面(如驱动下载)在HPE,官网首页是http://www.hpe.com,不是http://www.hp.com

HPE大约从2015年起变更了服务支持策略,普通驱动可以无限制下载,但是BIOS、SPP更新等可能需要用主机序列号注册、且在主机质保期内方能下载,超期就只能等待别人搬运分享了。

MicroServer Gen8在AHCI模式时,五个SATA和普通主板的功能一样,唯一不同的是MS G8的BIOS不能选择用哪个硬盘启动。
它会尝试从SATA1引导,如果SATA1没有连接硬盘,则尝试从SATA2引导,以此类推。
可是SATA1~4是硬盘笼子;通常人们都是将4个3.5寸仓库盘放到笼子里面;然后通过SATA5连接一个2.5寸硬盘(放置在9.5mm光驱位置)做系统盘。当五个硬盘都连接时,BIOS仅尝试从SATA1引导,结果出现引导失败。

解决方法就是通过安装一个U盘或MicroSD卡,从而间接引导SATA5接口上的系统盘。

网上给出的方法都是引导Windows系统的,而我们安装的如果是Linux系统的话,则无法简单的使用这些方法来引导系统的。
自己探索了一下,通过使用Rufus来使用并且修改Super GRUB2 Disk的方式来启动Debian的方法。

解决方法


1.安装Debian Linux系统

只插上光驱位置上的硬盘,然后安装Debian Linux系统,只有这样,才能正常安装系统,否则会出现无法安装到正确的磁盘上面。

2.开启SATA AHCI模式

通过 Intelligent Provisioning安装根本就找不到 tf卡(在 bios中可以把默认的 Dynamic HP Smart Array改成 SATA AHCI模式后就可以顺利安装了,但这样 raid功能也没了)

sata-mode-in-bios

3.下载必须的软件

下载Super GRUB2 Disk最新的镜像文件,官网为http://www.supergrubdisk.org/,一定要下载hybrid版本。
下载Rufus最新的版本,官网地址为https://rufus.akeo.ie/
也可以在本网站下载我使用的版本Super GRUB2 Diskrufus-3.10

4.安装Super GRUB2 Disk到SD卡

按照下图的步骤处理:

如果使用2.8.x版本,参考下图(注意,如果使用最新的2.0.4版本的Super GRUB2 Disk,只能使用3.11.x版本的Rufus

Rufus-ISO-Select
Rufus 2.8.886

只有如下选项才能保证可以在Windows中可以正常访问修改Super GRUB2 Disk已经安装到SD卡上的内容。

Rufus-ISO-Select-ISOHybrid

如果使用3.10.x版本(已知`Rufus 3.11.1678`写入的数据无法引导系统,只能使用`3.10.x`版本),参考下图

Rufus 3.10.1647

只有如下选项才能保证可以在`Windows`中可以正常访问修改`Super GRUB2 Disk`已经安装到`SD`卡上的内容。

5.增加Gen8的启动配置文件

在刻录好的SD卡的`\boot\grub\`(Super GRUB2 Disk 2.02)或者`\boot\grub\sgd`(Super GRUB2 Disk 2.04)目录下创建一个名为Gen8.cfg的配置文件,内容如下:

# Super Grub Disk - Gen8.cfg

set option_title=$"Gen8"

function run_option {
  echo "Starting Gen8 ..."
  # for uuid
  #search --set=root --fs-uuid 763A-9CB6
  # for file 
  search --set=root --file /boot/grub/grubenv
  # debian 12.5 for file 
  #search --set=root --file /boot/grub/unicode.pf2
  set prefix=(${root})/boot/grub
  normal
}

一般建议是通过指定磁盘的uuid的方法来启动系统,如果能够确定系统磁盘上存在一个唯一的文件,也可以通过简单的指定文件的方法来让GRUB2来搜索的方式找到启动磁盘。

对于 Super GRUB2 Disk 2.02
修改\boot\grub\main.cfg,在
process_main_option "${prefix}/language_select.cfg"
这行代码下面增加
process_enable "${prefix}/Gen8.cfg" rootmenu

对于 Super GRUB2 Disk 2.04
修改\boot\grub\sgd\main.cfg,在
process_main_option "${sg2d_directory}/language_select.cfg"
这行代码下面增加
process_enable "${sg2d_directory}/Gen8.cfg" rootmenu

并且打开被注释掉的set timeout=10项目,让系统自动启动,否则需要手工点击一下回车。
修改后的内容如下:

# Super Grub Disk Main Configuration file
# Copyright (C) 2009,2010,2011,2012,2013,2014,2015  Adrian Gibanel Lopez.
#
# Super Grub Disk is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Super Grub Disk is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Super Grub Disk.  If not, see <http://www.gnu.org/licenses/>.

# Configure gfxterm, but allow it to be disabled by holding shift during boot.
# gfxterm is required to display non-ASCII translations.

loadfont "$prefix/unifont.pf2"
if keystatus --shift; then
  disable_gfxterm=true
  # export disable_gfxterm is needed so that the setting will persist even after
  # a "configfile /boot/grub/main.cfg" (which is what language_select.cfg does after
  # you select a new language)
  export disable_gfxterm

  # The following strings are intentionally not made translateable.
  echo "It has been detected that the shift key was held down. Because of this SG2D"
  echo "will use VGA text mode rather than gfxterm. This will cause display problems"
  echo "when using some non-English translations."
  echo
  echo "Press escape to continue to the main menu"
  sleep --interruptible 9999
fi

if [ "$disable_gfxterm" != true ]; then
  insmod all_video
  gfxmode=640x480
  if terminal_output --append gfxterm
  then
      terminal_output --remove console
  fi
fi

# Export the variables so that they persist when loading a new menu.
export menu_color_normal
export menu_color_highlight
export menu_color_background
export bwcolor

function set_sgd_colors {
  if [ "$bwcolor" = "yes" ]; then
    menu_color_normal=white/black
    menu_color_highlight=black/white
    menu_color_background=black/white
  else
    menu_color_normal=white/brown
    menu_color_highlight=white/blue
    menu_color_background=yellow/cyan
  fi
}

set_sgd_colors

# Set secondary_locale_dir to the directory containing SG2D specific mo files.
# This makes grub aware of translations for SG2D specific strings.
secondary_locale_dir="${prefix}/sgd_locale/"

insmod part_acorn
insmod part_amiga
insmod part_apple
insmod part_bsd
insmod part_gpt
insmod part_msdos
insmod part_sun
insmod part_sunpc

function process_main_option {

  set option_cfg="$1"
  source "${option_cfg}"
  menuentry "${option_title}" "${option_cfg}" {
    set chosen=""
    export chosen
    set sourced_cfgs="${2}"
    export sourced_cfgs
    configfile "${prefix}/processoption.cfg"
  }

}

function process_option {

  set option_cfg="$1"
  source "${option_cfg}"
  menuentry "${finaloption_tab_str}${option_title}" "${option_cfg}" {
    set chosen=""
    export chosen
    set sourced_cfgs="${2}"
    export sourced_cfgs
    configfile "${prefix}/processoption.cfg"
  }

}

function process_enable {

  set option_cfg="$1"
  set forced_prefix="$2"
  if [ "$forced_prefix" = "rootmenu" ]; then
    menu_prefix_str=""
  else
    menu_prefix_str="${finaloption_tab_str}"
  fi
  source "${option_cfg}"
  menuentry "${menu_prefix_str}${option_title}" "${option_cfg}" {
    set chosen=""
    export chosen
    set sourced_cfgs="${2}"
    export sourced_cfgs
    configfile "${prefix}/processenable.cfg"
  }

}

function submenu_title {

  menuentry "${secondoption_prefixtab_str}${chosen}${secondoption_postfixtab_str}" {
    sleep 1s
  }

}

# Timeout for menu
set timeout=10

# Set default boot entry as Entry number 2 (counting from 0)
set default=2

# Init Super Grub2 Disk variables
insmod regexp
regexp -s "sg2d_dev_name" '^\((.*)\).*$' "$prefix"
rmmod regexp
export sg2d_dev_name

# Get the version number for this Super GRUB2 Disk release
source "${prefix}/version.cfg"

# Get design variables
source "${prefix}/design.cfg"

menuentry "               ====---==- Super Grub2 Disk $sgrub_version  -==---==== " {
  # Set pager=1 so ls output doesn't scroll past the top of the screen
  # but restore $pager to its previous value when finished
  set oldpager="${pager}"
  set pager=1

  cat /boot/grub/AUTHORS
  cat /boot/grub/COPYING

  set pager="${oldpager}"
  unset oldpager
  echo $"Press escape to return to the main menu"
  sleep --interruptible 9999
}

process_main_option "${prefix}/language_select.cfg"

process_enable "${prefix}/gen8.cfg" rootmenu

# Everything
menuentry $"Detect and show boot methods" {
  configfile "${prefix}/everything.cfg"
}

process_enable "${prefix}/enableraidlvm.cfg" rootmenu

process_enable "${prefix}/enablenative.cfg" rootmenu

submenu $"Boot manually""${three_dots_str}" {

  submenu_title

  process_option "${prefix}/osdetect.cfg"

  process_option "${prefix}/cfgextract.cfg"

  process_option "${prefix}/cfgdetect.cfg"

  process_option "${prefix}/menulstdetect.cfg"

  process_option "${prefix}/grubdetect.cfg"

  process_option "${prefix}/diskpartchainboot.cfg"

  process_option "${prefix}/autoiso.cfg"

  source "${prefix}/return.cfg"

}

submenu $"Extra GRUB2 functionality""${three_dots_str}" {

  submenu_title

  process_enable "${prefix}/enablelvm.cfg"

  process_enable "${prefix}/enableraid.cfg"

  process_enable "${prefix}/enableencrypted.cfg"

  process_enable "${prefix}/enablenative.cfg"

  process_enable "${prefix}/enableserial.cfg"

  process_enable "${prefix}/searchfloppy.cfg"

  process_enable "${prefix}/searchcdrom.cfg"

  process_enable "${prefix}/searchsgd.cfg"

  source "${prefix}/return.cfg"

}
menuentry $"Print devices/partitions" {
  # Set pager=1 so ls output doesn't scroll past the top of the screen
  # but restore $pager to its previous value when finished
  set oldpager="${pager}"
  set pager=1

  ls -l

  set pager="${oldpager}"
  unset oldpager

  echo $"Press escape to return to the main menu"
  sleep --interruptible 9999
}

menuentry $"Color ON/OFF" {
  if [ "$bwcolor" = 'yes' ]; then
    bwcolor=no
  else
    bwcolor=yes
  fi

  set_sgd_colors
}

submenu $"Exit""${three_dots_str}" {

  submenu_title

  process_option "${prefix}/halt.cfg"
  process_option "${prefix}/reboot.cfg"

  source "${prefix}/return.cfg"
}

# If it exists, source $prefix/sgd_custom.cfg. This follows the same idea as
# grub-mkconfig generated grub.cfg files sourcing $prefix/custom.cfg, though
# it's less needed here since one could add custom code to this file directly
# whereas their distro might automatically overwrite /boot/grub/grub.cfg on
# kernel upgrades. The main motivation for adding this was the vmtest script
# which I use heavily during Super GRUB2 Disk development, but this feature
# might also be useful to others.
if [ -e "$prefix/sgd_custom.cfg" ]; then
  source "$prefix/sgd_custom.cfg"
fi

6.插上SD卡,断电,然后冷重启

7.常见问题

注意:如果安装升级的是`ubuntu 20.04.1`,系统启动的时候,会出现`error: symbol 'grub_calloc' not found`。

如下图:

但是奇怪的是,如果在系统启动的时候选择`Detect and show boot methods`,显示出的任何菜单,都可以正常启动系统,如下:

这个问题是因为`ubuntu 20.04.1`系统使用的是新版本的`grub2`引导系统,启动配置信息需要进行调整,修改我们创建的`\boot\grub\Gen8.cfg`(Super GRUB2 Disk 2.02)或者`\boot\grub\sgd\Gen8.cfg`(Super GRUB2 Disk 2.04),内容调整为如下:

# Super Grub Disk - Gen8.cfg

set option_title=$"Gen8"

function run_option {
  echo "Starting Gen8 ..."
  #for uuid
  #search --set=root --fs-uuid 763A-9CB6
  #for file
  insmod xfs
  search --set=root --file /boot/grub/grubenv
  # debian 12.5 for file 
  #search --set=root --file /boot/grub/unicode.pf2
  configfile /boot/grub/grub.cfg
}

参考链接


  1. 制作HP MicroServer Gen8可用的ESXi 5.x SD/TF卡启动盘
  2. HP ProLiant MicroServer Gen8迷你服务器 汇总贴:降噪、升级、改造
  3. Rescatux & Super Grub2 Disk
  4. Rufus
  5. HP ProLiant MicroServer Gen8 资源:SPP 2016.04、iLO4 2.40
  6. Gen8通过TF卡启动光驱位硬盘的新办法(增减硬盘无需改动)
  7. GRUB
  8. GNU GRUB Manual 2.04
  9. grub boot error : "symbol 'grub_calloc' not found

gitlab: MergeRequest、reset

Merge request

什么是Merge request,多人开发项目时,发起将一个远程分支merge到另一个分支(一般为主分支)的请求。

merge request步骤:

1.如果开发完了某个模块的功能,需要提交到线上。

2.首先,git fetch --all,仓库代码图拉下来,把线上的代码更新后合并到自己的本地分支上。

3.解决冲突

4.再次合并代码,没有问题后,git push origin 本地分支名。这样就会在远程仓库创建一个remotes/origin/本地分支名 的分支。

5.gitlab上,,进入mergeRequest页面,选择newMergeRequest(右上角绿色按钮)。merge

6.选择要merge的source分支,CONTINUE。

7.填写描述,添加reviwer(重要)。

8.等待结果,如果有冲突,需要从头再来。

Reset

bg2014061202

 

git reset 撤销命令

reset有三种模式

git reset --soft HEAD 本地文件不变,撤销掉commit,不撤销index

git reset --hard HEAD 本地文件改为HEAD,所有commit/index都修改掉

git reset HEAD     本地文件不变,撤销掉commit/index

 

如果只想恢复某一个文件,只需要

git checkout filename

恢复所有文件到当前HEAD

git checkout .