背景

最近想学习使用下Java,下载了IDEA,配置了tomcat后,发现输出的日志信息,竟然是中文乱码,如下图所示,起初以为是IDEA的编码问题,Setting里设置了各种编码和语言,并没有起作用。
中文乱码.png

经过

网上搜了相关文章,但是试了几个并没有起作用。其中大部分是修改IDEA中Setting里面相关设置。

后来突然意识到,这应该是Tomcat输出的日志信息本来就是乱码造成的,于是有了如下解决办法。

解决

在Tomcat安装目录下,找到conf/logging.properties文件,打开后,找到 java.util.logging.ConsoleHandler.encoding的配置项,将其值修改为GBK即可。
修改前:
java.util.logging.ConsoleHandler.encoding = UTF-8

修改后:
java.util.logging.ConsoleHandler.encoding = GBK

中文乱码解决.png

解释

根据字面意思,很容易猜出来,改配置项是设置的tomcat控制台输出的log文本的编码,正是我们出现中文乱码的地方。

背景

项目中有日志自动记录功能,但随着使用增加,会产生大量日志记录。于是,打算利用Hangfire中的RecurringJob定时执行日志清理工作,设定为每天凌晨执行任务。

然后因日志量过于巨大,每天可产生大约近百万条日志记录。起初,使用EF进行删除,虽然知道效率贼低,但因EF没用批量操作功能,而且想着EF删除应该是多条删除同时提交,加上反正晚上没啥访问,慢慢删吧。然而,悲催的事情发生了,第二天发现服务器挂了,因数据库无法访问导致网站崩溃。几经波折,发现是日志删除的后台任务执行时,时间过长卡死了。

解决过程

既然EF没有批量删除功能,那就执行sql语句吧。删除条件是保留最近一个月的,之前的全部删除掉,需要使用时间进行检索删除,可能我数据库性能也不行,直接执行sql语句也奇慢无比。为了保证后台任务不再出现上次的直接死掉的严重问题,我决定通过每次删除1000条,多次执行sql语句方式进行删除。

遇到问题

我的处理方式是这样的:
通过IDbContextProvider获取dbContext。之后调用dbContext.Database.ExecuteSqlCommand(sql)执行删除语句。

var deleteunit = 1000;
var deadline = DateTime.Now.AddDays(-30);
bool isContinue = true;
while (isContinue)
{
    var first = _logRepository.GetAll().OrderBy(o => o.Id).FirstOrDefault();
    if (first == null || first.ExecutionTime > deadline)
    {
        isContinue = false;
        break;
    }

    var endId = first.Id + deleteunit;
    string sql = $"DELETE FROM logs where Id < {endId}" ;
    var rows = ExecuteSql(sql);
} 

ExecuteSql只有一行代码,执行sql语句

private int ExecuteSql(string sql)
{
    return _dbContextProvider.GetDbContext().Database.ExecuteSqlCommand(sql);
}

事情并没有按照我预想的方向发展。网站依然崩溃了,后台任务依然卡死了。sql语句并没有按照我预想的那样,一条一条的执行,而是当所有循环结束是,一起提交执行的,这样的话,执行一条语句和执行多条语句并没有什么区别了,甚至更慢了。

分析原因

因删除数据量过大,EF没有批量操作功能,最初的方案中,逐条删除时,在执行了所有的删除命令后,最后统一进行了提交操作,这时mysql才去进行实际的删除操作,而删除过程太过漫长,直接死掉。然而通过_dbContextProvider.GetDbContext()获取的DbContext与系统中仓储操作使用的是同一个DbContext,并没有新建DbContext。这就导致了网站其他功能直接挂掉了。

然而,第二次更改,直接执行sql语句,也是同理,abp默认支持UOW,循环执行sql语句,说白了还是一样,最后统一提交执行的。所以并没太大的改进,当然执行sql语句总比逐条删除好些。我曾经试过使用CurrentUnitOfWork.SaveChanges();试图关闭UOW,不过好像没起作用,我也没再细究。

解决方案

