Walt 发布的文章

<Border>
    <Border.Style>
        <Style TargetType="Border">
            <Setter Property="Background" Value="{x:Null}"/>
            <Setter Property="BorderThickness" Value="0"/>
            <Setter Property="Background" Value="White"/>
            <Setter Property="Effect">
                <Setter.Value>
                    <DropShadowEffect BlurRadius="20" Opacity="0.3" ShadowDepth="0" Color="#19334E"/>
                </Setter.Value>
            </Setter>
        </Style>
    </Border.Style>
    <Image Width="1080" Height="720"  Source="Imgs/01.png" />
</Border>

效果图如下:

带阴影效果的图片.png

背景

初学java,在学习spring时,一步一步安装视频教程进行配置,编码,在最后运行时,却无法访问controller中的请求,提示404。首页可正常访问,因controller中直接指向了一个静态jsp文件,经测试,直接修改url可以正常访问该页面,但经过controller访问却提示404。

HTTP状态 404 - 未找到
类型 状态报告

描述 源服务器未能找到目标资源的表示或者是不愿公开一个已经存在的资源表示。

Apache Tomcat/9.0.36

实现代码

1、pom.xml中已经正确配置了所需的dependency。
2、web.xml配置如下

    <servlet>
        <servlet-name>DispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>DispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

3、UserController代码:

@Controller
public class UserController {

    @RequestMapping("/quick")
    public String save(){
        System.out.println("Controller save running ...");
        return "success.jsp";
    }
}

4、spring-mvc.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
              http://www.springframework.org/schema/beans/spring-beans.xsd
              http://www.springframework.org/schema/mvc
              http://www.springframework.org/schema/mvc/spring-mvc.xsd
              http://www.springframework.org/schema/context
              http://www.springframework.org/schema/context/spring-context.xsd">

    <mvc:annotation-driven/>
    <context:component-scan base-package="info.hnbc.controller"/>
</beans>

分析

在反复测试后,发现根本就没有进入到Controller中,初步估计是根本就没有正确扫描到我们的controller,也就是在ioc容器进行解析我们的UserController时出错。各种折腾后发现在打包过程中,没有jar包,这也就导致ioc容器无法正常加载,从而导致UserController类没有加载打包给tomcat。具体原因还在研究,先介绍解决方法,后续再进行更新。

解决方案

打开Project Structure,选择Artifacts选项卡,在右侧,展开WEB-INF,新建一个名为lib的文件夹,
spring-mvc-study-1.png

之后将对应的库添加到lib下,重新运行即可解决。

使用IDEA过程中,碰到了这个菜鸟级别的问题,不过鉴于我第一次使用,还是记录下。

问题

IDEA新建了Web 应用,在配置了Tomcat后,直接运行没有问题,正常启动了,但是使用调试运行过程中,出现了无法打开Debugger port的问题。
QQ截图20200612151429.png

15:11    Error running 'Tomcat 9.0.36': Address localhost:8080 is already in use

15:11    Error running 'Tomcat 9.0.36': Unable to open debugger port (127.0.0.1:8123): java.net.SocketException "Interrupted function call: accept failed"

因为在左下角弹出了错误提示,只有上面的第二个错误信息,以为是那个8123的端口被占用,来来回回改了好几个都一样。后来点开了日志,才发现是8080被占用了。

解决方案

方案一

知道了问题就好办了,这个端口明显是tomcat配置的,打开tomcat安装目录,找到conf/server.xml。修改其中的<Connector port="9888" protocol="HTTP/1.1"将port端口修改一个未被占用的,保存后重新debug就可以了(还是不行,可以重启tomcat服务试试)。
修改端口号.png

方案二

查找占用了8080端口的进程,结束掉。该方法简单暴力,但最好确认占用端口的进程是否可以随意结束。

背景

最近想学习使用下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);
    }
}