每天一剂开发良药

主要介绍一些小技巧之类,是为备忘也。

TypeScript 导入 *.vue 报错:Cannot find module

如图
在这里插入图片描述
创建一个 shims.d.ts 文件,放置到 src/globalDeclare 中。

declare module '*.vue' {
  import Vue from 'vue';
  export default Vue;
}

typescript-eslint 自作多情提示 xxx is assigned a value but never used

eslintrc.js 加上

 "no-unused-vars": "off",
 "@typescript-eslint/no-unused-vars": ["error"],
 "@typescript-eslint/ban-ts-comment": "off",
 "@typescript-eslint/explicit-function-return-type": "off",
 "@typescript-eslint/no-explicit-any": ["off"]

ViewUI Table 单元格 文本将不换行,超出部分显示为省略号

组件写法,比较麻烦

<Table :columns="columns1" :data="list">
  <template slot-scope="{ row, index }" slot="action">
    <a href="javascript:void(0);" @click="handleEdit(row, index)">编辑</a> 
     <Divider type="vertical" />
    <Poptip confirm transfer title="是否要删除此行?" @on-ok="handleDelete(index)">
      <a href="javascript:void(0);" style="color:red;">删除</a>
    </Poptip>
  </template>

 <template slot-scope="{ row }" slot="url">
    <Ellipsis :text="row.url" :length="50" tooltip :transfer="true"></Ellipsis>
  </template>
</Table>

其实可以在列配置中声明:

 { title: '链接地址', minWidth: 190, key: 'url', ellipsis:true, tooltip:true },

另外每一列设置 width/minWidth 就可以保证不受浏览器宽度挤压

Vue 工程里面怎么引入公共的 Less 样式库?

例如 Less 的函数。

安装下面插件

  • “less”: “^3.0.4”,
  • “less-loader”: “^5.0.0”,
  • “style-resources-loader”: “^1.4.1”

打开 vue.config.js 配置文件,

module.exports = {
    pluginOptions: {
        'style-resources-loader': {
            preProcessor: 'less',
            patterns: ['C:\\code\\ajaxjs\\aj-js\\aj-ui\\src\\style\\common-functions.less']
        }
    },
    lintOnSave: true,
    devServer: {
        overlay: {
            warnings: true,
            error: true
        }
    }
};

路径写死,改相对路径

var path = require("path");

module.exports = {
    pluginOptions: {
        'style-resources-loader': {
            preProcessor: 'less',
            patterns: [path.resolve(__dirname, './src/style/common-functions.less')]
        }
    },
    lintOnSave: true,
    devServer: {
        overlay: {
            warnings: true,
            error: true
        }
    }
};

MySQL varchar 文本包含数字的计数器

需求:如果重复值,则自增 1、2、3……
思路:先查询是否重复:

SELECT id FROM ${tableName} WHERE urlDir = ? AND datasourceId = ? LIMIT 1

如果是,获取最大值 MaxId,通过正则查询、排序,注意参数拼接了字符串(参数就是重复值)。

SELECT urlDir FROM ${tableName} WHERE urlDir REGEXP CONCAT(?, '_[0-9]+$') AND datasourceId = ? ORDER BY urlDir DESC LIMIT 1

若无则 1,有则 MaxId++

快速 SQL 转换 Java Bean/ POJO

找过好几个的,都不太符合需求,于是自己写个脚本,也很快。

<html>

<head>
    <meta charset="utf-8" />
    <title>SQL2pojo</title>
</head>

<body>
    <textarea id="sql" rows="20" cols="100"></textarea>
    <br />
    <br />
    <button onclick="sql2pojo()">SQL2pojo</button>
    <pre></pre>
</body>
<script>
    let tpl = '';
    function sql2pojo() {
        let sql = document.querySelector("#sql").value;
        let arr = sql.match(/CREATE TABLE `(?:\w+|_)` \(((\s|\S)+)(?=PRIMARY KEY)/);
        let result = arr[1].trim();

        arr = result.split(',');

        let output = [];
        arr.forEach(item => {
            if (item) {
                item = item.trim();
                console.log(item);
                let _arr = item.match(/^`(\w+)`\s+((?:\w|\(|\))+).*COMMENT '(.*?)'/);
                let _t = _arr[2], type = 'Object';

                if (_t.indexOf('VARCHAR') != -1 || _t.indexOf('TEXT') != -1)
                    type = 'String';

                if (_t.indexOf('TINYINT(1)') != -1)
                    type = 'Boolean';
                else if (_t.indexOf('TINYINT') != -1)
                    type = 'Integer';
                else if (_t.indexOf('INT') != -1)
                    type = 'Long';

                if (_t.indexOf('DATETIME') != -1 || _t.indexOf('DATE') != -1)
                    type = 'Date';

                tpl = `
/**
 *  ${_arr[3]}
 */
private ${type} ${_arr[1]};`;
                // console.log(tpl);
                output.push(tpl);
            }
        });

        document.querySelector('pre').innerHTML = output.join('<br />');
    }
</script>

</html>

Vue+TS 工程发布 npm 组件不能携带 *.vue 问题

当前 Vue 工程既有网站,也希望发布为 npm 组件。使用 tsc 编译结果到 dist 目录,注意下面问题:

  • 配置 main 文件,不然 import 包时候会 undefined。具体就是在 package.json 配置结果目录的 index.jsindex.d.ts
    在这里插入图片描述
  • 编译依靠不能使用 vue-cli-service build,那是编译网站的。我们目的是打包组件,使用 tsc 即可。其实就是生成 js 和 map,我的tsconfig.json 配置如下。
{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "strict": false,
    "jsx": "preserve",
    "importHelpers": true,
    "moduleResolution": "node",
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "sourceMap": true,
    "baseUrl": ".",
    "outDir": "./dist",
    "types": ["webpack-env"],
    "paths": {
      "@/*": ["src/*"]
    },
    "lib": ["esnext", "dom", "dom.iterable", "scripthost"]
  },
  "include": [
    "src/**/*.ts",
    "src/**/*.tsx",
    "src/**/*.vue",
    "tests/**/*.ts",
    "tests/**/*.tsx"
  ],

  "exclude": ["node_modules", "dist"]
}

