using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using Fast.Framework.Models; namespace Fast.Framework.Extensions
{ /// <summary>
/// 表达式扩展类
/// </summary>
public static class ExpressionExtensions
{
/// <summary>
/// 解析Sql
/// </summary>
/// <param name="expression">表达式</param>
/// <param name="options">选项</param>
/// <returns></returns>
public static SqlInfo ResolveSql(this Expression expression, SqlExpressionOptions options)
{
var ex = new SqlExpressionVisitor(options);
ex.Visit(expression);
return ex.sqlInfo;
}
} /// <summary>
/// 解析类型
/// </summary>
public enum ResolveType
{
/// <summary>
/// Sql字符串
/// </summary>
SqlString = 0, /// <summary>
/// 调用
/// </summary>
Call = 1, /// <summary>
/// 左相似
/// </summary>
LeftLike = 2, /// <summary>
/// 右相似
/// </summary>
RightLike = 3, /// <summary>
/// 相似
/// </summary>
Like = 4
} /// <summary>
/// 表达式映射
/// </summary>
public static class ExpressionMapper
{
/// <summary>
/// 表达式类型
/// </summary>
public static readonly Dictionary<ExpressionType, string> expressionTypes; /// <summary>
/// 方法调用
/// </summary>
public static readonly Dictionary<string, Action<SqlInfo, MethodCallExpression, Func<Expression, Expression>, Action<ResolveType>>> methodCall; /// <summary>
/// 转换
/// </summary>
public static readonly Action<MethodCallExpression, Func<Expression, Expression>, Action<ResolveType>> convert; /// <summary>
/// 构造方法
/// </summary>
static ExpressionMapper()
{
expressionTypes = new Dictionary<ExpressionType, string>()
{
{ExpressionType.Add,"+" },
{ExpressionType.Subtract,"-" },
{ExpressionType.Multiply,"*" },
{ExpressionType.Divide,"/" },
{ExpressionType.AndAlso,"AND" },
{ExpressionType.OrElse,"OR" },
{ExpressionType.Equal,"=" },
{ExpressionType.NotEqual,"<>" },
{ExpressionType.GreaterThan,">" },
{ExpressionType.LessThan,"<" },
{ExpressionType.GreaterThanOrEqual,">=" },
{ExpressionType.LessThanOrEqual,"<=" }
}; methodCall = new Dictionary<string, Action<SqlInfo, MethodCallExpression, Func<Expression, Expression>, Action<ResolveType>>>()
{
#region 聚合函数
{"Sum", (sql,node,visit,call)=>
{
sql.SqlStack.Push(")");
visit.Invoke(node.Arguments[0]);
sql.SqlStack.Push("(");
sql.SqlStack.Push("SUM");
} },
{"Max", (sql,node,visit,call)=>
{
sql.SqlStack.Push(")");
visit.Invoke(node.Arguments[0]);
sql.SqlStack.Push("(");
sql.SqlStack.Push("MAX");
} },
{"Min", (sql,node,visit,call)=>
{
sql.SqlStack.Push(")");
visit.Invoke(node.Arguments[0]);
sql.SqlStack.Push("(");
sql.SqlStack.Push("MIN");
} },
{"Count", (sql,node,visit,call)=>
{
sql.SqlStack.Push(")");
visit.Invoke(node.Arguments[0]);
sql.SqlStack.Push("(");
sql.SqlStack.Push("COUNT");
} },
{"Avg", (sql,node,visit,call)=>
{
sql.SqlStack.Push(")");
visit.Invoke(node.Arguments[0]);
sql.SqlStack.Push("(");
sql.SqlStack.Push("AVG");
} },
#endregion #region 日期函数
{"GetDate", (sql,node,visit,call)=>
{
sql.SqlStack.Push(")");
sql.SqlStack.Push("(");
sql.SqlStack.Push("GETDATE");
} },
{"Year", (sql,node,visit,call)=>
{
sql.SqlStack.Push(")");
visit.Invoke(node.Arguments[0]);
sql.SqlStack.Push("(");
sql.SqlStack.Push("YEAR");
} },
{"Month", (sql,node,visit,call)=>
{
sql.SqlStack.Push(")");
visit.Invoke(node.Arguments[0]);
sql.SqlStack.Push("(");
sql.SqlStack.Push("MONTH");
} },
{"Day", (sql,node,visit,call)=>
{
sql.SqlStack.Push(")");
visit.Invoke(node.Arguments[0]);
sql.SqlStack.Push("(");
sql.SqlStack.Push("DAY");
} },
{"Current_Timestamp", (sql,node,visit,call)=>
{
sql.SqlStack.Push(")");
sql.SqlStack.Push("(");
sql.SqlStack.Push("CURRENT_TIMESTAMP");
} },
{"Current_Date", (sql,node,visit,call)=>
{
sql.SqlStack.Push(")");
sql.SqlStack.Push("(");
sql.SqlStack.Push("CURRENT_DATE");
} },
{"Current_Time", (sql,node,visit,call)=>
{
sql.SqlStack.Push(")");
sql.SqlStack.Push("(");
sql.SqlStack.Push("CURRENT_TIME");
} },
#endregion #region 数学函数
{"Abs", (sql,node,visit,call)=>
{
sql.SqlStack.Push(")");
visit.Invoke(node.Arguments[0]);
sql.SqlStack.Push("(");
sql.SqlStack.Push("ABS");
} },
{"Round", (sql,node,visit,call)=>
{
sql.SqlStack.Push(")");
visit.Invoke(node.Arguments[1]);
sql.SqlStack.Push(",");
visit.Invoke(node.Arguments[0]);
sql.SqlStack.Push("(");
sql.SqlStack.Push("ROUND");
} },
#endregion #region 字符串函数
{"Contains", (sql,node,visit,call)=>
{
call(ResolveType.Like);
visit.Invoke(node.Arguments[0]);
call(ResolveType.SqlString);
sql.SqlStack.Push(" LIKE ");
visit.Invoke(node.Object);
} },
{"StartsWith", (sql,node,visit,call)=>
{
call(ResolveType.LeftLike);
visit.Invoke(node.Arguments[0]);
call(ResolveType.SqlString);
sql.SqlStack.Push(" LIKE ");
visit.Invoke(node.Object);
} },
{"EndsWith", (sql,node,visit,call)=>
{
call(ResolveType.RightLike);
visit.Invoke(node.Arguments[0]);
call(ResolveType.SqlString);
sql.SqlStack.Push(" LIKE ");
visit.Invoke(node.Object);
} },
{"Substring", (sql,node,visit,call)=>
{
sql.SqlStack.Push(")");
if (node.Arguments.Count > 1)
{
visit.Invoke(node.Arguments[1]);
sql.SqlStack.Push(",");
}
visit.Invoke(node.Arguments[0]);
sql.SqlStack.Push(",");
visit.Invoke(node.Object);
sql.SqlStack.Push("(");
sql.SqlStack.Push("SUBSTRING");
} },
{"Replace", (sql,node,visit,call)=>
{
sql.SqlStack.Push(")");
visit.Invoke(node.Arguments[1]);
sql.SqlStack.Push(",");
visit.Invoke(node.Arguments[0]);
sql.SqlStack.Push(",");
visit.Invoke(node.Object);
sql.SqlStack.Push("(");
sql.SqlStack.Push("REPLACE");
} },
{"ToUpper", (sql,node,visit,call)=>
{
sql.SqlStack.Push(")");
visit.Invoke(node.Object);
sql.SqlStack.Push("(");
sql.SqlStack.Push("UPPER");
} },
{"ToLower", (sql,node,visit,call)=>
{
sql.SqlStack.Push(")");
visit.Invoke(node.Object);
sql.SqlStack.Push("(");
sql.SqlStack.Push("LOWER");
} },
{"Trim", (sql,node,visit,call)=>
{
sql.SqlStack.Push(")");
visit.Invoke(node.Object);
sql.SqlStack.Push("(");
sql.SqlStack.Push("TRIM");
} },
#endregion #region 其它函数
{"Equals", (sql,node,visit,call)=>
{
visit.Invoke(node.Arguments[0]);
sql.SqlStack.Push(" = ");
visit.Invoke(node.Object);
} },
{"IsNull", (sql,node,visit,call)=>
{
sql.SqlStack.Push(")");
visit.Invoke(node.Arguments[1]);
sql.SqlStack.Push(",");
visit.Invoke(node.Arguments[0]);
sql.SqlStack.Push("(");
sql.SqlStack.Push("ISNULL");
visit.Invoke(node.Object);
} }
#endregion
};
}
} /// <summary>
/// Sql表达式访问
/// </summary>
public class SqlExpressionVisitor : ExpressionVisitor
{ /// <summary>
/// Sql信息
/// </summary>
public readonly SqlInfo sqlInfo; /// <summary>
/// 参数索引
/// </summary>
private readonly Dictionary<string, int> parameterIndex; /// <summary>
/// 成员信息
/// </summary>
private readonly Stack<MemberInfo> memberInfos; /// <summary>
/// 表达式选项
/// </summary>
private readonly SqlExpressionOptions options; /// <summary>
/// 解析类型
/// </summary>
private ResolveType resolveType; /// <summary>
/// 构造方法
/// </summary>
/// <param name="options">表达式选项</param>
public SqlExpressionVisitor(SqlExpressionOptions options, ResolveType resolveType = ResolveType.SqlString)
{
sqlInfo = new SqlInfo();
parameterIndex = new Dictionary<string, int>();
memberInfos = new Stack<MemberInfo>();
this.options = options;
this.resolveType = resolveType;
} /// <summary>
/// 访问
/// </summary>
/// <param name="node"></param>
/// <returns></returns>
[return: NotNullIfNotNull("node")]
public override Expression Visit(Expression node)
{
//Console.WriteLine(node?.NodeType);
return base.Visit(node);
} /// <summary>
/// 访问Lambda
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="node">节点</param>
/// <returns></returns>
protected override Expression VisitLambda<T>(Expression<T> node)
{
if (!options.IgnoreParameterExpression)
{
var index = 0;
foreach (var parameter in node.Parameters)
{
parameterIndex.Add(parameter.Name, index);
index++;
}
}
return Visit(node.Body);
} /// <summary>
/// 访问一元表达式
/// </summary>
/// <param name="node">节点</param>
/// <returns></returns>
protected override Expression VisitUnary(UnaryExpression node)
{
var memberExpression = (node as UnaryExpression).Operand as MemberExpression;
var sqlExpressionVisitor = new SqlExpressionVisitor(options, ResolveType.Call);
var array = (sqlExpressionVisitor.Visit(memberExpression) as ConstantExpression).Value as object[];
return Visit(Expression.Constant(array.Length));
} /// <summary>
/// 访问二元表达式
/// </summary>
/// <param name="node">节点</param>
/// <returns></returns>
protected override Expression VisitBinary(BinaryExpression node)
{
if (node.NodeType == ExpressionType.ArrayIndex)
{
var sqlExpressionVisitor = new SqlExpressionVisitor(options, ResolveType.Call);
var array = (sqlExpressionVisitor.Visit(node.Left) as ConstantExpression).Value as object[];
var value = array[Convert.ToInt32((sqlExpressionVisitor.Visit(node.Right) as ConstantExpression).Value)];
return VisitConstant(Expression.Constant(value));
}
else
{
if (!ExpressionMapper.expressionTypes.ContainsKey(node.NodeType))
{
throw new Exception($"暂不支持{node.NodeType}类型解析");
}
var op = ExpressionMapper.expressionTypes[node.NodeType];
if (node.Right.NodeType == ExpressionType.Constant && ((ConstantExpression)node.Right).Value == null)//判断null值
{
if (op == "=")
{
sqlInfo.SqlStack.Push(" IS NULL");
}
else if (op == "<>")
{
sqlInfo.SqlStack.Push(" IS NOT NULL");
}
}
else
{
if (node.Right is BinaryExpression && node.Right.NodeType == ExpressionType.Equal)
{
sqlInfo.SqlStack.Push(")");
Visit(node.Right);//右边带括号
sqlInfo.SqlStack.Push("(");
}
else
{
Visit(node.Right);//右边
}
sqlInfo.SqlStack.Push($" {op} ");//操作符
}
if (node.Left is BinaryExpression && node.Left.NodeType == ExpressionType.Equal)
{
sqlInfo.SqlStack.Push(")");
Visit(node.Left); //左边带括号
sqlInfo.SqlStack.Push("(");
}
else
{
Visit(node.Left); //左边
}
}
return node;
} /// <summary>
/// 访问索引
/// </summary>
/// <param name="node">节点</param>
/// <returns></returns>
protected override Expression VisitIndex(IndexExpression node)
{
return base.VisitIndex(node);
} /// <summary>
/// 访问参数
/// </summary>
/// <param name="node">节点</param>
/// <returns></returns>
protected override Expression VisitParameter(ParameterExpression node)
{
if (!options.IgnoreParameterExpression)
{
sqlInfo.SqlStack.Push($"p{parameterIndex[node.Name]}.");
}
return node;
} /// <summary>
/// 访问对象
/// </summary>
/// <param name="node">节点</param>
/// <returns></returns>
protected override Expression VisitNew(NewExpression node)
{
if (resolveType == ResolveType.SqlString)
{
for (int i = 0; i < node.Arguments.Count; i++)
{
var name = AddIdentifier(node.Members[i].IsDefined(typeof(ColumnAttribute)) ? node.Members[i].GetCustomAttribute<ColumnAttribute>().Name : node.Members[i].Name);
Visit(node.Arguments[i]);
var value = string.Join("", sqlInfo.SqlStack.ToList());
sqlInfo.NewKeyValues.Add(name, value);
sqlInfo.NewNames.Add(name);
sqlInfo.NewValues.Add(value);
sqlInfo.NewAssignMapper.Add($"{name} = {value}");
sqlInfo.NewAsMapper.Add($"{value} AS {name}");
sqlInfo.SqlStack.Clear();
}
}
else if (resolveType == ResolveType.Call)
{
var sqlExpressionVisitor = new SqlExpressionVisitor(options, ResolveType.Call); var args = new List<object>();
foreach (var item in node.Arguments)
{
args.Add((sqlExpressionVisitor.Visit(item) as ConstantExpression).Value);
} var type = node.Type; object obj = null; if (type.IsGenericTypeDefinition)//泛型类
{
type = type.MakeGenericType(type.GetGenericArguments());
}
if (type.IsDefined(typeof(CompilerGeneratedAttribute)))//匿名对象
{
var argsTypes = args.Select(s => s?.GetType()).ToList().GetTypeDefaultValue();
obj = Activator.CreateInstance(type, argsTypes.ToArray());
for (int i = 0; i < node.Members.Count; i++)
{
var member = node.Members[i];
type.GetField($"<{member.Name}>i__Field", BindingFlags.NonPublic | BindingFlags.CreateInstance | BindingFlags.Instance).SetValue(obj, args[i]);
}
}
else
{
obj = Activator.CreateInstance(type, args.ToArray());
}
return VisitConstant(Expression.Constant(obj));
}
return node;
} /// <summary>
/// 访问列表初始化
/// </summary>
/// <param name="node">节点</param>
/// <returns></returns>
protected override Expression VisitListInit(ListInitExpression node)
{
var sqlExpressionVisitor = new SqlExpressionVisitor(options, ResolveType.Call); var args = new List<object>();
args.Add((sqlExpressionVisitor.Visit(node.NewExpression) as ConstantExpression).Value); var type = node.Type;
if (type.IsGenericTypeDefinition)
{
type = type.MakeGenericType(type.GetGenericArguments());
}
var list = Activator.CreateInstance(type, args.ToArray());
foreach (var item in node.Initializers)
{
var args2 = new List<object>();
foreach (var item2 in item.Arguments)
{
args2.Add((sqlExpressionVisitor.Visit(item2) as ConstantExpression).Value);
}
item.AddMethod.Invoke(list, args2.ToArray());
}
if (resolveType == ResolveType.SqlString)
{
return VisitConstant(Expression.Constant(string.Join(",", list)));
} return VisitConstant(Expression.Constant(list));
} /// <summary>
/// 访问对象数组
/// </summary>
/// <param name="node">节点</param>
/// <returns></returns>
protected override Expression VisitNewArray(NewArrayExpression node)
{
var sqlExpressionVisitor = new SqlExpressionVisitor(options, ResolveType.Call); var array = Array.CreateInstance(node.Type.GetElementType(), node.Expressions.Count); for (int i = 0; i < array.Length; i++)
{
array.SetValue((sqlExpressionVisitor.Visit(node.Expressions[i]) as ConstantExpression).Value, i);
} if (resolveType == ResolveType.SqlString)
{
return VisitConstant(Expression.Constant(string.Join(",", array as object[])));
} return VisitConstant(Expression.Constant(array));
} /// <summary>
/// 访问方法
/// </summary>
/// <param name="node">节点</param>
/// <returns></returns>
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (ExpressionMapper.methodCall.ContainsKey(node.Method.Name) && resolveType == ResolveType.SqlString)
{
ExpressionMapper.methodCall[node.Method.Name].Invoke(sqlInfo, node, e => Visit(e), (type) =>
{
resolveType = type;//回调设置类型
});
}
else
{
var sqlExpressionVisitor = new SqlExpressionVisitor(options, ResolveType.Call);
var args = new List<object>();
foreach (var item in node.Arguments)
{
args.Add((sqlExpressionVisitor.Visit(item) as ConstantExpression).Value);
}
object obj = null;
if (node.Object == null)
{
obj = this;
}
else
{
obj = (sqlExpressionVisitor.Visit(node.Object) as ConstantExpression).Value;
}
var value = node.Method.Invoke(obj, args.ToArray());
resolveType = ResolveType.SqlString;
return Visit(Expression.Constant(value));
}
return node;
} /// <summary>
/// 访问成员初始化
/// </summary>
/// <param name="node"></param>
/// <returns></returns>
protected override Expression VisitMemberInit(MemberInitExpression node)
{
var expressions = (node.Reduce() as BlockExpression).Expressions.Skip(1).SkipLast(1).ToList();//去除不需要的表达式; if (resolveType == ResolveType.SqlString)
{
for (int i = 0; i < expressions.Count; i++)
{
var binaryExpression = (BinaryExpression)expressions[i];
Visit(binaryExpression.Right);
var name = AddIdentifier(node.Bindings[i].Member.IsDefined(typeof(ColumnAttribute)) ? node.Bindings[i].Member.GetCustomAttribute<ColumnAttribute>().Name : node.Bindings[i].Member.Name);
var value = string.Join("", sqlInfo.SqlStack.ToList());
sqlInfo.NewKeyValues.Add(name, value);
sqlInfo.NewNames.Add(name);
sqlInfo.NewValues.Add(value);
sqlInfo.NewAssignMapper.Add($"{name} = {value}");
sqlInfo.NewAsMapper.Add($"{value} AS {name}");
sqlInfo.SqlStack.Clear();
}
}
else if (resolveType == ResolveType.Call)
{
var sqlExpressionVisitor = new SqlExpressionVisitor(options, ResolveType.Call);
var obj = (sqlExpressionVisitor.Visit(node.NewExpression) as ConstantExpression).Value;
for (int i = 0; i < expressions.Count; i++)
{
if (node.Bindings[i].Member.MemberType == MemberTypes.Field)
{
var fields = (FieldInfo)node.Bindings[i].Member;
fields.SetValue(obj, (sqlExpressionVisitor.Visit((expressions[i] as BinaryExpression).Right) as ConstantExpression).Value);
}
if (node.Bindings[i].Member.MemberType == MemberTypes.Property)
{
var propertyInfo = (PropertyInfo)node.Bindings[i].Member;
propertyInfo.SetValue(obj, (sqlExpressionVisitor.Visit((expressions[i] as BinaryExpression).Right) as ConstantExpression).Value);
}
}
return VisitConstant(Expression.Constant(obj));
}
return node;
} /// <summary>
/// 访问成员
/// </summary>
/// <param name="node">节点</param>
/// <returns></returns>
protected override Expression VisitMember(MemberExpression node)
{
memberInfos.Push(node.Member);
if (node.Expression == null && node.NodeType == ExpressionType.MemberAccess)
{
return Visit(Expression.Constant(null));//子表达式为空但有成员处理
}
if (node.Expression.NodeType == ExpressionType.Parameter)
{
if (node.Member.IsDefined(typeof(ColumnAttribute)))
{
sqlInfo.SqlStack.Push(AddIdentifier(node.Member.GetCustomAttribute<ColumnAttribute>().Name));
}
else
{
sqlInfo.SqlStack.Push(AddIdentifier(node.Member.Name));
}
}
return Visit(node.Expression);
} /// <summary>
/// 访问常量
/// </summary>
/// <param name="node">节点</param>
/// <returns></returns>
protected override Expression VisitConstant(ConstantExpression node)
{
var value = node.Value;
if (value != null && value.GetType().FullName.EndsWith("c__DisplayClass0_0"))
{
foreach (var member in memberInfos)
{
if (member.MemberType == MemberTypes.Field)
{
var fieldInfo = (FieldInfo)member;
value = fieldInfo.GetValue(value);
}
else if (member.MemberType == MemberTypes.Property)
{
var propertyInfo = (PropertyInfo)member;
value = propertyInfo.GetValue(value);
}
}
} if (resolveType != ResolveType.Call)
{
if (value != null && value.GetType().Equals(typeof(DateTime)))
{
value = Convert.ToDateTime(value).ToString(options.DateTimeFormat);//格式化日期类型
}
if (memberInfos.Count > 0)
{
var parameterName = Guid.NewGuid().ToString().Replace("-", "");
sqlInfo.SqlStack.Push($"{options.ParameterNotation}{parameterName}");
sqlInfo.SqlParameters.Add(parameterName, AddWildcard(value));
}
else
{
value = AddQuotes(AddWildcard(value));
sqlInfo.SqlStack.Push(Convert.ToString(value));
}
} //最后一个节点初始化
memberInfos.Clear();
return Expression.Constant(value);//返回新的常量表达式
} #region 私有公共方法 /// <summary>
/// 添加引号
/// </summary>
/// <param name="value">值</param>
/// <returns></returns>
private static object AddQuotes(object value)
{
if (value == null)
{
return null;
}
var type = value.GetType();
if (type.Equals(typeof(char)) || type.Equals(typeof(string)) || type.Equals(typeof(DateTime)))
{
return $"'{value}'";
}
return value;
} /// <summary>
/// 添加识别符
/// </summary>
/// <param name="name">名称</param>
/// <returns></returns>
private string AddIdentifier(string name)
{
if (string.IsNullOrWhiteSpace(options.Identifier))
{
return name;
}
return options.Identifier.Insert(1, name);
} /// <summary>
/// 添加通配符
/// </summary>
/// <param name="value">值</param>
/// <returns></returns>
private object AddWildcard(object value)
{
if (resolveType == ResolveType.LeftLike)
{
return $"%{value}";
}
else if (resolveType == ResolveType.RightLike)
{
return $"{value}%";
}
else if (resolveType == ResolveType.Like)
{
return $"%{value}%";
}
return value;
}
#endregion }
}