还是同样的方式执行sql语句,不过每次执行完后进行一次事务提交。也就是每次执行完后,销毁dbcontext,下次需要的是再次创建。不过_dbContextProvider.GetDbContext()获取的是系统内单例实现的,一旦销毁,其他仓储服务也无法使用了。所以我们可以新建DbContext,通过using方式使用。

本项目使用的是abp core2.2版本,这里的DbContext构造函数需要DbContextOptions类型参数。

public MyDbContext(DbContextOptions<MyDbContext> options)
    : base(options)
{
}

之前.net framework版本abp好像可以直接new DbContext()使用。当然,多一个参数而已,并不会阻碍我们的脚步。可以通过如下方式继续。

重写ExecuteSql方法。

private int ExecuteSql(string sql)
{
    using (var dbContext = new MyDbContextFactory().CreateDbContext(null))
    {
        return dbContext.Database.ExecuteSqlCommand(sql);
    }
    //return _dbContextProvider.GetDbContext().Database.ExecuteSqlCommand(sql);

}

Factory主要用来创建DbContext。

public class MyDbContextFactory : IDesignTimeDbContextFactory<MyDbContext>
{
    public MyDbContextFactory CreateDbContext(string[] args)
    {
        var builder = new DbContextOptionsBuilder<MyDbContext>();
        var configuration = AppConfigurations.Get(
            WebContentDirectoryFinder.CalculateContentRootFolder(), addUserSecrets: true
        );
        (new MySqlDbContextConfigurer()).ConfigureByConnectionString(
            builder,
            configuration.GetConnectionString(MyCoreConsts.ConnectionStringName)
        );

        return new MyDbContext(builder.Options);
    }
}

问题介绍

nginx执行nginx -s reload时,出现如下错误:

nginx: [error] CreateFile() "C:\web\nginx-1.17.9/logs/nginx.pid" failed (2: The system cannot find the file specified)

排查方法

执行命令nginx -t查看配置文件是否正确,大部分问题都是因为配置文件造成的,此命令会列出配置文件错误项。如下:

C:\web\nginx-1.17.9>nginx.exe -t
nginx: the configuration file C:\web\nginx-1.17.9/conf/nginx.conf syntax is ok
nginx: [emerg] bind() to 0.0.0.0:8090 failed (10013: An attempt was made to access a socket in a way forbidden by its access permissions)
nginx: configuration file C:\web\nginx-1.17.9/conf/nginx.conf test failed

可以看出,配置文件中监听端口号无法正常启动,应是被占用了,修改端口之后,重新执行nginx -s reload即可。

P.S. 本人nginx并不熟悉,此文也仅是记录下本次碰到此问题的解决过程。故此方案未必能解决所有同类问题。

背景

使用WebApi,通过接口获取请求数据,修改部分内容后转发给另一个网址。其中,Request.Body均为文本数据。

方案

首先获取Request.Body的值。

using (var stram = new MemoryStream())
using (var reader = new StreamReader(stram))
{
    Request.Body.Seek(0, SeekOrigin.Begin);
    Request.Body.CopyTo(stram);
    stram.Seek(0, SeekOrigin.Begin);
    var body = reader.ReadToEnd();
}  

之后进行修改赋值

byte[] array = Encoding.UTF8.GetBytes(body);
MemoryStream stream = new MemoryStream(array);
var streamContent = new StreamContent(stream);
requestMessage.Content = streamContent;

背景

net core2 web项目中,使用IdentityServer4时,在ConfigureServices中添加Identity配置,如下:

services.AddIdentityServer()
.AddTemporarySigningCredential()
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients());

编译时,发现如下错误提示:

错误 CS1061 '“IIdentityServerBuilder”未包含“AddTemporarySigningCredential”的定义,并且找不到可接受第一个“IIdentityServerBuilder”类型参数的可访问扩展方法“AddTemporarySigningCredential”(是否缺少 using 指令或程序集引用?)

这是因为,AddTemporarySigningCredential是在net core1.0中使用的,在net core2.0中,使用AddDeveloperSigningCredential 代替。.

services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients());