Codeuml - 像编码一样快速设计 UML 图






4.99/5 (51投票s)
Codeuml.com 是一个开源的免费 Web 版 UML 图编辑器。您可以使用一种特殊的语言来描述图表,从而像打字一样快速地编写 UML 图。

引言
Codeuml 是一个基于 Web 的 UML 设计器,您可以使用一种特殊的语言来编写图表,它会实时生成图表。它比使用任何可视化设计器都快,后者需要您拖放图表元素并使用鼠标连接它们。Codeuml 使用开源的 plantuml 引擎从文本生成图表。您可以像编码一样快速地生成 UML 图。
这个 Web 应用程序展示了一些有趣的设计和编码挑战。首先,它展示了如何构建一个模仿 Windows 8 Metro UI 的基于 Web 的 IDE 环境。其次,它展示了如何定期从网站收集数据,在后台异步发送到服务器,并实时获取生成的结果。第三,也是最重要的,它展示了如何维护一个服务器端非常昂贵的资源池,这些资源不能在每次服务器请求时都创建,而必须拥有一个所有 Web 用户共享的有限池。
获取代码
实时站点可在以下网址找到:www.codeuml.com
从以下网址获取代码: http://code.google.com/p/codeuml/
构建前端
UI 灵感来自 Windows 8 的 Metro UI,并且对平板电脑友好。您可以在平板电脑上轻松触摸按钮。3 列可调整大小的面板使用 jQuery Splitter 插件构建。文本编辑器是出色的 CodeMirror 文本编辑器,它有一个糟糕的 Logo。行情信息由 jQuery New Ticker 插件提供。
3 列视图使用以下 HTML 构建
<div id="MySplitter">
<div class="SplitterPane unselectable">
<div id="umlsnippets">
.
.
.
</div>
</div>
<div id="CenterAndRight">
<div class="SplitterPane">
<img src="img/ajax-loader.gif" id="ProgressIndicator" />
<textarea id="umltext" rows="10" cols="40"></textarea>
</div>
<div class="SplitterPane">
<div id="umlimage_container">
<img id="umlimage" src="img/defaultdiagram.png" />
<div id="ticker">
News ticker
</div>
</div>
</div>
</div>
</div>
// Main vertical splitter, anchored to the browser window
$("#MySplitter").splitter({
type: "v",
outline: true,
minLeft: 60, sizeLeft: 100, maxLeft: 250,
anchorToWindow: true,
resizeOnWindow: true,
accessKey: "L"
});
// Second vertical splitter, nested in the right pane of the main one.
$("#CenterAndRight").splitter({
type: "v",
outline: true,
minRight: 200, sizeRight: ($(window).width() * 0.6), maxRight: ($(window).width() * 0.9),
accessKey: "R"
});
$(window).resize(function () {
$("#MySplitter").trigger("resize");
});
myCodeMirror = CodeMirror.fromTextArea($('#umltext').get(0),
{
onChange: refreshDiagram
});
myCodeMirror.focus();
myCodeMirror.setCursor({ line: myCodeMirror.lineCount() + 1, ch: 1 });
<div id="scrollable">
<!-- Sequence diagram -->
<h2>
Sequence
</h2>
<div class="sequence_diagram">
<div class="button">
<div class="icon">
A→B</div>
<div class="title">
Sync Msg</div>
<pre class="umlsnippet">A -> B: Sync Message</pre>
</div>
</div>
注入到文本编辑器中的文本位于 `<pre>` 标签内。
您可以根据需要创建任意数量的按钮,只需将需要插入的 UML 片段放在带有 `umsnippet` 类的 `<pre>` 标签内。
单击这些按钮时,以下 JavaScript 会将 `<pre>` 内的代码注入到文本编辑器中。
$("#umlsnippets").find(".button").click(function () { var diagramType = $(this).parent().attr("class"); if (lastUmlDiagram !== diagramType) { if (!confirm("The current diagram will be cleared? Do you want to continue?")) return; myCodeMirror.setValue(""); } changeDiagramType(diagramType); var umlsnippet = $(this).find("pre.umlsnippet").text(); var pos = myCodeMirror.getCursor(true); // When replaceRange or replaceSelection is called // to insert text, in IE 8, the code editor gets // screwed up. So, it needs to be recreated after this. myCodeMirror.replaceRange(umlsnippet, myCodeMirror.getCursor(true)); // recreate the code editor to fix screw up in IE 7/8 myCodeMirror.toTextArea(); myCodeMirror = CodeMirror.fromTextArea($('#umltext').get(0), { onChange: refreshDiagram }); myCodeMirror.focus(); myCodeMirror.setCursor(pos); refreshDiagram(); });
这里有一个棘手的问题是,如果我注入调用 `replaceRange` 的文本,CodeMirror 编辑器就会停止工作。必须重新创建它才能使其再次工作。
边输入边生成图表
边输入边刷新图表是最具挑战性的部分。以下 JavaScript 函数在文本编辑器中的任何内容发生变化时都会触发。但是,它确保每秒只向服务器发送一次 UML。因此,即使您持续输入,它每秒也只会将 UML 发送一次到服务器。
function refreshDiagram() {
if (lastTimer == null) {
lastTimer = window.setTimeout(function () {
// Remove starting and ending spaces
var umltext = myCodeMirror.getValue().replace(/(^[\s\xA0]+|[\s\xA0]+$)/g, '');
var umltextchanged =
(umltext !== lastUmlText)
&& validDiagramText(umltext);
if (umltextchanged) {
$('#ProgressIndicator').show();
lastUmlText = umltext;
$.post("SendUml.ashx", { uml: umltext }, function (result) {
var key = $.trim(result);
$("#umlimage").attr("src", "getimage.ashx?key=" + key);
}, "text");
try {
var forCookie = $.base64.encode(umltext).replace(/==/, '');
if (forCookie.length > 3800) {
alert("Sorry maximum 3800 characters allowed in a diagram");
}
else {
createCookie('uml', forCookie, 30);
var test = readCookie('uml');
if (test !== forCookie) {
createCookie('uml', '', 30);
}
}
} catch (e) {
}
}
}, 1000);
}
else {
window.clearTimeout(lastTimer);
lastTimer = null;
refreshDiagram();
}
}
这段代码相当智能。首先,它确保当用户只输入空格或按 Enter 键,并且文本实际上没有会生成新图像的变化时,它不会将 UML 文本发送到服务器进行昂贵的图像生成过程。它还会进行一些验证,以防止不完整的图表文本过早发送到服务器。您在这里能捕获的越多,就能在服务器上避免越多的无用图像生成。
首先,它将 UML 文本发布到一个名为 SendUml.ashx 的 HTTP 处理程序。它会记住文本并返回一个 GUID。然后,该 GUID 用于命中 `GetImage.ashx`,该处理程序负责生成图表。`SendUml.ashx` 中的代码非常简单
public class SendUml : IHttpHandler {
public void ProcessRequest (HttpContext context) {
string uml = context.Request["uml"];
string key = Guid.NewGuid().ToString();
context.Cache.Add(key, uml, null, DateTime.Now.AddSeconds(60), System.Web.Caching.Cache.NoSlidingExpiration,
System.Web.Caching.CacheItemPriority.Default, null);
context.Response.ContentType = "text/plain";
context.Response.Write(key);
}
它只是将文本缓存很短时间,因为预期浏览器在获得密钥 GUID 后会立即命中 `GetImage.ashx`。
public void ProcessRequest (HttpContext context) {
string key = context.Request["key"];
string umltext = context.Cache[key] as string;
context.Response.ContentType = "image/png";
context.Response.Cache.SetCacheability(HttpCacheability.Private);
context.Response.Cache.SetExpires(DateTime.Now.AddMinutes(5));
if (context.Request["saveMode"] == "1")
{
context.Response.AddHeader("Content-Disposition", "attachment; filename=diagram.png");
}
var connection = PlantUmlConnectionPool.Get(TimeSpan.FromSeconds(15));
if (connection == null)
throw new ApplicationException("Connection not found in pool.");
try
{
var uploadFileName = key + ".txt";
var downloadFileName = key + ".png";
connection.Upload(uploadFileName,
"@startuml " + downloadFileName + Environment.NewLine +
umltext + Environment.NewLine +
"@enduml");
System.Threading.Thread.Sleep(100);
using (MemoryStream memoryStream = new MemoryStream())
{
connection.Download(downloadFileName, stream =>
{
byte[] buffer = new byte[0x1000];
int bytesRead;
while ((bytesRead = stream.Read(buffer, 0, 0x1000)) > 0)
{
memoryStream.Write(buffer, 0, bytesRead);
}
});
首先,它从查询字符串中读取密钥,然后从缓存中加载 UML 文本。然后,它连接到 PlantUml FTP 服务器(稍后将在下一节中解释),并将 UML 文本作为文件上传到 FTP 服务器。Plantuml 然后生成图表图像并使其可供下载。然后,处理程序从 FTP 服务器下载图像。然后,它在图像上添加水印并将其发送回浏览器。
using (Bitmap b = Bitmap.FromStream(memoryStream, true, false) as Bitmap)
using (Bitmap newBitmap = new Bitmap(b.Width, b.Height + 20))
using (Graphics g = Graphics.FromImage(newBitmap))
{
// Put the original image on the top left corner.
g.FillRectangle(Brushes.White, 0, 0, newBitmap.Width, newBitmap.Height);
g.DrawImage(b, 0, 0);
// Add the watermark
SizeF size = g.MeasureString(WatermarkText, _font);
g.DrawString(WatermarkText, _font, Brushes.Black, newBitmap.Width - size.Width, newBitmap.Height - 15);
// Save the image to the response stream directly.
newBitmap.Save(context.Response.OutputStream, System.Drawing.Imaging.ImageFormat.Png);
}
context.Response.Flush();
完成后,它将连接返回到池中
PlantUmlConnectionPool.Put(connection);
这就是前端部分的内容
使用 Plantuml 生成图表
Plantuml 是一个 Java 应用程序,可以作为 FTP 服务器运行,您可以将图表文本作为文件上传,它会生成一个您可以下载的图表图像。由于它作为一个 FTP 服务器运行,我必须维护一个正在运行的 FTP 服务器池。我不能只是启动 FTP 服务器然后生成图像。这会太慢。因此,我必须在应用程序启动期间启动几个 FTP 服务器实例,然后维护到 FTP 服务器的连接池。每当出现一个生成图表的 `getimage.ashx` 请求时,它就会从池中获取一个连接,处理请求,然后将连接返回到池中。当您需要与许多要求苛刻的客户共享有限的昂贵资源时,这是一种常见的模式。
首先,我维护一个 Plantuml 实例池。在 `Application_Start` 事件期间,以下代码会启动几个 Plantuml FTP 服务器并准备连接池。
public static class PlantUmlProcessManager
{
private static readonly List<Process> _processes = new List<Process>();
public static void Startup()
{
if (_processes.Count > 0)
Shutdown();
var javaPath = ConfigurationManager.AppSettings["java"];
if (!File.Exists(javaPath))
throw new ApplicationException("Java.exe not found: " + javaPath);
var host = ConfigurationManager.AppSettings["plantuml.host"];
var startPort = Convert.ToInt32(ConfigurationManager.AppSettings["plantuml.start_port"]);
var instances = Convert.ToInt32(ConfigurationManager.AppSettings["plantuml.instances"]);
var plantumlPath = ConfigurationManager.AppSettings["plantuml.path"];
if (!File.Exists(plantumlPath))
throw new ApplicationException("plantuml.jar not found in " + plantumlPath);
for (int i = 0; i < instances; i++)
{
var argument = "-jar " + plantumlPath + " -ftp:" + (startPort + i);
ProcessStartInfo pInfo = new ProcessStartInfo(javaPath, argument);
pInfo.CreateNoWindow = true;
pInfo.UseShellExecute = false;
pInfo.RedirectStandardInput = true;
pInfo.RedirectStandardError = true;
pInfo.RedirectStandardOutput = true;
Process process = Process.Start(pInfo);
Thread.Sleep(5000);
_processes.Add(process);
PlantUmlConnection connection = new PlantUmlConnection();
connection.Connect(host, startPort + i);
PlantUmlConnectionPool.Put(connection);
}
}
连接池定义如下
public static class PlantUmlConnectionPool
{
private readonly static Queue<PlantUmlConnection> _connectionPool = new Queue<PlantUmlConnection>();
private readonly static ManualResetEvent _availableEvent = new ManualResetEvent(false);
public static PlantUmlConnection Get(TimeSpan timeout)
{
if (_connectionPool.Count == 0)
{
_availableEvent.Reset();
if (_availableEvent.WaitOne(timeout))
{
return _connectionPool.Dequeue();
}
else
{
return null;
}
}
else
{
lock (_connectionPool)
{
if (_connectionPool.Count == 0)
return null;
else
return _connectionPool.Dequeue();
}
}
}
算法如下
- 检查池中是否有可用连接。
- 如果没有,则等待固定时长,直到有连接可用。
- 如果在等待超时期间没有连接可用,则返回 null。
将连接放回池中非常简单
public static void Put(PlantUmlConnection connection)
{
lock (_connectionPool)
_connectionPool.Enqueue(connection);
<span class="Apple-tab-span" style="white-space: pre; "> </span>_availableEvent.Set();
}
为了维护与正在运行的 FTP 服务器的连接就绪,我使用了 Alex Pilotti 的 FTP 客户端。
public class PlantUmlConnection : IDisposable
{
private FTPSClient client = new FTPSClient();
private string _host;
private int _port;
public void Connect(string host, int port)
{
_host = host;
_port = port;
Debug.WriteLine("Connecting to FTP " + host + ":" + port);
client.Connect(host, port,
new NetworkCredential("yourUsername","yourPassword"),
ESSLSupportMode.ClearText,
null,
null,
0,
0,
0,
3000,
true,
EDataConnectionMode.Active
);
Debug.WriteLine("Connection successful " + host + ":" + port);
}
在 FTP 服务器初始化期间,对于每个 FTP 服务器实例,这个连接类的一个实例会建立一个打开的连接。
需要生成图表时,它会将包含图表文本的文本文件上传到 FTP 服务器。然后,Plantuml 引擎会启动并生成图像。
public void Upload(string remoteFileName, string content)
{
Debug.WriteLine("Uploading to " + _host + ":" + _port + "/" + remoteFileName);
using (var stream = client.PutFile(remoteFileName))
{
byte[] data = Encoding.UTF8.GetBytes(content);
stream.Write(data, 0, data.Length);
}
Debug.WriteLine("Successfully uploaded " + _host + ":" + _port + "/" + remoteFileName);
}
然后您可以使用 Download 函数下载图像
public void Download(string remoteFileName, Action<Stream> processStream)
{
Debug.WriteLine("Downloading from " + _host + ":" + _port + "/" + remoteFileName);
using (var stream = client.GetFile(remoteFileName))
{
processStream(stream);
}
Debug.WriteLine("Successfully downloaded " + _host + ":" + _port + "/" + remoteFileName);
}
关于管理 PlantUML 服务器就这些了。
自行设置 Codeuml
您可以在自己的服务器上安装 codeuml。在这种情况下,请仔细遵循 readme 文件。它需要一些非常仔细的设置才能使 Plantuml 引擎正常工作。我将为您粘贴 readme 文件,但请务必检查最新的代码和 readme 文件。
There are several pre-requisits before you run this website.
1. Install Java
===============
Download and install latest Java. Make sure you know where
you are installing java. Usually it will be:
"c:\Program Files\Java\jre6\bin"
1. Configure Graphviz
=============================================================
First, you have to install graphviz.
https://graphviz.cn/
Once you have installed, create a SYSTEM environment variable
called GRAPHVIZ_DOT which points to the dot.exe found in the
graphviz bin folder. Usually it is:
c:\Program Files\Graphviz2.26.3\bin\dot.exe
Once you have done so, start a new command line window and run
this:
set graphviz_dot
If this shows you:
GRAPHVIZ_DOT=c:\Program Files\Graphviz2.26.3\bin\dot.exe
Then it is ok.
2. Installing on IIS 7+
=============================================================
If you are hosting this on a Windows Server, there are various
steps you need to do:
* First create a new app pool.
* Create a new website or virtual directory that points to this
website.
* Give the app pool user (IIS AppPool\YourAppPoolName or NETWORK
SERVICE)
Read & Execute permission on the:
** Java folder. Eg. "c:\Program Files\Java\jre6\bin"
** Graphviz bin folder: Eg c:\Program Files\Graphviz2.26.3\bin
** Within this website:
plantuml folder.
3. Configuring web.config
==============================================================
You must fix the following entries before you can run:
<add key="java" value="c:\Program Files\Java\jre6\bin\java.exe" />
<add key="plantuml.path" value="C:\Dropbox\Dropbox\OSProjects\PlantUmlRunner\plantuml\plantuml.jar"/>
These are both absolute paths. No relative path allowed.
4. Running and testing the website
============================================================
Run the Manage.aspx.
It will take a while to start the page as it tries to launch java
and run the plantuml engine at the application_start event.
Once the site is up and running, click on Test button to test
a UML generation. If it works, you have configured everything
properly.
Disable the Manage.aspx on production.
结论
Codeuml 作为一个 Web 应用程序虽然不大,但它展示了如何构建高度响应式的 AJAX 前端,该前端模仿 Visual Studio 风格的 IDE,并使用一些非常昂贵的有限资源池从服务器生成输出。它向您展示了如何自行实现一个昂贵的资源池。