xEasyApp之后端的介绍
前文我已经说了,为了能够让大家更好的理解xjplugin如何在asp.net mvc中应用,我编写了这样一个demo,本篇博文简要的说明下xEasy的结构,讲解一下ASP.NET MVC 和xjplugin 之外的东西。从这里下载到代码。要求安装了VS2010 和ASP.NETMVC3.0.打开解决方案,我们可以看到有两个主要的项目和一个解决方案文件夹,如下图所示:
其中xEasyApp.Web为网站, 包括视图,控制器,视图所需的特殊的Model 和js,css,image等文件。
xEasyApp.Core则包含项目的业务逻辑层,数据访问层,和一起一些公用的类,如异常,配置读取类等。MVC中的Model在这里。。
refdll中是项目中所引用的外部DLL,这里只有一个StructureMap (这时候一个IOC的类库,非常轻巧,也方便使用)。
1: 利用T4模板生成数据访问
也许还有人问T4是什么,关于T4的一些细节大家可以参考这里的几篇文章:
http://msdn.microsoft.com/zh-cn/library/dd820620.aspx
http://t4-editor.tangible-engineering.com/How-Do-I-With-T4-Editor-Text-Templates.htm
http://www.cnblogs.com/artech/archive/2010/10/23/codegeneration_t4.html
说到底它是一个基于模板的代码生成工具,其中模板又可以用C#编写,所以很方便哦
在xEasyApp中利用T4模板根据数据库的结构生成基本的访问代码(它生成的不是一个完整的ORM框架,事实上如果要做的话是可以的。要知道Entity Framework就是基于T4的代码生成),在生成代码之前,我编写了两个基类用来包含一些功能的方法和属性,以便在编码的更好的调用
一个是:BaseRepository :定义了数据访问的基本方法如ExcuteDataReader,ExcuteDataTable,ExecuteScalar,ExcuteNoQuery,还有执行相关存储过程的方法。 如果你在实际的项目中需要更好的控制连接池,或者数据访问层,可以对这个基类进行调整。xEasyApp现在是使用的SqlHelper我相信已经可以满足大部分的企业内部应用需求。
另外一个是:BaseEntity 定义数据实体的基类,定义了实体状态的一些基本方法,如变更的字段,是否为新增记录等等。
另外还定义了存储过程的封装。
定义了五个模板分别生成数据访问,实体和存储过程访问封装,其中Settings.ttinclude 定义的是配置和配置读取类,还有一些最基本的,如表信息,列信息的描述等,而SQLServer.ttinclude则包含读取数据库中
Repositories.tt 是生成数据访问类,每个表一个类,会生成新增更新和删除,获取方法。
1: <#@ template language="C#v3.5" debug="False" hostspecific="True" #>
2: <#@ output extension=".cs" #>
3: <#@ include file="SQLServer.ttinclude" #>
4: <# var tables = LoadTables();#>
5: //=============================================
6: // 该
7: // 生 <#= DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") #>
8: // =============================================
9: using System;
10: using System.Data;
11: using System.Text;
12: using System.Data.SqlClient;
13: using System.Collections.Generic;
14:
15:
16: namespace <#=Namespace#> {
17:
18: <# foreach(var tbl in tables){
19: if(!ExcludeTables.Contains(tbl.Name))
20: {
21: var pkColumn = tbl.Columns.SingleOrDefault(x => x.Name.ToLower().Trim() == tbl.PrimaryKey.ToLower().Trim());
22:
23: #>
24: /// <summary>
25: /// Table: <#=tbl.Name#>
26: /// Primary Key: <#=tbl.PrimaryKey#>
27: /// </summary>
28: public partial class <#=Cleans(tbl.CleanName)#>Repository:BaseRepository
29: {
30: public void Save(<#=Cleans(tbl.CleanName)#> item)
31: {
32: if(item.IsNew)
33: {
34: Insert(item);
35: }
36: else
37: {
38: Update(item);
39: }
40: }
41: public <#=Cleans(tbl.CleanName)#> Get(<#=pkColumn.SysType#> key)
42: {
43: string sql = "SELECT <#
44: int i=0;
45: foreach(var col in tbl.Columns){
46: if(i>0)
47: {
48: #>,<#
49: }
50: #>[<#= col.Name #>]<#
51: i++;
52: }
53: #> FROM [<#=tbl.Name#>] WHERE [<#=tbl.PrimaryKey#>]=@<#=tbl.PrimaryKey#>";
54: SqlParameter p =new SqlParameter("@<#=tbl.PrimaryKey#>",key);
55: <#=Cleans(tbl.CleanName)#> item =null;
56: using(IDataReader reader = base.ExcuteDataReader(sql,p))
57: {
58: if(reader.Read())
59: {
60: item =new <#=Cleans(tbl.CleanName)#>();
61: <#
62: i=0;
63: foreach(var col in tbl.Columns){
64: if(col.IsNullable)
65: {
66: #>if(!reader.IsDBNull(<#=i#>))
67: {
68: item.<#= col.CleanName #> = <#=GetReadFormatString("reader.",col.DataType,i) #>;
69: }
70: <# }
71: else
72: {
73: #>
74: item.<#= col.CleanName #> = <#=GetReadFormatString("reader.",col.DataType,i) #>;
75: <#
76: }
77: i++;
78: }#>
79:
80: }
81: }
82: return item;
83: }
84: public int Delete(<#=pkColumn.SysType#> key)
85: {
86: string sql ="DELETE FROM [<#=tbl.Name#>] WHERE [<#=tbl.PrimaryKey#>]=@<#=tbl.PrimaryKey#>";
87: SqlParameter p =new SqlParameter("@<#=tbl.PrimaryKey#>",key);
88: return base.ExecuteNonQuery(sql,p);
89: }
90: public void Insert(<#=Cleans(tbl.CleanName)#> item)
91: {
92: <#
93: bool pkAutoIncrement = false;
94: #>
95: string sql="INSERT INTO [<#=tbl.Name#>] (<# i = 0; foreach (var col in tbl.Columns)
96: {
97: if (col.AutoIncrement) { if (!pkAutoIncrement) { pkAutoIncrement = col.IsPK; }; continue; } if (i > 0)
98: {#>,<#}#>[<#= col.Name #>]<# i++;}#>) VALUES (<# i=0;foreach(var col in tbl.Columns){ if(col.AutoIncrement){continue;} if(i>0){#>,<#}#>@<#= col.CleanName #><# i++;}#>)";
99: List<SqlParameter> SPParams = new List<SqlParameter>();
100: <#foreach(var col in tbl.Columns){ if(col.AutoIncrement){continue;}#>
101: SPParams.Add(new SqlParameter("@<#=col.CleanName#>",item.<#=col.CleanName#>));
102: <#}#>
103: <#
104: if(pkAutoIncrement)
105: {
106: #>
107: sql +=";SELECT Scope_Identity()";
108: object o = base.ExecuteScalar(sql, SPParams.ToArray());
109: if(o!=null){
110: item.<#=tbl.PrimaryKey#> =Convert.ToInt32(o);
111: }
112: <#
113: }
114: else
115: {
116: #>
117: base.ExecuteNonQuery(sql, SPParams.ToArray());
118: <#
119: }
120: #>
121: }
122: public void Update(<#=Cleans(tbl.CleanName)#> item)
123: {
124: if(item.ChangedPropertyCount>0)
125: {
126: StringBuilder sqlbuilder = new StringBuilder();
127: sqlbuilder.Append("UPDATE [<#=tbl.Name#>] SET ");
128: Dictionary<string,string> cols =new Dictionary<string,string>();
129: <#foreach(var col in tbl.Columns){ if(col.IsPK){continue;}#>
130: cols.Add("<#= col.CleanName #>","[<#= col.Name #>]");
131: <#}#>
132: int i = 0;
133: //UPDATE COLUMNS
134: foreach (string p in item.ChangedPropertyList)
135: {
136: if(!cols.ContainsKey(p))
137: {
138: continue;
139: }
140: if (i > 0)
141: {
142: sqlbuilder.Append(",");
143: }
144: sqlbuilder.AppendFormat("{0}=@{1}", cols[p], p);
145: i++;
146: }
147: //WHERE;
148: sqlbuilder.Append(" WHERE [<#=tbl.PrimaryKey#>]=@<#=tbl.PrimaryKey#>");
149:
150: List<SqlParameter> SPParams = new List<SqlParameter>();
151: <#foreach(var col in tbl.Columns){#> <#if(!col.IsPK){#>
152: if(item.IsChanged("<#=col.CleanName #>"))
153: {
154: SPParams.Add(new SqlParameter("@<#=col.CleanName#>",item.<#=col.CleanName#>));
155: }<#}else{#>
156: SPParams.Add(new SqlParameter("@<#=col.CleanName#>",item.<#=col.CleanName#>));
157: <#}}#>
158:
159: base.ExecuteNonQuery(sqlbuilder.ToString(), SPParams.ToArray());
160: }
161: }
162: public List<<#=Cleans(tbl.CleanName)#>> QueryAll()
163: {
164: string sql ="SELECT <# i=0;foreach(var col in tbl.Columns){ if(i>0){#>,<#}#>[<#= col.Name #>]<# i++;}#> FROM [<#=tbl.Name#>]";
165: List<<#=Cleans(tbl.CleanName)#>> list =new List<<#=Cleans(tbl.CleanName)#>>();
166: using(IDataReader reader = base.ExcuteDataReader(sql))
167: {
168: while(reader.Read())
169: {
170: <#=Cleans(tbl.CleanName)#> item =new <#=Cleans(tbl.CleanName)#>();
171: <#
172: i=0;
173: foreach(var col in tbl.Columns){
174: if(col.IsNullable)
175: {
176: #>if(!reader.IsDBNull(<#=i#>))
177: {
178: item.<#= col.CleanName #> = <#=GetReadFormatString("reader.",col.DataType,i) #>;
179: }
180: <# }
181: else
182: {
183: #>
184: item.<#= col.CleanName #> = <#=GetReadFormatString("reader.",col.DataType,i) #>;
185: <#
186: }
187: i++;
188: }#>
189: list.Add(item);
190: }
191: }
192: return list;
193: }
194:
195: }
196:
197: <#
198: }
199:
200: }
201: #>
202: }
而Structs.tt 比较简单还是根据表数据生成实体,这里可以扩展比如你要在实体上加上验证。
1: <#@ template language="C#" debug="False" hostspecific="True" #>
2: <#@ output extension=".cs" #>
3: <#@ include file="SQLServer.ttinclude" #>
4: <#
5: var tables = LoadTables();
6: #>
7: //=============================================
8: // 该
9: // 生 <#= DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") #>
10: // =============================================
11: using System;
12: using System.Collections.Generic;
13: using System.Data;
14:
15: namespace <#=Namespace#> {
16: <# foreach(var tbl in tables){
17: if(!ExcludeTables.Contains(tbl.Name))
18: {
19: #>
20: /// <summary>
21: /// Table: <#=tbl.Name#>
22: /// Primary Key: <#=tbl.PrimaryKey#>
23: /// <#=string.IsNullOrEmpty(tbl.Description)?tbl.Name:tbl.Description.Replace("\r\n", "\r\n ///")#>
24: /// </summary>
25: public partial class <#=Cleans(tbl.CleanName)#>:BaseEntity {
26:
27: <# foreach(var col in tbl.Columns){#>
28: private <#=col.SysType#> _<#=col.CleanName#>;
29: /// <summary>
30: /// <#=string.IsNullOrEmpty(col.Description)?col.Name:col.Description.Replace("\r\n", "\r\n ///")#>
31: /// </summary>
32: public <#=col.SysType#> <#=col.CleanName#>{
33: get{
34: return _<#=col.CleanName#>;
35: }
36: set
37: {
38: _<#=col.CleanName#>= value;
39: OnPropertyChanged("<#=col.CleanName#>");
40: }
41: }
42: <# }#>
43: }
44:
45: <#}}#>
46: }
StoredProcedures.tt 则是生成对存储过程的封装,不要再去记那繁琐的存储过程名字,参数名字,参数类型了。
1: <#@ template language="C#v3.5" debug="False" hostspecific="True" #>
2: <#@ output extension=".cs" #>
3: <#@ include file="SQLServer.ttinclude" #>
4: <#
5: var sps = GetSPs();
6: if(sps.Count>0){
7: #>
8: //=============================================
9: // 该
10: // 生 <#= DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") #>
11: // =============================================
12: using System;
13: using System.Data;
14:
15: namespace <#=Namespace#>{
16: public partial class StoredProcedures{
17:
18: <# foreach(var sp in sps){#>
19: public static StoredProcedure <#=sp.CleanName#>(<#=sp.ArgList#>){
20: StoredProcedure sp=new StoredProcedure("<#=sp.Name#>");
21: <# foreach(var par in sp.Parameters){#>
22: sp.AddParameter("<#=par.Name#>",<#=par.CleanName#>,DbType.<#=par.DbType#>);
23: <# }#>
24: return sp;
25: }
26: <# }#>
27:
28: }
29:
30: }
31: <# }#>
有了这些至少最基本的代码不用编写,同时在编写有些代码的时候我们可以复制了。。
尽管如此,其实在代码中我大多的数据访问代码除了实体和存储过程调用,其他的我还是自己写sql的,哈哈。。。。
2 使用 StructureMap 来实现IOC
上面已经给出了StructureMap ,以前也用过Castle,不过我发现这个就一个DLL,而且使用起来很简洁方便。性能上也还不错
在控制器中通过构造函数引入服务
定义服务的接口
实现服务,如果服务的实现又依赖其他服务可以通过同样的方式引入
然后注册服务,我这里是通过代码进行映射的,也可以通过配置文件等方法,同时也可以通过配置文件传递构造函数的参数等,具体请看
http://structuremap.net/structuremap/
声明一个注册器,根据不能服务类型可以声明不同的注册器,这里我只声明了一个
1: public class ServiceRegistry : Registry
2: {
3: public ServiceRegistry()
4: {
5: For<ISysManageService>().Use<SysManageService>();
6: For<IUserService>().Use<UserService>();
7: For<ILogService>().Use<LogService>();
8: }
9:
10: }
11:
然后是注册注册器的包装了,如果有多个注册器,这里则需要初始化多个注册器
1: public static class Bootstrapper
2: {
3: public static void ConfigureStructureMap()
4: {
5: ObjectFactory.Initialize(x => {
6: x.AddRegistry(new Core.ServiceRegistry());
7: });
8: }
9: }
10:
接着我们要重新实现以下MVC的控制器工厂类
public class StructureMapControllerFactory : DefaultControllerFactory{protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType){if (controllerType == null){throw new HttpException(404,String.Format("请求的地址{0}不存在",requestContext.HttpContext.Request.Path));}if (!typeof(IController).IsAssignableFrom(controllerType)){throw new ArgumentException(String.Format( "没有合适的控制器实现{0}",controllerType),"controllerType");}try{return ObjectFactory.GetInstance(controllerType) as Controller;}catch (StructureMapException){System.Diagnostics.Debug.WriteLine(ObjectFactory.WhatDoIHave());throw;}}}
最后需要注册以下,在网站的Global.asax 中
protected void Application_Start(){AreaRegistration.RegisterAllAreas(); RegisterGlobalFilters(GlobalFilters.Filters);RegisterRoutes(RouteTable.Routes);//Configure StructureMapConfigurationBootstrapper.ConfigureStructureMap();//Set current Controller factory as StructureMapControllerFactoryControllerBuilder.Current.SetControllerFactory(new xEasyApp.Core.StructureMapControllerFactory()); }
3 编写json实体用于和客户端交互
因为xEasyApp多数的请求都是ajax访问的定义json提示非常又必要,不然自己去拼接json字符串,容易出错,也不好维护。其实大家就可以通过实体看出json数据格式了。也便于大家理解
比较复杂就Flexigrid的数据实体了 但是编写成服务端实体就看着不麻烦
public class JsonFlexiGridData{public JsonFlexiGridData(){}public JsonFlexiGridData(int pageIndex, int totalCount, IList<FlexiGridRow> data){page = pageIndex;total = totalCount;rows = data;}public int page { get; set; }public int total { get; set; }public IList<FlexiGridRow> rows { get; set; }/// <summary>/// Gets or sets the error./// </summary>/// <value>The error.</value>public FlexiGridError error { get; set; }public static JsonFlexiGridData ConvertFromList<T>(List<T> list,string key,string[] cols) where T:class{JsonFlexiGridData data = new JsonFlexiGridData();data.page = 1;if (list != null){data.total = list.Count;data.rows = new List<FlexiGridRow>();foreach (T t in list){ FlexiGridRow row =new FlexiGridRow();row.id = getValue<T>(t,key);row.cell = new List<string>();foreach(string col in cols){row.cell.Add(getValue<T>(t, col));}data.rows.Add(row);}}else{data.total = 0;}return data;}private static string getValue<T>(T t,string pname) where T:class{Type type = t.GetType();PropertyInfo pinfo = type.GetProperty(pname);if (pinfo != null){object v = pinfo.GetValue(t, null);return v != null ? v.ToString() : "";}return "";}public static JsonFlexiGridData ConvertFromPagedList<T>(PagedList<T> pagelist, string key, string[] cols) where T : class{JsonFlexiGridData data = new JsonFlexiGridData();data.page = pagelist.PageIndex+1;if (pagelist.PageIndex == 0){data.total = pagelist.Total;}data.rows = new List<FlexiGridRow>();foreach (T t in pagelist.DataList){FlexiGridRow row = new FlexiGridRow();row.id = getValue<T>(t, key);row.cell = new List<string>();foreach (string col in cols){row.cell.Add(getValue<T>(t, col));}data.rows.Add(row);}return data;}}
/// <summary>/// flexigrid的数据行
/// </summary>public class FlexiGridRow{public string id { get; set; }public List<string> cell { get; set; }} 这里定义了两个比较有用的方法,一个是List转换成flexigrid .另外一个则是通过PageList转换成flexigrid的数据
你只需提供主键列和列信息(而这两个 已经由前端发送到服务端了哦) 。这样可以避免在写查询的时候也要和前端的列定义一一对应和编写繁琐的转换代码了
生成的flexigrid格式是大概这样的
{"page" : 2, //页码"total" : -1, //总记录数,flexigrid我修改为只在第一页获取总记录数"rows" : [{"id" : "26", //行主键"cell" : ["26", "棉花糖", "每箱30盒", "31.2300", "15", "0", "26"] //每行的数据 ,必须和前端定义的列一一对应,顺序必须一致}, {"id" : "27","cell" : ["27", "牛肉干", "每箱30包", "43.9000", "49", "0", "27"] }] }
另一个常用的则是TreeNode之前竟然有人来问为什么一切都是对的,但是前端就是没有显示数据,多半是因为服务端json数据格式不正确或者属性的值设置不正确导致的
public class JsonTreeNode{#region properties/// <summary>/// treenode的主键必须唯一/// </summary>/// <value>The id.</value>public string id { get; set; }/// <summary>/// treenode的显示文本/// </summary>/// <value>The text.</value>public string text { get; set; }/// <summary>/// 节点的值/// </summary>/// <value>The value.</value>public string value { get; set; }/// <summary>/// 是否显示checkbox,如果前端设置了配置 showcheck: true,这里设置为true也有用,否则始终不会显示checkbox/// 相反就算你前端设置了showcheck: true ,这个属性设置false那么前端这个节点也不会显示checkbox。/// </summary>/// <value><c>true</c> if showcheck; otherwise, <c>false</c>.</value>public bool showcheck { get; set; }/// <summary>/// 是否展开,一般用于父节点展开,同时再获取一下节点的数据。/// 当设置为true时应该保证他的下级数据已经加载。/// </summary>/// <value><c>true</c> if isexpand; otherwise, <c>false</c>.</value>public bool isexpand { get; set; }/// <summary>/// 选中的状态0,1,2 0为没有选中,1为选中,2为半选/// </summary>/// <value>The checkstate.</value>public byte checkstate { get; set; }/// <summary>/// 是否有子节点/// </summary>/// <value>/// <c>true</c> if this instance has children; otherwise, <c>false</c>./// </value>public bool hasChildren { get; set; }/// <summary>/// 是否已经完成加载,如果这个节点设置为true则,展开节点时不会发起ajax请求。/// </summary>/// <value><c>true</c> if complete; otherwise, <c>false</c>.</value>public bool complete { get; set; }/// <summary>/// 节点的自定义样式,可以为节点设置特定的样式,如修改节点的图标/// </summary>/// <value>The classes.</value>public string classes { get; set; }/// <summary>/// 额外的数据/// </summary>/// <value>The data.</value>public Dictionary<string, string> data { get; set; }private List<JsonTreeNode> _ChildNodes;public List<JsonTreeNode> ChildNodes{get{if (_ChildNodes == null){_ChildNodes = new List<JsonTreeNode>();}return _ChildNodes;}}#endregion}
我在代码中加入了详细的说明,大家可以参考下。。另外几个都是比较简单的如一般操作的返回成功失败的消息定义,就不一一介绍了
在MVC中只需 这样就可以了方便吧
下节,我讲介绍客户端的调用和注意事项,并且描述下几个常用的场景和我的思考,另外介绍下最近的一些更新。。
你的支持是我继续写作的动力
xEasyApp之后端的介绍相关推荐
- 棋牌游戏前后端技术介绍
1前端技术介绍 Cocos2d Cocos2d-x+lua 版本稳定,技术相对成熟,可以ios adroid pc三端打包,支持热更新,相关技术人员多,相对比较好招人. Cocos Creator ...
- 卓医通项目后端架构介绍
卓医通项目后端架构介绍 项目简介 卓医通是以完整的基层医疗机构-信息化解决方案为出发点,打造链接诊所.医生.患者.一站式互联网医疗服务系统,深度挖掘基层医疗机构需求,解决其真正痛点:全面提升医疗管理质 ...
- 后端自我介绍_新人入职自我介绍
明天终于要上班了,来上海闲了有一个半月,急得不行,刚刚hr发来信息让我准备一段自我介绍和一张生活照. 生活照我就选了去年底去三亚玩的时候拍的海边照,如此的朴实无华,没有任何ps: 重点是个人介绍啦,在 ...
- 芯片设计了解吗?芯片设计之前后端设计介绍
为增进大家对芯片的认识,本文将对芯片设计的前后端设计予以介绍. 芯片是电子设备的重要组成器件之一,芯片的制作材料其实就是二氧化硅,可以从沙子中进行提取.为增进大家对芯片的认识,本文将对芯片设计的前后端 ...
- 【 Ecology9 】- 2 前后端联调介绍
1 前端页面 以 Demo06完整增删改 为例: 配置路由地址: /main/cs/app/cc3645868cbd4397940e4784319c2701_baseTable 浏览器访问地址: /w ...
- php post请求后端拿不到值_[精选] uniapp实现多端开发,与PHP是如何结合的
文章来自:https://www.jianshu.com/p/b2cb2a2c3313 作者:Neyo_凉 点击加入:PHP自学中心技术交流微信群 商务合作: 请加微信(QQ):2230304070 ...
- 《浅谈架构之路:前后端分离模式》 - 山人行 - 博客园
前言:分离模式 对前后端分离研究了一段时间,恰逢公司有一个大项目决定尝试使用前后端分离模式进行,便参与其中.该项目从2016年初立项至今,平平稳稳得度过,但也涌现出越来越多的问题,绝对不是说前后端分离 ...
- 《浅谈架构之路:前后端分离模式》
前言:分离模式 对前后端分离研究了一段时间,恰逢公司有一个大项目决定尝试使用前后端分离模式进行,便参与其中.该项目从2016年初立项至今,平平稳稳得度过,但也涌现出越来越多的问题,绝对不是说前后端分离 ...
- 【编程规范】 后端API接口设计编写与文档编写参考
文章目录 0 统一规范 0.1 理清业务流程 0.2 定义前后端开发的接口规范 0.3 定义接口文档 1 后端接口编写 1.0 后端接口介绍 1.0.1 接口交互 1.0.2 返回格式 1.0.3 C ...
- 【总览】程序员前端、后端资源合集
[总览]程序员前端.后端资源合集 1.程序员日常聚集交流地 2.前端界面介绍 2.1 辅助工具 2.2 好的架构 2.3 源码 3后端API介绍 3.1 辅助工具 3.2 好的架构 3.3源码 4.前 ...
最新文章
- es存在某个字段的查阅_ElasticSearch系列02:ES基础概念详解
- http://jsbeautifier.org/
- 如何在Cordova Android 7.0.0 以下版本集成最新插件 极光插件为例 1
- 每天一道LeetCode-----计算从二维数组的左上角到达右下角的所有路径数及最短的那条,如果存在障碍物时又是多少
- 近期打算及毕业前要补完的题
- 数据库存储模型-数据存储
- java通过POI技术将html转成word
- 信息学奥赛一本通(2049:【例5.19】字符串判等)
- 局域网ARP病毒的清理
- Linux多线程编程详细解析----条件变量 pthread_cond_t
- 西门子TIA portal中如何安装FANUC机器人的GSD文件
- html表单有几部分组成,网页制作时表单由哪两部分组成
- html展示微信昵称特殊字符,微信昵称特殊符号(独一无二的特殊符号)
- DHCP Relay 配置教程
- vue中prop的用法
- 2022 CCF中国软件大会(CCF Chinasoft)“CCF-华为胡杨林基金-系统软件专项”论坛成功召开...
- 对 kubeadm 进行故障排查
- 密码库LibTomCrypt学习记录——(2.15)分组密码算法的工作模式——GCM加密认证模式
- Adobe或QQ的oxc000007b错误解决方案.
- Java毕设项目大学生创业众筹系统(java+VUE+Mybatis+Maven+Mysql)