‘编程学习’ 分类下的所有文章
20193 月19

NutzWk 5.2.0(一周年版) 微服务开发框架,运维中心重磅发布

NutzWk 5.2.0 版本已发布,演示地址: https://nutzwk.wizzer.cn

源码Github:https://github.com/Wizzercn/NutzWk
码云Gitee:https://gitee.com/wizzer/NutzWk

后端技术架构:nutzboot + dubbo + sentinel + redis + zookeeper
前端技术架构:vue.js + element.js

简述:
自 2018.03.20 发布 v5.0.1 第一个微服务分布式版本、2018.11.13 发布 v5.1.0 第一个Vue版本,一年来 NutzWk 根据项目实践及业务需要,逐步完善功能、修复bug、添加新特性,朝着“快速开发、功能丰富、扩展性强、性能优越”,在力所能及的情况下,最大限度的提高Web开发人员的生产力的目标继续前进。

随着项目越做越多,运维成了繁重的工作,本次 v5.2.0 周年版本主要带来了可在线上传jar包、编辑配置文件、关闭实例进程、启动新实例进程、动态修改日志等级、查看服务器资源占用情况等功能,支持分布式部署。详见:发行注记

01

02

03

04

20191 月24

NutzWk 5.1.4 发布,Java 微服务分布式开发框架

NutzWk 5.1.4 Vue版本已发布,演示地址: https://nutzwk.wizzer.cn

源码Github:https://github.com/Wizzercn/NutzWk
码云Gitee:https://gitee.com/wizzer/NutzWk

 

NutzWk 5.1.4 更新内容:

  • * 集成 Sentinel 流控框架,支持流量控制、熔断降级、系统负载保护等(默认不启用);
  • * 日志系统从 log4j 改为 logback,编写starter-logback-exts 方便微服务模块调用(默认启用);
  • * 新增系统监控功能,后台可查看运行实例的进程ID、运行时间、JVM内存情况等,并可在不重启的情况下动态修改各微服务模块的日志等级;
  • * 系统微服务模块原 daocache 数据库缓存改为 wkcache 方法缓存,支持分布式部署;
  • * 为每个微服务模块瘦身,去掉多余的依赖包;
  • * 一些第三方依赖包的版本升级;
  • * 一些功能的修复及完善;

 

NutzWk 是 Java 微服务分布式开发框架,5.x 是采用nutzboot、nutz、dubbo、sentinel、redis、zookeeper、shiro、quartz、beetl、logback等开源技术的微服务分布式版本,自带系统管理、简易CMS、微信模块、定时任务、服务API等功能,目前已全面应用于各类商业项目中。

201811 月12

NutzWk 5.1.0 微服务开发框架,Vue版本已发布

NutzWk 5.1.0 Vue版本已发布,演示地址: https://nutzwk.wizzer.cn

源码Github:https://github.com/Wizzercn/NutzWk
码云Gitee:https://gitee.com/wizzer/NutzWk

本次更新内容:
* wk-nb-web-vue 新增全新的Vue后台管理界面,基于Vue.js + Element.js 等,增强交互体验;
* 基础服务类 BaseService 新增一些常用的查询方法;
* 微信模块增加图片自动回复、群发图片、微信菜单可配小程序等功能;
* CMS模块增加前台模板标签示例代码;
* 增强Oracle及MySQL兼容性;
* 支持Openjdk 11;

后端技术架构:java nutzboot + dubbo + zookeeper
前端技术架构:vue.js + element.js + bootstrap

20187 月19

MqttWk: 基于nutzboot + t-io + redis + kafka 实现的MQTT服务端开源项目

项目首页:https://github.com/Wizzercn/MqttWk

MqttWk

基于 nutzboot + t-io + redis + kafka 实现的MQTT服务broker

本项目代码主要来源于 netty/t-io/iot-mqtt-server 等众多项目,开源免费,欢迎交流学习

参考项目

使用说明

软件架构说明

  1. 使用t-io实现通信及协议解析
  2. 使用nutzboot提供依赖注入及属性配置
  3. 使用redis实现消息缓存,集群
  4. 使用kafka实现消息代理

项目结构

MqttWk
  ├── mqtt-codec -- MQTT协议解析的t-io实现
  ├── mqtt-auth -- MQTT服务连接时用户名和密码认证
  ├── mqtt-broker -- MQTT服务器功能的核心实现
  ├── mqtt-common -- 公共类及其他模块使用的服务接口及对象
  ├── mqtt-store -- MQTT服务器会话信息(redis缓存及kafka加载)
  ├── mqtt-zoo -- 教程文档或文件
    ├── mqtt-test-kafka -- kafka消费者接收消息
    ├── mqtt-test-websocket -- websocket通信测试示例

