6月 05

写了个简单的代码生成工具。不愿意使用拼接字符串的方式,所以将目光投向了Razor

在.Net Framework时代,我们可以直接只用 RazorEngine 做执行输出。

但是我试了发现不支持.Net Core大的版本,所以上github看了看,发现了替代品RazorLight

 

使用起来很简单,在Nuget上有包

Install-Package RazorLight

一般调用方式

            var engine = new RazorLightEngineBuilder()
              .UseFilesystemProject(@"D:\Test\CoreTest\ConsoleApp.RazorConsole")
              .UseMemoryCachingProvider()
              .Build();
            string result = engine.CompileRenderAsync("Ocean.cshtml",
                new { Name = "Ocean" }).Result;

Ocean.cshtml是我的razor文件

@{
}
这是一个测试,我是 @Model.Name

 

另外我做了一个简单封装

        public static string ExeRazor(string template,object obj)
        {
            var path = IOHelper.RootDevPath; 
            var engine = new RazorLightEngineBuilder()
             .UseFilesystemProject(path)
             .UseMemoryCachingProvider()
             .Build(); 
            string result = engine.CompileRenderAsync(template, obj).Result;
            return result;
        }

 

好了,这样就可以使用razor做一些string的输出了,比拼接字符串是方便了不少。

最关键的是,我可以直接打包进dll中,既干净又清爽,实在是居家旅行之良品。

 

项目官网上有更多的使用方法。

项目地址:https://github.com/toddams/RazorLight

written by ocean

5月 28

分布式锁就不多介绍了,刚好有一个这样的需求,就上github上找一找,发现一个分布式锁的项目,使用的是redis

项目地址:https://github.com/samcook/RedLock.net

首先使用nuget安装dll

 RedLock.net

我这边做了一个模拟测试,因为实在太简单了,直接看注释就可以了

        static int MAX_THREAD = 5;//模拟5个线程

        static void Main(string[] args)
        {

            var start = DateTime.Now;

            var endPoints = new List<RedLockEndPoint>
            {
                new DnsEndPoint("172.28.3.125", 6379),
            };
            var redlockFactory = RedLockFactory.Create(endPoints);
             
            List<Task> list = new List<Task>();
            for (int i = 0; i < MAX_THREAD; i++)
            {
                Task t = new Task(() =>
                {
                    for (int index = 0; index < 100; index++)
                    { 
                        var id = index % 10; 
                        var expiry = TimeSpan.FromSeconds(30);
                        var key = "key" + id;
                        var wait = TimeSpan.FromSeconds(30);
                        var retry = TimeSpan.FromSeconds(1);
                        using (var redLock = redlockFactory.CreateLock(key, expiry, wait, retry))
                        {
                            if (redLock.IsAcquired)
                            {
                                var info = LockTestDAL.GetLockTestInfo(id);
                                info.Result1++;
                                LockTestDAL.Update(info);
                                info.Result2++;
                                LockTestDAL.Update(info);
                                info.Result3++;
                                LockTestDAL.Update(info);
                            }
                        } 
                    }
                });
                list.Add(t);
            }
            for (int i = 0; i < list.Count; i++)
            {
                list[i].Start();
            }

            Task.WaitAll(list.ToArray());

            Console.WriteLine("done");
            var d = (DateTime.Now - start).TotalSeconds;
            Console.WriteLine($"耗时{d}秒");
            Console.ReadLine(); 
        }

其中有几个参数

expiry是指的超时时间,如果内部执行超过expity则会释放锁

key就是锁定时候的数据特征,一般是主键

wait是指获取锁的时候等待的时间

retry是指每隔多少时间请求一次。

 

一定要注意,锁不是一定能拿到的,所以需要加判断

if (redLock.IsAcquired) //判断是否拿到锁

 

在我的测试用例中,表中有多个数据需要更新,如果不加锁的话会有脏读脏写,数据明显错误,执行大概34秒。

加锁之后数据完全正确,执行时间会稍长,大概38秒。

written by ocean

4月 15

做多服务器集群的时候,需要处理session。

一般两种处理方法,

第一,无session,就是不用session,不用session的话自然没有问题

第二,集中化session,所有session集中放在redis种。这样也没有问题

 

记录一下集中化session

首先引用包

Microsoft.AspNetCore.Session
Microsoft.Extensions.Caching.Redis.Core

 在startup中添加中间件

            services.AddDistributedRedisCache(option =>
            {
                //redis 数据库连接字符串
                option.Configuration = "192.168.1.132:6379";
                //redis 实例名
                option.InstanceName = "master";
            });
            services.AddSession();
            
            //添加使用
            app.UseSession();

这样就可以了。真是太方便了

 

【Session 写入方法】

HttpContext.Session.SetString("key", "strValue");

【Session 读取方法】

