0、写在前面的话

如何实现微信平台后台管理中的,图文消息发送功能?

大概的过程如下:
  • 通过类似表单的形式,将文章各部分内容提交到后台,封装成一个实体类,并持久化到数据库中
  • 需要推送的时候,将不同的文章选择取出交给后台,由后台组装成规范化的数据结构,调用微信的图文消息素材上传和群发接口
  • 其中文章的主体部分,我们采用UEditor富文本编辑器

本文主要针对模拟表单的图片上传,以及结合UEditor进行图文内图片上传,进行重点说明。细节和具体代码流程就不再详细展开了。

参考链接:

1、什么是图文消息

所谓图文消息,就是我们常在订阅号中收到的如下图所示的消息类型:

 
微信开发者文档 - 消息管理 - 发送消息 - 群发接口和原创校验 - 上传图文消息素材 中可以看到,图文消息就是文章的集合,每条图文消息包含的文章不得超过8条。

我们从开发文档中上传图文消息素材的POST格式来看,一个文章大概包含的内容有:
{
"articles": [
{
"thumb_media_id":"qI6_Ze_6PtV7svjolgs-rN6stStuHIjs9_DidOHaj0Q-mwvBelOXCFZiq2OsIU-p",
"author":"xxx",
"title":"Happy Day",
"content_source_url":"www.qq.com",
"content":"content",
"digest":"digest",
"show_cover_pic":1
},
{
"thumb_media_id":"qI6_Ze_6PtV7svjolgs-rN6stStuHIjs9_DidOHaj0Q-mwvBelOXCFZiq2OsIU-p",
"author":"xxx",
"title":"Happy Day",
"content_source_url":"www.qq.com",
"content":"content",
"digest":"digest",
"show_cover_pic":0
}
]
}
22
 
1
{ 
2
   "articles": [
3
         {
4
             "thumb_media_id":"qI6_Ze_6PtV7svjolgs-rN6stStuHIjs9_DidOHaj0Q-mwvBelOXCFZiq2OsIU-p",
5
             "author":"xxx",
6
             "title":"Happy Day",
7
             "content_source_url":"www.qq.com",
8
             "content":"content",
9
             "digest":"digest",
10
             "show_cover_pic":1
11
         },
12
         {
13
             "thumb_media_id":"qI6_Ze_6PtV7svjolgs-rN6stStuHIjs9_DidOHaj0Q-mwvBelOXCFZiq2OsIU-p",
14
             "author":"xxx",
15
             "title":"Happy Day",
16
             "content_source_url":"www.qq.com",
17
             "content":"content",
18
             "digest":"digest",
19
             "show_cover_pic":0
20
         }
21
   ]
22
}

参数     是否必须     说明
Articles 图文消息,一个图文消息支持1到8条图文
thumb_media_id 图文消息缩略图的media_id,可以在基础支持-上传多媒体文件接口中获得
author 图文消息的作者
title 图文消息的标题
content_source_url 在图文消息页面点击“阅读原文”后的页面,受安全限制,如需跳转Appstore,可以使用itun.es或appsto.re的短链服务,并在短链后增加 #wechat_redirect 后缀。
content 图文消息页面的内容,支持HTML标签。具备微信支付权限的公众号,可以使用a标签,其他公众号不能使用
digest 图文消息的描述
show_cover_pic 是否显示封面,1为显示,0为不显示

而本篇博客要解决的,就是如何获取上图红色部分的 thumb_media_id 和 content,后者主要也是阐述其内容中的图片上传过程。

2、群发图文消息的过程和UEditor使用

由微信的官方文档可知,群发图文消息的过程如下:
  1. 首先,调用接口上传封面缩略图素材,获取thumb_media_id;(目标1)
  2. 然后将图文消息中需要用到的图片,使用上传图文消息内图片接口,上传成功并获得图片 URL;(目标2)
  3. 上传图文消息素材,需要用到图片时,请使用上一步获取的图片 URL;
  4. 使用对用户标签的群发,或对 OpenID 列表的群发,将图文消息群发出去,群发时微信会进行原创校验,并返回群发操作结果;
  5. 在上述过程中,如果需要,还可以预览图文消息、查询群发状态,或删除已群发的消息等。

