0%

问题:

有类似如下返回值接口:

1
2
3
4
5
6
7
8
@RestController
public class TestController {

@PostMapping(value = "test")
public String test() throws JsonProcessingException {
return "hello";
}
}

请求返回 json 格式数据,返回值应为:"hello",但实际收到的数据为:hello

原因:

  1. SpringBoot的Message响应处理流程

    a. test() 接口执行并返回String

    b.

    1
    2
    3
    HandlerMethodReturnValueHandlerComposite.handleReturnValue(Object returnValue,  MethodParameter returnType, …)  {
    selectHandler()
    }

    c.

    1
    2
    3
    RequestResponseBodyMethodProcessor.handleReturnValue() {
    writeWithMessageConverters()
    }

    writeWithMessageConverters()RequestResponseBodyMethodProcessor的父类AbstractMessageConverterMethodProcessor中,其中第一个部分

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType, ...) throws IOException ... {

    Object body;
    Class<?> valueType;
    Type targetType;

    // 这里直接对字符序列类型数据写入body
    if (value instanceof CharSequence) {
    body = value.toString();
    valueType = String.class;
    targetType = String.class;
    }
    else {
    body = value;
    valueType = getReturnValueType(body, returnType);
    targetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass());
    }
    ... ...

    对于字符序列的返回值做了特殊处理,直接将值toString()写入body中

    第二个部分

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    for (HttpMessageConverter<?> converter : this.messageConverters) {
    ... ...
    if (... converter.canWrite(...)) {
    body = getAdvice().beforeBodyWrite(...);
    if (body != null) {
    ... ...
    if (genericConverter != null) {
    genericConverter.write(body, targetType, selectedMediaType, outputMessage);
    }
    else {
    ((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
    }
    }
    else {
    logger ... ...
    }
    return; // 直接返回终止遍历
    }
    }

    进行HttpMessageConverters的遍历调用,messageConverters如下:

    HttpMessageConverters

    对当前返回值进行类型对应Converter适配并在能够处理时,进行处理并返回值。

    两个StringHttpMessageConverters

    StringHttpMessageConverters

  2. 问题分析

    writeWithMessageConverters() 这个方法对字符串的默认处理,以及 HttpMessageConverters 的默认顺序,以及处理返回机制,导致了String类型返回值不能呗 Jackson 的 MessageConverter 处理

解决方案:

  1. 对返回String类型值的接口,在返回的时候进行特殊处理,例如使用fastJson:

    1
    return JSON.toJSONString(returnValue)
  2. 【推荐】对返回值进行封装成对象就不会出现问题了,如下

    1
    2
    3
    4
    5
    {
    "code": 200,
    "msg": "ok",
    "data": "hello"
    }

问题

win环境下,jupyter使用如 !dir 执行DOS命令乱码问题

解决方法

修改编码:

1
2
3
4
5
chcp 65001 换成UTF-8代码页

chcp 936 可以换回默认的GBK

chcp 437 是美国英语

表分区过程是为单个数据库建立多个 文件组(夹) 及里面的 文件 ,然后把表内数据按照 分区函数方案 ,放到不同的数据库文件里。通过这样的处理,使得表的数据、索引等分成多个部分,缓解单表过大导致检索太慢等问题。

阅读全文 »

简述

Jmeter4.0与前面的版本有所不一样,所有的插件由新的插件管理器下载,收集服务器性能信息的包也有点难找,远程脚本调试的配置也有点小坑

阅读全文 »

Vue + Typescript 模块化项目的构建

得益于 vue-cli 3.0 多了 create,项目构建变得非常的简单了

构建主要步骤

  1. 默认已经有 node.js 环境,最好8+版本

  2. 安装 vue-cli 3.0

    1.0 卸载旧版2.x版本,如果有的话

    1.1 npm i -g @vue/cli,(chromedriver可能安装失败,需要从taobao镜像安装缓存,然后再安装cli)

    1.2 vue -V 确认版本为 3.x(目前为3.0.0-beta.16)

  3. 使用 vue-cli 构建框架

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
> vue create vue

Vue CLI v3.0.0-beta.16
? Please pick a preset: Manually select features

? Check the features needed for your project:
(*) Babel
(*) TypeScript
( ) Progressive Web App (PWA) Support # 网页App
(*) Router
(*) Vuex
(*) CSS Pre-processors
(*) Linter / Formatter
(*) Unit Testing
( ) E2E Testing # 端到端调试

? Use class-style component syntax? Yes

? Use Babel alongside TypeScript for auto-detected polyfills? Yes

? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): LESS

? Pick a linter / formatter config: TSLint

? Pick additional lint features: Lint on save

? Pick a unit testing solution: Mocha

? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? In dedicated config files # 分离配置文件

以上为我的具体框架选择

  1. npm/yarn serve运行即可

  2. 需要修改 webpack 等设置,新建 vue.config.js 可以自定义配置,具体配置见官方 vue-cli 仓库-> docs -> config

    注:支持Jsx,内置webpack、babel、ts等,基本无需多余配置,开箱即用,相对2.x自己构建已经非常精简了

    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
    {
    "name": "vue",
    "version": "0.1.0",
    "private": true,
    "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint",
    "test:unit": "vue-cli-service test:unit"
    },
    "dependencies": {
    "bootstrap-vue": "^2.0.0-rc.11",
    "vue": "^2.5.16",
    "vue-class-component": "^6.0.0",
    "vue-property-decorator": "^6.0.0",
    "vue-router": "^3.0.1",
    "vuex": "^3.0.1",
    "vuex-class": "^0.3.1"
    },
    "devDependencies": {
    "@types/chai": "^4.1.0",
    "@types/mocha": "^2.2.46",
    "@vue/cli-plugin-babel": "^3.0.0-beta.15",
    "@vue/cli-plugin-typescript": "^3.0.0-beta.15",
    "@vue/cli-plugin-unit-mocha": "^3.0.0-beta.15",
    "@vue/cli-service": "^3.0.0-beta.15",
    "@vue/test-utils": "^1.0.0-beta.16",
    "chai": "^4.1.2",
    "less": "^3.0.4",
    "less-loader": "^4.1.0",
    "vue-template-compiler": "^2.5.16"
    },
    "browserslist": [
    "> 1%",
    "last 2 versions",
    "not ie <= 8"
    ]
    }
  3. VSCode调试断点,安装Debugger插件,这里用的是 Debugger for FireFox,配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch localhost",