HttpContext.Session.GetString("key")

 另外,页面上为了区分不同的server,这里取一下hostname

var hostname= Environment.GetEnvironmentVariable("COMPUTERNAME") ??Environment.GetEnvironmentVariable("HOSTNAME");

 

 

接下来,在docker中部署了5台app,1台nginx做负载均衡器,1台redis做session缓存。 记录下脚本

docker stop a1
docker stop a2
docker stop a3
docker stop a4
docker stop a5
docker rm a1
docker rm a2
docker rm a3
docker rm a4
docker rm a5
docker run --name a1 --restart=always -d -p 8001:80 -v /ocean/www:/wwwroot -w /wwwroot/ microsoft/aspnetcore dotnet /wwwroot/WebApplication1.dll
docker run --name a2 --restart=always -d -p 8002:80 -v /ocean/www:/wwwroot -w /wwwroot/ microsoft/aspnetcore dotnet /wwwroot/WebApplication1.dll
docker run --name a3 --restart=always -d -p 8003:80 -v /ocean/www:/wwwroot -w /wwwroot/ microsoft/aspnetcore dotnet /wwwroot/WebApplication1.dll
docker run --name a4 --restart=always -d -p 8004:80 -v /ocean/www:/wwwroot -w /wwwroot/ microsoft/aspnetcore dotnet /wwwroot/WebApplication1.dll
docker run --name a5 --restart=always -d -p 8005:80 -v /ocean/www:/wwwroot -w /wwwroot/ microsoft/aspnetcore dotnet /wwwroot/WebApplication1.dll

测试了一下,果然高兴的太早,试了一下,当server变化的时候,session不会同步。这是个问题,继续研究。

 

session其实是根据cookie的一个值来取的,而这个值得不同是因为.net core对其做了数据保护(data Protection) 

数据保护会调用机器自身的一个key值,该key值每台机器都不一样,因此最终造成cookie的值也不一样

 

为了解决这个问题。.Net Core团队提供了包将秘钥保存到redis中

添加包引用

Microsoft.AspNetCore.DataProtection.Redis

修改startup文件如下

            var redis = ConnectionMultiplexer.Connect("192.168.1.132:6379");
            services.AddDataProtection()
                .SetApplicationName("session_application_name")
                .PersistKeysToRedis(redis, "DataProtection-Keys");
            services.AddDistributedRedisCache(option =>
            {
                //redis 数据库连接字符串
                option.Configuration = "192.168.1.132:6379";
                //redis 实例名
                option.InstanceName = "master";
            });
            services.AddSession();

这次是真的OK了。哈哈哈

 

另外之前有写过一篇类似的 

http://blog.wx6.org/2017/929.htm

written by ocean

4月 03

这个功能很有意思。直接引用dll即可以浏览页面。

方法很简单,要被共享的功能单独写成一个.net core的webapplication

 

在另外一个.net core的webapplication中引用上面的dll,切记不要忘掉PrecompiledViews

之后在使用共享UI的dll应用中,startup中添加如下代码即可

 public void ConfigureServices(IServiceCollection services)
        {
            var assembly = typeof(OceanController).GetTypeInfo().Assembly;
            services.AddMvc()
            .AddApplicationPart(assembly);  
            services.Configure<RazorViewEngineOptions>(options =>
            {
                options.FileProviders.Add(
                    new EmbeddedFileProvider(typeof(OceanController).GetTypeInfo().Assembly));
            });
        }

OceanController即共享UI的Controller

written by ocean

1月 03
Vue.js(读音 /vjuː/, 类似于 view)是一个构建数据驱动的 web 界面的库。Vue.js 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。

Vue.js 自身不是一个全能框架——它只聚焦于视图层。因此它非常容易学习,非常容易与其它库或已有项目整合。另一方面,在与相关工具和支持库一起使用时,Vue.js 也能完美地驱动复杂的单页应用。

页面首先需要引用vue.js

    <script src="https://cdn.bootcss.com/vue/2.5.13/vue.min.js"></script>

一个最简单的例子

    <div id="app">
        <p>{{ message }}</p>
        <input v-model="message">
    </div> 
    <script>
        new Vue({
            el: '#app',
            data: {
                message: 'Hello Vue.js!'
            }
        }) 
    </script>

written by ocean

11月 10
<!doctype html>
<html>

	<head>
		<meta charset="UTF-8">
		<title></title>
		<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
		<script type="text/javascript">