我们的目标很明确,下面就开始按步骤进行说明。

2.1 启用UEditor

UEditor是百度的一款富文本编辑器,用它的主要目的是因为它配置比较灵活,所见即所得的友好体验。这里只会提供编辑器部分,其他诸如标题作者,需要自行在网页添加,此处不再阐述。

简单说来,最终我们是为了实现一个类似提交表单的功能,把各部分内容最后封装到一个实体类中,而UEditor部分就是用来封装文章的content的。


UEditor的安装和工具栏设置等不是本文的讨论重点,此处请自行谷歌,或按照官方文档进行部署。网页上部署好以后(如下图中的部分3),再自行添加如标题输入,作者输入等input标签,得到效果如下:

参考链接:

 

2.2 封面和文章内图片上传

2.2.1 上传封面缩略图永久素材

在永久图文消息中,要求缩略图也是永久素材类型,所以我们这里调用的接口是 “新增其他类型的永久素材的接口”,如下所示(这里type我们要用thumb):
http请求方式: POST,需使用https
https://api.weixin.qq.com/cgi-bin/material/add_material?access_token=ACCESS_TOKEN&type=TYPE 调用示例(使用curl命令,用FORM表单方式新增一个其他类型的永久素材,curl命令的使用请自行查阅资料)
4
 
1
http请求方式: POST,需使用https
2
https://api.weixin.qq.com/cgi-bin/material/add_material?access_token=ACCESS_TOKEN&type=TYPE
3

4
调用示例(使用curl命令,用FORM表单方式新增一个其他类型的永久素材,curl命令的使用请自行查阅资料)

参数说明:
  • access_token(必需) 调用接口凭证
  • type(必需) 媒体文件类型,分别有图片(image)、语音(voice)、视频(video)和缩略图(thumb)
  • media(必需) form-data中媒体文件标识,有filename、filelength、content-type等信息

那么问题来了,为了避免接口次数的频繁使用和浪费,我们在标题0中提到的过程是,将文章各部分内容提交到后台,封装成一个实体类,并持久化到数据库中。需要推送的时候,再取出来调用微信接口进行上传和推送(而不是编辑的时候就调用微信的接口)。

也就是说,缩略图信息,都是提前写好并整合封装在某个类中的(或者放在数据库中),这意味着我们要在推送消息的时候从内容里把需要上传的东西抽出来再去调用接口,获取返回值后再组装微信格式的图文消息发送。

而这里的接口要求使用表单的形式提交才能生效,但用这种方式,密钥信息accessToken就暴露在页面中,并且也获取不到上传成功后的媒体ID。怎么办?