功能说明

  1. 参考MQTT3.1.1规范实现
  2. 完整的QoS服务质量等级实现
  3. 遗嘱消息, 保留消息及消息分发重试
  4. 心跳机制
  5. MQTT连接认证(可选择是否开启)
  6. SSL方式连接(可选择是否开启)
  7. 主题过滤(支持单主题订阅如 /mqtt/test –不可以/结尾, 通配符订阅 /mqtt/# –以/#结尾)
  8. Websocket支持(可选择是否开启)
  9. 集群功能

快速开始

  • 下载已打包好的可运行的jar文件
  • 运行jar文件(如果需要修改配置项,可以在application.properties修改)
  • 打开mqtt-spy客户端, 填写相应配置下载
  • 连接端口:8885, websocket 端口: 9995 websocket path: /mqtt
  • 连接使用的用户名: demo
  • 连接使用的密码: 8F3B8DE2FDC8BD3D792BE77EAC412010971765E5BDD6C499ADCEE840CE441BDEF17E30684BD95CA708F55022222CC6161D0D23C2DFCB12F8AC998F59E7213393
  • 连接使用的证书在 mqtt-zoo\keystore\server.cer

集群使用

  • 多机环境集群:
    • mqttwk.broker.kafka.bootstrap.servers=192.168.1.101:9092,192.168.1.102:9093
    • redis.mode=cluster
    • redis.nodes=192.168.1.103:16379,192.168.1.104:26379
  • 单机环境集群:
    • mqttwk.broker.kafka.bootstrap.servers=127.0.0.1:9092,127.0.0.1:9093
    • redis.mode=normal
    • redis.host=127.0.0.1

自定义 – 连接认证

  • 默认只是简单使用对用户名进行RSA密钥对加密生成密码, 连接认证时对密码进行解密和相应用户名进行匹配认证
  • 使用中如果需要实现连接数据库或其他方式进行连接认证, 只需要重写mqtt-auth模块下的相应方法即可

自定义 – 服务端证书

  • 服务端证书存储在mqtt-brokerresources/keystore/server.jks
  • 用户可以制作自己的证书, 但存储位置和文件名必须使用上述描述的位置及文件名

生成环境部署

  • 生成环境部署建议使用keepalived+nginx+mqtt-broker方式
  • 使用nginx的tcp和websocket反向代理mqtt-broker集群实现负载均衡
  • 使用keepalived实现nginx的高可用
20186 月9

NutzWk 5.0.x 微服务分布式版本开发及部署说明

NutzWk 5.x 已发布一段时间,这段时间基于此版本开发了智慧水务系统(NB-IOT)、某物联网平台、某设备租赁平台、某智慧睡眠平台、某智慧园区项目等,开发和部署过程中遇到一些小问题,开这个帖子把一些经验分享出来省的大家走弯路。

项目地址1: https://github.com/Wizzercn/NutzWk
项目地址2: https://gitee.com/wizzer/NutzWk

1、运行环境
其实项目readme和wk-wiki 已经写的很清楚了,在此强调一下,不是说非这些版本不可,但对于新手来说最好版本号保持一致,能跑起来了您再折腾玩~~

JDK 8 162 +
Maven 3.5.3 +
Redis 4.0.8 +
MySql 5.7 +
Zookeeper 3.4.11 +

2、开发环境
一般建议使用IDEA进行开发,因为是maven多模块的项目,直接用IDEA打开项目根目录,它会通过maven下载jar包,自动构建项目
然后如何启动项目呢,有很多种方式,简单说几个:
1)打开每个NB项目(nutzboot简称)项目里的main类,右击运行,例如 cn.wizzer.sys.commons.core.***MainLauncher
2)通过IDEA 的Run 配置 Application 运行,详见 https://github.com/Wizzercn/NutzWk/blob/nutzboot-dubbo/wk-wiki/01.QuickStart/01.02.Start.md
3)命令行在NB项目根目录运行mvn compile nutzboot:run 或者IDEA右侧Maven管理界面里通过插件运行,,详见 https://github.com/nutzam/nutzboot-maven-plugin

3、启动顺序
保证MySQL、Redis、Zookeeper 都正常启动且为默认端口及默认配置(当然这些配置项可以在application.properties 修改的)
1)MySQL创建一个空白数据库,编码格式为UTF-8,数据库名称 nutzwk_nb
2)NB项目的模块启动顺序是 sys –> cms[可选] –> wx[可选] –> task[可选] –> web-platform –> web-api[可选]
3)如上所述,如果想运行访问后台,只需要启动 sys 和 web-platform即可,注意是有启动顺序的,其他模块需要用就启
4)task 定时任务是依赖于sys的,而web-platform系统管理对定时任务管理是依赖于 task模块的,如果你想让task独立运行并且不需要通过页面进行管理,自己少做改动即可,不是不可以哦