其中的 include "src/**/*.vue", 其实没作用,因为 tsc 只管 js/ts/json 的编译,其他文件它不处理的。那么问题来了——打包就需要 *.vue 文件,——我搜索了很久终于找到一个比较简单的方法,就是直接复制过去。package.json 增加一个 scripts 命令:

"release": "tsc && xcopy src\\components dist\\components /s /y /d && npm publish --access public",

tsc 编译后通过 DOS 命令 xcopy 复制目录,/s 表示包含所有子目录和文件, /y 表示不确认并进行覆盖,/d 表示文件日期对比,日期较新的不覆盖。

另外,tsc 不会覆盖现有文件,所以最好先删除一下 dist 目录再 npm run release

Vue 中单页面组件中 render 函数不运行?

要用 render 函数,把 <template> 给去掉。https://segmentfault.com/q/1010000016677825

简单使得元素可拖动

/**
 * 使得面板浮动,可拖放
 */
export default function float() {
    setTimeout(() => {
        let el: HTMLElement = this.$el;
        let rect: DOMRect = el.getBoundingClientRect();

        let controls: HTMLElement = (<HTMLElement>el.querySelector('.controls'));
        let top: number = rect.top - el.offsetTop;
        let left: number = rect.left - el.offsetLeft - controls.offsetWidth - 10;
        let style: CSSStyleDeclaration = controls.style;

        style.top = top + 'px';
        style.left = left + 'px';

        makeDD(controls, <HTMLElement>controls.querySelector('.movable'));

        let btns: HTMLElement = (<HTMLElement>el.querySelector('.btns'));
        top = rect.top - el.offsetTop - btns.offsetHeight - 10;
        left = rect.left - el.offsetLeft;
        style = btns.style;

        style.top = top + 'px';
        style.left = left + 'px';

        makeDD(btns, <HTMLElement>btns.querySelector('.movable'));
    }, 10);
}

/**
 * 拖放
 * 
 * @param box       被拖放的区域
 * @param dragBar   拖放的按钮
 */
function makeDD(box: HTMLElement, dragBar: HTMLElement): void {
    // 鼠标按下的函数
    dragBar.onmousedown = function (oEvent: MouseEvent) {
        // 求出鼠标和box的位置差值
        let x: number = oEvent.clientX - box.offsetLeft, y: number = oEvent.clientY - box.offsetTop;

        // 鼠标移动的函数
        // 把事件加在document上,解决因为鼠标移动太快时,鼠标超过box后就没有了拖拽的效果的问题
        document.onmousemove = function (oEvent: MouseEvent) {
            // 只能拖动窗口标题才能移动
            if (oEvent.target != dragBar) {
                // return;
            }

            // 保证拖拽框一直保持在浏览器窗口内部,不能被拖出的浏览器窗口的范围
            let l: number = oEvent.clientX - x, t = oEvent.clientY - y;
            let doc: HTMLElement = document.documentElement;

            if (l < 0)
                l = 0;
            else if (l > doc.clientWidth - box.offsetWidth)
                l = doc.clientWidth - box.offsetWidth;


            if (t < 0)
                t = 0;
            else if (t > doc.clientHeight - box.offsetHeight)
                t = doc.clientHeight - box.offsetHeight;

            box.style.left = l + "px";
            box.style.top = t + "px";
        }

        // 鼠标抬起的函数
        document.onmouseup = function () {
            document.onmousemove = document.onmouseup = null;
        }

        // 火狐浏览器在拖拽空div时会出现 bug return false阻止默认事件,解决火狐的bug
        return false;
    }
}

Spring MVC 加入 JSP 支持

/WEB-INF/jsp/ .jsp

解决烦人的 sockjs-node/info 跨域问题

在这里插入图片描述

打开 build/webpack.conf.js

const config = {
  resolve: {
    alias: {

    }
  },
  devServer: {
    // host: 'localhost',
    disableHostCheck: true,
    public: '0.0.0.0'
  },
};

module.exports = config;

JSP 页面异常

JSP 需要加上下面代码,运行时才不会出现 java.lang.IllegalStateException: getOutputStream() has already been called …等异常。

/**
 *
 * @param ctx 页面上下文
 */
public static void fix(PageContext ctx) {
    HttpServletResponse response = (HttpServletResponse) ctx.getResponse();

    try {
        OutputStream out = response.getOutputStream();
        out.flush();
        out.close();
        response.flushBuffer();
        ctx.getOut().clear();
        ctx.pushBody();
        // out = pageContext.pushBody();
    } catch (IOException e) {
        LOGGER.warning(e);
    }
}

参考 JSP 内置对象 out 和 response.getWrite() 的区别

  • http://blog.sina.com.cn/s/blog_7217e4320101l8gq.html
  • http://www.2cto.com/kf/201109/103284.html

响应禁止缓存

/**
 * 新的输出,不要缓存
 *
 * @return 当前对象
 */
public MvcOutput noCache() {
	setHeader("Pragma", "No-cache");
	setHeader("Cache-Control", "no-cache");
	setDateHeader("Expires", 0);

	return this;
}

返回到前一页并刷新

window.location = document.referrer;

静态的错误提示页

<title>操作错误</title>
<meta charset="utf-8" />
<div style="height: 100%%; display: flex; justify-content: center; align-items: center;">
  <table>
    <tr>
      <td align="center"> <svg width="150px" viewBox="0 0 1000 1000">
          <g>
            <path fill="#ea8010" d="M500,10c-46.7,0-84.5,38-84.5,84.9v573.7c0,46.9,37.8,84.9,84.5,84.9c46.7,0,84.5-38,84.5-84.9V94.9C584.5,48,546.7,10,500,10z M500,821c-46.7,0-84.5,37.8-84.5,84.5c0,46.7,37.8,84.5,84.5,84.5c46.7,0,84.5-37.8,84.5-84.5C584.4,858.9,546.6,821,500,821z" />
          </g>
        </svg></td>
    </tr>
    <tr>
      <td align="center"><br />%s<br /><a href="javascript:history.go(-1);">返回</a></td>
    </tr>
  </table>