解决办法(参考链接):先把文件上传到服务器(数据库),从服务器获取文件,然后用HttpURLConnection同下面微信调用接口的url建立连接
public static String uploadTemp(File file, String url) throws IOException {
String REQUEST_METHOD = "POST";
String result = null; //请求地址
URL urlObj = new URL(url); //连接
HttpURLConnection connection = (HttpURLConnection) urlObj.openConnection(); //设置关键值
//POST提交表单,默认是GET
connection.setRequestMethod(REQUEST_METHOD);
connection.setDoInput(true);
connection.setDoOutput(true);
//POST方式无法使用缓存
connection.setUseCaches(false); //设置请求头信息
connection.setRequestProperty("Connection", "Keep-Alive");
connection.setRequestProperty("Charset", "UTF-8"); //设置边界
String BOUNDARY = "----------" + System.currentTimeMillis();
connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY); //请求正文信息
//part1 开头
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("--"); // 必须多两道线
stringBuilder.append(BOUNDARY);
stringBuilder.append("\r\n");
stringBuilder.append("Content-Disposition: form-data;name=\"file\";filename=\"" + file.getName() + "\"\r\n");
stringBuilder.append("Content-Type:application/octet-stream\r\n\r\n"); byte[] head = stringBuilder.toString().getBytes("utf-8"); //获得输出流
OutputStream out = new DataOutputStream(connection.getOutputStream());
//输出表头
out.write(head); //part2 文件正文
//把文件以流文件的方式,推入到url中
DataInputStream in = new DataInputStream(new FileInputStream(file));
int bytes = 0;
byte[] bufferOut = new byte[1024];
while ((bytes = in.read(bufferOut)) != -1) {
out.write(bufferOut, 0, bytes);
}
in.close(); //part3 结尾部分
byte[] foot = ("\r\n--" + BOUNDARY + "--\r\n").getBytes("utf-8");// 定义最后数据分隔线
out.write(foot); out.flush();
out.close(); StringBuffer buffer = new StringBuffer();
BufferedReader reader = null;
try {
// 定义BufferedReader输入流来读取URL的响应
reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line = null;
while ((line = reader.readLine()) != null) {
buffer.append(line);
}
if (result == null) {
result = buffer.toString();
}
} catch (IOException e) {
log.error("发送POST请求出现异常!" + e);
e.printStackTrace();
throw new IOException("数据读取异常");
} finally {
if(reader!=null) {
reader.close();
}
} return result;
}
85
 
1
public static String uploadTemp(File file, String url) throws IOException {
2
    String REQUEST_METHOD = "POST";
3
    String result = null;
4

5
    //请求地址
6
    URL urlObj = new URL(url);
7

8
    //连接
9
    HttpURLConnection connection = (HttpURLConnection) urlObj.openConnection();
10

11
    //设置关键值
12
    //POST提交表单,默认是GET
13
    connection.setRequestMethod(REQUEST_METHOD);
14
    connection.setDoInput(true);
15
    connection.setDoOutput(true);
16
    //POST方式无法使用缓存
17
    connection.setUseCaches(false);
18

19
    //设置请求头信息
20
    connection.setRequestProperty("Connection", "Keep-Alive");
21
    connection.setRequestProperty("Charset", "UTF-8");
22

23
    //设置边界
24
    String BOUNDARY = "----------" + System.currentTimeMillis();
25
    connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY);
26

27
    //请求正文信息
28
    //part1 开头
29
    StringBuilder stringBuilder = new StringBuilder();
30
    stringBuilder.append("--"); // 必须多两道线
31
    stringBuilder.append(BOUNDARY);
32
    stringBuilder.append("\r\n");
33
    stringBuilder.append("Content-Disposition: form-data;name=\"file\";filename=\"" + file.getName() + "\"\r\n");
34
    stringBuilder.append("Content-Type:application/octet-stream\r\n\r\n");
35

36
    byte[] head = stringBuilder.toString().getBytes("utf-8");
37

38
    //获得输出流
39
    OutputStream out = new DataOutputStream(connection.getOutputStream());
40
    //输出表头
41
    out.write(head);
42

43
    //part2 文件正文
44
    //把文件以流文件的方式,推入到url中
45
    DataInputStream in = new DataInputStream(new FileInputStream(file));
46
    int bytes = 0;
47
    byte[] bufferOut = new byte[1024];
48
    while ((bytes = in.read(bufferOut)) != -1) {
49
        out.write(bufferOut, 0, bytes);
50
    }
51
    in.close();
52

53
    //part3 结尾部分
54
    byte[] foot = ("\r\n--" + BOUNDARY + "--\r\n").getBytes("utf-8");// 定义最后数据分隔线
55
    out.write(foot);
56

57

58
    out.flush();
59
    out.close();
60

61

62
    StringBuffer buffer = new StringBuffer();
63
    BufferedReader reader = null;
