MySQLHelper/Util类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 |
using System; using System.Collections.Generic; using System.Configuration; using MySql.Data.MySqlClient; using System.Data; namespace Utils { /// <summary> /// MySQL数据库工具类 /// </summary> public abstract class MySQLUtil { /// <summary> /// 数据库连接字符串 /// </summary> public static readonly string ConnStr = ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString; /// <summary> /// 执行语句并返回影响的行数 /// </summary> /// <param name="connStr">数据库连接字符串</param> /// <param name="cmdType">命令类型</param> /// <param name="cmdText">命令字符串</param> /// <param name="parameters">参数集</param> /// <returns>影响的行数</returns> public static int ExecuteNonQuery(string connStr, CommandType cmdType, string cmdText, params MySqlParameter[] parameters) { using (var conn = new MySqlConnection(connStr)) { return ExecuteNonQuery(conn, cmdType, cmdText, parameters); } } /// <summary> /// 执行语句并返回影响的行数 /// </summary> /// <param name="conn">数据库连接</param> /// <param name="cmdType">命令类型</param> /// <param name="cmdText">命令字符串</param> /// <param name="parameters">参数集</param> /// <returns>影响的行数</returns> public static int ExecuteNonQuery(MySqlConnection conn, CommandType cmdType, string cmdText, params MySqlParameter[] parameters) { using (var cmd = new MySqlCommand()) { PrepareCommand(cmd, conn, null, cmdType, cmdText, parameters); var val = cmd.ExecuteNonQuery(); cmd.Parameters.Clear(); return val; } } /// <summary> /// 执行语句并返回影响的行数 /// </summary> /// <param name="trans">事务</param> /// <param name="cmdType">命令类型</param> /// <param name="cmdText">命令字符串</param> /// <param name="parameters">参数集</param> /// <returns>影响的行数</returns> public static int ExecuteNonQuery(MySqlTransaction trans, CommandType cmdType, string cmdText, params MySqlParameter[] parameters) { using (var cmd = new MySqlCommand()) { PrepareCommand(cmd, trans.Connection, trans, cmdType, cmdText, parameters); var val = cmd.ExecuteNonQuery(); cmd.Parameters.Clear(); return val; } } /// <summary> /// 执行语句并返回DataReader对象 /// </summary> /// <param name="connStr">数据库连接字符串</param> /// <param name="cmdType">命令类型</param> /// <param name="cmdText">命令字符串</param> /// <param name="parameters">参数集</param> /// <returns>执行语句并返回DataReader对象</returns> /// <remarks>P.S:DataReader使用后要手动释放</remarks> public static MySqlDataReader ExecuteReader(string connStr, CommandType cmdType, string cmdText, params MySqlParameter[] parameters) { var cmd = new MySqlCommand(); var conn = new MySqlConnection(connStr); try { PrepareCommand(cmd, conn, null, cmdType, cmdText, parameters); var dr = cmd.ExecuteReader(CommandBehavior.CloseConnection); cmd.Parameters.Clear(); return dr; } catch { conn.Close(); throw; } } /// <summary> /// 执行语句并返回DataSet对象 /// </summary> /// <param name="cmdText"></param> /// <param name="parameters"></param> /// <returns></returns> public static DataSet ExecuteDataSet(string cmdText, params MySqlParameter[] parameters) { return ExecuteDataSet(ConnStr, CommandType.Text, cmdText, parameters); } /// <summary> /// 执行语句并返回DataSet对象 /// </summary> /// <param name="connStr">数据库连接字符串</param> /// <param name="cmdType">命令类型</param> /// <param name="cmdText">命令字符串</param> /// <param name="parameters">参数集</param> /// <returns>执行语句并返回DataSet对象</returns> /// <returns></returns> public static DataSet ExecuteDataSet(string connStr, CommandType cmdType, string cmdText, params MySqlParameter[] parameters) { using (var conn = new MySqlConnection(connStr)) { return ExecuteDataSet(conn, cmdType, cmdText, parameters); } } /// <summary> /// 执行语句并返回DataSet对象 /// </summary> /// <param name="connStr">数据库连接字符串</param> /// <param name="cmdType">命令类型</param> /// <param name="cmdText">命令字符串</param> /// <param name="parameters">参数集</param> /// <returns>执行语句并返回DataSet对象</returns> /// <returns></returns> public static DataSet ExecuteDataSet(MySqlConnection conn, CommandType cmdType, string cmdText, params MySqlParameter[] parameters) { using (var cmd = new MySqlCommand()) { PrepareCommand(cmd, conn, null, cmdType, cmdText, parameters); using (var da = new MySqlDataAdapter(cmd)) { var ds = new DataSet(); try { da.Fill(ds, "ds"); cmd.Parameters.Clear(); } catch (MySqlException ex) { throw new Exception(ex.Message); } return ds; } } } /// <summary> /// 执行语句并返回一个对象 /// </summary> /// <param name="cmdText">命令字符串</param> /// <param name="parameters">参数集</param> /// <returns>返回一个object对象,对象中只包含一列。</returns> public static object ExecuteScalar(string cmdText, params MySqlParameter[] parameters) { return ExecuteScalar(ConnStr, CommandType.Text, cmdText, parameters); } /// <summary> /// 执行语句并返回一个对象 /// </summary> /// <param name="connStr">数据库连接字符串</param> /// <param name="cmdType">命令类型</param> /// <param name="cmdText">命令字符串</param> /// <param name="parameters">参数集</param> /// <returns>返回一个object对象,对象中只包含一列。</returns> public static object ExecuteScalar(string connStr, CommandType cmdType, string cmdText, params MySqlParameter[] parameters) { using (var conn = new MySqlConnection(connStr)) { return ExecuteScalar(conn, cmdType, cmdText, parameters); } } /// <summary> /// 执行语句并返回一个对象 /// </summary> /// <param name="conn">数据库连接</param> /// <param name="cmdType">命令类型</param> /// <param name="cmdText">命令字符串</param> /// <param name="parameters">参数集</param> /// <returns>返回一个object对象,对象中只包含一列。</returns> public static object ExecuteScalar(MySqlConnection conn, CommandType cmdType, string cmdText, params MySqlParameter[] parameters) { using (var cmd = new MySqlCommand()) { PrepareCommand(cmd, conn, null, cmdType, cmdText, parameters); var val = cmd.ExecuteScalar(); cmd.Parameters.Clear(); return val; } } /// <summary> /// 为执行准备命令对象 /// </summary> /// <param name="cmd">command 对象</param> /// <param name="conn">连接对象</param> /// <param name="trans">事务对象</param> /// <param name="cmdType">命令类型</param> /// <param name="cmdText">命令字符串</param> /// <param name="cmdParms">参数集</param> private static void PrepareCommand(MySqlCommand cmd, MySqlConnection conn, MySqlTransaction trans, CommandType cmdType, string cmdText, IEnumerable<MySqlParameter> parameters) { if (conn.State != ConnectionState.Open) conn.Open(); cmd.Connection = conn; cmd.CommandText = cmdText; if (trans != null) cmd.Transaction = trans; cmd.CommandType = cmdType; if (parameters == null) return; foreach (var parm in parameters) cmd.Parameters.Add(parm); } } } |
View Details
检测到有潜在危险的 Request.Form 值
这种问题是因为你提交的Form中有HTML字符串,例如你在TextBox中输入了html标签,或者在页面中使用了HtmlEditor组件等,解决办法是禁用validateRequest。 如果你是.net 4.0或更高版本,一定要看方法3。 此方法在asp.net webForm和MVC中均适用 方法1: 在.aspx文件头中加入这句:
1 |
<%@ Page validateRequest="false" %> |
方法2: 修改web.config文件:
1 2 3 4 5 |
<configuration> <system.web> <pages validateRequest="false" /> </system.web> </configuration> |
因为validateRequest默认值为true。只要设为false即可。 方法3: web.config里面加上
1 2 3 |
<system.web> <httpRuntime requestValidationMode="2.0" /> </system.web> |
因为4.0的验证在HTTP的BeginRequest前启用,因此,请求的验证适用于所有ASP.NET资源,aspx页面,ashx页面,Web服务和一些HTTP处理程序等. from:https://www.cnblogs.com/youring2/p/3559781.html
View Details正则?=、?<=、?!、?
exp1(?=exp2):查找 exp2 前面的 exp1。 (?<=exp2)exp1:查找 exp2 后面的 exp1。 exp1(?!exp2):查找后面不是 exp2 的 exp1。 (?<!exp2)exp1:查找前面不是 exp2 的 exp1。 from:https://www.runoob.com/regexp/regexp-syntax.html
View Details正则表达式匹配最后一部分
今天,同事问了我一个正则,最后解决了,给大家提供一下参考。用于取网址的最后一部分。本文用于讲解(?= 和 ?<= 和 ?>=和 ?! 的用法) 数据如下: https://download.microsoft.com/download/5/3/8/5388ECC4-C2E2-4D40-8C21-D1EA26FEA0CA/msodll40ui2016-kb4018324-fullfile-x86-glb.exe https://download.microsoft.com/download/B/2/5/B25D200D-804D-44B9-A345-9D1A21386470/osetup2010-kb4022208-fullfile-x86-glb.exe https://download.microsoft.com/download/6/7/0/6703678B-8270-41ED-B16B-5F7D08D05A84/osetup2010-kb4022208-fullfile-x64-glb.exe https://download.microsoft.com/download/6/B/1/6B1BD3F3-A8CC-44BA-86EF-E85A0E066E33/ose2010-kb4022206-fullfile-x86-glb.exe https://download.microsoft.com/download/9/0/1/901D9393-AB8E-4018-BF2D-78341A0B60BE/ose2010-kb4022206-fullfile-x64-glb.exe 通过如下正则可以匹配 / 之后的部分:[^/]+(?!.*exe) 匹配到的数据如下: msodll40ui2016-kb4018324-fullfile-x86-glb.exe osetup2010-kb4022208-fullfile-x86-glb.exe osetup2010-kb4022208-fullfile-x64-glb.exe ose2010-kb4022206-fullfile-x86-glb.exe ose2010-kb4022206-fullfile-x64-glb.exe 说明: [^/]+ 表示匹配任意长度的字符串,字符串中不包含有字符 / ,就可以把以 / 分割开的字符串全匹配到. .*exe 表示任意以exe 结尾的字符串,可以把后在带有exe的字符串匹配到,前面再加上?!,再用圆括号包住表示排除掉(?!.*exe) 整个表达式的意思就是匹配任意长度的不包含/ 的字符串,并把以exe结尾的字符串匹配出来。 举个例子进行讲解: 有个字符串如下 str = "程序人生 程序 人生 序人" 我们想匹配字符串里"程序人生"的"人"字而不要其他的"人"字,这时就需要用到前瞻后顾。 正则表达式如下: (?<=程序)人(?=生) 反过来,我们不想要"程序人生"的"人"字,但是想要其他"人"字。这时就要用到负前瞻,负后顾 正则表达式如下: (?<!程序)人(?!生) 总结一下: 前瞻: exp1(?=exp2) 查找exp2前面的exp1 后顾: (?<=exp2)exp1 查找exp2后面的exp1 负前瞻: exp1(?=exp2) 查找后面不是exp2的exp1 负后顾: (?<=exp2)exp1 查找前面不是exp2的exp1 可以发现,负前瞻、负后顾就是把前瞻、后顾中的"="改成了"!" 注意:后顾功能在大多数语言中有长度限制,只能使用定长的表达式,像\w+和\d 这样的表达式长度可变,不能用在后顾中 备注:最近太忙,看半天也没有真正理解,先记录一下,回头有空再看。 ———————————————— 版权声明:本文为CSDN博主「白夜鬼魅」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/shuryuu/article/details/82910857
View Details一个项目设置两个Git地址,实现同时推送到两个Git仓库
方法一 直接给git仓库添加Origin时,可以实现推送到两个仓库,但是需要推送两次,还是记录一下 在已有Git仓库的项目中gitA中添加另一个gitB远端的地址
1 |
git remote add origin2 地址2 // origin2可以自定义 |
先拉取gitB地址的数据
1 |
git pull origin2 master --allow-unrelated-histories (--allow-unrelated-histories是为了解决冲突) |
把gitA中的内容推送到gitB的地址中
1 |
git push origin2 master |
至此,我们可以通过推送两次的方法,将一个项目提交到两个git地址了
1 2 |
git push origin master git push origin2 master |
但是略繁琐,所以我们通常使用方法2,操作方法2的时候,注意清掉上方操作,删除上面写好的gitB 的远端地址
1 2 3 |
git remote -v // 查看此时的包括两个远程地址 git remote rm origin2 // 删除git B的远程地址 git remote -v //此时应该只有git A的远程地址 |
方法二 给origin 增加一个可以push的地址
1 2 |
git remote set-url --add origin 地址 //给origin添加一个远程push地址,这样一次push就能同时push到两个地址上面 git remote -v //查看是否多了一条push地址(这个可不执行) |
至此,我们就可以直接一个push,同时推送到两个git地址。
1 |
git push origin master -f // 如果第一次推不上去代码,可以使用强推的方式 |
这样一份代码就可以提交到两个git仓库上了 注意:如果需要删除
1 |
git remote set-url --delete origin 地址 |
方法三 可以通过直接修改 .git\config文件 原内容如下
1 2 3 4 5 6 7 8 9 10 11 12 13 |
[core] repositoryformatversion = 0 filemode = true bare = false logallrefupdates = true ignorecase = true precomposeunicode = true [remote "origin"] url = 地址 fetch = +refs/heads/*:refs/remotes/origin/* [branch "master"] remote = origin merge = refs/heads/master |
我们需要添加一个新的remote节点和branch节点就可以实现一次推送更改两个仓库。 添加后
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
[core] repositoryformatversion = 0 filemode = true bare = false logallrefupdates = true ignorecase = true precomposeunicode = true [remote "origin"] url = 地址 fetch = +refs/heads/*:refs/remotes/origin/* [branch "master"] url = 地址2 remote = origin merge = refs/heads/master |
———————————————— 版权声明:本文为CSDN博主「CXLiao」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/CXLiao/article/details/105286752
View Detailslog4net独立文件配置
🥝 NuGet引用log4net 🥝 Configs文件夹下创建log4net.config
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/> </configSections> <log4net> <appender name="InfoRollingLogFileAppender" type="log4net.Appender.RollingFileAppender"> <file value="Logs\\" /> <lockingModel type="log4net.Appender.FileAppender+MinimalLock" /> <appendToFile value="true" /> <rollingStyle value="Date" /> <datePattern value="yyyyMM\\"info_"yyyy-MM-dd".log"" /> <staticLogFileName value="false" /> <layout type="log4net.Layout.PatternLayout"> <conversionPattern value="***************************************************************************************************************************************************%n[%d] [%p] [%F Line:%L] %m%n" /> </layout> <filter type="log4net.Filter.LevelRangeFilter"> <param name="LevelMin" value="INFO" /> <param name="LevelMax" value="INFO" /> </filter> </appender> <appender name="DebugRollingLogFileAppender" type="log4net.Appender.RollingFileAppender"> <file value="Logs\\" /> <lockingModel type="log4net.Appender.FileAppender+MinimalLock" /> <appendToFile value="true" /> <rollingStyle value="Date" /> <datePattern value="yyyyMM\\"debug_"yyyy-MM-dd".log"" /> <staticLogFileName value="false" /> <layout type="log4net.Layout.PatternLayout"> <conversionPattern value="***************************************************************************************************************************************************%n[%d] [%p] [%F Line:%L] %m%n" /> </layout> <filter type="log4net.Filter.LevelRangeFilter"> <param name="LevelMin" value="DEBUG" /> <param name="LevelMax" value="DEBUG" /> </filter> </appender> <appender name="WarnRollingLogFileAppender" type="log4net.Appender.RollingFileAppender"> <file value="Logs\\" /> <lockingModel type="log4net.Appender.FileAppender+MinimalLock" /> <appendToFile value="true" /> <rollingStyle value="Date" /> <datePattern value="yyyyMM\\"warn_"yyyy-MM-dd".log"" /> <staticLogFileName value="false" /> <layout type="log4net.Layout.PatternLayout"> <conversionPattern value="***************************************************************************************************************************************************%n[%d] [%p] [%F Line:%L] %m%n" /> </layout> <filter type="log4net.Filter.LevelRangeFilter"> <param name="LevelMin" value="WARN" /> <param name="LevelMax" value="WARN" /> </filter> </appender> <appender name="ErrorRollingLogFileAppender" type="log4net.Appender.RollingFileAppender"> <file value="Logs\\" /> <lockingModel type="log4net.Appender.FileAppender+MinimalLock" /> <appendToFile value="true" /> <rollingStyle value="Date" /> <datePattern value="yyyyMM\\"error_"yyyy-MM-dd".log"" /> <staticLogFileName value="false" /> <layout type="log4net.Layout.PatternLayout"> <conversionPattern value="***************************************************************************************************************************************************%n[%d] [%p] [%F Line:%L] %m%n" /> </layout> <filter type="log4net.Filter.LevelRangeFilter"> <param name="LevelMin" value="Error" /> <param name="LevelMax" value="FATAL" /> </filter> </appender> <root> <appender-ref ref="InfoRollingLogFileAppender" /> <appender-ref ref="DebugRollingLogFileAppender" /> <appender-ref ref="WarnRollingLogFileAppender" /> <appender-ref ref="ErrorRollingLogFileAppender" /> </root> </log4net> </configuration> |
🥝 AssemblyInfo.cs文件下添加
1 |
[assembly: log4net.Config.XmlConfigurator(ConfigFile = @"Configs\log4net.config", Watch = true)] |
🥝 类中引用
1 |
private ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); |
View Details
Cassandra vs MongoDB vs CouchDB vs Redis vs Riak vs HBase vs Couchbase vs OrientDB vs Aerospike vs Neo4j vs Hypertable vs ElasticSearch vs Accumulo vs VoltDB vs Scalaris vs RethinkDB comparison
(Yes it’s a long title, since people kept asking me to write about this and that too :) I do when it has a point.) While SQL databases are insanely useful tools, their monopoly in the last decades is coming to an end. And it’s just time: I can’t even count the things that were forced into relational databases, but never really fitted them. (That being said, relational databases will always be the best for the stuff that has relations.) But, the differences between NoSQL databases are much bigger […]
View Details在XML中放一段HTML文本
最近在写一些东西的时候,有一些数据是使用XML来保存的,而其中有一些数据是一段HTML文本,一开始觉得没什么问题,当把这些HTML放到XML的一个结点的时候,才发现数据已经变样了。 原来在XML中有5个预定义的实体引用: < < 小于 > > 大于 & & 和号 ' ' 省略号 " " 引号 参考:http://www.w3school.com.cn/xml/xml_cdata.asp HTML和XML差不多,是由一些预定义的标签组成的,所以包含了大量的"<"、">"符号。 所以有两个方法可以将HTML插入到XML中: 1、将HTML进行一次转换,将所有的"<"、">"等预定义符号转换成相应的实体引用。 2、将HTML包在CDATA中。 很明显,第一种方法比较烦琐,序列化数据的时候和把序列化的时候都需要进行转换。 第二种方法则很简单了。 写了一个方法:
1 2 3 4 5 6 7 |
public class XMLUtil { public static function CDATA(data:String):XML { return new XML( " <![CDATA[ " + data + " ]]\> " ); } } |
可以这样使用:
1 |
var node:XML = < node > {XMLUtil.CDATA( " 字符串 " )} </ node > ; |
参考: http://cookbooks.adobe.com/post_Create_CDATA_tags_between_XML_nodes_using_AS-6142.html 转载于:https://www.cnblogs.com/yili16438/archive/2011/04/13/2015369.html from:https://blog.csdn.net/weixin_30556161/article/details/97479499
View Detailsmysql强制使用指定索引查询
语法: select * from table_name force index (index_name) where conditions; 使用 关键字 force
1 2 3 |
#########mysql强制使用指定索引查询 SELECT * FROM `yrd_pay_flow` FORCE INDEX(`idx_trxn_date`) WHERE trxn_date > '2017-08-12 59:59:59' AND trxn_date < '2016-06-23 00:00:00'; |
from:https://www.cnblogs.com/pc-boke/articles/9916986.html
View Detailsspring boot服务docker镜像优化之旅
缘由 我们在使用spring boot开发的服务中,一般会选择打包成单体的fatjar来发布服务,这在传统的部署方式下是非常方便的,但是当我们选择使用docker这种容器化的方式来部署应用的时候,却有一点点的不便之处,因为这个单体的jar一般都比较大,每次镜像push到仓库和从仓库拉取都需要比较长的时间。 原因是什么了? docker的一大特色就是镜像的存储是分层的,参考下面这张官图 我们在Dockerfile中的每一个指令会对应到镜像的每一层,docker在更新镜像时,只会推送变更过的层,当它计算出来这一层的摘要和之前的版本一致时,会复用上一次打包镜像时的缓存,会极大的提高打包镜像以及镜像push/pull操作的速度。 那么问题来了,当我们springboot打包出来的单体jar的时候,每次编译这个jar都会发生变化,对应的存储层也会发生变化,push和pull操作时都需要重新推送,而且这个jar一般都不小,一个典型的应用会在100M左右,对应用部署和发布的速度会有比较大的影响。 稍作思考,很容易就能发现这个肥大的jar文件里面,大部分其实都是固定不变的各种依赖库,我们真正每次编译会变化的业务代码部分其实很小很小,可能也就只有几百KB,只要能将这两部分分离,变成docker镜像中的两层,一定能极大的提升镜像发布的速度 牛刀小试 首先拿来动手尝试的是一个springboot admin的项目,项目的结构是这样的: 使用最常见的打包方式:
1 2 3 4 |
<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> |
会生成一个32M的jar文件,优化之前的Dockerfile非常简单:
1 2 3 4 5 6 7 8 |
FROM openjdk:11.0.5-stretch as builder RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime RUN echo 'Asia/Shanghai' >/etc/timezone VOLUME /tmp COPY target/*.jar ./app.jar CMD ["/bin/bash", "-c", "java -jar -server app.jar"] EXPOSE 8080 |
可以看到这种方式在构建v2版本的镜像时,会重新copy整个完整的jar 如果要拆开这个单体的jar,有两种方式,一是修改mvn打包的配置,将lib包放在独立的文件夹下,在这里我们考虑到项目众多,尽量减少修改,选择了在Docker打包镜像时,解压打包出来的jar包,将其中的内容分开来copy,修改后的Dockerfile如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#采用docker的分阶段构建方式,第一阶段负责解压jar包 FROM openjdk:11.0.5-stretch as builder RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime RUN echo 'Asia/Shanghai' >/etc/timezone VOLUME /tmp WORKDIR /target ADD target/*.jar app.jar RUN jar xf app.jar #这里分别copy解压后的内容 FROM openjdk:11.0.5-stretch VOLUME /tmp WORKDIR /app COPY --from=builder target/BOOT-INF/lib ./lib COPY --from=builder target/org/ ./org COPY --from=builder target/META-INF/ ./META-INF COPY --from=builder target/BOOT-INF/classes ./classes CMD ["/bin/bash", "-c", "java -cp .:./classes/:./lib/* -server org.springframework.boot.loader.JarLauncher"] EXPOSE 8080 |
看看修改后的效果: 在copy lib目录时,是直接using cache的。来看看push的时候效果对比 首先是优化前的push: 可以看到在push v2的时候还是会push一个33MB的层,虽然其实我们一行代码没有修改。 然后是优化后的: 可以看到这一次仅仅只推送了一个13KB的层,推送的速度快了非常多,同理也可以想象的到,我们在拉取镜像更新版本时速度会快很多。 路遇荆棘 在针对springboot-admin这个最简单的项目的优化取得很好的效果之后,就开始准备照搬到其他的项目中,没想到同样的方式怎么折腾都无效,分离之后的lib目录依然会每次需要全量重新push。出问题的项目结构大概是这样的: 一个常见的多模块mvn项目,有common,domain,rest-client,rest-server 这4个子模块,其中rest-server会依赖common和domain这两个子模块,打包出来的jar是在rest-server这个模块中。 究竟是什么鬼了? 终得正果 苦苦思索一番之后,lib目录既然不能复用上一次的cache,那一定是因为里面的内容有变化,遂将jar包解压,进到lib目录,真凶果然在此: 项目自身的3个子模块在每次编译的时候也会做为jar包放到lib目录下,这3个jar包每次编译都会有变化,所以导致这一层的cache失效。 找到问题之后,解决的思路就很简单了,将这种jar单独copy到一个目录下即可,修改后的Dockerfile如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
FROM openjdk:11.0.5-stretch as builder RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime RUN echo 'Asia/Shanghai' >/etc/timezone VOLUME /tmp WORKDIR /target ADD target/*.jar app.jar RUN jar xf app.jar #创建一个snapshot目录,把snapshot的jar复制过去 RUN mkdir BOOT-INF/snapshot RUN mv BOOT-INF/lib/*SNAPSHOT.jar BOOT-INF/snapshot/ FROM openjdk:11.0.5-stretch VOLUME /tmp WORKDIR /app COPY --from=builder target/BOOT-INF/lib ./lib COPY --from=builder target/org/ ./org COPY --from=builder target/META-INF/ ./META-INF #单独复制snapshot这一层 COPY --from=builder target/BOOT-INF/snapshot ./snapshot COPY --from=builder target/BOOT-INF/classes ./classes CMD ["/bin/bash", "-c", "java -cp .:./classes/:./lib/*:./snapshot/* -server org.springframework.boot.loader.JarLauncher"] EXPOSE 8089 |
这样修改之后效果就和上面单模块的项目一样了,至此,基本完成了springboot项目的docker镜像优化,在jenkins的流水线上可以将原来镜像push的时间从1分钟以上优化到10s左右 未来之路 在整个优化的过程中,发现springboot2.3 M1版本已经有针对性的优化方案,增加了LAYERED_JAR的打包格式,未来可期。 具体可参考下文: www.jdon.com/53738 注: 本文中举例的两个项目案例,可在github上找到:github.com/yishh/sprin… 作者:thor_lee 链接:https://juejin.cn/post/6844904119338008583 来源:掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
View Details