</div>

随时随地获取 Request/Response

为获取请求的上下文,能够在控制器中拿到最常用的对象,例如 HttpServletRequestHttpServletResponse 等的对象(甚至 Web App 的启动上下文( 在 web.xml 中配置的参数)),因此还需要设计一个 RequestHelper 类,通过 ThreadLocal 让控制器能轻易地访问到这些对象。

一个容器,向这个容器存储的对象,在当前线程范围内都可以取得出来,向 ThreadLocal 里面存东西就是向它里面的 Map 存东西的,然后 ThreadLocal 把这个 Map 挂到当前的线程底下,这样 Map 就只属于这个线程了。

private static ThreadLocal<HttpServletRequest> threadLocalRequest = new ThreadLocal<>();

private static ThreadLocal<HttpServletResponse> threadLocalResponse = new ThreadLocal<>();

/**
 * 保存一个 request 对象
 *
 * @param req 请求对象
 */
public static void setHttpServletRequest(HttpServletRequest req) {
	threadLocalRequest.set(req);
}

/**
 * 获取请求对象
 *
 * @return 请求对象
 */
public static HttpServletRequest getHttpServletRequest() {
	return threadLocalRequest.get();
}

/**
 * 保存一个 response 对象
 *
 * @param resp 响应对象
 */
public static void setHttpServletResponse(HttpServletResponse resp) {
	threadLocalResponse.set(resp);
}

/**
 * 获取上下文中 response 对象
 *
 * @return response 响应对象
 */
public static HttpServletResponse getHttpServletResponse() {
	HttpServletResponse resp = threadLocalResponse.get();

	if (resp == null)
		throw new RuntimeException("响应对象未初始化");

	return resp;
}

/**
 * 清空 request 和 response
 */
public static void clean() {
	threadLocalRequest.set(null);
	threadLocalResponse.set(null);
}

javax.servlet.forward.request_uri 之作用

绑定 JSP 时候,获取原请求的 uri,而非模版所在的 uri。

生成模拟数据的 SQL

随机 SET

UPDATE enterprise_tract_meeting SET TYPE = ELT(FLOOR(RAND() * 3 + 1), 1, 2, 3);

IDEA 设置 Javac 编译参数对于 Maven 无效

Eclipse 不会那样,Idea 2018 无效。你 re-build 然后 deploy 那样就可以。下面的 pom.xml 永久解决。

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.9.0</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
                <compilerArgs>
                    <arg>-parameters</arg><!-- IDEA 设置 Javac 编译参数对于 Maven 无效 -->
                </compilerArgs>
            </configuration>
        </plugin>
    </plugins>
</build>

Mysql 创建流水号

方法一,触发器:

CREATE TRIGGER saledetail_id BEFORE INSERT ON saledetail
FOR EACH ROW BEGIN
	declare n int;
	select IFNULL(max(right(ItemID,4)),0) into n from saledetail where mid(ItemID,1,8)=DATE_FORMAT(CURDATE(),'%Y%m%d');
	set NEW.ItemID=concat(DATE_FORMAT(CURDATE(),'%Y%m%d'),right(10001+n,4));
END;

注意在插入的时候主键要设置一个默认值才能插入进去,这里我设置的是空字符串 ""

方法二:

SELECT
 substr(CONCAT('0000', (IFNULL(MAX(substr(fund_code, -3)),0) + 1)), -3)
FROM enterprise_trace_fund
WHERE fund_code LIKE 'DF20211223%'

fund_code 字段值就是传过来的字符串,mysql 数据库会自动进行匹配,然后自行自增。fund_code 字段值如果加入日期值,三位的流水号一般是够用的。

但这做法并不能在并发下保证流水号的唯一性。可以用 MySQL 写锁(select...for update,也叫 X 锁,排它锁)。

方法三:

CREATE DEFINER=`root`@`localhost` PROCEDURE `GetSerialNo`(IN tsCode VARCHAR(50),OUT result VARCHAR(200) )
BEGIN   
   
   DECLARE  tsValue  VARCHAR(50);  
   DECLARE  tdToday  VARCHAR(20);       
   DECLARE  nowdate  VARCHAR(20);        
   DECLARE  tsQZ     VARCHAR(50);  
   DECLARE t_error INTEGER DEFAULT 0;    
   DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET t_error=1;    
   START TRANSACTION;    
     /* UPDATE sys_sno  SET sValue=sValue WHERE sCode=tsCode;  */
      SELECT sValue INTO tsValue  FROM sys_sno  WHERE sCode=tsCode for UPDATE;  
      SELECT sQz INTO tsQZ FROM sys_sno WHERE sCode=tsCode ;  
    -- 因子表中没有记录,插入初始值     
      IF tsValue IS NULL  THEN   
          SELECT CONCAT(DATE_FORMAT(NOW(),'%y%m'),'0001') INTO tsValue;  
          UPDATE sys_sno SET sValue=tsValue WHERE sCode=tsCode ;  
          SELECT CONCAT(tsQZ,tsValue) INTO result;  
      ELSE                  
          SELECT  SUBSTRING(tsValue,1,4) INTO tdToday;  
          SELECT  CONVERT(DATE_FORMAT(NOW(),'%y%m'),SIGNED) INTO nowdate;
          -- 判断年月是否需要更新
          IF tdToday = nowdate THEN  
             SET  tsValue=CONVERT(tsValue,SIGNED) + 1;  
          ELSE  
             SELECT CONCAT(DATE_FORMAT(NOW(),'%y%m') ,'0001') INTO tsValue ;  
          END IF;  
          UPDATE sys_sno SET sValue =tsValue WHERE sCode=tsCode;  
          SELECT CONCAT(tsQZ,tsValue) INTO result;  
     END IF;  
        
     IF t_error =1 THEN    
       ROLLBACK;    
       SET result = 'Error';  
     ELSE    
        COMMIT;    
     END IF;   
     SELECT  result ;     