64
    try {
65
        // 定义BufferedReader输入流来读取URL的响应
66
        reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
67
        String line = null;
68
        while ((line = reader.readLine()) != null) {
69
            buffer.append(line);
70
        }
71
        if (result == null) {
72
            result = buffer.toString();
73
        }
74
    } catch (IOException e) {
75
        log.error("发送POST请求出现异常!" + e);
76
        e.printStackTrace();
77
        throw new IOException("数据读取异常");
78
    } finally {
79
        if(reader!=null) {
80
            reader.close();
81
        }
82
    }
83

84
    return result;
85
}

最终返回的result字符串,是一个JSON格式的微信服务器返回的值,其中我们可以提取其中的media_id。

2.2.2 结合UEditor实现图文内图片消息的图片上传

上传图文消息内的图片,有专门的接口,这个接口上传的图片是不占用永久素材的数量限制的。调用该接口,可以获得图片的URL,这里的调用方式,也是和如上一样使用表单模拟提交的方式,这里主要讲解的是,如何结合UEditor。

UEditor编辑器中的图片上传,我们需要修改其上传后的地址,也就是说希望它把图片以流的形式给我们自己写的Action,再由我们的Action把文件提交给微信服务器以获取URL,再放到文章中去。

首先,更改编辑器的上传图片的后台地址,重写其js脚本部分的getActionUrl方法,定义跳转到自己的Action:
ue = UE.getEditor("editor");
//重写UEDITOR的getActionUrl 方法,定义自己的Action,上传图片保存至相应的位置
UE.Editor.prototype._bkGetActionUrl = UE.Editor.prototype.getActionUrl;
UE.Editor.prototype.getActionUrl = function (action) {
if (action == 'uploadimage' || action == 'uploadfile') {
var id = $('#carInfoId').val();
//修改定义自己的Action
return '/admin/news/do/ueditor_uploadNewsImage.q';
} else {
return this._bkGetActionUrl.call(this, action);
}
};
12
 
1
ue = UE.getEditor("editor");
2
//重写UEDITOR的getActionUrl 方法,定义自己的Action,上传图片保存至相应的位置
3
UE.Editor.prototype._bkGetActionUrl = UE.Editor.prototype.getActionUrl;
4
UE.Editor.prototype.getActionUrl = function (action) {
5
    if (action == 'uploadimage' || action == 'uploadfile') {
6
        var id = $('#carInfoId').val();
7
        //修改定义自己的Action
8
        return '/admin/news/do/ueditor_uploadNewsImage.q';
9
    } else {
10
        return this._bkGetActionUrl.call(this, action);
11
    }
12
};

由于提交过来的是tmp格式,直接调用微信接口会出现类型错误的返回值,所以在Action中我们要将图片格式转换一下再调用微信接口:

参考链接:

/**
* ueditor编辑器上传图片到微信服务器
* <p>该部分是使用了百度的富文本web编辑器UEditor,以及微信的图文消息内图片上传接口</p>
*
* @throws IOException
*/
public void ueditor_uploadNewsImage() throws IOException {
WeChatUtil weChatUtil = new WeChatUtil();
String accessToken = weChatUtil.getAccessToken();
HttpServletResponse response = ServletActionContext.getResponse(); //struts请求 包装过滤器
MultiPartRequestWrapper wrapper = (MultiPartRequestWrapper) ServletActionContext.getRequest();
//获取文件过滤器
File tmp = wrapper.getFiles("upfile")[0];
//获得上传的文件名
String filename = wrapper.getFileNames("upfile")[0]; int suffixIndex = filename.indexOf(".");
//文件后缀确定
if (suffixIndex == filename.lastIndexOf(".")) {
String suffix = filename.substring(suffixIndex);
File file = File.createTempFile("tmp", suffix);
FileCopyUtils.copy(tmp, file); String imageUrl = weChatUtil.uploadImg(accessToken, file); //这里最终还是调用的表单模拟的那个方法
//如果获取到了有效的imageUrl
if (imageUrl != null && imageUrl.substring(0, 4).equals("http")) {
String json = "{\"state\":\"SUCCESS\", \"url\":\"%s\", \"title\":\"%s\", \"original\":\"%s\"}";
json = String.format(json, imageUrl, filename, filename);
log.debug("上传图片返回信息:" + json);
response.getWriter().write(json);
}
}
}
x
 