4、部署注意事项
1)因为登录页面对密码进行了RSA加密,有时候部署会遇到怎么也登录不了,而后台抛异常 java.lang.SecurityException: JCE cannot authenticate the provider BC 的情况,解决方法在代码注释里已写明了,不过很少有人去看
https://github.com/Wizzercn/NutzWk/blob/nutzboot-dubbo/wk-app/wk-nb-web-platform/src/main/java/cn/wizzer/app/web/commons/shiro/filter/PlatformAuthenticationFilter.java

1、编辑文件 /usr/java/jdk1.8.0_162/jre/lib/security/java.security
     9下面添加 security.provider.10=org.bouncycastle.jce.provider.BouncyCastleProvider
2、拷贝 bcprov-jdk16-143.jar  bcprov-jdk15-135.jar  /usr/java/jdk1.8.0_162/jre/lib/ext 目录下
3、别问我上面两个文件怎么找……

(如果您是https的话可以把RSA加密方式改掉弃用哦)
2)服务器注意事项:服务器时间同步做没做、hosts里配没配主机名hostname和127.0.0.1的映射关系、内存够不够用(有没有给jar指定内存大小)等

5、其他
1)请关注 NutzWk 的动态,有新的版本发布建议及时更新,往往会修复问题或新增功能
2)如果 NutzWk 给了您帮助,或已用于生产, https://wizzer.cn/donation 欢迎打赏一定金额以资鼓励,创造国内良好的开源环境
3)最后感谢兽兽及nutz社区广大网友的帮助和鼓励,没有您们的支持,这个项目不会历经6年多还在更新前进

20184 月24

Java代码中使用代理

//设置代理  
System.setProperty("http.proxySet", "true");  
System.setProperty("http.proxyHost", "127.0.0.1");  
System.setProperty("http.proxyPort", "1080");
20182 月26

利用nutz+t-io实现硬件设备的socket通信

 

1、MainServer 启动类

/**
 * Created by Wizzer on 2018/2/26.
 */
@IocBean
public class MainServer {
    private static final Log log = Logs.get();

