文章标签 ‘JAVA’
20247 月23

BudIot 开源物联网设备平台v1.0发布

BUDIOT 是一个开源的、企业级的物联网平台,它集成了设备管理、协议解析、消息订阅、场景联动等一系列物联网核心能力,支持以平台适配设备的方式连接海量设备,支持在线下发指令实现远程控制,支持扩展水电气等各类计费业务场景。

本平台是在千万级设备实时计费物联网平台经验基础上,在不损失性能的前提下进行功能删减、结构优化而来,小而美,同时又具备灵活的扩展性。

源码: https://github.com/budwk/budiot

在线演示地址: https://demo.budiot.com 用户名: superadmin 密码: 1

官网: https://budiot.com

开发框架

基于自研 Java 微服务框架 https://budwk.com

简单说明

Jar 运行模块

  • budiot-access/budiot-access-gateway 设备网关,用于设备协议和 network 组件
  • budiot-access/budiot-access-processor 设备数据上报业务处理模块
  • budiot-server WEB 服务 API ,定时任务等

其他模块说明

  • budiot-access/budiot-access-network 网络组件,支持 TCP/MQTT/UDP/HTTP 等
  • budiot-access/budiot-access-protocol 设备协议开发包,内含 demo 示例
  • budiot-access/budiot-access-storage 设备数据存储,可扩展时序数据库等

前端模块

  • budiot-vue-admin Vue3 + Element-Plus

开发环境

  • OpenJDK 11
  • Redis 6.x
  • MariaDB 10.x
  • MongoDB 7.0.x
  • RocketMQ 5.2.x

设备上报有效数据存储

默认采用 MongoDB 7 的时序集合,可根据项目规模需要,扩展为 TDEngine 等时序数据库

20247 月9

Java项目运行一段时间后报错 Comparison method violates its general contract

java.util.concurrent.ExecutionException: java.lang.IllegalArgumentException: Comparison method violates its general contract!

错误代码:
children.sort((o1, o2) -> (int) (o2.getLong("createdAt", 0) - o1.getLong("createdAt", 0)));

修复后:
children.sort((o1, o2) -> Long.compare(o2.getLong("createdAt", 0), o1.getLong("createdAt", 0)));

排序器 children.sort((o1, o2) -> (int) (o2.getLong("createdAt", 0) - o1.getLong("createdAt", 0))); 存在一个潜在的问题,即当时间戳的差值超出 int 的范围时,强制转换为 int 会导致数据溢出,从而导致比较结果不正确。

一个解决方法是使用 Long.compare 方法进行比较,而不是手动进行减法运算。Long.compare 方法确保了比较的对称性和一致性。

20247 月8

600万设备连接平台遇到的坑

MongoDB 的坑

listCollections

  • 问题:代码中存在 listCollections 操作,大量数据上报时,造成MongoDB CPU高升,使处理性能受到影响。经过检查,发现通信报文日表是自动创建的,每次都会判断集合是否存在;而mongo驱动包里判断集合是否存在的操作,就是先执行 listCollections;
  • 优化:提前创建好需要的集合,不要在数据上报的时候进行判断、创建;ps:其实其他数据库,比如mysql、tdengine等,都是同样的道理;

连接数过大

  • 问题:MongoDB 连接数配置较大,导致很多handler服务,过多的线程会导致上下文切换开销变大,同时内存开销也会上涨;
  • 优化:调低连接数配置,进行压力测试;

规则引擎的坑

  • 问题:为了提升性能,往往采用队列+规则引擎来处理业务,设备协议解析+计费业务是在一个handler里,而规则引擎则在下一个队列处理;规则引擎负责为欠费的表具创建短信提醒、关阀指令(产品欠费规则配置),往往在第一个队列里需等待N秒,将规则引擎创建的指令一起下方给表具,每天几百万表具实时上报数据+实时计费,数据并发量较大,如果都等待N秒,会严重影响处理性能;
  • 优化:对计费后余额大于等于0的的表具,不等待N秒,直接回复结束指令,绝大部分表具其实已经是关阀状态,无需本次下发关阀指令,哪怕有需要关阀的,延后到下次通信再执行也没影响;对于小于0的表具,则使用原有逻辑;优化后,降低了90%的等待时间,大大提升高峰高并发处理性能;

HTTP订阅的坑

  • 问题:前期主要通过AEP平台的http订阅实现NB表的通信,但是http服务在高并发时有时候会挂掉(具体表象就是两个http服务,只有一个存活,另一个服务存在,但不处理数据);
  • 优化:将http服务单独服务部署,不要和其他handler、服务抢占服务器资源,jvm也就不会崩了;后期采用MQ订阅方式;

站内信的坑

  • 问题:原有产品设计当设备产生告警时(原生告警、规则告警等)有站内信提醒,经过项目实际运行,站内信数据非常庞大,时刻都有设备告警,导致web页面卡顿、后台web服务资源占用高;
  • 优化:去掉设备站内信告警功能,一是站内信管理人员完全看不过来,二是设备告警在功能菜单里可以查询到;

20238 月10

Nutz Https请求忽略SSL证书

@IocBean
@Slf4j
public class CimApiServer {
    @Inject
    private RedisService redisService;
    private String redis_key = "cim:accessToken";
    @Inject
    @Reference(check = false)
    private ISysConfigProvider sysConfigProvider;

    public String getAccessToken() {
        String token = redisService.get(redis_key);
        if (Strings.isBlank(token)) {
            token = this.getHttpToken();
            redisService.setex(redis_key, 3600 * 24 - 100, token);
        }
        return token;
    }