1
/**
2
 * ueditor编辑器上传图片到微信服务器
3
 * <p>该部分是使用了百度的富文本web编辑器UEditor,以及微信的图文消息内图片上传接口</p>
4
 *
5
 * @throws IOException
6
 */
7
public void ueditor_uploadNewsImage() throws IOException {
8
    WeChatUtil weChatUtil = new WeChatUtil();
9
    String accessToken = weChatUtil.getAccessToken();
10
    HttpServletResponse response = ServletActionContext.getResponse();
11

12
    //struts请求 包装过滤器
13
    MultiPartRequestWrapper wrapper = (MultiPartRequestWrapper) ServletActionContext.getRequest();
14
    //获取文件过滤器
15
    File tmp = wrapper.getFiles("upfile")[0];
16
    //获得上传的文件名
17
    String filename = wrapper.getFileNames("upfile")[0];
18

19
    int suffixIndex = filename.indexOf(".");
20
    //文件后缀确定
21
    if (suffixIndex == filename.lastIndexOf(".")) {
22
        String suffix = filename.substring(suffixIndex);
23
        File file = File.createTempFile("tmp", suffix);
24
        FileCopyUtils.copy(tmp, file);
25

26
        String imageUrl = weChatUtil.uploadImg(accessToken, file); //这里最终还是调用的表单模拟的那个方法
27
        //如果获取到了有效的imageUrl
28
        if (imageUrl != null && imageUrl.substring(0, 4).equals("http")) {
29
            String json = "{\"state\":\"SUCCESS\", \"url\":\"%s\", \"title\":\"%s\", \"original\":\"%s\"}";
30
            json = String.format(json, imageUrl, filename, filename);
31
            log.debug("上传图片返回信息:" + json);
32
            response.getWriter().write(json);
33
        }
34
    }
35
}

另外,需要注意的是,UEditor要求后端返回固定格式的返回码才能实现图片插入到文本编辑内容中。注意这里的json返回码格式,内部必须使用双引号,使用单引号替代是无效的。

上面两个就是该部分的核心内容,还有一个小地方需要修改,如果使用的Struts框架,那么要注意,由于Struts2框架默认使用Apache的Commons FileUpload组件和内建的FileUploadInterceptor拦截器实现文件上传,将request中的文件域封装到action中的一个File类型的属性中,并删除request中的原有文件域,因此直接用Ueditor上传文件会失败。

解决的方法是自己写一个Filter替代Struts2的Filter,排除掉ueditor的controller.jsp文件。

参考链接:

