作者存档
20215 月12

Vue ElmentUI 趟过的坑-1

1、el-tabs 中放置 el-tree 使用 this.$ref[‘tree’] 获取不到对象

使用 this.$ref[‘tree’][0] 可以取到

2、el-cascader 动态加载行政区划,修改表单时无法初始化选中的值

需要设置属性 :key=”xx” ,在数据加载完成后让 xx ++ 自加1


					
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
20213 月24

BudWK V6 代码生成器 IDEA 插件发布

本插件不同于V5代码生成器插件,无须引入项目中其他jar包,无须事先编译POJO类:

  • 插件不依赖任何第三方jar包
  • 通过 POJO 类生成接口类、接口实现类、控制类
  • IDEA 须从项目根目录打开加载项目(以获取正确的 projectBasePath )
  • 打开 POJO 类Java文件,在文件内部右击选择”Generate”->”WkCodeGenerator”

插件下载:
https://gitee.com/budwk/budwk-codegenerator/releases

插件源码:

https://gitee.com/budwk/budwk-codegenerator
https://github.com/budwk/budwk-codegenerator
20213 月12

M1 MacBook Pro 常用知识

1、终端设置代理

export http_proxy=socks5://127.0.0.1:1080
export https_proxy=socks5://127.0.0.1:1080

2、安装Homebrew

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

3、Spotlight 索引占有CPU过高

关闭

sudo mdutil -a -i off

打开

sudo mdutil -a -i on

20211 月13

MacBook Pro M1 真香

之前,长期是 ThinkPad 忠实用户,从 X230 、X1 2016直到X1 2019。

X1 2019 太让人失望了(挂闲鱼卖了),首先配置是 i7 10710u + 16G + 1T + 4K,CPU 6核12线程,单核起步频率1.1GHz最高4.7GHz。表现起来还没 X1 2016款来的快,因为它动态调整频率,睿频上下波动。

撸码编译时风扇爆转,吹出的热气烫手,而且风扇口设计不合理,刚好对着鼠标位置的手面吹。夜里稍微多开个任务,那风扇的噪音能把娃吵醒,电池撑死能用两小时,最后20%电量掉电非常快。4K屏幕就是个鸡肋,GPU比较弱,占用资源不说玩LOL掉帧厉害,平时也没啥用途。

用 X1 2019 最悲惨得一次是,死机后SSD硬盘居然坏了,售后免费更换新的,但十几年的数据都丢了(好在部分重要资料有备份),害得我不得不上了群晖 NAS 以保万无一失。自从用 X1 2019, 蓝屏 2 次,死机N次,用得太心累了,提心吊胆的,刚巧看到M1性能评测那么高,下定决心换之。

自从换了 M1,那感受完全不一样,首先性能爆表,编译或玩游戏,CPU占用在10-20%之间,很少超过20%,偶尔一次50%。CPU占用不光低,关键风扇还不转,都不用动一下去散热的!底部外壳冰冷冰冷的,看来也只能通过处理4K视频压CPU来暖手了?

Mac系统上的优势就更不用说了,首先不用去磁盘分区,不用考虑C盘分多少,够不够用啊,D盘分多少?不用关机,X1 2019 Windows 10要等半天电脑才能关掉,有时候很关不掉要强制关机,Mac合上盖子拎起就走。有一次X1睡眠放包里,回到家包里温度很烫都快炸了都感觉,打开一看电量所剩无几,而Mac完全不会。

说到电池,M1的电池非常给力,看两小时视频+浏览一小时网页,玩了一个晚上还剩余82%的电量,以后下班不用带电源线回家了。

软件方面,目前M1用于Java开发足够了,相信后面原生支持M1的应用会越来越多,那时候M1性能可以充分发挥出来。

20211 月12

MacBook Pro M1 环境变量maven配置

使用root权限修改文件

sudo vi ~/.bash_profile

vi 操作命令省略,插入如下内容

export MAVEN_HOME=/Users/wizzer/work/server/apache-maven-3.6.3

export PATH=$PATH:$MAVEN_HOME/bin

只读文件强制保存

:wq!

source ~/.bash_profile

解决新窗口和重启找不到mvn命令的问题:

touch ~/.zshrc

open ~/.zshrc

文件内容如下,command+S保存

source ~/.bash_profile

202012 月23

el-cascader 级联选择框懒加载的回显

 <el-cascader ref="area" v-model="infoForm.area" :props="cascaderProps" style="width: 500px;"
                                     size="small" placeholder="请选择区域"></el-cascader>

cascaderProps: {
                    lazy: true,
                    value: 'id',
                    label: 'text',
                    lazyLoad: function (node, resolve) {
                        if (node.level === 0) {
                            var url = base + "/assets/platform/plugins/zoning/0.json";
                            $.get(url, function (d) {
                                resolve(d);
                            }, "json");
                        } else {
                            var pidaspath = node.data.id > 0 ? (node.data.id.substring(0, 2) + "/" + node.data.id) : 0;
                            var url = base + "/assets/platform/plugins/zoning/"+pidaspath+".json";
                            $.get(url, function (d) {
                                resolve(d);
                            }, "json");
                        }
                    }
                },
                infoForm: {
                    type: 'designer',
                    area: [],
                    areaCode: '',
                    areaText: '',
                },
// 后台返回数据时
this.$set(this.infoForm,'area',this.infoForm.areaCode.split(','))

// 前端入库数据处理
if (this.infoForm.area) {
                                        this.$set(this.infoForm, 'areaCode', this.infoForm.area.toString());
                                    }
                                    if (this.$refs['area']) {
                                        var tree = this.$refs['area'].getCheckedNodes();
                                        this.$set(this.infoForm, 'areaText', tree[0].pathLabels.toString());
                                    }
202012 月22

zoning 行政区划数据处理成vue tree格式,加上leaf属性

File[] files=Files.lsDir(new File("D://node/zoning/dist/zoning-3"),"");
        for(File dir:files){
            if(Files.isDirectory(dir)){
                File[] files2=Files.lsAll(dir.getAbsoluteFile(),"json");
                for(File file:files2){
                    System.out.println(file.getAbsolutePath());
                    System.out.println(file.getName());
                    if(file.getName().length()==9||file.getName().equalsIgnoreCase("81.json")||file.getName().equalsIgnoreCase("82.json")){
                        String str=Files.read(file);
                        List<NutMap> list=Json.fromJsonAsList(NutMap.class,str);
                        List<NutMap> list2=new ArrayList<>();
                        for(NutMap nutMap:list){
                            nutMap.addv("leaf",true);
                            list2.add(nutMap);
                        }
                        Files.write(file,Json.toJson(list2, JsonFormat.compact()));
                    }
                }
            }
        }

PS:临时用,不要纠结命名~~

202012 月16

Java实现加减乘除验证码

pom.xml

<dependency>
    <groupId>com.github.whvcse</groupId>
    <artifactId>easy-captcha</artifactId>
    <version>1.6.2</version>
</dependency>

生成验证码:

public NutMap getCode() {
        String uuid = UUID.randomUUID().toString().replace("-", "");
        ArithmeticCaptcha captcha = new ArithmeticCaptcha(120, 40);
        captcha.getArithmeticString();  // 获取运算的公式:3+2=?
        String text = captcha.text();
        redisService.setex(RedisConstant.REDIS_CAPTCHA_KEY + uuid, 180, text);
        return NutMap.NEW().addv("key", uuid).addv("codeUrl", captcha.toBase64());
    }

验证验证码:(表单传递验证码及验证码key)

public void checkCode(String key, String code) throws CaptchaException {
        String codeFromRedis = redisService.get(RedisConstant.REDIS_CAPTCHA_KEY + key);

        if (Strings.isBlank(code)) {
            throw new CaptchaException("请输入验证码");
        }
        if (Strings.isEmpty(codeFromRedis)) {
            throw new CaptchaException("验证码已过期");
        }
        if (!Strings.equalsIgnoreCase(code, codeFromRedis)) {
            throw new CaptchaException("验证码不正确");
        }
        redisService.del(RedisConstant.REDIS_SMSCODE_KEY + key);
    }


