一、需求概述

多人聊天(群组,讨论组,聊天室,以下统称: “群组” )生成一个拼接头像,需要把最先加入群组的几个人(最多4个人,以下简称:头部用户A、B、C、D)的头像拼凑成在一起。

群组创建后,A、B、C、D其中任何一个修改了自己的头像,需要 "异步" 更新群组头像。

以上是简单的需求描述。

本文使用.net core实现了N张图片拼接算法。

完整代码点击:https://github.com/night-king/ImageMerge

二、方案实现探讨

本需求可以在服务端实现,也可以在客户端实现。

(1)服务端实现:

群组创建时,下载A、B、C、D的头像,然后合并即可;

头部用户修改头像后,后台任务刷新群组头像。

(2)客户端实现:客户端包括iOS,Android,React,Web。

情况有些复杂,所以最后决定直接采用服务端实现。

Github上搜索无果,只能自己动手完成了。

先看效果:

采用四位Github大神头像作为源图片:

    

结果如下:

三、解决方案

群组2个用户,3个用户,4个以及以上用户头像不一样,总结如下:

    /// <summary>
/// 合并布局枚举
/// </summary>
public enum Merge2LayoutEnum
{
/// <summary>
/// 2张图片,上下各1个长方形
/// ———————————————————
/// | |
/// | R1 |
/// | |
/// ———————————————————
/// | |
/// | R2 |
/// | |
/// ———————————————————
/// </summary>
Merge2R1 = , /// <summary>
/// 2张图片,左右各1个长方形
/// ———————————————————
/// | | |
/// | | |
/// | | |
/// | R1 | R2 |
/// | | |
/// | | |
/// | | |
/// ———————————————————
/// </summary>
Merge2R2 =
} public enum Merge3LayoutEnum
{ /// <summary>
/// 3张图片, 上面一个长方形,下面2个正方形并排
/// ———————————————————
/// | |
/// | R1 |
/// | |
/// ———————————————————
/// | | |
/// | S1 | S2 |
/// | | |
/// ———————————————————
/// </summary>
Merge1R2S1 = , /// <summary>
/// 3张图片,上面2个正方形并排,下面一个长方形
/// ———————————————————
/// | | |
/// | S1 | S2 |
/// | | |
/// ———————————————————
/// | |
/// | R1 |
/// | |
/// ———————————————————
/// </summary>
Merge1R2S2 = , /// <summary>
/// 3张图片,上面2个正方形并排,下面一个长方形
/// ———————————————————
/// | | |
/// | | S1 |
/// | | |
/// | R1 |—————————
/// | | |
/// | | S2 |
/// | | |
/// ———————————————————
/// </summary>
Merge1R2S3 = , /// <summary>
/// 3张图片,上面2个正方形并排,下面一个长方形
/// ———————————————————
/// | | |
/// | S2 | |
/// | | |
/// |—————————| R1 |
/// | | |
/// | S2 | |
/// | | |
/// ———————————————————
/// </summary>
Merge1R2S4 =
} public enum Merge4LayoutEnum
{
/// <summary>
/// 4张图片,上面一个长方形,下面2个正方形并排
/// ———————————————————
/// | | |
/// | S1 | S2 |
/// | | |
/// ———————————————————
/// | | |
/// | S3 | S4 |
/// | | |
/// ———————————————————
/// </summary>
Merge4S = }

以下是2张图片实现代码:

        /// <summary>