public class MyStrutsFilter extends StrutsPrepareAndExecuteFilter {
public void doFilter(ServletRequest req, ServletResponse res,FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
//不过滤的url
String url = request.getRequestURI();
System.out.println(url);
try {
if (url.contains("/controller.jsp")) {
System.out.println("使用自定义的过滤器");
chain.doFilter(req, res);
} else {
System.out.println("使用默认的过滤器");
super.doFilter(req, res, chain);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
19
 
1
public class MyStrutsFilter extends StrutsPrepareAndExecuteFilter {
2
    public void doFilter(ServletRequest req, ServletResponse res,FilterChain chain) throws IOException, ServletException {
3
        HttpServletRequest request = (HttpServletRequest) req;
4
        //不过滤的url
5
        String url = request.getRequestURI();
6
        System.out.println(url);
7
        try {
8
            if (url.contains("/controller.jsp")) {
9
                System.out.println("使用自定义的过滤器");
10
                chain.doFilter(req, res);
11
            } else {
12
                System.out.println("使用默认的过滤器");
13
                super.doFilter(req, res, chain);
14
            }
15
        } catch (Exception e) {
16
            e.printStackTrace();
17
        }
18
    }
19
}

2.3 图文素材上传和发送

到这里,我们再来看下上传图文永久素材所需要的格式:
{
"articles": [
{
"thumb_media_id":"qI6_Ze_6PtV7svjolgs-rN6stStuHIjs9_DidOHaj0Q-mwvBelOXCFZiq2OsIU-p",
"author":"xxx",
"title":"Happy Day",
"content_source_url":"www.qq.com",
"content":"content",
"digest":"digest",
"show_cover_pic":1
},
{
"thumb_media_id":"qI6_Ze_6PtV7svjolgs-rN6stStuHIjs9_DidOHaj0Q-mwvBelOXCFZiq2OsIU-p",
"author":"xxx",
"title":"Happy Day",
"content_source_url":"www.qq.com",
"content":"content",
"digest":"digest",
"show_cover_pic":0
}
]
}
 
1
{
2
   "articles": [
3
         {
4
             "thumb_media_id":"qI6_Ze_6PtV7svjolgs-rN6stStuHIjs9_DidOHaj0Q-mwvBelOXCFZiq2OsIU-p",
5
             "author":"xxx",
6
             "title":"Happy Day",
7
             "content_source_url":"www.qq.com",
8
             "content":"content",
9
             "digest":"digest",
10
             "show_cover_pic":1
11
         },
12
         {
13
             "thumb_media_id":"qI6_Ze_6PtV7svjolgs-rN6stStuHIjs9_DidOHaj0Q-mwvBelOXCFZiq2OsIU-p",
14
             "author":"xxx",
15
             "title":"Happy Day",
16
             "content_source_url":"www.qq.com",
17
             "content":"content",
18
             "digest":"digest",
19
             "show_cover_pic":0
20
         }
21
   ]
22
}
  • thumb_media_id 上传封面缩略图后返回的media_id,我们已经说明了
  • author 这个写个input就可以搞定
  • title 同上
  • content_source_url 非必需
  • content 这里就是我们用UEditor编辑器中的内容了,其中重点是图片上传,我们已经说明了
  • digest 摘要,加input解决
  • show_cover_pic 非必需

东西都有了,调用接口也就简单了。然后接着调用群发的接口,也就很容易了。这里主要参考文档(微信公众平台技术文档 - 消息管理 - 发送消息 - 群发接口和原创校验)就行了,时间精力有限,就不再展开了。


[3] 微信公众号开发 - 结合UEditor实现图文消息群发功能的更多相关文章

  1. 微信公众号开发C#系列-7、消息管理-接收事件推送

    1.概述 在微信用户和公众号产生交互的过程中,用户的某些操作会使得微信服务器通过事件推送的形式通知到开发者在开发者中心处设置的服务器地址,从而开发者可以获取到该信息.其中,某些事件推送在发生后,是允许 ...

  2. 微信公众号开发 [03] 结合UEditor实现图文消息群发功能

    0.写在前面的话 如何实现微信平台后台管理中的,图文消息发送功能? 大概的过程如下: 通过类似表单的形式,将文章各部分内容提交到后台,封装成一个实体类,并持久化到数据库中 需要推送的时候,将不同的文章 ...

  3. C#微信公众号开发系列教程三(消息体签名及加解密)

    http://www.cnblogs.com/zskbll/p/4139039.html C#微信公众号开发系列教程一(调试环境部署) C#微信公众号开发系列教程一(调试环境部署续:vs远程调试) C ...

  4. 微信公众号开发C#系列-6、消息管理-普通消息接受处理

    1.概述 通过前面章节的学习,我们已经对微信的开发有了基本的掌握与熟悉,基本可以上手做复杂的应用了.本篇我们将详细讲解微信消息管理中普通消息的接收与处理.当普通微信用户向公众账号发消息时,微信服务器将 ...

  5. C#微信公众号开发-高级接口-之模板消息开发,附源码

    个人觉得模板消息功能的增加对公众号的作用非常大,可以说是真正意义上的实现了所谓的轻app,商家可以通过模板消息给用户发送重要的信息,交易.预约.消费.邮件.物流等信息.之前我做过的系统通过邮件发送订单 ...

  6. Java微信公众平台开发(五)--文本及图文消息回复的实现

    转自:http://www.cuiyongzhi.com/post/43.html 上篇我们说到回复消息可以根据是否需要上传文件到微信服务器可划分为[普通消息]和[多媒体消息],这里我们来讲述普通消息 ...

  7. C#微信公众号开发系列教程六(被动回复与上传下载多媒体文件)

    微信公众号开发系列教程一(调试环境部署) 微信公众号开发系列教程一(调试环境部署续:vs远程调试) C#微信公众号开发系列教程二(新手接入指南) C#微信公众号开发系列教程三(消息体签名及加解密) C ...

  8. C#微信公众号开发系列教程五(接收事件推送与消息排重)

    微信公众号开发系列教程一(调试环境部署) 微信公众号开发系列教程一(调试环境部署续:vs远程调试) C#微信公众号开发系列教程二(新手接入指南) C#微信公众号开发系列教程三(消息体签名及加解密) C ...

  9. C#微信公众号开发系列教程四(接收普通消息)

    微信公众号开发系列教程一(调试环境部署) 微信公众号开发系列教程一(调试环境部署续:vs远程调试) C#微信公众号开发系列教程二(新手接入指南) C#微信公众号开发系列教程三(消息体签名及加解密) C ...

随机推荐

  1. java版飞机大战 实战项目详细步骤.md

    [toc] 分析 飞机大战 首先对这个游戏分析,在屏幕上的物体都是飞行物,我们可以把建一个类,让其他飞行物继承这个类.游戏中应有英雄机(也就是自己控制的飞机).敌人.而敌人应该分为打死给分的飞机(就是 ...

  2. 7.21.01 if语句

    if语句 一个if语句包含一个布尔表达式和一条或多条语句. 语法 if语句的用语法如下: if(布尔表达式) { //如果布尔表达式为true将执行的语句 } 如果布尔表达式的值为true,则执行if ...

  3. PHPcms9.6.0任意文件上传漏洞直接getshell 利用教程

    对于PHPcms9.6.0 最新版漏洞,具体利用步骤如下: 首先我们在本地搭建一个php环境,我这里是appserv或者使用phpnow (官网下载地址:http://servkit.org/) (只 ...

  4. HTTPS 证书配置

    HTTPS 证书配置 现在阿里云和腾讯云都支持申请 HTTPS 证书,这里不再提,有需要的可自行google解决方案. 本文主要介绍的是通过 letsencrypt 申请免费的HTTPS证书,并将其配 ...

  5. 12块钱搭建一个ss(包括一个免费服务器)

    AWS搭建ss(shadow socks) 如果你符合以下条件,那么继续看还是有点帮助的: 想搞个服务器(包括windows Linux)(免费) 想科学上网(也免费) 之前也从网上搜过本文内容,但手 ...

  6. jmeter性能测试 套路二

    1.一般我们不会通过下面这种去跑性能测试 2.我们会通过这种方式去跑性能测试 3.录制自动化 就用新的 4.录制性能测试  就用

  7. 插入排序与希尔排序Java实现

    public class TestMain { public static void main(String[] args) { Integer[] a = new Integer[5000]; fo ...

  8. Wampserver查看php配置信息

    Wampserver安装完成之后输入localhost会有欢迎Wampserver界面. [查看php配置信息]:在页面点击"phpinfo()"进入php配置信息页面. [使用p ...

  9. 原创:LNMP架构部署个人博客网站 禁止转载复制

    nginx编译安装步骤 ①. 检查软件安装的系统环境 cat /etc/redhat-release uname -r ②. 安装nginx的依赖包(pcre-devel openssl-devel) ...

  10. WPF的TextBox水印效果详解

    一种自以为是的方式: 本来只是想简单的做个水印效果,在文本框内容为空的时候提示用户输入,这种需求挺常见.网上一搜 都是丢给你你一大段xaml代码.用c#代码实现我是不倾向了 既然用wpf就得Xaml啊 ...