END;

出处:https://www.jianshu.com/p/d7570564f104

Eclipse Maven Dependencies下引入本地工程的 jar 包却变成源码文件夹

How to tell Maven to include the jar dependency, not the subproject source directory in Eclipse?
开始找到这个方法 https://blog.csdn.net/Harbourside1/article/details/111871122 是不对的,后来找到这个 https://blog.csdn.net/oh_maxy/article/details/48347897,正确!就是没有选 Utility Module 这个导致的:

在这里插入图片描述

Spring MVC Java 代替 xml

web.xml

原来 web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee"
	xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">

	<!-- 启用 Spring -->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
	<!-- // -->

	<!-- 对 Request、Response 的扩展 -->
	<filter>
		<filter-name>InitMvcRequest</filter-name>
		<filter-class>com.ajaxjs.util.spring.InitMvcRequest</filter-class>
	</filter>
	<filter-mapping>
		<filter-name>InitMvcRequest</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>

	<!-- 全部允许跨域 -->
	<filter>
		<filter-name>Cors</filter-name>
		<filter-class>com.ajaxjs.util.spring.CorsFilter</filter-class>
	</filter>
	<filter-mapping>
		<filter-name>Cors</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
	<!-- // -->
</web-app>
<!-- // -->

采用 Java:

public abstract void initWeb(ServletContext servletContext);

@Override
public void onStartup(ServletContext servletCxt) {
	LOGGER.info("WEB 程序启动中……");
	servletCxt.setInitParameter("contextConfigLocation", "classpath:applicationContext.xml");
	servletCxt.addListener(new ContextLoaderListener()); // 监听器

	FilterRegistration.Dynamic filterReg = servletCxt.addFilter("InitMvcRequest", new InitMvcRequest());
	filterReg.addMappingForUrlPatterns(null, true, "/*");
	
	initWeb(servletCxt);
	……
}

数据库连接池失效

注意 Connection 一定要关闭!

<!-- 配置数据源 https://blog.csdn.net/syslbjjly/article/details/97108560 -->
<bean id="dataSource" class="org.apache.tomcat.jdbc.pool.DataSource" destroy-method="close">
	<property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
	<!--内网数据库 -->
	<property name="url" value="jdbc:mysql://10.201.xxx.xxx:3306/bdp?useUnicode=true&amp;characterEncoding=UTF-8&amp;useSSL=false"></property> <property 
		name="username" value="root"></property> <property name="password" value="xxx"></property> 
	<!-- 验证连接是否有效,(String) SQL 查询,用来验证从连接池取出的连接,在将连接返回给调用者之前。 如果指定,则查询必须是一个 SQL SELECT 并且必须返回至少一行记录查询不必返回记录,但这样将不能抛出 SQL 异常 -->
	<property name="validationQuery" value="SELECT 1" />
	<!-- (long) 避免过度验证,保证验证不超过这个频率——以毫秒为单位。如果一个连接应该被验证, 但上次验证未达到指定间隔,将不再次验证。 30000(30秒) -->
	<property name="validationInterval" value="18800" />
	<!-- 验证失败时,是否将连接从池中丢弃 -->
	<property name="testWhileIdle" value="true" />
	<property name="testOnBorrow" value="true" />
	<property name="testOnReturn" value="true" />
</bean>

MVC 框架中静态资源的划分

一般都是 MVC 框架接收所有的请求然后分别处理,去控制器的,还是静态资源的,

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.regex.Pattern;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

/**
 * 自定义请求对象,对请求对象和响应对象有很多需要扩展的地方
 * 
 * @author Frank Cheung<sp42@qq.com>
 *
 */
//@Component
public class InitMvcRequest implements Filter {
	/**
	 * 字符串判断是否静态文件
	 */
	private static final Pattern IS_STATIC = Pattern.compile("\\.jpg|\\.png|\\.gif|\\.js|\\.css|\\.less|\\.ico|\\.jpeg|\\.htm|\\.swf|\\.txt|\\.mp4|\\.flv");

	@Override
	public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
		HttpServletRequest _req = (HttpServletRequest) req;
		try {
			// 为防止中文乱码,统一设置 UTF-8,设置请求编码方式
			_req.setCharacterEncoding(StandardCharsets.UTF_8.toString());
		} catch (UnsupportedEncodingException e) {
		}

		if (!IS_STATIC.matcher(_req.getRequestURI()).find())
			chain.doFilter(req, resp);
		else
			chain.doFilter(req, resp);
	}

	@Override
	public void init(FilterConfig arg0) {
	}

	@Override
	public void destroy() {
	}
}

关于 Tomcat 的一些冷知识

Tomcat 自带许多有用的组件,直接可用。

  • Tomcat 也有自己的数据库连接池 jdbc-pool
  • 自带管理监控工具 Manager,若不满可以参考 PSI Probe
  • 想要一个模板系统?用 Tomcat 自带的 EL表达式解析器 吧,可惜的是我找不到相关的教程……只能用 Spring 的
  • 可插拔以及 SCI 的实现原理,以及 Wrapper
  • 对于特定资源的保护,Tomcat 提供了安全域的功能实现
  • Tomcat 也可以做 SSO……
  • 一堆过滤器

我们知道 Spring 本身自带一堆过滤器,Tomcat 也有呀。

观察代码执行时间

分析效率用,用 Spring 的 StopWatch

StopWatch sw = new StopWatch();

sw.start("起床");
Thread.sleep(1000);
sw.stop();

sw.start("洗漱");
Thread.sleep(2000);
sw.stop();

System.out.println(sw.prettyPrint());
System.out.println(sw.getTotalTimeMillis());
System.out.println(sw.getLastTaskName());
System.out.println(sw.getLastTaskInfo());
System.out.println(sw.getTaskCount());

如何对Spring MVC中的 Controller 进行单元测试

例子如下,参见

@ContextConfiguration(locations = { "classpath*:applicationContext.xml" })
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
public class TestAlipay {
	MockMvc mockMvc;

	@Autowired
	WebApplicationContext wac;