"type": "firefox",
"request": "launch",
"reAttach": true,
"url": "http://localhost:8080/#/",
"webRoot": "${workspaceFolder}",
"firefoxExecutable": "C:/Program Files/Mozilla Firefox/firefox.exe",
"firefoxArgs": ["-start-debugger-server", "-no-remote"]
}
]
}

服务还是要先命令行运行yarn serve

参考

vue-cli官网:有介绍及教程等(瞎折腾好久才发现)

官方仓库vue-cli

VSCode调试运行在Chrome, Firefox与Edge内的JS程序

一开始不知道有vue-cli 3.0,用的2.9.6,各种包一个个装,ts也要自己配置,搞了一两天,终于能跑了,但是VSCode就是提示找不到模块,想想应该是包装的太乱太杂了。3.0中间不需要按2.x一样装配ts、jsx等等,省的自己装的乱七八糟的,终于能安心看vue和ts了。

简介

MSMQ(Microsoft Message Queue),消息队列是用于消息传输的中间存储容器,主要可以用于 异步处理、应用解耦、流量削峰、日志处理及消息通讯等等

使用记录

  1. PeekRecieve

    两者都是获取队列消息的方法,区别的是,Peek 获取后不删除队列内消息,Recieve 则是获取后删除对应消息

  2. 异步接收消息

    异步接收消息需要先提供异步事件处理方法,然后初始化一个异步接收操作,直到接收到消息,或超时。

    1
    2
    3
    4
    5
    6
    7
    8
    // 异步接收消息
    queue.PeekCompleted += new PeekCompletedEventHandler(method);
    queue.BeginPeek();

    // 异步接收并删除队列内对应消息
    queue.ReceiveCompleted += new ReceiveCompletedEventHandler(method);
    queue.BeginReceive();
    queue.BeginReceive(TimeSpan.FromMilliseconds(100));
  3. 简单封装类,如需要更多方法,根据情况自行封装或者选择不封装

    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
    using System;
    using System.Messaging;

    namespace Utils
    {
    public class MessageQueueHelper : IDisposable
    {
    protected MessageQueueTransactionType transactionType = MessageQueueTransactionType.Automatic;
    protected MessageQueue queue; // 消息队列
    protected TimeSpan timeout; // 接收监听超时时间

    public MessageQueueHelper(string queuePath, int timeoutSeconds)
    {
    Createqueue(queuePath);
    queue = new MessageQueue(queuePath);
    timeout = TimeSpan.FromSeconds(Convert.ToDouble(timeoutSeconds));

    //设置当应用程序向消息对列发送消息时默认情况下使用的消息属性值
    //queue.DefaultPropertiesToSend.AttachSenderId = false;
    //queue.DefaultPropertiesToSend.UseAuthentication = false;
    //queue.DefaultPropertiesToSend.UseEncryption = false;
    //queue.DefaultPropertiesToSend.AcknowledgeType = AcknowledgeTypes.None;
    //queue.DefaultPropertiesToSend.UseJournalQueue = false;
    }

    /// <summary>
    /// 消息接收
    /// </summary>
    public virtual object Receive()
    {
    try
    {
    using (Message message = queue.Receive(timeout, transactionType))
    return message;
    }
    catch (MessageQueueException e)
    {
    LogHelper.ErrorLog(typeof(MessageQueueHelper), e, "队列接收消息异常!");
    if (e.MessageQueueErrorCode == MessageQueueErrorCode.IOTimeout)
    throw new TimeoutException();

    throw e;
    }
    }

    /// <summary>
    /// 消息发送
    /// </summary>
    public virtual void Send(object msg)
    {
    queue.Send(msg, transactionType);
    }

    /// <summary>
    /// 创建使用指定路径的新消息队列
    /// </summary>
    /// <param name="queuePath">队列存储路径</param>
    public static void Createqueue(string queuePath)
    {
    try
    {
    if (!MessageQueue.Exists(queuePath))
    {
    MessageQueue.Create(queuePath, true);
    }
    }
    catch (MessageQueueException e)
    {
    throw e;
    }
    }

    #region 实现 IDisposable 接口成员
    public void Dispose()
    {
    queue.Dispose();
    }
    #endregion
    }
    }

    进一步实现特定队列

    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
    using System;
    using System.Collections.Generic;
    using System.Messaging;
    using Newtonsoft.Json;
    using System.Threading;
    using Utils; // Helper的命名空间

    namespace API.App_Start
    {
    public class ReceiveProcessQueue : MessageQueueHelper
    {
    // 获取配置文件中有关消息队列路径的参数
    private static readonly string queuePath = @".\private$\rpmsmq";
    private static int queueTimeout = 30;

    public ReceiveProcessQueue()
    : base(queuePath, queueTimeout)
    {
    // 设置消息的序列化方式
    queue.Formatter = new XmlMessageFormatter(new Type[] { typeof(string) });
    }

    /// <summary>
    /// 接收消息
    /// </summary>
    public void ReceiveData()
    {
    // 指定消息队列事务的类型,Automatic枚举值允许发送发部事务和从外部事务接收
    transactionType = MessageQueueTransactionType.Automatic;
    Message msg = (Message)base.Receive();
    Process(msg);
    }

    /// <summary>
    /// 接收消息指定超时时间
    /// </summary>
    /// <param name="timeout">超时时间</param>
    public void ReceiveData(int timeout)
    {
    base.timeout = TimeSpan.FromSeconds(Convert.ToDouble(timeout));
    ReceiveData();
    }

    /// <summary>
    /// 异步消息接收
    /// </summary>
    /// <param name="method">异步处理方法</param>
    public void ReceiveByAsync()
    {
    queue.ReceiveCompleted += new ReceiveCompletedEventHandler(ReceiveCompleted);
    // 指定初始化异步并行处理数量
    #if !DEBUG
    int MAX_THREAD = 16;
    #else
    int MAX_THREAD = 3;
    #endif
    for (int i = 0; i < MAX_THREAD; i++)
    {
    queue.BeginReceive();
    }
    }

    /// <summary>
    /// 异步处理方法
    /// </summary>
    /// <param name="source">队列</param>
    /// <param name="asyncResult">异步结果</param>
    public void ReceiveCompleted(Object source, ReceiveCompletedEventArgs asyncResult)
    {
    MessageQueue queue = (MessageQueue)source;
    queue.Formatter = new XmlMessageFormatter(new Type[] { typeof(string) });
    // 完成指定的异步接收操作
    Message msg = queue.EndReceive(asyncResult.AsyncResult);
    Process(msg);
    // 消息处理完成后,初始化新的异步接收操作
    queue.BeginReceive();
    }

    /// <summary>
    /// 发送消息
    /// </summary>
    /// <param name="msg">消息</param>
    public void SendData(string msg)
    {
    // 指定消息队列事务的类型,Single枚举值用于单个内部事务的事务类型
    base.transactionType = MessageQueueTransactionType.Single;
    base.Send(msg);
    }

    public void Process(Message msg)
    {
    string msgStr = null;
    try
    {
    if (ReferenceEquals(msg.Body, null))
    {
    Console.WriteLine("null");
    return;
    }
    msgStr = (string)msg.Body;
    Console.WriteLine(msgStr);
    Thread.Sleep(100); // 睡眠随机时间可以看到多线程异步效果
    }
    catch (Exception ex)
    {
    throw ex;
    }
    }
    }
    }

结束

经过单元测试,测试正常,后面就要根据业务来做调整了,考虑如何提升并行处理的效率,且不会爆栈。

解决方案有:

  1. 异步 + 多线程(现在的)

  2. 轮询动态增减消费者(使用定时任务,定时检查队列消息数量,动态增减消费者)

需要主要对系统开销,以及应对峰值等场景的效果,进行权衡

之前写过 RabbitMQ 的demo,现在发现不实际使用真的是很多问题都不知道。