document.addEventListener('plusready', function(){
var xhr = new plus.net.XMLHttpRequest();
xhr.onreadystatechange = function () {
	switch ( xhr.readyState ) {
		case 0:
			//alert( "xhr请求已初始化" );
		break;
		case 1:
			/alert( "xhr请求已打开" );
		break;
		case 2:
			//alert( "xhr请求已发送" );
		break;
		case 3:
			//alert( "xhr请求已响应");
			break;
		case 4:
			if ( xhr.status == 200 ) {
				alert( "xhr请求成功:"+xhr.responseText );
			} else {
				alert( "xhr请求失败:"+xhr.readyState );
			}
			break;
		default :
			break;
	}
}
xhr.open( "GET", "http://hq.sinajs.cn/list=sz000559,sh603729,sh600460,sz300056,sz002558,sz000037,sz002236,sz000546,sh603959,sh600754,sz300630,sz300298" );
xhr.send();
}, false );
	</script>
</head>
	<body>
	</body>
</html>

written by ocean

11月 09

新购一台阿里云服务器。今天装了个mysql,记录一下期间遇到的坑。

 

安装就不描述了。本地用Native链接不上

http://blog.wx6.org/2015/434.htm  之前这篇已经介绍过了。

按照正常情况,现在应该轻轻松松连接上了。可是并没有,

出了一个10038的问题,

Telnet检查了链接不上。远程服务器上所有的防火墙都检查过了,没有问题。可是依然连接不上。

网上所有遇到的可能性都尝试了,依然连不上。

 

这时候想到是不是阿里云有什么特俗的地方。  搜了一下 阿里云 3306, 果然有人遇到过这个坑了。

阿里云的安全策略里面是阻断了3306端口,只要加入站规则进去即可。

记录下来,以后不要再犯。

 

随后使用Navicat for Mysql链接出现错误

1045 - Access denied for user 'root'@'X.X.X.X' (using password: YES)

这是需要更改密码

执行登录myql

mysql -h localhost -u root -p

 首先选中mysql表

use mysq;

修改密码

update mysql.user set authentication_string=password('root') where user='root' ;

退出

exit

重启mysql即可

service mysql restart

written by ocean

11月 06

安装完配好root登录之后,winscp一直连接不上

报错

Disconnected: Server protocol violation: unexpected SSH2_MSG_UNIMPLEMENTED packet

最后发现解决问题如下

For me the key was that the "Diffie-Hellman group exchange" key exchange algorithm was not implemented on the server (see Connection > SSH > KEX).

Moving this key exchange algorithm to the bottom of the list and making the algorithm "Diffie-Hellman group 14" first solved the problem for me.

written by ocean

8月 01

设置Symbol Search Path

SRV*C:\MyLocalSymbols*http://msdl.microsoft.com/download/symbols

Open Crash Dump,选择我们抓取到的dmp文件

加载SOS调试扩展,加载之后才可以做各种调试,根据不同的版本选择加载不同的文件

.load C:/WINDOWS/Microsoft.NET/Framework/v2.0.50727/sos.dll
.load C:/WINDOWS/Microsoft.NET/Framework/v4.0.30319/sos.dll

如果是64位

.load C:\Windows\Microsoft.NET\Framework64\v2.0.50727\SOS.dll
.load C:\Windows\Microsoft.NET\Framework64\v4.0.30319\SOS.dll

 

查看运行环境版本信息

lmvm mscorwks

另外,windbg默认的字体真的很难受

View–>Font 可以设置字体

 

颜色的话一般设置命令行颜色不同即可

View–>Options–>Colors

    "Prompt level command window text"

    "Prompt level command windows text background"

这样调完之后看着舒服多了

written by ocean

7月 27

我在这里主要用来监控每个页面的执行时间

    public class VisitLogFilter : IActionFilter
    {
        private const string DURATION = "DURATION";

        public void OnActionExecuted(ActionExecutedContext context)
        {

            var stopwach = context.RouteData.Values[DURATION] as Stopwatch;
            stopwach.Stop();
            var time = stopwach.Elapsed;

            var url = context.HttpContext.Request.Host + context.HttpContext.Request.Path;
            var agent = ((FrameRequestHeaders)context.HttpContext.Request.Headers).HeaderUserAgent.ToString();
            var ip = IPHelper.GetUserIp(context.HttpContext);
            LogHelper.Info(time.TotalMilliseconds + "\t\t" + ip + "\t\t" + url + "\t\t\t\t" + agent);

            if (context.Result is ViewResult)
            {
                ((ViewResult)context.Result).ViewData[DURATION] = (time.TotalMilliseconds).ToString("#.##");
            }
        }

        public void OnActionExecuting(ActionExecutingContext context)
        {
            var stopwach = new Stopwatch();
            stopwach.Start();
            context.RouteData.Values.Add(DURATION, stopwach);           
        }

    }

 

注入全局

            services.AddMvc(
                config =>
                {
                    config.Filters.Add(new VisitLogFilter()); 
                });

前台展示的代码

                        @if (ViewData.ContainsKey("DURATION"))
                        {
                            <p>页面执行时间 : @(ViewData["DURATION"]) ms.</p>
                        }

written by ocean