/// 合并2张图片
/// </summary>
/// <param name="image1"></param>
/// <param name="image2"></param>
/// <param name="layout"></param>
/// <returns></returns>
public static Image Merge2Images(Image image1, Image image2, Merge2LayoutEnum layout = Merge2LayoutEnum.Merge2R1, int size = )
{
var width = size;
var height = size;
var pf = PixelFormat.Format32bppArgb;
using (var bg = new Bitmap(width, height, pf))
{
using (var g = Graphics.FromImage(bg))
{
g.FillRectangle((Brush)Brushes.White, , , width, height);//全幅背景为白色
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
switch (layout)
{
/// <summary>
/// 2张图片,上下各1个长方形
/// ———————————————————
/// | |
/// | R1 |
/// | |
/// ———————————————————
/// | |
/// | R2 |
/// | |
/// ———————————————————
/// </summary>
case Merge2LayoutEnum.Merge2R1:
{
using (var img1 = ZoomToSqure(image1, size))
{
var newHeight = height / ;// =125
var newWidth = width;// =250
var srcWidth = img1.Width;// =250
var srcHeight = img1.Height * newHeight / newWidth;//=250*125/250=125
g.DrawImage(img1, new Rectangle(, , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
using (var img2 = ZoomToSqure(image2, size))
{
var newHeight = height / ;// =125
var newWidth = width; // =250
var srcWidth = img2.Width;// =250
var srcHeight = img2.Height * newHeight / newWidth;//=250*125/250=125
g.DrawImage(img2, new Rectangle(, height / , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
}
break;
/// <summary>
/// 2张图片,左右各1个长方形
/// ———————————————————
/// | | |
/// | | |
/// | | |
/// | R1 | R2 |
/// | | |
/// | | |
/// | | |
/// ———————————————————
/// </summary>
case Merge2LayoutEnum.Merge2R2:
{
using (var img1 = ZoomToSqure(image1, size))
{
var newWidth = width / ;// =125
var newHeight = height; // =250
var srcWidth = img1.Width * newWidth / newHeight;//=250*125/250=125
var srcHeight = img1.Height;// =250
g.DrawImage(img1, new Rectangle(, , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
using (var img2 = ZoomToSqure(image2, size))
{
var newWidth = width / ;// =125
var newHeight = height; // =250
var srcWidth = img2.Width * newWidth / newHeight;//=500*125/250=250
var srcHeight = img2.Height;// =500
g.DrawImage(img2, new Rectangle(width / , , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
}
break;
}
g.Save();
image1.Dispose();
image2.Dispose();
} using (var ms = new MemoryStream())
{
bg.Save(ms, ImageFormat.Png);
var buffers = ms.ToArray();
return ConvertToImage(buffers);
}
}
}

以下是三种图片的生成算法

        /// <summary>
/// 合并3张图片
/// </summary>
/// <param name="image1"></param>
/// <param name="image2"></param>
/// <param name="image3"></param>
/// <param name="layout"></param>
/// <returns></returns>
public static Image Merge3Images(Image image1, Image image2, Image image3, Merge3LayoutEnum layout = Merge3LayoutEnum.Merge1R2S1, int size = )
{
var width = size;
var height = size;
var pf = PixelFormat.Format32bppArgb;
using (var bg = new Bitmap(width, height, pf))
{
using (var g = Graphics.FromImage(bg))
{
g.FillRectangle((Brush)Brushes.White, , , width, height);//全幅背景为白色
switch (layout)
{
/// <summary>
/// 3张图片, 上面一个长方形,下面2个正方形并排
/// ———————————————————
/// | |
/// | R1 |
/// | |
/// ———————————————————
/// | | |
/// | S1 | S2 |
/// | | |
/// ———————————————————
/// </summary>
case Merge3LayoutEnum.Merge1R2S1:
{
using (var img1 = ZoomToSqure(image1, size))
{
var newHeight = height / ;// =125
var newWidth = width;// =250
var srcWidth = img1.Width;// =250
var srcHeight = img1.Height * newHeight / newWidth;//=250*125/250=125
g.DrawImage(img1, new Rectangle(, , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
using (var img2 = ZoomToSqure(image2, size))
{
var newHeight = height / ;// =125
var newWidth = width / ; // =125
var srcWidth = img2.Width; // =250
var srcHeight = img2.Height;// =250
g.DrawImage(img2, new Rectangle(, height / , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
using (var img3 = ZoomToSqure(image3, size))
{
var newHeight = height / ; // =125
var newWidth = width / ; // =125
var srcWidth = img3.Width; // =250
var srcHeight = img3.Height;// =250
g.DrawImage(img3, new Rectangle(width / , height / , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
}
break;
/// <summary>
/// 3张图片,上面2个正方形并排,下面一个长方形
/// ———————————————————
/// | | |
/// | S1 | S2 |
/// | | |
/// ———————————————————
/// | |
/// | R1 |
/// | |
/// ———————————————————
/// </summary>
case Merge3LayoutEnum.Merge1R2S2:
{
using (var img1 = ZoomToSqure(image1, size))
{
var newHeight = height / ;// =125
var newWidth = width / ; // =125
var srcWidth = img1.Width; // =250
var srcHeight = img1.Height;// =250
g.DrawImage(img1, new Rectangle(, , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
using (var img2 = ZoomToSqure(image2, size))
{
var newHeight = height / ; // =125
var newWidth = width / ; // =125
var srcWidth = img2.Width; // =250
var srcHeight = img2.Height;// =250
g.DrawImage(img2, new Rectangle(width / , , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
using (var img3 = ZoomToSqure(image3, size))
{
var newHeight = height / ;// =125
var newWidth = width;// =250
var srcWidth = img3.Width;// =250
var srcHeight = img3.Height * newHeight / newWidth;//=250*125/250=125
g.DrawImage(img3, new Rectangle(, height / , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
}
break;
/// <summary>
/// 3张图片,上面2个正方形并排,下面一个长方形
/// ———————————————————
/// | | |
/// | | S1 |
/// | | |
/// | R1 |—————————
/// | | |
/// | | S2 |
/// | | |
/// ———————————————————
/// </summary>
case Merge3LayoutEnum.Merge1R2S3:
{
using (var img1 = ZoomToSqure(image1, size))
{
var newHeight = height;// =250
var newWidth = width / ;// =125
var srcHeight = img1.Height;//=250
var srcWidth = img1.Width * newWidth / newHeight;// =250*125/250=125;
g.DrawImage(img1, new Rectangle(, , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
using (var img2 = ZoomToSqure(image2, size))
{
var newHeight = height / ;// =125
var newWidth = width / ; // =125
var srcWidth = img2.Width; // =250
var srcHeight = img2.Height;// = 250
g.DrawImage(img2, new Rectangle(width / , , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
using (var img3 = ZoomToSqure(image3, size))
{
var newHeight = height / ; // =125
var newWidth = width / ; // =125
var srcWidth = img3.Width; // =250
var srcHeight = img3.Height;// =250
g.DrawImage(img3, new Rectangle(width / , height / , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
}
break;
/// <summary>
/// 3张图片,上面2个正方形并排,下面一个长方形
/// ———————————————————
/// | | |
/// | S2 | |
/// | | |
/// |—————————| R1 |
/// | | |
/// | S2 | |
/// | | |
/// ———————————————————
/// </summary>
case Merge3LayoutEnum.Merge1R2S4:
{
using (var img1 = ZoomToSqure(image1, size))
{
var newHeight = height / ;// =125
var newWidth = width / ; // =125
var srcWidth = img1.Width; // =250
var srcHeight = img1.Height;// =250
g.DrawImage(img1, new Rectangle(, , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
using (var img2 = ZoomToSqure(image2, size))
{
var newHeight = height / ; // =125
var newWidth = width / ; // =125
var srcWidth = img2.Width; // =250
var srcHeight = img2.Height;// =250
g.DrawImage(img2, new Rectangle(, height / , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
using (var img3 = ZoomToSqure(image3, size))
{
var newHeight = height;// =250
var newWidth = width / ;// =125
var srcHeight = img3.Height;//=250
var srcWidth = img3.Width * newWidth / newHeight;// =250*125/250=125;
g.DrawImage(img3, new Rectangle(width / , , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
}
break;
}
g.Save();
image1.Dispose();
image2.Dispose();
image3.Dispose();
}
using (var ms = new MemoryStream())
{
bg.Save(ms, ImageFormat.Png);
var buffers = ms.ToArray();
return ConvertToImage(buffers);
}
}
}

以下是4张图片的生成是算法:

        /// <summary>
/// 合并4张图片
/// </summary>
/// <param name="image1"></param>
/// <param name="image2"></param>
/// <param name="image3"></param>
/// <param name="image4"></param>
/// <param name="layout"></param>
/// <returns></returns>
public static Image Merge4Images(Image image1, Image image2, Image image3, Image image4, Merge4LayoutEnum layout = Merge4LayoutEnum.Merge4S, int size = )
{
var width = size;
var height = size;
var pf = PixelFormat.Format32bppArgb;
using (var bg = new Bitmap(width, height, pf))
{
using (var g = Graphics.FromImage(bg))
{
g.FillRectangle((Brush)Brushes.White, , , width, height);//全幅背景为白色
switch (layout)
{
/// <summary>
/// 4张图片,上面一个长方形,下面2个正方形并排
/// ———————————————————
/// | | |
/// | S1 | S2 |
/// | | |
/// ———————————————————
/// | | |
/// | S3 | S4 |
/// | | |
/// ———————————————————
/// </summary>
case Merge4LayoutEnum.Merge4S:
{
using (var img1 = ZoomToSqure(image1, size))
{
var newHeight = height / ;// =125
var newWidth = width / ; // =125
var srcWidth = img1.Width; // =250
var srcHeight = img1.Height;// =250
g.DrawImage(img1, new Rectangle(, , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
using (var img2 = ZoomToSqure(image2, size))
{
var newHeight = height / ; // =125
var newWidth = width / ; // =125
var srcWidth = img2.Width; // =250
var srcHeight = img2.Height;// =250
g.DrawImage(img2, new Rectangle(width / , , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
using (var img3 = ZoomToSqure(image3, size))
{
var newHeight = height / ; // =125
var newWidth = width / ; // =125
var srcWidth = img3.Width; // =250
var srcHeight = img3.Height;// =250
g.DrawImage(img3, new Rectangle(, height / , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
using (var img4 = ZoomToSqure(image4, size))
{
var newHeight = height / ; // =125
var newWidth = width / ; // =125
var srcWidth = img4.Width; // =250
var srcHeight = img4.Height;// =250
g.DrawImage(img4, new Rectangle(width / , height / , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
}
break;
}
g.Save();
image1.Dispose();
image2.Dispose();
image3.Dispose();
image4.Dispose();
}
using (var ms = new MemoryStream())
{
bg.Save(ms, ImageFormat.Png);
var buffers = ms.ToArray();
return ConvertToImage(buffers);
}
}
}

以下是用到的Helper方法:

        /// <summary>
/// 下载图片
/// </summary>
/// <param name="imageUrl"></param>
/// <returns></returns>
public static byte[] Download(string imageUrl)
{
if (imageUrl.StartsWith("http"))
{
using (var ms = new MemoryStream())
{
var request = (HttpWebRequest)HttpWebRequest.Create(imageUrl);// 打开网络连接
using (var rs = request.GetResponse().GetResponseStream())// 向服务器请求,获得服务器的回应数据流
{
byte[] btArray = new byte[];// 定义一个字节数据,用来向readStream读取内容和向writeStream写入内容
int size = rs.Read(btArray, , btArray.Length);// 向远程文件读第一次 while (size > )// 如果读取长度大于零则继续读
{
ms.Write(btArray, , size);// 写入本地文件
size = rs.Read(btArray, , btArray.Length);// 继续向远程文件读取
}
return ms.ToArray();
}
}
}
else
{
using (var ms = new MemoryStream())
{
var img = Image.FromFile(imageUrl);
img.Save(ms, img.RawFormat);
return ms.ToArray();
}
}
} /// <summary>
/// 将byte数组转化为Image
/// </summary>
/// <param name="buffer"></param>
/// <returns></returns>
public static Image ConvertToImage(byte[] buffer)
{
using (MemoryStream ms = new MemoryStream(buffer))
{
return Image.FromStream(ms);
}
} /// <summary>
/// 将Image转化为byte数组
/// </summary>
/// <param name="image"></param>
/// <returns></returns>
public static byte[] ConvertToByte(Image image)
{
using (MemoryStream ms = new MemoryStream())
{
image.Save(ms, image.RawFormat);
return ms.ToArray();
}
} /// <summary>
/// 等比缩放/放大成正方形图片
/// </summary>
/// <param name="orginal">原始图片</param>
/// <param name="size">目标宽高</param>
/// <param name="cute">超出部分是否剪裁</param>
/// <returns></returns>
public static Image ZoomToSqure(Image orginal, int size, bool cute = true)
{
var width = 0d;//图片宽度
var height = 0d;//图片高度
if (orginal.Width > orginal.Height)//原始图片宽度大于高度
{
height = size;
width = cute ? size : (orginal.Width * height / orginal.Height);
}
else if (orginal.Width < orginal.Height)//原始图片高度大于宽度
{
width = size;
height = cute ? size : (orginal.Height * width / orginal.Width);
}
else//原始图片是正方形,刚好
{
width = size;
height = size;
}
var board = new Bitmap((int)width, (int)height);
using (var g = Graphics.FromImage(board))
{
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;//设置质量
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;//设置质量
g.Clear(Color.White);//置背景色
g.DrawImage(orginal, new Rectangle(, , board.Width, board.Height), new Rectangle(, , orginal.Width, orginal.Height), System.Drawing.GraphicsUnit.Pixel); //画图
orginal.Dispose();//释放原图
return board;
}
}

完整代码点击:https://github.com/night-king/ImageMerge

IM即时通讯群组头像拼接.net core 解决方案的更多相关文章

  1. 一行实现QQ群组头像,微信群组,圆角等效果. 并支持url直接加载图片

    说点题外话. Coding中我们总是经历着这么几个过程. 学会使用: 不管是API也好, 开源库也好. 总是在最开始的学会去用. 了解实现原理: 可能会因为一些不兼容, 代码的异常状态的处理不够完美等 ...

  2. layim即时通讯实例各功能整合

    一.系统演示1.1 聊天窗体主界面演示 1.2 模拟两人在线聊天(点击图片查看演示视频) 1.3 在线演示> 在线演示,点击进入系统到这里,若是您想要的,接下来听我娓娓道来二.开发工具开发软件: ...

  3. Activiti6.0 工作流引擎 websocket即时聊天发图片文字 好友群组 SSM源码

    即时通讯:支持好友,群组,发图片.文件,消息声音提醒,离线消息,保留聊天记录 (即时聊天功能支持手机端,详情下面有截图) 工作流模块---------------------------------- ...

  4. java工作流引擎 Activiti6.0 websocket 即时聊天发图片文字 好友群组 SSM源码

    时通讯:支持好友,群组,发图片.文件,消息声音提醒,离线消息,保留聊天记录 工作流模块--------------------------------------------------------- ...

  5. java ssm 后台框架平台 项目源码 websocket即时聊天发图片文字 好友群组 SSM源码

    官网 http://www.fhadmin.org/D 集成安全权限框架shiro  Shiro 是一个用 Java 语言实现的框架,通过一个简单易用的 API 提供身份验证和授权,更安全,更可靠E ...

  6. mui初级入门教程(五)— 聊聊即时通讯(IM),基于环信 web im SDK

    文章来源:小青年原创发布时间:2016-06-15关键词:mui,环信 web im,html5+,im,页面传值,缓存转载需标注本文原始地址: http://zhaomenghuan.github. ...

  7. iOS:融云即时通讯快速集成

    一.介绍 即时通讯在众多社交软件.生活软件以及教育软件中已经是必备的功能了,在当前国内,即时通讯SDK做的比较不错的有那么几家,例如环信SDK.融云SDK...,这两家做的都很不错,各有千秋吧,要是真 ...

  8. GIM企业即时通讯

    GIM企业即时通讯是笔者Garfield(QQ:3674571)采用.NetFramework4.0+SQL2008R2开发的一套企业内网/外网 通用的即时通讯(IM)软件,分为服务器端和客户端,通讯 ...

  9. apicloud+融云实现即时通讯

    请尊重作者的辛勤劳动!!! 使用apicloud开发已经快2个月了,起初的目的就是为了实现安卓和苹果的兼容,属于一个试验项目,究竟apicloud是否能够满足公司的要求?最 终看来还是不错的,使用ap ...

随机推荐

  1. “帮你APP”团队冲刺5

    1.整个项目预期的任务量 (任务量 = 所有工作的预期时间)和 目前已经花的时间 (所有记录的 ‘已经花费的时间’),还剩余的时间(所有工作的 ‘剩余时间’) : 所有工作的预期时间:88h 目前已经 ...

  2. 设计模式之第10章-桥接模式(Java实现)

    设计模式之第10章-桥接模式(Java实现) “一入软件深似海,从此早睡是路人.黑夜给了我黑色的眼睛,我却用他去寻找八阿哥.”“怎么了,又来那么多的感慨啊.”“还能有什么啊,老板是说让换个APP做,这 ...

  3. 【Luogu P1637】 三元上升子序列

    对于每个数$a_i$,易得它对答案的贡献为 它左边比它小的数的个数$\times$它右边比它大的数的个数. 可以离散化后再处理也可以使用动态开点的线段树. 我使用了动态开点的线段树,只有需要用到这个节 ...

  4. Python中@property和@classmethod和@staticmethod

    前戏 首先,先要弄清楚一个类里面的,各个组成部分都应该怎么称呼. - 注:可能叫法会不太一样. 关于@property 顾名思义:它的意思为‘属性’. 作用: 1:使用它你将会把类方法,变为类属性.并 ...

  5. js后台提交成功后 关闭当前页 并刷新父窗体(转)

    原文地址:http://www.cnblogs.com/chenghu/p/3696433.html 后台提交成功后 关闭当前页 并刷新父窗体 this.ClientScript.RegisterSt ...

  6. mysql的下载及配置(复制1)

    ---恢复内容开始--- MySQL数据库安装与配置详解 目录 一.概述 二.MySQL安装 三.安装成功验证 四.NavicatforMySQL下载及使用 一.概述 MySQL版本:5.7.17 下 ...

  7. 2017年浙江工业大学之江学院程序设计竞赛决赛 I: qwb VS 去污棒(可持久化Trie+离线)

    问题 I: qwb VS 去污棒 时间限制: 2 Sec  内存限制: 256 MB 提交: 74  解决: 26 [提交][状态][讨论版] 题目描述 qwb表白学姐失败后,郁郁寡欢,整天坐在太阳底 ...

  8. 购物(sum)

    购物(sum) 题目描述 visit_world 有一个商店,商店里卖N个商品,第ii 个的价格为 a[[i] 我们称一个正整数K 是美妙的,当且仅当我们可以在商店里选购若干个商品,使得价格之和落在区 ...

  9. 获取所有querystring变量名

    原文发布时间为:2009-12-04 -- 来源于本人的百度文章 [由搬家工具导入] protected void Page_Load(object sender, EventArgs e)    { ...

  10. django+vue+nginx生产环境部署配置

    部署环境: 1. linux redhat 7.1 2.python 3.6.3 3. vue 4. nginx 5. gunicorn 6. supervisord 安装: 一. 基础环境安装 1. ...