2020 9 月12
前言
为何要入手群晖呢……主要动机是因上段时间,电脑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机柜保护一下,防止浇花洒水等碰到。
2020 9 月12
本次 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
2020 1 月17
CREATE USER 'budwk'@'localhost' IDENTIFIED BY '123';
GRANT ALL PRIVILEGES ON budwk_demo.* TO budwk@localhost;
FLUSH PRIVILEGES;
2019 8 月2
项目介绍:
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);
非常适合小型项目快速开发;
2019 3 月19
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包、编辑配置文件、关闭实例进程、启动新实例进程、动态修改日志等级、查看服务器资源占用情况等功能,支持分布式部署。详见:发行注记 。
2019 1 月24
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等功能,目前已全面应用于各类商业项目中。
2018 7 月19
项目首页:https://github.com/Wizzercn/MqttWk
MqttWk
基于 nutzboot + t-io + redis + kafka 实现的MQTT服务broker
本项目代码主要来源于 netty/t-io/iot-mqtt-server 等众多项目,开源免费,欢迎交流学习
参考项目
使用说明
软件架构说明
使用t-io实现通信及协议解析
使用nutzboot提供依赖注入及属性配置
使用redis实现消息缓存,集群
使用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通信测试示例
功能说明
参考MQTT3.1.1规范实现
完整的QoS服务质量等级实现
遗嘱消息, 保留消息及消息分发重试
心跳机制
MQTT连接认证(可选择是否开启)
SSL方式连接(可选择是否开启)
主题过滤(支持单主题订阅如 /mqtt/test –不可以/结尾, 通配符订阅 /mqtt/# –以/#结尾)
Websocket支持(可选择是否开启)
集群功能
快速开始
下载已打包好的可运行的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-broker
的resources/keystore/server.jks
用户可以制作自己的证书, 但存储位置和文件名必须使用上述描述的位置及文件名
生成环境部署
生成环境部署建议使用keepalived+nginx+mqtt-broker
方式
使用nginx的tcp和websocket反向代理mqtt-broker集群实现负载均衡
使用keepalived实现nginx的高可用
2018 6 月9
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年多还在更新前进
2018 4 月24
//设置代理
System.setProperty("http.proxySet", "true");
System.setProperty("http.proxyHost", "127.0.0.1");
System.setProperty("http.proxyPort", "1080");
2018 2 月26
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
2018 1 月21
后台代码,自定义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>
<%} %>
2018 1 月21
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字段默认不允许排序,是单独设置数据格式
2017 9 月15
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();
}
}
2017 8 月11
/**
* 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 };
}
}
2017 7 月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);
}
}
2017 3 月23
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;
}
2017 3 月17
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>
2016 12 月8
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));
2016 11 月28
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哦)