	@Before
	public void init() {
		mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
	}

//	@Test
	public void testCommonUpload() throws Exception {
		File file = new File("C:\\Users\\frank\\Desktop\\abldj75zav.png");
		byte[] bytes = FileHelper.openAsByte(file);
		String filenmae = "abldj75zav.png";
		MockMultipartFile mockMultipartFile = new MockMultipartFile("file", filenmae, MediaType.MULTIPART_FORM_DATA_VALUE, bytes);
		ResultActions andDo = mockMvc.perform(multipart("/upload").file(mockMultipartFile)).andExpect(status().isOk()).andExpect(content().string(filenmae)).andDo(print());

		System.out.println(andDo);
		assertNotNull(andDo);
	}

	@Test
	public void testNso() {
		assertTrue(true);
	}

}

MySQL 于 Tomcat 的冲突

出现异常:checkStateForResourceLoading Illegal access,Eclipse 提示异常,生产环境应该不会。写一个 Listener 解决。出处:12

import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Enumeration;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

import org.springframework.stereotype.Component;

import com.ajaxjs.Version;
import com.mysql.cj.jdbc.AbandonedConnectionCleanupThread;

/**
 * Eclipse 提示异常,生产环境应该不会
 * 
 * @author Frank Cheung<sp42@qq.com>
 *
 */
@WebListener
@Component
public class ContainerContextClosedHandler implements ServletContextListener {
	@SuppressWarnings("deprecation")
	@Override
	public void contextDestroyed(ServletContextEvent arg0) {
		if (Version.isDebug) {
			Enumeration<Driver> drivers = DriverManager.getDrivers();

			Driver driver = null;

			// clear drivers
			while (drivers.hasMoreElements()) {
				try {
					driver = drivers.nextElement();
					DriverManager.deregisterDriver(driver);
				} catch (SQLException ex) {
					// deregistration failed, might want to do something, log at the very least
				}
			}

			// MySQL driver leaves around a thread. This static method cleans it up.
			try {
				AbandonedConnectionCleanupThread.shutdown();
			} catch (Exception e) {
				// again failure, not much you can do
			}
		}
	}

	@Override
	public void contextInitialized(ServletContextEvent arg0) {
		System.out.println("------------------------------------------");
	}

}

错误信息 HTML

急用一个错误提示页面:

<title>操作错误</title>
<meta charset="utf-8" />
<div style="height: 100%%; display: flex; justify-content: center; align-items: center;">
  <table>
    <tr>
      <td align="center"> <svg width="150px" viewBox="0 0 1000 1000">
          <g>
            <path fill="#ea8010" d="M500,10c-46.7,0-84.5,38-84.5,84.9v573.7c0,46.9,37.8,84.9,84.5,84.9c46.7,0,84.5-38,84.5-84.9V94.9C584.5,48,546.7,10,500,10z M500,821c-46.7,0-84.5,37.8-84.5,84.5c0,46.7,37.8,84.5,84.5,84.5c46.7,0,84.5-37.8,84.5-84.5C584.4,858.9,546.6,821,500,821z" />
          </g>
        </svg></td>
    </tr>
    <tr>
      <td align="center"><br />错误XXXX<br /><a href="javascript:history.go(-1);">返回</a></td>
    </tr>
  </table>
</div>

效果如图
在这里插入图片描述

Eclipse 重启 Tomcat 提示 May be locked by another process

每次重启都会,很烦。原因是有打开文件的文件未关闭,Files.lines() 打开返回的 Stream<String> 也要关闭!

StringBuilder sb = new StringBuilder();

try (Stream<String> lines = Files.lines(path, encode);) {
	lines.forEach(str -> sb.append(str));

	return sb.toString();
} catch (IOException e) {
	LOGGER.warning(e);
}

典型的 Spring MVC 控制器单测

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import com.ajaxjs.data_service.api.ApiController;

@ContextConfiguration(locations = { "classpath*:applicationContext.xml" })
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
public class TestSsoAccessTokenInterceptor {
	MockMvc mockMvc;

	@Autowired
	WebApplicationContext wac;

	@Autowired
	ApiController apiController;

	@Before
	public void init() {
		apiController.initCache();
		mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
	}

	@Test
	public void test() throws Exception {
		MockHttpServletRequestBuilder req = get("/user_api").param("redirect_uri", "https://www.qq.com").param("client_id", "dss23s");

		mockMvc.perform(req).andExpect(status().is2xxSuccessful()).andDo(print()).andReturn().getResponse();
	}
}

规避使用 PowerDesigner

老版本 12 没有上传数据的功能,可以规避版权检测。通用破解方法:修改安装目录下的 pdflm12.dll 文件,使用二进制编辑器打开此文件,查找:83 C4 14 8B 85 E4 FE FF FF将此字符串改为 83 C4 14 33 C0 90 90 90 90

推荐 Hex Editor: wxMEdit。 Frhed 也小巧,但找不到搜索 Hex 的方法,郁闷。

使用正则提取网页中a标签的链接和标题

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Test1 {

	public static void main(String[] args) {
		String str1 = "<a href=\"https://www.zifangsky.cn/2015/10/hello-world/\" title=\"\" data-original-title=\"Hello World\">Hello World</a>";
		String str2 = "<a href=\"http://banzhuanboy.com/363.html\" class=\"post-feature\" \">123</a>";
		String str3 = " <a class=\"article-title\" href=\"/2015/12/17/Webstorm-Hotkeys-For-Mac/\">c</a>";
		String str4 = " <a rel=\"bookmark\" title=\"Permanent Link to  黑客组织‘SkidNP’涂改了Phantom Squad的网站首页\" href='12/hack-30127.htm'>黑</a>";
		String str5 = "<a href=\"http://www.imorlin.com/2015/12/24/1-3/\" title=\"\" data-original-title=\"2015圣诞节雪花代码[天猫+C店]\"> 2015圣诞节雪花代码[天猫+C店] <span class=\"label label-new entry-tag\">New</span> </a>";
		
		Pattern pattern = Pattern.compile("<a.*?href=[\"']?((https?://)?/?[^\"']+)[\"']?.*?>(.+)</a>");  
		Matcher matcher = pattern.matcher(str1);
		if(matcher.find()){
			String link = matcher.group(1).trim();
			String title = matcher.group(3).trim();
			if(!link.startsWith("http")){
				if(link.startsWith("/"))
					link = "https://www.zifangsky.cn" + link;
				else 
					link = "https://www.zifangsky.cn" + link;	
			}
			System.out.println("link: " + link);
			System.out.println("title: " + title);
		}
	
	}
}