https://gitee.com/China-Mr-zhong/Fast.Framework 欢迎Star

最新文章

  1. 基于jsp+servlet图书管理系统之后台万能模板
  2. UML大战需求分析--阅读笔记02
  3. Mac OS X 上安装 ASP.NET 5
  4. PetGenie
  5. HTML5画布实现方法:
  6. java初学者应掌握的30个基本概念
  7. JS打印页面指定区域
  8. zstu.2512. Moving Tables(贪心)
  9. android界面横屏和竖屏的切换
  10. WGS84、Web墨卡托、火星坐标、百度坐标互转
  11. 如何对HTML进行编码和解码?
  12. html的下拉框的几个基本使用方法
  13. Java知识积累——单元测试和JUnit(一)
  14. DevExpress控件XtraGrid的Master-Detail中DetailViewCaption显示问题
  15. Linux 特殊权限位
  16. javascript触发input-file的click事件
  17. webpack的配置及使用
  18. Kafka测试
  19. C++ 函数映射使用讲解
  20. UVa 10188 - Automated Judge Script

热门文章

  1. Kubernetes系列(四) StatefulSet
  2. Netty学习(四)FastThreadLocal
  3. joblib保存模型和joblib的并行化处理和tqdm
  4. 明火烟雾目标检测项目部署(YoloV5+Flask)
  5. ASP.NET Core 6框架揭秘实例演示[24]:中间件的多种定义方式
  6. QQ密码防盗5招
  7. 查看mysql是否开启慢查询
  8. 模型 _meta API ( options )
  9. APIO2015 八邻旁之桥/巴邻旁之桥
  10. 如何理解Node.js和JavaScript的关系