    private String getHttpToken() {
        String CIM_GIS_APPID = sysConfigProvider.getString("COMMON", "CIM_GIS_APPID");
        String CIM_GIS_HTTP_BASE = sysConfigProvider.getString("COMMON", "CIM_GIS_HTTP_BASE");
        String CIM_GIS_APPKEY = sysConfigProvider.getString("COMMON", "CIM_GIS_APPKEY");
        String CIM_GIS_APPSECRET = sysConfigProvider.getString("COMMON", "CIM_GIS_APPSECRET");
        Map<String, Object> params = new HashMap<>();
        params.put("apiKey", CIM_GIS_APPKEY);
        params.put("secret", CIM_GIS_APPSECRET);
        Header header = Header.create();
        header.addv("Content-Type", "application/json");
        Request request = Request.create(CIM_GIS_HTTP_BASE + "/auth/getAccessToken", Request.METHOD.POST);
        request.setHeader(header);
        request.setData(Json.toJson(params));

        Sender sender = Sender.create(request).setTimeout(20 * 1000);
        if (CIM_GIS_HTTP_BASE.startsWith("https")) {
            try {
                SSLContext sslcontext = createIgnoreVerifySSL();
                sender.setSSLSocketFactory(sslcontext.getSocketFactory());
                sender.setHostnameVerifier((urlHostName, session) -> true);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        Response response = sender.send();
        if (response.isOK()) {
            NutMap map = Json.fromJson(NutMap.class, response.getContent());
            log.debug("getHttpToken:::" + Json.toJson(map));
            if (0 == map.getInt("code")) {
                return map.getString("data");
            }
        }
        return "";
    }

    private static class TrustAllManager
            implements X509TrustManager {
        public X509Certificate[] getAcceptedIssuers() {
            return null;
        }

        public void checkServerTrusted(X509Certificate[] certs,
                                       String authType) {
        }

        public void checkClientTrusted(X509Certificate[] certs,
                                       String authType) {
        }
    }


    public SSLContext createIgnoreVerifySSL() throws NoSuchAlgorithmException, KeyManagementException {
        SSLContext sc = SSLContext.getInstance("SSL");
        sc.init(null, new TrustManager[]{new TrustAllManager()}, null);
        return sc;
    }
}
20229 月22

数据库文档生成工具Java

 <dependency>
      <groupId>cn.smallbun.screw</groupId>
      <artifactId>screw</artifactId>
      <version>1.0.5</version>
      <scope>import</scope>
 </dependency>

public void generate() throws Exception {
        //数据源
        HikariConfig hikariConfig = new HikariConfig();
        hikariConfig.setDriverClassName("com.mysql.cj.jdbc.Driver");
        hikariConfig.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/xx?useUnicode=true&characterEncoding=utf8&useSSL=false" +
                "&serverTimezone=Asia/Shanghai");
        hikariConfig.setUsername("root");
        hikariConfig.setPassword("root");
        //设置可以获取tables remarks信息
        hikariConfig.addDataSourceProperty("useInformationSchema", "true");
        hikariConfig.setMinimumIdle(2);
        hikariConfig.setMaximumPoolSize(5);
        DataSource dataSource = new HikariDataSource(hikariConfig);
        //生成配置
        String fileOutputDir = "./";
        EngineConfig engineConfig = EngineConfig.builder()
                //生成文件路径
                .fileOutputDir(fileOutputDir)
                //打开目录
                .openOutputDir(true)
                //文件类型
                .fileType(EngineFileType.WORD)
                //生成模板实现
                .produceType(EngineTemplateType.velocity)
                //自定义文件名称
                .fileName("xx数据库说明").build();

        //忽略表
        ArrayList<String> ignoreTableName = new ArrayList<>();
        //忽略表前缀
        ArrayList<String> ignorePrefix = new ArrayList<>();
        ignorePrefix.add("sys_qrtz_");
        //忽略表后缀
        ArrayList<String> ignoreSuffix = new ArrayList<>();
//        ignoreSuffix.add("_");
        ProcessConfig processConfig = ProcessConfig.builder()
                //指定生成逻辑、当存在指定表、指定表前缀、指定表后缀时,将生成指定表,其余表不生成、并跳过忽略表配置
                //根据名称指定表生成
                .designatedTableName(null)
                //根据表前缀生成
                .designatedTablePrefix(null)
                //根据表后缀生成
                .designatedTableSuffix(null)
                //忽略表名
                .ignoreTableName(ignoreTableName)
                //忽略表前缀
                .ignoreTablePrefix(ignorePrefix)
                //忽略表后缀
                .ignoreTableSuffix(ignoreSuffix).build();
        //配置
        Configuration config = Configuration.builder()
                //版本
                .version("1.0.0")
                //描述
                .description("数据库设计文档生成")
                //数据源
                .dataSource(dataSource)
                //生成配置
                .engineConfig(engineConfig)
                //生成配置
                .produceConfig(processConfig)
                .build();
        //执行生成
        new DocumentationExecute(config).execute();
    }

20213 月25

TDengine 时序数据库的 NutzBoot 开发实例

nutz 及 nutzboot 已支持 TDengine

基于 nutzboot 开发 TDengine 实例

开发环境

  • 服务端:CentOS 8.2 64 位
  • 客户端:Windows 10 64 位

TDengine 安装及配置

  • 官网下载 rpm 安装包
  • 执行安装 rpm -ivh TDengine-server-2.0.18.0-Linux-x64.rpm
  • 修改配置文件 vi /etc/taos/taos.cfg 加上当前服务器 hostname 主机名
# first fully qualified domain name (FQDN) for TDengine system
firstEp                   wizzer-test:6030
# local fully qualified domain name (FQDN)
fqdn                      wizzer-test

  • 若为默认密码则直接输入 taos 或 taos -h 127.0.0.1 执行数据库创建命令
taos > create database test;

Windows 10 hosts 配置

  • 修改 C:\Windows\System32\drivers\etc\hosts
  • 添加 ip wizzer-test

创建 nutzboot Maven 项目