解释:

1 选取了几个有代表性的 a 标签样式进行测试

2 关于正则匹配模式”<a.*?href=[\”‘]?((https?://)?/?[^\”‘]+)[\”‘]?.*?>(.+)</a>“的说明:

i)<a.*?href= <a 开头,中间紧跟着有0个或者多个字符,然后再跟着 href=

ii)[\”‘]?((https?://)?/? 一个或者0个的 或者 ,然后再跟着0个或者一个的http://或者https:// ,再跟着0个或者1个的 /

iii)[^\”‘]+ 表示1个以上的不包括或者 的任意字符

iv)[\”‘]?表示链接后面的或者 当然也可能没有

后面的可以根据前面的自己推理,就不解释了

3 matcher.group(1)表示取出链接,也就是第二个()的内容(PS:第一个()表示的是整个正则表达式,默认省略了,在正则中是这一段规则:((https?://)?/?[^\”‘]+)

4 matcher.group(3) 同理可知,对应的是这一段规则:(.+)

5 对于代码中的 https://www.zifangsky.cn ,这是由于部分链接使用了相对路径,比如说:href=’12/hack-30127.htm’ 。这时我们就需要加上它的域名,当然需要根据实际情况来加。这里我就随便乱加了

构建可重复读取 inputStream 的 request

我们知道,request 的 inputStream 只能被读取一次,多次读取将报错,那么如何才能重复读取呢?答案之一是:增加缓冲,记录已读取的内容。

import org.springframework.mock.web.DelegatingServletInputStream;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;

/**
 * request wrapper: 可重复读取request.getInputStream
 */
public class RepeatedlyReadRequestWrapper extends HttpServletRequestWrapper {
    private static final int BUFFER_START_POSITION = 0;

    private static final int CHAR_BUFFER_LENGTH = 1024;

    /**
     * input stream 的buffer
     */
    private final String body;

    /**
     * @param request {@link javax.servlet.http.HttpServletRequest} object.
     */
    public RepeatedlyReadRequestWrapper(HttpServletRequest request) {
        super(request);
        StringBuilder stringBuilder = new StringBuilder();
        InputStream inputStream = null;
        
        try {
            inputStream = request.getInputStream();
        } catch (IOException e) {
            log.error("Error reading the request body…", e);
        }
        if (inputStream != null) {
            try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) {
                char[] charBuffer = new char[CHAR_BUFFER_LENGTH];
                int bytesRead;
                while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
                    stringBuilder.append(charBuffer, BUFFER_START_POSITION, bytesRead);
                }
            } catch (IOException e) {
                log.error("Fail to read input stream",e);
            }
        } else {
            stringBuilder.append("");
        }
        body = stringBuilder.toString();
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
        return new DelegatingServletInputStream(byteArrayInputStream);
    }
}

接下来,需要一个对应的 Filter

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

public class RepeatlyReadFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        if (request instanceof HttpServletRequest) {
            request = new RepeatedlyReadRequestWrapper((HttpServletRequest) request);
        }
        chain.doFilter(request, response);
    }
    @Override
    public void destroy() { }
}

出处

使用 Javassist 在 tomcat 容器中实现动态 Mock

在某些复杂场景下,我们需要对运行在 tomcat 容器中部分功能进行 mock(替换其实现),但该部分功能散落在各处,我们希望不修改源代码以非侵入的方式来实现 Mock,在这种情况下,我们可以应用 Javassist 来实现。

使用Javassist在tomcat容器中动态替换源码来实现动态 Mock
我们可以定义一个 ContextListener 的实例,在 tomcat 启动时通过 Javassis t对源代码进行动态替换,来实现 mock 的功能。

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import java.util.HashMap;
import java.util.Map;
import org.lightfw.utilx.dynamic.JavassistUtil;

public class MockListener implements ServletContextListener {

    public void contextInitialized(ServletContextEvent contextEvent) {
        log.info("Context Listener start................");
        //将TestController类的test方法的实现替换为"return 1;"
        JavassitUtil.replaceMethodBody("com.xx.web.controller.TestController", "test", "return 1");
        log.info("Context Listener started");
    }

    public void contextDestroyed(ServletContextEvent sc) {
        log.info("Context Listener stopped");
    }
}

Javassist 相关代码,需要使用的工具类

    private static ClassPool classPool;

    static {
        classPool = ClassPool.getDefault();
        classPool.insertClassPath(new ClassClassPath(JavassitUtil.class)); //主要用于web环境
    }

 /**
     * 替换方法体
     *
     * @param className     类名,如:foo.Student
     * @param methodName    方法名
     * @param newMethodBody 新的方法体,如:"System.out.println(\"this method is changed dynamically!\");"
     */
    public static void replaceMethodBody(String className, String methodName, String newMethodBody) {
        try {
            CtClass clazz =classPool.get(className);
            CtMethod method = clazz.getDeclaredMethod(methodName);
            method.setBody(newMethodBody);
            clazz.toClass();
        } catch (NotFoundException | CannotCompileException e) {
            throw new RuntimeException(e);
        }
    }

出处

Spring 单元测试干掉 xml

Spring 全注解化了,但单测还是有个 xml 的小尾巴。

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

	<!-- 扫描的包 -->
	<context:component-scan
		base-package="com.ajaxjs.data_service, com.ajaxjs.entity, com.ajaxjs.rpc" />
</beans>

二货 Eclipse 整天校验这个 xml,还卡住。——其实可以创建一个 Java 类来代替 XML 文件:

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan({ "com.ajaxjs.data_service", "com.ajaxjs.entity", "com.ajaxjs.rpc" })
public class TestConfig {

}