try {
      validateService.checkCode(key, code);
} catch (CaptchaException e) {
      return Result.error(e.getMessage());
}

异常类:

public class CaptchaException extends Exception{
    public CaptchaException(String message) {
        super(message);
    }
}

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

群晖NAS DS220+ 入手

前言

为何要入手群晖呢……主要动机是因上段时间,电脑Windows 10 自动更新,重启后找不到SSD硬盘,硬盘数据完全丢失,痛定思痛下血本入手了群晖NAS,哭……

在入群晖之前家里已有软路由(J1900+8G内存+1T SSD),装了爱快+LEDE双系统,做下载机使用。软路由稳定运行一年多,性能尚可,但已无做黑裙等性能升级可能性,加之了解群晖主要的卖点是各类客户端软件,也省得自己折腾了。

家庭网络拓扑图

入手

DS220+ 双盘位

2G内存+免费升级至6G内存 + 酷狼8T硬盘 优惠后4300

PS:入手后才知道有个微信朋友有渠道,从他那买可以便宜500以上,啊啊啊

一块硬盘还是觉得不太安全(一块硬盘做不了raid1啊),但8T实在太贵,只好另加一块4T做备份用,至于这么做备份,后面软件章节介绍。

软件

Moments – 私人相册,利用手机客户端同步手机相册及微信相册

Photo – 家庭相册,将精选的相片放到相册里,可以家人共享也可以分享给其他人

Drive – 核心应用,同步电脑及NAS文件夹

Video – 家庭媒体服务器,占用空间比较大,收藏了N年的电影/科幻片/动画片/周星驰/成龙/宫崎骏等

媒体服务器 – 提供DLNA服务,感觉没“网络邻居”共享文件夹来的速度快

Hyper Backup – 备份任务,将重要文件同步到第二块硬盘(没做raid1的情况下备份利器)

Cloud Sync – 同步百度云数据,百度云比较垃圾啊,又限速又经常断开要重新授权

经验

Video 视频播放没有音频的问题:

详见教程 https://wp.gxnas.com/7561.html

自动安装是不可能的啦,首先要开SSH,获取root权限,然后手动安装ffmpeg

root教程 https://wp.gxnas.com/1385.html

后面配置自动获取电影封面和介绍,也需要root权限哦

Video 电影封面及介绍下载(自动或手动)问题:

获取密钥 https://www.synology.com/zh-cn/knowledgebase/DSM/tutorial/Multimedia/How_to_apply_for_a_personal_API_key_to_get_video_info

更改hosts文件

vi /etc/hosts

31.13.70.9 api.themoviedb.org

噪音问题:

这款NAS风扇一点声音都没有,主要是硬盘读写有咯咯咯的声音,放客厅或弱电箱(靠近餐桌)都能明显听到,最后把它放阳台了,好在装修的时候阳台都预留了网口和电源,网购了一个4U机柜保护一下,防止浇花洒水等碰到。

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 官网及开发指南

20201 月17

Debian 9 安装 MariaDB root登陆不需要密码问题

CREATE USER 'budwk'@'localhost' IDENTIFIED BY '123';
GRANT ALL PRIVILEGES ON budwk_demo.* TO budwk@localhost;
FLUSH PRIVILEGES;
201911 月13

2019双11入手华为全家桶 Mate 30 Pro 5G 、FreeBuds 3 、车载无线充电

谈谈使用体验:

一、手机篇

实体店现货买的翡冷翠(本来预订青山黛,但实在等不了一个月,看有现货就拿了),6899¥什么优惠都没,就送了个透明手机壳,手机壳还是满合身的,满满的安全感。考虑屏幕那么大,还是花了299¥买了碎屏险。手机出厂自带贴膜(据说第一批出厂手机没贴膜),所以这点比较好,不用纠结贴什么膜了,上手即用。