    public static void main(String[] args) {
        try {
            ComboIocLoader loader = new ComboIocLoader(
                    new String[]{"*json", "config/ioc/", "*anno", "cn.wizzer","*rabbitmq"}
            );
            NutIoc ioc = new NutIoc(loader);
            //socket
            ioc.get(SocketServer.class).init();
            //http
            ioc.get(HttpServer.class).init();
            //mq
            String topicQueue = "sweeper-tioTopicQueue";
            ConnectionFactory factory = ioc.get(ConnectionFactory.class, "rabbitmq_cf");
            Connection rabbitmq_conn = factory.newConnection();
            Channel rabbitmq_channel = rabbitmq_conn.createChannel();
            rabbitmq_channel.queueDeclare(topicQueue, true, false, false, null);
            rabbitmq_channel.exchangeDeclare("sweeper-tioTopicExchange", BuiltinExchangeType.TOPIC, true);
            rabbitmq_channel.queueBind(topicQueue, "sweeper-tioTopicExchange", "tio.#");


        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 

2、SocketServer 数据接收

 

/**
 * Created by Wizzer on 2018/2/26.
 */
@IocBean
public class SocketServer {
    private static final Log log = Logs.get();
    //handler, 包括编码、解码、消息处理
    @Inject
    private MyServerAioHandler myServerAioHandler;
    //事件监听器,可以为null,但建议自己实现该接口,可以参考showcase了解些接口
    private ServerAioListener aioListener;
    //一组连接共用的上下文对象
    private ServerGroupContext serverGroupContext;
    //aioServer对象
    private AioServer aioServer;
    //有时候需要绑定ip,不需要则null
    private String serverIp;
    @Inject
    private PropertiesProxy conf;

    public void init() throws Exception {
        int port = conf.getInt("server.socket.port", 8600);
        log.debug("socket port::" + port);
        serverGroupContext = new ServerGroupContext("tio", myServerAioHandler, aioListener);
        serverGroupContext.setHeartbeatTimeout(30000);
        aioServer = new AioServer(serverGroupContext);
        aioServer.start(serverIp, port);
    }
}

3、socket 数据包的解析

4、RabbitMQ 队列+消费者 实现数据入库

5、HttpServer 提供HTTP API用于对设备发送命令

6、socket 命令包的下发

 

源码:https://gitee.com/wizzer/demo

20181 月21

Elasticsearch 6.1.2 (二)分页查询、排序、关键词查询,集合beetl实现前台展示

后台代码,自定义tag:

package cn.wizzer.app.web.modules.tags;

import cn.wizzer.app.web.commons.ex.elasticsearch.EsService;
import cn.wizzer.app.web.commons.utils.YcDateUtil;
import cn.wizzer.app.ycold.modules.services.YcoldInquiryService;
import cn.wizzer.framework.page.Pagination;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.beetl.core.GeneralVarTagBinding;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchType;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.nutz.ioc.impl.PropertiesProxy;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Strings;
import org.nutz.lang.Times;
import org.nutz.log.Log;
import org.nutz.log.Logs;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * Created by wizzer on 2018/1/20.
 */
@IocBean
public class YcoldInquiryListTag extends GeneralVarTagBinding {
    private final static Log log = Logs.get();
    @Inject
    private EsService esService;
    @Inject
    private YcoldInquiryService ycoldInquiryService;
    @Inject
    private PropertiesProxy cfg;

    @Override
    public void render() {
        String startDate = Strings.sNull(this.getAttributeValue("startDate"));
        String endDate = Strings.sNull(this.getAttributeValue("endDate"));
        String keyword = Strings.sNull(this.getAttributeValue("keyword"));
        int pageNumber = NumberUtils.toInt(Strings.sNull(this.getAttributeValue("pageNumber")), 1);
        int pageSize = NumberUtils.toInt(Strings.sNull(this.getAttributeValue("pageSize")), 10);
        boolean highlight = BooleanUtils.toBoolean(Strings.sNull(this.getAttributeValue("highlight")));
        boolean explain = BooleanUtils.toBoolean(Strings.sNull(this.getAttributeValue("explain")));
        String sortName = Strings.sNull(this.getAttributeValue("sortName"));
        String sortOrder = Strings.sNull(this.getAttributeValue("sortOrder"));
        Pagination page = new Pagination();
        page.setPageNo(pageNumber);
        page.setPageSize(pageSize);
        try {
            BoolQueryBuilder query = QueryBuilders.boolQuery();
            //根据名称查询
            if (Strings.isNotBlank(keyword)) {
                query.must(QueryBuilders.wildcardQuery("CASNM", "*" + keyword + "*"));
            }
            //截止时间大于等于现在
            query.must(QueryBuilders.rangeQuery("IQDAT").gte(Times.format("yyyyMMddHHmmss", new Date())));
            //公共日期起
            if (Strings.isNotBlank(startDate)) {
                query.must(QueryBuilders.rangeQuery("ANNODAT").gte(startDate.replaceAll("-","")));
            }
            //公共日期至
            if (Strings.isNotBlank(endDate)) {
                query.must(QueryBuilders.rangeQuery("ANNODAT").lte(endDate.replaceAll("-","")));
            }
            //几个状态条件
            query.must(QueryBuilders.matchQuery("BUYER_STS", "N"));//采购商状态
            query.must(QueryBuilders.matchQuery("STS", "A"));//状态
            query.must(QueryBuilders.matchQuery("ANNOMK", "Y"));//公告註記
            SearchRequestBuilder srb = esService.getClient().prepareSearch(cfg.get("es.index.name"))
                    .setSearchType(SearchType.DFS_QUERY_THEN_FETCH)
                    .setTypes("inquiry")
                    .setQuery(query)
                    //分页
                    .setFrom((pageNumber - 1) * pageSize).setSize(pageSize)
                    //是否按匹配度排序
                    .setExplain(explain);

            if (highlight) {
                HighlightBuilder highlightBuilder = new HighlightBuilder().field("*").requireFieldMatch(false);
                highlightBuilder.preTags("<span style=\"color:red\">");
                highlightBuilder.postTags("</span>");
                srb.highlighter(highlightBuilder);
            }
            if (Strings.isNotBlank(sortName)) {
                String[] sortNames = StringUtils.split(sortName, ",");
                if ("asc".equalsIgnoreCase(sortOrder)) {
                    for (String s : sortNames) {
                        srb.addSort(s, SortOrder.ASC);
                    }
                } else {
                    for (String s : sortNames) {
                        srb.addSort(s, SortOrder.DESC);
                    }
                }
            }
            log.debug("srb:::\r\n" + srb.toString());

            SearchResponse response = srb.execute().actionGet();
            SearchHits hits = response.getHits();
            page.setTotalCount((int) hits.getTotalHits());
            List<Map<String, Object>> list = new ArrayList<>();
            hits.forEach(searchHit -> {
                Map<String, Object> source = searchHit.getSourceAsMap();
                Map<String, HighlightField> highlightFields = searchHit.getHighlightFields();
                //name高亮
                HighlightField nameField = highlightFields.get("CASNM");
                if (nameField != null) {
                    Text[] fragments = nameField.fragments();
                    String tmp = "";
                    for (Text text : fragments) {
                        tmp += text;
                    }
                    source.put("CASNM", tmp);
                }
                source.put("IQDAT", YcDateUtil.get_yyyyMMdd_HHmm(Strings.sNull(source.get("IQDAT"))));
                source.put("ANNODAT", YcDateUtil.get_yyyyMMdd(Strings.sNull(source.get("ANNODAT"))));
                list.add(source);
            });
            page.setList(list);
        } catch (Exception e) {
            e.printStackTrace();
        }
        this.binds(page);
        this.doBodyRender();
    }
}

前台beetl页面:

  <#ycold_inquiry_list pageNumber="${pageNumber}" pageSize="${pageSize}" startDate="${startDate}" endDate="${endDate}" keyword="${keyword}" sortName="ANNODAT" sortOrder="desc" highlight="false" var="p">

    <table class="list_table_blue even_table">
        <thead>
        <tr>
            <th>公告日期</th>
            <th>案件名称</th>
            <th>交货地点</th>
            <th>报价截止日期</th>
            <th width="110">查看详细</th>
        </tr>
        </thead>
        <tbody>
        <%for(o in p.list){%>
        <tr>
            <td>${o.ANNODAT!}</td>
            <td class="l_text"><a href="${base!}/purchase/info/${o.XUID!}" target="_blank" class="td_a hide1">${o.CASNM!}</a>
            </td>
            <td class="l_text">${o.DLSITE!}</td>
            <td>${o.IQDAT!}</td>
            <td><a href="${base!}/purchase/info/${o.XUID!}" target="_blank" class="more_a png"></a></td>
        </tr>
        <%}%>
        </tbody>
    </table>
    <%if(p.totalCount>1){%>
    <div class="page round_s_a"></div>
    <script type="text/javascript">
        $(function () {
            $(".page").createPage({
                pageCount: ${p.totalPage},
                totalCount: ${p.totalCount},
                current: ${p.pageNo},
                backFn: function (p) {
                    window.location.href = "?page=" + p + "&size=${p.pageSize}";
                }
            });
        });
    </script>
    <%}%>
20181 月21

Elasticsearch 6.1.2 (一)中文分词设置、字符串字段排序设置

if (!esService.isExistsType(cfg.get("es.index.name"), type)) {
                //初始化索引表
                XContentBuilder mapping = jsonBuilder().startObject()
                        .startObject(type)
                        .startObject("_all")//设置IK分词
                        .field("analyzer", "ik_max_word")
                        .field("search_analyzer", "ik_max_word")
                        .field("term_vector", "no")
                        .field("store", "false")
                        .endObject()
                        .startObject("properties")
                        .startObject("CASNM").field("type", "text").field("analyzer", "ik_max_word").endObject()
                        .startObject("IQDAT").field("type", "text").field("index", "true").field("fielddata","true").endObject()
                        .startObject("ANNODAT").field("type", "text").field("index", "true").field("fielddata","true").endObject()
                        .endObject()
                        .endObject()
                        .endObject();
                esService.putMapping(cfg.get("es.index.name"), "inquiry", mapping);
            }

    /**
     * @param indexName 索引名
     * @param type      数据类型(表名)
     * @param mapping   mapping对象
     */
    public boolean putMapping(String indexName, String type, XContentBuilder mapping) {
        PutMappingRequest mappingRequest = Requests.putMappingRequest(indexName).type(type).source(mapping);
        PutMappingResponse response = getClient().admin().indices().putMapping(mappingRequest).actionGet();
        return response.isAcknowledged();
    }

.field(“fielddata”,”true”) //text字段默认不允许排序,是单独设置数据格式

20179 月15

使用Java API绘制验证码图片

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Random;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 生成验证码,将验证码绘制成一张图片返回浏览器
 * 
 * @author FreeDroid
 *
 */
public class CheckcodeServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

	public void service(HttpServletRequest request, 
              HttpServletResponse response) throws 
                     ServletException, IOException {
		/*
		 * step1.绘图
		 */
		Random r = new Random();
		//从getString()方法取出A-Z,0-9的指定长度随机字符串
		String number = getString(5);
		//先创建一个画布(内存映像对象)
		BufferedImage image = new BufferedImage(80, 30, BufferedImage.TYPE_INT_BGR);
		//获得画笔
		Graphics g = image.getGraphics();
		//给画笔设置颜色
		g.setColor(new Color(255, 255, 255));
		//给画布设置背景颜色
		g.fillRect(0, 0, 80, 30);
		//设置字体
		g.setFont(new Font(null, Font.BOLD|Font.ITALIC, 20));
		//给每个字符设置随机颜色,并画到画布上
		for (int i = 0; i < number.length(); i++) {
			//给画笔设置颜色
			g.setColor(new Color(r.nextInt(255),r.nextInt(255),r.nextInt(255)));
			//在画布上画字符
			g.drawString(String.valueOf(number.charAt(i)), i*15, 25);
		}
		//加一些干扰线
		for (int i = 0; i < 8; i++) {
			//给画笔设置颜色
			g.setColor(new Color(r.nextInt(255),r.nextInt(255),r.nextInt(255)));
			//在画布范围内画线
			g.drawLine(r.nextInt(80), r.nextInt(30), r.nextInt(80), r.nextInt(30));
		}
		
		/*
		 * step2.将图片压缩,然后输出
		 */
		//设置响应内容类型为JPEG格式的图片
		response.setContentType("image/jpeg");
		//获得字节输出流(图片是二进制数据)
		OutputStream os = response.getOutputStream();
		//将原始图片(image)按照指定的算法压缩(jpeg)
                //然后将压缩之后得到的字节写入response对象。
		javax.imageio.ImageIO.write(image, "jpeg", os);
		os.close();		
	}

	/**
	 * 随机生成指定长度的大写字母和数字组合
	 * @param length
	 * @return
	 */
	private String getString(int length) {
		StringBuilder str = new StringBuilder();
		Random r = new Random();
		//取出大写字母
		for (int i = 0; i < 26; i++) {
			str.append((char) ('A' + i));
		}
		//取出数字
		for (int i = 0; i < 10; i++) {
			str.append((char) ('0' + i));
		}
		//从字符串str随机取length个字符放进str2组成新的字符串
		StringBuilder str2 = new StringBuilder();
		for (int i = 0; i < length; i++) {
			str2.append(str.charAt(r.nextInt(str.length())));
		}
		return str2.toString();
	}
	
}
20178 月11

UTF-8中文字符编码和解码,用位运算实现

/**
 * UTF中文字符编码和解码
 * 中文字符占3个字节,前缀分别是:1110XXXX 10XXXXXX 10XXXXXX
 * 
 * @author FreeDroid
 *
 */
public class Utf8codeANDdecode {

	public static void main(String[] args) {
		int ch = '我';
		byte[] bytes = codeUTF8(ch);
		char ch2 = decodeUTF8(bytes);
		System.out.println(ch2);
	}

	/**
	 * 解码
	 * @param bytes
	 * @return
	 */
	public static char decodeUTF8(byte[] bytes) {
		int ch = (bytes[0]<<12&0xffff)|(bytes[1]<<6&0x3fff)|(bytes[2]&0x3f);
		return (char) ch;
	}
	
	/**
	 * 编码
	 * @param ch
	 * @return
	 */
	public static byte[] codeUTF8(int ch) {
		int b3 = ch & 0x3f | 0x80;
		int b2 = ch >>> 6 & 0x3f | 0x80;
		int b1 = ch >>> 12 & 0xf | 0xe0;
		return new byte[] { (byte) b1, (byte) b2, (byte) b3 };
	}

}
20177 月30

同名文件自动建立副本递归实现

public void newFile(File file){
	try {
		if (file.createNewFile()) {
			System.out.println("文件创建成功!");
		} else {
			String cName = changeName(file);
			File files = new File("." + File.separator + cName);
			newFile(files);
		}
	} catch (IOException e) {
		e.printStackTrace();
	}
}
public String changeName(File file) {
	String name = file.getName();
	int index = name.lastIndexOf('.');
	int index2 = name.lastIndexOf("_副本");
	if (index2 < 0) {
		return name.substring(0, index) + "_副本1" + name.substring(index);
	} else {
		String num = name.substring(index2+3, index);
		int i = Integer.valueOf(num)+1;
		return name.substring(0, index2) + "_副本" + i + name.substring(index);
	}
}
20173 月23

NutzWk: 微信AccessToken没有持久化造成超出调用限制的问题解决

1、wx_config 实体类添加三个字段,对应的表结构也要手动修改:


    @Column
    @Comment("access_token")
    @ColDefine(type = ColType.VARCHAR, width = 255)
    private String access_token;

    @Column
    @Comment("access_token_expires")
    @ColDefine(type = ColType.INT)
    private Integer access_token_expires;

    @Column
    @Comment("access_token_lastat")
    @ColDefine(type = ColType.VARCHAR, width = 50)
    private String access_token_lastat;

    get  set ...方法生成出来

2、nutzwx版本升级为1.r.61-SNAPSHOT

        <dependency>
            <groupId>org.nutz</groupId>
            <artifactId>nutzwx</artifactId>
            <version>1.r.61-SNAPSHOT</version>
        </dependency>


3、nutz版本升级为1.r.60

        <dependency>
            <groupId>org.nutz</groupId>
            <artifactId>nutz</artifactId>
            <version>1.r.60</version>
        </dependency>

4、WxConfigService 类getWxApi2替换为如下代码(主要是DaoAccessTokenStore从数据库取access_token)

    public WxApi2 getWxApi2(String wxid) {
        Wx_config appInfo = this.fetch(Cnd.where("id", "=", wxid));
        DaoAccessTokenStore myDaoAccessTokenStore = new DaoAccessTokenStore(dao());
        Map<String, Object> params = new HashMap<>();
        params.put("id", appInfo.getId());
        myDaoAccessTokenStore.setTableAccessToken("access_token");
        myDaoAccessTokenStore.setTableAccessTokenExpires("access_token_expires");
        myDaoAccessTokenStore.setTableAccessTokenLastat("access_token_lastat");
        myDaoAccessTokenStore.setFetch("select access_token,access_token_expires,access_token_lastat from wx_config where id=@id");
        myDaoAccessTokenStore.setUpdate("update wx_config set access_token=@access_token, access_token_expires=@access_token_expires, access_token_lastat=@access_token_lastat where id=@id");
        myDaoAccessTokenStore.setParams(params);
        WxApi2Impl wxApi2 = new WxApi2Impl();
        wxApi2.setAppid(appInfo.getAppid());
        wxApi2.setAppsecret(appInfo.getAppsecret());
        wxApi2.setEncodingAesKey(appInfo.getEncodingAESKey());
        wxApi2.setToken(appInfo.getToken());
        wxApi2.setAccessTokenStore(myDaoAccessTokenStore);
        return wxApi2;
    }
20173 月17

log4j 换成 logback 注意事项

pom.xml

 

<dependency>  
    <groupId>ch.qos.logback</groupId>  
    <artifactId>logback-classic</artifactId>  
    <version>1.2.2</version>  
</dependency>  
<dependency>  
    <groupId>org.logback-extensions</groupId>  
    <artifactId>logback-ext-spring</artifactId>  
    <version>0.1.4</version>  
</dependency>  
<dependency>  
    <groupId>org.slf4j</groupId>  
    <artifactId>jcl-over-slf4j</artifactId>  
    <version>1.7.25</version>  
</dependency>
<dependency>  
    <groupId>org.slf4j</groupId>  
    <artifactId>log4j-over-slf4j</artifactId>  
    <version>1.7.25</version>  
</dependency>

 

排除其他包的log4j引用,特别是dubbo的、shiro的

 

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.8.4</version>
            <exclusions>
                <exclusion>
                    <groupId>log4j</groupId>
                    <artifactId>log4j</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>javax.ws.rs</groupId>
            <artifactId>javax.ws.rs-api</artifactId>
            <version>2.0</version>
            <exclusions>
                <exclusion>
                    <groupId>log4j</groupId>
                    <artifactId>log4j</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-ehcache</artifactId>
            <version>1.3.2</version>
            <exclusions>
                <exclusion>
                    <groupId>log4j</groupId>
                    <artifactId>log4j</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-web</artifactId>
            <version>1.3.2</version>
            <exclusions>
                <exclusion>
                    <groupId>log4j</groupId>
                    <artifactId>log4j</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

配置文件:

<?xml version="1.0" encoding="UTF-8"?>

<!-- For assistance related to logback-translator or configuration  -->
<!-- files in general, please contact the logback user mailing list -->
<!-- at http://www.qos.ch/mailman/listinfo/logback-user             -->
<!--                                                                -->
<!-- For professional support please see                            -->
<!--    http://www.qos.ch/shop/products/professionalSupport         -->
<!--                                                                -->
<configuration>
    <appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
        <file>/monitor.log</file>

        <!-- Policy定义如何滚动,按文件大小滚动生成日志 -->
        <!-- 如果是按文件大小滚动生成日志,前面的file标签可省略,而使用fileNamePattern标签定义的名字 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
            <fileNamePattern>/monitor.%i.log.gz
            </fileNamePattern>
            <!-- 归档日志的下标,替换fileNamePattern的%i,最多3个归档文件 -->
            <minIndex>1</minIndex>
            <maxIndex>5</maxIndex>
        </rollingPolicy>

        <!-- triggeringPolicy定义什么时候滚动,下面是定义了文件大小超过100M的时候产生归档文件 -->
        <triggeringPolicy
                class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
            <maxFileSize>300MB</maxFileSize>
        </triggeringPolicy>

        <!-- append是否接着上次写文件结尾继续写,默认为true -->
        <append>true</append>
        <encoding>GBK</encoding>

        <!-- layout,定义格式 -->
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>[%-5level] %d{HH:mm:ss.SSS} [%thread] %logger %caller{2} - %msg%n</pattern>
        </layout>
    </appender>
    <logger name="org.springframework" level="info"/>
    <logger name="org.nutz.dao" level="debug"/>
    <logger name="net.sf.ehcache" level="info"/>
    <logger name="druid.sql" level="info"/>
    <logger name="com.alibaba.druid" level="info"/>
    <logger name="org.apache.shiro" level="info"/>
    <logger name="org.quartz" level="info"/>
    <root level="debug">
        <appender-ref ref="Console"/>
    </root>
</configuration>
201612 月8

Nutz:组合查询、子查询示例代码

        Cnd cnd = Cnd.NEW();
        if (!Strings.isBlank(src)) {
            cnd.and("srcFrom", "=", src);
        }
        if (!Strings.isBlank(name)) {
            String[] n = StringUtils.split(name, " ");
            SqlExpressionGroup group = new SqlExpressionGroup();
            for (String s : n) {
                SqlExpression sqlExpression = Cnd.exp("srcName", "like", "%" + s + "%");
                group.or(sqlExpression);
            }
            cnd.and(group);
        }
        cnd.and(Cnd.exps("productSku", "=", "").or("productSku", "is", null));
201611 月28

Elasticsearch 5.0 以上 elasticsearch-head 安装教程

elasticsearch 5.0/5.0.1以上版本的elasticsearch-head插件,使用node单独运行,会出现跨站访问的问题:

http://localhost:9200/_nodes. No 'Access-Control-Allow-Origin' 
header is present on the requested resource. 
Origin 'http://localhost:9100' is therefore not allowed access.

elasticsearch-head 安装教程如下:

1、安装node;
2、下载elasticsearch-head,安装node组件:

>git clone git://github.com/mobz/elasticsearch-head.git
>cd elasticsearch-head
>npm i
>npm i grunt-cli -g
>grunt server

3、elasticsearch.yml 增加配置项

http.cors.enabled: true
http.cors.allow-origin: /http?:\/\/127.0.0.1(:[0-9]+)?/

4、Gruntfile.js 增加hostname配置项

connect: {
         server: {
            options: {
               port: 9100,
               hostname: '*',
               base: '.',
               keepalive: true
            }
         }
      }

5、修改配置文件后各自重启

http://127.0.0.1:9100/ 访问即可(http.cors.allow-origin 里配的是127.0.0.1哦)

201610 月20

Nutz:fetchLinks 三级关联表的应用

        Shop_goods_type obj = shopGoodsTypeService.fetch(id);
        shopGoodsTypeService.fetchLinks(obj, null, Cnd.orderBy().asc("location"));
        for (Shop_goods_type_props o : obj.getPropsList()) {
            shopGoodsTypeService.dao().fetchLinks(o, null, Cnd.orderBy().asc("location"));
        }
20169 月12

VUE: htmlWebpackPlugin is not defined 解决办法

webpack.base.conf.js

vue-html 注释掉

// {
//   test: /\.html$/,
//   loader: 'vue-html'
// },

20168 月19

Nginx:绑定域名代理tomcat

server {
	listen 80;
	listen 443 ssl;
	if ( $scheme = "http" ) {
            rewrite ^(.*) https://$host$1;
	}
	server_name nutzwk.wizzer.cn;
	root "/java/work/nutzwk";	
	ssl_certificate /java/work/1_nutzwk.wizzer.cn_bundle.crt;
	ssl_certificate_key /java/work/2_nutzwk.wizzer.cn.key;
	add_header "X-UA-Compatible" "IE=Edge, chrome=1";
	location / {
		proxy_pass http://127.0.0.1:9091;
		proxy_set_header Host $host;
		proxy_set_header X-Real-IP $remote_addr;
		proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
	}
}
20168 月16

备份:HttpClient 4.x POST请求

            List<BasicNameValuePair> formParams = new ArrayList<>();
            formParams.add(new BasicNameValuePair("urlpp", ""));
            formParams.add(new BasicNameValuePair("urlkm", ""));
            formParams.add(new BasicNameValuePair("khjb", "2"));
            HttpEntity httpEntity = new UrlEncodedFormEntity(formParams, "UTF-8");
            HttpPost httpPost = new HttpPost(tesseract.njwztx_baseUrl + "fetchNextList.action");
            httpPost.setEntity(httpEntity);
            httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
            httpPost.setHeader("X-Requested-With", "XMLHttpRequest");
            httpPost.setHeader("Cookie", "rememberPhone=1; phoneNumber=" + tesseract.getNjwztx_dhhm() + "; friendlyReminder=true;JSESSIONID=" + JSESSIONID + ";");
            HttpClient httpclient = HttpClients.createDefault();
            HttpResponse response = httpclient.execute(httpPost);
            HttpEntity entity = response.getEntity();
            Header[] headers = response.getAllHeaders();
            for (int i = 0; i < headers.length; i++) {
                log.debug("header" + i + ":::" + headers[i]);
            }
            if (entity != null) {
                log.debug("getContentEncoding:::" + entity.getContentEncoding());
                log.debug("getContentType:::" + entity.getContentType());
                String str = EntityUtils.toString(response.getEntity());
                log.debug(str);
                Files.write("2.html", str);
            }