单测:

import static org.junit.Assert.assertNotNull;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;

import com.ajaxjs.entity.datadict.DataDictService;

@ContextConfiguration(classes = TestConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
public class TestDataDict {
	@Autowired
	DataDictService dataDictService;

	@Test
	public void test() {
		assertNotNull(dataDictService);
	}
}

Java 枚举技巧

枚举除了名称还有常量值,可以用 int 保存。

public static enum Lock {
	ADD_LOCK(0), UNLOCK(1);
	
	private int value;
	
	Lock(int value) {
		this.value = value;
	}

	public int getValue() {
		return value;
	}
}

如果常量值是 0、1、2 顺序的,可以不设置 int,直接:

public static enum Lock {
	ADD_LOCK, UNLOCK;
}

lock.ordinal() 即可返回顺序的 int。

Ubuntu sudo 不用每次都输入密码的解决办法

虽然 sudo -i 可以避免输入密码,但 sftp 不行啊,怎么办!?

网上说的办法都不行,直接修改/etc/sudoers文件的最后一行:

%sudo   ALL=(ALL:ALL) ALL   修改为  %sudo   ALL=(ALL:ALL) NOPASSWD:ALL

我还改崩了,无法使用 sudo,解决办法参见《Ubuntu改坏sudoers后无法使用sudo的解决办法》

实际上 如果你对那文件有用户权限,是不用输入密码的,使用 chown -R 用户名 修改就行。

前端:左菜单,右主区域,左绝对值,右自动填满的布局

用 Flex 布局,如下:

.container {
    display: flex;
    height: 100%;

    .left {
        height: 100%;
        flex: 0 0 300px;
        border-right: 1px solid lightgray;
    }

    .right {
        height: 100%;
        flex: 1;
        /*div占据所有剩余宽度 */
    }
}

JS 快速压缩 CSS 代码

很简单的:

/* 压缩 css 并保存 */
function compress(code) {  
    code = code.replace(/\n/ig, '');            // 去掉换行  
    code = code.replace(/(\s){2,}/ig, '$1');    // 多空间(两个以上) 变 一个空格  
    code = code.replace(/\t/ig, '');            // 去掉tab  
    code = code.replace(/\n\}/ig, '\}');        // 换行+} 变 不换行  
    code = code.replace(/\n\{\s*/ig, '\{');     // {+换行 变 不换行  
    code = code.replace(/(\S)\s*\}/ig, '$1\}'); // 去掉 内容 与 } 之间的空格  
    code = code.replace(/(\S)\s*\{/ig, '$1\{'); // 去掉 内容 与 { 之间的空格  
    code = code.replace(/\{\s*(\S)/ig, '\{$1'); // 去掉 { 与 内容之间空格  
    return code;  
}  

MySQL 调整时区

SELECT TIMEDIFF(NOW(), UTC_TIMESTAMP); 

如果是中国标准时间, 会输出08:00

修改时区

set global time_zone = '+8:00';  ##修改mysql全局时区为北京时间,即我们所在的东8区
set time_zone = '+8:00';  ##修改当前会话时区
flush privileges;  #立即生效

IDEA 社区版新建 SpringBoot 项目

社区版下,Spring BootHelper 居然收费。怎么破?利用官方的 Spring Initializr 生成项目,导入即可。
在这里插入图片描述

Mybatis insert 的入参为map时,insert 语句中获取key和value的写法

https://blog.csdn.net/qq_40580023/article/details/84992429
MyBatis更新数据(输入参数类型为Map)
https://blog.csdn.net/Dr_Guo/article/details/79057153

Servlet 3 原生文件上传

package com.ajaxjs.image;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Collection;

import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;

import com.ajaxjs.util.io.StreamHelper;

/**
 * Servlet implementation class Api
 */
@WebServlet("/img_api/*")
@MultipartConfig
public class Api extends HttpServlet {
	private static final long serialVersionUID = 1L;

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
	 *      response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		new Target(request);

		String filename = "c:\\temp\\11.jpg";
		try (InputStream bin = new BufferedInputStream(new FileInputStream(filename));) {
			StreamHelper.write(bin, response.getOutputStream(), true);
		}
	}

	String savePath = "c:\\temp\\";

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
	 *      response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		Collection<Part> parts = request.getParts();// 获取上传的文件集合

		if (parts.size() == 1) {// 上传单个文件
			// Servlet3.0 将 multipart/form-data 的 POST 请求封装成 Part,通过 Part 对上传的文件进行操作。
			// Part part = parts[0];//从上传的文件集合中获取 Part 对象
			Part part = request.getPart("file");// 通过表单 file 控件(<input type="file" name="file">)的名字直接获取 Part 对象
			uplaod(part, savePath);
		} else {
			for (Part part : parts)// 一次性上传多个文件
				uplaod(part, savePath);
		}

//		response.getWriter().append("Served at: ").append(request.getContextPath());
		try (PrintWriter out = response.getWriter();) {
			out.println("上传成功");
		}
	}

	public static void uplaod(Part part, String savePath) {
		String header = part.getHeader("content-disposition");// 获取请求头,请求头的格式:form-data; name="file"; filename="snmp4j--api.zip"

		try {
			part.write(savePath + File.separator + getFileName(header));// 把文件写到指定路径
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Servlet3 没有提供直接获取文件名的方法,需要从请求头中解析出来 根据请求头解析出文件名
	 * 请求头的格式:火狐和google浏览器下:form-data; name="file"; filename="snmp4j--api.zip"
	 * IE浏览器下:form-data; name="file"; filename="E:\snmp4j--api.zip"
	 * 
	 * @param header 请求头
	 * @return 文件名
	 */
	public static String getFileName(String header) {
		/*
		 * String[] tempArr1 = header.split(";");代码执行完之后,在不同的浏览器下,tempArr1数组里面的内容稍有区别
		 * 火狐或者google浏览器下:tempArr1={form-data,name="file",filename="snmp4j--api.zip"}
		 * IE浏览器下:tempArr1={form-data,name="file",filename="E:\snmp4j--api.zip"}
		 */
		String[] tempArr1 = header.split(";");

		/*
		 * 火狐或者google浏览器下:tempArr2={filename,"snmp4j--api.zip"}
		 * IE浏览器下:tempArr2={filename,"E:\snmp4j--api.zip"}
		 */
		String[] tempArr2 = tempArr1[2].split("=");

		// 获取文件名,兼容各种浏览器的写法
		String fileName = tempArr2[1].substring(tempArr2[1].lastIndexOf("\\") + 1).replaceAll("\"", "");

		return fileName;
	}

}

这样理解 java 中的 volatile 更简单些

程序运行时,有2大块内存,主内存和本地内存,当某线程读或写一个变量时,先操作本地内存,再选择合适的时机同步到主内存中。

并发三个重要的概念:原子性,可见性,有序性。

关于原子性:

1,synchronized{}修饰的代码块,可保证原子性

2,对于volatile int i = 0;

i = 2;是原子操作

i++;不是原子操作,因为要先读取i的当前值,再进行自增,再进行赋值操作

i = i;不是原子操作,因为要先读取i的当前值,再进行赋值操作

int j = i;不是原子操作,因为要先读取i的当前值,再进行赋值操作

  • volatile 只具备可见性和有序性。
  • volatile 可见性,当线程给该变量赋值时,该新值会先修改到本地内存,再直接同步到主内存。
  • volatile 有序性,当纯种读取该变量值时,必须先从主内存读取最新的值,再同步到本地内存中,再从本地内存中读取最新值。

Linux 启动 JAR 包 Shell 脚本

实用呀,可以 kill 掉已有进程然后启动

port=8083
pid=$(netstat -nlp | grep :$port | awk '{print $7}' | awk -F"/" '{ print $1 }')
kill -9 ${pid}
echo "killed ${pid}"

nohup java -jar auth.jar  > /dev/null &
tail -f /data/logs/uam/auth/log_debug.log

最后一行 tail -f /data/logs/uam/auth/log_debug.log 观察日志的可以不执行或者修改 文档地址。

如何测试高并发的线程安全

结合 CountDownLatch类和Semaphore类测试(出处),例子如下。

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
 
/**
 * @author binghe
 * @version 1.0.0
 * @description 测试SimpleDateFormat的线程不安全问题
 */
public class SimpleDateFormatTest01 {
    //执行总次数
    private static final int EXECUTE_COUNT = 1000;
    //同时运行的线程数量
    private static final int THREAD_COUNT = 20;
    //SimpleDateFormat对象
    private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
 
    public static void main(String[] args) throws InterruptedException {
        final Semaphore semaphore = new Semaphore(THREAD_COUNT);
        final CountDownLatch countDownLatch = new CountDownLatch(EXECUTE_COUNT);
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < EXECUTE_COUNT; i++){
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    try {
                        simpleDateFormat.parse("2020-01-01");
                    } catch (ParseException e) {
                        System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
                        e.printStackTrace();
                        System.exit(1);
                    }catch (NumberFormatException e){
                        System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
                        e.printStackTrace();
                        System.exit(1);
                    }
                    semaphore.release();
                } catch (InterruptedException e) {
                    System.out.println("信号量发生错误");
                    e.printStackTrace();
                    System.exit(1);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        System.out.println("所有线程格式化日期成功");
    }
}

maven中 Failed to read schema document 错误

https://blog.csdn.net/qq_39741730/article/details/104663761

在这里插入图片描述

自动 kill 进程再启动,并输出日志文件

# 获取进程名
process_name=new-fleet-market-business-1.0-SNAPSHOT.jar

# 查找进程 ID
pid=$(jps -l | grep $process_name | awk '{print $1}')

# 打印进程 ID
echo "进程 ID 为:$pid"

# 判断进程 ID 是否为空
if [ -n "$pid" ]; then
    # 终止进程
    kill -9 $pid
    echo "停止进程 $pid"
else
    echo "没有找到进程 $process_name"
fi

echo "启动程序"
nohup java -Xms512m -Xmx512m -jar ./$process_name >message.log 2>&1 &

数据库保存经纬度采用什么数据类型好?

在这里插入图片描述

所以,只需要精确到小数点后7位,精度就是1CM,因此,数据库保存经纬度采用 decimal(10,7) 即可。

问题1:为什么不采用float?
答:float,double容易产生误差,对精确度要求比较高时,建议使用decimal来存,decimal在mysql内存是以字符串存储的,

问题2:为什么不用字符串?
答:字符串不方便数据库计算

JAR 包运行用外置配置 yml 文件

在这里插入图片描述
启动命令

java -jar -Dspring.config.location=config/application.yml datachangenew.jar

获取当前文件上一条与下一条记录

上一条的sql语句,从news表里按从大到小的顺序选择一条比当前ID小的新闻;

下一条的sql语句,从news表里按从小到大的顺序选择一条比当前ID大的新闻;

如果ID是主键或者有索引,可以直接查找:

方法一:

# 上一条
select * from table_a where id = (select id from table_a where id < {$id} order by id desc limit 1); 
# 下一条
select * from table_a where id = (select id from table_a where id > {$id} order by id asc limit 1);

方法二:

# 上一条
select * from table_a where id = (select max(id) from table_a where id < {$id});
# 下一条
select * from table_a where id = (select min(id) from table_a where id > {$id});

synchronized的参数用什么?

很多人用synchronized(参数)时,随便找个string,hashmap就作为参数了。但是这个参数有什么用呢?synchronized不就是保证每个进来的线程结束后再放下一个线程进来,对吧?这个例子保证你能明白

class 人
人 你 = new();
人 我 = new();
 
如果是
synchronized() {
    吃饭();
}

那么说明你只能一顿一顿吃,不能同时(多线程)吃好几顿饭;
 
如果是
synchronized(人.class) {
  吃饭();
}

那么只要我在吃饭,你就不能吃饭,得等我吃完!
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sp42a

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值