  • pom.xml 文件加入 nutzboot 及 TDengine JDBC 依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.budwk</groupId>
    <artifactId>test</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <nutzboot.version>2.4.2-SNAPSHOT</nutzboot.version>
        <jaxb-api.version>2.3.1</jaxb-api.version>
        <slf4j.version>1.7.25</slf4j.version>
        <logback.version>1.2.3</logback.version>
        <taos-jdbcdriver.version>2.0.23</taos-jdbcdriver.version>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.nutz</groupId>
            <artifactId>nutzboot-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.nutz</groupId>
            <artifactId>nutzboot-starter-nutz-dao</artifactId>
        </dependency>
        <dependency>
            <groupId>org.nutz</groupId>
            <artifactId>nutzboot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>com.taosdata.jdbc</groupId>
            <artifactId>taos-jdbcdriver</artifactId>
            <version>${taos-jdbcdriver.version}</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${slf4j.version}</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>${logback.version}</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>${logback.version}</version>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.nutz</groupId>
                <artifactId>nutzboot-parent</artifactId>
                <version>${nutzboot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <repositories>
        <repository>
            <id>nutz</id>
            <url>http://jfrog.nutz.cn/artifactory/libs-release</url>
        </repository>
        <repository>
            <id>nutz-snapshots</id>
            <url>http://jfrog.nutz.cn/artifactory/snapshots</url>
            <snapshots>
                <enabled>true</enabled>
                <updatePolicy>always</updatePolicy>
            </snapshots>
            <releases>
                <enabled>false</enabled>
            </releases>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>nutz-snapshots</id>
            <url>http://jfrog.nutz.cn/artifactory/snapshots</url>
            <snapshots>
                <enabled>true</enabled>
                <updatePolicy>always</updatePolicy>
            </snapshots>
            <releases>
                <enabled>false</enabled>
            </releases>
        </pluginRepository>
    </pluginRepositories>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.7.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <compilerArgs>
                        <arg>-parameters</arg>
                    </compilerArgs>
                    <useIncrementalCompilation>false</useIncrementalCompilation>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.nutz.boot</groupId>
                <artifactId>nutzboot-maven-plugin</artifactId>
                <version>${nutzboot.version}</version>
            </plugin>
        </plugins>
    </build>
</project>
  • 创建实体类
/**
 * 注意 TDengine 表及字段名都为小写字母
 */
@Table("iot_dev")
public class Iot_dev implements Serializable {
    private static final long serialVersionUID = 1L;

    @Column
    @Comment("ID")
    @ColDefine(type = ColType.TIMESTAMP)
    private Date ts;

    @Column("devid") //字段名都为小写字母
    @Comment("设备 ID")
    @ColDefine(type = ColType.VARCHAR, width = 32)
    private String devId;

    @Column("devtype") //字段名都为小写字母
    @Comment("设备类型")
    @ColDefine(type = ColType.BINARY, width = 32)
    private String devType;

    @Column
    @Comment("状态")
    @ColDefine(type = ColType.BOOLEAN)
    private Boolean status;

    @Column
    @Comment("读数 1")
    @ColDefine(type = ColType.DOUBLE)
    private Double val1;

    @Column
    @Comment("读数 2")
    @ColDefine(type = ColType.INT)
    private Integer val2;

    @Column
    @Comment("读数 3")
    @ColDefine(type = ColType.INT,width = 3)
    private Integer val3;

    @Column
    @Comment("读数 4")
    @ColDefine(type = ColType.INT,width = 2)
    private Integer val4;

}

完整代码见

https://gitee.com/wizzer/demo/tree/master/nutzboot-tdengine-demo
202012 月2

Java分页获取文件目录列表工具类





package com.budwk.app.base.utils;

import org.nutz.lang.util.NutMap;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Stream;

/**
 * @author wizzer@qq.com
 */
public class FileUtil {

    /**
     * 分页获取文件列表
     *
     * @param basePath   目录
     * @param pageNumber 页码
     * @param pageSize   页大小
     * @param sort       按文件名排序
     * @return 列表
     * @throws Exception
     */
    public static NutMap readListPage(String basePath, Integer pageNumber, Integer pageSize, String sort)
            throws Exception {
        int offset = (pageNumber - 1) * pageSize;
        int limit = pageNumber * pageSize;
        long total = 0;
        List<NutMap> list = new ArrayList<>();
        Comparator<Path> comparator = Comparator.naturalOrder();
        if ("desc".equals(sort)) {
            comparator = Comparator.reverseOrder();
        }
        try (Stream<Path> fileList = Files.list(Paths.get(basePath))) {
            total = fileList.count();
        }
        try (Stream<Path> fileList = Files.list(Paths.get(basePath)).sorted(comparator).skip(offset)
                .limit(limit)) {
            fileList.forEach(file -> {
                NutMap nutMap = NutMap.NEW();
                String fileName = file.getFileName().toString();
                nutMap.addv("fileName", fileName);
                if (Files.isDirectory(file.toAbsolutePath())) {
                    nutMap.addv("folder", true);
                    nutMap.addv("suffix", "folder");
                } else {
                    String suffix = fileName.substring(fileName.indexOf(".") + 1).toLowerCase();
                    nutMap.addv("folder", false);
                    nutMap.addv("suffix", suffix);
                }
                list.add(nutMap);
            });
            return NutMap.NEW().addv("total", total).addv("list", list);
        }
    }
}

202010 月28

BudWK V6 之微信支付V3开发流程

  • 一、商户后台设置V3 Key密钥及下载V3 API证书(三个文件分别为apiclient_key.pem、apiclient_cert.pem、apiclient_cert.p12)
  • 二、设计表结构实现管理功能
package com.budwk.nb.wx.models;

import com.budwk.nb.commons.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;

import java.io.Serializable;

/**
 * 微信支付配置表
 * @author wizzer@qq.com
 */
@Data
@EqualsAndHashCode(callSuper = true)
@Table("wx_pay")
public class Wx_pay extends BaseModel implements Serializable {
    private static final long serialVersionUID = 1L;
    @Column
    @Name
    @Comment("ID")
    @ColDefine(type = ColType.VARCHAR, width = 32)
    @PrevInsert(els = {@EL("uuid()")})
    private String id;

    @Column
    @ColDefine(type = ColType.VARCHAR, width = 32)
    private String name;

    @Column
    @ColDefine(type = ColType.VARCHAR, width = 32)
    private String mchid;

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

    /**
     * apiclient_key.pem 物理路径
     */
    @Column
    @ColDefine(type = ColType.VARCHAR, width = 255)
    private String v3keyPath;

    /**
     * apiclient_cert.pem 物理路径
     */
    @Column
    @ColDefine(type = ColType.VARCHAR, width = 255)
    private String v3certPath;

    /**
     * apiclient_cert.p12 物理路径
     */
    @Column
    @ColDefine(type = ColType.VARCHAR, width = 255)
    private String v3certP12Path;

   /**
     * 平台证书失效时间
     */
    @Column
    private Long expire_at;
}
package com.budwk.nb.wx.models;

import com.budwk.nb.commons.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;

import java.io.Serializable;

/**
 * 平台证书临存表
 * @author wizzer@qq.com
 */
@Data
@EqualsAndHashCode(callSuper = true)
@Table("wx_pay_cert")
@TableIndexes({@Index(name = "INDEX_WX_PAY_CERT", fields = {"mchid", "serial_no"}, unique = true)})
public class Wx_pay_cert extends BaseModel implements Serializable {
    private static final long serialVersionUID = 1L;
    @Column
    @Name
    @Comment("ID")
    @ColDefine(type = ColType.VARCHAR, width = 32)
    @PrevInsert(els = {@EL("uuid()")})
    private String id;

    @Column
    @ColDefine(type = ColType.VARCHAR, width = 32)
    private String mchid;

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

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

    @Column
    private Long effective_at;

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

    @Column
    private Long expire_at;

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

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

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

    @Column
    @ColDefine(type = ColType.TEXT)
    private String ciphertext;

    @Column
    @ColDefine(type = ColType.TEXT)
    private String certificate;
}
  • 三、封装下订单/JSAPI/平台证书更新等功能服务类
package com.budwk.nb.web.commons.ext.wx;

import com.alibaba.dubbo.config.annotation.Reference;
import com.budwk.nb.web.commons.base.Globals;
import com.budwk.nb.wx.models.Wx_pay;
import com.budwk.nb.wx.models.Wx_pay_cert;
import com.budwk.nb.wx.services.WxPayCertService;
import com.budwk.nb.wx.services.WxPayService;
import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.json.Json;
import org.nutz.lang.Strings;
import org.nutz.lang.Times;
import org.nutz.lang.util.NutMap;
import org.nutz.log.Log;
import org.nutz.log.Logs;
import org.nutz.weixin.bean.WxPay3Response;
import org.nutz.weixin.util.WxPay3Api;
import org.nutz.weixin.util.WxPay3Util;

import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.List;

/**
 * @author wizzer@qq.com
 */
@IocBean
public class WxPay3Service {
    private static final Log log = Logs.get();
    private static final SimpleDateFormat DATE_TIME_ZONE = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
    @Inject
    @Reference(check = false)
    private WxPayCertService wxPayCertService;
    @Inject
    @Reference(check = false)
    private WxPayService wxPayService;


    // 通过商户号获取 wx_pay 对象
    public synchronized Wx_pay getWxPay(String mchid) {
        Wx_pay wxPay = Globals.WxPay3Map.getAs(mchid, Wx_pay.class);
        if (wxPay == null) {
            wxPay = wxPayService.fetch(Cnd.where("mchid", "=", mchid));
            Globals.WxPay3Map.put(wxPay.getMchid(), wxPay);
        }
        checkPlatfromCerts(wxPay);
        return wxPay;
    }

    // 检查及更新平台证书机制
    public void checkPlatfromCerts(Wx_pay wxPay) {
        if (wxPay == null)
            throw new IllegalStateException("Wx_pay is null");
        if (wxPay.getExpire_at() == null || wxPay.getExpire_at() == 0 || wxPay.getExpire_at() < 8 * 3600 * 1000 + System.currentTimeMillis()) {
            getPlatfromCerts(wxPay.getMchid(), wxPay.getV3key(), wxPay.getV3keyPath(), wxPay.getV3certPath());
            wxPay = wxPayService.fetch(Cnd.where("mchid", "=", wxPay.getMchid()));
            Globals.WxPay3Map.put(wxPay.getMchid(), wxPay);
        }
    }

    // jsapi 订单下单
    public WxPay3Response v3_order_jsapi(String mchid, String body) throws Exception {
        log.debug("v3_order_jsapi body::" + body);
        String serialNo = WxPay3Util.getCertSerialNo(getWxPay(mchid).getV3certPath());
        return WxPay3Api.v3_order_jsapi(mchid, serialNo, getWxPay(mchid).getV3keyPath(), body);
    }

    // 通过jsapi 订单号生成js参数
    public NutMap v3_call_jsapi(String mchid, String appid, String prepay_id) throws Exception {
        return WxPay3Util.getJsapiSignMessage(appid, prepay_id, getWxPay(mchid).getV3keyPath());
    }

    // 验证http响应签名结果
    public boolean verifySignature(WxPay3Response wxPay3Response, String mchid) throws Exception {
        Wx_pay_cert wxPayCert = wxPayCertService.fetch(Cnd.where("mchid", "=", mchid).and("serial_no", "=", wxPay3Response.getHeader().get("Wechatpay-Serial")));
        return WxPay3Util.verifySignature(wxPay3Response, wxPayCert.getCertificate());
    }

    // 验证回调通知签名及内容
    public String verifyNotify(String mchid, String serialNo, String body, String signature, String nonce, String timestamp) throws Exception {
        Wx_pay_cert wxPayCert = wxPayCertService.fetch(Cnd.where("mchid", "=", mchid).and("serial_no", "=", serialNo));
        return WxPay3Util.verifyNotify(serialNo, body, signature, nonce, timestamp,
                getWxPay(mchid).getV3key(), wxPayCert.getCertificate());
    }

    /**
     * 请求并保存新证书
     *
     * @param mchid
     * @return
     */
    public void getPlatfromCerts(String mchid, String v3Key, String v3KeyPatch, String v3CertPath) {
        try {
            wxPayCertService.clear(Cnd.where("mchid", "=", mchid).and("expire_at", "<", System.currentTimeMillis()));
            String serialNo = WxPay3Util.getCertSerialNo(v3CertPath);
            WxPay3Response wxPay3Response = WxPay3Api.v3_certificates(mchid, serialNo, v3KeyPatch);
            if (wxPay3Response.getStatus() == 200) {
                NutMap nutMap = Json.fromJson(NutMap.class, wxPay3Response.getBody());
                List<NutMap> list = nutMap.getList("data", NutMap.class);
                for (NutMap cert : list) {
                    Wx_pay_cert wxPayCert = new Wx_pay_cert();
                    wxPayCert.setMchid(mchid);
                    wxPayCert.setEffective_time(cert.getString("effective_time"));
                    wxPayCert.setExpire_time(cert.getString("expire_time"));
                    long expire_at = 0;
                    try {
                        expire_at = Times.parse(DATE_TIME_ZONE, cert.getString("expire_time")).getTime();
                        wxPayCert.setEffective_at(Times.parse(DATE_TIME_ZONE, cert.getString("effective_time")).getTime());
                        wxPayCert.setExpire_at(expire_at);
                    } catch (ParseException e) {
                        e.printStackTrace();
                    }
                    wxPayCert.setSerial_no(cert.getString("serial_no"));
                    NutMap encrypt_certificate = cert.getAs("encrypt_certificate", NutMap.class);
                    wxPayCert.setAlgorithm(encrypt_certificate.getString("algorithm"));
                    wxPayCert.setAssociated_data(encrypt_certificate.getString("associated_data"));
                    wxPayCert.setCiphertext(encrypt_certificate.getString("ciphertext"));
                    wxPayCert.setNonce(encrypt_certificate.getString("nonce"));
                    String platformCertificate = WxPay3Util.decryptToString(v3Key.getBytes(StandardCharsets.UTF_8),
                            encrypt_certificate.getString("associated_data").getBytes(StandardCharsets.UTF_8),
                            encrypt_certificate.getString("nonce").getBytes(StandardCharsets.UTF_8),
                            encrypt_certificate.getString("ciphertext")
                    );
                    wxPayCert.setCertificate(platformCertificate);
                    try {
                        wxPayCertService.insert(wxPayCert);
                    } catch (Exception e) {
                        //重复的插入会报错,不管它
                    }
                }
                Wx_pay_cert wxPayCert = wxPayCertService.fetch(Cnd.where("mchid", "=", mchid).orderBy("effective_at", "desc"));
                if (wxPayCert != null) {
                    wxPayService.update(Chain.make("expire_at", wxPayCert.getExpire_at()), Cnd.where("mchid", "=", mchid));
                }
            }
        } catch (Exception e) {
            log.errorf("获取平台证书失败,mchid=%s", mchid, e);
        }
    }
}
  • 四、小程序支付业务代码
@Test
    public void test_v3_order() throws Exception {
        String orderPayNo = R.UU32();
        String orderId = R.UU32();
        NutMap wxPayUnifiedOrder = NutMap.NEW();
        wxPayUnifiedOrder.addv("appid", appid);
        wxPayUnifiedOrder.addv("mchid", mchid);
        wxPayUnifiedOrder.addv("description", new String(("LaiShop-order-" + orderId).getBytes(), StandardCharsets.UTF_8));
        wxPayUnifiedOrder.addv("out_trade_no", orderPayNo);
        Date now = new Date();
        wxPayUnifiedOrder.addv("time_expire", DateUtil.getDateAfterMinute(now, 30));
        // 回调通知URL传递mchid商户号,便于系统支持接入N个小程序及支付商户账号
        wxPayUnifiedOrder.addv("notify_url", Globals.AppDomain + "/shop/open/wxpay/" + mchid + "/notify");
        wxPayUnifiedOrder.addv("amount", NutMap.NEW().addv("total", 1).addv("currency", "CNY"));
        wxPayUnifiedOrder.addv("payer", NutMap.NEW().addv("openid", "o9Bnd4lXKfNsOci-6H98zCMWyBps"));
        String body = Json.toJson(wxPayUnifiedOrder);
        System.out.println("body::" + body);
        WxPay3Response wxPay3Response = wxPay3Service.v3_order_jsapi(mchid, body);
        System.out.println("wxPay3Response::" + Json.toJson(wxPay3Response));
        boolean verifySignature = wxPay3Service.verifySignature(wxPay3Response, mchid);
        System.out.println("verifySignature::" + verifySignature);
        NutMap v3order = Json.fromJson(NutMap.class, wxPay3Response.getBody());
        NutMap resp = wxPay3Service.v3_call_jsapi(mchid, appid, v3order.getString("prepay_id"));
        System.out.println("resp::" + Json.toJson(resp));
    }
  • 五、回调通知业务代码
package com.budwk.nb.web.controllers.open.pay;

import com.alibaba.dubbo.config.annotation.Reference;
import com.budwk.nb.web.commons.ext.wx.WxPay3Service;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.servers.Server;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.json.Json;
import org.nutz.lang.Streams;
import org.nutz.lang.util.NutMap;
import org.nutz.log.Log;
import org.nutz.log.Logs;
import org.nutz.mvc.adaptor.VoidAdaptor;
import org.nutz.mvc.annotation.AdaptBy;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Reader;
import java.nio.charset.StandardCharsets;

/**
 * @author wizzer@qq.com
 */
@IocBean
@At("/shop/open/wxpay")
@Ok("json")
@OpenAPIDefinition(tags = {@Tag(name = "商城_微信支付回调")}, servers = @Server(url = "/"))
public class WxPay3NotifyController {
    private static final Log log = Logs.get();
    @Inject
    private WxPay3Service wxPay3Service;

    @At("/{mchid}/notify")
    @Ok("raw")
    @AdaptBy(type = VoidAdaptor.class)
    public void notify(String mchid, Reader reader, HttpServletRequest req, HttpServletResponse resp) throws IOException {
        try {
            NutMap map = NutMap.NEW();
            String timestamp = req.getHeader("Wechatpay-Timestamp");
            String nonce = req.getHeader("Wechatpay-Nonce");
            String serialNo = req.getHeader("Wechatpay-Serial");
            String signature = req.getHeader("Wechatpay-Signature");
            log.debugf("timestamp=%s,nonce=%s,serialNo=%s,signature=%s", timestamp, nonce, serialNo, signature);
            String body = Streams.readAndClose(reader);
            // 需要通过证书序列号查找对应的证书,verifyNotify 中有验证证书的序列号
            String plainText = wxPay3Service.verifyNotify(mchid, serialNo, body, signature, nonce, timestamp);
            log.debugf("支付通知明文=%s", plainText);
            NutMap res = Json.fromJson(NutMap.class, plainText);
            NutMap payer = res.getAs("payer", NutMap.class);
            String trade_state = res.getString("trade_state");
            String out_trade_no = res.getString("out_trade_no");
            String openid = payer.getString("openid");
            boolean ok = true;//业务代码入库
            if ("SUCCESS".equals(trade_state) && ok) {
                resp.setStatus(200);
                map.put("code", "SUCCESS");
                map.put("message", "SUCCESS");
            } else {
                resp.setStatus(500);
                map.put("code", "ERROR");
                map.put("message", "签名错误");
            }
            resp.setHeader("Content-type", "application/json");
            resp.getOutputStream().write(Json.toJson(map).getBytes(StandardCharsets.UTF_8));
            resp.flushBuffer();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

本文档以 BudWk 框架代码为例,源码地址: https://gitee.com/budwk/budwk-nutzboot

演示地址: https://demo.budwk.com

20209 月12

NutzWk 5.2.7 发布,Java 国产微服务分布式开发框架

本次 v5.2.7 发布带来三个版本:

  • v5.2.7-nacos Nacos 注册中心及配置中心功能微服务版本
  • v5.2.7-zookeeper Zookeeper 注册中心微服务版本
  • v5.2.7-mini 单应用一个 Jar 或 War 即可启动运行版本

技术体系

后端技术:nutzboot(国产,基于国产nutz) + dubbo + redis + shiro + quartz + logback + beetl 等主流技术
前端技术:jquery + vue.js + elementUI 和 jquery + bootstrap 两个版本可选

演示地址

V6演示地址: https://demo.budwk.com (前后端分离 nuxt+vue+elementUI)

V5演示地址: https://nutzwk.wizzer.cn (beetl+html+vue.js+elementUI)

项目介绍

NutzWk(V6起更名为BudWk)发展自2010年,2012年开始用于商业项目,至今已服务于全国各地公司大大小小上千个项目,行业涉及政务、电商、物联网等,随着经验积累及从事行业的不同分别发布了v1.x至v6.x多个版本,每个版本都是完整运行且完全开源免费的,您可以根据项目规模选择不同版本。本项目案例众多,省厅级项目、市级平台、大数据项目、电商平台、物联网平台等等,详见官网 https://budwk.com

源码地址:

V6源码地址: https://gitee.com/budwk/budwk-nutzboot

V1-V5源码地址: https://gitee.com/wizzer/NutzWk

20208 月21

BudWk 国产开源Java微服务分布式框架在智慧燃气行业的应用

燃气公司现状及痛点

  • 燃气表品牌多、型号多、计费类型多,厂家附送系统各自独立且无法自动对账,导致账目误差时有出现,实际经营情况无法实时掌握。
  • 物联网智能表具原来越多,物联网设备本身的安全监测是当前的核心问题,且因设备厂家、型号多样化,设备的统一接入、监控就尤为重要。
  • 从市场拓展到客户服务无法全流程业务管控、联动和监督,导致物资储备、施工安排、工程监督等无法根据实际情况实时管控,用户开户、移表等服务无法高效响应和调度。

BudWk微服务解决方案

  • IC卡表的统一集成

使用 WPF + CefSharp 技术,C/S客户端 + B/S浏览器的组合,利用客户端实现IC卡读写器的集成开发,实现IC卡表的读写功能,利用B/S浏览器,将营收系统嵌入浏览器,实现WEB营收业务代码热更新、数据统一管理等功能。

  • 物联网表的统一集成

抽象设备接入层,实现电信AEP平台、移动OneNET、厂家物联网平台等平台接入,实现 NB-IOT/MQTT/HTTP 等协议的适配和转换,将各表厂繁杂不一的数据格式转换为本平台统一数据格式,并利用规则引擎技术,实现数据的智能化处理。

  • 计费类型的统一集成

系统内置预付费、后付费、表端计费等计费类型,支持“购气/退气”和“充值/退费”等业务形态,支持阶梯计价、区域计价等价格规则。

  • 工单系统的统一集成

燃气报装、报修、维修、安检等业务流程标准化、制度化,通过流程配置、节点配置、权限配置等,实现业务工单的动态分配和统一管理。

后端技术框架

采用 BudWk 国产微服务分布式架构,基于 nutzboot + dubbo + nacos + druid 技术体系,核心框架为国产开源框架 nutzboot,采用 Sa-Token权限系统及JWT。根据业务划分微服务模块,如:

  • Sys – 系统及权限模块
  • Cms – 内容及资讯模块
  • Wx – 微信服务模块
  • Dev – 物联网表接入模块
  • Gas – 营收业务模块
  • WebAPI – 后端服务API模块
  • OpenAPI – 第三方服务API模块

前端技术框架

采用 Vite + Vue3 + ElementPlus 常用组合,前后端分离开发模式,封装集成多语言、路由、权限控制、文件上传等功能。

BudWk微服务分布式框架介绍

BudWk(原名NutzWk)发展自2010年,2012年开始用于商业项目,至今已服务于全国各地公司大大小小数千个项目,行业涉及政务、电商、物联网等,随着个人经验积累及从事行业的不同分别发布了1.x至8.x多个版本,您可以根据项目规模选择不同版本。本项目案例众多,省厅级项目、市级平台、大数据项目、电商平台、物联网平台等等。

https://demo.budwk.com V8演示地址

https://nutzwk.wizzer.cn V5演示地址

https://budwk.com 官网及开发指南

20198 月2

NutzWk 5.2.6 及 5.2.6-mini 发布,Java 微服务分布式开发框架

项目介绍:

NutzWk 是有五年多历史的Java Web开源开发框架,其5.x 是Java 微服务分布式版本,采用nutzboot(nutz核心)、dubbo、redis、zookeeper、shiro、quartz、beetl、logback、vue、sentinel(流控框架,可选)、seata(分布式事务,可选) 等开源技术的微服务分布式版本,自带系统管理、简易CMS、微信模块、定时任务、服务API等功能,目前已应用于全国各地上千个各类商业项目中。

演示地址(Vue版本): https://nutzwk.wizzer.cn

后端技术架构:nutzboot(国产,基于国产nutz) + dubbo(国产) + redis + zookeeper + shiro + quartz + logback 等主流技术
前端技术架构:vue+ element

NutzWk v5.2.6 更新内容:

  • 修改错误页机制,将前后台404/403/505 错误页严格区分;
  • 修改POJO类,引入 @PrevInsert 注解,在执行fastInsert数据快速入库时有效;
  • 修改web项目,Globals类 Map 对象改为 NutMap 方便开发;
  • 修改vue后台,系统管理中的页面弹出框,改为手动关闭;
  • 修复vue后台,数据字典管理的排序问题;
  • 其他功能的完善,vue页面优化等;
  • 相关jar包及第三方依赖的版本升级;

NutzWk v5.2.6-mini 版本发布:

  • 具有v5.2.6 全部功能的微服务mini版本(系统管理/CMS管理/微信管理/API接口等功能齐全);
  • 管理后台 vue + element;
  • 非分布式,一个jar包即可运行(无dubbo依赖、无需zookepper,只需安装数据库+redis);
  • 非常适合小型项目快速开发;
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

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的高可用
20151 月28

Nutz:通过Java代码生成表格图片

效果图:

i

源码不解释:

 private void ResultToJpg(List<Map> list, String title, HttpServletResponse response) {
        BufferedImage image;
        int totalrow = list.size();
        int totalcol = 7;
        int imageWidth = 640;
        int rowheight = 40;
        int imageHeight = totalrow * rowheight + 105;
        int startHeight = 20;
        int startWidth = 10;
        int colwidth = (int) ((imageWidth - 20) / totalcol);
        int colwidth2 = (int) ((imageWidth - 20) / (totalcol + 1));
        image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_RGB);
        Graphics2D graphics = (Graphics2D) image.getGraphics();
        graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        graphics.setColor(Color.WHITE);
        graphics.fillRect(0, 0, imageWidth, imageHeight);
        Font font = new Font("微软雅黑", Font.BOLD, 20);
        graphics.setFont(font);
        graphics.setColor(new Color(112, 48, 158));
        graphics.drawString(Strings.sNull(title), startWidth, startHeight + 10);
        font = new Font("微软雅黑", Font.PLAIN, 13);
        graphics.setColor(new Color(99, 99, 99));
        graphics.setFont(font);
        String subtitle1 = "欢迎访问";
        graphics.drawString(subtitle1, startWidth, startHeight + 30);
        font = new Font("Arial, Helvetica, sans-serif", Font.BOLD, 13);
        graphics.setColor(new Color(112, 48, 158));
        graphics.setFont(font);
        String subtitle2 = "inm.xuetang.cn";
        graphics.drawString(subtitle2, startWidth + 60, startHeight + 30);
        font = new Font("微软雅黑", Font.PLAIN, 13);
        graphics.setColor(new Color(99, 99, 99));
        graphics.setFont(font);
        String subtitle3 = "冲榜、制榜!";
        graphics.drawString(subtitle3, startWidth + 165, startHeight + 30);
        //画首行
        graphics.setColor(new Color(112, 48, 158));
        graphics.fillRect(startWidth, startHeight + rowheight, imageWidth - 20, rowheight);
        font = new Font("微软雅黑", Font.PLAIN, 12);
        graphics.setColor(Color.WHITE);
        graphics.setFont(font);
        graphics.drawString("#", startWidth + 10, startHeight + rowheight * 2 - 15);
        graphics.drawString("公众号", startWidth + colwidth - 10, startHeight + rowheight * 2 - 15);
        //画表头
        String[] headCells = {"发布", "总阅读数", "  头条", " 平均 ", "总点赞数", " WCI"};
        for (int m = 0; m < headCells.length; m++) {
            graphics.drawString(headCells[m].toString(), startWidth + colwidth2 * m + colwidth * 2, startHeight + rowheight * 2 - 15);
        }
        //画行
        for (int j = 0; j < totalrow; j++) {
            if (j % 2 == 0) {
                graphics.setColor(new Color(255, 255, 255));
            } else {
                graphics.setColor(new Color(238, 238, 238));
            }
            graphics.fillRect(startWidth, startHeight + (j + 1) * rowheight + 40, imageWidth - 20, startHeight + (j + 1) * rowheight);
            font = new Font("微软雅黑", Font.BOLD, 12);
            graphics.setColor(new Color(112, 48, 158));
            graphics.setFont(font);
            graphics.drawString(String.valueOf(j + 1), startWidth + 10, startHeight + (j + 3) * rowheight - 15);
            Map map = list.get(j);
            font = new Font("微软雅黑", Font.PLAIN, 12);
            graphics.setColor(new Color(0, 0, 0));
            graphics.setFont(font);
            graphics.drawString(Strings.sNull(map.get("wx_nickname")), startWidth + (colwidth / 2) - 10, startHeight + (j + 3) * rowheight - 25);
            graphics.drawString(Strings.sNull(map.get("wx_name")), startWidth + (colwidth / 2) - 10, startHeight + (j + 3) * rowheight - 10);
            graphics.drawString(Strings.sNull(map.get("url_times")) + "/" + Strings.sNull(map.get("url_num")), startWidth + colwidth2 + colwidth + 10, startHeight + (j + 3) * rowheight - 15);
            if (NumberUtils.toInt(Strings.sNull(map.get("url_num_10w"))) > 0) {
                String readnum_all = Strings.sNull(map.get("readnum_all"));
                int t = NumberUtils.toInt(readnum_all.substring(readnum_all.length() - 4, readnum_all.length() - 3));
                if (t > 0) {
                    readnum_all = readnum_all.substring(0, readnum_all.length() - 4) + "." + String.valueOf(t);
                } else {
                    readnum_all = readnum_all.substring(0, readnum_all.length() - 4);
                }
                graphics.drawString(readnum_all + "万+", startWidth + colwidth2 + colwidth * 2 + 5, startHeight + (j + 3) * rowheight - 15);
            } else {
                graphics.drawString(Strings.sNull(map.get("readnum_all")), startWidth + colwidth2 + colwidth * 2 + 5, startHeight + (j + 3) * rowheight - 15);
            }
            if (NumberUtils.toInt(Strings.sNull(map.get("url_times_readnum"))) > 100000) {
                String url_times_readnum = Strings.sNull(map.get("url_times_readnum"));
                int t = NumberUtils.toInt(url_times_readnum.substring(url_times_readnum.length() - 4, url_times_readnum.length() - 3));
                if (t > 0) {
                    url_times_readnum = url_times_readnum.substring(0, url_times_readnum.length() - 4) + "." + String.valueOf(t);
                } else {
                    url_times_readnum = url_times_readnum.substring(0, url_times_readnum.length() - 4);
                }
                graphics.drawString(url_times_readnum+"万+", startWidth + colwidth2 + colwidth * 3 - 5, startHeight + (j + 3) * rowheight - 15);
            } else {
                graphics.drawString(Strings.sNull(map.get("url_times_readnum")), startWidth + colwidth2 + colwidth * 3 - 5, startHeight + (j + 3) * rowheight - 15);
            }
            if (NumberUtils.toInt(Strings.sNull(map.get("readnum_av"))) > 100000) {
                String readnum_av = Strings.sNull(map.get("readnum_av"));
                int t = NumberUtils.toInt(readnum_av.substring(readnum_av.length() - 4, readnum_av.length() - 3));
                if (t > 0) {
                    readnum_av = readnum_av.substring(0, readnum_av.length() - 4) + "." + String.valueOf(t);
                } else {
                    readnum_av = readnum_av.substring(0, readnum_av.length() - 4);
                }
                graphics.drawString(readnum_av+"万+", startWidth + colwidth2 + colwidth * 4 - 15, startHeight + (j + 3) * rowheight - 15);
            } else {
                graphics.drawString(Strings.sNull(map.get("readnum_av")), startWidth + colwidth2 + colwidth * 4 - 15, startHeight + (j + 3) * rowheight - 15);

            }
            graphics.drawString(Strings.sNull(map.get("likenum_all")), startWidth + colwidth2 + colwidth * 5 - 20, startHeight + (j + 3) * rowheight - 15);
            graphics.drawString(Strings.sNull(map.get("wci")), startWidth + colwidth2 + colwidth * 6 - 40, startHeight + (j + 3) * rowheight - 15);
        }
        //末行
        graphics.setColor(Color.WHITE);
        graphics.fillRect(startWidth, startHeight + (totalrow + 1) * rowheight + 40, imageWidth - 20, startHeight + (totalrow + 1) * rowheight);
        try {
            response.setContentType("image/png");
            OutputStream out = response.getOutputStream();
            ImageIO.write(image, "png", out);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

 

201411 月14

Java:数字补零

String str = String.format(“%010d”, 1212);

20149 月23

NutzWk 企业级WEB后台开发框架开源了

NutzWk

https://github.com/Wizzercn/NutzWk

基于Nutz的开源企业级开发框架。

文件编码全部为UTF-8,可以导入Eclispe、IDEA中,jdk7,tomcat 6/7.
创建空的数据库,首次启动项目会自动初始化数据.

 

本框架已成功应用于XX省交通厅网络问政平台、XX省交通厅CMS内容管理系统、XX公司舆情监测管理中心等项目。

使用条款:
1、个人开源,可以任意修改使用;
2、商业使用,必须更改后台菜单布局、CSS样式、界面颜色等元素(既:不可使用原始界面用于商业项目)。

20148 月26

Java:乱码字符不能插入MySQL的解决办法

 

 

.replaceAll(“[^a-zA-Z_\u4e00-\u9fa5]”, “”)

 

只剩下中文和英文字母了,悲催。

 

 

Caused by: java.sql.SQLException: Incorrect string value: ‘\xF0\x9F\x98\xB7’ for column ‘description’ 

20147 月8

JAVA:网易微博模拟登陆

网易微博登陆验证,第一次请求使用BASE64加密、第二次请求使用MD5+RSA加密,比较变态,于是使用JAVA+JS相结合的方式,调用其JS方法得到加密字符串。

/core1.7.0.js 是经过处理的,删掉几行在JAVA引用中会报错的浏览器对象。

 

import org.apache.http.HttpResponse;
import org.apache.http.client.CookieStore;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.velocity.util.StringUtils;
import org.nutz.lang.Files;
import org.nutz.lang.util.ClassTools;
import org.nutz.repo.Base64;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;

/**
 * Created by Wizzer on 14-7-7.
 */
public class Netease {
    static String index_url = "http://t.163.com/session";
    static String login1_url = "http://reg.163.com/services/httpLoginExchgKeyNew";
    static String login2_url = "http://reg.163.com/httpLoginVerifyNew.jsp";
    static String status_url = "http://t.163.com/share/check/status";
    UrlUtil urlUtil = new UrlUtil();

    public static void main(String[] args) {
        CookieStore cookieStore = new Netease().login("email", "password");
    }

    public CookieStore login(String userid, String password) {
        try {
            DefaultHttpClient client = new DefaultHttpClient();
            HttpGet get = new HttpGet(login1_url + "?rnd=" + Base64.encodeToString(userid.getBytes(), true) + "&jsonp=setLoginStatus");
            get.setHeader("Accept", "*/*");
            get.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36");
            HttpResponse response = client.execute(get);
            int code = response.getStatusLine().getStatusCode();
            if (code == 200) {
                InputStream in = response.getEntity().getContent();
                BufferedReader reader = new BufferedReader(new InputStreamReader(in));
                String line = "", res = "";
                while (null != (line = reader.readLine())) {
                    res += line;
                }
                System.out.println("res:::" + res);
                if (res.contains("200")) {
                    String[] str = StringUtils.split(urlUtil.getStr(res, "setLoginStatus(\"", "\")"), "\\n");
                    String o = str[1], h = str[2];
                    ScriptEngineManager sem = new ScriptEngineManager();
                    ScriptEngine se = sem.getEngineByName("javascript");
                    se.eval(getJs());
                    String jiami = "";
                    if (se instanceof Invocable) {
                        Invocable invoke = (Invocable) se;
                        jiami = invoke.invokeFunction("getCode",
                                password, o, h).toString();

                        System.out.println("jiami = " + jiami);
                    }

                    DefaultHttpClient client2 = new DefaultHttpClient();
                    client2.setCookieStore(client.getCookieStore());
                    HttpGet get2 = new HttpGet(login2_url + "?rcode=" + jiami + "&product=t&jsonp=setLoginStatus&savelogin=0&username=" + userid);
                    get2.setHeader("Accept", "*/*");
                    get2.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36");
                    HttpResponse response2 = client2.execute(get2);
                    int code2 = response2.getStatusLine().getStatusCode();
                    if (code2 == 200) {
                        InputStream in2 = response2.getEntity().getContent();
                        BufferedReader reader2 = new BufferedReader(new InputStreamReader(in2));
                        String line2 = "", res2 = "";
                        while (null != (line2 = reader2.readLine())) {
                            res2 += line2;
                        }
                        System.out.println("res2:::" + res2);
                        if (res.contains("200")) {
                            return client2.getCookieStore();
                        }
                    }
                }
            }
            return null;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    private String getJs() {
        String jscontent = Files.read(ClassTools.getClassLoader().getResource("").getPath() + "netease" + "/core1.7.0.js");
        jscontent += "function getCode(p,o,h){\n" +
                "\t\t\t\tvar l=new RSAKey();\n" +
                "\t\t\t\tl.setPublic(h,o);\n" +
                "\t\t\t\treturn l.encrypt(getMd5(p));\t\t\t\t\n" +
                "   }";
        return jscontent;
    }
}
20145 月16

Linux:后台执行Java类

编写执行文件 job.sh

#!/bin/sh
CLASSPATH=classes:lib/druid-1.0.1.jar:lib/htmlparser-1.6.jar:lib/log4j-1.2.17.jar:lib/mysql-connector-java-5.1.26-bin.jar:lib/nutz-1.b.50.jar:lib/ojdbc14.jar:lib/quartz-2.2.1.jar:lib/quartz-jobs-2.2.1.jar:lib/slf4j-api-1.6.6.jar:lib/slf4j-log4j12-1.6.6.jar:lib/sqljdbc4.jar:lib/commons-pool2-2.2.jar:
#export CLASSPATH
java -classpath $CLASSPATH cn.xuetang.job.MyJob &

 赋予job.sh 执行权限

chmod a+x job.sh

后台执行,并把标准输出重定向,关闭终端仍在执行

nohup bash /home/work/ImageTask/job.sh > success.log 2>error.log &

输出在当前控制台,关闭该终端时,将退出启动的进程

/home/work/ImageTask/job.sh

每两分钟启动一次,关闭终端不影响任务

crontab -e

*/2 * * * * /home/work/ImageTask/job.sh > success.log 2>error.log &

201311 月26

Nutz开发日志

计划基于Nutz开发一套全新的系统管理-权限框架。界面演示:/?p=2586
之前尝试Nutz做过一个demo,但有很多不足,比如没有做防SQL注入、不能兼容多数据库、列表没有实现Ajax等。

基于多年来开发过程接触的各式各样的框架,想整一套开发方便UI简洁的框架,要求有如下功能:
1.开发简便、框架结构清晰
2.性能优越、兼容主流数据库、兼容各版本浏览器
3.UI简洁、开发简单、主流JS插件集成、全Ajax
4.集成权限框架、单点登陆、登陆日志、操作日志
5.工作流引擎、表单自定义
6.代码生成器

JS引入:
jquery-1.10.2.min.js
ueditor-1.2.6.1
My97Datepicker-4.8 Beta4
zDialog-2.0
ztree-3.5.15

框架引入:
Nutz-1.49.r2
log4j-1.2.17
json-1.0
c3p0-0.9.1.2
classes12
mysql-connector-java-5.1.14
ojdbc14
slf4j-api-1.6.1
slf4j-log4j12-1.6.1
sqljdbc4
velocity-1.7
velocity-tools-2.0
velocity-tools-generic-2.0
velocity-tools-view-2.0
json-lib-2.4-jdk15
ezmorph-1.0.6
commons-beanutils-1.7.0
commons-chain-1.1
commons-collections-3.2
commons-digester-1.8
commons-lang-2.2
commons-logging-1.1
commons-validator-1.3.1
待更新。

——————————————————–
在Eclipse中处理图片,需要引入两个包:
import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGImageEncoder;
报错:
Access restriction: The type JPEGImageEncoder is not accessible due to restriction on required library C:\Java\jre1.6.0_07\lib\rt.jar

此时解决办法:
Eclipse默认把这些受访问限制的API设成了ERROR。只要把Windows-Preferences-Java-Complicer-Errors/Warnings里面的Deprecated and restricted API中的Forbidden references(access rules)选为Warning就可以编译通过。

—————————————————————
Nutz开发说明:

您一定了解SSH,还有Strust1+Velocity框架

2013年11月30日(星期六) 晚上7:03给我写信

您一定了解SSH,还有Strust1+Velocity框架。前者配置文件实在是太多,多层架构,开发一个功能模块涉及很多类、很多关系,一方面上手非常难另外开发效率也不高。后者随着技术发展,对前端技术支持不太友好,比如AjaxJS框架等,需要中间跳转页面进行输出,再者也需要配置。

根据项目开发中实际遇到的各种问题,研究各种框架优缺点之后,决定选择Nutz国产开源框架作为基础来构建本开发框架。选择Nutz有如下优点:

1URL“零”配置

您再也不用为繁琐的配置文件烦恼了,Nutz-Mvc使用全注解的方式来封装URL,使用简单的@At 在类或方法上实现URL映射。

2、灵活的多数据源支持

通过Nutz.Ioc您只要在数据库配置文件datasource.json中配置对象,如dao1dao2,那么在Action类中,

@Inject

    protected Dao dao1;

@Inject

protected Dao dao2;

即可使用daoCtl的方法直接操作这两个数据库。daoCtl里集成了常用的添加、修改、查询、分页等方法。

3、功能强大的视图

Nutz-Mvc使用@Ok @Fail 注解,来实现执行成功或失败后的输出,可以外部重定向、内部重定向、返回字符串、返回JSON等等,总之您想要什么结果就能给您返回什么,再也不用使用单独的Action类来封装JSON数据了,您想要随时都可以有。

4、功能强大的数据库操作

Nutz.Dao封装了常用的数据库操作,减少自定义SQL的编写,提高开发效率和安全性,通过工具类

Cnd.where(“loginname”, “=”,
user.getLoginname())

        Sql sql = Sqls.create("DELETE FROM $table WHERE name=@name");

sql.vars().set(“table”,”t_abc”);

sql.params().set(“name”,”Peter”);

中的sql.params().set() 方法,可以有效的防止SQL注入。

更多使用方法请参考本目录下的nutz开发手册。

5Velocity

对于大家常用的Velocity模板语言,本框架也进行了整合。

req.setAttribute(“menulist”,
menulist);

将对象输出到页面,在页面上即可直接使用模板语言输出。

 

UI表现层上面,本次框架也进行了调整,原则上支持纯Ajax开发。后台集成jQuery EasyUIzDialogEasyUI.DataGridztreeMy97DatePickerJS组件。

1、兼容主流浏览器

您再也不用考虑浏览器兼容性问题,只要您的JS全部使用jQuery来编写,那么还用担心什么呢,本框架目前在IE6/IE7/IE8/IE9/IE10/IE11ChromeFrieFox360测试通过。

2zDialog的使用更加简便

通过使用Ajax以及对zDialog的优化,您再也不需要写中间页面进行页面跳转了。您可以在弹出框的父页面进行弹出表单的提交,在父页面写弹出框按钮的事件。

通过$DW.$(“#form1”).serialize();  获取弹出窗口表单内容

         $DW.$Z(“name”).focus();     操作弹出框内容,赋值等

         $D.close();                                  关闭弹出窗口

让我们看一个完成的示例:

function addSave(){ 



if($DW.Verify.hasError()){


return;


}


jQuery.ajax({


type:
‘POST’,


url :
“/private/sys/unit/addSave” ,


data :
$DW.$(“#form1”).serialize(),


success :
function (res) {


if(res!=””){


$D.close();


Dialog.alert(“
添加成功!“);


initTree();


view(res);


}else{


Dialog.alert(“
添加失败!“);


}



return
false;


},


fail :
function(res) {


Dialog.alert(“
系统错误?!”);


}


});


}

3Ajax分页支持

集成jQuery.Easyui.DataGrid,从后台封装的JSON数据格式,到前台展示,分页查询从来没有如此简便过。

daoCtl.getListPageJson()
封装了easyui.datagrid分页所需格式数据,更不用您手动写烦人的JSON了。

4、权限控制到按钮

您只需要在JS中使用

              Page.initBtn();


$!initBtn

即可控制页面按钮是否可用,当然,在此之前,您需要在资源菜单管理中添加按钮ID,在角色管理中对用户分配按钮权限。