1、系统

对于做IT的来说,上手很快,摸索一圈就知道权限如何设置、通知啊、广告怎么移除等。隔空手势用了,感觉实用性不大,试想下看着抖音,用手悬停半秒,然后隔空滑动屏幕,手抬着很累的手酸了都,还没手动滑屏比较快。

发现一个bug,或也可以说设置不合理的地方,156版本系统,短信的通知关闭后不知道如何打开。设置项不在“通知”里,也不在“应用”一级菜单里,而是需要到“应用”下面的“应用管理”里面,找到“信息”,然后再点“通知管理”,对于新手来说,跟捉迷藏一样,不百度或者问客服,真不知道。

APP的打开及使用,响应速度很快,几乎和iOS系统一样,为什么说几乎呢,是因为一些功能细节上,能感觉出比iOS系统慢那么一丢丢,比如微信支付成功的通知,可能与系统的通知机制不一样造成的,不过不影响使用,几乎察觉不出来。游戏还没安装,暂时略过。

短信拦截/手机拦截等功能太好了,每天收到几十条推销短信/电话,不用单独安装拦截软件,太爽了吧。

NFC功能也很好,8P虽然带NFC但是几乎没有用武之地,华为手机的NFC可以模拟小区门禁卡,真是很方便的。

关于提子,装了v2xxxNG,正好昨天又申请新的G服务器,搭建提子可以上x网,但是没有G套件,只能通过网页访问G的服务,还不是很方便。

壁纸和锁屏界面,可以自动更新一些精美的照片,很好。

情景智能,是自动弹出来的一个功能,开通后可以绑定手机号,通过快递查询平台查询到关联的所有快递物流信息,功能还是比较贴心的。

说几点不爽之处吧:

1)Android系统里的一些官方APP内嵌广告,这个在苹果APP里很少见;

2)各种APP各种请求权限,什么存储、位置、读取设备信息等等,一般很难判断该不该禁用,有的权限禁用了APP就启动不了了,如果都赋权,那么感觉手机像裸奔。

2、屏幕

屏幕很亮眼,逛商城的时候四五个小姐姐围着手机看,有一定的误操作的几率,可能用习惯就好了吧,毕竟拿到手还没有48小时。

屏幕分辨率,刚从8p LCD切换过来的时候还不习惯,看多了也就习惯了,给眼睛一个适应过程。

3、后置摄像头

额,怎么说呢,广角拍出来效果很好,但AI有点过度,色彩失真。近距拍摄,不是那么清晰的感觉,可能每个人体验不一样吧,个人感觉摄像头数量很多,但效果不是说的那么神(不知道为啥群里的网友拍的都那么好看?我是买到了假的么……23333)。平时拍照少,也就无所谓了。

3、前置摄像头

这个要吐槽一下了,后置摄像头有AI美化,前置摄像头一点AI和美化都没有,当镜子照啊,可以非常清晰的看到脸上的斑点/粉刺/黑头,额……

4、电量

比较给力的,比之前用的ip8p持久的多了。

二、车载无线快充电

今早上班开车20来分钟,60%电量充到80%,目测一天不用手动充电,上班下班通过车载充电就够用了啊哈哈哈。买的27W功率的那款。

三、蓝牙耳机

Freebuds 3 双11刚买,EMS 12号中午就送到了。耳机支持蓝牙5.1协议版本,Mate 30 Pro 5G 也是5.1协议,搭配起来很爽哦,正好坐在靠路边的窗口位置,试用了下降噪功能,开启主动降噪功能,路上的车水马龙声音真的消失了,,不过办公室里的说话声音是过滤不掉的。耳机不带调节音量功能 – -。

还有个买蓝牙耳机的重要原因,那就是Mate 30 系列打电话是屏幕发声,不开免提都能听到声音,所以为了隐私,打电话还是用蓝牙耳机比较好。

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

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